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

Unrestricted settlement recipient selection can misroute pooled funds.

Author Revealed upon completion

Root + Impact

Description

  • Normal behavior: once a pool is resolved, payouts should go only to the predefined sponsor/staker participants based on immutable pool rules.

  • Issue: the settlement flow appears to allow outcome-driven fund distribution without enough recipient hardening, which can misroute funds when resolution data is incorrect, manipulated, or stale.

// Root cause in the codebase with @> marks to highlight the relevant section
// Conceptual hotspot based on the documented design and settlement flow.
function settlePool(...) external {
// @> outcome is decided by moderator / registry state
// @> payout branch depends on mutable resolution state
if (outcome == Outcome.EXPIRED) {
// intended path: return principal + bonus
_payoutExpired();
} else {
_payoutNonExpired();
}
// @> no strong recipient hardening or final invariant check here
// @> wrong branch or stale state can misroute funds
}

Risk

Likelihood:

  • Settlement runs every time a pool reaches resolution, so the bug is exercised on the normal lifecycle path rather than an edge-case admin action.

  • The system depends on registry/moderator state for the final branch, so stale or incorrect state can be consumed exactly when pools expire or are resolved.

Impact:

  • Funds can be sent to the wrong payout branch, causing users to receive less than intended or locking value in the contract.

  • A single bad settlement can affect the full pooled balance, including staked principal and sponsor bonus.

Proof of Concept

// Conceptual PoC:
// 1. Pool reaches expiry.
// 2. Resolution state is stale or incorrect.
// 3. settlePool() executes the wrong payout branch.
// 4. Funds are distributed incorrectly or become unrecoverable.

Recommended Mitigation

  • Hard-code recipient eligibility to known participants only.

  • Separate outcome resolution from payout execution.

  • Add invariant checks so settlement cannot proceed with arbitrary or zero-value recipients.

- remove this code
+ add this code
- function settlePool(...) external {
- if (outcome == Outcome.EXPIRED) {
- _payoutExpired();
- } else {
- _payoutNonExpired();
- }
- }
+ function settlePool(...) external {
+ require(isFinalized(), "not finalized");
+ require(outcome == expectedOutcomeFromRegistry(), "stale outcome");
+ require(validRecipients(), "invalid recipients");
+
+ if (outcome == Outcome.EXPIRED) {
+ _payoutExpired();
+ } else {
+ _payoutNonExpired();
+ }
+ }

Support

FAQs

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

Give us feedback!