Skip to content
$1,000,000 in security audit grants are live now, Apply here →

Security review · August 2026

Protocol Review

for Clanker

Guardian's review of Protocol Review for Clanker, published August 2026. The report records 74 findings across 2 review rounds, including 20 high and 13 medium.

Published
Review window
June 29 to July 29, 2026
Rounds
Main Review, Remediation Review
Language
Solidity
Chains
Base
Sector
Token launches
  • 0 Critical
  • 20 High
  • 13 Medium
  • 20 Low
  • 21 Informational

36 resolved · 8 partially resolved · 30 acknowledged

Scope

46 files in scope · 4,713 nSLOC
FilenSLOCLines
src/v5/ClankerV5.sol744744
src/v5/ClankerCompatibilityRegistry.sol616616
src/v5/ClankerDeploymentRegistry.sol6767
src/v5/adapters/UniswapV4Adapter.sol276276
src/v5/adapters/PancakeInfinityCLAdapter.sol117117
src/v5/adapters/BaselineMercuryWrapper.sol104104
src/v5/adapters/AmmAdapterModuleBase.sol7373
src/v5/adapters/types/AmmAdapterBlobTypes.sol2828
src/v5/extensions/ClankerUniv4EthDevBuyV5.sol8080
src/v5/extensions/ExtensionModuleBase.sol5757
src/v5/extensions/ClankerVaultV5.sol6363
src/v5/extensions/ClankerAirdropV5.sol6363
src/v5/extensions/ClankerAirdropV5V2.sol6363
src/v5/fees/FeeRoutingModuleBase.sol7373
src/v5/fees/ClankerFeeRouting.sol4444
src/v5/fees/types/FeeRoutingBlobTypes.sol1212
src/v5/mechanics/LaunchMechanicModuleBase.sol7373
src/v5/mechanics/DefaultLaunchMechanic.sol6565
src/v5/mechanics/types/MechanicBlobTypes.sol1515
src/v5/token-sources/BaseBTokenSource.sol197197
src/v5/token-sources/ClankerTokenSource.sol136136
src/v5/token-sources/TokenSourceModuleBase.sol8383
src/v5/token-sources/TokenDescriptorRegistry.sol7575
src/v5/token-sources/types/TokenSourceBlobTypes.sol3333
src/v5/libraries/LaunchValidation.sol134134
src/v5/libraries/ClankerV5Constants.sol129129
src/v5/libraries/SupplyAccounting.sol6969
src/v5/libraries/BaseBTokenInitCallValidation.sol5454
src/v5/libraries/ClankerModuleKindLibrary.sol5353
src/v5/libraries/UniswapV4PoolKeyValidation.sol5151
src/v5/libraries/UniswapV4LaunchIndexing.sol3838
src/v5/types/ClankerV5Types.sol161161
src/v5/interfaces/IClankerCompatibilityRegistry.sol234234
src/v5/interfaces/IClankerV5.sol222222
src/v5/interfaces/ITokenDescriptorRegistry.sol6565
src/v5/interfaces/IClankerV5LaunchFactory.sol5656
src/v5/interfaces/IClankerAmmAdapter.sol4141
src/v5/interfaces/IClankerTokenSource.sol3737
src/v5/interfaces/IClankerDeploymentRegistry.sol3737
src/v5/interfaces/IPancakeInfinityCLLauncher.sol3535
src/v5/interfaces/IClankerExtensionV5.sol3131
src/v5/interfaces/IClankerFeeRouting.sol2525
src/v5/interfaces/IBaselineMercuryLauncher.sol2525
src/v5/interfaces/IClankerModule.sol2424
src/v5/interfaces/IClankerLaunchMechanic.sol2424
src/v5/interfaces/IClankerUniswapV4MevFinalizer.sol1111

Findings 74

Main Review

45 findings · June 29 to July 16, 2026
  1. H-01 High Forced ETH Transfer Bricks All Launches DoS Resolved
    Location
    src/v5/ClankerV5.sol:235-237
    Round
    Main Review

    Description

    deployLaunch finalizes with a strict zero-balance check:

    if (address(this).balance != 0) {
        revert MsgValueMismatch();
    }
    

    This assumes core holds no ETH beyond the msg.value that extensions consume during the launch. Although there is no receive/fallback mechanism, anyone can force ETH into core with selfdestruct(core), which EIP-6780 still allows if the attack contract is created and destroyed in the same transaction.

    From that point every deployLaunch reverts at this check, since address(this).balance > 0 always holds, as the deployment function only consumes msg.value = requiredMsgValue.

    Recommendation

    This check already guarantees the user has provided sufficient ETH:

    if (msg.value != requiredMsgValue) {
                revert MsgValueMismatch();
            }
    

    Therefore, consider refactoring the if (address(this).balance != 0) check to refund excess ETH by tracking before and after amounts:

    // at entry, before extensions run
    uint256 ethBaseline = address(this).balance - msg.value;
    // ...
    // at exit
      uint256 ethBalance = address(this).balance;
      if (ethBalance < ethBaseline) {
          revert MsgValueMismatch(); // can't consume more than supplied
      }
      uint256 launchEthRemaining = ethBalance - ethBaseline;
      if (launchEthRemaining != 0) {
          // Refund unspent launch ETH to creator or relayer
          (bool refunded,) = payable(msg.sender).call{value: launchEthRemaining}("");
          if (!refunded) revert MsgValueMismatch();
      }
    

    Consider if you want to refund to msg.sender or envelope.creator.

  2. L-01 Low setCore Missing Access Control Access Control Resolved
    Location
    src/v5/ClankerDeploymentRegistry.sol:33
    Round
    Main Review

    Description

    ClankerDeploymentRegistry.setCore sets the CORE address with no caller authentication:

    function setCore(address core_) external {
        if (CORE != address(0) || core_ == address(0)) {
            revert UnauthorizedCore();
        }
    
        CORE = core_;
    }
    

    The deploy script constructs the registry with address(0) then calls setCore in a later transaction. Between those two transactions an attacker can call setCore which a malicious core address, making the deployer's setCore(core) revert and forcing redeployment (unless unnoticed).

    Recommendation

    Add access control to setCore (e.g. onlyOwner or a stored deployer address)

  3. L-02 Low Permissionless createToken Enables Launch Grief Access Control Resolved
    Location
    src/v5/token-sources/ClankerTokenSource.sol:66
    Round
    Main Review

    Description

    The token launch deployment flow can call ClankerTokenSource.createToken or BaseBTokenSource.createToken, which have no access control, anyone can call them directly.

    The deployed address is CREATE2-deterministic from the constructor args, therefore allows an attacker who knows a victim's pending parameters tokenAdmin and the full tokenSourceData to deploy the victim's exact address first via direct createToken calls. The victim's later deployLaunch then reverts at the CREATE2 deploy step inside createToken.

    Since Base has no public mempool, the attack relies on knowing the victim's parameters before-hand rather than a direct front-run attack by observing the mempool.

    Recommendation

    Either gate createToken to the core factory (msg.sender == CORE) or include msg.sender in the salt.

  4. H-02 High Hidden Genesis Mint Bypasses Supply Conservation Validation Resolved
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/token-sources/BaseBTokenSource.sol#L93, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/token-sources/BaseBTokenSource.sol#L126, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/token-sources/BaseBTokenSource.sol#L138-L140
    Round
    Main Review

    Description

    BaseBTokenSource.createToken treats balanceOf(source) == config.totalSupply as its only supply-conservation check and never validates the token's real totalSupply(). The B-20 factory mints nothing itself, as the entire supply is inputted by caller-controlled mint/batchMint initCalls. Because BaseBTokenInitCallValidation gates selectors (and not arguments), a mint(attacker, 5x) initCall passes validation: the mint selector is allowlisted and the recipient and amount are never checked.

    A creator adds one extra mint alongside the required one:

    initCalls = [ mint(source, TOTAL_SUPPLY),        // satisfies balanceOf(source) == totalSupply
                  mint(attacker, 5 * TOTAL_SUPPLY) ]; // hidden, never accounted
    

    The launch reports totalLaunchSupply == TOTAL_SUPPLY and seeds liquidity as if that is 100% of supply, while the token's real totalSupply() is 6x, with 5x already sitting in the creator's wallet, invisible to the launch and to v5's supply-conservation invariant. The creator can then dump the hidden supply into the freshly seeded pool.

    This does not apply to ClankerTokenSource, since it mints a fixed supply in the token constructor, so balanceOf(source) == totalSupply implies token.totalSupply() == totalSupply.

    Recommendation

    In BaseBTokenSource.createToken, assert IERC20(token).totalSupply() == config.totalSupply in addition to the existing source-balance check. The balance check enforces that the accounted supply landed with the source, while the total-supply check closes the mint-elsewhere hole, since any extra genesis mint inflates totalSupply(). Also consider validating BaseBTokenInitCallValidation mint/batchMint arguments, restricting recipients to the source or disallowing genesis mints outside the accounted supply.

  5. L-03 Low Caller launchId Enables DoS And Collision DoS Resolved
    Location
    src/v5/ClankerV5.sol:455
    Round
    Main Review

    Description

    launchId is a caller-supplied bytes32 and _reserveLaunch marks it taken on first use and reverts InvalidLaunchId on any reuse:

    function _reserveLaunch(bytes32 launchId, address creator) internal {
        if (_launchIdTaken[launchId]) {
            revert InvalidLaunchId();
        }
        _launchIdTaken[launchId] = true;
        _launchStatuses[launchId] = LaunchLifecycleStatus.Reserved;
        emit LaunchReserved(launchId, creator, LaunchLifecycleStatus.Reserved);
    }
    

    Because the id is chosen by the caller and keyed globally rather than per creator, two launches can collide on the same value.

    This happens maliciously if an attacker knows a victim's intended launchId in advance and submits it first, causing the victim's deployLaunch to revert. It can also happen naturally if launch tooling assigns sequential ids, since independent creators may pick the same value.

    On Base there is no public mempool, so any attack depends on the victim's id being known beforehand rather than observed in flight.

    Recommendation

    Consider keying keccak256(creator, launchId) instead of the raw launchId, removing both the malicious DoS and the accidental collision regardless of what raw value a creator supplies.

  6. L-04 Low B-20 Descriptor Cannot Derive Address Configuration Resolved
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/token-sources/BaseBTokenSource.sol#L126, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/token-sources/BaseBTokenSource.sol#L157-L158
    Round
    Main Review

    Description

    BaseBTokenSource records the raw config.salt as the descriptor's create2Salt with create2Deterministic == true, but the true B-20 address derives from (variant, source, salt), not the standard keccak256(0xff, deployer, salt, initCodeHash) scheme. ClankerTokenSource, by contrast, records keccak256(tokenAdmin, salt). A consumer that treats the create2Salt field uniformly across sources, expecting standard CREATE2 derivation, cannot reconstruct the B-20 token address and will mispredict it.

    Recommendation

    Either store enough information in the descriptor to reconstruct the B-20 address (variant, source, and salt), or document that B-20 descriptors' create2Salt is the raw factory salt and that deriving the address additionally requires the source address and variant.

  7. I-01 Informational Asset Multiplier Not Shown in Capabilities Informational Resolved
    Location
    src/v5/token-sources/BaseBTokenSource.sol:172-176
    Round
    Main Review

    Description

    The B-20 Asset variant carries an operator-controlled rebase multiplier (updateMultiplier, OPERATOR_ROLE), but _assetCapabilityFlags() showcases no rebase/multiplier bit, so a consumer reading the descriptor gets no signal the token has a multiplier mechanism.

    The multiplier does seem to affect standard ERC-20 balanceOf() or totalSupply() accounting, so the impact is limited. However, it is still a nonstandard B-20 Asset feature, and omitting it from _assetCapabilityFlags() leaves descriptor consumers without a clear signal that operator-controlled multiplier behavior exists.

    Recommendation

    Consider adding a TOKEN_CAP_REBASING (multiplier) flag and set it for asset-variant tokens, for completeness of the capability descriptor.

  8. I-02 Informational Zero totalSupply Diverges Between Token Sources Documentation Resolved
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/token-sources/ClankerTokenSource.sol#L61, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/token-sources/BaseBTokenSource.sol#L89
    Round
    Main Review

    Description

    ClankerTokenSource silently defaults totalSupply == 0 to the 100B default supply, while BaseBTokenSource reverts ZeroTotalSupply on the same input. An integration that passes 0 to either source therefore gets inconsistent behavior: a 100B-supply token from one and a revert from the other. This inconsistency can cause integrations that use totalSupply = 0 as a sentinel for using the default supply to behave differently depending on the selected token source.

    Recommendation

    Consider documenting this behavior or enforce consistent totalSupply = 0 handling for both token sources.

  9. H-03 High Baseline Launches With Modular Fee Can Be DoS'ed DoS Resolved
    Location
    src/v5/fees/ClankerVolumeThresholdFee.sol:65
    Round
    Main Review

    Description

    The intended v5 fee-routing path is modular fee routing, where launches route fees through a fee module rather than relying only on the legacy/v4-parity path. In the current implementation, this means ClankerVolumeThresholdFee, which supports both active and passive fee collection. Uniswap/Pancake-style fee flows are active, while Baseline Mercury fee flows are always passive because Mercury sends reserve-token fees directly to the configured fee recipient.

    Passive fee collection is guarded by _passiveFeeTokenVenues, but this mapping is keyed only by fee token and not by launchId or venue instance. As a result, the shared ClankerVolumeThresholdFee can only initialize one passive Baseline fee policy per reserve token. Once WETH, USDC, or another reserve token has been used by any passive Baseline launch, subsequent Baseline launches using modular fee routing and that same reserve token revert during fee policy initialization.

    This can happen naturally as normal launches reuse common reserve assets, or it can be forced by an attacker. Because launches are permissionless once the adapter and modular fee path are enabled, an attacker can cheaply launch arbitrary tokens once per common reserve token, permanently occupying those reserve-token slots in the shared fee module and DoS'ing later modular-fee Baseline launches for those reserves.

    Recommendation

    Route Baseline passive fees to launch-specific fee sink contracts that receive Mercury fees for a single launch/reserve and then forward attributed amounts into the shared fee module, or alternatively deploy isolated fee module instances for passive Baseline launches.

  10. L-05 Low Fee Token Can Mismatch Venue Assets Validation Resolved
    Location
    src/v5/fees/ClankerVolumeThresholdFee.sol:120
    Round
    Main Review

    Description

    ClankerVolumeThresholdFee lets launchers configure an arbitrary non-zero feeToken without validating that it matches the assets from which the selected venue actually generates fees. The documented configuration is feeToken = address(0) for active Uniswap v4 and Pancake venues so pool currencies are accepted, and feeToken = reserve or zero for Baseline so the reserve token is used.

    However, there is no validation regarding caller provided non-zero feeToken. If a launcher provides an incorrect non-zero feeToken, active venues can reject legitimate collected pool fees during receiveFees, while Baseline can track a token different from the reserve token Mercury is expected to pay. This breaks fee collection or distribution for the affected launch and may leave actual venue fees unattributed. Additionally, for Baseline venues, a launcher can configure one token as the reserve while setting a different token as the fee token, causing distributeFees to attribute and distribute passive balance increases of the incorrect feeToken if that token is later transferred to the fee module.

    Recommendation

    Validate feeToken against venue assets during fee policy initialization, or enforce venue-specific defaults by deriving the accepted fee token set from the initialized position data.

  11. M-01 Medium VenueId Collision With Different Pancake Fees Logical Error Resolved
    Location
    src/v5/launchers/PancakeInfinityCLClankerLauncher.sol:170
    Round
    Main Review

    Description

    Pancake Infinity CL launches create a pool key that includes the configured lpFee, but the returned Clanker venueId is derived only from currency0, currency1, and parameters. As a result, two Pancake pools for the same token pair and tick spacing but different fee tiers resolve to the same Clanker venue even though they are distinct AMM pools.

    V5 supports appending additional protocol-tracked liquidity positions after launch, and those positions are not required to use the same Pancake pool as the original launch. When a token admin adds another Pancake position for the same pair and tick spacing but a different fee tier, both positions are routed through the same launchId + venueId fee policy. Their collected LP fees, threshold accounting, pending balances, and fee events are merged as if they came from one venue.

    Recommendation

    Include poolKey.fee in the Pancake venueId derivation, preferably by hashing the full canonical Pancake pool key.

  12. H-04 High Adapter Donations Block Liquidity Additions DoS Acknowledged
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/adapters/BaselineMercuryWrapper.sol#L97-L100, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/adapters/PancakeInfinityCLAdapter.sol#L124-L126
    Round
    Main Review

    Description

    After a successful launch, any holder of the launch token can transfer a small amount of that token directly to the Pancake or Baseline adapter. Later, when the token admin attempts to add supplemental liquidity through addLiquidityPosition, core transfers the intended tokenAmount to the adapter, but the adapter’s existing donated balance interferes with its accounting.

    For Pancake, the launcher consumes exactly the assigned tokenAmount, leaving the donated dust in the adapter. The adapter then checks that its launch-token balance is zero and reverts with LauncherDidNotConsumeTokens. For Baseline, the adapter checks before calling Mercury that its total launch-token balance equals the assigned tokenAmount; because the donated amount makes the balance tokenAmount + donation, it reverts with ExceedsAssignedTokenAmount.

    This creates a repeatable griefing vector against supplemental liquidity additions for Pancake venues. Admin asset recovery can clear the adapter balance, but an attacker can donate again after recovery, forcing operational intervention each time the token admin wants to add liquidity. Additionally, initial launch validation does not allow the same adapter to be used more than once in the launch envelope, so a token owner who wants to add another liquidity position through the same venue/adapter after launch must use addLiquidityPosition.

    This does not affect Uniswap v4 in the same way as its adapter approves and places only the assigned tokenAmount without requiring the adapter’s pre- or post-call token balance to exactly match that amount.

    Recommendation

    Make adapter accounting delta-based or assigned-amount-based instead of absolute-balance-based, so pre-existing donated balances are ignored or swept separately while the adapter still verifies that only the intended tokenAmount is consumed for the liquidity operation

  13. H-05 High Tick Gap Bypasses Launch FDV Guard Validation Partially resolved
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/ClankerV5.sol#L203, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/adapters/UniswapV4Adapter.sol#L238-L257, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/adapters/UniswapV4Adapter.sol#L129-L141, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/adapters/UniswapV4Adapter.sol#L183-L187
    Round
    Main Review

    Description

    The launch valuation guard relies on the adapter’s quoteLaunchFdvPaired() result to enforce max-FDV limits. In the Uniswap v4 adapter, the quoted FDV is computed from the pool’s initialized starting tick.

    int24 startingTick = token0IsClanker
    
    ? config.poolConfig.tickIfToken0IsClanker
    
    : -config.poolConfig.tickIfToken0IsClanker;
    
    uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(startingTick);
    
    fdvPaired = LaunchValuationMath.fdvPairedFromSqrtPriceX96(
    
    context.supplyAllocation.totalLaunchSupply,
    
    sqrtPriceX96,
    
    token0IsClanker
    
    );
    

    However, liquidity is placed separately through the locker configuration IClankerV5LaunchFactory(LAUNCH_FACTORY).factoryPlaceV4Liquidity.

    Both UniswapV4 and PancakeInfinity adapter quotes price FDV at the init spot. UniswapV4Adapter.quoteLaunchFdvPaired computes it from tickIfToken0IsClanker, and PancakeInfinityCLAdapter.quoteLaunchFdvPaired from params.sqrtPriceX9 (both via LaunchValuationMath). Meanwhile the liquidity is single-sided and minted in [tickLower, tickUpper], and the only constraint tying it to spot is the locker's tickLower[i] >= startingTick check, which prevents liquidity below spot but does not require min (tickLower) == startingTick.

    Therefore, for the same pool, launches can accept a malicious config such as poolConfig.tickIfToken0IsClanker = -230400; // very low starting tick, lockerConfig.tickLower[0] = -30000; // much higher first liquidity tick, lockerConfig.positionBps[0] = 10_000; // all liquidity here.

    The adapter attempts to validate the tick arrays via UniswapV4Adapter._validateConfig, but it does not require the lowest liquidity tick to equal the initialized starting tick. As a result, a creator can initialize the pool at a low starting tick to pass the max-FDV guard, then place all launch liquidity at a much higher tickLower. This creates a liquidity-free gap between the guarded spot price and the first executable liquidity price.

    The attached PoCs confirm this, as a launch initialized at startingTick = -230400 passed valuation using an FDV of approximately 9.87 WETH, while all liquidity was placed near tickLower = -30000. A real WETH to token swap skipped the empty gap and filled near -29999, giving an effective entry FDV of approximately 4.98e9 WETH. This demonstrates that buyers can enter at a valuation far above the value checked by the guard.

    This defeats the purpose of max-FDV guarded launches. The launch appears compliant because the guard measures the initialized spot price, but users actually trade at the first executable liquidity price.

    Recommendation

    Compute guarded FDV at the first executable liquidity price instead of the initialized spot. For Uniswap launches, use the minimum tickLower across launch liquidity positions, or require min(tickLower) == startingTick. For Pancake launches, use the lowest liquidity tick from the configured range, or require the initial sqrtPriceX96 to match the first liquidity tick.

  14. H-06 High Extension Refund Reentrancy Drains Core Fees Reentrancy Resolved
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/ClankerV5.sol#L918, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/ClankerV5.sol#L733
    Round
    Main Review

    Description

    ClankerV5._executeExtensionAllocations() temporarily authorizes the active extension by setting _activeExtensionModule. This flag is intended to allow only the currently executing extension to call sensitive factory helpers such as factoryTriggerV4Extension().

    However, in the failed extension path, _activeExtensionModule remains set while the contract executes a refund to tokenAdmin via an external call:

    _activeExtensionModule = extensionModule;
    
    try IClankerExtensionV5(extensionModule)
    
    .receiveLaunchAllocation{value: extensionMsgValue}(
    
    context,
    
    envelope.extensionAllocations[i],
    
    extensionSupply
    
    )
    
    {
    
    ...
    
    } catch (bytes memory reason) {
    
    ...
    
    (bool refunded,) =
    
    payable(context.tokenAdmin).call{value: extensionMsgValue}("");
    
    if (!refunded) {
    
    revert MsgValueMismatch();
    
    }
    
    ...
    
    }
    
    _activeExtensionModule = address(0);
    

    Because tokenAdmin is creator-controlled, an attacker can set it to a contract and reenter during the ETH refund. At that point _activeExtensionModule is still set, so the attacker can route through a legitimate registered extension wrapper and reach factoryTriggerV4Extension().

    That function is gated only by onlyActiveExtensionModule and then approves an arbitrary token from the core to the supplied extension path:

    function factoryTriggerV4Extension(
    
    address extension,
    
    IClanker.DeploymentConfig calldata deploymentConfig,
    
    PoolKey calldata poolKey,
    
    address token,
    
    uint256 extensionSupply,
    
    uint256 extensionIndex
    
    ) external payable onlyActiveExtensionModule {
    
    IERC20 tokenContract = IERC20(token);
    
    SafeERC20.forceApprove(tokenContract, extension, extensionSupply);
    
    IClankerExtension(extension).receiveTokens{value: msg.value}(
    
    deploymentConfig,
    
    poolKey,
    
    token,
    
    extensionSupply,
    
    extensionIndex
    
    );
    
    SafeERC20.forceApprove(tokenContract, extension, 0);
    
    }
    

    This allows the attacker to make ClankerV5 approve and transfer foreign ERC20 balances held by the core. The launch can still finalize because the final supply conservation check only verifies the current launch token balance, not unrelated ERC20 balances.

    The allows direct theft of accumulated hook protocol fees held by the core, as the claimTeamFees natspec states "Claims accumulated hook protocol fees held by core. Distinct from stray recovery; sweeps the full token balance as protocol fees."

    Any creator can trigger the issue by configuring a failed extension allocation with a non-reverting failure policy and a refund to an attacker-controlled tokenAdmin. The exploit does not require registering a malicious extension, since a legitimate registered wrapper can be used as the reentry proxy.

    Recommendation

    Clear _activeExtensionModule before performing any external refund or token transfer in the failed extension path:

    catch (bytes memory reason) {
        _activeExtensionModule = address(0); //@audit reset here
    
         ...
            (bool refunded,) =
                payable(context.tokenAdmin).call{value: extensionMsgValue}("");
            if (!refunded) {
                revert MsgValueMismatch();
            }
        ...
    }
    
  15. L-06 Low Public Adapters Spoof Launch Events Access Control Partially resolved
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/adapters/BaselineMercuryWrapper.sol#L75, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/adapters/PancakeInfinityCLAdapter.sol#L91
    Round
    Main Review

    Description

    PancakeInfinityCLAdapter.placeLiquidity and BaselineMercuryWrapper.placeLiquidity are externally callable and do not require msg.sender to be ClankerV5. A caller can pre-fund the adapter with tokens, provide arbitrary LaunchContext, LiquidityAllocation, and adapter data, then execute the adapter directly outside the core deploy or addLiquidityPosition flows.

    This does not update Clanker’s deployment registry or current liquidity position registry, so core view functions remain unaffected. However, the direct call can still create real external protocol state, such as an unregistered Pancake/Baseline liquidity position or market, and the adapters emit LiquidityPlaced(context.launchId, ...) using the caller-supplied launchId. As a result, an attacker can emit adapter-level events that appear to reference another user’s launch and may confuse off-chain systems that trust adapter events without reconciling them against ClankerV5 core events or registry state.

    Uniswap v4 is not affected in the same way because its adapter must call back into ClankerV5 factory helpers guarded by onlyActiveLiquidityAdapter, which fails outside the core-controlled launch/add-liquidity flow. However, Baseline and Pancake skip those guarded callbacks when the caller supplies a zero fee module.

    Recommendation

    Consider restricting adapter placeLiquidity entrypoints to the authorized launch factory/core.

  16. H-07 High Same Pool Liquidity Additions Always Fail Compatibility Partially resolved
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/adapters/BaselineMercuryWrapper.sol#L118, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/launchers/BaselineMercuryClankerLauncher.sol#L74, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/launchers/PancakeInfinityCLClankerLauncher.sol#L161
    Round
    Main Review

    Description

    ClankerV5.addLiquidityPosition is exposed as a post-launch path for appending supplemental liquidity positions, but it calls the same placeLiquidity entry point used during the initial deploy flow on all adapters. The production adapters then execute their pool-creation logic again instead of placing liquidity into an already initialized pool.

    As a result, same-pool supplemental additions revert across the current production paths. Uniswap v4 tries pool initialization before locker placement, Pancake Infinity CL calls CL_POOL_MANAGER.initialize(...), and Baseline Mercury calls createPoolFromInvariant(...); each path rejects an already initialized pool. This prevents token admins from adding a new LP position in the same pool, including normal use cases such as adding liquidity in a different tick range.

    As a result, supplemental liquidity cannot be added to the original launch pool. Callers may still create additional positions by changing pool-key parameters such as the fee tier or paired token, but that creates a different pool rather than adding liquidity to the launch pool and fragments liqudity.

    Recommendation

    Do not reuse the same placeLiquidity path for supplemental liquidity as that path always attempts to initialize or create the pool. Implement dedicated supplemental-liquidity logic for each AMM that first checks whether the target pool is already initialized, then either adds a new position to the existing pool or initializes the pool before placing the new position.

  17. H-08 High All Passive Baseline Fees Can Be Stolen Access Control Resolved
    Location
    src/v5/fees/ClankerVolumeThresholdFee.sol:194-206
    Round
    Main Review

    Description

    ClankerVolumeThresholdFee.receiveFeesForDepositor only checks that the supplied depositor address is authorized for the launch and venue. It does not require msg.sender to be that depositor or an approved forwarder. Because authorized depositor addresses are public, any caller can pass a valid depositor and make the fee module account an existing token balance to that venue.

    This allows passive Baseline fees to be stolen atomically. Baseline Mercury claimPoolFees is permissionless and transfers accrued reserve fees to the v5 fee module. An attacker can call claimPoolFees for a Baseline market and then immediately call receiveFeesForDepositor for their own active venue, causing the newly transferred reserve fees to be accounted to the attacker’s venue. The attacker can then call distributeFees for their venue and receive the passive fees through their configured recipients.

    Additionally, because active venues with feeToken == address(0) accept any ERC20, the same missing caller authentication lets an attacker credit arbitrary worthless token balances as fee revenue, prematurely crossing the threshold or pushing cumulativeRevenue near type(uint256).max and causing future fee accounting to revert.

    Recommendation

    Require receiveFeesForDepositor to authenticate msg.sender as the depositor or an approved forwarder, and prevent active fee accounting from consuming passive fee balances belonging to other venues.

  18. H-09 High FDV Guard Bypass Via Mining Clanker as currency1 Logical Error Resolved
    Location
    src/v5/launchers/PancakeInfinityCLClankerLauncher.sol:176-201
    Round
    Main Review

    Description

    Pancake Infinity CL valuation uses the decoded sqrtPriceX96 from launchParams, or derives it from the user-provided tickLower when sqrtPriceX96 == 0. The production launcher then initializes the pool at that same unmirrored price. However, when the Clanker token sorts as currency1, the launcher mirrors only the LP tick range by returning (-tickUpper, -tickLower) and does not mirror the initialization price used for valuation.

    A malicious creator can intentionally mine/grind the token-source salt so the Clanker token sorts after the paired token, making Clanker currency1. They can then provide launch params that pass valuation at the unmirrored price while liquidity is actually deposited in the mirrored range. For example, valuation can be approved at tick +30000, while the real LP range is [-60000, -30000]; the first executable buy-side liquidity sits near -30000, which is materially more expensive (~400x) than the guarded valuation point.

    This bypasses valuation-guarded Pancake launches. The launch passes valuation and finalizes as compliant, but executable liquidity sits at an attacker-favorable price far from the approved valuation.

    Additionally, this is not limited to valuation-guarded launches: under the default mechanic, Pancake token1 launches initialize the pool at the unmirrored price while minting liquidity in the mirrored range, causing incorrect pool initialization even without any valuation check.

    Recommendation

    Mirror the valuation/init price as well as the liquidity range so both are checked and executed in the same tick space when Clanker is currency1. Also, consider enforcing that the guarded price matches the first executable liquidity boundary.

  19. L-07 Low Relayed Launch Extension Refund Mismatch Logical Error Resolved
    Location
    src/v5/ClankerV5.sol:918
    Round
    Main Review

    Description

    ClankerV5 supports relayed launches where msg.sender is an authorized relayer instead of the launch creator. In that path, the relayer supplies the full msg.value required by the launch envelope, including extension native budgets.

    When a non-reverting extension failure occurs, ClankerV5 refunds the failed extension’s msgValue to tokenAdmin rather than the payer. This may be acceptable for direct launches, but in relayed launches it sends the relayer-funded refund to a creator-controlled address while the launch continues.

    Recommendation

    Refund failed extension native value to the actual launch payer, or add an explicit signed refund recipient for relayed launches. Alternatively, document this behavior clearly if this is intended and accepted behavior.

  20. M-02 Medium DevBuy ETH Can Be Stranded In UniversalRouter Logical Error Partially resolved
    Location
    ClankerUniv4EthDevBuyV5.sol
    Round
    Main Review

    Description

    The in-scope ClankerUniv4EthDevBuyV5 extension is a wrapper that forwards dev-buy execution to the legacy ClankerUniv4EthDevBuy extension. That legacy extension forwards the full native amountIn to UniversalRouter when the configured v4 hop uses native ETH.

    Although the route uses SWAP_EXACT_IN_SINGLE, Uniswap v4 does not guarantee that the full specified input is consumed. If available liquidity is insufficient, the swap can stop before consuming the full amountIn. SETTLE_ALL then settles only the pool’s actual debt, not the full ETH value sent to the router.

    The extension does not sweep or refund the remaining ETH. As a result, leftover ETH stays on the shared Universal Router and can be taken by any caller through the permissionless SWEEP command. Additionally, ERC20 inputs can remain unused when less than the approved amount is consumed, but those leftovers stay in the extension rather than becoming permissionlessly sweepable from the router.

    Recommendation

    Track actual input consumed and explicitly recover any unspent input after each router swap, using a router SWEEP for native ETH and refunding any remaining ERC20 balance from the extension to the intended recipient.

  21. L-08 Low Passive Fees Can Be Misattributed Logical Error Resolved
    Location
    src/v5/fees/ClankerVolumeThresholdFee.sol:358-360
    Round
    Main Review

    Description

    ClankerVolumeThresholdFee snapshots only the passive venue’s _distributableToken(state) during initialization. For Baseline/passive venues this is intended to be the reserve or configured feeToken. However, distributeFees later accepts state.token as well, even though state.token was not necessarily snapshotted for passive accounting.

    An attacker can launch token A with a passive venue whose fee token is B, causing only B to be snapshotted. If another passive venue later uses token A as its reserve or fee token, any uncustodied token A balance in the shared fee module can be distributed through the attacker’s venue because A == state.token passes _isAcceptedFeeToken and its last passive balance is zero.

    This requires the victim passive fee token to equal the attacker’s launched token, so it is unlikely for standard Baseline reserves. Still, the passive accounting invariant is broken: passive venues should only distribute assets that were initialized and snapshotted for that passive venue.

    Recommendation

    For passive venues, snapshot and account every token that distributeFees accepts including the state.token, or restrict passive distribution to exactly the configured distributable fee token.

  22. L-09 Low Uniswap Adapter Underreports Capabilities Compatibility Resolved
    Location
    src/v5/adapters/UniswapV4Adapter.sol:97-101
    Round
    Main Review

    Description

    UniswapV4Adapter does not declare MODULE_CAP_MOVES_TOKENS, while the Pancake and Baseline adapters do. This is inconsistent because Uniswap also participates in ERC20 movement: it approves launch tokens to LAUNCH_FACTORY, which then pulls them into the v4 locker/liquidity path.

    The missing capability does not appear to affect current execution, since AMM adapters only require MODULE_CAP_LP_PLACEMENT and do not forbid token movement. However, it weakens the capability model for registry review, admin policy, and any UI/tooling that relies on module capability masks as a risk signal.

    Recommendation

    Add MODULE_CAP_MOVES_TOKENS to UniswapV4Adapter so its declared capabilities match its token-moving authority.

    Additionally, consider also reviewing whether MODULE_CAP_POOL_INIT and MODULE_CAP_MEV_CONTROL should be declared for adapters that initialize pools or arm MEV modules, unless those omissions are intentional policy choices.

  23. I-03 Informational Baseline Launches Require Zero Extensions Validation Resolved
    Location
    src/v5/launchers/BaselineMercuryClankerLauncher.sol:74
    Round
    Main Review

    Description

    Clanker v5’s general allocation model allows launch supply to be split between liquidity allocations and extension allocations. That works for AMM paths that can accept a partial launch-supply liquidity allocation, but it does not compose with the current Baseline Mercury path.

    Baseline Mercury ultimately calls createPoolFromInvariant, which requires the supplied initialPoolBTokens to equal the token’s live totalSupply (Reference: https://github.com/0xBaseline/mercury/blob/6bc32cfbf4e7e2b0a09f1de814007af3c419203a/src/components/BFactory.sol#L279-L283). Therefore, any Baseline launch with nonzero extension allocations will provide less than the full token supply to Mercury and revert late during the Baseline handoff.

    This constraint is not documented in the v5 Baseline integration docs, and there is no Clanker-side validation to fail fast. As a result, otherwise well-formed Baseline launch envelopes can pass Clanker validation and only fail much later at the Mercury call.

    Recommendation

    Document that Baseline launches require zero extension allocations and add an early Clanker-side validation for Baseline allocations to fail before reaching Mercury

  24. L-10 Low BToken Descriptors Can Be False Validation Resolved
    Location
    src/v5/token-sources/BaseBTokenSource.sol:152-153
    Round
    Main Review

    Description

    BaseBTokenSource registers the token descriptor from the caller-provided config after createB20 returns. However, B20 initCalls are executed before descriptor registration and Clanker only validates their selectors, not their arguments or final effects.

    A launcher can use allowed initCalls to change the token’s live name/symbol and to grant or revoke admin roles before Clanker records the descriptor. As a result, the registered descriptor and deployment record can show the original configured metadata and tokenAdmin, while the actual BToken already has different metadata or is controlled by different role holders at launch finalization.

    Recommendation

    After createB20 returns, read back and validate the live BToken metadata and role/admin state before registering the descriptor, or disallow initCalls that can mutate descriptor-recorded fields during bootstrap.

  25. M-03 Medium Admin Rotation Is Not Recorded Logical Error Resolved
    Location
    src/v5/ClankerV5.sol:952
    Round
    Main Review

    Description

    Clanker v5 records both tokenAdmin and originalTokenAdmin at launch finalization. originalTokenAdmin is stored as the launch-time original admin, while tokenAdmin is used by v5 for admin-gated lifecycle actions such as supplemental liquidity additions and liquidity migration authorization.

    However, tokenAdmin is also immutable in the v5 deployment record. ClankerToken.updateAdmin can legitimately rotate the token-level admin, and B20 admin roles can be transferred or revoked, but v5 never updates the recorded tokenAdmin. As a result, admin-gated v5 actions continue to authorize the launch-time recorded address rather than the token’s current admin.

    This can DoS supplemental liquidity additions and liquidity migrations when the recorded admin is no longer available. More importantly, it can also let the old admin continue performing v5 admin-gated actions after token-level authority has been rotated away, bypassing the intended access-control change.

    Recommendation

    Add a v5-side admin-rotation flow for deployment records and document token level updates require record update as well.

  26. H-10 High Stolen Protocol Fees Due To Arbitrary Recipient Validation Partially resolved
    Location
    src/v5/fees/ClankerVolumeThresholdFee.sol:129
    Round
    Main Review

    Description

    ClankerVolumeThresholdFee lets launchers provide protocolRecipient directly in feeModuleData. During fee policy initialization the module stores that address as-is, and validation only requires it to be nonzero.

    In the default/v4-parity route, protocol trading fees accrue to the core/factory and are later claimed to the team fee recipient. Contrary to this, a launcher can route the protocol share of modular fee revenue to themselves instead of the Clanker protocol treasury.

    This conflicts with the documented economic model where protocol and creator economics come from trading fees after launch. Unless arbitrary protocol-recipient substitution is explicitly intended, modular fee routing lets external launchers bypass Clanker’s trading-fee revenue.

    Recommendation

    Derive protocolRecipient from protocol-controlled configuration, or validate that the caller-provided value equals the approved Clanker protocol fee recipient.

    Additionally, if zero protocol fees are not explicitly intended, also enforce protocol-governed minimum values for initialProtocolNumerator and postThresholdProtocolNumerator to prevent revenue loss.

  27. L-11 Low BToken Capabilities Are Underreported Compatibility Resolved
    Location
    src/v5/token-sources/BaseBTokenSource.sol:173-182
    Round
    Main Review

    Description

    BaseBTokenSource records BToken capability flags as supply-capped, mintable, pausable, and transfer-policy controlled. However, B20 tokens also expose mutable metadata and role-gated burn functionality that are not reflected in the descriptor.

    This can cause indexers, UIs, and future compatibility checks to treat launched BTokens as less mutable or less permissioned than they actually are. The clearest mismatch is metadata mutability: B20 supports name, symbol, contract URI, and asset extra-metadata updates, but TOKEN_CAP_MUTABLE_METADATA is not set. B20 also supports role-gated burning, including burn, burnWithMemo, and burnBlocked, while TOKEN_CAP_BURN is not set.

    Recommendation

    BaseBTokenSource capability flags to include metadata and burn capabilities, or document that BToken descriptors intentionally do not advertise those behaviors.

  28. I-04 Informational Warning Regarding B20 Transfer Policies Trust Assumptions Resolved
    Location
    src/v5/token-sources/BaseBTokenSource.sol:175
    Round
    Main Review

    Description

    B20 tokens launched through Clanker may configure transfer policies during initCalls, or admin can update policies after launc. These policies can restrict which addresses are allowed to send tokens, receive tokens, or execute transferFrom.

    A restrictive policy can be configured at launch to allow the Clanker deployment flow and AMM setup while limiting normal user transfers afterward. For example, a malicious allowlisted sender policy can permit protocol setup addresses and pool-side transfers, allowing users to buy tokens, while preventing ordinary users from sending tokens back to the pool to sell. This means a B20 token can be launched through Clanker with honeypot-like transfer behavior from the beginning.

    Recommendation

    Document this behavior clearly and warn users for B20 policies.

  29. I-05 Informational Misleading InvalidLockerRewards Error Usage Error Resolved
    Location
    src/v5/adapters/UniswapV4Adapter.sol:382-390
    Round
    Main Review

    Description

    UniswapV4Adapter._validateConfig validates both reward configuration and liquidity position configuration. However, failures in the position array checks for tickLower, tickUpper, and positionBps also revert with InvalidLockerRewards, which is misleading as these fields define LP position ranges and allocation splits, not reward configuration.

    Recommendation

    Use a separate error such as InvalidLockerPositions for tick range and positionBps validation failures.

  30. H-11 High Uniswap Supplemental Liqudity Always Reverts DoS Resolved
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/adapters/UniswapV4Adapter.sol#L184-L186, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/ClankerV5.sol#L712
    Round
    Main Review

    Description

    Uniswap v4 launches require locker reward configuration in practice because the downstream production locker rejects empty reward recipients and stores reward state for each launched token. During the initial launch, placeLiquidity function in locker records _tokenRewards[token] for the launched token and uses that token-level state to manage the locker’s LP reward accounting.

    ClankerV5.addLiquidityPosition is intended to support supplemental liquidity for the same launched token, but the Uniswap adapter routes supplemental additions through the same placeLiquidity deployment path. When the same locker is used again, the locker sees that _tokenRewards[token] already exists and reverts with TokenAlreadyHasRewards. As a result, Uniswap supplemental liquidity is permanently unusable for normal production launches using the standard locker.

    Noting that this is separate from the previous H-07 issue where same pool liquidity additions revert with PoolAlreadyInitialized. Even if the token admin avoids the original pool and targets a fresh Uniswap pool, the supplemental add still fails because the locker state.

    Recommendation

    Implement dedicated supplemental liquidity flows instead of reusing launch-time placeLiquidity functions. For Uniswap, this likely requires locker-level support for appending positions or a separate supplemental custody path, because the current production locker cannot cleanly support same-token supplemental additions without implementation changes.

  31. I-06 Informational Supplemental MEV Reinitialization Risk Warning Resolved
    Location
    src/v5/adapters/UniswapV4Adapter.sol:313
    Round
    Main Review

    Description

    Current Uniswap supplemental liquidity additions is already blocked by separate pool-initialization and locker-state issues (H-07, H-11). However, even if those blockers are fixed, same-pool supplemental liquidity may still fail because the v5 supplemental lifecycle always calls finalizeSupplementalPosition after placement, and the Uniswap adapter always attempts to initialize the MEV module for the position’s pool.

    For pools whose MEV module was already initialized during the original launch, this can cause secondary failures or unexpected behavior. One-time MEV modules such as descending-fee or sniper-auction modules revert on a second initialization, while delay-based modules may overwrite the unlock time and re-enable/re-lock the pool. This means a future fix for supplemental liquidity should also account for MEV state, not only pool and locker state.

    Recommendation

    When implementing dedicated supplemental liquidity flows, distinguish fresh-pool additions from same-pool additions and only initialize MEV for pools that have not already had their MEV module initialized

  32. M-04 Medium FDV Guard Does Not Validate Denomination Token Validation Resolved
    Location
    src/v5/libraries/LaunchValuationValidation.sol:61-74
    Round
    Main Review

    Description

    The protocol admins can set valuation profiles via setValuationProfile which serves as a protocol price safety policy for a launch. This stores the minFdvPaired and maxFdvPaired values, but the FDV denomination used to enforce them is creator-controlled. The min/max values are checked against the fdvPaired value if a valuation guard is enabled by the creator. However, PancakeInfinityCLAdapter.quoteLaunchFdvPaired returns FDV denominated in config.pairedToken, which is supplied by the creator and is only checked to be nonzero and different from the launch token.

    (uint256 fdvPaired, address denominationToken) =
        IClankerAmmValuationAdapter(allocation.adapter).quoteLaunchFdvPaired(
            context,
            allocation,
            liquidityTokenAmounts[i],
            allocation.adapterData
        );
    
    if (policy.maxFdvPaired > 0 && fdvPaired > policy.maxFdvPaired) {
        revert LaunchFdvAboveMaximum(fdvPaired, policy.maxFdvPaired);
    }
    

    In the adapter, the fdv quote is calculated from the pool price and returned with the following:

    fdvPaired = LaunchValuationMath.fdvPairedFromSqrtPriceX96(
        context.supplyAllocation.totalLaunchSupply,
        sqrtPriceX96,
        token0IsClanker
    );
    
    denominationToken = config.poolConfig.pairedToken;
    

    So the creator controls config.poolConfig.pairedToken, while the policy compares raw fdvPaired against raw maxFdvPaired without validating the denomination token. For example, an admin can set maxFdvPaired = 50e18 (i.e., for weth denominated cap), but a user can pair their launch with a 6-decimal token, which will bypass the guard since the same raw maxFdvPaired value is used to represent a much larger amount of the paired token. As a result, a creator can satisfy an admin-governed maxFdvPaired while making the cap represent a far larger amount of the selected paired token than the admin intended.

    Recommendation

    Bind each valuation profile to an expected denomination token and decimal scale, then require the adapter’s returned denominationToken to match before applying minFdvPaired or maxFdvPaired. Alternatively, restrict guarded launches to approved denomination tokens and normalize FDV values before comparison.

    struct ValuationProfile {
        bool enabled;
        uint256 minFdvPaired;
        uint256 maxFdvPaired;
        address expectedDenominationToken; //@audit check against expected denom token
        uint8 expectedDecimals; //@audit check against expected decimals
    }
    
  33. I-07 Informational Pancake Allows Arbitrary Static LP Fee Validation Resolved
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/libraries/UniswapV4PoolKeyValidation.sol#L37, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/launchers/libraries/PancakeInfinityLaunchParams.sol#L13
    Round
    Main Review

    Description

    Uniswap pools are forced to use fee == DYNAMIC_FEE_FLAG through UniswapV4PoolKeyValidation, making fees hook-managed capped at MAX_LP_FEE, where creators select a hook contract from a protocol supplied set. The Pancake path instead accepts creator-supplied params.lpFee and creates the pool with hooks = address(0), with no validation on the static fee.

    Buyers are still able to see this when interacting with the pool, this creates inconsistent behavior between Pancake and Uniswap launches.

    Recommendation

    Bound params.lpFee to an approved range, or force a fee path similar to Uniswap. If arbitrary static Pancake fees are intended, document the asymmetry for users and integrators.

  34. L-12 Low Airdrop V2 Allows Admin Root Replacement Validation Resolved
    Location
    src/extensions/ClankerAirdropV2.sol:35
    Round
    Main Review

    Description

    ClankerAirdropV5V2 forwards the configured airdrop admin and merkle root into ClankerAirdropV2, which custodies the launch’s airdrop allocation. Unlike V1, ClankerAirdropV2 does not reject a zero merkle root during initialization.

    updateMerkleRoot allows the airdrop admin to replace the root, and its time-based overwrite guard is skipped when the current root is zero. As a result, a V2 airdrop can be created with merkleRoot == 0, custody the full airdrop allocation, block normal claims, and leave the admin able to install a new root at any later time. The admin can then reassign the previous allocation to an addresses of its choosing.

    Even with a nonzero root, V2 also allows the admin to overwrite the root if no claims have occurred within the configured post-unlock overwrite window. This work as a recovery mechanism, but it weakens recipient guarantees because unclaimed allocations remain mutable after launch until at least one claim occurs.

    Recommendation

    Restore V1’s zero-root rejection in ClankerAirdropV2.receiveTokens so V2 airdrops cannot be initialized in an indefinitely mutable zero-root state. Consider using a timelock for root replacement or clearly documenting that the airdrop admin can replace the root until the overwrite conditions are no longer satisfied.

  35. L-13 Low Passive Mode Accepted For Active Venues Validation Resolved
    Location
    src/v5/fees/ClankerVolumeThresholdFee.sol:121
    Round
    Main Review

    Description

    Uniswap v4 and Pancake Infinity CL currently support active modular fee collection paths. Their hook, locker, or launcher transfers collected fees to the fee module and then calls receiveFees or receiveFeesForDepositor to account them.

    However, ClankerVolumeThresholdFee does not validate this venue constraint and accepts passiveFeeCollection = true for Uniswap and Pancake venues as well. Once configured this way, their normal active fee collection calls revert with PassiveVenueDisallowsReceiveFees, making modular fee collection unusable for that launch.

    Recommendation

    Validate passiveFeeCollection during fee policy initialization and reject passive mode for Uniswap v4 and Pancake Infinity CL unless those venues add a passive-compatible fee path.

  36. M-05 Medium Raw Fee Units Skew Thresholds Logical Error Resolved
    Location
    src/v5/fees/ClankerVolumeThresholdFee.sol:376
    Round
    Main Review

    Description

    ClankerVolumeThresholdFee tracks one cumulativeRevenue value per launch venue, but active AMM fees can be received in both pool tokens. Pancake collects and routes both currency0 and currency1 LP fees to the fee module, and Uniswap LP fee routing can also forward both reward tokens. Each received amount is then added directly to the same venue-level revenue counter without recording the token denomination or normalizing decimals.

    As a result, fees from assets with different decimals or values are mixed as raw token units. For example, in a Pancake pool with an 18-decimal launch token and 6-decimal USDC pair, 1e18 launch-token units and 1e6 USDC units are both added directly to cumulativeRevenue. The USDC side is therefore effectively treated as dust relative to the launch token side, even though it may represent the economically meaningful fee asset.

    This makes the volume threshold inaccurate for common mixed-decimal pools and can cause the protocol-share transition to occur too early or too late depending on which token side produces fees. The configured threshold therefore does not reliably represent fee volume or value across the venue.

    Additionally, even when both assets use 18 decimals, the same raw-unit accounting remains incorrect: for example, 1e18 WETH fee units and 1e18 launch-token fee units are treated as equal revenue even though their economic values can differ by orders of magnitude.

    Recommendation

    Track threshold revenue in a single normalized denomination, or maintain separate per-token thresholds/accounting so fees from different assets are not summed as raw units.

  37. M-06 Medium Lost LP Fees When v4-parity With Pancake Configuration Resolved
    Location
    src/v5/adapters/PancakeInfinityCLAdapter.sol:131
    Round
    Main Review

    Description

    Pancake Infinity CL allocations can currently be launched with the v4-parity fee-routing path, even though that path does not configure a runtime fee module for Pancake LP fee collection. In this path, FeePolicyLibrary.decodeFeePolicy returns no runtime fee module, so PancakeInfinityCLAdapter never calls factoryConfigurePancakeFeeModule for the minted Pancake LP NFT.

    This leaves the Pancake launcher holding the LP NFT without a _feeModuleRouting entry for its tokenId. Pancake LP fees will accrue on the position, but the documented collection path, collectLpFeesToFeeModule, reverts with FeeModuleNotConfigured. Therefore, Pancake launches using the allowed v4-parity path lose access to their LP fees through the intended v5 fee-routing mechanism.

    Recommendation

    Enforce adapter-aware fee-routing compatibility so Pancake launches either configure a runtime fee module before liquidity placement or use a dedicated no-module Pancake fee collection path with explicit fee recipients.

  38. I-08 Informational Baseline Fee Sink Remains Mutable Warning Resolved
    Location
    src/v5/launchers/BaselineMercuryClankerLauncher.sol:67
    Round
    Main Review

    Description

    For modular-fee Baseline launches, BaselineMercuryWrapper rewrites the Mercury feeRecipient to the selected v5 fee module, but the separate Mercury creator field remains launcher-supplied. Because Mercury allows the recorded creator to later call setFeeRecipient, the initial v5 fee-module sink can be changed after launch.

    This can redirect future fees that would have flowed into the v5 fee module, including amounts that the module would have split as protocol fees and beneficiary fees. This does not directly affect other launches or existing balances, and may be acceptable if launch creators are expected to control their own Baseline fee economics. However, integrators should not assume that a Baseline launch’s recorded v5 fee module remains the immutable recipient of future Mercury creator fees.

    Recommendation

    Document this behavior or constrain the Mercury creator/fee-recipient authority for Baseline launches where immutable v5 fee-module routing is required

  39. I-09 Informational Mercury Requires Full Supply At Launch Informational Resolved
    Location
    src/v5/adapters/BaselineMercuryWrapper.sol:22
    Round
    Main Review

    Description

    Baseline Mercury’s market creation model requires the launch token allocation to be supplied when the market is initialized, and the Clanker integration forwards the full assigned token supply to Mercury during launch. While v5 docs and tests imply supplemental liquidity is intended and supported for Baseline, Mercury itself does not expose an existing-market liquidity addition mechanism like the other AMM venues.

    Specifically, v5-liquidity-ownership-and-management.md says Baseline can “Append positions (single-sided token path),” and test_addLiquidityPosition_baselineAdapter_appendsPosition asserts Baseline supplemental liquidity works through a mock launcher. In production, this expectation does not match Mercury’s market model, so Baseline should be treated as a day-one allocation venue.

    Recommendation

    Update the docs/tests to mark Baseline Mercury as day-one allocation only and cannot support supplemental liquidity unless the downstream protocol implements it.

  40. M-07 Medium Supplemental Pools Can Break Fees And Swaps Validation Resolved
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/adapters/UniswapV4Adapter.sol#L199, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/fees/ClankerVolumeThresholdFee.sol#L111-L113
    Round
    Main Review

    Description

    Uniswap v4 modular fee policy is stored per (launchId, venueId), and all Uniswap v4 positions use the same VENUE_ID_UNISWAP_V4. A fixed feeToken policy is valid for the original pool because ClankerVolumeThresholdFee accepts both the launch token and the configured fee token. For example, a WETH-paired launch with feeToken = WETH can account launch-token fees and WETH fees.

    However, when the token admin later adds a supplemental Uniswap v4 pool with a different paired token, such as USDC, the new pool is still bound to the same launch-wide Uniswap fee policy. The second initializeFeePolicy call is a no-op because the venue state is already initialized, so the supplemental pool’s USDC fee asset is never validated against or added to the accepted fee-token set.

    After USDC fees accrue, a later swap on the supplemental pool triggers the hook fee-claim path. The hook forwards USDC to receiveFees(launchId, VENUE_ID_UNISWAP_V4, USDC, ...), but the fee module rejects USDC because it is neither the launch token nor the configured WETH fee token. The rejection bubbles up through the hook and reverts the swap, so trading on the supplemental pool can stop once incompatible fees are pending.

    Noting that the supplemental Uniswap liquidity is currently blocked by separate pool/locker issues, but this becomes reachable once those blockers are fixed.

    Recommendation

    Validate supplemental pool fee assets against the already-initialized venue fee policy before binding hooks/lockers to the fee module. This prevents incompatible paired-token pools from being added through the supplemental path when the existing policy uses a fixed feeToken.

    If supplemental pools with different paired assets are intended to be supported, scope fee policy by a pool/position identifier instead of only (launchId, venueId), or document that creators must use feeToken = address(0) when they want one Uniswap fee policy to accept multiple paired assets.

  41. H-12 High Unchecked lockerData Allows Malicious Fee Module Validation Resolved
    Location
    src/v5/adapters/UniswapV4Adapter.sol:154-178
    Round
    Main Review

    Description

    UniswapV4Adapter.placeLiquidity validates fee-module locker data only when context.feeModule.module != address(0). When context.feeModule.module == address(0), launcher-provided lockerData is forwarded to the downstream locker as-is, without checking its schema or contents.

    A launcher can therefore select the no-module/v4-parity fee path while still providing SCHEMA_LOCKER_FEE_MODULE_V1-shaped lockerData. Downstream locker decodes that data independently and, if the embedded feeModule is nonzero, stores it as the token’s fee route. Later LP-fee collection transfers fees to that embedded module and calls receiveFees.

    This bypasses v5 fee-module validation entirely and allows an arbitrary, unregistered contract to become the LP-fee sink for the Uniswap position. The launch appears to have no runtime fee module at the v5 level, but locker-collected fees are routed through attacker-controlled module.

    Recommendation

    Reject SCHEMA_LOCKER_FEE_MODULE_V1 locker data whenever context.feeModule.module == address(0), and keep requiring embedded fee-module locker data to match the current v5 fee-module context when a module is configured

  42. I-10 Informational Supplemental Liquidity Re-binds Revoked Fee Module Informational Partially resolved
    Location
    src/v5/ClankerCompatibilityRegistry.sol:214
    Round
    Main Review

    Description

    deployLaunch validates the concrete runtime fee module, but addLiquidityPosition rebuilds LaunchContext from the immutable deployment record and validateSupplementalLiquidityPath only revalidates the token source, launch mechanic, fee-routing module, and added adapter path. If governance later revokes the recorded fee module, a token admin can still add a supplemental position which re-binds and authorizes context.feeModule.module for the new pool without checking that the fee module remains enabled and unrevoked.

    Already-deployed positions should continue using the fee module they were launched with, even if that module is later revoked, because disabling existing fee routes can break live deployments. However, whether a newly added supplemental position under the same launchId should be treated as existing usage or as a new module binding should be an explicit protocol decision.

    If revocation is intended to block only new launches, the current behavior is acceptable; if it is intended to block all new future fee-module bindings, supplemental liquidity should revalidate the recorded fee module before placement.

    Recommendation

    Define revocation semantics for supplemental liquidity. If revoked modules should not be used for new supplemental fee routes, revalidate record.feeModule during addLiquidityPosition before adapter execution.

  43. I-11 Informational Pancake Default Params Ignore Decimals Informational Resolved
    Location
    src/v5/launchers/libraries/PancakeInfinityLaunchParams.sol:42-57
    Round
    Main Review

    Description

    Pancake launches may omit launchParams, in which case the launcher falls back to protocol defaults. These defaults use fixed raw tick/price based only on token ordering. They are not adjusted for paired-token decimals, so using assets like USDC can initialize the pool at an unexpected human-normalized price.

    Recommendation

    Document that empty Pancake launchParams are raw defaults, or require explicit launch parameters when using arbitrary paired tokens.

  44. M-08 Medium Pancake Residuals Bypass Consumption Check Rounding Partially resolved
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/launchers/PancakeInfinityCLClankerLauncher.sol#L198-L204, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/ef734f58e018a0eee57d78d3f5a0d8f01c082edc/src/v5/launchers/PancakeInfinityCLClankerLauncher.sol#L228
    Round
    Main Review

    Description

    PancakeInfinityCLClankerLauncher prefunds the Pancake CL_POSITION_MANAGER with the full assigned tokenAmount, computes integer CL liquidity from that amount, and then records the original tokenAmount as placed liquidity. The surrounding adapter/launcher checks show the intended invariant is full consumption: if the assigned launch tokens are not consumed, the flow should revert.

    However, the post-mint balance checks only inspect the launcher/adapter balances. They do not account for the residual ERC20 balances left on CL_POSITION_MANAGER. For normal/default Pancake ranges this appears to leave no meaningful remainder, but with extreme custom tick ranges, floor-rounded liquidity can require materially less than the pre-funded amount. The launch can finalize while v5 records the full allocation as locked LP even though a large remainder remains as raw ERC20 balance on CL_POSITION_MANAGER.

    Because Pancake’s position manager exposes a public SWEEP action, any caller can sweep the residual launch tokens. This can make a finalized launch appear fully collateralized by LP while part of the allocation was never actually deposited into the position.

    Recommendation

    Sweep residual launch tokens from CL_POSITION_MANAGER after minting. If the protocol strictly requires the full allocation to be added as liquidity, revert when any residual remains; however, this may DoS legitimate ranges due to dust-level rounding residue. Otherwise, enforce a bounded dust threshold and revert only when the swept residual exceeds it.

  45. H-13 High collectRewardsWithoutUnlock Lets Fees Stolen Access Control Partially resolved
    Location
    src/lp-lockers/ClankerLpLockerFeeConversion.sol:457
    Round
    Main Review

    Description

    ClankerLpLockerFeeConversion.collectRewardsWithoutUnlock is public even though it is intended for the hook-controlled path while the pool is already unlocked. An attacker can directly call PoolManager.unlock, then execute a TAKE action first on PositionManager via PositionManager.modifyLiquiditiesWithoutUnlock and transfer fee tokens to themselves. This creates negative delta debt against the shared PositionManager account.

    Then, before the unlock ends, the attacker executes collectRewardsWithoutUnlock(token). The locker then collects pending LP fees with a zero-liquidity decrease. Since the locker owns the LP NFT, the PositionManager ownership check passes, and the collected LP fees credit the same PositionManager delta account. Those credits net against the attacker-created debt, so the unlock settles while the attacker keeps the taken tokens.

    As a result, pending LP fees can be stolen before normal fee collection via collectRewardsWithoutUnlock route.

    Recommendation

    Restrict collectRewardsWithoutUnlock to only trusted hooks or another approved collection context.

Remediation Review

29 findings · July 25 to 29, 2026
  1. H-01 High Supplemental Liquidity Can Still Be DoS'ed DoS Acknowledged
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/90655e9efbf4504766004279cfb330b1a8697645/src/v5/launchers/PancakeInfinityCLClankerLauncher.sol#L214, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/90655e9efbf4504766004279cfb330b1a8697645/src/v5/launchers/PancakeInfinityCLClankerLauncher.sol#L270
    Round
    Remediation Review

    Description

    As part of the previous Pancake residual-token fix, _mintSingleSidedLiquidity was updated to include a Pancake SWEEP action after minting. This is safe during the initial launch because the launch token is unique and cannot be pre-donated before deployment, which is also reflected by the code comment: "Shared PM sweeps are safe for launch tokens (unique per deployment)".

    However, initial liquidity and supplemental liquidity are now separate flows, but both paths use the same _mintSingleSidedLiquidity helper. After launch, the token is live and anyone can transfer even 1 wei of the launch token directly to the shared Pancake CL_POSITION_MANAGER. This causes unconditional SWEEP to pull donated dust back to the launcher, causing the launcher’s exact balance-delta check to fail. As a result, anyone can still DoS the supplemental addition similar to the previous donation issue on main review but with a different path.

    Recommendation

    Consider using separate minting logic for supplemental liquidity like performing delta based accounting, or make the sweep launch-only.

    If supplemental minting also intended to have residual handling, account only for the current call’s residual rather than sweeping arbitrary pre-existing balances.

  2. H-02 High Launches With ETH Buy Can Easily DoS'ed DoS Acknowledged
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/90655e9efbf4504766004279cfb330b1a8697645/src/v5/extensions/ClankerUniv4EthDevBuyV5.sol#L15, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/90655e9efbf4504766004279cfb330b1a8697645/src/extensions/ClankerUniv4EthDevBuy.sol#L172-L174
    Round
    Remediation Review

    Description

    The previous ETH dev-buy remediation added router sweep/refund handling for partial exact-input swaps, but also introduced a strict pre-swap check that reverts with UnexpectedRouterEthBalance whenever the shared Universal Router already has any native ETH balance.

    The check is too strict because anyone can leave ETH dust on Universal Router through its payable execute function without performing any action, for example by calling it with empty commands and inputs. Once the router has even 1 wei of native ETH, every launch using native ETH dev-buy reverts with UnexpectedRouterEthBalance, causing a permissionless DoS of that launch path.

    Recommendation

    Remove the global router pre-balance requirement and track/refund only the ETH delta created by the current dev-buy operation, without depending on the router’s unrelated pre-existing balance

  3. I-01 Informational Note Regarding Uniswap Supplemental Liquidity Informational Acknowledged
    Location
    src/v5/adapters/UniswapV4Adapter.sol:578-580
    Round
    Remediation Review

    Description

    Uniswap v4 supplemental liquidity cannot be added while the launch MEV module is operational. UniswapV4Adapter requires a nonzero MEV module for v4 launches, and after launch finalization the hook enables that module for the pool. During this period, ClankerHookV2._beforeAddLiquidity rejects liquidity additions with MevModuleEnabled.

    Noting that this does not appear to permanently break supplemental liquidity. On the current fork path, same-pool Uniswap supplemental liquidity succeeds after MAX_MEV_MODULE_DELAY, which is 2 minutes, or earlier if the configured MEV module disables itself through swap flow. The behavior is therefore should be treated as an operational/documentation concern.

    Recommendation

    Document that Uniswap v4 supplemental liquidity can only be added after the MEV protection window has ended, or explicitly surface this condition in the supplemental liquidity flow

  4. H-03 High Broken Supplemental Liquidity Due To venueId DoS Acknowledged
    Location
    src/v5/launchers/PancakeInfinityCLClankerLauncher.sol:224
    Round
    Remediation Review

    Description

    PancakeInfinityCLClankerLauncher uses different venue id derivation between launch-time and supplemental liquidity placement. During initial placement, the launcher records the venue id with PancakeVenueIdLib.venueId(poolKey), which hashes the full Pancake pool key including currency0, currency1, hooks, poolManager, fee, and parameters.

    However, during placeInfinityCLSupplementalLiquidity, after validating that the supplemental request targets the same pool key, it returns a venue id derived only from currency0, currency1, and parameters. PancakeInfinityCLAdapter.placeSupplementalLiquidity then compares that returned venue id against the recorded launch position’s venue id and reverts with SupplementalLiquidityPoolMismatch.

    As a result, Pancake same-pool supplemental liquidity is unusable. This is not an intentionally rejected new-pool case: the supplemental call targets the same pool, but fails because the launcher returns a non-canonical venue id. Token admins cannot append additional Pancake liquidity to their launch pool through the intended v5 supplemental liquidity flow.

    Additionally, this is separate from the prior Pancake supplemental-liquidity issue (remediations H-01) as they have different root causes: that one is caused by sweep/balance accounting, while this one is caused by inconsistent venueId derivation, so fixing one does not resolve the other failure path.

    Recommendation

    Use the same canonical PancakeVenueIdLib.venueId(poolKey) derivation in both initial and supplemental Pancake liquidity paths.

  5. I-02 Informational Refunds Require Payable Callers Documentation Acknowledged
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/90655e9efbf4504766004279cfb330b1a8697645/src/v5/ClankerV5.sol#L251, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/90655e9efbf4504766004279cfb330b1a8697645/src/v5/ClankerV5.sol#L1014
    Round
    Remediation Review

    Description

    Refunds during deployment or failed extension paths are returned to the msg.sender. However, if the caller is a contract (for example during relayed launches) without a payable receive or fallback, these refund transfers revert and the launch fails.

    Recommendation

    Document that callers need to be able accept native tokens for refunds.

  6. I-03 Informational Document Dev-Buy Recipient Also Receives Refunds Documentation Acknowledged
    Location
    src/extensions/ClankerUniv4EthDevBuy.sol:227-238
    Round
    Remediation Review

    Description

    ClankerUniv4EthDevBuy refunds unspent dev-buy input to devBuyData.recipient for both native ETH and ERC20 inputs. This is reasonable because the legacy extension is called by core, so msg.sender is not the actual payer; refunding to msg.sender would send funds back to core and require additional steps to reach the actual payer.

    Recommendation

    Document that devBuyData.recipient receives both bought tokens and any unspent ETH or ERC20 input refunded after router execution. If separate refund routing is desired, add an explicit refund-recipient parameter.

    Additionally, document that refund recipient needs to be able to accept native tokens for refunds.

  7. I-04 Informational Registry Admin Update Event Is Not Emitted Events Acknowledged
    Location
    src/v5/ClankerDeploymentRegistry.sol:78
    Round
    Remediation Review

    Description

    Both ClankerV5 and ClankerDeploymentRegistry declare DeploymentTokenAdminUpdated, but only core emits it.

    ClankerDeploymentRegistry.updateTokenAdmin updates record.tokenAdmin without emitting the registry event.

    Recommendation

    Either emit the event from the registry as well, or remove the registry event declaration.

  8. L-01 Low Mechanic Context Uses Raw Launch Id Compatibility Acknowledged
    Location
    src/v5/mechanics/DefaultLaunchMechanic.sol:58
    Round
    Remediation Review

    Description

    Core now derives the canonical launch id as keccak256(creator, envelope.launchId) and uses it in LaunchContext.launchId, registry records, events, fees, and position tracking. However, the default and valuation-guarded launch mechanics still encode the raw envelope.launchId inside launchMechanicContext.

    Noting that current production modules do not use that embedded id for state. Future consumers decoding mechanic context may treat the raw id as canonical and treat it incorrectly

    Recommendation

    Encode the canonical launch id in mechanic context too, or document the embedded value as the raw/client launch id.

  9. L-02 Low Revoked Fee Module Blocks Supplemental Adds DoS Acknowledged
    Location
    src/v5/ClankerCompatibilityRegistry.sol:278
    Round
    Remediation Review

    Description

    addLiquidityPosition revalidates the launch’s recorded runtime fee module before allowing supplemental liquidity. If that module is later revoked or disabled, the launch can keep using its already-bound fee route, but new supplemental liquidity reverts because rebinding the revoked module is prevented.

    Ideally, a launch that originally used fee module A should not be able to bind A again after A is revoked, but it should have a way to use a governance-approved replacement fee module B for future liquidity additions.

    However, that path is not supported at the moment, so the fix also blocks all future supplemental liquidity for launches that used the revoked module.

    Recommendation

    Evaluate whether this behavior is intended and document that revoking a fee module disables future supplemental liquidity for launches using that module.

    If continued liquidity additions are required after revocation, add an explicit validated fee-module replacement path. However, this may increase the complexity and introduce accounting concerns.

  10. L-03 Low Missing onlyLaunchFactory When Adding Liqudity Access Control Acknowledged
    Location
    src/v5/adapters/PancakeInfinityCLAdapter.sol:200
    Round
    Remediation Review

    Description

    PancakeInfinityCLAdapter.placeLiquidity was restricted to onlyLaunchFactory, but the newly added placeSupplementalLiquidity remains public and trusts caller-supplied context/target position data. This allows direct callers to bypass core validation and mint untracked supplemental Pancake positions, including against an existing launch pool if they supply the launch token themselves.

    Noting that the same boundary exists one layer lower: PancakeInfinityCLClankerLauncher.placeInfinityCLLiquidity and placeInfinityCLSupplementalLiquidity are also public, so callers can bypass the adapter entirely and mint unmanaged launcher-custodied NFTs if they supply the tokens.

    Also, similar issues exist in the Baseline paths. However, Baseline supplemental liquidity is not supported and reverts regardless of the caller, and launchWithTokenAllocation requires the full token supply, so direct calls are unlikely to succeed in practice.

    Recommendation

    Restrict Pancake supplemental placement in adapter to the launch factory as well. Additionally, consider restricting launcher functionalities to the adapter as well.

  11. I-05 Informational Supplemental CL Ranges Must Match Price Documentation Acknowledged
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/90655e9efbf4504766004279cfb330b1a8697645/src/v5/launchers/PancakeInfinityCLClankerLauncher.sol#L203, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/90655e9efbf4504766004279cfb330b1a8697645/src/v5/launchers/PancakeInfinityCLClankerLauncher.sol#L215
    Round
    Remediation Review

    Description

    Supplemental liquidity paths supply only the launch token, so the added tick range must be single-sided relative to the current pool price. If the live price is inside the range, the position requires both assets and may revert or fail exact-consumption checks.

    This is expected CL behavior, but should be documented for token admins/integrators.

    Noting that the same constraint exists at launch time, but the launch paths perform stricter range/starting-price validation than supplemental additions.

    Recommendation

    Document the provided range must be valid compared to the live price/slot0 values, and consider adding fail-fast validation where the live price is available.

  12. H-04 High Min FDV Bypass Via 1 BPS Allocation Logical Error Acknowledged
    Location
    src/v5/adapters/UniswapV4Adapter.sol:368-376
    Round
    Remediation Review

    Description

    The H-05 remediation moved Uniswap valuation away from the initialized spot and toward executable liquidity, which resolves the previous max-FDV gap where liquidity could sit above the guarded price. However, the new quote logic represents all Uniswap ranges with a single value: the highest nonzero tickLower. While this remediation works for a maximum-FDV cap, it is not sufficient for a minimum-FDV floor.

    A creator can place 9,999 bps of launch liquidity at a low tick that would fail the governed minimum FDV, then add a 1 bps decoy position at a higher tick that satisfies the floor. quoteLaunchFdvPaired reports only the high-tick position, so LaunchValuationValidation accepts the launch even though almost all executable supply is available at the prohibited low valuation.

    A buyer can then acquire from the low range while the deployment appears to have passed the minimum-FDV guard. Later buyers or integrations relying on the guard are exposed to a manipulated launch where the creator/early buyer captured supply under the prohibited floor.

    Recommendation

    Validate every actually funded range boundary instead of selecting one representative high tick. Enforce the minimum against the lowest executable funded boundary and the maximum against the highest.

  13. H-05 High Hook-Only Fee Collection Still Allows Stolen Fee Access Control Acknowledged
    Location
    src/lp-lockers/ClankerLpLockerFeeConversion.sol:523-532
    Round
    Remediation Review

    Description

    H-13 was remediated by restricting collectRewardsWithoutUnlock so only the token’s registered pool hook can call it. This blocks the direct public-call variant where an attacker opens PoolManager.unlock, preloads debt against the shared PositionManager delta account, and then calls the locker directly. However, additional testing surfaced that a similar debt-netting attack path is still possible through a trusted hook.

    An attacker can open a PoolManager.unlock, call PositionManager.modifyLiquiditiesWithoutUnlock(TAKE) to create debt on the shared PositionManager account, and then perform a normal swap on the victim Clanker pool within the same unlock. During beforeSwap, the trusted Clanker hook calls collectRewardsWithoutUnlock, so the hook-only caller check passes. The locker then collects LP fees through the same shared PositionManager delta account, and those fee credits net against the attacker’s preloaded debt.

    As a result, pending LP fees can still be stolen even though the locker is only called by the registered hook. Fee recipients receive only the remaining net collection, while the attacker keeps the tokens taken before the hook-triggered collection.

    Recommendation

    Require zero PositionManager transient currency deltas for both pool currencies before unlocked fee collection. If either delta is nonzero, consider skipping hook-triggered collection instead of reverting, since reverting could DoS legitimate composed transactions with preexisting deltas.

    Note that this may delay fee realization in those composed transactions, but fees can still be collected later through the locked collection path.

  14. H-06 High Pancake Launches Revert On Rounding Dust Compatibility Acknowledged
    Location
    src/v5/launchers/PancakeInfinityCLClankerLauncher.sol:166-172
    Round
    Remediation Review

    Description

    M-08 was fixed by enforcing a full-consumption invariant after sweeping residual launch tokens, which prevents launch tokens from remaining outside the recorded LP position. However, the non-tolerant full-consumption check also makes normal Pancake CL rounding incompatible with many realistic launch ranges.

    Pancake Infinity CL launch liquidity is minted by converting the requested launch-token amount into a floor-rounded liquidity value, then settling the actual token amount required for that liquidity. Because the conversion is effectively amount -> liquidity -> amount, the settled amount can be slightly lower than the original requested amount for otherwise valid tick ranges.

    With the default 100B token supply, a $10k-$100k FDV launch corresponds to roughly $0.0000001-$0.000001 per Clanker token. At an assumed $2,000/WETH, that maps to approximately [-237240, -214140] raw WETH/token ticks. With USDC as the paired token, assuming $1/USDC and 6 decimals, the same human price band maps to approximately [-437520, -414480] ticks.

    While the launches at default range is successful, realistic FDV-aware ranges such as $10k-$50k, $10k-$100k, $100k-$200k, $100k-$1M, $1M-$10M, and $10M-$100M leave residual dust across WETH-paired and USDC-paired launches, for both Clanker token0 and token1 ordering. These launches revert with LaunchTokensNotFullyConsumed even though the configured ranges are economically realistic and the leftover amount is only a rounding artifact.

    Recommendation

    Sweep residual launch tokens and allow a tightly bounded dust tolerance instead of requiring exact full-token consumption for every valid Pancake range, and consider refunding the tolerated dust. Revert only when the residual exceeds that tolerance.

  15. I-06 Informational Baseline Deploy Docs Use Old Address Documentation Acknowledged
    Location
    v5-baseline-mercury-launcher-deploy.md
    Round
    Remediation Review

    Description

    BaselineMercuryLauncherDeployConfig was updated with a new PREDICTED_ADDRESS and INIT_CODE_HASH for the current BaselineMercuryClankerLauncher bytecode. However, baseline-mercury-launcher-deploy.md still documents the previous allowlist address and init-code hash

    Recommendation

    Update the markdown deployment guide to match BaselineMercuryLauncherDeployConfig

  16. M-01 Medium Crosschain FDV Uses Local Supply Logical Error Acknowledged
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/90655e9efbf4504766004279cfb330b1a8697645/src/v5/token-sources/SyncedTokenSource.sol#L140, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/90655e9efbf4504766004279cfb330b1a8697645/src/v5/token-sources/SyncedTokenSource.sol#L158
    Round
    Remediation Review

    Description

    SyncedTokenSource reports only the current chain’s LOCAL_GENESIS_ALLOCATION as totalLaunchSupply, while the deployed ClankerTokenCrosschain also has an immutable GLOBAL_SUPPLY and controller-gated bridge minting. Core then uses this local totalLaunchSupply for launch supply accounting and valuation checks.

    As a result, valuation-guarded launches can approve a crosschain launch based only on the local tranche rather than the full global token supply. For example, a launch with 30% of supply minted on the current chain and 70% allocated to other chains will be valued using only the 30% local tranche, even though the remaining supply can later bridge into the same market. This can make a launch appear to satisfy max-FDV limits while the actual global FDV is materially higher.

    Recommendation

    For SyncedTokenSource, compute valuation against GLOBAL_SUPPLY, or disallow valuation-guarded launch paths unless the policy explicitly defines the limit as local-chain FDV only.

  17. L-04 Low Descriptor Records Only Local Supply Logical Error Acknowledged
    Location
    src/v5/token-sources/SyncedTokenSource.sol:158
    Round
    Remediation Review

    Description

    SyncedTokenSource registers the token descriptor with totalLaunchSupply set to the current chain’s LOCAL_GENESIS_ALLOCATION. However, the deployed ClankerTokenCrosschain also exposes an immutable GLOBAL_SUPPLY and a full chain allocation manifest, which are not recorded in the descriptor.

    This can mislead indexers, UIs, or integrations that rely on TokenDescriptorRegistry as the normalized token metadata source. For crosschain tokens, the descriptor’s totalLaunchSupply is only the local launch tranche, not the global supply basis of the token.

    Recommendation

    Extend crosschain descriptors to include global supply and controller metadata, or clearly document that totalLaunchSupply is chain-local for SyncedTokenSource and consumers must read GLOBAL_SUPPLY from the token.

  18. I-07 Informational Warning Regarding Multi Chains Warning Acknowledged
    Location
    Global
    Round
    Remediation Review

    Description

    Current documented deployments are mainly on chains such as Base, Arbitrum, and Unichain, where classic public mempool front-running assumptions are weaker or do not directly apply. However, the crosschain test files in the latest commit include chain id 534352, which corresponds to Scroll, and Scroll exposes pending transaction / mempool-style behavior.

    If protocol is expected to be deployed on chains with public mempools, deterministic launch parameters may let attackers front-run a pending launch and directly initialize the target Uniswap v4 or Pancake pool before the official launch transaction executes. This can cause the later launch to revert with an already-initialized pool.

    Noting that this is only a deployment-environment warning and can be discarded if those chain ids are present only for testing.

    Recommendation

    Be aware of this situation. If chains with mempools will be used, consider private RPCs to prevent frontrunning/deterministic address DoS attacks

  19. I-08 Informational Unbound Bridge LaunchId Validation Acknowledged
    Location
    CrosschainSupplyController.sol
    Round
    Remediation Review

    Description

    CrosschainSupplyController.bridgeOut accepts a caller-provided launchId, and bridgeIn only authenticates the exact message through the transport adapter; neither side verifies that the launchId belongs to the bridged token. This can produce real burn/mint transfers with misleading launch ids in bridge events/messages.

    No production ICrosschainTransportAdapter implementation is included in the reviewed codebase and is OOS. Only the interface and mock adapter are present, so this may be part of unfinished crosschain integration work.

    Recommendation

    Bind token => launchId in the controller or require adapters to enforce it when they are production ready.

  20. M-02 Medium One Token-Restricted Beneficiary Blocks Fees Logical Error Acknowledged
    Location
    src/v5/fees/ClankerVolumeThresholdFee.sol:524
    Round
    Remediation Review

    Description

    ClankerVolumeThresholdFee stores the creator-selected beneficiary list during fee policy initialization and later distributes each venue’s fees through a single push-based distributeFees call. The function transfers the protocol share and then pushes each beneficiary share with SafeERC20.safeTransfer.

    If the fee token rejects transfer to any configured beneficiary, the entire distribution reverts. This rolls back the protocol payment, all other beneficiary payments, and the pending-fee accounting updates.

    This can happen with B20 transfer policies or externally blacklisted fee assets, and permanently lock pending and future fees for the affected launch, venue, and token.

    Recommendation

    Use pull-based per-recipient accounting or otherwise isolate failed payouts so one restricted beneficiary cannot block distribution for the protocol and all other recipients. Alternatively, consider adding an authorized beneficiary-rotation path for permanently unpayable recipients.

  21. M-03 Medium Pancake Defaults Depend On Token Order Logical Error Acknowledged
    Location
    https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/90655e9efbf4504766004279cfb330b1a8697645/src/v5/launchers/libraries/PancakeInfinityLaunchParams.sol#L94, https://github.com/GuardianOrg/contracts-team1-1782421575052/blob/90655e9efbf4504766004279cfb330b1a8697645/src/v5/launchers/libraries/PancakeInfinityLaunchParams.sol#L138-L144
    Round
    Remediation Review

    Description

    PancakeInfinityLaunchParams builds default launch parameters from _configuredDefaults(token0IsClanker) and then passes the result through _orient. For token1-Clanker launches, _configuredDefaults(false) already selects the positive canonical range [60, 6000], but _orient(false) mirrors it again into the negative pool-side range [-6000, -60].

    As a result, default Pancake launches start at different normalized prices depending only on token address ordering. When Clanker is token0, the pool starts at tickLower = -6000, so the launch price is pairedToken / Clanker at tick -6000. When Clanker is token1, the final pool-side range becomes [-6000, -60] and the pool starts at tickUpper = -60; because pool price is then Clanker / pairedToken, the normalized pairedToken / Clanker price is the inverse, equivalent to tick +60.

    This means two otherwise identical launches using empty/default Pancake params can differ by 6060 ticks, roughly 1.83x, solely due to token sorting. The valuation quote uses the same order-dependent defaults, so it reports the divergent FDV instead of correcting it. This can change early buyer execution and make valuation-guard outcomes depend on token address order.

    Recommendation

    Define Pancake defaults in one canonical pairedToken / Clanker price frame, then apply token-order orientation exactly once for pool placement and valuation.

  22. M-04 Medium Baseline Uses Local Supply As Full Supply Compatibility Acknowledged
    Location
    src/v5/launchers/BaselineMercuryClankerLauncher.sol:74
    Round
    Remediation Review

    Description

    Baseline Mercury placement requires the full token supply to be deposited at launch. SyncedTokenSource only mints and hands core the chain-local genesis tranche, but at deployment time that tranche equals the token’s live local totalSupply, so the Baseline full-supply check passes.

    After the corresponding crosschain deployments and bridge mints occur, additional legitimate supply can arrive on the initial chain. Mercury and Clanker still record the launch against the smaller local tranche, creating a discrepancy between the recorded full-supply market and the token’s actual crosschain supply.

    Recommendation

    Consider rejecting Synced/crosschain token sources for Baseline. Alternatively, if Baseline supports a crosschain-specific initialization path, use that path explicitly so the market is initialized against global supply rather than deployment-time local supply.

  23. M-05 Medium Zero Protocol Numerators Disable Fees Logical Error Acknowledged
    Location
    src/v5/fees/ClankerVolumeThresholdFee.sol:252-253
    Round
    Remediation Review

    Description

    ClankerVolumeThresholdFee lets creators set initialProtocolNumerator and postThresholdProtocolNumerator, and validation only enforces an upper bound. The protocol recipient is governance-controlled, but the protocol share can still be set to zero.

    A creator can configure both numerators as zero and route all future venue fees to creator-selected beneficiaries. They can also set volumeThreshold to 0 as well.

    This makes protocol trading-fee revenue optional for modular-fee launches. If Clanker is expected to earn protocol fees from modular fee routes, this allows creators to opt out permanently.

    Recommendation

    Enforce governance-approved fee profiles or nonzero minimum protocol numerators for both threshold states.

  24. I-09 Informational Passive Sinks Assume Standard ERC20 Informational Acknowledged
    Location
    src/v5/fees/ClankerVolumeThresholdFee.sol:23
    Round
    Remediation Review

    Description

    Passive sinks flush/recover their full token balance in one transfer. Nonstandard reserve tokens with caps, blacklists, transfer fees, or balance-dependent restrictions can make those calls revert or account unexpected amounts.

    Recommendation

    Document standard ERC20 reserve requirements.

  25. L-05 Low Synced Pool Squatting Across Chains Frontrunning Acknowledged
    Location
    Global
    Round
    Remediation Review

    Description

    Synced tokens have deterministic same-address deployments across chains. Once one chain is launched, the token address and pool parameters may be public before the same launch is completed on another chain.

    During this gap, an attacker can call public initializePoolOpen on the destination chain with the future token address and expected Uniswap pool parameters. The later v5 launch then deploys the token but reverts when the adapter tries to initialize the already-created pool.

    Noting that this is different from mempool-dependent front-running: deployment on the first chain can reveal the token address and pool parameters, giving anyone enough information to squat the destination-chain pool during the multi-chain deployment gap

    Recommendation

    Document the sequencing risk and use protected/private execution for synced deployments where this matters.

  26. I-10 Informational Misleasding Comment In Fee Module Informational Acknowledged
    Location
    src/v5/fees/ClankerVolumeThresholdFee.sol:85
    Round
    Remediation Review

    Description

    The ClankerVolumeThresholdFee has this code comment: "Halves hook protocol share after a cumulative revenue threshold is reached."

    However, the current behavior is not halving the protocol share as both pre and post threshold numerators are launcher provided.

    Recommendation

    Update the comment. If the intended behavior is indeed halving the fees, enforce it in the implementation.

  27. L-06 Low Reauthorization Revives Stale Forwarders Logical Error Acknowledged
    Location
    src/v5/fees/ClankerVolumeThresholdFee.sol:536
    Round
    Remediation Review

    Description

    ClankerVolumeThresholdFee tracks whether a depositor is authorized independently from the generation used to validate its delegated fee forwarders.

    When the launch factory or module owner revokes a depositor through authorizeFeeDepositor(..., false), the function only clears _authorizedDepositors. It does not advance _feeForwarderGeneration or otherwise invalidate forwarders previously authorized by that depositor.

    While the depositor remains revoked, both the depositor and its forwarders are rejected because receiveFeesForDepositor first checks _authorizedDepositors. However, if the same depositor is subsequently reauthorized, every forwarder whose stored generation still matches the unchanged current generation automatically becomes authorized again.

    Recommendation

    If this is not explicitly intended, consider advancing the depositor’s forwarder generation whenever it transitions from authorized to unauthorized, requiring forwarders to be explicitly authorized again.

  28. L-07 Low Getter Hides Paired-Token Pending Fees Unexpected Behavior Acknowledged
    Location
    src/v5/fees/ClankerVolumeThresholdFee.sol:639
    Round
    Remediation Review

    Description

    venueState.pendingBalance reports pending fees only for the threshold-denomination token. When a venue collects both launch-token and paired-token fees, paired-token fees may be pending and distributable even though the getter reports zero.

    No other view function exposes _pendingByToken for an arbitrary token, so keepers and interfaces relying on venueState may overlook paired-token fees and leave them undistributed.

    Recommendation

    Consider adding a view function returning the pending balance for a specified (launchId, venueId, token).

  29. H-07 High Shared Hook Enables Cross-Pool Fee Theft Logical Error Acknowledged
    Location
    ClankerHookV2
    Round
    Remediation Review

    Description

    Hook protocol fees are stored as ERC-6909 claims keyed only by the shared hook address and currency. At the beginning of a swap, _hookFeeClaim burns the hook’s entire balance for the current paired currency and credits it to the launch associated with whichever pool swaps next, without tracking which pool generated the fees.

    An attacker can create a pool using the same hook and paired currency, wait for a confirmed victim swap to generate fees, and execute a small regular swap through the attacker pool. The victim’s pending fees are credited to the attacker launch and can be distributed to attacker-controlled beneficiaries, making the theft permissionless and repeatable.

    Additionally, this does not require an active attack and occurs naturally, as each swap assigns the previous swap’s pending protocol fees to whichever shared-currency pool swaps next.

    Note: This issue was identified in the out-of-scope legacy ClankerHookV2 contract while testing the in-scope V5 integration; the contract was not audited as part of this engagement, and this finding should not be interpreted as a comprehensive review or security assurance of that contract.

    Recommendation

    Track accrued hook claims per PoolId and forward only the amount generated by the pool currently being claimed.

Put your code through the same review.

This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.

Get a quote