DeFi Security AllianceRequest an audit
Menu

EVM patterns

Airdrop Contract Security: Entitlements, Replay State and Unclaimed Supply

A correct proof can still authorize a mistaken allocation, and it can remain valid after use. Airdrop contract security depends on the entitlement dataset, consumption state, token behavior and authority that survives the claim period.

Small parachutes carrying coins descending onto a branching tree of nodes, representing airdrop claim contract security.

Key facts

Original census
26 archived test declarations; none executed in this research
Manual classification
5 repeated-claim or exactly-once cases; 1 narrow name signal
Proof boundary
Membership does not establish allocation fairness or unique human identity
Lifecycle boundary
Unclaimed-supply authority remains a separate review target

Merkle claim design

Airdrop contract security begins with a precise entitlement: which account may receive which amount under which distribution. The claim contract verifies that entitlement and records its consumption. A valid cryptographic proof is only part of the design. The distribution data, replay state, token behavior and authority over unclaimed funds also need review.

The fetched Uniswap MerkleDistributor implementation binds an index, account and amount into the leaf it verifies. The root and token are immutable in that implementation. A successful claim marks the index as claimed before transferring tokens to the account. Those choices define a compact state machine that can be inspected independently of the website used to present a claim button.

Follow the entitlement from dataset to transfer
StageSecurity questionEvidence
Dataset generationDoes the intended allocation match the generated leaves?Input dataset, generator revision and reconciliation totals
Leaf encodingAre account, amount and claim identity bound unambiguously?Encoding specification and boundary tests
Root publicationDoes the deployed root match the approved distribution?Root hash and deployment record
Claim executionDoes the proof authorize this recipient and amount?Valid and invalid proof tests
Token transferDoes the actual token behavior match the accounting assumption?Token identity and integration tests

The root commits to the generated dataset, not to the fairness or correctness of the allocation policy. If the generator assigns the wrong amount to an account, a correct proof can authorize that wrong amount. Reconcile the dataset against the approved allocation before deployment and preserve enough information to reproduce the root. A review that starts only at the Solidity contract misses this boundary.

Leaf encoding must also match the proof library. The fetched OpenZeppelin MerkleProof source documents assumptions about sorted pairs and warns about leaf construction that can be confused with internal-node encoding. Do not mix a generator from one scheme with a verifier from another merely because both produce a value called a Merkle root.

Treat the claim interface as another distribution channel, not the source of entitlement. A user should be able to verify the official contract and the requested operation. The frontend's presentation cannot override the recipient and amount committed in the proof. A compromised interface can still mislead a user into signing an unrelated transaction even when the claim contract is correct.

The token audit guide is relevant because the distributor ultimately relies on token behavior. A nominal transfer amount does not automatically establish the recipient's balance change for every token design. The airdrop review should state which token assumptions were tested.

Replay and double-claim

A proof can remain cryptographically valid after it has been used. Preventing another payout therefore requires state that records consumption or another enforceable uniqueness rule. In the inspected distributor, the index selects a bit in a bitmap. The proof binds that index to the account and amount, while the bitmap records whether it has already been claimed.

The bitmap arithmetic divides the index by 256 to select a storage word and uses the remainder to select a bit. Those constants come from the fetched implementation. Boundary tests should exercise adjacent indices across a word boundary, as well as repeated use of the same index. A storage optimization is still part of the authorization state machine.

A proof can remain valid after its entitlement is consumedA second submission can pass the same membership proof but must fail the consumption check before another payout.Committed leafProof acceptedClaim consumedTransfer effect
A proof can remain valid after its entitlement is consumed. A second submission can pass the same membership proof but must fail the consumption check before another payout.
  1. The root binds the approved claim identity, recipient and amount.
  2. Verification establishes membership in that distribution.
  3. Persistent state records that the entitlement has been used.
  4. Tokens move only after the claim state is updated under the intended flow.

Do not confuse account uniqueness with claim uniqueness. A distribution may legitimately contain several allocations for one account. If the design uses an index as its claim identity, the replay state must follow that identity. A rule that simply marks the account as used could reject valid allocations, while a rule that fails to bind the index into the leaf could create a different authorization problem.

Cross-distribution replay is a separate question. Two contracts or two chains may reuse the same root or signing configuration. Determine whether a claim is intended to be valid independently in each deployment. If not, the authorization scheme must bind the relevant distribution domain. A local claimed bitmap cannot record consumption in another contract by itself.

The original test census below is deliberately labeled archival. The public repository contains 26 named test declarations in a file ending .old. We inspected those declarations and their blocks; we did not execute them as a current suite. Several tests address repeated claims, but a keyword scan found fewer than manual review because the names express the same property in different ways.

A useful replay test specifies both the first successful effect and the second rejected effect. Merely expecting a revert on a malformed second transaction does not establish that a valid duplicate is rejected. Preserve the input proof, claim identity and resulting state so the test's intended property is visible to another reviewer.

Signature-based claims

A signed entitlement replaces or supplements a dataset proof with an authorization from a signer. The review then needs to identify who can sign, what the signed message means and how the contract prevents reuse outside the intended context. A signature demonstrates authorization under a key and message scheme; it does not establish that the off-chain allocation decision was correct.

EIP-712 defines typed structured data signing and domain separation. Its specification explicitly leaves replay protection to the application. Including a domain is useful, but a contract still needs a consumption rule, expiry behavior or another mechanism appropriate to the authorization being granted.

Fields to define for a signed entitlement
Field or ruleQuestionFailure to test
Recipient and amountWho receives what under the signature?A caller substitutes another recipient or value
Distribution domainWhich contract, chain and campaign may use it?The authorization is accepted in an unintended deployment
Nonce or claim identityWhat makes the authorization consumable once?A valid signature pays repeatedly
ExpiryWhen does the entitlement cease to be usable?Boundary behavior differs from the product rule
Signer authorityWhich key or contract is authorized to issue claims?A rotated or unapproved signer remains accepted

Decide whether anyone may submit a valid claim for the recipient. The inspected Merkle implementation sends tokens to the account bound in the leaf, so the submitting caller is not automatically the beneficiary. A signature-based design needs an equally explicit rule. If a relayer fee or alternative recipient is supported, bind that choice into the authorization rather than infer it from a transaction sender.

Signer rotation is a state transition with pending authorizations. Determine whether old signatures remain usable and how users can identify the applicable signer version. Revoking a key can prevent abuse while also invalidating legitimate unclaimed entitlements. The operating procedure needs a recovery or reissuance policy consistent with the contract's actual rules.

The signature replay analysis provides the broader domain and nonce questions. For an airdrop, add the distribution lifecycle: allocation freeze, claim opening, expiry and handling of unclaimed supply. A correct signature check cannot establish that a post-expiry sweep authority is configured safely.

Keep private signing material outside the published dataset and claim interface. The audit package can document signer roles and test fixtures without exposing production secrets. What matters to the reviewer is the authority model, the signed payload and the verifiable behavior of the on-chain checks.

Sybil controls

A Merkle tree proves membership in a dataset. It does not prove that each address corresponds to a distinct person. Sybil resistance concerns the allocation policy's ability to limit one actor's use of multiple identities. That policy sits largely upstream of the claim verifier, and it has its own evidence and error costs.

Keep eligibility rules reproducible where possible. Record the observation window, the data sources, the treatment of linked accounts and the appeal process. A dataset can be internally consistent while excluding legitimate users or including coordinated accounts. Those are allocation-quality questions, not necessarily defects in Merkle proof verification.

Separate allocation review from claim review
Review layerQuestionWhat a passing result means
Eligibility policyDoes the rule describe the intended participants?The selection logic is specified, not that every identity is unique
Data processingWas the rule applied consistently to the recorded inputs?The dataset can be reproduced from those inputs
Appeal or correctionCan a documented error be resolved under the lifecycle rules?There is a controlled process, not an unlimited administrative override
Claim contractCan only committed entitlements produce the intended effect?The verifier enforces its scope, not the fairness of the allocation

A late correction can create contract-level consequences. Changing a root may invalidate proofs already distributed or interact with claimed indices. If the design permits root updates, define how allocations and consumption state carry across versions. If the root is immutable, the team needs an alternative distribution process that does not misrepresent the original contract's behavior.

Do not publish an unsupported Sybil-detection accuracy rate. A credible rate needs a labeled population and a definition of false acceptance and false rejection. This research did not obtain such a dataset, so the article makes no claim that a particular filter identifies a stated share of coordinated accounts.

The claim review and the allocation review should produce separate conclusions. Joining them in one launch package is useful, but collapsing them into a single "secure airdrop" badge hides the boundary between cryptographic enforcement and policy judgment.

Named incidents

The ZKsync primary incident report describes a compromise of the administrator for unclaimed airdrop supply. On , the attacker used the authority to mint 111,881,122 unclaimed ZK tokens. The report identifies three Merkle distributor contracts and an administrator configured as a one-of-one multisig, rather than the intended governance arrangement.

The relevant function was sweepUnclaimed(). The report attributes the exposure to a procedural risk-classification and configuration failure, and says the exact method of compromising the key was unknown at the time of the report. It does not describe a broken Merkle membership proof. The same account reports that funds were returned after a safe-harbor offer.

Primary case and the boundary it exposes
CaseConfirmed mechanismIntegration lesson
ZKsync unclaimed-token incident, April 2025Compromised administrator used the unclaimed-supply functionReview post-claim authority and deployment configuration separately from proof verification

The general engineering lesson is to inspect the distribution after the claim window ends. An airdrop can stop accepting ordinary claims while retaining powerful authority over unclaimed funds. That authority may receive less attention because the public launch is over, yet it remains part of the live attack surface.

For a new design, list every post-expiry operation and its caller. Compare the intended governance owner with the deployed one. Then test the transition from active claims to the completed distribution state. This is an original review recommendation drawn from the mechanism, not a claim that the same configuration appears in other airdrops.

Avoid importing an incident's dollar estimate into a separate loss table without its valuation time and accounting basis. Token quantities, sale proceeds and returned assets are different measurements. The post-mortem guide explains why those figures should remain distinct.

Audit checks

Freeze the dataset generator and contract revision together. The auditor should be able to regenerate the root from the approved input and compare it with the deployment. Include the token address, claim window if one exists, administrative roles and any sweep or recovery path. A contract-only package is incomplete when the security property depends on off-chain allocation or post-deployment configuration.

Airdrop review acceptance record
CheckExpected evidenceUnresolved state
Allocation reconciliationApproved totals and reproducible root generationDataset cannot be traced to the intended policy
Proof boundariesRecipient, amount and index substitution testsOnly successful claims were tested
Consumption stateRepeated valid claim rejected after the first effectReplay behavior is assumed from a helper library
Token integrationActual transfer behavior and failure handlingToken assumptions are undocumented
Administrative lifecycleCurrent roles and post-expiry operations reviewedLaunch configuration differs from intended governance

Run tests for failure as well as success. Include insufficient distributor balance, an invalid proof, a valid proof with a changed field and a repeated valid entitlement. For designs with callbacks or nonstandard tokens, test the actual interaction model. State whether those cases ran against the release code, an isolated model or an archived reference suite.

The security tools hub can support static analysis and testing, while the Pharos Production member profile provides one reference for external review scope. Ask a reviewer to identify which allocation, contract and operational boundaries were examined. A provider name alone does not tell a reader whether the root generator or sweep authority was included.

Retain a deployment checklist that compares intended roles with live configuration. This should be a recorded verification step with an accountable owner. A correct source-level access-control rule does not establish that the right account received the role during setup.

The distribution is ready for a release decision when its entitlement data, consumption rule, transfer behavior and lifecycle authority have explicit evidence. Any missing layer should remain visible in the acceptance record.

Original archive census: declared tests are not executed results

Method

Source and selection
Select every literal it() declaration from the complete archived MerkleDistributor.spec.ts.old at the recorded repository commit. Count blocks containing .reverted and names mentioning replay-like words separately.
Retrieved
Observation
One complete .old test file at the recorded Uniswap repository commit; other files excluded.

Results

Recorded observations on
ObservationCountInterpretation
Named test declarations26All literal it() declarations in the archived file
Blocks with revert assertions10Assertion syntax in the recorded block
Replay-like name signals1Narrow automatic name match
Manually classified replay cases5Repeated-claim or exactly-once assertions after inspecting the cases
Tests executed in this researchNot runNo current passing-suite claim

Manual review classified 5 cases as repeated-claim or exactly-once checks, compared with 1 narrow keyword signal. Names such as "cannot allow two claims" express replay behavior without using the words in the automatic pattern. The classification rule was the asserted repeated-entitlement behavior, not the mere presence of a revert.

This difference illustrates why keyword counts need interpretation. It does not establish exhaustive replay coverage. The file is archived, and the current implementation uses a different dependency and error style from parts of that historical test context. A release team must run its own compatible suite against the code it intends to deploy.

Limits

  • Archived declarations only; no test was executed and no pass rate was calculated.
  • Manual replay classification covers named cases, not all possible transaction sequences.
  • One reference implementation cannot establish the prevalence of defects across airdrop contracts.

The reproducible record is kept in the repository: seo/research/articles-42-51-2026-09-06/surveys.py and seo/research/articles-42-51-2026-09-06/survey-47.json. These file paths are not public downloads.

Frequently asked questions

Can a relayer submit a claim for someone else?

That depends on the design. If the recipient is bound in the proof or signature and the contract always pays that recipient, submission by another caller may be intentional. Fees or recipient changes need their own authorization.

What if a recipient loses access to the eligible wallet?

Use only the distribution's documented recovery or reallocation policy. An immutable entitlement cannot be reassigned by a website operator unless the contract explicitly supports an authorized path.

Should a root be published before the dataset is finalized?

Publishing a preliminary commitment can confuse users and integrations. Label any preliminary artifact clearly, then preserve the approved generator input and final deployed root as the authoritative release record.

Comments

8
  1. Priya S.

    The claim bitmap and the Merkle proof answer different questions. A proof can still verify after payment, so the repeated-claim test needs to inspect the consumption state too.

  2. Arjun P.

    What happens if the allocation generator assigns the same recipient more than one index? I would want the intended entitlement rule checked before treating either claim as a duplicate.

  3. Elena F.

    The unclaimed-supply example makes the post-distribution roles worth reviewing. Closing the normal claim window does not settle who can reach the remaining tokens.

  4. Iris A.

    A dataset hash beside the contract revision would make the release packet easier to reproduce. The reviewer should be able to rebuild the root from the approved allocation input.

  5. Felix I.

    For a signature-based claim, would a signature from an earlier distribution still be accepted by a later one? The message needs enough context to distinguish those authorizations.

  6. Ivan K.

    The census labels are clear about what was counted. An archived test declaration is evidence of a written scenario, while a passing result needs an actual execution record.

  7. Anya M.

    Sybil review belongs upstream of the verifier. Rejecting a bad proof cannot correct an allocation dataset that already includes several addresses controlled by the same actor.

  8. Daniel W.

    I would include a token-transfer failure in the claim tests. The expected consumption state after that failure should be explicit, so an unsuccessful payment does not silently use the entitlement.

Leave a comment

Share a question or observation about this article.

10 to 3,000 characters.