Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions evm/script/deploy/OptimismDeploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,12 @@ contract OptimismDeploy is BaseScript {
if (deps.airdrop.reserveAddress() != params.treasury) {
revert("Airdrop reserveAddress is not Treasury");
}
// 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.

revert("Airdrop reserve has not approved the Airdrop (would be unclaimable)");
}
if (!deps.airdrop.hasRole(deps.airdrop.DEFAULT_ADMIN_ROLE(), params.admin)) {
revert("Airdrop default admin is not Admin MultiSig");
}
Expand Down
Loading