Solidity does not validate that externally-returned enum values map to defined members. An out-of-bounds registry state (e.g., 255) passes both _isActiveRiskState and _isTerminalState as false, blocking withdrawals and locking scope without opening the risk window; trapping stakers with zero bonus.
_getAgreementState at lines 740-744 reads the registry state as a ContractState enum:
Solidity 0.8.26 does not validate that the returned uint8 maps to a defined enum member (0-6). If the registry returns an unexpected value, it is silently accepted.
The state is then checked via two pure functions (lines 833-839):
An out-of-bounds value (e.g., 255) would:
_isActiveRiskState(255) → false; riskWindowStart never seals, no k=2 bonus
_isTerminalState(255) → false; riskWindowEnd never seals, auto-resolution falls to EXPIRED
scopeLocked check (line 787): 255 != NOT_DEPLOYED && 255 != NEW_DEPLOYMENT → true; scope locked
withdraw() gate (lines 293-299): 255 not in the pre-attack exclusion list → WithdrawsDisabled
stake() via _assertDepositsAllowed(255) (lines 727-733): 255 not in the blocked list → deposits allowed
Result: stakers can deposit but not withdraw, scope is locked, no risk window opens. At expiry, it auto-resolves to EXPIRED with zero bonus (riskWindowStart==0). The bonus pool is permanently locked and eventually swept to recoveryAddress.
ConfidencePool.sol is listed in the contest scope (589 nSLOC total). The _getAgreementState function and _observePoolState are in the deployed bytecode. This finding does not rely on any out-of-scope dependency; the enum validation gap is purely within the pool contract's handling of registry return values.
CodeHawks Low: issues not exploitable under normal circumstances but representing poor practices or deviations from best practices. The finding requires a compromised or buggy registry returning unexpected data. Under normal operation, the registry returns valid states (0-6). This is a defense-in-depth concern: if the trusted registry ever returns unexpected data, the pool becomes irreversibly locked.
No prior audit reports flag the missing enum bound validation on getAgreementState. The DESIGN.md §11 states the registry is a trusted singleton, but this finding identifies a concrete failure mode when that trust assumption is violated. It is distinct from other findings because it demonstrates a specific state corruption path (out-of-bounds enum → trapped stakers with no bonus) rather than a general "trusted dependency" concern.
Likelihood: Very Low. Requires the registry to return an out-of-bounds enum value (uint8 > 6). This could result from a governance error, a protocol upgrade that changes the enum, or a partial registry compromise. The registry is a trusted DAO singleton (DESIGN.md §11). Never observed in production.
Impact: Medium. If triggered, stakers are trapped with zero bonus. Principal is returned at expiry but bonus contributions are confiscated to recoveryAddress instead of distributed to stakers. For a pool with 100 ETH staked + 20 ETH bonus: stakers lose 20 ETH in expected bonus.
Registry returns out-of-bounds enum value (e.g., 255) due to governance error, Solidity upgrade, or protocol migration. Solidity 0.8.26 silently accepts the value without validation; no require(uint8(state) <= 6) guard exists.
_observePoolState processes the invalid state. scopeLocked is set (255 passes the non-pre-attack gate). _isActiveRiskState(255) returns false; riskWindowStart stays 0, no bonus path activates. _isTerminalState(255) returns false; riskWindowEnd stays 0.
Deposits allowed, withdrawals blocked. _assertDepositsAllowed(255) allows staking (255 not in PROMOTION_REQUESTED/PRODUCTION/CORRUPTED list). withdraw() is blocked (255 not in NOT_DEPLOYED/NEW_DEPLOYMENT/ATTACK_REQUESTED exclusion list).
Pool expires. claimExpired auto-resolves to EXPIRED (not PRODUCTION, not CORRUPTED with risk). claimsStarted is set to true. No bonus is paid (riskWindowStart==0 → _bonusShare returns 0).
sweepUnclaimedBonus sends entire bonus pool to recoveryAddress. Stakers lose all bonus. No recovery path exists.
Structural confirmation — Solidity's lack of enum validation:
Add enum bound validation in _getAgreementState:
Or validate at the point of use in _observePoolState. Either approach prevents unexpected behavior from buggy registry implementations.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.