diff --git a/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap index 8ae96e8..fbe6440 100644 --- a/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap @@ -1 +1 @@ -178189 \ No newline at end of file +178162 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap index 835e268..250531a 100644 --- a/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap @@ -1 +1 @@ -184774 \ No newline at end of file +184048 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap index 8972ae6..cbed68c 100644 --- a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap @@ -1 +1 @@ -311515 \ No newline at end of file +311216 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap index e6e7790..93bf46a 100644 --- a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap @@ -1 +1 @@ -189692 \ No newline at end of file +189420 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerBytecodeSize.snap b/.forge-snapshots/BinPoolManagerBytecodeSize.snap index 70341d9..91769c1 100644 --- a/.forge-snapshots/BinPoolManagerBytecodeSize.snap +++ b/.forge-snapshots/BinPoolManagerBytecodeSize.snap @@ -1 +1 @@ -24173 \ No newline at end of file +23911 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap b/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap index 3dc4c18..d39cabe 100644 --- a/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap +++ b/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap @@ -1 +1 @@ -133833 \ No newline at end of file +133811 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap index b3d3bba..94f5576 100644 --- a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap +++ b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap @@ -1 +1 @@ -32518 \ No newline at end of file +32521 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap index 1e8e434..1afcdd5 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap @@ -1 +1 @@ -142629 \ No newline at end of file +142616 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap index 4cfa439..caeaa75 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap @@ -1 +1 @@ -289662 \ No newline at end of file +289602 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap index 840fba6..25d6f23 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap @@ -1 +1 @@ -127005 \ No newline at end of file +126984 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap index c3af14f..9cd1996 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap @@ -1 +1 @@ -970510 \ No newline at end of file +968494 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap index 26feb40..be9547b 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap @@ -1 +1 @@ -329822 \ No newline at end of file +327806 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap index c2421d1..8c1e492 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap @@ -1 +1 @@ -337829 \ No newline at end of file +337530 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap index 563ff6c..2fd7a38 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap @@ -1 +1 @@ -140380 \ No newline at end of file +140081 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap index d729758..8ca7bd7 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap @@ -1 +1 @@ -173317 \ No newline at end of file +173046 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap index eb2276a..c7a37b8 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap @@ -1 +1 @@ -179345 \ No newline at end of file +179074 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap index 01ba963..39786e8 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap @@ -1 +1 @@ -133348 \ No newline at end of file +133077 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testInitialize_gasCheck_withoutHooks.snap b/.forge-snapshots/BinPoolManagerTest#testInitialize_gasCheck_withoutHooks.snap index 4e350ed..3e01165 100644 --- a/.forge-snapshots/BinPoolManagerTest#testInitialize_gasCheck_withoutHooks.snap +++ b/.forge-snapshots/BinPoolManagerTest#testInitialize_gasCheck_withoutHooks.snap @@ -1 +1 @@ -163446 \ No newline at end of file +162720 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap index a7e7092..03f0219 100644 --- a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap +++ b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap @@ -1 +1 @@ -304787 \ No newline at end of file +304488 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerBytecodeSize.snap b/.forge-snapshots/CLPoolManagerBytecodeSize.snap index a5d452b..0a9152a 100644 --- a/.forge-snapshots/CLPoolManagerBytecodeSize.snap +++ b/.forge-snapshots/CLPoolManagerBytecodeSize.snap @@ -1 +1 @@ -21216 \ No newline at end of file +20856 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap index 0dc04c2..c0ab2e6 100644 --- a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap @@ -1 +1 @@ -150173 \ No newline at end of file +149202 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap index 4f72d56..43879c4 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap @@ -1 +1 @@ -131090 \ No newline at end of file +130815 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap index 5ea8431..f79faf1 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap @@ -1 +1 @@ -163667 \ No newline at end of file +163156 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap index 537566d..a6d00c4 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap @@ -1 +1 @@ -149106 \ No newline at end of file +148595 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap index 7be1b20..760abfc 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap @@ -1 +1 @@ -71701 \ No newline at end of file +71190 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap index 12dd6e9..9cbdf5b 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap @@ -1 +1 @@ -143343 \ No newline at end of file +143062 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap index f20f62e..cd18fc6 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap @@ -1 +1 @@ -87879 \ No newline at end of file +87368 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap index 644373c..351ef5c 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap @@ -1 +1 @@ -71704 \ No newline at end of file +71193 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap b/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap index c2550d4..3119dbe 100644 --- a/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap +++ b/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap @@ -1 +1 @@ -31971 \ No newline at end of file +31953 \ No newline at end of file diff --git a/src/pool-bin/BinPoolManager.sol b/src/pool-bin/BinPoolManager.sol index 3431ce3..707449d 100644 --- a/src/pool-bin/BinPoolManager.sol +++ b/src/pool-bin/BinPoolManager.sol @@ -22,6 +22,7 @@ import {BinHooks} from "./libraries/BinHooks.sol"; import {PriceHelper} from "./libraries/PriceHelper.sol"; import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol"; import "./interfaces/IBinHooks.sol"; +import {BinSlot0} from "./types/BinSlot0.sol"; /// @notice Holds the state for all bin pools contract BinPoolManager is IBinPoolManager, ProtocolFees, Extsload { @@ -59,9 +60,9 @@ contract BinPoolManager is IBinPoolManager, ProtocolFees, Extsload { /// @inheritdoc IBinPoolManager function getSlot0(PoolId id) external view override returns (uint24 activeId, uint24 protocolFee, uint24 lpFee) { - BinPool.Slot0 memory slot0 = pools[id].slot0; + BinSlot0 slot0 = pools[id].slot0; - return (slot0.activeId, slot0.protocolFee, slot0.lpFee); + return (slot0.activeId(), slot0.protocolFee(), slot0.lpFee()); } /// @inheritdoc IBinPoolManager @@ -282,7 +283,7 @@ contract BinPoolManager is IBinPoolManager, ProtocolFees, Extsload { BinHooks.beforeDonate(key, amount0, amount1, hookData); /// @dev Share is 1:1 liquidity when liquidity is first added to bin - uint256 currentBinShare = pool.shareOfBin[pool.slot0.activeId]; + uint256 currentBinShare = pool.shareOfBin[pool.slot0.activeId()]; if (currentBinShare <= MIN_BIN_SHARE_FOR_DONATE) { revert InsufficientBinShareForDonate(currentBinShare); } diff --git a/src/pool-bin/libraries/BinPool.sol b/src/pool-bin/libraries/BinPool.sol index dd0182c..8fb7708 100644 --- a/src/pool-bin/libraries/BinPool.sol +++ b/src/pool-bin/libraries/BinPool.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import {BalanceDelta, toBalanceDelta} from "../../types/BalanceDelta.sol"; +import {BinSlot0} from "../types/BinSlot0.sol"; import {LiquidityConfigurations} from "./math/LiquidityConfigurations.sol"; import {PackedUint128Math} from "./math/PackedUint128Math.sol"; import {Uint256x256Math} from "./math/Uint256x256Math.sol"; @@ -48,21 +49,9 @@ library BinPool { /// @dev if swap exactIn, x for y, unspecifiedToken = token y. if swap x for exact out y, unspecified token is x error BinPool__InsufficientAmountUnSpecified(); - struct Slot0 { - // the current activeId - uint24 activeId; - // protocol fee, expressed in hundredths of a bip - // upper 12 bits are for 1->0, and the lower 12 are for 0->1 - // the maximum is 1000 - meaning the maximum protocol fee is 0.1% - // the protocolFee is taken from the input first, then the lpFee is taken from the remaining input - uint24 protocolFee; - // lp fee, either static at initialize or dynamic via hook - uint24 lpFee; - } - /// @dev The state of a pool struct State { - Slot0 slot0; + BinSlot0 slot0; /// @notice binId ==> (reserve of token x and y in the bin) mapping(uint256 binId => bytes32 reserve) reserveOfBin; /// @notice binId ==> (total share minted) @@ -79,21 +68,21 @@ library BinPool { function initialize(State storage self, uint24 activeId, uint24 protocolFee, uint24 lpFee) internal { /// An initialized pool will not have activeId: 0 - if (self.slot0.activeId != 0) revert PoolAlreadyInitialized(); + if (self.slot0.activeId() != 0) revert PoolAlreadyInitialized(); - self.slot0 = Slot0({activeId: activeId, protocolFee: protocolFee, lpFee: lpFee}); + self.slot0 = BinSlot0.wrap(bytes32(0)).setActiveId(activeId).setProtocolFee(protocolFee).setLpFee(lpFee); } function setProtocolFee(State storage self, uint24 protocolFee) internal { self.checkPoolInitialized(); - self.slot0.protocolFee = protocolFee; + self.slot0 = self.slot0.setProtocolFee(protocolFee); } /// @notice Only dynamic fee pools may update the swap fee. function setLPFee(State storage self, uint24 lpFee) internal { self.checkPoolInitialized(); - self.slot0.lpFee = lpFee; + self.slot0 = self.slot0.setLpFee(lpFee); } struct SwapParams { @@ -118,17 +107,17 @@ library BinPool { internal returns (BalanceDelta result, SwapState memory swapState) { - Slot0 memory slot0Cache = self.slot0; - swapState.activeId = slot0Cache.activeId; + BinSlot0 slot0Cache = self.slot0; + swapState.activeId = slot0Cache.activeId(); bool swapForY = params.swapForY; swapState.protocolFee = - swapForY ? slot0Cache.protocolFee.getZeroForOneFee() : slot0Cache.protocolFee.getOneForZeroFee(); + swapForY ? slot0Cache.protocolFee().getZeroForOneFee() : slot0Cache.protocolFee().getOneForZeroFee(); bool exactInput = params.amountSpecified < 0; { uint24 lpFee = params.lpFeeOverride.isOverride() ? params.lpFeeOverride.removeOverrideAndValidate(LPFeeLibrary.TEN_PERCENT_FEE) - : slot0Cache.lpFee; + : slot0Cache.lpFee(); /// @dev swap fee includes protocolFee (charged first) and lpFee swapState.swapFee = swapState.protocolFee == 0 ? lpFee : swapState.protocolFee.calculateSwapFee(lpFee); @@ -173,7 +162,7 @@ library BinPool { if (amountsInWithFees > 0) { /// @dev calc protocol fee for current bin, totalFee * protocolFee / (protocolFee + lpFee) - bytes32 pFee = totalFee.getProtocolFeeAmt(slot0Cache.protocolFee, swapState.swapFee); + bytes32 pFee = totalFee.getProtocolFeeAmt(slot0Cache.protocolFee(), swapState.swapFee); if (pFee != 0) { swapState.feeAmountToProtocol = swapState.feeAmountToProtocol.add(pFee); amountsInWithFees = amountsInWithFees.sub(pFee); @@ -200,7 +189,7 @@ library BinPool { if (amountsUnspecified == 0) revert BinPool__InsufficientAmountUnSpecified(); - self.slot0.activeId = swapState.activeId; + self.slot0 = self.slot0.setActiveId(swapState.activeId); unchecked { // uncheckeck as negating positive int128 is safe if (exactInput) { @@ -308,13 +297,14 @@ library BinPool { returns (BalanceDelta result, uint256[] memory ids, bytes32[] memory amounts) { ids = params.ids; + uint256 idsLength = ids.length; uint256[] memory amountsToBurn = params.amountsToBurn; - if (ids.length == 0 || ids.length != amountsToBurn.length) revert BinPool__InvalidBurnInput(); + if (idsLength == 0 || idsLength != amountsToBurn.length) revert BinPool__InvalidBurnInput(); bytes32 amountsOut; - amounts = new bytes32[](ids.length); - for (uint256 i; i < ids.length;) { + amounts = new bytes32[](idsLength); + for (uint256 i; i < idsLength;) { uint24 id = ids[i].safe24(); uint256 amountToBurn = amountsToBurn[i]; @@ -349,7 +339,7 @@ library BinPool { internal returns (BalanceDelta result, uint24 activeId) { - activeId = self.slot0.activeId; + activeId = self.slot0.activeId(); bytes32 amountIn = amount0.encode(amount1); bytes32 binReserves = self.reserveOfBin[activeId]; @@ -381,7 +371,8 @@ library BinPool { bytes32 amountsInToBin; bytes32 binFeeAmt; bytes32 binCompositionFee; - for (uint256 i; i < params.liquidityConfigs.length;) { + uint256 liquidityConfigsLength = params.liquidityConfigs.length; + for (uint256 i; i < liquidityConfigsLength;) { // fix stack too deep { bytes32 maxAmountsInToBin; @@ -426,8 +417,8 @@ library BinPool { bytes32 compositionFeeAmount ) { - Slot0 memory slot0Cache = self.slot0; - uint24 activeId = slot0Cache.activeId; + BinSlot0 slot0Cache = self.slot0; + uint24 activeId = slot0Cache.activeId(); bytes32 binReserves = self.reserveOfBin[id]; uint256 price = id.getPriceFromId(params.binStep); @@ -441,11 +432,11 @@ library BinPool { /// eg. current bin is 40/60 (a,b) but user tries to add liquidity with 50/50 ratio uint24 lpFee = params.lpFeeOverride.isOverride() ? params.lpFeeOverride.removeOverrideAndValidate(LPFeeLibrary.TEN_PERCENT_FEE) - : slot0Cache.lpFee; + : slot0Cache.lpFee(); bytes32 feesAmount; (feesAmount, feeAmountToProtocol) = - binReserves.getCompositionFeesAmount(slot0Cache.protocolFee, lpFee, amountsIn, supply, shares); + binReserves.getCompositionFeesAmount(slot0Cache.protocolFee(), lpFee, amountsIn, supply, shares); compositionFeeAmount = feesAmount; if (feesAmount != 0) { { @@ -492,7 +483,7 @@ library BinPool { } function checkPoolInitialized(State storage self) internal view { - if (self.slot0.activeId == 0) { + if (self.slot0.activeId() == 0) { // revert PoolNotInitialized(); assembly ("memory-safe") { mstore(0x00, 0x486aa307) diff --git a/src/pool-bin/types/BinSlot0.sol b/src/pool-bin/types/BinSlot0.sol new file mode 100644 index 0000000..ed7c1d0 --- /dev/null +++ b/src/pool-bin/types/BinSlot0.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @dev BinSlot0 is a packed version of solidity structure. + * Using the packaged version saves gas by not storing the structure fields in memory slots. + * + * Layout: + * 184 bits empty | 24 bits lpFee | 12 bits protocolFee 1->0 | 12 bits protocolFee 0->1 | 24 bits activeId + * + * Fields in the direction from the least significant bit: + * + * The current activeId + * uint24 activeId; + * + * Protocol fee, expressed in hundredths of a bip, upper 12 bits are for 1->0, and the lower 12 are for 0->1 + * the maximum is 1000 - meaning the maximum protocol fee is 0.1% + * the protocolFee is taken from the input first, then the lpFee is taken from the remaining input + * uint24 protocolFee; + * + * The current LP fee of the pool. If the pool is dynamic, this does not include the dynamic fee flag. + * uint24 lpFee; + */ +type BinSlot0 is bytes32; + +using BinSlot0Library for BinSlot0 global; + +/// @notice Library for getting and setting values in the Slot0 type +library BinSlot0Library { + uint24 internal constant MASK_24_BITS = 0xFFFFFF; + + uint8 internal constant PROTOCOL_FEE_OFFSET = 24; + uint8 internal constant LP_FEE_OFFSET = 48; + + //////////////////////////////////////////////////////////////////////////////////////// + // #### GETTERS #### + //////////////////////////////////////////////////////////////////////////////////////// + function activeId(BinSlot0 _packed) internal pure returns (uint24 _activeId) { + assembly ("memory-safe") { + _activeId := and(MASK_24_BITS, _packed) + } + } + + function protocolFee(BinSlot0 _packed) internal pure returns (uint24 _protocolFee) { + assembly ("memory-safe") { + _protocolFee := and(MASK_24_BITS, shr(PROTOCOL_FEE_OFFSET, _packed)) + } + } + + function lpFee(BinSlot0 _packed) internal pure returns (uint24 _lpFee) { + assembly ("memory-safe") { + _lpFee := and(MASK_24_BITS, shr(LP_FEE_OFFSET, _packed)) + } + } + + //////////////////////////////////////////////////////////////////////////////////////// + // #### SETTERS #### + //////////////////////////////////////////////////////////////////////////////////////// + function setActiveId(BinSlot0 _packed, uint24 _activeId) internal pure returns (BinSlot0 _result) { + assembly ("memory-safe") { + _result := or(and(not(MASK_24_BITS), _packed), and(MASK_24_BITS, _activeId)) + } + } + + function setProtocolFee(BinSlot0 _packed, uint24 _protocolFee) internal pure returns (BinSlot0 _result) { + assembly ("memory-safe") { + _result := + or( + and(not(shl(PROTOCOL_FEE_OFFSET, MASK_24_BITS)), _packed), + shl(PROTOCOL_FEE_OFFSET, and(MASK_24_BITS, _protocolFee)) + ) + } + } + + function setLpFee(BinSlot0 _packed, uint24 _lpFee) internal pure returns (BinSlot0 _result) { + assembly ("memory-safe") { + _result := + or(and(not(shl(LP_FEE_OFFSET, MASK_24_BITS)), _packed), shl(LP_FEE_OFFSET, and(MASK_24_BITS, _lpFee))) + } + } +} diff --git a/src/pool-cl/CLPoolManager.sol b/src/pool-cl/CLPoolManager.sol index 40be9aa..8211610 100644 --- a/src/pool-cl/CLPoolManager.sol +++ b/src/pool-cl/CLPoolManager.sol @@ -24,6 +24,7 @@ import {CLHooks} from "./libraries/CLHooks.sol"; import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol"; import {Currency} from "../types/Currency.sol"; import {TickMath} from "./libraries/TickMath.sol"; +import {CLSlot0} from "./types/CLSlot0.sol"; contract CLPoolManager is ICLPoolManager, ProtocolFees, Extsload { using SafeCast for int256; @@ -53,8 +54,8 @@ contract CLPoolManager is ICLPoolManager, ProtocolFees, Extsload { override returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee) { - CLPool.Slot0 memory slot0 = pools[id].slot0; - return (slot0.sqrtPriceX96, slot0.tick, slot0.protocolFee, slot0.lpFee); + CLSlot0 slot0 = pools[id].slot0; + return (slot0.sqrtPriceX96(), slot0.tick(), slot0.protocolFee(), slot0.lpFee()); } /// @inheritdoc ICLPoolManager diff --git a/src/pool-cl/libraries/CLPool.sol b/src/pool-cl/libraries/CLPool.sol index 3a07f3f..fcfa6fb 100644 --- a/src/pool-cl/libraries/CLPool.sol +++ b/src/pool-cl/libraries/CLPool.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.0; import {CLPosition} from "./CLPosition.sol"; import {TickMath} from "./TickMath.sol"; import {BalanceDelta, BalanceDeltaLibrary, toBalanceDelta} from "../../types/BalanceDelta.sol"; +import {CLSlot0} from "../types/CLSlot0.sol"; import {Tick} from "./Tick.sol"; import {TickBitmap} from "./TickBitmap.sol"; import {SqrtPriceMath} from "./SqrtPriceMath.sol"; @@ -47,22 +48,8 @@ library CLPool { /// @notice Thrown by donate if there is currently 0 liquidity, since the fees will not go to any liquidity providers error NoLiquidityToReceiveFees(); - struct Slot0 { - // the current price - uint160 sqrtPriceX96; - // the current tick - int24 tick; - // protocol fee, expressed in hundredths of a bip - // upper 12 bits are for 1->0, and the lower 12 are for 0->1 - // the maximum is 1000 - meaning the maximum protocol fee is 0.1% - // the protocolFee is taken from the input first, then the lpFee is taken from the remaining input - uint24 protocolFee; - // used for the lp fee, either static at initialize or dynamic via hook - uint24 lpFee; - } - struct State { - Slot0 slot0; + CLSlot0 slot0; /// @dev accumulated lp fees uint256 feeGrowthGlobal0X128; uint256 feeGrowthGlobal1X128; @@ -77,11 +64,12 @@ library CLPool { internal returns (int24 tick) { - if (self.slot0.sqrtPriceX96 != 0) revert PoolAlreadyInitialized(); + if (self.slot0.sqrtPriceX96() != 0) revert PoolAlreadyInitialized(); tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96); - self.slot0 = Slot0({sqrtPriceX96: sqrtPriceX96, tick: tick, protocolFee: protocolFee, lpFee: lpFee}); + self.slot0 = CLSlot0.wrap(bytes32(0)).setSqrtPriceX96(sqrtPriceX96).setTick(tick).setProtocolFee(protocolFee) + .setLpFee(lpFee); } struct ModifyLiquidityParams { @@ -110,13 +98,13 @@ library CLPool { int24 tickUpper = params.tickUpper; Tick.checkTicks(tickLower, tickUpper); - int24 tick = self.slot0.tick; + int24 tick = self.slot0.tick(); (uint256 feesOwed0, uint256 feesOwed1) = _updatePosition(self, params, tick); ///@dev calculate the tokens delta needed int128 liquidityDelta = params.liquidityDelta; if (liquidityDelta != 0) { - uint160 sqrtPriceX96 = self.slot0.sqrtPriceX96; + uint160 sqrtPriceX96 = self.slot0.sqrtPriceX96(); int128 amount0; int128 amount1; if (tick < tickLower) { @@ -202,7 +190,7 @@ library CLPool { returns (BalanceDelta balanceDelta, SwapState memory state) { // cache variables for gas optimization - Slot0 memory slot0Start = self.slot0; + CLSlot0 slot0Start = self.slot0; bool zeroForOne = params.zeroForOne; uint160 sqrtPriceLimitX96 = params.sqrtPriceLimitX96; @@ -211,10 +199,10 @@ library CLPool { // Under certain circumstances outlined below, the tick will preemptively reach MIN_TICK without swapping there if ( zeroForOne - ? (sqrtPriceLimitX96 >= slot0Start.sqrtPriceX96 || sqrtPriceLimitX96 <= TickMath.MIN_SQRT_RATIO) - : (sqrtPriceLimitX96 <= slot0Start.sqrtPriceX96 || sqrtPriceLimitX96 >= TickMath.MAX_SQRT_RATIO) + ? (sqrtPriceLimitX96 >= slot0Start.sqrtPriceX96() || sqrtPriceLimitX96 <= TickMath.MIN_SQRT_RATIO) + : (sqrtPriceLimitX96 <= slot0Start.sqrtPriceX96() || sqrtPriceLimitX96 >= TickMath.MAX_SQRT_RATIO) ) { - revert InvalidSqrtPriceLimit(slot0Start.sqrtPriceX96, sqrtPriceLimitX96); + revert InvalidSqrtPriceLimit(slot0Start.sqrtPriceX96(), sqrtPriceLimitX96); } // cache variables for gas optimization @@ -225,17 +213,17 @@ library CLPool { // init swap state { uint16 protocolFee = - zeroForOne ? slot0Start.protocolFee.getZeroForOneFee() : slot0Start.protocolFee.getOneForZeroFee(); + zeroForOne ? slot0Start.protocolFee().getZeroForOneFee() : slot0Start.protocolFee().getOneForZeroFee(); uint24 lpFee = params.lpFeeOverride.isOverride() ? params.lpFeeOverride.removeOverrideAndValidate(LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE) - : slot0Start.lpFee; + : slot0Start.lpFee(); state = SwapState({ amountSpecifiedRemaining: params.amountSpecified, amountCalculated: 0, - sqrtPriceX96: slot0Start.sqrtPriceX96, - tick: slot0Start.tick, + sqrtPriceX96: slot0Start.sqrtPriceX96(), + tick: slot0Start.tick(), swapFee: protocolFee == 0 ? lpFee : protocolFee.calculateSwapFee(lpFee), protocolFee: protocolFee, feeGrowthGlobalX128: zeroForOne ? self.feeGrowthGlobal0X128 : self.feeGrowthGlobal1X128, @@ -349,11 +337,11 @@ library CLPool { } // update tick and price if changed - if (state.tick != slot0Start.tick) { - (self.slot0.sqrtPriceX96, self.slot0.tick) = (state.sqrtPriceX96, state.tick); + if (state.tick != slot0Start.tick()) { + self.slot0 = self.slot0.setSqrtPriceX96(state.sqrtPriceX96).setTick(state.tick); } else { // otherwise just update the price - self.slot0.sqrtPriceX96 = state.sqrtPriceX96; + self.slot0 = self.slot0.setSqrtPriceX96(state.sqrtPriceX96); } // update liquidity if it changed @@ -467,25 +455,25 @@ library CLPool { if (amount1 > 0) { state.feeGrowthGlobal1X128 += UnsafeMath.simpleMulDiv(amount1, FixedPoint128.Q128, state.liquidity); } - tick = state.slot0.tick; + tick = state.slot0.tick(); } } function setProtocolFee(State storage self, uint24 protocolFee) internal { self.checkPoolInitialized(); - self.slot0.protocolFee = protocolFee; + self.slot0 = self.slot0.setProtocolFee(protocolFee); } /// @notice Only dynamic fee pools may update the lp fee. function setLPFee(State storage self, uint24 lpFee) internal { self.checkPoolInitialized(); - self.slot0.lpFee = lpFee; + self.slot0 = self.slot0.setLpFee(lpFee); } function checkPoolInitialized(State storage self) internal view { - if (self.slot0.sqrtPriceX96 == 0) { + if (self.slot0.sqrtPriceX96() == 0) { // revert PoolNotInitialized(); assembly ("memory-safe") { mstore(0x00, 0x486aa307) diff --git a/src/pool-cl/types/CLSlot0.sol b/src/pool-cl/types/CLSlot0.sol new file mode 100644 index 0000000..95b5325 --- /dev/null +++ b/src/pool-cl/types/CLSlot0.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @dev CLSlot0 is a packed version of solidity structure. + * Using the packaged version saves gas by not storing the structure fields in memory slots. + * + * Layout: + * 24 bits empty | 24 bits lpFee | 12 bits protocolFee 1->0 | 12 bits protocolFee 0->1 | 24 bits tick | 160 bits sqrtPriceX96 + * + * Fields in the direction from the least significant bit: + * + * The current price + * uint160 sqrtPriceX96; + * + * The current tick + * int24 tick; + * + * Protocol fee, expressed in hundredths of a bip, upper 12 bits are for 1->0, and the lower 12 are for 0->1 + * the maximum is 1000 - meaning the maximum protocol fee is 0.1% + * the protocolFee is taken from the input first, then the lpFee is taken from the remaining input + * uint24 protocolFee; + * + * The current LP fee of the pool. If the pool is dynamic, this does not include the dynamic fee flag. + * uint24 lpFee; + */ +type CLSlot0 is bytes32; + +using CLSlot0Library for CLSlot0 global; + +/// @notice Library for getting and setting values in the Slot0 type +library CLSlot0Library { + uint160 internal constant MASK_160_BITS = 0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + uint24 internal constant MASK_24_BITS = 0xFFFFFF; + + uint8 internal constant TICK_OFFSET = 160; + uint8 internal constant PROTOCOL_FEE_OFFSET = 184; + uint8 internal constant LP_FEE_OFFSET = 208; + + //////////////////////////////////////////////////////////////////////////////////////// + // #### GETTERS #### + //////////////////////////////////////////////////////////////////////////////////////// + function sqrtPriceX96(CLSlot0 _packed) internal pure returns (uint160 _sqrtPriceX96) { + assembly ("memory-safe") { + _sqrtPriceX96 := and(MASK_160_BITS, _packed) + } + } + + function tick(CLSlot0 _packed) internal pure returns (int24 _tick) { + assembly ("memory-safe") { + _tick := signextend(2, shr(TICK_OFFSET, _packed)) + } + } + + function protocolFee(CLSlot0 _packed) internal pure returns (uint24 _protocolFee) { + assembly ("memory-safe") { + _protocolFee := and(MASK_24_BITS, shr(PROTOCOL_FEE_OFFSET, _packed)) + } + } + + function lpFee(CLSlot0 _packed) internal pure returns (uint24 _lpFee) { + assembly ("memory-safe") { + _lpFee := and(MASK_24_BITS, shr(LP_FEE_OFFSET, _packed)) + } + } + + //////////////////////////////////////////////////////////////////////////////////////// + // #### SETTERS #### + //////////////////////////////////////////////////////////////////////////////////////// + function setSqrtPriceX96(CLSlot0 _packed, uint160 _sqrtPriceX96) internal pure returns (CLSlot0 _result) { + assembly ("memory-safe") { + _result := or(and(not(MASK_160_BITS), _packed), and(MASK_160_BITS, _sqrtPriceX96)) + } + } + + function setTick(CLSlot0 _packed, int24 _tick) internal pure returns (CLSlot0 _result) { + assembly ("memory-safe") { + _result := or(and(not(shl(TICK_OFFSET, MASK_24_BITS)), _packed), shl(TICK_OFFSET, and(MASK_24_BITS, _tick))) + } + } + + function setProtocolFee(CLSlot0 _packed, uint24 _protocolFee) internal pure returns (CLSlot0 _result) { + assembly ("memory-safe") { + _result := + or( + and(not(shl(PROTOCOL_FEE_OFFSET, MASK_24_BITS)), _packed), + shl(PROTOCOL_FEE_OFFSET, and(MASK_24_BITS, _protocolFee)) + ) + } + } + + function setLpFee(CLSlot0 _packed, uint24 _lpFee) internal pure returns (CLSlot0 _result) { + assembly ("memory-safe") { + _result := + or(and(not(shl(LP_FEE_OFFSET, MASK_24_BITS)), _packed), shl(LP_FEE_OFFSET, and(MASK_24_BITS, _lpFee))) + } + } +} diff --git a/test/pool-bin/types/BinSlot0.t.sol b/test/pool-bin/types/BinSlot0.t.sol new file mode 100644 index 0000000..58a42dc --- /dev/null +++ b/test/pool-bin/types/BinSlot0.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {BinSlot0, BinSlot0Library} from "../../../src/pool-bin/types/BinSlot0.sol"; + +contract BinSlot0Test is Test { + function test_slot0_constants_masks() public pure { + assertEq(BinSlot0Library.MASK_24_BITS, type(uint24).max); + } + + function test_fuzz_slot0_pack_unpack(uint24 activeId, uint24 protocolFee, uint24 lpFee) public pure { + // pack starting from "lowest" field + BinSlot0 _slot0 = BinSlot0.wrap(bytes32(0)).setActiveId(activeId).setProtocolFee(protocolFee).setLpFee(lpFee); + + assertEq(_slot0.activeId(), activeId); + assertEq(_slot0.protocolFee(), protocolFee); + assertEq(_slot0.lpFee(), lpFee); + + // pack starting from "highest" field + _slot0 = BinSlot0.wrap(bytes32(0)).setLpFee(lpFee).setProtocolFee(protocolFee).setActiveId(activeId); + + assertEq(_slot0.activeId(), activeId); + assertEq(_slot0.protocolFee(), protocolFee); + assertEq(_slot0.lpFee(), lpFee); + } +} diff --git a/test/pool-cl/CLPoolManager.t.sol b/test/pool-cl/CLPoolManager.t.sol index 3b3e308..380939c 100644 --- a/test/pool-cl/CLPoolManager.t.sol +++ b/test/pool-cl/CLPoolManager.t.sol @@ -38,6 +38,7 @@ import {SafeCast} from "../../src/libraries/SafeCast.sol"; import {NoIsolate} from "../helpers/NoIsolate.sol"; import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol"; import {CLPoolGetter} from "./helpers/CLPoolGetter.sol"; +import {CLSlot0} from "../../src/pool-cl/types/CLSlot0.sol"; contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnapshot { using CLPoolParametersHelper for bytes32; @@ -346,12 +347,12 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps poolManager.initialize(key, TickMath.MIN_SQRT_RATIO); - (CLPool.Slot0 memory slot0, uint256 feeGrowthGlobal0X128, uint256 feeGrowthGlobal1X128, uint128 liquidity) = + (CLSlot0 slot0, uint256 feeGrowthGlobal0X128, uint256 feeGrowthGlobal1X128, uint128 liquidity) = poolManager.pools(key.toId()); - assertEq(slot0.sqrtPriceX96, TickMath.MIN_SQRT_RATIO); - assertEq(slot0.tick, TickMath.MIN_TICK); - assertEq(slot0.protocolFee, 0); + assertEq(slot0.sqrtPriceX96(), TickMath.MIN_SQRT_RATIO); + assertEq(slot0.tick(), TickMath.MIN_TICK); + assertEq(slot0.protocolFee(), 0); assertEq(feeGrowthGlobal0X128, 0); assertEq(feeGrowthGlobal1X128, 0); assertEq(liquidity, 0); @@ -419,9 +420,9 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps ); poolManager.initialize(key, sqrtPriceX96); - (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); - assertEq(slot0.sqrtPriceX96, sqrtPriceX96); - assertEq(slot0.protocolFee, 0); + (CLSlot0 slot0,,,) = poolManager.pools(key.toId()); + assertEq(slot0.sqrtPriceX96(), sqrtPriceX96); + assertEq(slot0.protocolFee(), 0); } } @@ -451,10 +452,10 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps ); poolManager.initialize(key, sqrtPriceX96); - (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); - assertEq(slot0.sqrtPriceX96, sqrtPriceX96); - assertEq(slot0.protocolFee, 0); - assertEq(slot0.tick, TickMath.getTickAtSqrtRatio(sqrtPriceX96)); + (CLSlot0 slot0,,,) = poolManager.pools(key.toId()); + assertEq(slot0.sqrtPriceX96(), sqrtPriceX96); + assertEq(slot0.protocolFee(), 0); + assertEq(slot0.tick(), TickMath.getTickAtSqrtRatio(sqrtPriceX96)); } function test_initialize_succeedsWithHooks(uint160 sqrtPriceX96) public { @@ -483,8 +484,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps vm.expectCall(address(hookAddr), 0, afterPayload, 1); poolManager.initialize(key, sqrtPriceX96); - (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); - assertEq(slot0.sqrtPriceX96, sqrtPriceX96); + (CLSlot0 slot0,,,) = poolManager.pools(key.toId()); + assertEq(slot0.sqrtPriceX96(), sqrtPriceX96); (Currency curr0, Currency curr1, IHooks hooks, IPoolManager pm, uint24 fee, bytes32 parameters) = poolManager.poolIdToPoolKey(key.toId()); @@ -540,8 +541,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps }); poolManager.initialize(key, sqrtPriceX96); - (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); - assertEq(slot0.sqrtPriceX96, sqrtPriceX96); + (CLSlot0 slot0,,,) = poolManager.pools(key.toId()); + assertEq(slot0.sqrtPriceX96(), sqrtPriceX96); } function test_initialize_revertsWithIdenticalTokens(uint160 sqrtPriceX96) public { @@ -2661,8 +2662,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps }); poolManager.initialize(key, SQRT_RATIO_1_1); - (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); - assertEq(slot0.protocolFee, 0); + (CLSlot0 slot0,,,) = poolManager.pools(key.toId()); + assertEq(slot0.protocolFee(), 0); poolManager.setProtocolFeeController(IProtocolFeeController(address(feeController))); feeController.setProtocolFeeForPool(key.toId(), protocolFee); @@ -2686,8 +2687,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps feeController.setProtocolFeeForPool(key.toId(), protocolFee); poolManager.initialize(key, SQRT_RATIO_1_1); - (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); - assertEq(slot0.protocolFee, protocolFee); + (CLSlot0 slot0,,,) = poolManager.pools(key.toId()); + assertEq(slot0.protocolFee(), protocolFee); } function testCollectProtocolFees_ERC20_returnsAllFeesIf0IsProvidedAsParameter() public { @@ -2708,8 +2709,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps feeController.setProtocolFeeForPool(key.toId(), protocolFee); poolManager.initialize(key, SQRT_RATIO_1_1); - (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); - assertEq(slot0.protocolFee, protocolFee); + (CLSlot0 slot0,,,) = poolManager.pools(key.toId()); + assertEq(slot0.protocolFee(), protocolFee); ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-120, 120, 10 ether, 0); @@ -2749,8 +2750,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps feeController.setProtocolFeeForPool(key.toId(), protocolFee); poolManager.initialize(key, SQRT_RATIO_1_1); - (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); - assertEq(slot0.protocolFee, protocolFee); + (CLSlot0 slot0,,,) = poolManager.pools(key.toId()); + assertEq(slot0.protocolFee(), protocolFee); ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-120, 120, 10 ether, 0); @@ -2790,8 +2791,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps feeController.setProtocolFeeForPool(key.toId(), protocolFee); poolManager.initialize(key, SQRT_RATIO_1_1); - (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); - assertEq(slot0.protocolFee, protocolFee); + (CLSlot0 slot0,,,) = poolManager.pools(key.toId()); + assertEq(slot0.protocolFee(), protocolFee); ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-120, 120, 10 ether, 0); diff --git a/test/pool-cl/CLProtocolFees.t.sol b/test/pool-cl/CLProtocolFees.t.sol index f9320f0..4157727 100644 --- a/test/pool-cl/CLProtocolFees.t.sol +++ b/test/pool-cl/CLProtocolFees.t.sol @@ -27,6 +27,7 @@ import {PoolKey} from "../../src/types/PoolKey.sol"; import {IVault} from "../../src/interfaces/IVault.sol"; import {ProtocolFeeLibrary} from "../../src/libraries/ProtocolFeeLibrary.sol"; import {CLPoolGetter} from "./helpers/CLPoolGetter.sol"; +import {CLSlot0} from "../../src/pool-cl/types/CLSlot0.sol"; contract CLProtocolFeesTest is Test, Deployers, TokenFixture, GasSnapshot { using Hooks for IHooks; @@ -73,8 +74,8 @@ contract CLProtocolFeesTest is Test, Deployers, TokenFixture, GasSnapshot { } function testSetProtocolFeeControllerFuzz(uint24 protocolFee) public { - (CLPool.Slot0 memory slot0,,,) = manager.pools(key.toId()); - assertEq(slot0.protocolFee, 0); + (CLSlot0 slot0,,,) = manager.pools(key.toId()); + assertEq(slot0.protocolFee(), 0); manager.setProtocolFeeController(IProtocolFeeController(protocolFeeController)); @@ -92,7 +93,7 @@ contract CLProtocolFeesTest is Test, Deployers, TokenFixture, GasSnapshot { manager.setProtocolFee(key, protocolFee); (slot0,,,) = manager.pools(key.toId()); - assertEq(slot0.protocolFee, protocolFee); + assertEq(slot0.protocolFee(), protocolFee); } function testNoProtocolFee(uint24 protocolFee) public { @@ -104,8 +105,8 @@ contract CLProtocolFeesTest is Test, Deployers, TokenFixture, GasSnapshot { vm.prank(address(protocolFeeController)); manager.setProtocolFee(key, protocolFee); - (CLPool.Slot0 memory slot0,,,) = manager.pools(key.toId()); - assertEq(slot0.protocolFee, protocolFee); + (CLSlot0 slot0,,,) = manager.pools(key.toId()); + assertEq(slot0.protocolFee(), protocolFee); int256 liquidityDelta = 10000; ICLPoolManager.ModifyLiquidityParams memory params = @@ -143,8 +144,8 @@ contract CLProtocolFeesTest is Test, Deployers, TokenFixture, GasSnapshot { vm.prank(address(protocolFeeController)); manager.setProtocolFee(key, protocolFee); - (CLPool.Slot0 memory slot0,,,) = manager.pools(key.toId()); - assertEq(slot0.protocolFee, protocolFee); + (CLSlot0 slot0,,,) = manager.pools(key.toId()); + assertEq(slot0.protocolFee(), protocolFee); ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-120, 120, 10e18, 0); router.modifyPosition(key, params, ZERO_BYTES); diff --git a/test/pool-cl/helpers/CLPoolGetter.sol b/test/pool-cl/helpers/CLPoolGetter.sol index e38b3bc..90199d4 100644 --- a/test/pool-cl/helpers/CLPoolGetter.sol +++ b/test/pool-cl/helpers/CLPoolGetter.sol @@ -4,19 +4,17 @@ pragma solidity ^0.8.24; import {CLPoolManager} from "../../../src/pool-cl/CLPoolManager.sol"; import {PoolId, PoolIdLibrary} from "../../../src/types/PoolId.sol"; import {CLPool} from "../../../src/pool-cl/libraries/CLPool.sol"; +import {CLSlot0} from "../../../src/pool-cl/types/CLSlot0.sol"; library CLPoolGetter { function pools(CLPoolManager manager, PoolId id) internal view - returns ( - CLPool.Slot0 memory slot0, - uint256 feeGrowthGlobal0X128, - uint256 feeGrowthGlobal1X128, - uint128 liquidity - ) + returns (CLSlot0 slot0, uint256 feeGrowthGlobal0X128, uint256 feeGrowthGlobal1X128, uint128 liquidity) { - (slot0.sqrtPriceX96, slot0.tick, slot0.protocolFee, slot0.lpFee) = manager.getSlot0(id); + (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee) = manager.getSlot0(id); + slot0 = CLSlot0.wrap(bytes32(0)).setSqrtPriceX96(sqrtPriceX96).setTick(tick).setProtocolFee(protocolFee) + .setLpFee(lpFee); (feeGrowthGlobal0X128, feeGrowthGlobal1X128) = manager.getFeeGrowthGlobals(id); liquidity = manager.getLiquidity(id); } diff --git a/test/pool-cl/libraries/CLPool.t.sol b/test/pool-cl/libraries/CLPool.t.sol index e7bb678..5886667 100644 --- a/test/pool-cl/libraries/CLPool.t.sol +++ b/test/pool-cl/libraries/CLPool.t.sol @@ -19,6 +19,7 @@ import {ICLPoolManager} from "../../../src/pool-cl/interfaces/ICLPoolManager.sol import {LPFeeLibrary} from "../../../src/libraries/LPFeeLibrary.sol"; import {ProtocolFeeLibrary} from "../../../src/libraries/ProtocolFeeLibrary.sol"; import {BalanceDelta, BalanceDeltaLibrary} from "../../../src/types/BalanceDelta.sol"; +import {CLSlot0} from "../../../src/pool-cl/types/CLSlot0.sol"; contract PoolTest is Test { using CLPool for CLPool.State; @@ -36,12 +37,12 @@ contract PoolTest is Test { state.initialize(sqrtPriceX96, protocolFee, lpFee); } else { state.initialize(sqrtPriceX96, protocolFee, lpFee); - assertEq(state.slot0.sqrtPriceX96, sqrtPriceX96); - assertEq(state.slot0.protocolFee, protocolFee); - assertEq(state.slot0.tick, TickMath.getTickAtSqrtRatio(sqrtPriceX96)); - assertLt(state.slot0.tick, TickMath.MAX_TICK); - assertGt(state.slot0.tick, TickMath.MIN_TICK - 1); - assertEq(state.slot0.lpFee, lpFee); + assertEq(state.slot0.sqrtPriceX96(), sqrtPriceX96); + assertEq(state.slot0.protocolFee(), protocolFee); + assertEq(state.slot0.tick(), TickMath.getTickAtSqrtRatio(sqrtPriceX96)); + assertLt(state.slot0.tick(), TickMath.MAX_TICK); + assertGt(state.slot0.tick(), TickMath.MIN_TICK - 1); + assertEq(state.slot0.lpFee(), lpFee); } } @@ -126,7 +127,7 @@ contract PoolTest is Test { testModifyPosition(sqrtPriceX96, modifyLiquidityParams, lpFee); swapParams.tickSpacing = modifyLiquidityParams.tickSpacing; - CLPool.Slot0 memory slot0 = state.slot0; + CLSlot0 slot0 = state.slot0; // avoid lpFee override valid if ( @@ -141,30 +142,30 @@ contract PoolTest is Test { : lpFee; if (swapParams.zeroForOne) { - if (swapParams.sqrtPriceLimitX96 >= slot0.sqrtPriceX96) { + if (swapParams.sqrtPriceLimitX96 >= slot0.sqrtPriceX96()) { vm.expectRevert( abi.encodeWithSelector( - CLPool.InvalidSqrtPriceLimit.selector, slot0.sqrtPriceX96, swapParams.sqrtPriceLimitX96 + CLPool.InvalidSqrtPriceLimit.selector, slot0.sqrtPriceX96(), swapParams.sqrtPriceLimitX96 ) ); } else if (swapParams.sqrtPriceLimitX96 <= TickMath.MIN_SQRT_RATIO) { vm.expectRevert( abi.encodeWithSelector( - CLPool.InvalidSqrtPriceLimit.selector, slot0.sqrtPriceX96, swapParams.sqrtPriceLimitX96 + CLPool.InvalidSqrtPriceLimit.selector, slot0.sqrtPriceX96(), swapParams.sqrtPriceLimitX96 ) ); } } else if (!swapParams.zeroForOne) { - if (swapParams.sqrtPriceLimitX96 <= slot0.sqrtPriceX96) { + if (swapParams.sqrtPriceLimitX96 <= slot0.sqrtPriceX96()) { vm.expectRevert( abi.encodeWithSelector( - CLPool.InvalidSqrtPriceLimit.selector, slot0.sqrtPriceX96, swapParams.sqrtPriceLimitX96 + CLPool.InvalidSqrtPriceLimit.selector, slot0.sqrtPriceX96(), swapParams.sqrtPriceLimitX96 ) ); } else if (swapParams.sqrtPriceLimitX96 >= TickMath.MAX_SQRT_RATIO) { vm.expectRevert( abi.encodeWithSelector( - CLPool.InvalidSqrtPriceLimit.selector, slot0.sqrtPriceX96, swapParams.sqrtPriceLimitX96 + CLPool.InvalidSqrtPriceLimit.selector, slot0.sqrtPriceX96(), swapParams.sqrtPriceLimitX96 ) ); } @@ -182,20 +183,20 @@ contract PoolTest is Test { if ( modifyLiquidityParams.liquidityDelta == 0 - || (swapParams.zeroForOne && slot0.tick < modifyLiquidityParams.tickLower) - || (!swapParams.zeroForOne && slot0.tick >= modifyLiquidityParams.tickUpper) + || (swapParams.zeroForOne && slot0.tick() < modifyLiquidityParams.tickLower) + || (!swapParams.zeroForOne && slot0.tick() >= modifyLiquidityParams.tickUpper) ) { // no liquidity, hence all the way to the limit if (swapParams.zeroForOne) { - assertEq(state.slot0.sqrtPriceX96, swapParams.sqrtPriceLimitX96); + assertEq(state.slot0.sqrtPriceX96(), swapParams.sqrtPriceLimitX96); } else { - assertEq(state.slot0.sqrtPriceX96, swapParams.sqrtPriceLimitX96); + assertEq(state.slot0.sqrtPriceX96(), swapParams.sqrtPriceLimitX96); } } else { if (swapParams.zeroForOne) { - assertGe(state.slot0.sqrtPriceX96, swapParams.sqrtPriceLimitX96); + assertGe(state.slot0.sqrtPriceX96(), swapParams.sqrtPriceLimitX96); } else { - assertLe(state.slot0.sqrtPriceX96, swapParams.sqrtPriceLimitX96); + assertLe(state.slot0.sqrtPriceX96(), swapParams.sqrtPriceLimitX96); } } } diff --git a/test/pool-cl/libraries/Tick.t.sol b/test/pool-cl/libraries/Tick.t.sol index 515acbf..9df5881 100644 --- a/test/pool-cl/libraries/Tick.t.sol +++ b/test/pool-cl/libraries/Tick.t.sol @@ -38,11 +38,11 @@ contract TickTest is Test, GasSnapshot { uint256 feeGrowthGlobal0X128, uint256 feeGrowthGlobal1X128 ) internal returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) { - pool.slot0.tick = tickCurrent; + pool.slot0 = pool.slot0.setTick(tickCurrent); pool.feeGrowthGlobal0X128 = feeGrowthGlobal0X128; pool.feeGrowthGlobal1X128 = feeGrowthGlobal1X128; return pool.ticks.getFeeGrowthInside( - tickLower, tickUpper, pool.slot0.tick, pool.feeGrowthGlobal0X128, pool.feeGrowthGlobal1X128 + tickLower, tickUpper, pool.slot0.tick(), pool.feeGrowthGlobal0X128, pool.feeGrowthGlobal1X128 ); } @@ -54,7 +54,7 @@ contract TickTest is Test, GasSnapshot { uint256 feeGrowthGlobal1X128, bool upper ) internal returns (bool flipped, uint128 liquidityGrossAfter) { - pool.slot0.tick = tickCurrent; + pool.slot0 = pool.slot0.setTick(tickCurrent); pool.feeGrowthGlobal0X128 = feeGrowthGlobal0X128; pool.feeGrowthGlobal1X128 = feeGrowthGlobal1X128; flipped = pool.ticks.update( @@ -79,7 +79,7 @@ contract TickTest is Test, GasSnapshot { bool upper, uint128 maxLiquidityPerTick ) internal returns (bool flipped, uint128 liquidityGrossAfter) { - pool.slot0.tick = tickCurrent; + pool.slot0 = pool.slot0.setTick(tickCurrent); pool.feeGrowthGlobal0X128 = feeGrowthGlobal0X128; pool.feeGrowthGlobal1X128 = feeGrowthGlobal1X128; flipped = pool.ticks.update( diff --git a/test/pool-cl/types/CLSlot0.t.sol b/test/pool-cl/types/CLSlot0.t.sol new file mode 100644 index 0000000..139c087 --- /dev/null +++ b/test/pool-cl/types/CLSlot0.t.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {CLSlot0, CLSlot0Library} from "../../../src/pool-cl/types/CLSlot0.sol"; + +contract CLSlot0Test is Test { + function test_slot0_constants_masks() public pure { + assertEq(CLSlot0Library.MASK_160_BITS, type(uint160).max); + assertEq(CLSlot0Library.MASK_24_BITS, type(uint24).max); + } + + function test_fuzz_slot0_pack_unpack(uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee) + public + pure + { + // pack starting from "lowest" field + CLSlot0 _slot0 = CLSlot0.wrap(bytes32(0)).setSqrtPriceX96(sqrtPriceX96).setTick(tick).setProtocolFee( + protocolFee + ).setLpFee(lpFee); + + assertEq(_slot0.sqrtPriceX96(), sqrtPriceX96); + assertEq(_slot0.tick(), tick); + assertEq(_slot0.protocolFee(), protocolFee); + assertEq(_slot0.lpFee(), lpFee); + + // pack starting from "highest" field + _slot0 = CLSlot0.wrap(bytes32(0)).setLpFee(lpFee).setProtocolFee(protocolFee).setTick(tick).setSqrtPriceX96( + sqrtPriceX96 + ); + + assertEq(_slot0.sqrtPriceX96(), sqrtPriceX96); + assertEq(_slot0.tick(), tick); + assertEq(_slot0.protocolFee(), protocolFee); + assertEq(_slot0.lpFee(), lpFee); + } +}