diff --git a/src/libraries/Pool.sol b/src/libraries/Pool.sol index f69044372..b993713d2 100644 --- a/src/libraries/Pool.sol +++ b/src/libraries/Pool.sol @@ -456,17 +456,17 @@ library Pool { } /// @notice Donates the given amount of currency0 and currency1 to the pool - function donate(State storage state, uint256 amount0, uint256 amount1) internal returns (BalanceDelta delta) { - uint128 liquidity = state.liquidity; + function donate(State storage self, uint256 amount0, uint256 amount1) internal returns (BalanceDelta delta) { + uint128 liquidity = self.liquidity; if (liquidity == 0) NoLiquidityToReceiveFees.selector.revertWith(); unchecked { // negation safe as amount0 and amount1 are always positive delta = toBalanceDelta(-(amount0.toInt128()), -(amount1.toInt128())); if (amount0 > 0) { - state.feeGrowthGlobal0X128 += FullMath.mulDiv(amount0, FixedPoint128.Q128, liquidity); + self.feeGrowthGlobal0X128 += FullMath.mulDiv(amount0, FixedPoint128.Q128, liquidity); } if (amount1 > 0) { - state.feeGrowthGlobal1X128 += FullMath.mulDiv(amount1, FixedPoint128.Q128, liquidity); + self.feeGrowthGlobal1X128 += FullMath.mulDiv(amount1, FixedPoint128.Q128, liquidity); } } } diff --git a/test/ModifyLiquidity.t.sol b/test/ModifyLiquidity.t.sol index 5cf4687b5..38542c3de 100644 --- a/test/ModifyLiquidity.t.sol +++ b/test/ModifyLiquidity.t.sol @@ -150,7 +150,7 @@ contract ModifyLiquidityTest is Test, Logger, Deployers, JavascriptFfi, Fuzzers, } // assert solc/js result is at most off by 1/100th of a bip (aka one pip) - function _checkError(int128 solc, int128 js, string memory errMsg) public pure returns (int128) { + function _checkError(int128 solc, int128 js, string memory errMsg) public pure { if (solc != js) { // Ensures no div by 0 in the case of one-sided liquidity adds. (int128 gtResult, int128 ltResult) = js > solc ? (js, solc) : (solc, js); diff --git a/test/libraries/Pool.t.sol b/test/libraries/Pool.t.sol index 9117b3bbd..0e281b91f 100644 --- a/test/libraries/Pool.t.sol +++ b/test/libraries/Pool.t.sol @@ -27,7 +27,7 @@ contract PoolTest is Test, GasSnapshot { uint24 constant MAX_PROTOCOL_FEE = ProtocolFeeLibrary.MAX_PROTOCOL_FEE; // 0.1% uint24 constant MAX_LP_FEE = LPFeeLibrary.MAX_LP_FEE; // 100% - function testPoolInitialize(uint160 sqrtPriceX96, uint24 protocolFee, uint24 swapFee) public { + function test_fuzz_initialize(uint160 sqrtPriceX96, uint24 protocolFee, uint24 swapFee) public { if (sqrtPriceX96 < TickMath.MIN_SQRT_PRICE || sqrtPriceX96 >= TickMath.MAX_SQRT_PRICE) { vm.expectRevert(TickMath.InvalidSqrtPrice.selector); state.initialize(sqrtPriceX96, protocolFee, swapFee); @@ -41,7 +41,110 @@ contract PoolTest is Test, GasSnapshot { } } - function testModifyLiquidity( + function test_initialize_updatesState() public { + uint160 sqrtPriceX96 = Constants.SQRT_PRICE_1_1; + uint24 protocolFee = MAX_PROTOCOL_FEE; + uint24 lpFee = 3000; + state.initialize(sqrtPriceX96, protocolFee, lpFee); + assertEq(state.slot0.sqrtPriceX96(), sqrtPriceX96); + assertEq(state.slot0.protocolFee(), protocolFee); + assertEq(state.slot0.tick(), TickMath.getTickAtSqrtPrice(sqrtPriceX96)); + assertLt(state.slot0.tick(), TickMath.MAX_TICK); + assertGt(state.slot0.tick(), TickMath.MIN_TICK - 1); + } + + function test_initialize_revertsWithInvalidSqrtPrice_tooLow() public { + uint160 sqrtPriceX96 = TickMath.MIN_SQRT_PRICE - 1; + uint24 protocolFee = MAX_PROTOCOL_FEE; + uint24 lpFee = 3000; + vm.expectRevert(TickMath.InvalidSqrtPrice.selector); + state.initialize(sqrtPriceX96, protocolFee, lpFee); + } + + function test_initialize_revertsWithInvalidSqrtPrice_tooHigh() public { + uint160 sqrtPriceX96 = TickMath.MAX_SQRT_PRICE; + uint24 protocolFee = MAX_PROTOCOL_FEE; + uint24 lpFee = 3000; + vm.expectRevert(TickMath.InvalidSqrtPrice.selector); + state.initialize(sqrtPriceX96, protocolFee, lpFee); + } + + function test_initialize_revertsWithPoolAlreadyInitialized() public { + uint160 sqrtPriceX96 = TickMath.MIN_SQRT_PRICE; + uint24 protocolFee = MAX_PROTOCOL_FEE; + uint24 lpFee = 3000; + state.initialize(sqrtPriceX96, protocolFee, lpFee); + vm.expectRevert(Pool.PoolAlreadyInitialized.selector); + state.initialize(sqrtPriceX96, protocolFee, lpFee); + } + + function test_fuzz_setProtocolFee( + uint160 sqrtPriceX96, + uint24 protocolFee, + uint24 lpFee, + uint24 newProtocolFee, + bool initialized + ) public { + if (initialized) { + test_fuzz_initialize(sqrtPriceX96, protocolFee, lpFee); + state.setProtocolFee(newProtocolFee); + assertEq(state.slot0.protocolFee(), newProtocolFee); + } else { + vm.expectRevert(Pool.PoolNotInitialized.selector); + state.setProtocolFee(newProtocolFee); + } + } + + function test_setProtocolFee_setsCorrectFee() public { + uint160 sqrtPriceX96 = TickMath.MIN_SQRT_PRICE; + uint24 protocolFee = MAX_PROTOCOL_FEE; + uint24 lpFee = 3000; + uint24 newProtocolFee = 5000; + test_fuzz_initialize(sqrtPriceX96, protocolFee, lpFee); + state.setProtocolFee(newProtocolFee); + assertEq(state.slot0.protocolFee(), newProtocolFee); + } + + function test_setProtocolFee_revertsWithPoolNotInitialized() public { + uint24 newProtocolFee = 5000; + vm.expectRevert(Pool.PoolNotInitialized.selector); + state.setProtocolFee(newProtocolFee); + } + + function test_fuzz_setLpFee( + uint160 sqrtPriceX96, + uint24 protocolFee, + uint24 lpFee, + uint24 newLpFee, + bool initialized + ) public { + if (initialized) { + test_fuzz_initialize(sqrtPriceX96, protocolFee, lpFee); + state.setLPFee(newLpFee); + assertEq(state.slot0.lpFee(), newLpFee); + } else { + vm.expectRevert(Pool.PoolNotInitialized.selector); + state.setLPFee(newLpFee); + } + } + + function test_setLPFee_succeedsWithNewFee() public { + uint160 sqrtPriceX96 = TickMath.MIN_SQRT_PRICE; + uint24 protocolFee = MAX_PROTOCOL_FEE; + uint24 lpFee = 3000; + uint24 newLpFee = 5000; + test_fuzz_initialize(sqrtPriceX96, protocolFee, lpFee); + state.setLPFee(newLpFee); + assertEq(state.slot0.lpFee(), newLpFee); + } + + function test_setLPFee_revertsWithPoolNotInitialized() public { + uint24 newLpFee = 5000; + vm.expectRevert(Pool.PoolNotInitialized.selector); + state.setLPFee(newLpFee); + } + + function test_fuzz_modifyLiquidity( uint160 sqrtPriceX96, uint24 protocolFee, uint24 lpFee, @@ -49,9 +152,11 @@ contract PoolTest is Test, GasSnapshot { ) public { // Assumptions tested in PoolManager.t.sol params.tickSpacing = int24(bound(params.tickSpacing, TickMath.MIN_TICK_SPACING, TickMath.MAX_TICK_SPACING)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); - testPoolInitialize(sqrtPriceX96, protocolFee, lpFee); + test_fuzz_initialize(sqrtPriceX96, protocolFee, lpFee); + // currently an empty position if (params.tickLower >= params.tickUpper) { vm.expectRevert(abi.encodeWithSelector(Pool.TicksMisordered.selector, params.tickLower, params.tickUpper)); } else if (params.tickLower < TickMath.MIN_TICK) { @@ -59,22 +164,27 @@ contract PoolTest is Test, GasSnapshot { } else if (params.tickUpper > TickMath.MAX_TICK) { vm.expectRevert(abi.encodeWithSelector(Pool.TickUpperOutOfBounds.selector, params.tickUpper)); } else if (params.liquidityDelta < 0) { + // can't remove liquidity before adding it vm.expectRevert(SafeCast.SafeCastOverflow.selector); } else if (params.liquidityDelta == 0) { + // b/c position is empty vm.expectRevert(Position.CannotUpdateEmptyPosition.selector); } else if (params.liquidityDelta > int128(Pool.tickSpacingToMaxLiquidityPerTick(params.tickSpacing))) { + // b/c liquidity before starts at 0 vm.expectRevert(abi.encodeWithSelector(Pool.TickLiquidityOverflow.selector, params.tickLower)); } else if (params.tickLower % params.tickSpacing != 0) { + // since tick will always be flipped first time vm.expectRevert( abi.encodeWithSelector(TickBitmap.TickMisaligned.selector, params.tickLower, params.tickSpacing) ); } else if (params.tickUpper % params.tickSpacing != 0) { + // since tick will always be flipped first time vm.expectRevert( abi.encodeWithSelector(TickBitmap.TickMisaligned.selector, params.tickUpper, params.tickSpacing) ); } else { // We need the assumptions above to calculate this - uint256 maxInt128InTypeU256 = uint256(uint128(Constants.MAX_UINT128)); + uint256 maxInt128InTypeUint256 = uint256(uint128(type(int128).max)); (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity( sqrtPriceX96, TickMath.getSqrtPriceAtTick(params.tickLower), @@ -82,7 +192,8 @@ contract PoolTest is Test, GasSnapshot { uint128(params.liquidityDelta) ); - if ((amount0 > maxInt128InTypeU256) || (amount1 > maxInt128InTypeU256)) { + // fuzz test not checking this + if ((amount0 > maxInt128InTypeUint256) || (amount1 > maxInt128InTypeUint256)) { vm.expectRevert(abi.encodeWithSelector(SafeCast.SafeCastOverflow.selector)); } } @@ -91,31 +202,84 @@ contract PoolTest is Test, GasSnapshot { state.modifyLiquidity(params); } - function testSwap( + function test_modifyLiquidity_revertsWithTickLiquidityOverflow_lower() public { + uint160 sqrtPriceX96 = Constants.SQRT_PRICE_1_1; + uint24 protocolFee = MAX_PROTOCOL_FEE; + uint24 lpFee = 3000; + state.initialize(sqrtPriceX96, protocolFee, lpFee); + Pool.ModifyLiquidityParams memory params = Pool.ModifyLiquidityParams({ + owner: address(this), + tickLower: -120, + tickUpper: 120, + liquidityDelta: 1e18, + tickSpacing: 60, + salt: 0 + }); + state.ticks[-120] = Pool.TickInfo({ + liquidityGross: type(uint128).max - uint128(params.liquidityDelta), + liquidityNet: 0, + feeGrowthOutside0X128: 0, + feeGrowthOutside1X128: 0 + }); + vm.expectRevert(abi.encodeWithSelector(Pool.TickLiquidityOverflow.selector, -120)); + state.modifyLiquidity(params); + } + + function test_modifyLiquidity_revertsWithTickLiquidityOverflow_upper() public { + uint160 sqrtPriceX96 = Constants.SQRT_PRICE_1_1; + uint24 protocolFee = MAX_PROTOCOL_FEE; + uint24 lpFee = 3000; + state.initialize(sqrtPriceX96, protocolFee, lpFee); + Pool.ModifyLiquidityParams memory params = Pool.ModifyLiquidityParams({ + owner: address(this), + tickLower: -120, + tickUpper: 120, + liquidityDelta: 1e18, + tickSpacing: 60, + salt: 0 + }); + state.ticks[120] = Pool.TickInfo({ + liquidityGross: type(uint128).max - uint128(params.liquidityDelta), + liquidityNet: 0, + feeGrowthOutside0X128: 0, + feeGrowthOutside1X128: 0 + }); + vm.expectRevert(abi.encodeWithSelector(Pool.TickLiquidityOverflow.selector, 120)); + state.modifyLiquidity(params); + } + + function test_fuzz_swap( uint160 sqrtPriceX96, uint24 lpFee, uint16 protocolFee0, uint16 protocolFee1, - Pool.SwapParams memory params + Pool.SwapParams memory params, + int24 tickLower, + int24 tickUpper, + int128 liquidityDelta ) public { // Assumptions tested in PoolManager.t.sol params.tickSpacing = int24(bound(params.tickSpacing, TickMath.MIN_TICK_SPACING, TickMath.MAX_TICK_SPACING)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); lpFee = uint24(bound(lpFee, 0, MAX_LP_FEE)); protocolFee0 = uint16(bound(protocolFee0, 0, MAX_PROTOCOL_FEE)); protocolFee1 = uint16(bound(protocolFee1, 0, MAX_PROTOCOL_FEE)); uint24 protocolFee = protocolFee1 << 12 | protocolFee0; + liquidityDelta = int128(bound(liquidityDelta, 1, type(int128).max)); + + vm.assume(tickLower < tickUpper); // initialize and add liquidity - testModifyLiquidity( + test_fuzz_modifyLiquidity( sqrtPriceX96, protocolFee, lpFee, Pool.ModifyLiquidityParams({ owner: address(this), - tickLower: -120, - tickUpper: 120, - liquidityDelta: 1e18, - tickSpacing: 60, + tickLower: tickLower, + tickUpper: tickUpper, + liquidityDelta: liquidityDelta, + tickSpacing: params.tickSpacing, salt: 0 }) ); @@ -126,10 +290,8 @@ contract PoolTest is Test, GasSnapshot { if (params.amountSpecified >= 0 && swapFee == MAX_LP_FEE) { vm.expectRevert(Pool.InvalidFeeForExactOut.selector); - state.swap(params); } else if (!swapFee.isValid()) { vm.expectRevert(LPFeeLibrary.FeeTooLarge.selector); - state.swap(params); } else if (params.zeroForOne && params.amountSpecified != 0) { if (params.sqrtPriceLimitX96 >= slot0.sqrtPriceX96()) { vm.expectRevert( @@ -137,10 +299,8 @@ contract PoolTest is Test, GasSnapshot { Pool.PriceLimitAlreadyExceeded.selector, slot0.sqrtPriceX96(), params.sqrtPriceLimitX96 ) ); - state.swap(params); } else if (params.sqrtPriceLimitX96 <= TickMath.MIN_SQRT_PRICE) { vm.expectRevert(abi.encodeWithSelector(Pool.PriceLimitOutOfBounds.selector, params.sqrtPriceLimitX96)); - state.swap(params); } } else if (!params.zeroForOne && params.amountSpecified != 0) { if (params.sqrtPriceLimitX96 <= slot0.sqrtPriceX96()) { @@ -149,25 +309,279 @@ contract PoolTest is Test, GasSnapshot { Pool.PriceLimitAlreadyExceeded.selector, slot0.sqrtPriceX96(), params.sqrtPriceLimitX96 ) ); - state.swap(params); } else if (params.sqrtPriceLimitX96 >= TickMath.MAX_SQRT_PRICE) { vm.expectRevert(abi.encodeWithSelector(Pool.PriceLimitOutOfBounds.selector, params.sqrtPriceLimitX96)); - state.swap(params); } + } + + uint160 sqrtPriceBefore = state.slot0.sqrtPriceX96(); + state.swap(params); + + if (params.amountSpecified == 0) { + assertEq(sqrtPriceBefore, state.slot0.sqrtPriceX96(), "amountSpecified == 0"); + } else if (params.zeroForOne) { + // fuzz test not checking this, checking in unit test: test_swap_zeroForOne_priceGreaterThanOrEqualToLimit + assertGe(state.slot0.sqrtPriceX96(), params.sqrtPriceLimitX96, "zeroForOne"); } else { - uint160 sqrtPriceBefore = state.slot0.sqrtPriceX96(); - state.swap(params); - - if (params.amountSpecified == 0) { - assertEq(sqrtPriceBefore, state.slot0.sqrtPriceX96(), "amountSpecified == 0"); - } else if (params.zeroForOne) { - assertGe(state.slot0.sqrtPriceX96(), params.sqrtPriceLimitX96, "zeroForOne"); - } else { - assertLe(state.slot0.sqrtPriceX96(), params.sqrtPriceLimitX96, "oneForZero"); - } + // fuzz test not checking this, checking in unit test: test_swap_oneForZero_priceLessThanOrEqualToLimit + assertLe(state.slot0.sqrtPriceX96(), params.sqrtPriceLimitX96, "oneForZero"); } } + function test_swap_withProtocolFee() public { + // not sure why line 380 is not being covered when this test has a protocol fee of over 1000 + uint160 sqrtPriceX96 = Constants.SQRT_PRICE_1_1; + uint24 protocolFee = 1000; + uint24 lpFee = 0; + Pool.SwapParams memory params = Pool.SwapParams({ + tickSpacing: TickMath.MIN_TICK_SPACING, + zeroForOne: true, + amountSpecified: 2459, + sqrtPriceLimitX96: Constants.SQRT_PRICE_1_2, + lpFeeOverride: 0 + }); + + // initialize and add liquidity + test_fuzz_modifyLiquidity( + sqrtPriceX96, + protocolFee, + lpFee, + Pool.ModifyLiquidityParams({ + owner: address(this), + tickLower: -120, + tickUpper: 120, + liquidityDelta: 1e18, + tickSpacing: 60, + salt: 0 + }) + ); + + state.swap(params); + } + + function test_swap_revertsWithPriceLimitAlreadyExceeded_zeroForOne() public { + uint160 sqrtPriceX96 = Constants.SQRT_PRICE_1_1; + uint24 protocolFee = 0; + uint24 lpFee = 0; + Pool.SwapParams memory params = Pool.SwapParams({ + tickSpacing: TickMath.MIN_TICK_SPACING, + zeroForOne: true, + amountSpecified: 2459, + sqrtPriceLimitX96: Constants.SQRT_PRICE_1_1, + lpFeeOverride: 0 + }); + params.tickSpacing = int24(bound(params.tickSpacing, TickMath.MIN_TICK_SPACING, TickMath.MAX_TICK_SPACING)); + + // initialize and add liquidity + test_fuzz_modifyLiquidity( + sqrtPriceX96, + protocolFee, + lpFee, + Pool.ModifyLiquidityParams({ + owner: address(this), + tickLower: -120, + tickUpper: 120, + liquidityDelta: 1e18, + tickSpacing: 60, + salt: 0 + }) + ); + + vm.expectRevert( + abi.encodeWithSelector( + Pool.PriceLimitAlreadyExceeded.selector, state.slot0.sqrtPriceX96(), params.sqrtPriceLimitX96 + ) + ); + state.swap(params); + } + + function test_swap_revertsWithPriceLimitOutOfBounds_zeroForOne() public { + uint160 sqrtPriceX96 = Constants.SQRT_PRICE_1_1; + uint24 protocolFee = 0; + uint24 lpFee = 0; + Pool.SwapParams memory params = Pool.SwapParams({ + tickSpacing: TickMath.MIN_TICK_SPACING, + zeroForOne: true, + amountSpecified: 2459, + sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE, + lpFeeOverride: 0 + }); + params.tickSpacing = int24(bound(params.tickSpacing, TickMath.MIN_TICK_SPACING, TickMath.MAX_TICK_SPACING)); + + // initialize and add liquidity + test_fuzz_modifyLiquidity( + sqrtPriceX96, + protocolFee, + lpFee, + Pool.ModifyLiquidityParams({ + owner: address(this), + tickLower: -120, + tickUpper: 120, + liquidityDelta: 1e18, + tickSpacing: 60, + salt: 0 + }) + ); + + vm.expectRevert(abi.encodeWithSelector(Pool.PriceLimitOutOfBounds.selector, params.sqrtPriceLimitX96)); + state.swap(params); + } + + function test_swap_revertsWithPriceLimitAlreadyExceeded_oneForZero() public { + uint160 sqrtPriceX96 = Constants.SQRT_PRICE_1_1; + uint24 protocolFee = 0; + uint24 lpFee = 0; + Pool.SwapParams memory params = Pool.SwapParams({ + tickSpacing: TickMath.MIN_TICK_SPACING, + zeroForOne: false, + amountSpecified: 2459, + sqrtPriceLimitX96: Constants.SQRT_PRICE_1_1 - 1, + lpFeeOverride: 0 + }); + params.tickSpacing = int24(bound(params.tickSpacing, TickMath.MIN_TICK_SPACING, TickMath.MAX_TICK_SPACING)); + + // initialize and add liquidity + test_fuzz_modifyLiquidity( + sqrtPriceX96, + protocolFee, + lpFee, + Pool.ModifyLiquidityParams({ + owner: address(this), + tickLower: -120, + tickUpper: 120, + liquidityDelta: 1e18, + tickSpacing: 60, + salt: 0 + }) + ); + + vm.expectRevert( + abi.encodeWithSelector( + Pool.PriceLimitAlreadyExceeded.selector, state.slot0.sqrtPriceX96(), params.sqrtPriceLimitX96 + ) + ); + state.swap(params); + } + + function test_swap_revertsWithPriceLimitOutOfBounds_oneForZero() public { + uint160 sqrtPriceX96 = Constants.SQRT_PRICE_1_1; + uint24 protocolFee = 0; + uint24 lpFee = 0; + Pool.SwapParams memory params = Pool.SwapParams({ + tickSpacing: TickMath.MIN_TICK_SPACING, + zeroForOne: false, + amountSpecified: 2459, + sqrtPriceLimitX96: TickMath.MAX_SQRT_PRICE, + lpFeeOverride: 0 + }); + params.tickSpacing = int24(bound(params.tickSpacing, TickMath.MIN_TICK_SPACING, TickMath.MAX_TICK_SPACING)); + + // initialize and add liquidity + test_fuzz_modifyLiquidity( + sqrtPriceX96, + protocolFee, + lpFee, + Pool.ModifyLiquidityParams({ + owner: address(this), + tickLower: -120, + tickUpper: 120, + liquidityDelta: 1e18, + tickSpacing: 60, + salt: 0 + }) + ); + + vm.expectRevert(abi.encodeWithSelector(Pool.PriceLimitOutOfBounds.selector, params.sqrtPriceLimitX96)); + state.swap(params); + } + + function test_swap_oneForZero_priceLessThanOrEqualToLimit() public { + uint160 sqrtPriceX96 = Constants.SQRT_PRICE_1_1; + uint24 protocolFee = 0; + uint24 lpFee = 0; + Pool.SwapParams memory params = Pool.SwapParams({ + tickSpacing: -2448282, + zeroForOne: false, + amountSpecified: 2459, + sqrtPriceLimitX96: Constants.SQRT_PRICE_2_1, + lpFeeOverride: 0 + }); + params.tickSpacing = int24(bound(params.tickSpacing, TickMath.MIN_TICK_SPACING, TickMath.MAX_TICK_SPACING)); + + // initialize and add liquidity + test_fuzz_modifyLiquidity( + sqrtPriceX96, + protocolFee, + lpFee, + Pool.ModifyLiquidityParams({ + owner: address(this), + tickLower: -120, + tickUpper: 120, + liquidityDelta: 1e18, + tickSpacing: 60, + salt: 0 + }) + ); + + state.swap(params); + + assertLe(state.slot0.sqrtPriceX96(), params.sqrtPriceLimitX96, "oneForZero"); + } + + function test_swap_zeroForOne_priceGreaterThanOrEqualToLimit() public { + uint160 sqrtPriceX96 = Constants.SQRT_PRICE_1_1; + uint24 protocolFee = 0; + uint24 lpFee = 0; + Pool.SwapParams memory params = Pool.SwapParams({ + tickSpacing: -2448282, + zeroForOne: true, + amountSpecified: 2459, + sqrtPriceLimitX96: Constants.SQRT_PRICE_1_2, + lpFeeOverride: 0 + }); + params.tickSpacing = int24(bound(params.tickSpacing, TickMath.MIN_TICK_SPACING, TickMath.MAX_TICK_SPACING)); + + // initialize and add liquidity + test_fuzz_modifyLiquidity( + sqrtPriceX96, + protocolFee, + lpFee, + Pool.ModifyLiquidityParams({ + owner: address(this), + tickLower: -120, + tickUpper: 120, + liquidityDelta: 1e18, + tickSpacing: 60, + salt: 0 + }) + ); + + state.swap(params); + + assertGe(state.slot0.sqrtPriceX96(), params.sqrtPriceLimitX96, "zeroForOne"); + } + + function test_fuzz_donate( + uint160 sqrtPriceX96, + Pool.ModifyLiquidityParams memory params, + uint24 lpFee, + uint24 protocolFee, + uint256 amount0, + uint256 amount1 + ) public { + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); + params.tickUpper = int24(bound(params.tickUpper, TickMath.getTickAtSqrtPrice(sqrtPriceX96), TickMath.MAX_TICK)); + params.tickLower = int24(bound(params.tickLower, TickMath.MIN_TICK, TickMath.getTickAtSqrtPrice(sqrtPriceX96))); + amount0 = bound(amount0, 0, uint256(int256(type(int128).max))); + amount1 = bound(amount1, 0, uint256(int256(type(int128).max))); + + vm.expectRevert(Pool.NoLiquidityToReceiveFees.selector); + state.donate(amount0, amount1); + + test_fuzz_modifyLiquidity(sqrtPriceX96, protocolFee, lpFee, params); + state.donate(amount0, amount1); + } + function test_fuzz_tickSpacingToMaxLiquidityPerTick(int24 tickSpacing) public pure { tickSpacing = int24(bound(tickSpacing, TickMath.MIN_TICK_SPACING, TickMath.MAX_TICK_SPACING)); // v3 math diff --git a/test/utils/Logger.sol b/test/utils/Logger.sol index 7a24ac30c..404d96705 100644 --- a/test/utils/Logger.sol +++ b/test/utils/Logger.sol @@ -7,7 +7,7 @@ import "forge-std/console2.sol"; // Useful for printing out the true values in a fuzz. For failing fuzzes, foundry logs the unsanitized params. contract Logger { - function logParams(IPoolManager.ModifyLiquidityParams memory params) public { + function logParams(IPoolManager.ModifyLiquidityParams memory params) public pure { console2.log("ModifyLiquidity.tickLower", params.tickLower); console2.log("ModifyLiquidity.tickUpper", params.tickUpper); console2.log("ModifyLiquidity.liquidityDelta", params.liquidityDelta);