Skip to content

Commit 9edde81

Browse files
author
Chandra Pratap
committed
fuzz-tests: add test for amount-{sat, msat} arithmetic
The `fuzz-amount` test doesn't fuzz the arithmetic operations for `struct amount_sat` and `struct amount_msat`. Add a test for them.
1 parent 44972b2 commit 9edde81

File tree

1 file changed

+295
-0
lines changed

1 file changed

+295
-0
lines changed

tests/fuzz/fuzz-amount-arith.c

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
#include "config.h"
2+
#include <assert.h>
3+
#include <math.h>
4+
#include <common/amount.h>
5+
#include <common/overflows.h>
6+
#include <wally_script.h>
7+
#include <tests/fuzz/libfuzz.h>
8+
9+
void init(int *argc, char ***argv) {}
10+
11+
#define MAX_BTC ((u64)WALLY_SATOSHI_PER_BTC * WALLY_BTC_MAX)
12+
#define MAX_FEE_BASE 10000000
13+
#define MAX_FEE_PROP 1000000
14+
15+
static struct amount_sat fromwire_amount_sat_bounded(const u8 **cursor, size_t *max)
16+
{
17+
struct amount_sat amt = fromwire_amount_sat(cursor, max);
18+
amt.satoshis %= (MAX_BTC + 1);
19+
return amt;
20+
}
21+
22+
static struct amount_msat fromwire_amount_msat_bounded(const u8 **cursor, size_t *max)
23+
{
24+
struct amount_msat amt;
25+
struct amount_sat amt_bounded = fromwire_amount_sat_bounded(cursor, max);
26+
if (!amount_sat_to_msat(&amt, amt_bounded))
27+
assert(false && "amount_sat_to_msat failed!");
28+
return amt;
29+
}
30+
31+
enum op {
32+
OP_MSAT_ADD,
33+
OP_MSAT_SUB,
34+
OP_MSAT_MUL,
35+
OP_MSAT_DIV,
36+
OP_MSAT_RATIO,
37+
OP_MSAT_RATIO_FLOOR,
38+
OP_MSAT_RATIO_CEIL,
39+
OP_MSAT_SCALE,
40+
OP_MSAT_ADD_SAT,
41+
OP_MSAT_SUB_SAT,
42+
OP_SAT_ADD,
43+
OP_SAT_SUB,
44+
OP_SAT_MUL,
45+
OP_SAT_DIV,
46+
OP_SAT_SCALE,
47+
OP_FEE,
48+
OP_ADD_FEE,
49+
OP_SUB_FEE,
50+
OP_TX_FEE,
51+
OP_FEERATE,
52+
OP_COUNT
53+
};
54+
55+
void run(const uint8_t *data, size_t size) {
56+
if (size < sizeof(uint8_t) + 2 * sizeof(struct amount_msat) + sizeof(double))
57+
return;
58+
59+
uint8_t op = *data++ % OP_COUNT;
60+
61+
struct amount_msat a = fromwire_amount_msat_bounded(&data, &size);
62+
struct amount_msat b = fromwire_amount_msat_bounded(&data, &size);
63+
64+
double f;
65+
memcpy(&f, data, sizeof(f));
66+
data += sizeof(f);
67+
68+
struct amount_sat sa = amount_msat_to_sat_round_down(a);
69+
struct amount_sat sb = amount_msat_to_sat_round_down(b);
70+
71+
u64 u64_param;
72+
memcpy(&u64_param, &f, sizeof(u64_param));
73+
74+
struct amount_msat out_ms;
75+
struct amount_sat out_s;
76+
77+
switch (op) {
78+
case OP_MSAT_ADD:
79+
{
80+
if (amount_msat_add(&out_ms, a, b)) {
81+
assert(out_ms.millisatoshis == a.millisatoshis + b.millisatoshis);
82+
}
83+
break;
84+
}
85+
86+
case OP_MSAT_SUB:
87+
{
88+
if (amount_msat_sub(&out_ms, a, b)) {
89+
assert(out_ms.millisatoshis + b.millisatoshis == a.millisatoshis);
90+
}
91+
break;
92+
}
93+
94+
case OP_MSAT_MUL:
95+
{
96+
if (amount_msat_mul(&out_ms, a, u64_param)) {
97+
assert(out_ms.millisatoshis == a.millisatoshis * u64_param);
98+
}
99+
break;
100+
}
101+
102+
case OP_MSAT_DIV:
103+
{
104+
if (u64_param == 0)
105+
break;
106+
out_ms = amount_msat_div(a, u64_param);
107+
assert(out_ms.millisatoshis == a.millisatoshis / u64_param);
108+
break;
109+
}
110+
111+
case OP_MSAT_RATIO:
112+
{
113+
if (b.millisatoshis == 0)
114+
break;
115+
double ratio = amount_msat_ratio(a, b);
116+
double expected = (double)a.millisatoshis / b.millisatoshis;
117+
assert(ratio == expected);
118+
break;
119+
}
120+
121+
case OP_MSAT_RATIO_FLOOR:
122+
{
123+
if (b.millisatoshis == 0)
124+
break;
125+
u64 floor = amount_msat_ratio_floor(a, b);
126+
assert(floor == a.millisatoshis / b.millisatoshis);
127+
break;
128+
}
129+
130+
case OP_MSAT_RATIO_CEIL:
131+
{
132+
if (b.millisatoshis == 0)
133+
break;
134+
u64 ceil = amount_msat_ratio_ceil(a, b);
135+
u64 quotient = a.millisatoshis / b.millisatoshis;
136+
u64 remainder = a.millisatoshis % b.millisatoshis;
137+
138+
assert(ceil == quotient + (remainder != 0));
139+
break;
140+
}
141+
142+
case OP_MSAT_SCALE:
143+
{
144+
/* Guard against NaN and -ve factors. */
145+
if (f != f || f < 0)
146+
break;
147+
148+
if (amount_msat_scale(&out_ms, a, f)) {
149+
double expect = (double)a.millisatoshis * f;
150+
assert(fabs((double)out_ms.millisatoshis - expect) < 1.0);
151+
}
152+
break;
153+
}
154+
155+
case OP_MSAT_ADD_SAT:
156+
{
157+
if (amount_msat_add_sat(&out_ms, a, sa)) {
158+
assert(out_ms.millisatoshis == sa.satoshis * MSAT_PER_SAT + a.millisatoshis);
159+
}
160+
break;
161+
}
162+
163+
case OP_MSAT_SUB_SAT:
164+
{
165+
if (amount_msat_sub_sat(&out_ms, a, sa)) {
166+
assert(out_ms.millisatoshis + sa.satoshis * MSAT_PER_SAT == a.millisatoshis);
167+
}
168+
break;
169+
}
170+
171+
case OP_SAT_ADD:
172+
{
173+
if (amount_sat_add(&out_s, sa, sb)) {
174+
assert(out_s.satoshis == sa.satoshis + sb.satoshis);
175+
}
176+
break;
177+
}
178+
179+
case OP_SAT_SUB:
180+
{
181+
if (amount_sat_sub(&out_s, sa, sb)) {
182+
assert(out_s.satoshis == sa.satoshis - sb.satoshis);
183+
}
184+
break;
185+
}
186+
187+
case OP_SAT_MUL:
188+
{
189+
if (amount_sat_mul(&out_s, sa, u64_param)) {
190+
assert(out_s.satoshis == sa.satoshis * u64_param);
191+
}
192+
break;
193+
}
194+
195+
case OP_SAT_DIV:
196+
{
197+
if (u64_param == 0)
198+
break;
199+
out_s = amount_sat_div(sa, u64_param);
200+
assert(out_s.satoshis == sa.satoshis / u64_param);
201+
break;
202+
}
203+
204+
case OP_SAT_SCALE:
205+
{
206+
/* Guard against NaN and -ve factors. */
207+
if (f != f || f < 0)
208+
break;
209+
210+
if (amount_sat_scale(&out_s, sa, f)) {
211+
double expect = sa.satoshis * f;
212+
assert(fabs((double)out_s.satoshis - expect) < 1.0);
213+
}
214+
break;
215+
}
216+
217+
case OP_FEE:
218+
{
219+
if (amount_msat_fee(&out_ms, a, (u32)(a.millisatoshis & UINT32_MAX), (u32)(b.millisatoshis & UINT32_MAX))) {
220+
assert(out_ms.millisatoshis >= (a.millisatoshis & UINT32_MAX));
221+
}
222+
break;
223+
}
224+
225+
case OP_ADD_FEE:
226+
{
227+
u32 fee_base = (u32)(a.millisatoshis & UINT32_MAX) % MAX_FEE_BASE;
228+
u32 fee_prop = (u32)(b.millisatoshis & UINT32_MAX) % MAX_FEE_PROP;
229+
230+
struct amount_msat original = a;
231+
struct amount_msat fee;
232+
233+
if (amount_msat_fee(&fee, original, fee_base, fee_prop)) {
234+
struct amount_msat total;
235+
if (amount_msat_add(&total, original, fee)) {
236+
assert(amount_msat_greater_eq(total, fee));
237+
238+
struct amount_msat expected_total;
239+
assert(amount_msat_add(&expected_total, original, fee));
240+
assert(amount_msat_eq(total, expected_total));
241+
242+
a = total;
243+
}
244+
}
245+
break;
246+
}
247+
248+
case OP_SUB_FEE:
249+
{
250+
u32 fee_base = (u32)(a.millisatoshis & UINT32_MAX) % MAX_FEE_BASE;
251+
u32 fee_prop = (u32)(b.millisatoshis & UINT32_MAX) % MAX_FEE_PROP;
252+
struct amount_msat input = a;
253+
struct amount_msat output = amount_msat_sub_fee(input, fee_base, fee_prop);
254+
struct amount_msat fee;
255+
if (amount_msat_fee(&fee, output, fee_base, fee_prop)) {
256+
struct amount_msat sum;
257+
if (amount_msat_add(&sum, output, fee))
258+
assert(amount_msat_less_eq(sum, input));
259+
}
260+
break;
261+
}
262+
263+
case OP_TX_FEE:
264+
{
265+
if (b.millisatoshis > SIZE_MAX)
266+
break;
267+
u32 fee_per_kw = (u32)(a.millisatoshis & UINT32_MAX);
268+
size_t weight = (size_t)(b.millisatoshis);
269+
270+
/* weights > 2^32 are not real tx and hence, discarded */
271+
if (mul_overflows_u64(fee_per_kw, weight))
272+
break;
273+
274+
struct amount_sat fee = amount_tx_fee(fee_per_kw, weight);
275+
u64 expected = (fee_per_kw * weight) / MSAT_PER_SAT;
276+
assert(fee.satoshis == expected);
277+
break;
278+
}
279+
280+
case OP_FEERATE:
281+
{
282+
struct amount_sat fee = amount_msat_to_sat_round_down(a);
283+
size_t weight = (size_t)(b.millisatoshis);
284+
u32 feerate;
285+
if (weight && amount_feerate(&feerate, fee, weight)) {
286+
u64 expected = (fee.satoshis * MSAT_PER_SAT) / weight;
287+
assert(feerate == expected);
288+
}
289+
break;
290+
}
291+
292+
default:
293+
assert(false && "Unknown operation!");
294+
}
295+
}

0 commit comments

Comments
 (0)