Appeal quorum failure loses both the activation deadline and the ruling
When an appeal panel fails quorum the case is rewound but its activation deadline is not restored. expireStuckAppeal, the only deadline-bounded exit, then always reverts, leaving the deposit and juror stakes locked for an unbounded period.
Description
expireStuckAppeal is the documented upper bound on
a stuck appeal, and hangs on one sentinel:
if (c.phase != Phase.Appealed) revert WrongPhase();if (c.appealActivationDeadline == 0) revert WrongPhase();
_activateAppealPanel deliberately zeroes that sentinel once a panel seats (#L1137), to stop a
stale deadline letting the winning party pre-empt retryStuckCase. The appeal quorum-failure
branch then rewinds the case to Appealed, resetting jurors, ruling, share, every deadline and
appealRound (#L770-L780) — but never restoring appealActivationDeadline. The case is now
exactly what expireStuckAppeal exists to rescue, and the next line rejects it. This is permanent:
the only non-zero writer is fileAppeal (#L905), which requires phase == Tallied.
The appellant's deposit and the round-zero panel's activeAssignments are then held with no
deadline and no owner function able to release either. Recovery depends on a fresh eligible panel being drawn, and the locked round-zero jurors are
removed from the eligible set in the meantime.
The same branch also sets c.ruling to Resolution.None and c.buyerShareBps to 0, discarding
the round-zero outcome although it remains preserved in appealRound1Ruling /
appealRound1BuyerShareBps. That is unreachable today precisely because the missing deadline
blocks expireStuckAppeal — but execute dispatches with no validity check, so None falls
through to resolveDisputeByArbitratorWithSplit(0), paying 100% to the seller. A fix that
restores the deadline without also restoring the ruling converts this lock into a mis-payment.
The two must be corrected together.
Impact
The only deadline-bounded exit from a stuck appeal is destroyed, leaving the deposit and three
juror stakes locked for an unbounded period. retryStuckCase survives, so this is loss of the
bounded exit rather than a permanent freeze — which is why it is Medium and not High. If the
deadline is restored without also restoring the ruling, a case whose appeal merely failed to reach
quorum settles the entire disputed principal to the seller, silently inverting the adjudicated
outcome.
Recommendation
One patch to the appeal quorum-fail branch, applied as a single change:
if (isAppeal) {delete c.jurors;c.phase = Phase.Appealed;- c.ruling = Resolution.None;- c.buyerShareBps = 0;+ c.ruling = appealRound1Ruling[caseId];+ c.buyerShareBps = appealRound1BuyerShareBps[caseId];...c.appealRound = 0;+ c.appealActivationDeadline =+ block.timestamp + ProtocolTimings.APPEAL_ACTIVATION_WINDOW;} else {
Size the re-armed window at no less than vrfRetryDelay() plus one VRF round trip, so a
good-faith retryStuckCase always wins the race. Stamp it from block.timestamp, not the
original fileAppeal time, so the deadline is never stale. Add a defensive
if (c.ruling == Resolution.None) revert in execute so no present or future path reaches the
0/100 fallthrough. Emit AppealRequeued(caseId, newActivationDeadline) — today a case moves from
bounded to unbounded with only QuorumFailed to show for it.
Deleting #L1137 is not the fix — it reintroduces the stale-deadline problem that line solves.
Resolution
Fixed. Appeal quorum failure now preserves the activation deadline, so the bounded exit survives.
Affected files
ArbitrationPool.sol#L770-L780, #L905, #L927-L931, #L943, #L972-L980, #L1137