Normal behavior: initialize() sets up all core pool configuration (agreement, stakeToken, safeHarborRegistry, outcomeModerator, expiry, minStake, recoveryAddress, outcome = UNRESOLVED), populates the initial scope via _replaceScope, and transfers ownership to the pool creator.
The issue: by the time _replaceScope(accounts) runs inside initialize(), all core config is already committed, but _transferOwnership(owner_) has not run yet and _scopeAccounts is only partially built when the loop's external call (agreement.isContractInScope) fires — and that call has no reentrancy guard, same as Finding 1's root cause.
This is the same class of bug as the setPoolScope reentrancy finding — an unguarded, attacker-reachable external call mid-loop — but with a different (currently benign) blast radius, since ownership hasn't transferred yet at this point.
Likelihood:
I traced the reachable surface at this exact point in execution:
stake(), contributeBonus(), withdraw(), pokeRiskWindow() are permissionless. None of them gate on scope today, so a reentrant call against this partially-initialized-but-otherwise-valid pool only lets the caller deposit their own tokens early or flip expiryLocked early — no path to loss or a bypassed check was found.
setPoolScope() / pause() / unpause() are onlyOwner, and at this point owner() is still address(0) (a fresh clone's storage has never had _transferOwnership called), so these all revert on reentry.
Given the current function set, there is no reachable path that turns this into an exploitable bug — likelihood of actual harm today is Low/theoretical.
Impact:
No fund-loss or bypassed-check path exists today, because the only owner-gated functions revert (owner is still address(0)) and the permissionless functions have no scope-gated logic to bypass.
The real risk is forward-looking: today's "no impact" conclusion depends entirely on the permissionless functions never gaining scope-gated logic. Any future addition of such logic would silently reintroduce a real, exploitable bug here, and there is currently no test that would catch it.
Not applicable as a fund-theft or state-corruption PoC against the current function set — the reentrancy surface exists structurally (unguarded external call mid-loop, before ownership transfer), but every currently-reachable path through it was traced and found to have no exploitable consequence: permissionless functions have no scope-gated logic to bypass, and owner-gated functions revert since owner() is still address(0) at this point in initialize().
Same fix class as the setPoolScope reentrancy finding: validate the full scope list into memory before any storage mutation (resolve all isContractInScope calls up front, then apply storage writes and emit the event as one atomic block), or add reentrancy protection around initialize() — this is compatible with OpenZeppelin's initializer semantics, since ReentrancyGuard's guard is independent of Initializable's. Closing this now, even though it's currently benign, prevents it from silently becoming a real bug if scope-gated logic is ever added to any permissionless function.
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.