1package prorata23import (4 "testing"56 "gno.land/p/nt/uassert/v0"7)89func sum(xs []int64) int64 {10 t := int64(0)11 for _, x := range xs {12 t += x13 }14 return t15}1617func TestSplitTable(t *testing.T) {18 cases := []struct {19 name string20 amount int6421 weights []int6422 want []int6423 }{24 {"exact thirds", 99, []int64{1, 1, 1}, []int64{33, 33, 33}},25 {"one unit of dust, to the lowest index", 10, []int64{1, 1, 1}, []int64{4, 3, 3}},26 {"two units of dust", 11, []int64{1, 1, 1}, []int64{4, 4, 3}},27 {"uneven weights", 100, []int64{1, 2, 3}, []int64{17, 33, 50}},28 {"a single claimant takes it all", 7, []int64{5}, []int64{7}},29 {"zero weight is paid nothing", 10, []int64{0, 1, 1}, []int64{0, 5, 5}},30 {"zero weight is not paid dust either", 10, []int64{0, 1, 1, 1}, []int64{0, 4, 3, 3}},31 {"nothing to split", 0, []int64{3, 1}, []int64{0, 0}},32 {"amount smaller than the roster", 2, []int64{1, 1, 1, 1}, []int64{1, 1, 0, 0}},33 {"weight dominates", 1000, []int64{999, 1}, []int64{999, 1}},34 }35 for _, tc := range cases {36 got, err := Split(tc.amount, tc.weights)37 uassert.NoError(t, err, tc.name)38 uassert.Equal(t, len(tc.want), len(got), tc.name)39 for i := range tc.want {40 uassert.Equal(t, tc.want[i], got[i], tc.name)41 }42 }43}4445// TestSplitAlwaysSumsToTheAmount is the whole point: a split that is short by46// dust strands coins in the realm forever, and one that is over cannot be paid.47func TestSplitAlwaysSumsToTheAmount(t *testing.T) {48 weightsets := [][]int64{49 {1, 1, 1},50 {1, 2, 3, 4, 5, 6, 7},51 {7, 0, 1},52 {100, 1},53 {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3},54 {1},55 {999983, 17, 5},56 }57 for _, ws := range weightsets {58 for amount := int64(0); amount < 60; amount++ {59 got, err := Split(amount, ws)60 uassert.NoError(t, err)61 uassert.Equal(t, amount, sum(got), "shares must sum to the amount")62 }63 for _, amount := range []int64{1000, 123457, 999999999} {64 got, err := Split(amount, ws)65 uassert.NoError(t, err)66 uassert.Equal(t, amount, sum(got))67 }68 }69}7071// TestLargestRemainderWins pins the allocation rule, so a payout cannot quietly72// change to "whoever the loop paid last".73func TestLargestRemainderWins(t *testing.T) {74 // total 10, amount 4: floors are 0,0,1,1 with remainders 4,8,2,6.75 got, err := Split(4, []int64{1, 2, 3, 4})76 uassert.NoError(t, err)77 uassert.Equal(t, int64(0), got[0], "smallest remainder, no extra unit")78 uassert.Equal(t, int64(1), got[1], "largest remainder, gets one")79 uassert.Equal(t, int64(1), got[2], "floor 1, remainder 2, gets none")80 uassert.Equal(t, int64(2), got[3], "second largest, gets the other")81 uassert.Equal(t, int64(4), sum(got))82}8384func TestSplitValidation(t *testing.T) {85 _, err := Split(-1, []int64{1})86 uassert.ErrorIs(t, err, ErrNegativeAmount)8788 _, err = Split(10, []int64{1, -1})89 uassert.ErrorIs(t, err, ErrNegativeWeight)9091 _, err = Split(10, nil)92 uassert.ErrorIs(t, err, ErrNoWeight, "an empty roster is the caller's problem, not dust")9394 _, err = Split(10, []int64{0, 0})95 uassert.ErrorIs(t, err, ErrNoWeight, "every weight zero is the same problem")96}9798// TestNoOverflowOnLargePots is the second failure mode: amount*weight wraps99// negative long before either factor is anywhere near the limit.100func TestNoOverflowOnLargePots(t *testing.T) {101 // A pot of 10^18 ugnot split across weights that would overflow if102 // multiplied together first.103 pot := int64(1000000000000000000)104 got, err := Split(pot, []int64{3000000000, 1000000000})105 uassert.NoError(t, err)106 uassert.Equal(t, pot, sum(got))107 uassert.Equal(t, int64(750000000000000000), got[0])108 uassert.Equal(t, int64(250000000000000000), got[1])109110 got, err = Split(maxInt64, []int64{1, 1})111 uassert.NoError(t, err)112 uassert.Equal(t, maxInt64, sum(got), "even the largest possible pot")113114 _, err = Total([]int64{maxInt64, 1})115 uassert.ErrorIs(t, err, ErrOverflow, "a wrapped total makes every share nonsense")116}117118func TestShare(t *testing.T) {119 got, err := Share(100, 1, 3)120 uassert.NoError(t, err)121 uassert.Equal(t, int64(33), got, "rounded down, the remainder is Split's job")122123 got, err = Share(1000000000000000000, 3000000000, 4000000000)124 uassert.NoError(t, err)125 uassert.Equal(t, int64(750000000000000000), got)126127 got, err = Share(100, 7, 3)128 uassert.NoError(t, err)129 uassert.Equal(t, int64(100), got, "a weight above the total is clamped, never over-paid")130131 _, err = Share(10, 1, 0)132 uassert.ErrorIs(t, err, ErrNoWeight)133 _, err = Share(-1, 1, 2)134 uassert.ErrorIs(t, err, ErrNegativeAmount)135 _, err = Share(10, -1, 2)136 uassert.ErrorIs(t, err, ErrNegativeWeight)137}138139func TestTotal(t *testing.T) {140 got, err := Total([]int64{1, 2, 3})141 uassert.NoError(t, err)142 uassert.Equal(t, int64(6), got)143144 got, err = Total(nil)145 uassert.NoError(t, err)146 uassert.Equal(t, int64(0), got, "empty sums to zero; Split is what refuses it")147}148Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.