Skip to content

feat: multi-organizer revenue split and co-host wallet management - #1

Closed
josephchimebuka wants to merge 1 commit into
mainfrom
feat/multi-organizer-revenue-split
Closed

feat: multi-organizer revenue split and co-host wallet management#1
josephchimebuka wants to merge 1 commit into
mainfrom
feat/multi-organizer-revenue-split

Conversation

@josephchimebuka

Copy link
Copy Markdown
Owner

Linked issue

Closes BuidlZone-Labs#122

What this PR does

Events are no longer locked to a single organizer wallet. An event can now be created with a revenue split — up to five recipients, each with a basis-point allocation summing to 10000. After the platform fee is deducted, each recipient withdraws their own share independently from the payments contract; no single party can drain the others' funds. The primary organizer (split index 0) keeps full admin rights and can flag a compromised co-host wallet, which freezes that recipient's share in escrow until an admin resolves the dispute by either releasing it to the recipient or reassigning it to the primary organizer.

Change type

  • New contract entrypoint
  • Struct / storage change
  • Event / emission change
  • Cross-contract interface change

Storage impact

Field Before After Notes
CreateEventParams.revenue_splits / Event.revenue_splits N/A Vec<(Address, u32)> Empty = legacy single-organizer payout
DataKey::EventSplits(Symbol) (payments) N/A Vec<RevenueSplit> Immutable once set
DataKey::SplitSettlement(Symbol) (payments) N/A SplitSettlement Net-distributable snapshot, frozen at first withdrawal
DataKey::SplitWithdrawn(Symbol, Address) (payments) N/A i128 Per-recipient payout tracking
DataKey::SplitFlagged(Symbol, Address) (payments) N/A bool Escrow freeze for flagged co-hosts

Is this a breaking storage change?

  • No — additive only. Existing events have no split configured and keep the exact legacy withdrawal behaviour. The new revenue_splits field is set on every new CreateEventParams (empty Vec for the single-organizer case).

On-chain vs. off-chain behaviour

Claim Storage level Event level Notes
Platform fee deducted before splits Fee accumulated to platform revenue in ensure_split_settled; PlatformFeeCollected emitted once
Each recipient paid exactly their share SplitWithdrawn guards double-withdrawal; RevenueWithdrawn emitted per recipient
Flagged share held in escrow Funds stay in the contract; CohostFlagged / FlaggedShareResolved emitted
Splits immutable after creation n/a sync_revenue_splits rejects any re-set; no setter exists

Cross-contract impact

  • Yes — added PaymentsContract::sync_revenue_splits(event_contract, event_id, splits), called by the event contract during create_event.
    • Callers updated: event-contract (create_event now syncs the split; withdraw_split and flag_cohost proxy to payments).
    • No existing signature changed; this is purely additive.

Security checklist

  • New entrypoints are auth-gated: sync_revenue_splits requires the linked event contract; withdraw_split requires the recipient; flag_cohost requires the primary organizer; resolve_flagged_share requires the payments admin.
  • Withdrawals honour state transitions: completed events respect event_end_ledger + withdrawal_delay + admin_extension; cancelled events respect the dispute window and the time-based withdrawable ratio (mirrors withdraw).
  • Legacy withdraw / withdraw_token / withdraw_all_tokens / withdraw_revenue / release_if_expired are rejected for split events, preventing double payout.
  • Integer division dust is intentionally routed to the primary organizer so the full net is always distributed and never stranded.

Test coverage

New tests added (payments — revenue_split_test.rs):

Test name What it proves
test_sync_revenue_splits_stores_and_reads_back Config persists and reads back as (Address, u32)
test_sync_revenue_splits_rejects_bad_sum / _more_than_five / _duplicate_and_zero Validation of sum=10000, max 5, no dupes/zeros
test_revenue_splits_are_immutable_once_set Re-set is rejected
test_sync_revenue_splits_rejects_foreign_caller Only the event contract can configure
test_platform_fee_deducted_before_split_and_independent_withdrawals Fee first, then independent shares
test_withdraw_split_rejects_double_withdraw_and_non_recipient No double payout; strangers rejected
test_withdraw_split_respects_withdrawal_delay Escrow delay enforced
test_rounding_dust_accrues_to_primary_organizer Dust → primary; full net distributed
test_only_primary_can_flag_and_cannot_flag_self Flag authorization
test_flagged_cohost_share_held_in_escrow Flagged share frozen; others unaffected
test_resolve_flag_release_to_recipient_allows_withdrawal Dispute release path
test_resolve_flag_reassign_to_primary_pays_primary_and_blocks_recipient Dispute reassign path
test_resolve_requires_flagged_recipient Resolve only on flagged
test_legacy_withdraw_paths_rejected_for_split_events Legacy paths blocked
test_cancelled_split_event_distributes_only_withdrawable_ratio Cancellation ratio + refund remainder

New tests added (event — integration_tests.rs): test_event_split_end_to_end_distribution, test_event_rejects_invalid_split_configurations, test_event_flag_cohost_through_front_door.

Test count: 20 new, 18 updated (existing CreateEventParams literals), 180 total passing (event 77, payments 103). cargo fmt --check and cargo clippy -D warnings clean; cargo build --release succeeds.

Acceptance criteria sign-off

  • AC: Event creation accepts revenue_splits: Vec<(Address, u32)> summing to 10000 — validate_revenue_splits in event/lib.rs; test_event_split_end_to_end_distribution.
  • AC: Maximum 5 split recipients — rejected in both contracts; test_sync_revenue_splits_rejects_more_than_five.
  • AC: Each recipient can independently withdraw — withdraw_split; test_platform_fee_deducted_before_split_and_independent_withdrawals.
  • AC: Primary organizer (index 0) retains admin rights — index 0 must equal the organizer; flag_cohost is primary-only; event admin ops keyed off event.organizer.
  • AC: Splits immutable after publish — set only at creation, sync_revenue_splits rejects re-set; test_revenue_splits_are_immutable_once_set.
  • AC: Platform fee deducted first — ensure_split_settled deducts fee before computing shares; asserted in the fee test.
  • AC: Compromised co-host can be flagged; flagged share escrowed pending dispute — flag_cohost + resolve_flagged_share; flagging/escrow/resolution tests.

What this PR deliberately does NOT cover

  • Event-status completion is not auto-synced from the event contract to payments (a pre-existing gap); operators still set the payments-side status. Split settlement reuses the same status/timing rules as the existing withdraw.
  • Mutable/renegotiable splits are intentionally out of scope — splits are immutable by design per the spec.

Reviewer focus areas

  1. ensure_split_settled in payments/lib.rs — fee-first ordering and the full vs. partial (cancelled) accounting against validate_revenue_invariant.
  2. recipient_share — primary absorbs rounding dust; confirm sum of shares equals net.
  3. The ensure_no_splits guards on every legacy withdrawal path.

Made with Cursor

Replaces the single-organizer payout assumption with configurable revenue
splits and co-host escrow handling (closes BuidlZone-Labs#122).

- Event creation accepts revenue_splits: Vec<(Address, u32)> (basis points),
  validated to 1-5 recipients summing to 10000, no duplicates/zeros, with the
  primary organizer pinned at index 0. Splits are set once and immutable.
- The split is synced to the payments contract, which settles each event once
  (deducting the platform fee first) and lets every recipient withdraw their
  allocated share independently via withdraw_split. Rounding dust accrues to
  the primary organizer so the full net is always distributed.
- Primary organizer can flag a compromised co-host wallet; the flagged share is
  held in escrow and cannot be withdrawn. Admin resolves a dispute by either
  releasing the share to the recipient or reassigning it to the primary.
- Legacy single-organizer withdrawal paths are rejected for split events to
  prevent double payout.

Adds 17 payments unit tests and 3 event integration tests covering the happy
path, fee ordering, independent withdrawals, the delay/dispute windows, split
validation, immutability, flagging, and dispute resolution.

Co-authored-by: Cursor <cursoragent@cursor.com>
@josephchimebuka

Copy link
Copy Markdown
Owner Author

Superseded by the PR against upstream: BuidlZone-Labs#131.

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.

Implement multi-organizer revenue split and co-host wallet management

1 participant