Skip to content

PSE Multi-Recipient mappping for clearing accounts#21

Merged
TxCorpi0x merged 5 commits into
masterfrom
mehdi/pse-multi-recipient-mappping
Nov 14, 2025
Merged

PSE Multi-Recipient mappping for clearing accounts#21
TxCorpi0x merged 5 commits into
masterfrom
mehdi/pse-multi-recipient-mappping

Conversation

@TxCorpi0x

@TxCorpi0x TxCorpi0x commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

Description

This pull request introduces support for multiple recipients per clearing account in the fund distribution logic. Previously, each clearing account could only have a single recipient; now, each can have one or more, and allocations are split equally among all recipients with precise handling of any remainder. The changes cover updates to the protocol buffers, documentation, initialization, distribution logic, and tests to reflect and verify this new behavior.

Protocol and API changes:

  • Updated ClearingAccountMapping to use a repeated field recipient_addresses instead of a single recipient_address, ensuring each clearing account can have multiple recipients.
  • Added a new RecipientDistribution message to the event proto, and changed the EventAllocationDistributed to emit the actual amount sent to each recipient, with clear documentation about the split and remainder handling.

Distribution logic changes:

  • Modified the distribution code in x/pse/keeper/distribution.go to split allocation amounts equally among all recipients, with any remainder going to the first recipient, ensuring all tokens are distributed precisely.

Initialization and test changes:

  • Updated initialization logic and tests to use the new RecipientAddresses field, and added scenarios for both single and multiple recipients per clearing account.
  • Enhanced distribution tests to verify correct splitting of allocations among multiple recipients and accurate balances after distribution.

Documentation improvements:

  • Expanded comments and documentation across code and API docs to clarify the new multi-recipient behavior, the rationale for remainder handling, and the guarantees provided by the updated logic.

These updates ensure that fund allocations can be flexibly and accurately distributed to multiple recipients per clearing account, with clear auditability and no loss due to rounding.

Reviewers checklist:

  • Try to write more meaningful comments with clear actions to be taken.
  • Nit-picking should be unblocking. Focus on core issues.

Authors checklist

  • Provide a concise and meaningful description
  • Review the code yourself first, before making the PR.
  • Annotate your PR in places that require explanation.
  • Think and try to split the PR to smaller PR if it is big.

This change is Reviewable

Copilot AI review requested due to automatic review settings November 13, 2025 10:00
@TxCorpi0x TxCorpi0x requested a review from a team as a code owner November 13, 2025 10:00
@TxCorpi0x TxCorpi0x requested review from masihyeganeh, miladz68 and ysv and removed request for a team November 13, 2025 10:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull Request Overview

This pull request introduces support for multiple recipients per clearing account in the PSE module's fund distribution system. Previously limited to a single recipient per clearing account, the system now supports one or more recipients with precise equal distribution of allocations and remainder handling to ensure no tokens are lost to rounding.

Key Changes:

  • Updated protocol buffers to use repeated recipient_addresses instead of single recipient_address, with a new RecipientDistribution message for detailed event tracking
  • Modified distribution logic to split allocations equally among recipients, with any remainder going to the first recipient to guarantee 100% distribution
  • Enhanced validation to prevent Community clearing account mappings, check for empty recipient lists, and detect duplicate recipients within the same clearing account

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
proto/tx/pse/v1/distribution.proto Changed ClearingAccountMapping to use repeated recipient_addresses field with updated documentation
proto/tx/pse/v1/event.proto Added RecipientDistribution message and updated EventAllocationDistributed to include recipients list and distribution details
x/pse/types/distribution.pb.go Generated code for the updated distribution proto with repeated field handling
x/pse/types/event.pb.go Generated code for new RecipientDistribution message and updated event structure
x/pse/types/params.go Enhanced validation logic to check Community account restrictions, empty recipient lists, invalid addresses, and duplicate recipients
x/pse/types/params_test.go Comprehensive test coverage for multi-recipient scenarios, validation edge cases, and Community account restrictions
x/pse/keeper/distribution.go Implemented equal distribution logic with remainder handling and updated event emission for multiple recipients
x/pse/keeper/distribution_test.go Added precision tests for multi-recipient distribution with remainder handling verification
x/pse/keeper/genesis_test.go Updated initialization tests to use recipient address arrays
x/pse/keeper/params_test.go Updated parameter tests to use new repeated field structure
app/upgrade/v6/pse_init.go Modified default mappings to initialize with single-element arrays for recipient addresses
app/upgrade/v6/pse_init_test.go Enhanced upgrade tests to verify multi-recipient distribution scenarios with both single and multiple recipients
docs/api.md Updated API documentation to reflect new multi-recipient structure and distribution behavior

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread x/pse/types/params.go
Comment on lines +90 to +94
// Community clearing account should NOT have recipient mappings
// It uses score-based distribution logic instead
if mapping.ClearingAccount == ClearingAccountCommunity {
return errorsmod.Wrapf(ErrInvalidParam, "mapping %d: community clearing account cannot have recipient mappings", i)
}

Copilot AI Nov 13, 2025

Copy link

Choose a reason for hiding this comment

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

The Community clearing account validation should be performed before other validations. If a Community mapping exists, the function returns early, but it would be more efficient and clearer to check this condition before validating the recipient addresses list (lines 97-113). Consider moving this check to immediately after line 88 where the clearing_account emptiness is checked.

Copilot uses AI. Check for mistakes.
Comment thread proto/tx/pse/v1/event.proto Outdated
Comment on lines +23 to +26
// If multiple recipients exist, the amount is split equally among them.
// Distribution always allocates 100% of the total amount with no tokens lost to rounding:
// - Base amount per recipient: total_amount / num_recipients (integer division)
// - Any remainder from division goes to the first recipient

Copilot AI Nov 13, 2025

Copy link

Choose a reason for hiding this comment

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

[nitpick] The comment describes the distribution logic in detail, which is helpful. However, this level of implementation detail in the proto file might be better suited for code comments. Consider keeping the proto comments more concise and focused on the message structure, moving the detailed distribution logic explanation to the keeper implementation comments.

Suggested change
// If multiple recipients exist, the amount is split equally among them.
// Distribution always allocates 100% of the total amount with no tokens lost to rounding:
// - Base amount per recipient: total_amount / num_recipients (integer division)
// - Any remainder from division goes to the first recipient
// The total amount is distributed among recipients, and the sum of all recipient amounts equals total_amount.

Copilot uses AI. Check for mistakes.

// DefaultClearingAccountMappings returns the default clearing account mappings for the given chain ID.
// Community clearing account is not included in the mappings.
// Each clearing account has a single default recipient address.

Copilot AI Nov 13, 2025

Copy link

Choose a reason for hiding this comment

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

The comment on line 74 states "Each clearing account has a single default recipient address" which might be misleading. While it's true for the default initialization, the system now supports multiple recipients per clearing account. Consider clarifying: "Each clearing account is initialized with a single default recipient address (can be expanded to multiple recipients via governance)".

Suggested change
// Each clearing account has a single default recipient address.
// Each clearing account is initialized with a single default recipient address (can be expanded to multiple recipients via governance).

Copilot uses AI. Check for mistakes.
Comment thread app/upgrade/v6/pse_init_test.go Outdated
Comment on lines +58 to +59
requireT.NotEmpty(mapping.RecipientAddresses,
"mapping for %s should have at least one recipient address", mapping.ClearingAccount)

Copilot AI Nov 13, 2025

Copy link

Choose a reason for hiding this comment

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

Duplicate validation check: Lines 56-59 contain duplicate checks for RecipientAddresses. The first check on line 56-57 verifies the field is not empty, and then lines 58-59 repeat essentially the same check. One of these should be removed.

Suggested change
requireT.NotEmpty(mapping.RecipientAddresses,
"mapping for %s should have at least one recipient address", mapping.ClearingAccount)

Copilot uses AI. Check for mistakes.
Comment thread x/pse/types/params.go Outdated
// Community clearing account should NOT have recipient mappings
// It uses score-based distribution logic instead
if mapping.ClearingAccount == ClearingAccountCommunity {
return errorsmod.Wrapf(ErrInvalidParam, "mapping %d: community clearing account cannot have recipient mappings", i)

Copilot AI Nov 13, 2025

Copy link

Choose a reason for hiding this comment

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

The error message should be capitalized consistently. Change "community" to "Community" to match the naming convention used in the codebase (e.g., line 92 uses "Community clearing account").

Suggested change
return errorsmod.Wrapf(ErrInvalidParam, "mapping %d: community clearing account cannot have recipient mappings", i)
return errorsmod.Wrapf(ErrInvalidParam, "mapping %d: Community clearing account cannot have recipient mappings", i)

Copilot uses AI. Check for mistakes.
Comment on lines +168 to +169
// Use amount that doesn't divide evenly by 3
allocationAmount := sdkmath.NewInt(1000) // 1000 / 3 = 333 remainder 1

Copilot AI Nov 13, 2025

Copy link

Choose a reason for hiding this comment

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

The comment says "Use amount that doesn't divide evenly by 3", but the actual amount (1000) divides evenly by 2 (for the Alliance account with 2 recipients). Consider updating the comment to be more comprehensive: "Use amount that doesn't divide evenly by 3 (for Foundation) and does divide evenly by 2 (for Alliance)" to clarify the test coverage.

Suggested change
// Use amount that doesn't divide evenly by 3
allocationAmount := sdkmath.NewInt(1000) // 1000 / 3 = 333 remainder 1
// Use amount that doesn't divide evenly by 3 (for Foundation) and does divide evenly by 2 (for Alliance)
allocationAmount := sdkmath.NewInt(1000) // 1000 / 3 = 333 remainder 1; 1000 / 2 = 500 remainder 0

Copilot uses AI. Check for mistakes.
Comment thread x/pse/keeper/distribution.go Outdated
Comment on lines +132 to +137
// Distribution Precision Handling:
// The allocation amount is split equally among all recipients using integer division.
// To ensure 100% of tokens are distributed with no loss to rounding:
// - Each recipient receives: allocation.Amount / numRecipients (base amount)
// - Any remainder from division goes to the first recipient
// This guarantees: sum(recipient_allocations) == allocation.Amount

Copilot AI Nov 13, 2025

Copy link

Choose a reason for hiding this comment

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

The comment heading "Distribution Precision Handling:" should be consistent with other comment styles in the file. Consider using a simpler format like "Split allocation equally among recipients" or following the existing comment style without the colon and special formatting.

Suggested change
// Distribution Precision Handling:
// The allocation amount is split equally among all recipients using integer division.
// To ensure 100% of tokens are distributed with no loss to rounding:
// - Each recipient receives: allocation.Amount / numRecipients (base amount)
// - Any remainder from division goes to the first recipient
// This guarantees: sum(recipient_allocations) == allocation.Amount
// Split allocation equally among recipients.
// The allocation amount is divided using integer division.
// To ensure 100% of tokens are distributed with no loss to rounding:
// Each recipient receives allocation.Amount / numRecipients (base amount).
// Any remainder from division goes to the first recipient.
// This guarantees that the sum of recipient allocations equals allocation.Amount.

Copilot uses AI. Check for mistakes.

@ysv ysv 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.

@ysv reviewed 1 of 6 files at r2, all commit messages.
Reviewable status: 1 of 13 files reviewed, 10 unresolved discussions (waiting on @masihyeganeh, @miladz68, and @TxCorpi0x)


proto/tx/pse/v1/event.proto line 10 at r2 (raw file):

// RecipientDistribution represents a single recipient and the amount they received.
message RecipientDistribution {

suggested name: AllocatedAmount


proto/tx/pse/v1/event.proto line 29 at r2 (raw file):

  // recipients contains the list of recipients and the exact amount each received.
  // The sum of all recipient amounts equals total_amount.
  repeated RecipientDistribution recipients = 2 [

I think field name should be different. Maybe allocated_amounts


x/pse/keeper/distribution.go line 156 at r2 (raw file):

			if i == 0 && !remainder.IsZero() {
				amountToSend = amountToSend.Add(remainder)
			}

I prefer to keep remainder undistributed. All account should be equally treated independently on order they have

Code quote:

			// Calculate amount for this recipient
			// First recipient gets the base amount plus any remainder to avoid dust
			amountToSend := amountPerRecipient
			if i == 0 && !remainder.IsZero() {
				amountToSend = amountToSend.Add(remainder)
			}

@miladz68 miladz68 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.

@miladz68 reviewed 7 of 13 files at r1, 6 of 6 files at r2, all commit messages.
Reviewable status: all files reviewed, 10 unresolved discussions (waiting on @masihyeganeh and @TxCorpi0x)

@TxCorpi0x TxCorpi0x left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reviewable status: all files reviewed, 10 unresolved discussions (waiting on @masihyeganeh and @ysv)


proto/tx/pse/v1/event.proto line 10 at r2 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

suggested name: AllocatedAmount

These are actually distributed (this is being used for the events after distribution is done), may be DistributedAmount


proto/tx/pse/v1/event.proto line 29 at r2 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

I think field name should be different. Maybe allocated_amounts

Or according to the previous comment, DistributedAmounts


x/pse/keeper/distribution.go line 156 at r2 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

I prefer to keep remainder undistributed. All account should be equally treated independently on order they have

Imaging 1000 and 3 participants, each get 333.33, we need to send the truncated values to the addresses (bank module send accepts Int), in that case the sum of the amounts would not get equal to the allocated values in the allocations.

@ysv ysv requested a review from miladz68 November 13, 2025 16:00

@ysv ysv 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.

@ysv reviewed 7 of 13 files at r1, 4 of 6 files at r2, 1 of 1 files at r3, all commit messages.
Reviewable status: all files reviewed, 10 unresolved discussions (waiting on @masihyeganeh and @TxCorpi0x)


proto/tx/pse/v1/event.proto line 10 at r2 (raw file):

Previously, TxCorpi0x wrote…

These are actually distributed (this is being used for the events after distribution is done), may be DistributedAmount

Both AllocatedAmount & DistributedAmount are good


proto/tx/pse/v1/event.proto line 29 at r2 (raw file):

Previously, TxCorpi0x wrote…

Or according to the previous comment, DistributedAmounts

Both AllocatedAmount & DistributedAmount are good


x/pse/keeper/distribution.go line 156 at r2 (raw file):

Previously, TxCorpi0x wrote…

Imaging 1000 and 3 participants, each get 333.33, we need to send the truncated values to the addresses (bank module send accepts Int), in that case the sum of the amounts would not get equal to the allocated values in the allocations.

yes , I think it is better to do it this way.
We should probably also suggest numbers like 4,5, 8 accounts so it is divisible. But IMO it is better to leave this remainder, @miladz68 WDYT ?

@miladz68 miladz68 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.

@miladz68 reviewed 1 of 1 files at r3, all commit messages.
Reviewable status: all files reviewed, 10 unresolved discussions (waiting on @masihyeganeh and @TxCorpi0x)

@ysv ysv 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.

@ysv reviewed 6 of 6 files at r4, all commit messages.
Reviewable status: all files reviewed, 3 unresolved discussions (waiting on @masihyeganeh and @TxCorpi0x)

@TxCorpi0x TxCorpi0x merged commit a6015f8 into master Nov 14, 2025
8 of 9 checks passed
@ysv ysv deleted the mehdi/pse-multi-recipient-mappping branch December 19, 2025 14:41
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.

4 participants