Files

498 lines
23 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import {IHooks} from "../../src/interfaces/IHooks.sol";
import {Hooks} from "../../src/libraries/Hooks.sol";
import {TickMath} from "../../src/libraries/TickMath.sol";
import {IPoolManager} from "../../src/interfaces/IPoolManager.sol";
import {ModifyLiquidityParams} from "../../src/types/PoolOperation.sol";
import {PoolKey} from "../../src/types/PoolKey.sol";
import {BalanceDelta} from "../../src/types/BalanceDelta.sol";
import {PoolId} from "../../src/types/PoolId.sol";
import {Currency} from "../../src/types/Currency.sol";
import {Deployers} from "../utils/Deployers.sol";
import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol";
import {Pool} from "../../src/libraries/Pool.sol";
import {TickBitmap} from "../../src/libraries/TickBitmap.sol";
import {FixedPoint128} from "../../src/libraries/FixedPoint128.sol";
import {StateLibrary} from "../../src/libraries/StateLibrary.sol";
import {Fuzzers} from "../../src/test/Fuzzers.sol";
contract StateLibraryTest is Test, Deployers, Fuzzers {
using FixedPointMathLib for uint256;
PoolId poolId;
function setUp() public {
// creates the pool manager, utility routers, and test tokens
Deployers.deployFreshManagerAndRouters();
(currency0, currency1) = Deployers.deployMintAndApprove2Currencies();
// Create the pool
key = PoolKey(currency0, currency1, 3000, 60, IHooks(address(0x0)));
poolId = key.toId();
manager.initialize(key, SQRT_PRICE_1_1);
}
function test_getSlot0() public {
// create liquidity
modifyLiquidityRouter.modifyLiquidity(key, ModifyLiquidityParams(-60, 60, 10_000 ether, 0), ZERO_BYTES);
modifyLiquidityRouter.modifyLiquidity(key, ModifyLiquidityParams(-600, 600, 10_000 ether, 0), ZERO_BYTES);
// swap to create fees, crossing a tick
uint256 swapAmount = 100 ether;
swap(key, true, -int256(swapAmount), ZERO_BYTES);
(uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 swapFee) = StateLibrary.getSlot0(manager, poolId);
vm.snapshotGasLastCall("extsload getSlot0");
assertEq(tick, -139);
// magic number verified against a native getter
assertEq(sqrtPriceX96, 78680104762184586858280382455);
assertEq(tick, -139);
assertEq(protocolFee, 0); // tested in protocol fee tests
assertEq(swapFee, 3000);
}
function test_getTickLiquidity() public {
modifyLiquidityRouter.modifyLiquidity(key, ModifyLiquidityParams(-60, 60, 10 ether, 0), ZERO_BYTES);
(uint128 liquidityGrossLower, int128 liquidityNetLower) = StateLibrary.getTickLiquidity(manager, poolId, -60);
vm.snapshotGasLastCall("extsload getTickLiquidity");
assertEq(liquidityGrossLower, 10 ether);
assertEq(liquidityNetLower, 10 ether);
(uint128 liquidityGrossUpper, int128 liquidityNetUpper) = StateLibrary.getTickLiquidity(manager, poolId, 60);
assertEq(liquidityGrossUpper, 10 ether);
assertEq(liquidityNetUpper, -10 ether);
}
function test_fuzz_getTickLiquidity(ModifyLiquidityParams memory params) public {
(ModifyLiquidityParams memory _params,) =
Fuzzers.createFuzzyLiquidity(modifyLiquidityRouter, key, params, SQRT_PRICE_1_1, ZERO_BYTES);
uint128 liquidityDelta = uint128(uint256(_params.liquidityDelta));
(uint128 liquidityGrossLower, int128 liquidityNetLower) =
StateLibrary.getTickLiquidity(manager, poolId, _params.tickLower);
assertEq(liquidityGrossLower, liquidityDelta);
assertEq(liquidityNetLower, int128(_params.liquidityDelta));
(uint128 liquidityGrossUpper, int128 liquidityNetUpper) =
StateLibrary.getTickLiquidity(manager, poolId, _params.tickUpper);
assertEq(liquidityGrossUpper, liquidityDelta);
assertEq(liquidityNetUpper, -int128(_params.liquidityDelta));
// confirm agreement with getTickInfo()
(uint128 _liquidityGrossLower, int128 _liquidityNetLower,,) =
StateLibrary.getTickInfo(manager, poolId, _params.tickLower);
assertEq(_liquidityGrossLower, liquidityGrossLower);
assertEq(_liquidityNetLower, liquidityNetLower);
(uint128 _liquidityGrossUpper, int128 _liquidityNetUpper,,) =
StateLibrary.getTickInfo(manager, poolId, _params.tickUpper);
assertEq(_liquidityGrossUpper, liquidityGrossUpper);
assertEq(_liquidityNetUpper, liquidityNetUpper);
}
function test_fuzz_getTickLiquidity_two_positions(
ModifyLiquidityParams memory paramsA,
ModifyLiquidityParams memory paramsB
) public {
(ModifyLiquidityParams memory _paramsA,) = Fuzzers.createFuzzyLiquidityWithTightBound(
modifyLiquidityRouter, key, paramsA, SQRT_PRICE_1_1, ZERO_BYTES, 2
);
(ModifyLiquidityParams memory _paramsB,) = Fuzzers.createFuzzyLiquidityWithTightBound(
modifyLiquidityRouter, key, paramsB, SQRT_PRICE_1_1, ZERO_BYTES, 2
);
uint128 liquidityDeltaA = uint128(uint256(_paramsA.liquidityDelta));
uint128 liquidityDeltaB = uint128(uint256(_paramsB.liquidityDelta));
(uint128 liquidityGrossLowerA, int128 liquidityNetLowerA) =
StateLibrary.getTickLiquidity(manager, poolId, _paramsA.tickLower);
(uint128 liquidityGrossLowerB, int128 liquidityNetLowerB) =
StateLibrary.getTickLiquidity(manager, poolId, _paramsB.tickLower);
(uint256 liquidityGrossUpperA, int256 liquidityNetUpperA) =
StateLibrary.getTickLiquidity(manager, poolId, _paramsA.tickUpper);
(uint256 liquidityGrossUpperB, int256 liquidityNetUpperB) =
StateLibrary.getTickLiquidity(manager, poolId, _paramsB.tickUpper);
// when tick lower is shared between two positions, the gross liquidity is the sum
if (_paramsA.tickLower == _paramsB.tickLower || _paramsA.tickLower == _paramsB.tickUpper) {
assertEq(liquidityGrossLowerA, liquidityDeltaA + liquidityDeltaB);
// when tick lower is shared with an upper tick, the net liquidity is the difference
(_paramsA.tickLower == _paramsB.tickLower)
? assertEq(liquidityNetLowerA, int128(liquidityDeltaA + liquidityDeltaB))
: assertApproxEqAbs(liquidityNetLowerA, int128(liquidityDeltaA) - int128(liquidityDeltaB), 1 wei);
} else {
assertEq(liquidityGrossLowerA, liquidityDeltaA);
assertEq(liquidityNetLowerA, int128(liquidityDeltaA));
}
if (_paramsA.tickUpper == _paramsB.tickLower || _paramsA.tickUpper == _paramsB.tickUpper) {
assertEq(liquidityGrossUpperA, liquidityDeltaA + liquidityDeltaB);
(_paramsA.tickUpper == _paramsB.tickUpper)
? assertEq(liquidityNetUpperA, -int128(liquidityDeltaA + liquidityDeltaB))
: assertApproxEqAbs(liquidityNetUpperA, int128(liquidityDeltaB) - int128(liquidityDeltaA), 2 wei);
} else {
assertEq(liquidityGrossUpperA, liquidityDeltaA);
assertEq(liquidityNetUpperA, -int128(liquidityDeltaA));
}
if (_paramsB.tickLower == _paramsA.tickLower || _paramsB.tickLower == _paramsA.tickUpper) {
assertEq(liquidityGrossLowerB, liquidityDeltaA + liquidityDeltaB);
(_paramsB.tickLower == _paramsA.tickLower)
? assertEq(liquidityNetLowerB, int128(liquidityDeltaA + liquidityDeltaB))
: assertApproxEqAbs(liquidityNetLowerB, int128(liquidityDeltaB) - int128(liquidityDeltaA), 1 wei);
} else {
assertEq(liquidityGrossLowerB, liquidityDeltaB);
assertEq(liquidityNetLowerB, int128(liquidityDeltaB));
}
if (_paramsB.tickUpper == _paramsA.tickLower || _paramsB.tickUpper == _paramsA.tickUpper) {
assertEq(liquidityGrossUpperB, liquidityDeltaA + liquidityDeltaB);
(_paramsB.tickUpper == _paramsA.tickUpper)
? assertEq(liquidityNetUpperB, -int128(liquidityDeltaA + liquidityDeltaB))
: assertApproxEqAbs(liquidityNetUpperB, int128(liquidityDeltaA) - int128(liquidityDeltaB), 2 wei);
} else {
assertEq(liquidityGrossUpperB, liquidityDeltaB);
assertEq(liquidityNetUpperB, -int128(liquidityDeltaB));
}
}
function test_getFeeGrowthGlobals0() public {
// create liquidity
uint256 liquidity = 10_000 ether;
modifyLiquidityRouter.modifyLiquidity(key, ModifyLiquidityParams(-60, 60, int256(liquidity), 0), ZERO_BYTES);
(uint256 feeGrowthGlobal0, uint256 feeGrowthGlobal1) = StateLibrary.getFeeGrowthGlobals(manager, poolId);
assertEq(feeGrowthGlobal0, 0);
assertEq(feeGrowthGlobal1, 0);
// swap to create fees on the input token (currency0)
uint256 swapAmount = 10 ether;
swap(key, true, -int256(swapAmount), ZERO_BYTES);
(feeGrowthGlobal0, feeGrowthGlobal1) = StateLibrary.getFeeGrowthGlobals(manager, poolId);
vm.snapshotGasLastCall("extsload getFeeGrowthGlobals");
uint256 feeGrowthGlobalCalc = swapAmount.mulWadDown(0.003e18).mulDivDown(FixedPoint128.Q128, liquidity);
assertEq(feeGrowthGlobal0, feeGrowthGlobalCalc);
assertEq(feeGrowthGlobal1, 0);
}
function test_getFeeGrowthGlobals1() public {
// create liquidity
uint256 liquidity = 10_000 ether;
modifyLiquidityRouter.modifyLiquidity(key, ModifyLiquidityParams(-60, 60, int256(liquidity), 0), ZERO_BYTES);
(uint256 feeGrowthGlobal0, uint256 feeGrowthGlobal1) = StateLibrary.getFeeGrowthGlobals(manager, poolId);
assertEq(feeGrowthGlobal0, 0);
assertEq(feeGrowthGlobal1, 0);
// swap to create fees on the input token (currency1)
uint256 swapAmount = 10 ether;
swap(key, false, -int256(swapAmount), ZERO_BYTES);
(feeGrowthGlobal0, feeGrowthGlobal1) = StateLibrary.getFeeGrowthGlobals(manager, poolId);
assertEq(feeGrowthGlobal0, 0);
uint256 feeGrowthGlobalCalc = swapAmount.mulWadDown(0.003e18).mulDivDown(FixedPoint128.Q128, liquidity);
assertEq(feeGrowthGlobal1, feeGrowthGlobalCalc);
}
function test_getLiquidity() public {
modifyLiquidityRouter.modifyLiquidity(key, ModifyLiquidityParams(-60, 60, 10 ether, 0), ZERO_BYTES);
modifyLiquidityRouter.modifyLiquidity(key, ModifyLiquidityParams(-120, 120, 10 ether, 0), ZERO_BYTES);
uint128 liquidity = StateLibrary.getLiquidity(manager, poolId);
vm.snapshotGasLastCall("extsload getLiquidity");
assertEq(liquidity, 20 ether);
}
function test_fuzz_getLiquidity(ModifyLiquidityParams memory params) public {
(ModifyLiquidityParams memory _params,) =
Fuzzers.createFuzzyLiquidity(modifyLiquidityRouter, key, params, SQRT_PRICE_1_1, ZERO_BYTES);
(, int24 tick,,) = StateLibrary.getSlot0(manager, poolId);
uint128 liquidity = StateLibrary.getLiquidity(manager, poolId);
// out of range liquidity is not added to Pool.State.liquidity
if (tick < _params.tickLower || tick >= _params.tickUpper) {
assertEq(liquidity, 0);
} else {
assertEq(liquidity, uint128(uint256(_params.liquidityDelta)));
}
}
function test_getTickBitmap() public {
int24 tickLower = -300;
int24 tickUpper = 300;
// create liquidity
modifyLiquidityRouter.modifyLiquidity(
key, ModifyLiquidityParams(tickLower, tickUpper, 10_000 ether, 0), ZERO_BYTES
);
(int16 wordPos, uint8 bitPos) = TickBitmap.position(tickLower / key.tickSpacing);
uint256 tickBitmap = StateLibrary.getTickBitmap(manager, poolId, wordPos);
vm.snapshotGasLastCall("extsload getTickBitmap");
assertNotEq(tickBitmap, 0);
assertEq(tickBitmap, 1 << bitPos);
(wordPos, bitPos) = TickBitmap.position(tickUpper / key.tickSpacing);
tickBitmap = StateLibrary.getTickBitmap(manager, poolId, wordPos);
assertNotEq(tickBitmap, 0);
assertEq(tickBitmap, 1 << bitPos);
}
function test_fuzz_getTickBitmap(ModifyLiquidityParams memory params) public {
(ModifyLiquidityParams memory _params,) =
Fuzzers.createFuzzyLiquidity(modifyLiquidityRouter, key, params, SQRT_PRICE_1_1, ZERO_BYTES);
(int16 wordPos, uint8 bitPos) = TickBitmap.position(_params.tickLower / key.tickSpacing);
(int16 wordPosUpper, uint8 bitPosUpper) = TickBitmap.position(_params.tickUpper / key.tickSpacing);
uint256 tickBitmap = StateLibrary.getTickBitmap(manager, poolId, wordPos);
assertNotEq(tickBitmap, 0);
// in fuzz tests, the tickLower and tickUpper might exist on the same word
if (wordPos == wordPosUpper) {
assertEq(tickBitmap, (1 << bitPos) | (1 << bitPosUpper));
} else {
assertEq(tickBitmap, 1 << bitPos);
}
}
function test_getPositionInfo() public {
// create liquidity
modifyLiquidityRouter.modifyLiquidity(key, ModifyLiquidityParams(-60, 60, 10_000 ether, 0), ZERO_BYTES);
// swap to create fees, crossing a tick
uint256 swapAmount = 10 ether;
swap(key, true, -int256(swapAmount), ZERO_BYTES);
(, int24 currentTick,,) = StateLibrary.getSlot0(manager, poolId);
assertNotEq(currentTick, -139);
// poke the LP so that fees are updated
modifyLiquidityRouter.modifyLiquidity(key, ModifyLiquidityParams(-60, 60, 0, 0), ZERO_BYTES);
bytes32 positionId =
keccak256(abi.encodePacked(address(modifyLiquidityRouter), int24(-60), int24(60), bytes32(0)));
(uint128 liquidity, uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) =
StateLibrary.getPositionInfo(manager, poolId, positionId);
vm.snapshotGasLastCall("extsload getPositionInfo");
assertEq(liquidity, 10_000 ether);
assertNotEq(feeGrowthInside0X128, 0);
assertEq(feeGrowthInside1X128, 0);
}
function test_fuzz_getPositionInfo(ModifyLiquidityParams memory params, uint256 swapAmount, bool zeroForOne)
public
{
(ModifyLiquidityParams memory _params, BalanceDelta delta) =
createFuzzyLiquidity(modifyLiquidityRouter, key, params, SQRT_PRICE_1_1, ZERO_BYTES);
uint256 delta0 = uint256(int256(-delta.amount0()));
uint256 delta1 = uint256(int256(-delta.amount1()));
// if one of the deltas is zero, ensure to swap in the right direction
if (delta0 == 0) {
zeroForOne = true;
} else if (delta1 == 0) {
zeroForOne = false;
}
swapAmount = bound(swapAmount, 1, uint256(int256(type(int128).max)));
swap(key, zeroForOne, -int256(swapAmount), ZERO_BYTES);
// poke the LP so that fees are updated
modifyLiquidityRouter.modifyLiquidity(
key, ModifyLiquidityParams(_params.tickLower, _params.tickUpper, 0, 0), ZERO_BYTES
);
bytes32 positionId = keccak256(
abi.encodePacked(address(modifyLiquidityRouter), _params.tickLower, _params.tickUpper, bytes32(0))
);
(uint128 liquidity, uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) =
StateLibrary.getPositionInfo(manager, poolId, positionId);
assertEq(liquidity, uint128(uint256(_params.liquidityDelta)));
if (zeroForOne) {
assertNotEq(feeGrowthInside0X128, 0);
assertEq(feeGrowthInside1X128, 0);
} else {
assertEq(feeGrowthInside0X128, 0);
assertNotEq(feeGrowthInside1X128, 0);
}
}
function test_getTickFeeGrowthOutside() public {
// create liquidity
modifyLiquidityRouter.modifyLiquidity(key, ModifyLiquidityParams(-60, 60, 10_000 ether, 0), ZERO_BYTES);
modifyLiquidityRouter.modifyLiquidity(key, ModifyLiquidityParams(-600, 600, 10_000 ether, 0), ZERO_BYTES);
// swap to create fees, crossing a tick
uint256 swapAmount = 100 ether;
swap(key, true, -int256(swapAmount), ZERO_BYTES);
(, int24 currentTick,,) = StateLibrary.getSlot0(manager, poolId);
assertEq(currentTick, -139);
int24 tick = -60;
(uint256 feeGrowthOutside0X128, uint256 feeGrowthOutside1X128) =
StateLibrary.getTickFeeGrowthOutside(manager, poolId, tick);
vm.snapshotGasLastCall("extsload getTickFeeGrowthOutside");
// magic number verified against a native getter on PoolManager
assertEq(feeGrowthOutside0X128, 3076214778951936192155253373200636);
assertEq(feeGrowthOutside1X128, 0);
tick = 60;
(feeGrowthOutside0X128, feeGrowthOutside1X128) = StateLibrary.getTickFeeGrowthOutside(manager, poolId, tick);
assertEq(feeGrowthOutside0X128, 0);
assertEq(feeGrowthOutside1X128, 0);
}
// also hard to fuzz because of feeGrowthOutside
function test_getTickInfo() public {
// create liquidity
modifyLiquidityRouter.modifyLiquidity(key, ModifyLiquidityParams(-60, 60, 10_000 ether, 0), ZERO_BYTES);
modifyLiquidityRouter.modifyLiquidity(key, ModifyLiquidityParams(-600, 600, 10_000 ether, 0), ZERO_BYTES);
// swap to create fees, crossing a tick
uint256 swapAmount = 100 ether;
swap(key, true, -int256(swapAmount), ZERO_BYTES);
(, int24 currentTick,,) = StateLibrary.getSlot0(manager, poolId);
assertEq(currentTick, -139);
int24 tick = -60;
(uint128 liquidityGross, int128 liquidityNet, uint256 feeGrowthOutside0X128, uint256 feeGrowthOutside1X128) =
StateLibrary.getTickInfo(manager, poolId, tick);
vm.snapshotGasLastCall("extsload getTickInfo");
(uint128 liquidityGross_, int128 liquidityNet_) = StateLibrary.getTickLiquidity(manager, poolId, tick);
(uint256 feeGrowthOutside0X128_, uint256 feeGrowthOutside1X128_) =
StateLibrary.getTickFeeGrowthOutside(manager, poolId, tick);
assertEq(liquidityGross, 10_000 ether);
assertEq(liquidityGross, liquidityGross_);
assertEq(liquidityNet, liquidityNet_);
assertNotEq(feeGrowthOutside0X128, 0);
assertEq(feeGrowthOutside1X128, 0);
assertEq(feeGrowthOutside0X128, feeGrowthOutside0X128_);
assertEq(feeGrowthOutside1X128, feeGrowthOutside1X128_);
}
function test_getFeeGrowthInside() public {
// create liquidity
modifyLiquidityRouter.modifyLiquidity(key, ModifyLiquidityParams(-60, 60, 10_000 ether, 0), ZERO_BYTES);
modifyLiquidityRouter.modifyLiquidity(key, ModifyLiquidityParams(-600, 600, 10_000 ether, 0), ZERO_BYTES);
// swap to create fees, crossing a tick
uint256 swapAmount = 100 ether;
swap(key, true, -int256(swapAmount), ZERO_BYTES);
(, int24 currentTick,,) = StateLibrary.getSlot0(manager, poolId);
assertEq(currentTick, -139);
// calculated live
(uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) =
StateLibrary.getFeeGrowthInside(manager, poolId, -60, 60);
vm.snapshotGasLastCall("extsload getFeeGrowthInside");
// poke the LP so that fees are updated
modifyLiquidityRouter.modifyLiquidity(key, ModifyLiquidityParams(-60, 60, 0, 0), ZERO_BYTES);
bytes32 positionId =
keccak256(abi.encodePacked(address(modifyLiquidityRouter), int24(-60), int24(60), bytes32(0)));
(, uint256 feeGrowthInside0X128_, uint256 feeGrowthInside1X128_) =
StateLibrary.getPositionInfo(manager, poolId, positionId);
assertNotEq(feeGrowthInside0X128, 0);
assertEq(feeGrowthInside0X128, feeGrowthInside0X128_);
assertEq(feeGrowthInside1X128, feeGrowthInside1X128_);
}
function test_fuzz_getFeeGrowthInside(ModifyLiquidityParams memory params, bool zeroForOne) public {
modifyLiquidityRouter.modifyLiquidity(
key,
ModifyLiquidityParams(
TickMath.minUsableTick(key.tickSpacing), TickMath.maxUsableTick(key.tickSpacing), 10_000 ether, 0
),
ZERO_BYTES
);
(ModifyLiquidityParams memory _params,) =
createFuzzyLiquidity(modifyLiquidityRouter, key, params, SQRT_PRICE_1_1, ZERO_BYTES);
swap(key, zeroForOne, -int256(100e18), ZERO_BYTES);
// calculated live
(uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) =
StateLibrary.getFeeGrowthInside(manager, poolId, _params.tickLower, _params.tickUpper);
// poke the LP so that fees are updated
modifyLiquidityRouter.modifyLiquidity(
key, ModifyLiquidityParams(_params.tickLower, _params.tickUpper, 0, 0), ZERO_BYTES
);
bytes32 positionId = keccak256(
abi.encodePacked(address(modifyLiquidityRouter), _params.tickLower, _params.tickUpper, bytes32(0))
);
(, uint256 feeGrowthInside0X128_, uint256 feeGrowthInside1X128_) =
StateLibrary.getPositionInfo(manager, poolId, positionId);
assertEq(feeGrowthInside0X128, feeGrowthInside0X128_);
assertEq(feeGrowthInside1X128, feeGrowthInside1X128_);
}
function test_getPositionLiquidity() public {
// create liquidity
modifyLiquidityRouter.modifyLiquidity(key, ModifyLiquidityParams(-60, 60, 10_000 ether, 0), ZERO_BYTES);
bytes32 positionId =
keccak256(abi.encodePacked(address(modifyLiquidityRouter), int24(-60), int24(60), bytes32(0)));
uint128 liquidity = StateLibrary.getPositionLiquidity(manager, poolId, positionId);
vm.snapshotGasLastCall("extsload getPositionLiquidity");
assertEq(liquidity, 10_000 ether);
}
function test_fuzz_getPositionLiquidity(ModifyLiquidityParams memory paramsA, ModifyLiquidityParams memory paramsB)
public
{
(ModifyLiquidityParams memory _paramsA) = Fuzzers.createFuzzyLiquidityParams(key, paramsA, SQRT_PRICE_1_1);
(ModifyLiquidityParams memory _paramsB) = Fuzzers.createFuzzyLiquidityParams(key, paramsB, SQRT_PRICE_1_1);
// Assume there are no overlapping positions
vm.assume(
_paramsA.tickLower != _paramsB.tickLower && _paramsA.tickLower != _paramsB.tickUpper
&& _paramsB.tickLower != _paramsA.tickUpper && _paramsA.tickUpper != _paramsB.tickUpper
);
modifyLiquidityRouter.modifyLiquidity(key, _paramsA, ZERO_BYTES);
modifyLiquidityRouter.modifyLiquidity(key, _paramsB, ZERO_BYTES);
bytes32 positionIdA = keccak256(
abi.encodePacked(address(modifyLiquidityRouter), _paramsA.tickLower, _paramsA.tickUpper, bytes32(0))
);
uint128 liquidityA = StateLibrary.getPositionLiquidity(manager, poolId, positionIdA);
assertEq(liquidityA, uint128(uint256(_paramsA.liquidityDelta)));
bytes32 positionIdB = keccak256(
abi.encodePacked(address(modifyLiquidityRouter), _paramsB.tickLower, _paramsB.tickUpper, bytes32(0))
);
uint128 liquidityB = StateLibrary.getPositionLiquidity(manager, poolId, positionIdB);
assertEq(liquidityB, uint128(uint256(_paramsB.liquidityDelta)));
}
}