Skip to content

feat: supplier payout batching, per-address rate limiting, dispute evidence versioning, dispute withdrawal - #327

Merged
shakurJJ merged 2 commits into
shakurJJ:mainfrom
Good-Coded:feat/284-285-286-287-payout-batching-rate-limit-evidence-dispute-withdrawal
Jul 29, 2026
Merged

feat: supplier payout batching, per-address rate limiting, dispute evidence versioning, dispute withdrawal#327
shakurJJ merged 2 commits into
shakurJJ:mainfrom
Good-Coded:feat/284-285-286-287-payout-batching-rate-limit-evidence-dispute-withdrawal

Conversation

@Good-Coded

Copy link
Copy Markdown
Contributor

Summary

Implements four features from the backlog in a single, cohesive change. All 105 tests pass (including fixes for two pre-existing broken tests in the original repo).


Changes

#284 — Supplier payout batching

Adds opt-in batched payout mode per supplier so milestone payments accumulate as a claimable balance instead of triggering a SAC transfer on every confirmation.

New public functions:

  • set_payout_mode(supplier, batched: bool) — toggles Batched vs Immediate per supplier; requires supplier auth
  • claim_payout(supplier, token) — transfers the full accumulated balance in one call and resets it to zero; panics if balance is zero
  • get_pending_payout(supplier) -> i128 — read-only getter

Implementation notes:

  • Default (no opt-in) is PayoutMode::Immediate — zero behaviour change for existing suppliers
  • Batched payments are accumulated in DataKey::PendingPayout(supplier) persistent storage
  • confirm_milestone, release_held_payment, and batch_confirm_milestones all respect the mode
  • check_address_outflow (required by feat: per-address outflow rate limiting #285) is called before the credit either path

#285 — Per-address outflow rate limiting

Adds an admin-configurable sliding-window outflow cap scoped per supplier address, independent of the existing global circuit breaker.

New public functions:

  • set_address_outflow_limit(admin, address, limit, window_ledgers) — admin-only; limit = 0 disables the per-address cap
  • get_address_outflow_limit(address) -> (i128, u32, u32, i128) — returns (limit, window_ledgers, window_start, window_outflow)

New internal helper:

  • check_address_outflow(env, address, payment) — mirrors the circuit breaker pattern using persistent storage; called in confirm_milestone, release_held_payment, and batch_confirm_milestones

Behaviour:

  • Either the global breaker or the per-address cap tripping reverts with no partial transfer
  • Window resets automatically when the configured window_ledgers have elapsed

#286 — Dispute evidence versioning

Allows supplier, logistics, or buyer to append versioned evidence entries to a disputed milestone while it is open, giving the arbiter richer context for off-chain review.

New public functions:

  • submit_dispute_evidence(caller, shipment_id, milestone_index, evidence_hash, evidence_type) — appends one DisputeEvidence entry; callable only while the milestone is Disputed; emits dispute_evidence_submitted
  • get_dispute_evidence(shipment_id, milestone_index) -> Vec<DisputeEvidence> — returns entries in submission order

New type: DisputeEvidence { submitter, evidence_hash, evidence_type, submitted_ledger }

Behaviour:

  • Entries are append-only; existing entries are never overwritten
  • Arbiter resolution logic (resolve_dispute) is entirely unchanged
  • Evidence remains queryable after resolution

#287 — Buyer-initiated dispute withdrawal

Allows a buyer to retract their own open dispute, unfreezing the milestone back to ProofSubmitted without requiring arbiter intervention.

New public function:

  • withdraw_dispute(buyer, shipment_id, milestone_index) — buyer-only, only while Disputed; reverts milestone to ProofSubmitted, decrements open_dispute_count, removes entry from the global active disputes list, emits dispute_withdrawn

Behaviour:

  • After withdrawal the supplier's original proof still stands; buyer can confirm_milestone immediately or raise a new dispute subject to the normal cooldown rules
  • Arbiter cannot call resolve_dispute on a withdrawn dispute (milestone is no longer Disputed)
  • Released amount from previously confirmed milestones is preserved

Pre-existing test fixes

Two tests in the original repo were broken due to the Soroban test environment starting at ledger sequence 0:

  • test_admin_action_log_capped_and_ordered — advanced the ledger by 1 each iteration so audit entries have strictly increasing sequence numbers
  • test_shipment_created_event_includes_all_role_addresses — advanced ledger to 1 before creating the shipment so created_at > 0

Test coverage

Feature Tests added
#284 Payout batching 7
#285 Per-address rate limiting 8
#286 Dispute evidence versioning 7
#287 Dispute withdrawal 8
Pre-existing fixes 2

Total: 105 tests, 0 failures.


Closes #284
Closes #285
Closes #286
Closes #287

…idence versioning, dispute withdrawal (shakurJJ#284 shakurJJ#285 shakurJJ#286 shakurJJ#287)

- shakurJJ#284: add set_payout_mode(), claim_payout(), get_pending_payout() and
  check_address_outflow() helper; batched mode accumulates milestone
  payments per-supplier until claim_payout is called in one transfer

- shakurJJ#285: add set_address_outflow_limit(), get_address_outflow_limit() and
  check_address_outflow() sliding-window helper; every payout checks both
  the global circuit breaker and the per-address cap independently

- shakurJJ#286: add submit_dispute_evidence(), get_dispute_evidence() and
  DisputeEvidence struct; evidence entries are append-only per disputed
  milestone; emits dispute_evidence_submitted event per entry

- shakurJJ#287: add withdraw_dispute(); buyer-only, reverts Disputed milestone
  back to ProofSubmitted, decrements open_dispute_count, removes from
  active disputes list; emits dispute_withdrawn event

Also fix two pre-existing broken tests:
  - test_admin_action_log_capped_and_ordered: advance ledger each
    iteration so entries have strictly increasing sequence numbers
  - test_shipment_created_event_includes_all_role_addresses: advance
    ledger past 0 before creating shipment so created_at > 0

All 105 tests pass.
@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@Good-Coded Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@shakurJJ
shakurJJ merged commit 659f19a into shakurJJ:main Jul 29, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants