SNARKeling Treasure Hunt

First Flight #59
Beginner FriendlyGameFiFoundry
100 EXP
Submission Details
Impact: high
Likelihood: high

withdraw() is permanently broken due to contradicting conditions

Author Revealed upon completion

The two require statements in withdraw() can never both be true simultaneously in normal flow.

Description

  • The withdraw() function is intended to allow the owner to recover leftover ETH after the hunt ends. In normal flow, the contract is funded with exactly 100 ETH and each of the 10 treasures pays out 10 ETH. When all 10 treasures are claimed, the balance reaches exactly 0 ETH.

  • The two conditions contradict each other — claimsCount >= MAX_TREASURES only becomes true when all rewards are paid out and balance is 0, making the balance > 0 check always fail.

// @> Both conditions can never be true simultaneously in normal flow
require(claimsCount >= MAX_TREASURES, "HUNT_NOT_OVER"); // true only when all 10 claimed
require(balance > 0, "NO_FUNDS_TO_WITHDRAW"); // false when all 10 claimed

Risk

Likelihood:

  • Hunt ends with fewer than 10 treasures claimed, leaving ETH stuck forever

  • Owner funded contract with exactly 100 ETH as intended by deployment script

Impact:

  • Owner permanently loses unclaimed ETH with no recovery path

  • withdraw() is completely unusable in normal flow

Proof of Concept

function test_withdrawAlwaysRevertsInNormalFlow() public {
vm.store(address(hunt), bytes32(uint256(2)), bytes32(uint256(10)));
assertEq(hunt.getClaimsCount(), 10);
vm.deal(address(hunt), 0 ether);
assertEq(address(hunt).balance, 0);
vm.prank(owner);
vm.expectRevert("NO_FUNDS_TO_WITHDRAW");
hunt.withdraw();
}

Recommended Mitigation

Remove the claimsCount >= MAX_TREASURES requirement and replace with a time-based or owner-controlled hunt-ending mechanism:
function withdraw() external onlyOwner {
uint256 balance = address(this).balance;
require(balance > 0, "NO_FUNDS_TO_WITHDRAW");
(bool sent, ) = owner.call{value: balance}("");
require(sent, "ETH_TRANSFER_FAILED");
emit Withdrawn(balance, address(this).balance);
}

Support

FAQs

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

Give us feedback!