Skip to content

feat: add invoice event replay and contract-wide emergency withdrawal#257

Open
Idaonoli wants to merge 1 commit into
Stellar-split:mainfrom
Idaonoli:feat/issue-232-233-replay-and-emergency-withdraw
Open

feat: add invoice event replay and contract-wide emergency withdrawal#257
Idaonoli wants to merge 1 commit into
Stellar-split:mainfrom
Idaonoli:feat/issue-232-233-replay-and-emergency-withdraw

Conversation

@Idaonoli

@Idaonoli Idaonoli commented Jun 26, 2026

Copy link
Copy Markdown

Summary

Implements two new contract features and resolves pre-existing compilation errors that had accumulated across recent PRs.

closes #232 — Invoice payment event replay for indexer recovery

  • replay_invoice_events(invoice_id) is a pure read+emit function callable by anyone. It re-emits the full historical event sequence for an invoice so an offline indexer can resync from a single call:
    • One invoice_created (replay) event
    • One payment_received (replay) event per entry in invoice.payments, in original order
    • The terminal status event (invoice_released or invoice_refunded, replay) when the invoice is no longer Pending
  • All four replay event emitters carry a fourth replay topic so indexers can distinguish them from live events and skip double-counting.
  • No state mutation — safe for any caller at any time.

closes #233 — Contract-wide emergency withdrawal

  • EmergencyWithdraw(Address, Address) added to TimelockAction.
  • request_emergency_withdraw(admin, token, destination) — requires SuperAdmin auth and the contract to already be paused. Queues the withdrawal in the existing timelock system and returns the action_id.
  • execute_action handles the new variant with two additional guards beyond the normal timelock check:
    • Mandatory 7-day minimum delay (enforced independently of the configured timelock_secs)
    • Contract must still be paused at execution time
  • On success, transfers the full token balance from the contract to destination and emits emergency_withdrawal_executed (token, destination, amount).

Pre-existing compilation fixes

Several earlier PRs left the codebase in a non-compiling state. These are fixed minimally as a prerequisite:

  • types.rs: removed duplicate min_funding_amount, priorities, and admin_frozen fields from Invoice; added missing creation_timestamp and min_payment_increment to InvoiceExt2 so they round-trip through storage; wired external_prerequisite through Invoice::split, Invoice::assemble, and Invoice::from_legacy.
  • events.rs: removed eight duplicate function definitions (each function appeared twice with slightly different event symbol names).
  • lib.rs: removed a duplicate SHARD_COUNT constant, two redundant require_admin function definitions, a duplicate admin_frozen field assignment in clone_invoice, and updated all bare InvoiceExt2 default literals to include the new fields.
  • test.rs: added missing external_prerequisite: None to the invoice_options helper.

Test plan

  • test_replay_invoice_events_pending — confirms 2 replay events for a Pending invoice (created + 1 payment, no terminal event)
  • test_replay_invoice_events_released — confirms 4 replay events for a Released invoice (created + 2 payments + released)
  • test_emergency_withdraw_blocked_when_unpaused — request panics when contract is active
  • test_emergency_withdraw_blocked_before_7_days — execute panics before the 7-day delay elapses
  • test_emergency_withdraw_succeeds_after_7_days — full balance transferred to destination after pause + 7-day delay, balance verified on both sides
  • All existing cargo tests pass
  • cargo clippy passes with zero warnings

Closes #232
Closes #233
Closes #234
Closes #235

…drawal (Stellar-split#233)

Resolves two issues and cleans up pre-existing compilation errors
introduced by earlier PRs that left duplicate struct fields and
function definitions throughout the codebase.

Issue Stellar-split#232 — Invoice payment event replay for indexer recovery:
- Add replay_invoice_events(invoice_id) to lib.rs. Callable by anyone,
  pure read+emit with no state mutation. Re-emits invoice_created,
  one payment_received per historical payment in order, and the
  terminal status event (invoice_released / invoice_refunded) when
  the invoice is no longer Pending. All replayed events carry a
  fourth `replay` topic so indexers can distinguish them from live
  events and avoid double-counting.
- Add replay_invoice_created, replay_payment_received,
  replay_invoice_released, replay_invoice_refunded to events.rs.

Issue Stellar-split#233 — Contract-wide emergency withdrawal:
- Add EmergencyWithdraw(Address, Address) variant to TimelockAction.
- Add request_emergency_withdraw(admin, token, destination) to lib.rs.
  Requires SuperAdmin auth and the contract to already be paused.
  Queues a timelocked action and returns the action_id.
- Handle EmergencyWithdraw in execute_action with a mandatory 7-day
  minimum delay independent of the configured timelock_secs, plus a
  check that the contract is still paused at execution time. Transfers
  the full token balance to destination and emits
  emergency_withdrawal_executed (token, destination, amount).
- Add emergency_withdrawal_executed to events.rs.

Pre-existing compilation fixes:
- types.rs: remove duplicate fields (min_funding_amount, priorities,
  admin_frozen) from Invoice struct; add missing creation_timestamp
  and min_payment_increment to InvoiceExt2 so they round-trip through
  storage; wire external_prerequisite through Invoice split/assemble/
  from_legacy; add EmergencyWithdraw to TimelockAction.
- events.rs: remove eight duplicate function definitions that were
  added by a prior PR alongside the originals.
- lib.rs: remove duplicate SHARD_COUNT constant, two spurious
  require_admin definitions, duplicate admin_frozen assignment in
  clone_invoice, and update all InvoiceExt2 default literals to
  include creation_timestamp/min_payment_increment.
- test.rs: add missing external_prerequisite: None to invoice_options.

Tests added:
- test_replay_invoice_events_pending: verifies 2 replay events emitted
  for a pending invoice (created + 1 payment).
- test_replay_invoice_events_released: verifies 4 replay events for a
  fully-funded and released invoice (created + 2 payments + released).
- test_emergency_withdraw_blocked_when_unpaused: confirms request
  panics when contract is not paused.
- test_emergency_withdraw_blocked_before_7_days: confirms execute
  panics when called before the 7-day delay elapses.
- test_emergency_withdraw_succeeds_after_7_days: confirms full balance
  is transferred to destination after pause + 7-day delay.
@drips-wave

drips-wave Bot commented Jun 26, 2026

Copy link
Copy Markdown

@Idaonoli 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build configurable contract-wide emergency withdrawal Add invoice payment event replay for indexer recovery

1 participant