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

Stale outcome during settlement can misroute pooled funds

Author Revealed upon completion

Root + Impact

Description

  • The intended behavior is that pools resolve only after the agreement term ends, and the expiry path should return principal plus bonus to the correct participants.

  • a stale or incorrect resolution can send the pool through the wrong payout branch and misroute funds.

// Root cause in the codebase with @> marks to highlight the relevant section
function settlePool(...) external {
// @> payout depends on mutable registry/moderator resolution
Outcome outcome = currentOutcome;
// @> expiry should pay principal + bonus, but stale state can choose wrong branch
if (outcome == Outcome.EXPIRED) {
_payoutExpired();
} else {
_payoutCorrupted();
}
// @> missing invariant: outcome freshness / finality / recipient validation
}

Risk

Likelihood:

  • Settlement executes on the normal lifecycle path whenever a pool expires or is resolved, so this bug is reached routinely, not rarely.

  • The design explicitly relies on moderator or registry state for final resolution, creating a real chance that stale state is consumed at settlement time.

Impact:

  • Pooled funds can be sent to the wrong branch, causing incorrect payouts or locked value.

  • The bug can affect the entire pool balance, including staked principal and sponsor bonus.

Proof of Concept

// 1. Pool reaches expiry.
// 2. Resolution state remains stale or is updated incorrectly.
// 3. settlePool() uses the wrong outcome.
// 4. Funds are distributed through the wrong payout branch.

Recommended Mitigation

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

Support

FAQs

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

Give us feedback!