diff --git a/.forge-snapshots/PositionManager_burn_empty.snap b/.forge-snapshots/PositionManager_burn_empty.snap index 10da4419..492cae30 100644 --- a/.forge-snapshots/PositionManager_burn_empty.snap +++ b/.forge-snapshots/PositionManager_burn_empty.snap @@ -1 +1 @@ -47170 \ No newline at end of file +47167 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty_native.snap b/.forge-snapshots/PositionManager_burn_empty_native.snap index 7cf4184e..6ad16fc4 100644 --- a/.forge-snapshots/PositionManager_burn_empty_native.snap +++ b/.forge-snapshots/PositionManager_burn_empty_native.snap @@ -1 +1 @@ -46988 \ No newline at end of file +46984 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap index 0c46e3d1..d9403287 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap @@ -1 +1 @@ -123226 \ No newline at end of file +123586 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap index 4daa5df5..003ba99a 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap @@ -1 +1 @@ -122733 \ No newline at end of file +123093 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap index 4e8dd523..02edc4f7 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap @@ -1 +1 @@ -130304 \ No newline at end of file +130664 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap index 695f0ba1..108d06a6 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap @@ -1 +1 @@ -129812 \ No newline at end of file +130172 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index a29149e6..1d9f0b2e 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -141698 \ No newline at end of file +142147 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index d0c0d21a..a81b3e19 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -150546 \ No newline at end of file +150995 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withClose.snap b/.forge-snapshots/PositionManager_collect_withClose.snap index d0c0d21a..a81b3e19 100644 --- a/.forge-snapshots/PositionManager_collect_withClose.snap +++ b/.forge-snapshots/PositionManager_collect_withClose.snap @@ -1 +1 @@ -150546 \ No newline at end of file +150995 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withTakePair.snap b/.forge-snapshots/PositionManager_collect_withTakePair.snap index 6654e101..f1001220 100644 --- a/.forge-snapshots/PositionManager_collect_withTakePair.snap +++ b/.forge-snapshots/PositionManager_collect_withTakePair.snap @@ -1 +1 @@ -149918 \ No newline at end of file +150367 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index bc5857fa..624e0da8 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -108833 \ No newline at end of file +109192 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap index d4154674..dbe6c306 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap @@ -1 +1 @@ -116089 \ No newline at end of file +116538 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap index cfe262e2..d421dfa0 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap @@ -1 +1 @@ -115461 \ No newline at end of file +115910 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index bf8a89f1..1f43cf81 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -134384 \ No newline at end of file +134740 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap index 64fa2092..b4eabb29 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -127123 \ No newline at end of file +127479 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap index 3df0c73c..062922cd 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -128805 \ No newline at end of file +129254 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_take_take.snap b/.forge-snapshots/PositionManager_decrease_take_take.snap index afeb3089..56793a69 100644 --- a/.forge-snapshots/PositionManager_decrease_take_take.snap +++ b/.forge-snapshots/PositionManager_decrease_take_take.snap @@ -1 +1 @@ -116622 \ No newline at end of file +117071 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap index b3a45767..f54d79a2 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap @@ -1 +1 @@ -154942 \ No newline at end of file +155245 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap index 6423f6ee..9a32f688 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -1 +1 @@ -153944 \ No newline at end of file +154247 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index 9aed0319..61c51099 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -136742 \ No newline at end of file +137045 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index d1ccfa88..b499b44a 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -132841 \ No newline at end of file +133390 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index 55dddada..5c2d6ce7 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -173757 \ No newline at end of file +174306 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index 9bac5046..0aff3df0 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -143713 \ No newline at end of file +144262 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index b9627da9..ec5fa61b 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -340639 \ No newline at end of file +341062 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap index 5c77d4aa..0eb07673 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -349131 \ No newline at end of file +349554 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap index aa3817d2..7cc33796 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -348433 \ No newline at end of file +348856 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index 2d86e061..dd01e09b 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -318621 \ No newline at end of file +319044 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index ff727a45..ecb6919a 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -319263 \ No newline at end of file +319686 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index d8c6c3a6..4b788a7b 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -244845 \ No newline at end of file +245268 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 99790fc7..04ed7a3f 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -374663 \ No newline at end of file +375086 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index 780d8a42..8ab21745 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -324639 \ No newline at end of file +325062 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index 8dab44ba..2c29315b 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -375939 \ No newline at end of file +376362 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index 6a830e87..a151e59d 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -375079 \ No newline at end of file +375502 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 5a4056a9..ddaa376d 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -420413 \ No newline at end of file +420836 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index d76b08ff..d56f30aa 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -217,8 +217,10 @@ contract PositionManager is bytes calldata hookData ) internal onlyIfApproved(msgSender(), tokenId) onlyValidConfig(tokenId, config) { // Note: The tokenId is used as the salt for this position, so every minted position has unique storage in the pool manager. - BalanceDelta liquidityDelta = _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData); - liquidityDelta.validateMaxInNegative(amount0Max, amount1Max); + (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) = + _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData); + // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued + (liquidityDelta - feesAccrued).validateMaxIn(amount0Max, amount1Max); } /// @dev Calling decrease with 0 liquidity will credit the caller with any underlying fees of the position @@ -231,8 +233,10 @@ contract PositionManager is bytes calldata hookData ) internal onlyIfApproved(msgSender(), tokenId) onlyValidConfig(tokenId, config) { // Note: the tokenId is used as the salt. - BalanceDelta liquidityDelta = _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData); - liquidityDelta.validateMinOut(amount0Min, amount1Min); + (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) = + _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData); + // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued + (liquidityDelta - feesAccrued).validateMinOut(amount0Min, amount1Min); } function _mint( @@ -252,8 +256,10 @@ contract PositionManager is _mint(owner, tokenId); // _beforeModify is not called here because the tokenId is newly minted - BalanceDelta liquidityDelta = _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData); - liquidityDelta.validateMaxIn(amount0Max, amount1Max); + (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) = + _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData); + // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued + (liquidityDelta - feesAccrued).validateMaxIn(amount0Max, amount1Max); positionConfigs.setConfigId(tokenId, config); emit MintPosition(tokenId, config); @@ -269,11 +275,12 @@ contract PositionManager is ) internal onlyIfApproved(msgSender(), tokenId) onlyValidConfig(tokenId, config) { uint256 liquidity = uint256(getPositionLiquidity(tokenId, config)); - BalanceDelta liquidityDelta; // Can only call modify if there is non zero liquidity. if (liquidity > 0) { - liquidityDelta = _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData); - liquidityDelta.validateMinOut(amount0Min, amount1Min); + (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) = + _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData); + // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued + (liquidityDelta - feesAccrued).validateMinOut(amount0Min, amount1Min); } delete positionConfigs[tokenId]; @@ -332,8 +339,7 @@ contract PositionManager is int256 liquidityChange, bytes32 salt, bytes calldata hookData - ) internal returns (BalanceDelta liquidityDelta) { - BalanceDelta feesAccrued; + ) internal returns (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) { (liquidityDelta, feesAccrued) = poolManager.modifyLiquidity( config.poolKey, IPoolManager.ModifyLiquidityParams({ diff --git a/src/libraries/SlippageCheck.sol b/src/libraries/SlippageCheck.sol index efeafca9..00d9d23b 100644 --- a/src/libraries/SlippageCheck.sol +++ b/src/libraries/SlippageCheck.sol @@ -2,33 +2,42 @@ pragma solidity ^0.8.0; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {SafeCastTemp} from "./SafeCast.sol"; /// @title Slippage Check Library /// @notice a library for checking if a delta exceeds a maximum ceiling or fails to meet a minimum floor library SlippageCheckLibrary { + using SafeCastTemp for int128; + error MaximumAmountExceeded(); error MinimumAmountInsufficient(); /// @notice Revert if one or both deltas does not meet a minimum output - /// @dev to be used when removing liquidity to guarantee a minimum output + /// @param delta The principal amount of tokens to be removed, does not include any fees accrued + /// @param amount0Min The minimum amount of token0 to receive + /// @param amount1Min The minimum amount of token1 to receive + /// @dev This should be called when removing liquidity (burn or decrease) function validateMinOut(BalanceDelta delta, uint128 amount0Min, uint128 amount1Min) internal pure { - if (uint128(delta.amount0()) < amount0Min || uint128(delta.amount1()) < amount1Min) { + // Called on burn or decrease, where we expect the returned delta to be positive. + // However, on pools where hooks can return deltas on modify liquidity, it is possible for a returned delta to be negative. + // Because we use SafeCast, this will revert in those cases when the delta is negative. + // This means this contract will NOT support pools where the hook returns a negative delta on burn/decrease. + if (delta.amount0().toUint128() < amount0Min || delta.amount1().toUint128() < amount1Min) { revert MinimumAmountInsufficient(); } } /// @notice Revert if one or both deltas exceeds a maximum input - /// @dev to be used when minting liquidity to guarantee a maximum input + /// @param delta The principal amount of tokens to be added, does not include any fees accrued (which is possible on increase) + /// @param amount0Max The maximum amount of token0 to spend + /// @param amount1Max The maximum amount of token1 to spend + /// @dev This should be called when adding liquidity (mint or increase) function validateMaxIn(BalanceDelta delta, uint128 amount0Max, uint128 amount1Max) internal pure { - if (uint128(-delta.amount0()) > amount0Max || uint128(-delta.amount1()) > amount1Max) { - revert MaximumAmountExceeded(); - } - } - - /// @notice Revert if one or both deltas exceeds a maximum input - /// @dev When increasing liquidity, delta can be positive when excess fees need to be collected - /// in those cases, slippage checks are not required - function validateMaxInNegative(BalanceDelta delta, uint128 amount0Max, uint128 amount1Max) internal pure { + // Called on mint or increase, where we expect the returned delta to be negative. + // However, on pools where hooks can return deltas on modify liquidity, it is possible for a returned delta to be positive (even after discounting fees accrued). + // Thus, we only cast the delta if it is guaranteed to be negative. + // And we do NOT revert in the positive delta case. Since a positive delta means the hook is crediting tokens to the user for minting/increasing liquidity, we do not check slippage. + // This means this contract will NOT support _positive_ slippage checks (minAmountOut checks) on pools where the hook returns a positive delta on mint/increase. if ( delta.amount0() < 0 && amount0Max < uint128(-delta.amount0()) || delta.amount1() < 0 && amount1Max < uint128(-delta.amount1()) diff --git a/test/position-managers/IncreaseLiquidity.t.sol b/test/position-managers/IncreaseLiquidity.t.sol index e9aa4946..1feef6bc 100644 --- a/test/position-managers/IncreaseLiquidity.t.sol +++ b/test/position-managers/IncreaseLiquidity.t.sol @@ -75,6 +75,75 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { config = PositionConfig({poolKey: key, tickLower: -300, tickUpper: 300}); } + /// @notice Increase liquidity by less than the amount of liquidity the position has earned, requiring a take + function test_increaseLiquidity_withCollection_takePair() public { + // Alice and Bob provide liquidity on the range + // Alice uses her exact fees to increase liquidity (compounding) + + uint256 liquidityAlice = 3_000e18; + uint256 liquidityBob = 1_000e18; + + // alice provides liquidity + vm.startPrank(alice); + uint256 tokenIdAlice = lpm.nextTokenId(); + mint(config, liquidityAlice, alice, ZERO_BYTES); + vm.stopPrank(); + + // bob provides liquidity + vm.startPrank(bob); + mint(config, liquidityBob, bob, ZERO_BYTES); + vm.stopPrank(); + + // donate to create fees + uint256 amountDonate = 0.1e18; + donateRouter.donate(key, amountDonate, amountDonate, ZERO_BYTES); + + // alice uses her half her fees to increase liquidity + // Slight error in this calculation vs. actual fees.. TODO: Fix this. + BalanceDelta feesOwedAlice = IPositionManager(lpm).getFeesOwed(manager, config, tokenIdAlice); + // Note: You can alternatively calculate Alice's fees owed from the swap amount, fee on the pool, and total liquidity in that range. + // swapAmount.mulWadDown(FEE_WAD).mulDivDown(liquidityAlice, liquidityAlice + liquidityBob); + + (uint160 sqrtPriceX96,,,) = StateLibrary.getSlot0(manager, config.poolKey.toId()); + uint256 liquidityDelta = LiquidityAmounts.getLiquidityForAmounts( + sqrtPriceX96, + TickMath.getSqrtPriceAtTick(config.tickLower), + TickMath.getSqrtPriceAtTick(config.tickUpper), + uint256(int256(feesOwedAlice.amount0() / 2)), + uint256(int256(feesOwedAlice.amount1() / 2)) + ); + + uint256 balance0BeforeAlice = currency0.balanceOf(alice); + uint256 balance1BeforeAlice = currency1.balanceOf(alice); + + // Set the slippage amounts to be exactly half the fees that alice is reinvesting. + + Plan memory planner = Planner.init(); + planner.add( + Actions.INCREASE_LIQUIDITY, + abi.encode( + tokenIdAlice, + config, + liquidityDelta, + feesOwedAlice.amount0() / 2, + feesOwedAlice.amount1() / 2, + ZERO_BYTES + ) + ); + bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(alice)); + vm.startPrank(alice); + lpm.modifyLiquidities(calls, _deadline); + vm.stopPrank(); + + // alices current balance is the balanceBefore plus half of her fees owed + assertApproxEqAbs( + currency0.balanceOf(alice), balance0BeforeAlice + uint256(int256(feesOwedAlice.amount0() / 2)), tolerance + ); + assertApproxEqAbs( + currency1.balanceOf(alice), balance1BeforeAlice + uint256(int256(feesOwedAlice.amount1() / 2)), tolerance + ); + } + /// @notice Increase liquidity with exact fees, taking dust function test_increaseLiquidity_withExactFees_take() public { // Alice and Bob provide liquidity on the range @@ -119,7 +188,10 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Plan memory planner = Planner.init(); planner.add( - Actions.INCREASE_LIQUIDITY, abi.encode(tokenIdAlice, config, liquidityDelta, 0 wei, 0 wei, ZERO_BYTES) + Actions.INCREASE_LIQUIDITY, + abi.encode( + tokenIdAlice, config, liquidityDelta, feesOwedAlice.amount0(), feesOwedAlice.amount1(), ZERO_BYTES + ) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); vm.startPrank(alice);