Terminal status (Completed/Refunded) reflects only the last dispute episode on milestone deals, not the deal's actual outcome
On milestone deals the terminal Completed/Refunded status is derived from whichever milestone settled last, not from what each party actually received across the deal, so the final label can misrepresent the outcome.
Description
When a milestone-scoped dispute resolves and happens to be the last unsettled milestone, the
terminal status is derived from only that milestone's buyerShareBps:
if (_allMilestonesSettled()) {status = buyerShareBps == 10_000 ? Status.Refunded : Status.Completed;}
buyerShareBps is decided fresh on every dispute and can differ per milestone, and milestones
released without a dispute never reach this branch. The final label is therefore whichever outcome
settled last, not an aggregate of what each party actually received.
Example: M0 releases normally and pays the seller in full; M1 is disputed and the buyer wins
outright; the deal is permanently marked Refunded, though the seller was paid for M0. The
reverse holds for a seller-favourable last ruling. docs/PROTOCOL_SPEC.md §6.2.2 documents these
values as whole-deal claims — "Seller paid", "Buyer refunded" — which the milestone-scoped path
cannot honour.
No arithmetic is wrong and no on-chain gate branches on the distinction; every check tests
!= Active, != Pending or == Disputed. The cost falls on explorers, indexers and any
integration reading status as the deal's outcome, which is the meaning the specification
promises it carries.
Recommendation
Do not collapse a multi-episode milestone outcome into a binary label from the last episode's
split. Either track cumulative buyer and seller payouts and set Refunded only when the seller's
cumulative share is zero (Completed when the buyer's is), introducing a Settled state for
mixed outcomes — or, at minimum, correct §6.2.2 to state that these values reflect only the terms
of the milestone that closed the deal.
Resolution
Fixed. Terminal status now derives from cumulative payouts across the whole deal, with a new Settled state for mixed outcomes.
Affected files
contracts/Escrow.sol#L859-L892—_resolveDisputeFunds, milestone-scoped branch (line 887)contracts/Escrow.sol#L927-L965—resolveDisputeByMutualAgreement,resolveDisputeByArbitrator,resolveDisputeByArbitratorWithSplit