SNARKeling Treasure Hunt

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

[M-01] Deployment script lacks validation for sufficient initial funding leading to insolvency

Author Revealed upon completion

Root + Impact

Description

  • The TreasureHunt protocol is designed with fixed parameters: REWARD = 10 ETH and MAX_TREASURES = 10. To function correctly and honor all 10
    potential claims, the contract must be funded with at least 100 ETH upon deployment

  • The issue lies in the Deploy.s.sol script, which retrieves the INITIAL_FUNDING amount from an environment variable but fails to validate if
    this amount is sufficient to cover the protocol's total liabilities. If the operator misconfigures the .env file or passes a lower value during
    deployment, the contract will be successfully deployed but will be insolvent before the hunt is completed

// contracts/scripts/Deploy.s.sol
function run() external {
// ...
// @> Root Cause: initialFunding is pulled from environment without a floor-price check
uint256 initialFunding = vm.envOr("INITIAL_FUNDING", DEFAULT_INITIAL_FUNDING);
vm.startBroadcast(deployerKey);
verifier = new HonkVerifier();
// @> The contract is deployed with potentially insufficient funds
hunt = new TreasureHunt{value: initialFunding}(address(verifier));
vm.stopBroadcast();
// @> This only verifies the balance matches the input, not the protocol requirement
require(hunt.getContractBalance() == initialFunding, "UNEXPECTED_BALANCE");
// ...
}

Risk

Likelihood:

  • This is a realistic scenario involving operator error during deployment to a live network (e.g., Mainnet or Sepolia).

  • Foundry scripts often rely on environment variables that can be easily mistyped or misconfigured in high-pressure deployment environments

Impact:

  • Protocol Insolvency: If the contract is underfunded (e.g., only 50 ETH), the last 5 participants who find physical treasures will be unable to
    claim their rewards. Their claim() transactions will revert with NotEnoughFunds(), leading to a breakdown in trust and financial loss for the
    finders

Proof of Concept

If the script is executed with the following command:
INITIAL_FUNDING=10000000000000000000 forge script Deploy.s.sol ... (which is only 10 ETH)

  1. The script will execute successfully without any warnings.

  2. The TreasureHunt contract goes live with a balance of only 10 ETH.

  3. The first successful claimant receives the 10 ETH.

  4. All subsequent 9 claimants (2nd to 10th) will have their valid proofs rejected by the contract due to insufficient balance, even though they
    found the physical treasures

function test_InsolvencyFromDeploymentScript() public {
// 1. Simulate an incorrect INITIAL_FUNDING value from the deployment script (e.g., 10 ETH)
uint256 insufficientFunding = 10 ether;
// 2. The Deploy.s.sol script would proceed to deploy the contract with this value
// There is no 'require(initialFunding >= 100 ether)' validation in the script
TreasureHunt insolventHunt = new TreasureHunt{value: insufficientFunding}(address(mockVerifier));
console.log("Contract deployed with balance:", address(insolventHunt).balance / 1e18, "ETH");
// 3. The first winner performs a valid claim
insolventHunt.claim("", keccak256("TREASURE_1"), payable(address(0x111)));
console.log("Winner 1 claimed 10 ETH. Remaining contract balance:", address(insolventHunt).balance);
// 4. REAL VULNERABILITY PROOF: The second winner attempts to claim
// Even though they have a valid ZK proof, the transaction REVERTS because funds are exhausted
vm.prank(attacker);
vm.expectRevert(abi.encodeWithSelector(TreasureHunt.NotEnoughFunds.selector));
insolventHunt.claim("", keccak256("TREASURE_2"), payable(address(0x222)));
console.log("Winner 2 failed to claim due to protocol insolvency caused by the deployment script!");
}

Recommended Mitigation

It is recommended to add a strict validation check within the run() function of the deployment script. The script should verify that the
initialFunding is at least equal to the total reward liability of the hunt (MAX_TREASURES * REWARD).

function run() external {
uint256 initialFunding = vm.envOr("INITIAL_FUNDING", DEFAULT_INITIAL_FUNDING);
+
+ // Ensure the contract is fully funded for all 10 rewards (10 * 10 ETH)
+ uint256 requiredFunding = 100 ether;
+ require(initialFunding >= requiredFunding, "ERROR: Deployment funding must be at least 100 ETH");
vm.startBroadcast(deployerKey);

Support

FAQs

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

Give us feedback!