FoundrySolidityLayer 2
7.25 ETH
Submission Details
Impact: low
Likelihood: low

Dead Guard — `if (!claimsStarted) claimsStarted = true` at Line 600

Author Revealed upon completion

Root + Impact

Description

In claimExpired(), line 600 checks if (!claimsStarted) claimsStarted = true. However, by the time execution reaches this line, claimsStarted has already been unconditionally set to true by the resolution block (line 549 for auto-CORRUPTED early return, line 575 for SURVIVED/EXPIRED fallthrough). The guard at line 600 can never fire in the current code path — it is dead code.

// ConfidencePool.sol — claimExpired() lines 549, 575, 600
if (state == CORRUPTED && ...) {
@> claimsStarted = true; // line 549 — auto-CORRUPTED path sets it
return;
}
// ... SURVIVED/EXPIRED paths ...
@> claimsStarted = true; // line 575 — always executed before line 600
}
// claim payout block...
@> if (!claimsStarted) claimsStarted = true; // line 600 — never true

Risk

Likelihood: N/A — the condition is structurally always false; the guard never executes.

Impact: Low — no runtime effect. The dead code consumes minimal bytecode (a conditional jump that always falls through). It exists as vestigial defense-in-depth but could confuse future maintainers.

Proof of Concept

File: L22-DeadGuard-ClaimsStarted.poc.t.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {IAttackRegistry} from "@battlechain/interface/IAttackRegistry.sol";
import {PoolStates} from "src/libraries/PoolStates.sol";
import {BaseConfidencePoolTest} from "test/helpers/BaseConfidencePoolTest.sol";
/// @notice PoC for L-22: Dead guard `if (!claimsStarted) claimsStarted = true` at line 600
contract L22_DeadGuard_ClaimsStarted_POC is BaseConfidencePoolTest {
function testPOC_L22_claimsStartedAlwaysTrueAtLine600() external {
_stake(alice, 100 * ONE);
vm.warp(pool.expiry());
// claimExpired resolution block (lines 518-576) unconditionally sets claimsStarted=true
// at line 549 (auto-CORRUPTED return) or line 575 (SURVIVED/EXPIRED fallthrough).
// By line 600, claimsStarted is always true — the guard is dead code.
vm.prank(alice);
pool.claimExpired();
// Verify claimsStarted was set by the resolution block, not the dead guard
assertTrue(pool.claimsStarted(), "claimsStarted set by resolution block");
// The guard at line 600 `if (!claimsStarted) claimsStarted = true` never fires
// because claimsStarted was already set to true at line 575
}
function testPOC_L22_deadGuard_line600_neverExecutes() external {
// Non-staker calls claimExpired to auto-resolve
vm.warp(pool.expiry());
vm.prank(bob); // bob has no stake
pool.claimExpired();
// claimsStarted was set at line 575 (SURVIVED/EXPIRED fallthrough),
// not at line 600
assertTrue(pool.claimsStarted());
}
}

Run: forge test --match-path 'L22-DeadGuard-ClaimsStarted.poc.t.sol' -vv

Recommended Mitigation

Remove the dead guard, or add a comment documenting it as defense-in-depth against future code changes:

- if (!claimsStarted) claimsStarted = true;
+ // claimsStarted is guaranteed true here (set at line 549 or 575 above).
+ // Guard retained as defense-in-depth against future refactoring.
+ // if (!claimsStarted) claimsStarted = true;

Support

FAQs

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

Give us feedback!