Skip to content

Commit

Permalink
Simple storage and memory caching optimizations in Pool (#567)
Browse files Browse the repository at this point in the history
* Cache `tick` and `sqrtPriceX96` in `Pool.modifyLiquidity`

This commit slightly modifies the `Pool` library to reduce gas usage. An intermediary variable `tick` and `sqrtPriceX96` are created to reduce the number of calls to `self.slot0`. This change results in smaller footprint in the snapshots, indicating reduction in gas consumption.

* Cache `zeroForOne` in stack in `Pool.swap`

Parameters in Pool.sol are now more concisely referenced with `zeroForOne` variable. This affects all the occurrences of `params.zeroForOne`, where it is replaced with `zeroForOne`. The snapshot files are also updated to reflect this change. This simplification helps to reduce bytecode size and increase overall efficiency of the code.

* Cache `self.liquidity` and remove clone to memory in `Position.update`

This commit focuses on optimizing the `Position.update` function from within the `Position.sol` file. It introduces a cached variable `liquidity`, thereby eliminating the need to clone `self` to memory. This change results in a more efficient use of memory and optimizes code execution. The snapshots have been updated to reflect the adjusted bytecode sizes for the various operations.

* Cache `params.liquidityDelta` to stack in `Pool.modifyLiquidity`

This commit primarily caches the variable `params.liquidityDelta` to stack in the `Pool.modifyLiquidity` method inside Pool.sol to improve efficiency. This tiny optimization reduces bytecode size and makes the underlying calculations faster and more efficient. This change is reflected in the updated .snap files for poolManager bytecode size and various test cases involving liquidity management.

* Cache `tickLower` and `tickUpper` to stack in `Pool.modifyLiquidity`

The changes in the code have optimized memory use in the `Pool.modifyLiquidity` function, by caching `tickLower` and `tickUpper` to the stack rather than repeatedly accessing them from memory. This minor adjustment not only optimizes memory usage, but also potentially improves the function performance by reducing the number of memory operations.
  • Loading branch information
shuhuiluo authored Apr 8, 2024
1 parent f80b503 commit c5a23f0
Show file tree
Hide file tree
Showing 22 changed files with 76 additions and 83 deletions.
2 changes: 1 addition & 1 deletion .forge-snapshots/addLiquidity with empty hook.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
265381
264723
2 changes: 1 addition & 1 deletion .forge-snapshots/addLiquidity with native token.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
140224
139579
2 changes: 1 addition & 1 deletion .forge-snapshots/addLiquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
145541
144896
2 changes: 1 addition & 1 deletion .forge-snapshots/poolManager bytecode size.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
23083
22844
2 changes: 1 addition & 1 deletion .forge-snapshots/removeLiquidity with empty hook.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
56072
55383
2 changes: 1 addition & 1 deletion .forge-snapshots/removeLiquidity with native token.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
148258
147569
2 changes: 1 addition & 1 deletion .forge-snapshots/removeLiquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
149722
149033
2 changes: 1 addition & 1 deletion .forge-snapshots/simple swap with native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
132902
132802
2 changes: 1 addition & 1 deletion .forge-snapshots/simple swap.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
146778
146678
Original file line number Diff line number Diff line change
@@ -1 +1 @@
72361
72297
2 changes: 1 addition & 1 deletion .forge-snapshots/swap against liquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
60364
60300
2 changes: 1 addition & 1 deletion .forge-snapshots/swap burn 6909 for input.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
80394
80330
2 changes: 1 addition & 1 deletion .forge-snapshots/swap burn native 6909 for input.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
76391
76327
2 changes: 1 addition & 1 deletion .forge-snapshots/swap mint native output as 6909.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
138742
138678
2 changes: 1 addition & 1 deletion .forge-snapshots/swap mint output as 6909.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
155593
155493
Original file line number Diff line number Diff line change
@@ -1 +1 @@
156254
156090
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with dynamic fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
89668
89568
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with hooks.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
60342
60278
2 changes: 1 addition & 1 deletion .forge-snapshots/update dynamic fee in before swap.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
140294
140194
95 changes: 46 additions & 49 deletions src/libraries/Pool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -156,90 +156,88 @@ library Pool {
internal
returns (BalanceDelta result)
{
checkTicks(params.tickLower, params.tickUpper);
int128 liquidityDelta = params.liquidityDelta;
int24 tickLower = params.tickLower;
int24 tickUpper = params.tickUpper;
checkTicks(tickLower, tickUpper);

uint256 feesOwed0;
uint256 feesOwed1;
{
ModifyLiquidityState memory state;

// if we need to update the ticks, do it
if (params.liquidityDelta != 0) {
if (liquidityDelta != 0) {
(state.flippedLower, state.liquidityGrossAfterLower) =
updateTick(self, params.tickLower, params.liquidityDelta, false);
(state.flippedUpper, state.liquidityGrossAfterUpper) =
updateTick(self, params.tickUpper, params.liquidityDelta, true);
updateTick(self, tickLower, liquidityDelta, false);
(state.flippedUpper, state.liquidityGrossAfterUpper) = updateTick(self, tickUpper, liquidityDelta, true);

if (params.liquidityDelta > 0) {
// `>` and `>=` are logically equivalent here but `>=` is cheaper
if (liquidityDelta >= 0) {
uint128 maxLiquidityPerTick = tickSpacingToMaxLiquidityPerTick(params.tickSpacing);
if (state.liquidityGrossAfterLower > maxLiquidityPerTick) {
revert TickLiquidityOverflow(params.tickLower);
revert TickLiquidityOverflow(tickLower);
}
if (state.liquidityGrossAfterUpper > maxLiquidityPerTick) {
revert TickLiquidityOverflow(params.tickUpper);
revert TickLiquidityOverflow(tickUpper);
}
}

if (state.flippedLower) {
self.tickBitmap.flipTick(params.tickLower, params.tickSpacing);
self.tickBitmap.flipTick(tickLower, params.tickSpacing);
}
if (state.flippedUpper) {
self.tickBitmap.flipTick(params.tickUpper, params.tickSpacing);
self.tickBitmap.flipTick(tickUpper, params.tickSpacing);
}
}

(state.feeGrowthInside0X128, state.feeGrowthInside1X128) =
getFeeGrowthInside(self, params.tickLower, params.tickUpper);
(state.feeGrowthInside0X128, state.feeGrowthInside1X128) = getFeeGrowthInside(self, tickLower, tickUpper);

(feesOwed0, feesOwed1) = self.positions.get(params.owner, params.tickLower, params.tickUpper).update(
params.liquidityDelta, state.feeGrowthInside0X128, state.feeGrowthInside1X128
);
Position.Info storage position = self.positions.get(params.owner, tickLower, tickUpper);
(feesOwed0, feesOwed1) =
position.update(liquidityDelta, state.feeGrowthInside0X128, state.feeGrowthInside1X128);

// clear any tick data that is no longer needed
if (params.liquidityDelta < 0) {
if (liquidityDelta < 0) {
if (state.flippedLower) {
clearTick(self, params.tickLower);
clearTick(self, tickLower);
}
if (state.flippedUpper) {
clearTick(self, params.tickUpper);
clearTick(self, tickUpper);
}
}
}

if (params.liquidityDelta != 0) {
if (self.slot0.tick < params.tickLower) {
if (liquidityDelta != 0) {
int24 tick = self.slot0.tick;
uint160 sqrtPriceX96 = self.slot0.sqrtPriceX96;
if (tick < tickLower) {
// current tick is below the passed range; liquidity can only become in range by crossing from left to
// right, when we'll need _more_ currency0 (it's becoming more valuable) so user must provide it
result = toBalanceDelta(
SqrtPriceMath.getAmount0Delta(
TickMath.getSqrtRatioAtTick(params.tickLower),
TickMath.getSqrtRatioAtTick(params.tickUpper),
params.liquidityDelta
TickMath.getSqrtRatioAtTick(tickLower), TickMath.getSqrtRatioAtTick(tickUpper), liquidityDelta
).toInt128(),
0
);
} else if (self.slot0.tick < params.tickUpper) {
} else if (tick < tickUpper) {
result = toBalanceDelta(
SqrtPriceMath.getAmount0Delta(
self.slot0.sqrtPriceX96, TickMath.getSqrtRatioAtTick(params.tickUpper), params.liquidityDelta
).toInt128(),
SqrtPriceMath.getAmount1Delta(
TickMath.getSqrtRatioAtTick(params.tickLower), self.slot0.sqrtPriceX96, params.liquidityDelta
).toInt128()
SqrtPriceMath.getAmount0Delta(sqrtPriceX96, TickMath.getSqrtRatioAtTick(tickUpper), liquidityDelta)
.toInt128(),
SqrtPriceMath.getAmount1Delta(TickMath.getSqrtRatioAtTick(tickLower), sqrtPriceX96, liquidityDelta)
.toInt128()
);

self.liquidity = params.liquidityDelta < 0
? self.liquidity - uint128(-params.liquidityDelta)
: self.liquidity + uint128(params.liquidityDelta);
self.liquidity = liquidityDelta < 0
? self.liquidity - uint128(-liquidityDelta)
: self.liquidity + uint128(liquidityDelta);
} else {
// current tick is above the passed range; liquidity can only become in range by crossing from right to
// left, when we'll need _more_ currency1 (it's becoming more valuable) so user must provide it
result = toBalanceDelta(
0,
SqrtPriceMath.getAmount1Delta(
TickMath.getSqrtRatioAtTick(params.tickLower),
TickMath.getSqrtRatioAtTick(params.tickUpper),
params.liquidityDelta
TickMath.getSqrtRatioAtTick(tickLower), TickMath.getSqrtRatioAtTick(tickUpper), liquidityDelta
).toInt128()
);
}
Expand Down Expand Up @@ -306,7 +304,8 @@ library Pool {

Slot0 memory slot0Start = self.slot0;
swapFee = slot0Start.swapFee;
if (params.zeroForOne) {
bool zeroForOne = params.zeroForOne;
if (zeroForOne) {
if (params.sqrtPriceLimitX96 >= slot0Start.sqrtPriceX96) {
revert PriceLimitAlreadyExceeded(slot0Start.sqrtPriceX96, params.sqrtPriceLimitX96);
}
Expand All @@ -324,9 +323,7 @@ library Pool {

SwapCache memory cache = SwapCache({
liquidityStart: self.liquidity,
protocolFee: params.zeroForOne
? slot0Start.protocolFee.getZeroForOneFee()
: slot0Start.protocolFee.getOneForZeroFee()
protocolFee: zeroForOne ? slot0Start.protocolFee.getZeroForOneFee() : slot0Start.protocolFee.getOneForZeroFee()
});

bool exactInput = params.amountSpecified < 0;
Expand All @@ -336,7 +333,7 @@ library Pool {
amountCalculated: 0,
sqrtPriceX96: slot0Start.sqrtPriceX96,
tick: slot0Start.tick,
feeGrowthGlobalX128: params.zeroForOne ? self.feeGrowthGlobal0X128 : self.feeGrowthGlobal1X128,
feeGrowthGlobalX128: zeroForOne ? self.feeGrowthGlobal0X128 : self.feeGrowthGlobal1X128,
liquidity: cache.liquidityStart
});

Expand All @@ -346,7 +343,7 @@ library Pool {
step.sqrtPriceStartX96 = state.sqrtPriceX96;

(step.tickNext, step.initialized) =
self.tickBitmap.nextInitializedTickWithinOneWord(state.tick, params.tickSpacing, params.zeroForOne);
self.tickBitmap.nextInitializedTickWithinOneWord(state.tick, params.tickSpacing, zeroForOne);

// ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds
if (step.tickNext < TickMath.MIN_TICK) {
Expand All @@ -362,7 +359,7 @@ library Pool {
(state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = SwapMath.computeSwapStep(
state.sqrtPriceX96,
(
params.zeroForOne
zeroForOne
? step.sqrtPriceNextX96 < params.sqrtPriceLimitX96
: step.sqrtPriceNextX96 > params.sqrtPriceLimitX96
) ? params.sqrtPriceLimitX96 : step.sqrtPriceNextX96,
Expand Down Expand Up @@ -409,13 +406,13 @@ library Pool {
int128 liquidityNet = Pool.crossTick(
self,
step.tickNext,
(params.zeroForOne ? state.feeGrowthGlobalX128 : self.feeGrowthGlobal0X128),
(params.zeroForOne ? self.feeGrowthGlobal1X128 : state.feeGrowthGlobalX128)
(zeroForOne ? state.feeGrowthGlobalX128 : self.feeGrowthGlobal0X128),
(zeroForOne ? self.feeGrowthGlobal1X128 : state.feeGrowthGlobalX128)
);
// if we're moving leftward, we interpret liquidityNet as the opposite sign
// safe because liquidityNet cannot be type(int128).min
unchecked {
if (params.zeroForOne) liquidityNet = -liquidityNet;
if (zeroForOne) liquidityNet = -liquidityNet;
}

state.liquidity = liquidityNet < 0
Expand All @@ -424,7 +421,7 @@ library Pool {
}

unchecked {
state.tick = params.zeroForOne ? step.tickNext - 1 : step.tickNext;
state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext;
}
} else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) {
// recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved
Expand All @@ -438,14 +435,14 @@ library Pool {
if (cache.liquidityStart != state.liquidity) self.liquidity = state.liquidity;

// update fee growth global
if (params.zeroForOne) {
if (zeroForOne) {
self.feeGrowthGlobal0X128 = state.feeGrowthGlobalX128;
} else {
self.feeGrowthGlobal1X128 = state.feeGrowthGlobalX128;
}

unchecked {
if (params.zeroForOne == exactInput) {
if (zeroForOne == exactInput) {
result = toBalanceDelta(
(params.amountSpecified - state.amountSpecifiedRemaining).toInt128(),
state.amountCalculated.toInt128()
Expand Down
23 changes: 10 additions & 13 deletions src/libraries/Position.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ library Position {
function get(mapping(bytes32 => Info) storage self, address owner, int24 tickLower, int24 tickUpper)
internal
view
returns (Position.Info storage position)
returns (Info storage position)
{
position = self[keccak256(abi.encodePacked(owner, tickLower, tickUpper))];
}
Expand All @@ -47,26 +47,23 @@ library Position {
uint256 feeGrowthInside0X128,
uint256 feeGrowthInside1X128
) internal returns (uint256 feesOwed0, uint256 feesOwed1) {
Info memory _self = self;
uint128 liquidity = self.liquidity;

uint128 liquidityNext;
if (liquidityDelta == 0) {
if (_self.liquidity == 0) revert CannotUpdateEmptyPosition(); // disallow pokes for 0 liquidity positions
liquidityNext = _self.liquidity;
if (liquidity == 0) revert CannotUpdateEmptyPosition(); // disallow pokes for 0 liquidity positions
liquidityNext = liquidity;
} else {
liquidityNext = liquidityDelta < 0
? _self.liquidity - uint128(-liquidityDelta)
: _self.liquidity + uint128(liquidityDelta);
liquidityNext =
liquidityDelta < 0 ? liquidity - uint128(-liquidityDelta) : liquidity + uint128(liquidityDelta);
}

// calculate accumulated fees. overflow in the subtraction of fee growth is expected
unchecked {
feesOwed0 = FullMath.mulDiv(
feeGrowthInside0X128 - _self.feeGrowthInside0LastX128, _self.liquidity, FixedPoint128.Q128
);
feesOwed1 = FullMath.mulDiv(
feeGrowthInside1X128 - _self.feeGrowthInside1LastX128, _self.liquidity, FixedPoint128.Q128
);
feesOwed0 =
FullMath.mulDiv(feeGrowthInside0X128 - self.feeGrowthInside0LastX128, liquidity, FixedPoint128.Q128);
feesOwed1 =
FullMath.mulDiv(feeGrowthInside1X128 - self.feeGrowthInside1LastX128, liquidity, FixedPoint128.Q128);
}

// update the position
Expand Down
3 changes: 1 addition & 2 deletions src/types/BalanceDelta.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ using BalanceDeltaLibrary for BalanceDelta global;
function toBalanceDelta(int128 _amount0, int128 _amount1) pure returns (BalanceDelta balanceDelta) {
/// @solidity memory-safe-assembly
assembly {
balanceDelta :=
or(shl(128, _amount0), and(0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff, _amount1))
balanceDelta := or(shl(128, _amount0), and(0xffffffffffffffffffffffffffffffff, _amount1))
}
}

Expand Down

0 comments on commit c5a23f0

Please sign in to comment.