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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

prorata_test.gno

4.68 Kb · 147 lines
  1package prorata
  2
  3import (
  4	"testing"
  5
  6	"gno.land/p/nt/uassert/v0"
  7)
  8
  9func sum(xs []int64) int64 {
 10	t := int64(0)
 11	for _, x := range xs {
 12		t += x
 13	}
 14	return t
 15}
 16
 17func TestSplitTable(t *testing.T) {
 18	cases := []struct {
 19		name    string
 20		amount  int64
 21		weights []int64
 22		want    []int64
 23	}{
 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}
 44
 45// TestSplitAlwaysSumsToTheAmount is the whole point: a split that is short by
 46// 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}
 70
 71// TestLargestRemainderWins pins the allocation rule, so a payout cannot quietly
 72// 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}
 83
 84func TestSplitValidation(t *testing.T) {
 85	_, err := Split(-1, []int64{1})
 86	uassert.ErrorIs(t, err, ErrNegativeAmount)
 87
 88	_, err = Split(10, []int64{1, -1})
 89	uassert.ErrorIs(t, err, ErrNegativeWeight)
 90
 91	_, err = Split(10, nil)
 92	uassert.ErrorIs(t, err, ErrNoWeight, "an empty roster is the caller's problem, not dust")
 93
 94	_, err = Split(10, []int64{0, 0})
 95	uassert.ErrorIs(t, err, ErrNoWeight, "every weight zero is the same problem")
 96}
 97
 98// TestNoOverflowOnLargePots is the second failure mode: amount*weight wraps
 99// 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 if
102	// 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])
109
110	got, err = Split(maxInt64, []int64{1, 1})
111	uassert.NoError(t, err)
112	uassert.Equal(t, maxInt64, sum(got), "even the largest possible pot")
113
114	_, err = Total([]int64{maxInt64, 1})
115	uassert.ErrorIs(t, err, ErrOverflow, "a wrapped total makes every share nonsense")
116}
117
118func 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")
122
123	got, err = Share(1000000000000000000, 3000000000, 4000000000)
124	uassert.NoError(t, err)
125	uassert.Equal(t, int64(750000000000000000), got)
126
127	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")
130
131	_, 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}
138
139func TestTotal(t *testing.T) {
140	got, err := Total([]int64{1, 2, 3})
141	uassert.NoError(t, err)
142	uassert.Equal(t, int64(6), got)
143
144	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}