storagecost_test.gno
6.55 Kb · 195 lines
1package storagecost
2
3import (
4 "testing"
5
6 "gno.land/p/nt/uassert/v0"
7)
8
9func TestRefund(t *testing.T) {
10 tests := []struct {
11 name string
12 bytes int64
13 price int64
14 want int64
15 }{
16 {"one byte at the default price", 1, DefaultStoragePrice, 100},
17 {"the measured reap", 17835, DefaultStoragePrice, 1783500},
18 {"the measured compact", 830, DefaultStoragePrice, 83000},
19 {"zero bytes", 0, DefaultStoragePrice, 0},
20 {"negative bytes are not a credit", -100, DefaultStoragePrice, 0},
21 {"a free chain refunds nothing", 1000, 0, 0},
22 {"a ten-fold price rise", 1000, 1000, 1000000},
23 }
24 for _, tt := range tests {
25 t.Run(tt.name, func(t *testing.T) {
26 uassert.Equal(t, tt.want, Refund(tt.bytes, tt.price))
27 })
28 }
29}
30
31func TestBreakEvenBytes(t *testing.T) {
32 tests := []struct {
33 name string
34 fee int64
35 want int64
36 }{
37 // A 2.6M gas call at the floor costs 2,600 ugnot, so 26 bytes.
38 {"the canonical 2.6M gas call", 2600, 26},
39 {"rounds up, never down", 101, 2},
40 {"exactly one byte", 100, 1},
41 {"a fee below one byte still needs a byte", 1, 1},
42 {"no fee, no threshold", 0, 0},
43 }
44 for _, tt := range tests {
45 t.Run(tt.name, func(t *testing.T) {
46 got := BreakEvenBytes(tt.fee, DefaultStoragePrice)
47 uassert.Equal(t, tt.want, got)
48 // The definition: this many bytes covers the fee, one fewer does not.
49 if tt.want > 0 {
50 uassert.True(t, Refund(got, DefaultStoragePrice) >= tt.fee)
51 uassert.False(t, Refund(got-1, DefaultStoragePrice) >= tt.fee)
52 }
53 })
54 }
55 uassert.Equal(t, int64(0), BreakEvenBytes(1000, 0))
56}
57
58func TestGasFee(t *testing.T) {
59 // The floor mainnet has actually accepted: 1 ugnot per 1000 gas.
60 uassert.Equal(t, int64(5000), FloorGasFee(5_000_000))
61 uassert.Equal(t, int64(2500), FloorGasFee(2_500_000))
62 // Rounds up, so a fee is never short.
63 uassert.Equal(t, int64(1), FloorGasFee(1))
64 uassert.Equal(t, int64(0), FloorGasFee(0))
65 // The harness price used in the measurements, 1.2 ugnot per gas.
66 uassert.Equal(t, int64(6_000_000), GasFee(5_000_000, 12, 10))
67 uassert.Equal(t, int64(0), GasFee(5_000_000, 0, 10))
68 uassert.Equal(t, int64(0), GasFee(5_000_000, 1, 0))
69}
70
71func TestEvaluateReproducesTheMeasuredReap(t *testing.T) {
72 // The measured run: 17,835 bytes freed by a call that used 4,120,876 gas,
73 // so gas_wanted 5,000,000 at the floor. Net +1,778,500 ugnot.
74 q := EvaluateAtFloor(17835, 5_000_000)
75 uassert.Equal(t, int64(1783500), q.Refund)
76 uassert.Equal(t, int64(5000), q.Fee)
77 uassert.Equal(t, int64(1778500), q.Net)
78 uassert.Equal(t, int64(50), q.BreakEven)
79 uassert.True(t, q.Worth())
80}
81
82func TestEvaluateReproducesTheMeasuredCompact(t *testing.T) {
83 q := EvaluateAtFloor(830, 2_500_000)
84 uassert.Equal(t, int64(83000), q.Refund)
85 uassert.Equal(t, int64(2500), q.Fee)
86 uassert.Equal(t, int64(80500), q.Net)
87 uassert.True(t, q.Worth())
88}
89
90func TestEvaluateAtTheHarnessPriceIsALoss(t *testing.T) {
91 // The same reap priced at 1.2 ugnot/gas, 1200x the floor, loses money.
92 // This is why the gas price cannot be baked into a contract.
93 q := Evaluate(17835, DefaultStoragePrice, 5_000_000, 12, 10)
94 uassert.Equal(t, int64(6_000_000), q.Fee)
95 uassert.False(t, q.Worth())
96 uassert.True(t, q.Net < 0)
97}
98
99func TestQuoteString(t *testing.T) {
100 worth := EvaluateAtFloor(17835, 5_000_000)
101 uassert.Equal(t,
102 "17835 bytes, refunds 1.7835 GNOT against 0.005 GNOT of gas, break-even 50 bytes: worth 1.7785 GNOT",
103 worth.String())
104
105 loss := Evaluate(10, DefaultStoragePrice, 5_000_000, 12, 10)
106 uassert.Equal(t,
107 "10 bytes, refunds 0.001 GNOT against 6 GNOT of gas, break-even 60000 bytes: not worth it yet",
108 loss.String())
109}
110
111func TestFormatGNOT(t *testing.T) {
112 tests := []struct {
113 amount int64
114 want string
115 }{
116 {0, "0 GNOT"},
117 {1, "0.000001 GNOT"},
118 {100, "0.0001 GNOT"},
119 {1_000_000, "1 GNOT"},
120 {1_500_000, "1.5 GNOT"},
121 {1_783_500, "1.7835 GNOT"},
122 {978_743_800, "978.7438 GNOT"},
123 {-2_100, "-0.0021 GNOT"},
124 {1_000_001, "1.000001 GNOT"},
125 }
126 for _, tt := range tests {
127 uassert.Equal(t, tt.want, FormatGNOT(tt.amount))
128 }
129}
130
131func TestEstimateBytes(t *testing.T) {
132 // Measured: a 1,024-byte payload occupied 1,898 bytes of realm state.
133 uassert.Equal(t, int64(1894), EstimateBytes(1024))
134 uassert.Equal(t, int64(0), EstimateBytes(0))
135 uassert.Equal(t, int64(0), EstimateBytes(-5))
136 // Monotonic, which is all a bounty estimate really needs.
137 uassert.True(t, EstimateBytes(2048) > EstimateBytes(1024))
138}
139
140// TestFormatGNOTIsTotal pins what v0 could not do. v0 negated in int64, so at
141// math.MinInt64 both halves kept a sign and it returned
142// "--9223372036854.-775808 GNOT". Delegating to kit/num is the fix; this
143// asserts the contract rather than the delegation.
144func TestFormatGNOTIsTotal(t *testing.T) {
145 const minInt64 = -9223372036854775808
146 uassert.Equal(t, "-9223372036854.775808 GNOT", FormatGNOT(minInt64))
147 uassert.Equal(t, "-1.5 GNOT", FormatGNOT(-1500000))
148 uassert.Equal(t, "-0.000001 GNOT", FormatGNOT(-1))
149 uassert.Equal(t, "1.5 GNOT", FormatGNOT(1500000))
150 uassert.Equal(t, "0 GNOT", FormatGNOT(0))
151}
152
153func TestEstimateNodes(t *testing.T) {
154 cases := []struct {
155 name string
156 nodes int64
157 want int64
158 }{
159 {"none", 0, 0},
160 {"negative is not an error", -5, 0},
161 // The mainnet measurement this constant comes from: compacting a
162 // drained 8-node board returned 6,848 bytes.
163 {"the mainnet sample", 8, 6848},
164 // And the local harness, which agreed to the byte.
165 {"the local harness sample", 31, 26536},
166 }
167 for _, tc := range cases {
168 uassert.Equal(t, tc.want, EstimateNodes(tc.nodes), tc.name)
169 }
170}
171
172// TestEstimateNodesBeatsEstimateBytesOnTheCaseThatBurnedUs pins the reason
173// EstimateNodes exists, not just what it computes.
174//
175// A realm advertised a bounty by running the payload length of five small
176// entries through EstimateBytes and got 481 bytes. Reaping and compacting them
177// actually returned 11,972. The payload path cannot see either the per-entry
178// floor or the container, and no existing test could catch that because they
179// all fed EstimateBytes the 1,024-byte payloads its ratio was measured on.
180func TestEstimateNodesBeatsEstimateBytesOnTheCaseThatBurnedUs(t *testing.T) {
181 const payload = 8 + 231 + 7 + 5 + 9 // the five note bodies, 260 bytes
182 const reallyFreed = 11972
183
184 guess := EstimateBytes(payload)
185 uassert.True(t, guess*20 < reallyFreed,
186 "EstimateBytes is still supposed to be the bad one here")
187
188 // The container half is the part that is countable, and counting it gets
189 // most of the way there on its own: 8 nodes were the larger share.
190 counted := EstimateNodes(8)
191 uassert.True(t, counted > guess*10,
192 "counting nodes should dominate guessing from payload")
193 uassert.True(t, counted <= reallyFreed,
194 "counting nodes must never over-advertise")
195}