Panoptic

Panoptic MultiToken Merkle Distributor

Cantina Security Report

Organization

@panoptic-labs

Engagement Type

Cantina Reviews

Period

-

Researchers


Findings

Medium Risk

1 findings

1 fixed

0 acknowledged

Low Risk

1 findings

1 fixed

0 acknowledged

Informational

3 findings

2 fixed

1 acknowledged


Medium Risk1 finding

  1. Anyone can call claim for any account

    Severity

    Severity: Medium

    Submitted by

    m4rio


    Description

    The function allows third parties to execute a claim for a given leaf, and tokens are always sent to the account encoded in the leaf. The trade‑off: if a leaf contains a wrong account, a third party can proactively claim to that wrong address, preventing later recovery via the admin’s withdrawUnclaimed.

    Recommendation

    Consider using msg.sender to claim instead of account or a signature from the account.

Low Risk1 finding

  1. Admin key is a single EOA set to msg.sender and immutable

    Severity

    Severity: Low

    Submitted by

    m4rio


    Description

    admin is set to the deployer EOA and cannot be changed. If the key is lost or compromised, unclaimed funds after withdrawableAt can be withdrawn by an attacker, and there is no recovery path.

    Recommendation

    Pass the admin address in the constructor and use a multisig. Consider adding a one‑time admin transfer mechanism or making admin upgradable behind a multisig/DAO if governance changes are expected.

Informational3 findings

  1. Token registry uses mapping + array instead of an enumerable set

    State

    Acknowledged

    Severity

    Severity: Informational

    Submitted by

    m4rio


    Description

    supportedTokens is a mapping(address => bool) plus a separate tokenList array. This pattern works but can drift out of sync (e.g., accidental duplicates in tokenList, or forgetting to push/remove). Managing two data structures also increases maintenance.

    Furthermore they are not enforced anymore on claim so right now it seems they are only for offchain reading?

    Recommendation

    Use EnumerableSet.AddressSet from OZ for a single source of truth (add/remove/check/iterate safely).

    Consider enforcing them on claim or removing them at all.

  2. Lack of end‑to‑end Merkle tests before release

    Severity

    Severity: Informational

    Submitted by

    m4rio


    Description

    Without fork/integration tests that attempt to claim every leaf, discrepancies in leaf encoding, amounts, or unsupported tokens can surface only after deployment, potentially DoS’ing some claims.

    Recommendation

    Add a pre‑release script/test that: (1) rebuilds the Merkle root from the JSON snapshot, (2) attempts a fork dry‑run claim for every leaf, and (3) checks contract balances cover all amounts for all tokens.

  3. Using MerkleProof.verify (memory) instead of verifyCalldata

    Severity

    Severity: Informational

    Submitted by

    m4rio


    Description

    verify copies the proof to memory. Since the proof is already calldata, using verifyCalldata avoids an unnecessary copy.

    Recommendation

    Replace with MerkleProof.verifyCalldata(merkleProof, merkleRoot, node).