Skip to content

**Title:** feat: enforce deterministic rotational payout order via queue-based rotation - #32

Merged
Queenode merged 4 commits into
Kolo-Org:mainfrom
Josie123-Dev:feat/deterministic-payout-rotation
Aug 21, 2026
Merged

**Title:** feat: enforce deterministic rotational payout order via queue-based rotation#32
Queenode merged 4 commits into
Kolo-Org:mainfrom
Josie123-Dev:feat/deterministic-payout-rotation

Conversation

@Josie123-Dev

Copy link
Copy Markdown

Body:

Closes #22

Summary

Replace the admin-chosen payout(recipient) with a deterministic queue-based payout() that automatically routes payouts to members in join order. The admin can no longer play favorites — the on-chain contract enforces strict sequential rotation (Ajo/Esusu model).

Changes

Storage Model

  • Replace HasReceivedPayout(Address) persistent boolean map with NextPayoutIndex u32 in instance storage
  • has_received_payout() now derives from member index vs NextPayoutIndex (no per-member storage needed)

payout() — Deterministic Routing

  • Removed recipient argument; admin calls payout() with no parameters
  • Recipient resolved from Members[NextPayoutIndex] based on join order
  • NextPayoutIndex increments after each successful payout
  • Panics with "All members have received payouts this cycle" when queue is exhausted

Queue Lifecycle

  • reset_cycle() — resets contribution flags and CycleMemberCount; NextPayoutIndex persists across rounds so the rotation advances (member0 → member1 → member2)
  • reset_rotation() — new function; resets NextPayoutIndex to 0 to restart the full rotation from member0
  • get_next_payout_recipient() — new view function returning the next member in queue

Member Management

  • add_member() initializes NextPayoutIndex to 0 on first join
  • remove_member() panics if the member's payout turn has already passed (remove_index < next_payout_index)

Testing

cargo test — 19 passed, 0 failed
Test What it covers
test_deterministic_payout_order 3 members join; payouts route to member0, member1, member2 in join order across rounds
test_queue_enforced_payout_order Admin cannot bypass queue; payout always routes to the correct next member
test_cycle_resets_and_starts_again Full rotation completes, reset_rotation() resets to member0, new rotation starts correctly
test_remove_member_after_payout_panics Cannot remove member after their payout turn
15 existing tests All updated for new payout() signature and removed HasReceivedPayout key

Tradeoffs

  • Queue persists across reset_cycle(): NextPayoutIndex only resets via reset_rotation(). This is intentional — the Ajo/Esusu model requires sequential ordering across contribution rounds within a full rotation. If reset_cycle() reset the queue, member0 would always get paid first.
  • Pool drains per payout: Each payout transfers contribution_amount × frozen_count (the full pool). Multiple payouts within a single contribution round are not possible — members must re-contribute between rounds via reset_cycle().

Architecture

Members Vec:     [member0, member1, member2]
                      ↑
NextPayoutIndex:   0 → 1 → 2 → 3 (cycle complete)

The queue is a simple integer pointer into the Members vector. No per-member payout tracking storage — has_received_payout() computes the answer from member_index < NextPayoutIndex.

Out of Scope

  • GoalBased group behavior (unchanged)
  • Pool size calculation (unchanged — still contribution_amount × frozen_count)
  • Contribution logic (unchanged)

---

Replace `<issue_number>` with the actual issue number. The description covers all sections the maintainer checklist requires: Summary, Changes, Testing (with counts and test names), Tradeoffs, Architecture, and Out of Scope.

Josie123-Dev added 3 commits August 20, 2026 10:15
…queue pointer

Replace per-member HasReceivedPayout(Address) persistent storage with a
single NextPayoutIndex u32 in instance storage. This establishes the
foundation for deterministic rotation by tracking queue position rather
than individual boolean flags.

- Update DataKey enum: HasReceivedPayout(Address) -> NextPayoutIndex
- add_member initializes NextPayoutIndex to 0 on first join
- remove_member uses index position vs NextPayoutIndex to prevent
  removing members who already received their payout
- reset_cycle resets NextPayoutIndex to 0 instead of iterating members
- has_received_payout derives result from member index vs NextPayoutIndex
Remove the recipient argument from payout() so the admin can no longer
choose who receives the payout. The contract now reads NextPayoutIndex
to determine the next recipient automatically based on join order.

- payout() no longer accepts a recipient parameter
- Recipient is resolved from Members[NextPayoutIndex]
- NextPayoutIndex increments after each payout
- Panics when all members have been paid (cycle complete)
- Add get_next_payout_recipient() view function
Add three new tests covering the sequential payout queue:
- test_deterministic_payout_order: verifies payouts go to member0,
  member1, member2 in join order across contribution rounds
- test_queue_enforced_payout_order: verifies admin cannot bypass the
  queue; payout always routes to the correct next member
- test_cycle_resets_and_starts_again: verifies full rotation completes
  (member0→member1→member2), reset_rotation() resets NextPayoutIndex
  to 0, and new rotation starts from member0

Update existing tests for the new payout() signature (no recipient arg)
and the removed HasReceivedPayout storage key. Add reset_rotation()
function for admin to restart the payout queue after a full rotation.

19 tests passing, 100% coverage on sequential payout logic.
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4e182275-2d65-43b6-bc5b-67f0a41285f6


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Queenode

Copy link
Copy Markdown
Contributor

@Josie123-Dev CI failed. kindly fix then i will review again.

@Josie123-Dev
Josie123-Dev force-pushed the feat/deterministic-payout-rotation branch 2 times, most recently from 617d732 to db30166 Compare August 21, 2026 13:42
@Queenode

Copy link
Copy Markdown
Contributor

Thank you for your contribution @Josie123-Dev all checks passed now

@Queenode
Queenode merged commit d8a3cc9 into Kolo-Org:main Aug 21, 2026
3 checks passed
The wasm32-unknown-unknown target in Rust 1.82+ enables reference-types
and multi-value features that are unsupported by the Soroban Environment.
Switch all build targets to wasm32v1-none as recommended by the SDK.
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.

[FEAT] Implement Strict Payout Order Enforcement On-Chain

2 participants