Vanguard

First Flight #56
Beginner FriendlyDeFiFoundry
0 EXP
Submission Details
Impact: low
Likelihood: high

Unused state variables

Author Revealed upon completion

Description

  • If a contract declares accounting variables (e.g., “total penalty fees collected”, “per‑address swap counts”, “penalty counts”), they should be updated where relevant logic occurs (e.g., on each swap, when a penalty is applied) or not be present at all. Otherwise they waste storage/gas and mislead operators and integrators.

  • In TokenLaunchHook, several state variables are declared but never updated anywhere in the code:

    • totalPenaltyFeesCollected — set to 0 in the constructor and never incremented.

    • addressTotalSwaps — declared but never incremented.

    • addressPenaltyCount — declared but never incremented.
      This creates dead state and suggests functionality that does not exist (e.g., protocol‑level fee collection or penalty accounting).

uint256 public launchStartBlock;
uint256 public initialLiquidity;
uint256 public totalPenaltyFeesCollected; // @> declared, but never updated
mapping(address => uint256) public addressSwappedAmount;
mapping(address => uint256) public addressLastSwapBlock;
mapping(address => uint256) public addressTotalSwaps; // @> declared, but never updated
mapping(address => uint256) public addressPenaltyCount; // @> declared, but never updated
// In _beforeSwap(...) no increments/updates occur for these three variables
// and there is no code path that writes
// to totalPenaltyFeesCollected/addressTotalSwaps/addressPenaltyCount.

Risk

Likelihood: High

  • Always: These variables are never written after deployment, regardless of activity level or penalties triggered.

Impact: Low

  • Misleading telemetry & operator UX: Dashboards or scripts reading these variables will show zeros and suggest no swaps/penalties occurred when they actually did.

  • Gas/storage waste & code smell: Unused state increases code and storage footprint without benefit; auditors/users may assume features (e.g., fee collection) that don’t exist.

  • Spec mismatch risk: The presence of “...FeesCollected” implies protocol‑level fee accrual, but fees set via beforeSwap override accrue to LPs by design; the hook doesn’t collect them, so the variable is confusing.

Proof of Concept

  • Create UnusedStateVariables.t.sol under test directory and copy code below.

  • Run command forge test --mt test_UnusedState_RemainZeroAfterSwapsAndPenalty -vvvv.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Test} from "forge-std/Test.sol";
import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol";
import {PoolSwapTest} from "v4-core/test/PoolSwapTest.sol";
import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol";
import {Hooks} from "v4-core/libraries/Hooks.sol";
import {HookMiner} from "v4-periphery/src/utils/HookMiner.sol";
import {LPFeeLibrary} from "v4-core/libraries/LPFeeLibrary.sol";
import {TickMath} from "v4-core/libraries/TickMath.sol";
import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol";
import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol";
import {TokenLaunchHook} from "../src/TokenLaunchHook.sol";
import {Currency} from "v4-core/types/Currency.sol";
import {SwapParams, ModifyLiquidityParams} from "v4-core/types/PoolOperation.sol";
import {PoolKey} from "v4-core/types/PoolKey.sol";
import {ERC1155TokenReceiver} from "solmate/src/tokens/ERC1155.sol";
contract UnusedStateVariablesTest is Test, Deployers, ERC1155TokenReceiver {
TokenLaunchHook hook;
MockERC20 token;
Currency ethCurrency = Currency.wrap(address(0));
Currency tokenCurrency;
uint256 phase1Duration = 100;
uint256 phase2Duration = 200;
uint256 phase1LimitBps = 100;
uint256 phase2LimitBps = 300;
uint256 phase1Cooldown = 5;
uint256 phase2Cooldown = 3;
uint256 phase1PenaltyBps = 500;
uint256 phase2PenaltyBps = 200;
address user = address(0xA11CE);
function setUp() public {
deployFreshManagerAndRouters();
token = new MockERC20("TOKEN", "TKN", 18);
tokenCurrency = Currency.wrap(address(token));
token.mint(address(this), 1_000 ether);
token.mint(user, 10 ether);
token.approve(address(modifyLiquidityRouter), type(uint256).max);
token.approve(address(swapRouter), type(uint256).max);
vm.prank(user);
token.approve(address(swapRouter), type(uint256).max);
// Mine correct permission flags: AFTER_INITIALIZE + BEFORE_SWAP
bytes memory creationCode = type(TokenLaunchHook).creationCode;
bytes memory args = abi.encode(
manager,
phase1Duration,
phase2Duration,
phase1LimitBps,
phase2LimitBps,
phase1Cooldown,
phase2Cooldown,
phase1PenaltyBps,
phase2PenaltyBps
);
uint160 flags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_INITIALIZE_FLAG);
(address mined, bytes32 salt) = HookMiner.find(address(this), flags, creationCode, args);
hook = new TokenLaunchHook{salt: salt}(
IPoolManager(manager),
phase1Duration,
phase2Duration,
phase1LimitBps,
phase2LimitBps,
phase1Cooldown,
phase2Cooldown,
phase1PenaltyBps,
phase2PenaltyBps
);
require(address(hook) == mined, "Hook address mismatch");
// Init pool & add liquidity (match the suite’s pattern)
(key,) = initPool(ethCurrency, tokenCurrency, hook, LPFeeLibrary.DYNAMIC_FEE_FLAG, SQRT_PRICE_1_1);
uint160 sqrtPriceAtTickUpper = TickMath.getSqrtPriceAtTick(60);
uint256 ethToAdd = 10 ether;
vm.deal(address(this), ethToAdd + 1 ether); // fund the test for settle
uint128 liqDelta = LiquidityAmounts.getLiquidityForAmount0(
SQRT_PRICE_1_1, sqrtPriceAtTickUpper, ethToAdd
);
modifyLiquidityRouter.modifyLiquidity{value: ethToAdd + 0.5 ether}(
key,
ModifyLiquidityParams({
tickLower: -60,
tickUpper: 60,
liquidityDelta: int256(uint256(liqDelta)),
salt: bytes32(0)
}),
ZERO_BYTES
);
}
function test_UnusedState_RemainZeroAfterSwapsAndPenalty() public {
// Two back-to-back swaps in the same block => cooldown violation => penalty branch is taken
uint256 swapAmount = 0.001 ether;
vm.deal(user, 1 ether);
vm.startPrank(user);
SwapParams memory params = SwapParams({
zeroForOne: true,
amountSpecified: -int256(swapAmount),
sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
});
PoolSwapTest.TestSettings memory settings =
PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false});
// First swap
swapRouter.swap{value: swapAmount}(key, params, settings, ZERO_BYTES);
// Second swap in the same block (cooldown not respected -> penalty should apply in logic)
swapRouter.swap{value: swapAmount}(key, params, settings, ZERO_BYTES);
vm.stopPrank();
// ASSERT: the following state vars are never updated by the hook
assertEq(hook.totalPenaltyFeesCollected(), 0, "totalPenaltyFeesCollected should remain 0 (never updated)");
assertEq(hook.addressTotalSwaps(address(swapRouter)), 0, "addressTotalSwaps[router] should remain 0");
assertEq(hook.addressPenaltyCount(address(swapRouter)), 0, "addressPenaltyCount[router] should remain 0");
}
}

Output:

[⠊] Compiling...
[⠢] Compiling 2 files with Solc 0.8.26
[⠆] Solc 0.8.26 finished in 64.20ms
No files changed, compilation skipped
Ran 1 test for test/UnusedStateVariables.t.sol:UnusedStateVariablesTest
[PASS] test_UnusedState_RemainZeroAfterSwapsAndPenalty() (gas: 278448)
Traces:
[278448] UnusedStateVariablesTest::test_UnusedState_RemainZeroAfterSwapsAndPenalty()
├─ [0] VM::deal(0x00000000000000000000000000000000000A11cE, 1000000000000000000 [1e18])
│ └─ ← [Return]
├─ [0] VM::startPrank(0x00000000000000000000000000000000000A11cE)
│ └─ ← [Return]
├─ [158624] PoolSwapTest::swap{value: 1000000000000000}(PoolKey({ currency0: 0x0000000000000000000000000000000000000000, currency1: 0x15cF58144EF33af1e14b5208015d11F9143E27b9, fee: 8388608 [8.388e6], tickSpacing: 60, hooks: 0x7E4A8F76FEc89Ed2EbF193e4E5Cc1c1ab32E9080 }), SwapParams({ zeroForOne: true, amountSpecified: -1000000000000000 [-1e15], sqrtPriceLimitX96: 4295128740 [4.295e9] }), TestSettings({ takeClaims: false, settleUsingBurn: false }), 0x)
│ ├─ [152820] PoolManager::unlock(0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000a11ce00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015cf58144ef33af1e14b5208015d11f9143e27b90000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000007e4a8f76fec89ed2ebf193e4e5cc1c1ab32e90800000000000000000000000000000000000000000000000000000000000000001fffffffffffffffffffffffffffffffffffffffffffffffffffc72815b39800000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000)
│ │ ├─ [150561] PoolSwapTest::unlockCallback(0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000a11ce00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015cf58144ef33af1e14b5208015d11f9143e27b90000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000007e4a8f76fec89ed2ebf193e4e5cc1c1ab32e90800000000000000000000000000000000000000000000000000000000000000001fffffffffffffffffffffffffffffffffffffffffffffffffffc72815b39800000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000)
│ │ │ ├─ [862] PoolManager::exttload(0xc6bd81b73d1fd76b6ab7b2a3e675f602f162633118b2ec0b74d6456669dd8579) [staticcall]
│ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000
│ │ │ ├─ [2552] MockERC20::balanceOf(0x00000000000000000000000000000000000A11cE) [staticcall]
│ │ │ │ └─ ← [Return] 10000000000000000000 [1e19]
│ │ │ ├─ [2552] MockERC20::balanceOf(PoolManager: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f]) [staticcall]
│ │ │ │ └─ ← [Return] 10000000000000000000 [1e19]
│ │ │ ├─ [862] PoolManager::exttload(0x85be7c2bd5cfd9e6e3a30072d5be012f0c0649c579d3433f4d5ee458bdb429be) [staticcall]
│ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000
│ │ │ ├─ [110236] PoolManager::swap(PoolKey({ currency0: 0x0000000000000000000000000000000000000000, currency1: 0x15cF58144EF33af1e14b5208015d11F9143E27b9, fee: 8388608 [8.388e6], tickSpacing: 60, hooks: 0x7E4A8F76FEc89Ed2EbF193e4E5Cc1c1ab32E9080 }), SwapParams({ zeroForOne: true, amountSpecified: -1000000000000000 [-1e15], sqrtPriceLimitX96: 4295128740 [4.295e9] }), 0x)
│ │ │ │ ├─ [76095] TokenLaunchHook::beforeSwap(PoolSwapTest: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], PoolKey({ currency0: 0x0000000000000000000000000000000000000000, currency1: 0x15cF58144EF33af1e14b5208015d11F9143E27b9, fee: 8388608 [8.388e6], tickSpacing: 60, hooks: 0x7E4A8F76FEc89Ed2EbF193e4E5Cc1c1ab32E9080 }), SwapParams({ zeroForOne: true, amountSpecified: -1000000000000000 [-1e15], sqrtPriceLimitX96: 4295128740 [4.295e9] }), 0x)
│ │ │ │ │ ├─ [2378] PoolManager::extsload(0xa88b2edf4fb72aaf361d8b4ec9d1adbe5291882f188b44e71deb27a0f6bb7388) [staticcall]
│ │ │ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000b4fb077cf724ed8bd3
│ │ │ │ │ └─ ← [Return] 0x575e24b4, 0, 4194304 [4.194e6]
│ │ │ │ ├─ emit Swap(id: 0xc6eac5486de8d3ffee00cfbcd1e87bfd63f7fbfb9d06fcf16e570fe9fa93a4b5, sender: PoolSwapTest: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], amount0: -1000000000000000 [-1e15], amount1: 999999700464594 [9.999e14], sqrtPriceX96: 79228138782624522581392935400 [7.922e28], liquidity: 3338502497096994491347 [3.338e21], tick: -1, fee: 0)
│ │ │ │ └─ ← [Return] -340282366920938463463374607431768211455000000299535406 [-3.402e53]
│ │ │ ├─ [862] PoolManager::exttload(0xc6bd81b73d1fd76b6ab7b2a3e675f602f162633118b2ec0b74d6456669dd8579) [staticcall]
│ │ │ │ └─ ← [Return] 0xfffffffffffffffffffffffffffffffffffffffffffffffffffc72815b398000
│ │ │ ├─ [552] MockERC20::balanceOf(0x00000000000000000000000000000000000A11cE) [staticcall]
│ │ │ │ └─ ← [Return] 10000000000000000000 [1e19]
│ │ │ ├─ [552] MockERC20::balanceOf(PoolManager: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f]) [staticcall]
│ │ │ │ └─ ← [Return] 10000000000000000000 [1e19]
│ │ │ ├─ [862] PoolManager::exttload(0x85be7c2bd5cfd9e6e3a30072d5be012f0c0649c579d3433f4d5ee458bdb429be) [staticcall]
│ │ │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000038d7e92ebf3d2
│ │ │ ├─ [1425] PoolManager::settle{value: 1000000000000000}()
│ │ │ │ └─ ← [Return] 1000000000000000 [1e15]
│ │ │ ├─ [10243] PoolManager::take(MockERC20: [0x15cF58144EF33af1e14b5208015d11F9143E27b9], 0x00000000000000000000000000000000000A11cE, 999999700464594 [9.999e14])
│ │ │ │ ├─ [8511] MockERC20::transfer(0x00000000000000000000000000000000000A11cE, 999999700464594 [9.999e14])
│ │ │ │ │ ├─ emit Transfer(from: PoolManager: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], to: 0x00000000000000000000000000000000000A11cE, amount: 999999700464594 [9.999e14])
│ │ │ │ │ └─ ← [Return] true
│ │ │ │ └─ ← [Stop]
│ │ │ └─ ← [Return] 0xfffffffffffffffffffc72815b398000000000000000000000038d7e92ebf3d2
│ │ └─ ← [Return] 0xfffffffffffffffffffc72815b398000000000000000000000038d7e92ebf3d2
│ └─ ← [Return] -340282366920938463463374607431768211455000000299535406 [-3.402e53]
├─ [73934] PoolSwapTest::swap{value: 1000000000000000}(PoolKey({ currency0: 0x0000000000000000000000000000000000000000, currency1: 0x15cF58144EF33af1e14b5208015d11F9143E27b9, fee: 8388608 [8.388e6], tickSpacing: 60, hooks: 0x7E4A8F76FEc89Ed2EbF193e4E5Cc1c1ab32E9080 }), SwapParams({ zeroForOne: true, amountSpecified: -1000000000000000 [-1e15], sqrtPriceLimitX96: 4295128740 [4.295e9] }), TestSettings({ takeClaims: false, settleUsingBurn: false }), 0x)
│ ├─ [70630] PoolManager::unlock(0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000a11ce00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015cf58144ef33af1e14b5208015d11f9143e27b90000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000007e4a8f76fec89ed2ebf193e4e5cc1c1ab32e90800000000000000000000000000000000000000000000000000000000000000001fffffffffffffffffffffffffffffffffffffffffffffffffffc72815b39800000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000)
│ │ ├─ [68371] PoolSwapTest::unlockCallback(0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000a11ce00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015cf58144ef33af1e14b5208015d11f9143e27b90000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000007e4a8f76fec89ed2ebf193e4e5cc1c1ab32e90800000000000000000000000000000000000000000000000000000000000000001fffffffffffffffffffffffffffffffffffffffffffffffffffc72815b39800000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000)
│ │ │ ├─ [862] PoolManager::exttload(0xc6bd81b73d1fd76b6ab7b2a3e675f602f162633118b2ec0b74d6456669dd8579) [staticcall]
│ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000
│ │ │ ├─ [552] MockERC20::balanceOf(0x00000000000000000000000000000000000A11cE) [staticcall]
│ │ │ │ └─ ← [Return] 10000999999700464594 [1e19]
│ │ │ ├─ [552] MockERC20::balanceOf(PoolManager: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f]) [staticcall]
│ │ │ │ └─ ← [Return] 9999000000299535406 [9.999e18]
│ │ │ ├─ [862] PoolManager::exttload(0x85be7c2bd5cfd9e6e3a30072d5be012f0c0649c579d3433f4d5ee458bdb429be) [staticcall]
│ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000
│ │ │ ├─ [40146] PoolManager::swap(PoolKey({ currency0: 0x0000000000000000000000000000000000000000, currency1: 0x15cF58144EF33af1e14b5208015d11F9143E27b9, fee: 8388608 [8.388e6], tickSpacing: 60, hooks: 0x7E4A8F76FEc89Ed2EbF193e4E5Cc1c1ab32E9080 }), SwapParams({ zeroForOne: true, amountSpecified: -1000000000000000 [-1e15], sqrtPriceLimitX96: 4295128740 [4.295e9] }), 0x)
│ │ │ │ ├─ [2811] TokenLaunchHook::beforeSwap(PoolSwapTest: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], PoolKey({ currency0: 0x0000000000000000000000000000000000000000, currency1: 0x15cF58144EF33af1e14b5208015d11F9143E27b9, fee: 8388608 [8.388e6], tickSpacing: 60, hooks: 0x7E4A8F76FEc89Ed2EbF193e4E5Cc1c1ab32E9080 }), SwapParams({ zeroForOne: true, amountSpecified: -1000000000000000 [-1e15], sqrtPriceLimitX96: 4295128740 [4.295e9] }), 0x)
│ │ │ │ │ └─ ← [Return] 0x575e24b4, 0, 4244304 [4.244e6]
│ │ │ │ ├─ emit Swap(id: 0xc6eac5486de8d3ffee00cfbcd1e87bfd63f7fbfb9d06fcf16e570fe9fa93a4b5, sender: PoolSwapTest: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], amount0: -1000000000000000 [-1e15], amount1: 949999160552349 [9.499e14], sqrtPriceX96: 79228116237579866750041480701 [7.922e28], liquidity: 3338502497096994491347 [3.338e21], tick: -1, fee: 50000 [5e4])
│ │ │ │ └─ ← [Return] -340282366920938463463374607431768211455050000839447651 [-3.402e53]
│ │ │ ├─ [862] PoolManager::exttload(0xc6bd81b73d1fd76b6ab7b2a3e675f602f162633118b2ec0b74d6456669dd8579) [staticcall]
│ │ │ │ └─ ← [Return] 0xfffffffffffffffffffffffffffffffffffffffffffffffffffc72815b398000
│ │ │ ├─ [552] MockERC20::balanceOf(0x00000000000000000000000000000000000A11cE) [staticcall]
│ │ │ │ └─ ← [Return] 10000999999700464594 [1e19]
│ │ │ ├─ [552] MockERC20::balanceOf(PoolManager: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f]) [staticcall]
│ │ │ │ └─ ← [Return] 9999000000299535406 [9.999e18]
│ │ │ ├─ [862] PoolManager::exttload(0x85be7c2bd5cfd9e6e3a30072d5be012f0c0649c579d3433f4d5ee458bdb429be) [staticcall]
│ │ │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000036004ea806b9d
│ │ │ ├─ [1425] PoolManager::settle{value: 1000000000000000}()
│ │ │ │ └─ ← [Return] 1000000000000000 [1e15]
│ │ │ ├─ [4643] PoolManager::take(MockERC20: [0x15cF58144EF33af1e14b5208015d11F9143E27b9], 0x00000000000000000000000000000000000A11cE, 949999160552349 [9.499e14])
│ │ │ │ ├─ [2911] MockERC20::transfer(0x00000000000000000000000000000000000A11cE, 949999160552349 [9.499e14])
│ │ │ │ │ ├─ emit Transfer(from: PoolManager: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], to: 0x00000000000000000000000000000000000A11cE, amount: 949999160552349 [9.499e14])
│ │ │ │ │ └─ ← [Return] true
│ │ │ │ └─ ← [Stop]
│ │ │ └─ ← [Return] 0xfffffffffffffffffffc72815b398000000000000000000000036004ea806b9d
│ │ └─ ← [Return] 0xfffffffffffffffffffc72815b398000000000000000000000036004ea806b9d
│ └─ ← [Return] -340282366920938463463374607431768211455050000839447651 [-3.402e53]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [2608] TokenLaunchHook::totalPenaltyFeesCollected() [staticcall]
│ └─ ← [Return] 0
├─ [2626] TokenLaunchHook::addressTotalSwaps(PoolSwapTest: [0x2e234DAe75C793f67A35089C9d99245E1C58470b]) [staticcall]
│ └─ ← [Return] 0
├─ [2956] TokenLaunchHook::addressPenaltyCount(PoolSwapTest: [0x2e234DAe75C793f67A35089C9d99245E1C58470b]) [staticcall]
│ └─ ← [Return] 0
└─ ← [Return]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 31.12ms (1.01ms CPU time)
Ran 1 test suite in 33.49ms (31.12ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

Recommended Mitigation

  • Implement the intended accounting

function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata)
internal override returns (bytes4, BeforeSwapDelta, uint24) {
...
- // (no updates to swaps/penalties today)
+ // Track total swaps per sender (note: currently `sender` is the router address;
+ // see separate bug report about tracking the actual end-user via hookData).
+ addressTotalSwaps[sender] += 1;
uint24 feeOverride = 0;
if (applyPenalty) {
- feeOverride = uint24((phasePenaltyBps * 100));
+ feeOverride = uint24(phasePenaltyBps * 100); // BPS -> pips
+ addressPenaltyCount[sender] += 1;
+ // Optional: track 'penalty applied (pips or swaps)' rather than 'fees collected'
+ // because override fees accrue to LPs, not to this hook.
+ // For example:
+ // totalPenaltyFeesCollected += feeOverride; // or count of penalized swaps
}
return (
BaseHook.beforeSwap.selector,
BeforeSwapDeltaLibrary.ZERO_DELTA,
- feeOverride | LPFeeLibrary.OVERRIDE_FEE_FLAG
+ feeOverride == 0 ? 0 : (feeOverride | LPFeeLibrary.OVERRIDE_FEE_FLAG)
);
}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!