Repository navigation
Conversation
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.
Overview
Implements the discounted batch transaction fee this issue asks for in
SocialPaymentContract::batch_payout. The batch fee was calculated at the same flat coefficient as a singlepaycall (total_volume * fee_coef / 10_000) with the item count playing no part, so bulk senders had no fee incentive over sending the same payments one at a time. The fee is now tiered on the number of payouts a batch settles, deepening as the batch grows up toMAX_BATCH_SIZE.The volume-based fee, the treasury routing, the 1-stroop minimum and the "charge on settled volume only" behaviour are all preserved — the discount is applied on top of them.
Related Issue
Addresses the fee model described for mass payout. Note this work is deliberately additive: batches of fewer than 5 payouts are charged exactly what they are charged today, so no existing caller's cost changes at small sizes.
Changes
contracts/contracts/social_payment/src/lib.rs[ADD]
BATCH_DISCOUNT_MIN_ITEMS(u32 = 5) — the batch size below which the single-payment rate applies unchanged. At that size the per-recipient saving would be dust and integer rounding would swallow it.[ADD]
batch_discount_bps(item_count)— the discount tier table, kept as one match so the tiers are auditable in a single place:MAX_BATCH_SIZE[ADD]
discounted_fee(fee, discount_bps)— applies the discount with a single integer division.[MODIFY]
batch_payout— the balance-check quote is nowcalculate_fee(total_volume, fee_coef), the same expression single payments use (including its 1-stroop floor) instead of an inlinedtotal_volume * fee_coef / 10000. Behaviour is identical for every volume the old expression handled, and the split multiply incalculate_feeremoves the overflow exposure on large volumes. The quote stays the undiscounted upper bound, so the balance requirement is unchanged and remains conservative.[MODIFY]
batch_payout— asuccessful_countcounter is tracked alongsidesuccessful_volume, and the charged fee isdiscounted_fee(calculate_fee(successful_volume, fee_coef), batch_discount_bps(successful_count))with the 1-stroop minimum re-applied. The tier is chosen from payouts that settled, not payouts that were requested, so the caller is rewarded for payments that actually went out.[MODIFY] the
batch_payoutdoc comment now states the tier table and that the fee is charged on settled volume.Tests added to the in-crate
testsmoduletest_small_batch_pays_the_single_payment_rate— 4 payouts of 1_000 route 4 stroops to the treasury, i.e.calculate_fee(4_000, 10), identical to a single payment. This pins the "existing behaviour is unchanged below the threshold" property.test_batch_payout_applies_bulk_discount— 10 payouts of 1_000 (volume 10_000, gross fee 10) route 9 stroops, the 10% tier, and theMassPayoutExecutedsummary reportsfee_charged: 9,batch_size: 10,total_volume: 10_000.test_batch_discount_tier_deepens_with_batch_size— every tier boundary (0, 1, 4, 5, 9, 10, 24, 25, 49, 50,MAX_BATCH_SIZE).test_batch_effective_fee_rate_never_rises_with_batch_size— over real volumes at 100 bps, the effective rate (fee per unit volume) is non-increasing across 1 → 100 payouts, and a 100-payout batch routes strictly less than the undiscounted rate. This is the property a bulk incentive has to hold, checked against rounding rather than just the basis-point table.test_discount_can_round_to_zero_and_the_caller_restores_the_minimum— a 100-payout batch of 1 stroop each has a gross fee at the 1-stroop minimum, which the 30% discount rounds to zero; the test asserts the helper exposes that plainly so the floor is applied deliberately at the call site.contracts/contracts/social_payment/CHANGELOG.mdVerification Results
batch_payoutcharges one fee for the batch, now tiered on the settled item count; the balance check is unchanged and remains conservativeTREAS_KEYin a single transfer, only when settled volume is non-zerobatch_discount_bps— 5% at 5 payouts rising to 30% at 50+, so one batch call is strictly cheaper than the equivalent individual paymentscalculate_fee(settled_volume, fee_coef)), settled item count selects the discount tierbatch_payout, two new private helpers, its doc comment, the crate CHANGELOG and the test moduleResidual risk / out of scope
batch_payoutassertssender_balance >= total_volume + batch_feebefore the loop, then startsremaining_balanceatsender_balance - batch_fee, which is therefore always>= total_volume. Every payout fits, soskipped_volumeis always 0 and theBatchPayoutRefundedevent never fires. Because of this the discount tier currently equals the requested batch size in practice; the tier is nevertheless derived fromsuccessful_countso it stays correct if that gate is relaxed. Fixing the gate is a separate decision about whether an underfunded batch should partially pay or fail, and is deliberately not changed here.batch_discount_bpsto make retuning a one-line change.Closes #976