Early rate$2,400 of senior audit time for $500. Early members keep the rate as it climbs.$2,400 of senior audit time for $500See how →
F-2026-0002·incorrect-accounting

Appeal-round jurors are paid from feePool in addition to the appellant's deposit

Fixedescrowarbitrationdispute-resolution
TL;DR

Appeal rounds are meant to be funded by the appellant's deposit, but the tally also pays every appeal juror a full fee from the fee pool. Each appealed case draws roughly two extra case fees from the pool, undermining its solvency guard.

Severity
HIGH
Impact
HIGH
Likelihood
MEDIUM
Method
MManual review
CAT.
Complexity
MEDIUM
Exploitability
MEDIUM
02Section · Description

Description

The funding model treats appeals as self-funding: the appellant's deposit pays the appeal panel, and the arbitration pool funds round 1 only. The deposit minimum is set on that basis, at three times the case fee B.

The code does not implement the separation. tally() calls applySlashAndReward unconditionally, for round 0 and for appealRound == 1 alike (ArbitrationPool.sol#L812), and that function pays every juror whose vote matches the ruling a full flatFee out of feePool. Only afterwards does applyAppealOutcome reach payAppealJurorsFromDeposit and distribute the appellant's deposit on top.

Two sizing errors compound it. _minAppealDeposit returns _caseFee(...) * 3 while the appeal panel is PANEL_SIZE_APPEAL = 5, so the deposit is set for the wrong panel. And payAppealJurorsFromDeposit divides the deposit evenly among winners rather than paying B each, so with five winners each receives 0.6 x B from the deposit.

Vulnerable Scenario: The following steps illustrate the issue:

  1. A Community-tier dispute opens with a case fee B of 2,000 cNGN. Round 1 tallies 3-0, so feePool pays 3 x 2,000 = 6,000 cNGN.
  2. The losing party files an appeal, depositing the minimum 3 x B = 6,000 cNGN.
  3. A five-juror appeal panel is seated and tallies 5-0.
  4. tally() reaches applySlashAndReward, which pays 5 x 2,000 = 10,000 cNGN from feePool — a cost the funding model places on the appellant, not the pool.
  5. payAppealJurorsFromDeposit then splits the 6,000 cNGN deposit across the same five jurors as an additional payment.
  6. Total pool outlay for the case is 16,000 cNGN against a deposit of 6,000 cNGN.
03Section · Impact

Impact

Every appealed case draws roughly two case fees more from feePool than the funding model provides for, in the pool-solvency area the design is most sensitive to.

It compounds with the withdrawal guard at ArbitrationPool.sol#L442-L450, whose floor reserves activeCaseCount x PANEL_SIZE_ROUND1 x tierFeeExpertCommercial. An appealed case can draw eight jurors' worth of fees while the floor reserves three, so the owner may withdraw to a floor the contract reports as safe and leave live appeals unfunded. Underfunded tallies degrade to JurorFeePartiallyPaid, which does not block settlement but removes the compensation jurors were recruited on.

04Section · Recommendation

Recommendation

Fund the appeal round from the deposit alone. Skip the feePool payment inside applySlashAndReward when c.appealRound > 0, raise _minAppealDeposit to _caseFee(...) * PANEL_SIZE_APPEAL, cap each appeal winner's payment at B in payAppealJurorsFromDeposit, and return any surplus to the appellant rather than distributing it.

Skip only the fee transfer. The same loop increments casesResolved and credits each winner their share of the slash pool, and both must continue to apply on an appeal round.

Raising the minimum to 5 x B changes the published appeal-deposit schedule, which states 3 x B per tier. Update the schedule alongside the contract so the two agree.

Leave the withdrawFeePool floor at PANEL_SIZE_ROUND1. Once the appeal round no longer draws from feePool, the pool's maximum liability for a case is three seats, and reserving five would block withdrawals the guard does not exist to block.

05Section · Resolution

Resolution

Fixed. The appeal deposit must now fund all five seats at the case-tier fee. Across a full appeal round the fee pool is exactly unchanged.

06Section · Affected files

Affected files

  • contracts/ArbitrationPool.sol#L812-L856
  • contracts/ArbitrationPool.sol#L1216-L1218
  • contracts/lib/ArbitrationPoolTallyLib.sol#L169-L194
  • contracts/lib/ArbitrationPoolTallyLib.sol#L292-L329
Status
Fixed
F-2026-0002