From fa267eda1897dcdceed3725f677c5baf801a4ea7 Mon Sep 17 00:00:00 2001 From: Mark Toda Date: Mon, 8 Dec 2025 17:58:49 -0500 Subject: [PATCH 1/7] feat: add fee override This commit adds behavior where the fee setter can override the fee for specific pools if needed --- src/feeAdapters/V3FeeAdapter.sol | 82 ++++++++++-- src/interfaces/IV3FeeAdapter.sol | 26 ++++ test/V3FeeAdapter.t.sol | 217 +++++++++++++++++++++++++++++++ 3 files changed, 312 insertions(+), 13 deletions(-) diff --git a/src/feeAdapters/V3FeeAdapter.sol b/src/feeAdapters/V3FeeAdapter.sol index 381d8bd..7ce4ea8 100644 --- a/src/feeAdapters/V3FeeAdapter.sol +++ b/src/feeAdapters/V3FeeAdapter.sol @@ -35,6 +35,13 @@ contract V3FeeAdapter is IV3FeeAdapter, Owned { /// @inheritdoc IV3FeeAdapter mapping(uint24 feeTier => uint8 defaultFeeValue) public defaultFees; + /// @inheritdoc IV3FeeAdapter + mapping(address pool => uint8 feeOverride) public poolFeeOverrides; + + /// @notice Sentinel value stored in poolFeeOverrides to represent "override to 0" + /// @dev We use 255 because it's an invalid fee value (both nibbles = 15, outside valid range) + uint8 internal constant OVERRIDE_TO_ZERO = type(uint8).max; + /// @return The fee tiers that are enabled on the factory. Iterable so that the protocol fee for /// pools of the same pair can be activated with the same merkle proof. /// @dev Returns four enabled fee tiers: 100, 500, 3000, 10000. May return more if more are @@ -100,18 +107,27 @@ contract V3FeeAdapter is IV3FeeAdapter, Owned { /// @inheritdoc IV3FeeAdapter function setDefaultFeeByFeeTier(uint24 feeTier, uint8 defaultFeeValue) external onlyFeeSetter { require(_feeTierExists(feeTier), InvalidFeeTier()); - // Extract the two 4-bit values - uint8 feeProtocol0 = defaultFeeValue % 16; - uint8 feeProtocol1 = defaultFeeValue >> 4; - // Validate both values match pool requirements: must be 0 or in range [4, 10] - require( - (feeProtocol0 == 0 || (feeProtocol0 >= 4 && feeProtocol0 <= 10)) - && (feeProtocol1 == 0 || (feeProtocol1 >= 4 && feeProtocol1 <= 10)), - InvalidFeeValue() - ); + _validateFeeValue(defaultFeeValue); defaultFees[feeTier] = defaultFeeValue; } + /// @inheritdoc IV3FeeAdapter + function overridePoolFee(address pool, uint8 feeOverride) external onlyFeeSetter { + _validateFeeValue(feeOverride); + // Store sentinel value if overriding to 0, otherwise store the actual value + poolFeeOverrides[pool] = feeOverride == 0 ? OVERRIDE_TO_ZERO : feeOverride; + emit PoolFeeOverrideSet(pool, feeOverride); + + // Update the pool's fee immediately if the pool is initialized + _setProtocolFeeForPool(pool); + } + + /// @inheritdoc IV3FeeAdapter + function removePoolFeeOverride(address pool) external onlyFeeSetter { + delete poolFeeOverrides[pool]; + emit PoolFeeOverrideRemoved(pool); + } + /// @inheritdoc IV3FeeAdapter function setFeeSetter(address newFeeSetter) external onlyOwner { feeSetter = newFeeSetter; @@ -164,20 +180,60 @@ contract V3FeeAdapter is IV3FeeAdapter, Owned { } } - /// @notice Sets the protocol fee for a specific pool based on its fee tier + /// @notice Sets the protocol fee for a specific pool based on its fee tier or override /// @dev Only sets the fee for initialized pools (sqrtPriceX96 != 0) - /// The feeValue encodes both fee0 (lower 4 bits) and fee1 (upper 4 bits) + /// If pool has an override, uses the override value; otherwise uses the default for the fee + /// tier The feeValue encodes both fee0 (lower 4 bits) and fee1 (upper 4 bits) /// @param pool The address of the Uniswap V3 pool - /// @param feeTier The fee tier of the pool, used to look up the default fee value + /// @param feeTier The fee tier of the pool, used to look up the default fee value if no override + /// exists function _setProtocolFee(address pool, uint24 feeTier) internal { // Check if pool is initialized by verifying sqrtPriceX96 is non-zero (uint160 sqrtPriceX96,,,,,,) = IUniswapV3Pool(pool).slot0(); if (sqrtPriceX96 == 0) return; // Pool exists but not initialized, skip - uint8 feeValue = defaultFees[feeTier]; + uint8 feeValue = _getEffectiveFee(pool, feeTier); IUniswapV3PoolOwnerActions(pool).setFeeProtocol(feeValue % 16, feeValue >> 4); } + /// @notice Sets the protocol fee for a specific pool using its override or fee tier default + /// @dev Only sets the fee for initialized pools. Used by overridePoolFee to immediately apply + /// changes. @param pool The address of the Uniswap V3 pool + function _setProtocolFeeForPool(address pool) internal { + // Check if pool is initialized by verifying sqrtPriceX96 is non-zero + (uint160 sqrtPriceX96,,,,,,) = IUniswapV3Pool(pool).slot0(); + if (sqrtPriceX96 == 0) return; // Pool exists but not initialized, skip + + uint8 feeValue = _getEffectiveFee(pool, IUniswapV3Pool(pool).fee()); + IUniswapV3PoolOwnerActions(pool).setFeeProtocol(feeValue % 16, feeValue >> 4); + } + + /// @notice Returns the effective fee for a pool, considering overrides + /// @param pool The pool address + /// @param feeTier The fee tier of the pool (used for default lookup) + /// @return The fee value to apply (0 if sentinel, override if set, otherwise default) + function _getEffectiveFee(address pool, uint24 feeTier) internal view returns (uint8) { + uint8 override_ = poolFeeOverrides[pool]; + if (override_ == OVERRIDE_TO_ZERO) return 0; + if (override_ != 0) return override_; + return defaultFees[feeTier]; + } + + /// @notice Validates that a fee value meets protocol requirements + /// @dev Both 4-bit fee values must be 0 or in the range [4, 10] + /// @param feeValue The packed fee value to validate (token1Fee << 4 | token0Fee) + function _validateFeeValue(uint8 feeValue) internal pure { + // Extract the two 4-bit values + uint8 feeProtocol0 = feeValue % 16; + uint8 feeProtocol1 = feeValue >> 4; + // Validate both values match pool requirements: must be 0 or in range [4, 10] + require( + (feeProtocol0 == 0 || (feeProtocol0 >= 4 && feeProtocol0 <= 10)) + && (feeProtocol1 == 0 || (feeProtocol1 >= 4 && feeProtocol1 <= 10)), + InvalidFeeValue() + ); + } + /// @notice Computes a double hash of token addresses for Merkle tree verification /// @dev Performs keccak256(abi.encode(keccak256(abi.encode(token0, token1)))) /// Uses assembly for gas optimization diff --git a/src/interfaces/IV3FeeAdapter.sol b/src/interfaces/IV3FeeAdapter.sol index c17e1d7..b012f90 100644 --- a/src/interfaces/IV3FeeAdapter.sol +++ b/src/interfaces/IV3FeeAdapter.sol @@ -20,6 +20,15 @@ interface IV3FeeAdapter { /// requirements. error InvalidFeeValue(); + /// @notice Emitted when a pool-specific fee override is set + /// @param pool The pool address the override applies to + /// @param feeOverride The override fee value (packed as token1Fee << 4 | token0Fee) + event PoolFeeOverrideSet(address indexed pool, uint8 feeOverride); + + /// @notice Emitted when a pool-specific fee override is removed + /// @param pool The pool address the override was removed from + event PoolFeeOverrideRemoved(address indexed pool); + /// @notice The input parameters for the collection. struct CollectParams { /// @param pool The pool to collect fees from. @@ -105,6 +114,23 @@ interface IV3FeeAdapter { /// inclusive interval [4, 10]. The fee value is packed (token1Fee << 4 | token0Fee) function setDefaultFeeByFeeTier(uint24 feeTier, uint8 defaultFeeValue) external; + /// @notice Sets a fee override for a specific pool, bypassing the fee tier default + /// @dev Only callable by `feeSetter`. The override immediately updates the pool's fee. + /// @param pool The address of the pool to set the override for + /// @param feeOverride The fee override value (packed as token1Fee << 4 | token0Fee) + function overridePoolFee(address pool, uint8 feeOverride) external; + + /// @notice Removes a fee override for a specific pool, reverting to fee tier defaults + /// @dev Only callable by `feeSetter`. Does not immediately update the pool's fee. + /// @param pool The address of the pool to remove the override from + function removePoolFeeOverride(address pool) external; + + /// @notice Returns the stored fee override for a specific pool + /// @dev Returns 0 if no override, 255 (sentinel) if override to 0, otherwise the override value + /// @param pool The pool address to query + /// @return feeOverride The stored override value + function poolFeeOverrides(address pool) external view returns (uint8 feeOverride); + /// @notice Triggers a fee update for a single pool with merkle proof verification. /// @param pool The pool address to update the fee for. /// @param merkleProof The merkle proof corresponding to the set merkle root. diff --git a/test/V3FeeAdapter.t.sol b/test/V3FeeAdapter.t.sol index bd42a6d..5766314 100644 --- a/test/V3FeeAdapter.t.sol +++ b/test/V3FeeAdapter.t.sol @@ -624,4 +624,221 @@ contract V3FeeAdapterTest is ProtocolFeesTestBase { if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); return IV3FeeAdapter.Pair({token0: tokenA, token1: tokenB}); } + + // ==================== Pool Fee Override Tests ==================== + + function test_overridePoolFee_success() public { + uint8 overrideFee = 7 << 4 | 6; // fee1=7, fee0=6 + + vm.prank(feeSetter); + vm.expectEmit(true, false, false, true); + emit IV3FeeAdapter.PoolFeeOverrideSet(pool, overrideFee); + feeAdapter.overridePoolFee(pool, overrideFee); + + // Verify override is stored + assertEq(feeAdapter.poolFeeOverrides(pool), overrideFee); + + // Verify pool fee was updated immediately + assertEq(_getProtocolFees(pool), overrideFee); + } + + function test_overridePoolFee_overridesDefaultFee() public { + uint8 defaultFee = 10 << 4 | 10; + uint8 overrideFee = 5 << 4 | 5; + + // Set default fee for the fee tier + vm.startPrank(feeSetter); + feeAdapter.setDefaultFeeByFeeTier(3000, defaultFee); + + // Set override for specific pool + feeAdapter.overridePoolFee(pool, overrideFee); + vm.stopPrank(); + + // Verify pool has override fee, not default + assertEq(_getProtocolFees(pool), overrideFee); + + // Trigger fee update via merkle proof and verify override is still used + bytes32[] memory leaves = new bytes32[](2); + leaves[0] = _hashLeaf(pool); + leaves[1] = _hashLeaf(pool1); + bytes32 root = merkle.getRoot(leaves); + + vm.prank(feeSetter); + feeAdapter.setMerkleRoot(root); + + bytes32[] memory proof = merkle.getProof(leaves, 0); + feeAdapter.triggerFeeUpdate(pool, proof); + + // Override should still be in effect + assertEq(_getProtocolFees(pool), overrideFee); + } + + function test_overridePoolFee_revertsUnauthorized() public { + address unauthorized = makeAddr("unauthorized"); + vm.prank(unauthorized); + vm.expectRevert(IV3FeeAdapter.Unauthorized.selector); + feeAdapter.overridePoolFee(pool, 0x55); + } + + function test_overridePoolFee_revertsInvalidFeeValue() public { + uint8 invalidFee = 15 << 4 | 15; // Both values > 10 + + vm.prank(feeSetter); + vm.expectRevert(IV3FeeAdapter.InvalidFeeValue.selector); + feeAdapter.overridePoolFee(pool, invalidFee); + } + + function test_overridePoolFee_allowsZeroFee() public { + // Zero fee should be valid (disables protocol fee) + uint8 zeroFee = 0; + uint8 OVERRIDE_TO_ZERO = type(uint8).max; // sentinel value + + vm.prank(feeSetter); + feeAdapter.overridePoolFee(pool, zeroFee); + + // Stored as sentinel value, but effective fee is 0 + assertEq(feeAdapter.poolFeeOverrides(pool), OVERRIDE_TO_ZERO); + assertEq(_getProtocolFees(pool), zeroFee); + } + + function test_overridePoolFee_skipsUninitializedPool() public { + // Create uninitialized pool + MockERC20 token2 = new MockERC20("Token2", "TKN2", 18); + address uninitializedPool = factory.createPool(address(token2), address(mockToken1), 3000); + + uint8 overrideFee = 6 << 4 | 6; + + // Should not revert, just skip setting the fee + vm.prank(feeSetter); + feeAdapter.overridePoolFee(uninitializedPool, overrideFee); + + // Override should be stored + assertEq(feeAdapter.poolFeeOverrides(uninitializedPool), overrideFee); + + // Pool fee should still be 0 (uninitialized) + (,,,,, uint8 poolFees,) = IUniswapV3Pool(uninitializedPool).slot0(); + assertEq(poolFees, 0); + } + + function test_removePoolFeeOverride_success() public { + uint8 overrideFee = 7 << 4 | 6; + + vm.startPrank(feeSetter); + feeAdapter.overridePoolFee(pool, overrideFee); + + // Verify override is set + assertEq(feeAdapter.poolFeeOverrides(pool), overrideFee); + + vm.expectEmit(true, false, false, false); + emit IV3FeeAdapter.PoolFeeOverrideRemoved(pool); + feeAdapter.removePoolFeeOverride(pool); + vm.stopPrank(); + + // Verify override is removed (back to 0 means no override) + assertEq(feeAdapter.poolFeeOverrides(pool), 0); + } + + function test_removePoolFeeOverride_revertsUnauthorized() public { + address unauthorized = makeAddr("unauthorized"); + vm.prank(unauthorized); + vm.expectRevert(IV3FeeAdapter.Unauthorized.selector); + feeAdapter.removePoolFeeOverride(pool); + } + + function test_triggerFeeUpdate_usesDefaultAfterOverrideRemoved() public { + uint8 defaultFee = 10 << 4 | 10; + uint8 overrideFee = 5 << 4 | 5; + + // Setup merkle tree + bytes32[] memory leaves = new bytes32[](2); + leaves[0] = _hashLeaf(pool); + leaves[1] = _hashLeaf(pool1); + bytes32 root = merkle.getRoot(leaves); + + vm.startPrank(feeSetter); + feeAdapter.setMerkleRoot(root); + feeAdapter.setDefaultFeeByFeeTier(3000, defaultFee); + + // Set override + feeAdapter.overridePoolFee(pool, overrideFee); + assertEq(_getProtocolFees(pool), overrideFee); + + // Remove override + feeAdapter.removePoolFeeOverride(pool); + vm.stopPrank(); + + // Trigger fee update - should use default now + bytes32[] memory proof = merkle.getProof(leaves, 0); + feeAdapter.triggerFeeUpdate(pool, proof); + + assertEq(_getProtocolFees(pool), defaultFee); + } + + function test_fuzz_overridePoolFee(address _pool, uint8 feeValue) public { + uint8 OVERRIDE_TO_ZERO = type(uint8).max; + vm.startPrank(feeSetter); + + uint8 feeProtocol0 = feeValue % 16; + uint8 feeProtocol1 = feeValue >> 4; + bool isValidFeeValue = (feeProtocol0 == 0 || (feeProtocol0 >= 4 && feeProtocol0 <= 10)) + && (feeProtocol1 == 0 || (feeProtocol1 >= 4 && feeProtocol1 <= 10)); + + if (!isValidFeeValue) { + vm.expectRevert(IV3FeeAdapter.InvalidFeeValue.selector); + feeAdapter.overridePoolFee(_pool, feeValue); + } else { + feeAdapter.overridePoolFee(_pool, feeValue); + // If feeValue is 0, it's stored as sentinel; otherwise stored as-is + uint8 expectedStored = feeValue == 0 ? OVERRIDE_TO_ZERO : feeValue; + assertEq(feeAdapter.poolFeeOverrides(_pool), expectedStored); + } + + vm.stopPrank(); + } + + function test_overridePoolFee_canUpdateExistingOverride() public { + uint8 firstOverride = 5 << 4 | 5; + uint8 secondOverride = 8 << 4 | 8; + + vm.startPrank(feeSetter); + feeAdapter.overridePoolFee(pool, firstOverride); + assertEq(_getProtocolFees(pool), firstOverride); + + feeAdapter.overridePoolFee(pool, secondOverride); + assertEq(_getProtocolFees(pool), secondOverride); + vm.stopPrank(); + } + + function test_triggerFeeUpdate_byPair_respectsOverride() public { + uint8 defaultFee3000 = 10 << 4 | 10; + uint8 defaultFee10000 = 8 << 4 | 8; + uint8 overrideFee = 5 << 4 | 5; + + // Setup merkle tree for the pair + bytes32[] memory leaves = new bytes32[](2); + leaves[0] = _hashLeaf(pool); + leaves[1] = _hashLeaf(pool1); + bytes32 root = merkle.getRoot(leaves); + + vm.startPrank(feeSetter); + feeAdapter.setMerkleRoot(root); + feeAdapter.setDefaultFeeByFeeTier(3000, defaultFee3000); + feeAdapter.setDefaultFeeByFeeTier(10_000, defaultFee10000); + + // Set override only for pool (3000 fee tier) + feeAdapter.overridePoolFee(pool, overrideFee); + vm.stopPrank(); + + // Trigger fee update by pair + (address _token0, address _token1) = address(mockToken) < address(mockToken1) + ? (address(mockToken), address(mockToken1)) + : (address(mockToken1), address(mockToken)); + + bytes32[] memory proof = merkle.getProof(leaves, 0); + feeAdapter.triggerFeeUpdate(_token0, _token1, proof); + + // pool should have override, pool1 should have default + assertEq(_getProtocolFees(pool), overrideFee); + assertEq(_getProtocolFees(pool1), defaultFee10000); + } } From bfd5ade1bc4014c40e892c2b0d8d35965c451f96 Mon Sep 17 00:00:00 2001 From: Mark Toda Date: Mon, 8 Dec 2025 18:03:57 -0500 Subject: [PATCH 2/7] fix: deduplicate set functions --- src/feeAdapters/V3FeeAdapter.sol | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/feeAdapters/V3FeeAdapter.sol b/src/feeAdapters/V3FeeAdapter.sol index 7ce4ea8..722f391 100644 --- a/src/feeAdapters/V3FeeAdapter.sol +++ b/src/feeAdapters/V3FeeAdapter.sol @@ -119,7 +119,7 @@ contract V3FeeAdapter is IV3FeeAdapter, Owned { emit PoolFeeOverrideSet(pool, feeOverride); // Update the pool's fee immediately if the pool is initialized - _setProtocolFeeForPool(pool); + _setProtocolFee(pool, IUniswapV3Pool(pool).fee()); } /// @inheritdoc IV3FeeAdapter @@ -196,18 +196,6 @@ contract V3FeeAdapter is IV3FeeAdapter, Owned { IUniswapV3PoolOwnerActions(pool).setFeeProtocol(feeValue % 16, feeValue >> 4); } - /// @notice Sets the protocol fee for a specific pool using its override or fee tier default - /// @dev Only sets the fee for initialized pools. Used by overridePoolFee to immediately apply - /// changes. @param pool The address of the Uniswap V3 pool - function _setProtocolFeeForPool(address pool) internal { - // Check if pool is initialized by verifying sqrtPriceX96 is non-zero - (uint160 sqrtPriceX96,,,,,,) = IUniswapV3Pool(pool).slot0(); - if (sqrtPriceX96 == 0) return; // Pool exists but not initialized, skip - - uint8 feeValue = _getEffectiveFee(pool, IUniswapV3Pool(pool).fee()); - IUniswapV3PoolOwnerActions(pool).setFeeProtocol(feeValue % 16, feeValue >> 4); - } - /// @notice Returns the effective fee for a pool, considering overrides /// @param pool The pool address /// @param feeTier The fee tier of the pool (used for default lookup) From 60206e6677ab68fa499f6875f3ce94226c415335 Mon Sep 17 00:00:00 2001 From: Mark Toda Date: Mon, 8 Dec 2025 18:05:27 -0500 Subject: [PATCH 3/7] fix: naming --- src/feeAdapters/V3FeeAdapter.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/feeAdapters/V3FeeAdapter.sol b/src/feeAdapters/V3FeeAdapter.sol index 722f391..1d36e84 100644 --- a/src/feeAdapters/V3FeeAdapter.sol +++ b/src/feeAdapters/V3FeeAdapter.sol @@ -201,9 +201,9 @@ contract V3FeeAdapter is IV3FeeAdapter, Owned { /// @param feeTier The fee tier of the pool (used for default lookup) /// @return The fee value to apply (0 if sentinel, override if set, otherwise default) function _getEffectiveFee(address pool, uint24 feeTier) internal view returns (uint8) { - uint8 override_ = poolFeeOverrides[pool]; - if (override_ == OVERRIDE_TO_ZERO) return 0; - if (override_ != 0) return override_; + uint8 poolFeeOverride = poolFeeOverrides[pool]; + if (poolFeeOverride == OVERRIDE_TO_ZERO) return 0; + if (poolFeeOverride != 0) return poolFeeOverride; return defaultFees[feeTier]; } From 96818c1949572007a1fc0e0f5e026b0bb4ece620 Mon Sep 17 00:00:00 2001 From: Mark Toda Date: Mon, 8 Dec 2025 18:50:00 -0500 Subject: [PATCH 4/7] fix: snapshots --- snapshots/V3FeeAdapterTest.json | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/snapshots/V3FeeAdapterTest.json b/snapshots/V3FeeAdapterTest.json index ffe21f9..479dc28 100644 --- a/snapshots/V3FeeAdapterTest.json +++ b/snapshots/V3FeeAdapterTest.json @@ -1,6 +1,7 @@ { - "batchTriggerFeeUpdate_allLeaves": "277996773", - "triggerFeeUpdate_0": "57728", - "triggerFeeUpdate_4500": "57704", - "triggerFeeUpdate_8999": "55136" -} \ No newline at end of file + "batchTriggerFeeUpdate_allLeaves": "298498773", + "triggerFeeUpdate_0": "60051", + "triggerFeeUpdate_4500": "60027", + "triggerFeeUpdate_8999": "57459" +} + From fe8015df0c1c3473f433dd762334ef35755a0bb3 Mon Sep 17 00:00:00 2001 From: Mark Toda Date: Mon, 8 Dec 2025 20:01:07 -0500 Subject: [PATCH 5/7] fix: tests --- snapshots/V3FeeAdapterTest.json | 3 +-- test/V3FeeAdapter.t.sol | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/snapshots/V3FeeAdapterTest.json b/snapshots/V3FeeAdapterTest.json index 479dc28..05b66c8 100644 --- a/snapshots/V3FeeAdapterTest.json +++ b/snapshots/V3FeeAdapterTest.json @@ -3,5 +3,4 @@ "triggerFeeUpdate_0": "60051", "triggerFeeUpdate_4500": "60027", "triggerFeeUpdate_8999": "57459" -} - +} \ No newline at end of file diff --git a/test/V3FeeAdapter.t.sol b/test/V3FeeAdapter.t.sol index 5766314..edf85f9 100644 --- a/test/V3FeeAdapter.t.sol +++ b/test/V3FeeAdapter.t.sol @@ -774,7 +774,7 @@ contract V3FeeAdapterTest is ProtocolFeesTestBase { assertEq(_getProtocolFees(pool), defaultFee); } - function test_fuzz_overridePoolFee(address _pool, uint8 feeValue) public { + function test_fuzz_overridePoolFee(uint8 feeValue) public { uint8 OVERRIDE_TO_ZERO = type(uint8).max; vm.startPrank(feeSetter); @@ -785,12 +785,12 @@ contract V3FeeAdapterTest is ProtocolFeesTestBase { if (!isValidFeeValue) { vm.expectRevert(IV3FeeAdapter.InvalidFeeValue.selector); - feeAdapter.overridePoolFee(_pool, feeValue); + feeAdapter.overridePoolFee(pool, feeValue); } else { - feeAdapter.overridePoolFee(_pool, feeValue); + feeAdapter.overridePoolFee(pool, feeValue); // If feeValue is 0, it's stored as sentinel; otherwise stored as-is uint8 expectedStored = feeValue == 0 ? OVERRIDE_TO_ZERO : feeValue; - assertEq(feeAdapter.poolFeeOverrides(_pool), expectedStored); + assertEq(feeAdapter.poolFeeOverrides(pool), expectedStored); } vm.stopPrank(); From 194d05e0b96e8889b3985802505cff8375198f02 Mon Sep 17 00:00:00 2001 From: Mark Toda Date: Tue, 9 Dec 2025 18:55:57 -0500 Subject: [PATCH 6/7] fix: check default before override --- src/feeAdapters/V3FeeAdapter.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/feeAdapters/V3FeeAdapter.sol b/src/feeAdapters/V3FeeAdapter.sol index 1d36e84..b4dcad0 100644 --- a/src/feeAdapters/V3FeeAdapter.sol +++ b/src/feeAdapters/V3FeeAdapter.sol @@ -202,9 +202,9 @@ contract V3FeeAdapter is IV3FeeAdapter, Owned { /// @return The fee value to apply (0 if sentinel, override if set, otherwise default) function _getEffectiveFee(address pool, uint24 feeTier) internal view returns (uint8) { uint8 poolFeeOverride = poolFeeOverrides[pool]; + if (poolFeeOverride == 0) return defaultFees[feeTier]; if (poolFeeOverride == OVERRIDE_TO_ZERO) return 0; - if (poolFeeOverride != 0) return poolFeeOverride; - return defaultFees[feeTier]; + return poolFeeOverride; } /// @notice Validates that a fee value meets protocol requirements From e760d79133eed2fb68092a611cf5ec74b4ed0216 Mon Sep 17 00:00:00 2001 From: Mark Toda Date: Tue, 9 Dec 2025 19:43:58 -0500 Subject: [PATCH 7/7] fix: gas --- snapshots/V3FeeAdapterTest.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/snapshots/V3FeeAdapterTest.json b/snapshots/V3FeeAdapterTest.json index 05b66c8..4b69036 100644 --- a/snapshots/V3FeeAdapterTest.json +++ b/snapshots/V3FeeAdapterTest.json @@ -1,6 +1,6 @@ { - "batchTriggerFeeUpdate_allLeaves": "298498773", - "triggerFeeUpdate_0": "60051", - "triggerFeeUpdate_4500": "60027", - "triggerFeeUpdate_8999": "57459" + "batchTriggerFeeUpdate_allLeaves": "298521523", + "triggerFeeUpdate_0": "60216", + "triggerFeeUpdate_4500": "60217", + "triggerFeeUpdate_8999": "57524" } \ No newline at end of file