**Title:** feat: enforce deterministic rotational payout order via queue-based rotation - #32
Merged
Queenode merged 4 commits intoAug 21, 2026
Conversation
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.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 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. Comment |
Contributor
|
@Josie123-Dev CI failed. kindly fix then i will review again. |
Josie123-Dev
force-pushed
the
feat/deterministic-payout-rotation
branch
2 times, most recently
from
August 21, 2026 13:42
617d732 to
db30166
Compare
Contributor
|
Thank you for your contribution @Josie123-Dev all checks passed now |
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Body:
Closes #22
Summary
Replace the admin-chosen
payout(recipient)with a deterministic queue-basedpayout()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
HasReceivedPayout(Address)persistent boolean map withNextPayoutIndexu32 in instance storagehas_received_payout()now derives from member index vsNextPayoutIndex(no per-member storage needed)payout()— Deterministic Routingrecipientargument; admin callspayout()with no parametersMembers[NextPayoutIndex]based on join orderNextPayoutIndexincrements after each successful payout"All members have received payouts this cycle"when queue is exhaustedQueue Lifecycle
reset_cycle()— resets contribution flags andCycleMemberCount;NextPayoutIndexpersists across rounds so the rotation advances (member0 → member1 → member2)reset_rotation()— new function; resetsNextPayoutIndexto 0 to restart the full rotation from member0get_next_payout_recipient()— new view function returning the next member in queueMember Management
add_member()initializesNextPayoutIndexto 0 on first joinremove_member()panics if the member's payout turn has already passed (remove_index < next_payout_index)Testing
test_deterministic_payout_ordertest_queue_enforced_payout_ordertest_cycle_resets_and_starts_againreset_rotation()resets to member0, new rotation starts correctlytest_remove_member_after_payout_panicspayout()signature and removedHasReceivedPayoutkeyTradeoffs
reset_cycle():NextPayoutIndexonly resets viareset_rotation(). This is intentional — the Ajo/Esusu model requires sequential ordering across contribution rounds within a full rotation. Ifreset_cycle()reset the queue, member0 would always get paid first.contribution_amount × frozen_count(the full pool). Multiple payouts within a single contribution round are not possible — members must re-contribute between rounds viareset_cycle().Architecture
The queue is a simple integer pointer into the
Membersvector. No per-member payout tracking storage —has_received_payout()computes the answer frommember_index < NextPayoutIndex.Out of Scope
contribution_amount × frozen_count)