Description
updateVerifier() at lines 263-269 lacks the zero-address guard that the constructor applies at line 68. If the owner passes address(0) (or any address with no code) by mistake, every subsequent claim() reverts inside verifier.verify(...) because a call to a code-less address returns empty bytes, which Solidity 0.8 cannot decode as bool.
if (_verifier == address(0)) revert InvalidVerifier();
function updateVerifier(IVerifier newVerifier) external {
require(paused, "THE_CONTRACT_MUST_BE_PAUSED");
require(msg.sender == owner, "ONLY_OWNER_CAN_UPDATE_VERIFIER");
verifier = newVerifier;
emit VerifierUpdated(address(newVerifier));
}
Risk
Likelihood: low. Requires an owner typo. Impact: temporary denial of claim() until the owner updates again; funds are not lost because emergencyWithdraw remains available.
Proof of Concept
pragma solidity ^0.8.27;
import "forge-std/Test.sol";
import {TreasureHunt} from "../src/TreasureHunt.sol";
import {IVerifier} from "../src/Verifier.sol";
contract PoC_L03 is Test {
TreasureHunt hunt;
address owner = address(0xDEAD);
function setUp() public {
address v = address(0xABCDEF);
vm.etch(v, hex"00");
vm.deal(owner, 200 ether);
vm.prank(owner);
hunt = new TreasureHunt{value: 100 ether}(v);
}
function test_zero_address_accepted() public {
vm.startPrank(owner);
hunt.pause();
hunt.updateVerifier(IVerifier(address(0)));
hunt.unpause();
vm.stopPrank();
vm.prank(address(0xBAD));
vm.expectRevert();
hunt.claim(hex"de", bytes32(uint256(1)), payable(address(0xA11CE)));
}
}
Recommended Mitigation
function updateVerifier(IVerifier newVerifier) external {
require(paused, "THE_CONTRACT_MUST_BE_PAUSED");
require(msg.sender == owner, "ONLY_OWNER_CAN_UPDATE_VERIFIER");
+ require(address(newVerifier) != address(0), "INVALID_VERIFIER");
+ require(address(newVerifier).code.length > 0, "VERIFIER_NOT_CONTRACT");
verifier = newVerifier;
emit VerifierUpdated(address(newVerifier));
}