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

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}