Skip to content

Checkpoint-Based State Recovery After Failed Payout #564

Description

@Kingsman-99

Description

If release_funds() fails mid-loop because one recipient's token transfer traps, the entire Soroban transaction rolls back — but the contract retains no record of which payouts were completed before the failure, so a re-attempt starts from index zero and risks double-payment to recipients earlier in the list. A checkpoint mechanism must record the index of the last successfully transferred payout so that a recovery entry point can resume from the correct position.

Technical Context

contracts/split/src/lib.rsrelease_funds() payout loop and a new resume_payout(invoice_id: u64, from_index: u32) #[contractimpl] entry point; contracts/split/src/storage.rs — add DataKey::PayoutCheckpoint(u64) storing a u32 index of the last confirmed transfer; contracts/split/src/types.rs — add InvoiceStatus::PayoutInProgress intermediate state; contracts/split/src/auth.rsresume_payout() requires auth from the invoice creator or the platform admin; contracts/split/src/events.rs — emit payout_checkpoint_saved(invoice_id, index, recipient) after each successful transfer().

Acceptance Criteria

  • After each successful token::Client::transfer() in release_funds(), DataKey::PayoutCheckpoint(invoice_id) is updated with the current 0-based recipient index before proceeding to the next
  • resume_payout(invoice_id, from_index) reads the stored checkpoint, returns Error::CheckpointMismatch if from_index does not match the stored value, and otherwise continues the transfer loop from that position
  • Attempting to pay a recipient at an index strictly less than the stored checkpoint value returns Error::AlreadyPaid without executing a transfer
  • Once all recipients are paid, DataKey::PayoutCheckpoint is deleted and invoice status transitions from PayoutInProgress to Settled
  • A test in contracts/split/src/tests/checkpoint_tests.rs simulates a trap on recipient index 2 of 5, then calls resume_payout(invoice_id, 2) and asserts that only recipients at indices 2, 3, and 4 receive funds in the recovery run
  • All CI checks (cargo build --target wasm32-unknown-unknown, cargo test, cargo clippy) pass and the branch has no merge conflicts

Metadata

Metadata

Assignees

No one assigned

    Labels

    complexity: highComplex feature requiring deep knowledge - 200 ptstestingTests — unit, integration, fuzz

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions