Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

/p/moul/x/games/prorata/v0

Directory · 4 Files
README.md Open

gno.land/p/moul/x/games/prorata/v0

A weighted split of an integer amount that adds up: Split, Share, Total.

1import "gno.land/p/moul/x/games/prorata/v0"
2
3prorata.Split(100, []int64{1, 2, 3})  // [17 33 50], and it sums to exactly 100
4prorata.Split(10, []int64{1, 1, 1})   // [4 3 3], the dust goes to the lowest index
5prorata.Share(100, 1, 3)              // 33, floored: "what would I get"

Every payout in a game is this operation: a pot, a roster, a weight each. Written the obvious way, amount*weight/total per claimant, it is wrong in two ways that only show up once there is real money in the contract.

The shares do not add up. Each one is rounded down, so their sum is short by up to one unit per claimant. That dust either sits in the realm forever or lands on whoever the loop happened to pay last, which is a rule nobody chose. Split distributes by largest remainder: floor every share, then hand the leftover units out one at a time to the claimants whose truncated fraction was largest, ties going to the lower index. The result sums to exactly the amount, and the rule is deterministic, so every node computes the same split and a Render of it does not change between calls.

amount*weight overflows an int64 long before either factor does, which turns a large payout negative. Nothing here ever computes that product: it splits the amount into whole multiples of the total and what is left over, so amount*weight/total becomes q*weight + r*weight/total with r already smaller than the total. A pot of 10^18 ugnot across billion-unit weights is exact, and what genuinely cannot fit returns ErrOverflow rather than wrapping.

Two smaller decisions, each with a test:

  • A zero weight is paid nothing, including no remainder unit. Somebody with no claim is not handed dust.
  • An empty roster, or one where every weight is zero, is an error, not an empty split. Silently returning nothing would strand the amount, and only the caller knows where it should go instead.

Nothing here reads the chain and nothing holds coins: the caller owns the roster and the custody.

Live game: r/moul/x/games/lastwords · render it at /r/moul/x/games/lastwords/v0.


Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.

🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.