Accounting field names, event semantics and the specification's own invariant diverge
releasedAmount counts refunded amounts as released, a full buyer refund emits no milestone event, and the specification's conservation invariant omits abandoned amounts, so indexers and invariant tests built from the spec reconstruct the wrong state.
Description
Three accounting and event semantics diverge from what a consumer of the contract's state would reasonably infer, and from the specification.
releasedAmountis gross, not seller-paid._resolveDisputeFundsadds the full milestone amount toreleasedAmount(Escrow.sol#L713) and then splits it, so a 100% buyer refund still increments a field named "released".getRemainingAmountstays correct; any consumer readingreleasedAmountas seller revenue does not.- A full refund emits no milestone event.
emit MilestoneReleased(idx, toSeller)sits insideif (toSeller > 0)(#L721-L724), so a 10,000-bps buyer ruling settles the milestone with noMilestoneReleasedat all and a naive indexer never sees the state change. - The specification's conservation invariant omits abandonment.
docs/PROTOCOL_SPEC.md§7.1 statestoken.balanceOf(escrow) >= totalAmount - releasedAmount, but the escrowed quantity istotalAmount - releasedAmount - abandonedAmount. After anyabandonUncommencedMilestonesthe stated invariant is violated by correct code, so an invariant test written from the specification would either fail or be written to the wrong property.
Recommendation
Emit a milestone settlement event unconditionally, publish canonical liability and lifecycle
semantics for consumers, and correct §7.1 to include abandonedAmount. Test indexer
reconstruction across settlement, abandonment, milestones and repeated disputes.
Resolution
Fixed. sellerPaidAmount and buyerPaidAmount were added and the held-principal invariant is documented alongside them.