Skip to content

fix(deploy): verify airdrop reserve allowance so it can't ship unclaimable#49

Closed
rplusq wants to merge 1 commit into
mainfrom
fix/airdrop-allowance-verify
Closed

fix(deploy): verify airdrop reserve allowance so it can't ship unclaimable#49
rplusq wants to merge 1 commit into
mainfrom
fix/airdrop-allowance-verify

Conversation

@rplusq

@rplusq rplusq commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Airdrop.claimTokens pulls tokens via transferFrom(reserveAddress, ...), but verifyDeployments never checked the reserve's allowance to the Airdrop. A deployment with an unfunded/unapproved treasury passed verification yet was unclaimable. Assert allowance(treasury, airdrop) > 0.

🤖 Generated with Claude Code

…mable

Airdrop.claimTokens pulls tokens via transferFrom(reserveAddress, ...), but verifyDeployments
never checked the reserve's allowance to the Airdrop. A deployment with an unfunded/unapproved
treasury passed verification yet was unclaimable. Assert allowance(treasury, airdrop) > 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jul 13, 2026

Copy link
Copy Markdown

Claude finished @rplusq's task in 1m 37s —— View job


PR Review

  • Gather context
  • Read changed file
  • Analyze changes
  • Post review
Found 1 issue(s)

Issue 1: allowance == 0 guard is too weak — non-zero but insufficient approval still ships an unclaimable airdrop

ID: optimismdeploy-allowance-weak-guard-a3f1
File: evm/script/deploy/OptimismDeploy.s.sol:444
Severity: MEDIUM
Category: code_quality

Context:

  • Pattern: The check deps.l2wct.allowance(params.treasury, address(deps.airdrop)) == 0 only catches a completely missing approval
  • Risk: An approval of 1 wei (or any amount less than the airdrop total) passes verification, yet claimTokens will revert on safeTransferFrom for virtually every real claim
  • Impact: The stated goal is "can't ship unclaimable" — but this check permits shipping an airdrop where allowance = 1 and the merkle tree distributes millions of tokens; the airdrop is still unclaimable in practice
  • Trigger: Treasury approved a non-zero but insufficient amount (easy to do by mistake, e.g. approving in the wrong decimals)

Recommendation: If the standard deployment pattern uses a max approval for the treasury (common for reserve-based airdrops), assert that explicitly:

if (deps.l2wct.allowance(params.treasury, address(deps.airdrop)) != type(uint256).max) {
    revert("Airdrop reserve allowance must be type(uint256).max");
}

If exact amounts are used instead, cross-check against the treasury balance as a proxy:

uint256 allowance = deps.l2wct.allowance(params.treasury, address(deps.airdrop));
uint256 reserve = deps.l2wct.balanceOf(params.treasury);
if (allowance < reserve) {
    revert("Airdrop reserve allowance is less than treasury balance");
}

Either bound is tighter than > 0 and actually guarantees the airdrop is claimable up to the treasury's current holding.

// claimTokens pulls via transferFrom(reserveAddress, ...), so the reserve MUST have approved the
// Airdrop to spend L2WCT — without it the airdrop is deployed but unclaimable. Verify the allowance
// so a missing treasury approval fails verification instead of shipping a dead airdrop.
if (deps.l2wct.allowance(params.treasury, address(deps.airdrop)) == 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Auto Review Issue: allowance == 0 guard is too weak — non-zero but insufficient approval still ships an unclaimable airdrop

Severity: MEDIUM
Category: code_quality
Tool: Claude Auto Review

Context:

  • Pattern: The check deps.l2wct.allowance(params.treasury, address(deps.airdrop)) == 0 only catches a completely missing approval
  • Risk: An approval of 1 wei (or any amount less than the airdrop total) passes verification, yet claimTokens will revert on safeTransferFrom for virtually every real claim
  • Impact: The stated goal is "can't ship unclaimable" — but this check permits shipping an airdrop where allowance = 1 and the merkle tree distributes millions of tokens; the airdrop is still unclaimable in practice
  • Trigger: Treasury approved a non-zero but insufficient amount (easy to do by mistake, e.g. approving in the wrong decimals)

Recommendation: If the standard deployment pattern uses a max approval for the treasury (common for reserve-based airdrops), assert that explicitly:

if (deps.l2wct.allowance(params.treasury, address(deps.airdrop)) != type(uint256).max) {
    revert("Airdrop reserve allowance must be type(uint256).max");
}

If exact amounts are used instead, cross-check against the treasury balance as a proxy:

uint256 allowance = deps.l2wct.allowance(params.treasury, address(deps.airdrop));
uint256 reserve = deps.l2wct.balanceOf(params.treasury);
if (allowance < reserve) {
    revert("Airdrop reserve allowance is less than treasury balance");
}

Either bound is tighter than > 0 and actually guarantees the airdrop is claimable up to the treasury's current holding.

@rplusq

rplusq commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator Author

Closing — verifying the airdrop reserve allowance is an operational (deploy-runbook / Safe-checklist) concern rather than something the deploy script should enforce. Not carrying this in the script layer.

@rplusq rplusq closed this Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant