Skip to content

fix(campaign-escrow): finalize state writes before token transfers - #72

Merged
JamesVictor-O merged 6 commits into
Ads-Bazaar:mainfrom
akintewe:fix/reorder-transfers-after-state-writes-63
Aug 22, 2026
Merged

fix(campaign-escrow): finalize state writes before token transfers#72
JamesVictor-O merged 6 commits into
Ads-Bazaar:mainfrom
akintewe:fix/reorder-transfers-after-state-writes-63

Conversation

@akintewe

Copy link
Copy Markdown
Contributor

Summary

Every fund-moving function in campaign-escrow issued its token::Client::transfer call(s) before finalizing the corresponding storage writes, inverting checks-effects-interactions. This reorders all six affected functions so storage writes land first and transfers are issued last:

  • claim_payment
  • resolve_dispute
  • cancel_campaign
  • expire_campaign
  • reclaim_surplus
  • emergency_recover_campaign

In each case, amounts and addresses needed for the transfer (fee, net, refund, surplus, recovered, treasury, business) are computed/read before the storage write, then the token::Client and transfer calls are moved after storage::set_campaign / storage::set_application. This is a pure reordering — no balances, statuses, or event payloads change as a result.

Test plan

  • Existing test suite (campaign-escrow/src/test.rs, campaign-escrow/tests/integration.rs) passes unchanged

closes #63

… claim_payment

Reorders claim_payment so application.status, campaign.escrow_balance
and campaign.committed_payouts are all persisted before the fee and
net token.transfer calls are issued, following checks-effects-
interactions instead of interactions-then-effects.
…cancel_campaign

Persists campaign.escrow_balance and campaign.status before issuing
the refund transfer, matching checks-effects-interactions.
…expire_campaign

Persists campaign.escrow_balance and campaign.status before issuing
the refund transfer, matching checks-effects-interactions.
…emergency_recover_campaign

Persists campaign.escrow_balance and campaign.status before sweeping
the unallocated remainder to treasury, matching
checks-effects-interactions.
…reclaim_surplus

Persists campaign.escrow_balance and campaign.status before issuing
the surplus transfer, matching checks-effects-interactions.
… resolve_dispute

Persists application.status/frozen/dispute_opened_at and
campaign.escrow_balance/committed_payouts/status before issuing the
fee, creator and business transfers, matching
checks-effects-interactions.

@JamesVictor-O JamesVictor-O left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review: verified all six reordered functions (claim_payment, resolve_dispute, cancel_campaign, expire_campaign, reclaim_surplus, emergency_recover_campaign) function by function — every transfer amount is snapshotted before the moved storage writes, no mutated field is read stale, and no transfer return value is consumed. Confirmed against Soroban host source (soroban-env-host) that a trapping transfer rolls back all prior storage writes in the same invocation, so this reordering is behaviorally pure — no double-payout or stuck-fund window either way. Existing tests re-validate real token balances for all six paths; CI clean (build, clippy, format, test). This is solid checks-effects-interactions hygiene. Approving.

@JamesVictor-O
JamesVictor-O merged commit adb6042 into Ads-Bazaar:main Aug 22, 2026
4 checks passed
Promise278 added a commit to Promise278/ads-bazaar-contract that referenced this pull request Aug 24, 2026
…dispute

- Use try_close_dispute (fallible) instead of close_dispute (infallible)
  so a broken/unset dispute-resolution contract cannot brick the admin
  settlement path. The close-out is now best-effort: state writes and
  token transfers are committed first atomically.
- Reorder resolve_dispute to finalize state writes before token transfers
  (aligns with PR Ads-Bazaar#72 pattern): if a transfer traps, the whole invocation
  reverts cleanly — no partial-failure window.
- Add DisputeOutcome to the DisputeResolved event so indexers/frontends
  can distinguish PayCreator/RefundBusiness/Split without ratio inference.
- Rewrite stale doc comment that described the admin path as working
  'without' dispute-resolution (it now calls try_close_dispute on every
  resolution, best-effort).
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.

bug: fund-moving functions transfer tokens before finalizing state writes across campaign-escrow

2 participants