SNARKeling Treasure Hunt

First Flight #59
Beginner FriendlyGameFiFoundry
100 EXP
View results
Submission Details
Severity: low
Valid

updateVerifier() accepts address(0) — verifier can be permanently bricked

Root + Impact

updateVerifier() allows the owner to swap in a new verifier contract while the hunt is paused. Every call to claim() delegates proof validation to verifier.verify(); if verifier holds the zero address, that call will revert unconditionally, making it impossible for anyone to claim a reward.

function updateVerifier(IVerifier newVerifier) external {
require(paused, "THE_CONTRACT_MUST_BE_PAUSED");
require(msg.sender == owner, "ONLY_OWNER_CAN_UPDATE_VERIFIER");
// @> no check that address(newVerifier) != address(0)
verifier = newVerifier;
emit VerifierUpdated(address(newVerifier));
}

Risk

Likelihood: Low

  • Requires the owner to pass address(0) — most likely an accidental copy-paste or scripting error during a verifier upgrade, rather than a deliberate act.

  • A compromised or phished owner key could use this as a targeted denial-of-service.

Impact: High

  • All future claim() calls revert with InvalidProof, permanently locking every unclaimed reward inside the contract.

  • Recovery requires calling updateVerifier() again with a valid address, which is only possible while the contract remains paused — the owner must notice the mistake before unpausing.

  • If the owner unpauses without noticing, claim() is broken for all users with no on-chain recovery path short of another pause-and-update cycle.

Proof of Concept

// Owner intends to deploy a new verifier but passes wrong argument
treasureHunt.pause();
treasureHunt.updateVerifier(IVerifier(address(0))); // accepted silently
treasureHunt.unpause();
// Now any claim attempt fails
treasureHunt.claim(validProof, validHash, recipient);
// Reverts: InvalidProof — verifier.verify() called on address(0)

Recommended Mitigation

-
+ function updateVerifier(IVerifier newVerifier) external {
require(paused, "THE_CONTRACT_MUST_BE_PAUSED");
require(msg.sender == owner, "ONLY_OWNER_CAN_UPDATE_VERIFIER");
+ if (address(newVerifier) == address(0)) revert InvalidVerifier();
verifier = newVerifier;
emit VerifierUpdated(address(newVerifier));
}
Updates

Lead Judging Commences

s3mvl4d Lead Judge 18 days ago
Submission Judgement Published
Validated
Assigned finding tags:

no zero-address check in updateVerifier()

The issue is that `updateVerifier()` allows the owner to replace the verifier with an arbitrary address, including `address(0)`, even though the constructor explicitly treats a zero verifier as invalid and reverts with `InvalidVerifier()` during initial deployment. In other words, the contract establishes at deployment time that a null verifier address is not an acceptable configuration, but then fails to preserve that same invariant when the verifier is later updated through the admin recovery path.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!