cln/tests/fuzz/fuzz-amount-arith.c
Chandra Pratap 9fae53fe94 fuzz-tests: add test for amount-{sat, msat} arithmetic
Changelog-None: The `fuzz-amount` test doesn't fuzz the arithmetic
operations for `struct amount_sat` and `struct amount_msat`. Add a
test for them.
2026-05-08 09:40:19 +10:00

237 lines
5.6 KiB
C

#include "config.h"
#include <assert.h>
#include <common/amount.h>
#include <math.h>
#include <tests/fuzz/libfuzz.h>
#include <wally_script.h>
#include <wire/wire.h>
void init(int *argc, char ***argv) {}
#define MAX_SATS ((u64)WALLY_SATOSHI_PER_BTC * WALLY_BTC_MAX)
#define MAX_MSATS (MAX_SATS * 1000)
#define MAX_FEE_BASE 10000000
#define MAX_FEE_PROP 1000000
static struct amount_msat fromwire_amount_msat_bounded(const u8 **cursor, size_t *max)
{
u64 amt = fromwire_u64(cursor, max) % MAX_MSATS;
return amount_msat(amt);
}
enum op {
OP_MSAT_ADD,
OP_MSAT_SUB,
OP_MSAT_MUL,
OP_MSAT_DIV,
OP_MSAT_RATIO,
OP_MSAT_RATIO_FLOOR,
OP_MSAT_RATIO_CEIL,
OP_MSAT_SCALE,
OP_MSAT_ADD_SAT,
OP_MSAT_SUB_SAT,
OP_SAT_ADD,
OP_SAT_SUB,
OP_SAT_MUL,
OP_SAT_DIV,
OP_SAT_SCALE,
OP_FEE,
OP_ADD_FEE,
OP_SUB_FEE,
OP_TX_FEE,
OP_FEERATE,
OP_COUNT
};
void run(const uint8_t *data, size_t size) {
u8 op;
u64 u64_param;
double f;
struct amount_msat a, b, out_ms;
struct amount_sat sa, sb, out_s;
a = fromwire_amount_msat_bounded(&data, &size);
b = fromwire_amount_msat_bounded(&data, &size);
sa = amount_msat_to_sat_round_down(a);
sb = amount_msat_to_sat_round_down(b);
u64_param = fromwire_u64(&data, &size);
op = fromwire_u8(&data, &size) % OP_COUNT;
if (size < sizeof(f))
return;
memcpy(&f, data, sizeof(f));
data += sizeof(f);
switch (op) {
case OP_MSAT_ADD:
{
if (amount_msat_add(&out_ms, a, b))
assert(out_ms.millisatoshis == a.millisatoshis + b.millisatoshis);
break;
}
case OP_MSAT_SUB:
{
if (amount_msat_sub(&out_ms, a, b))
assert(out_ms.millisatoshis + b.millisatoshis == a.millisatoshis);
break;
}
case OP_MSAT_MUL:
{
if (amount_msat_mul(&out_ms, a, u64_param))
assert(out_ms.millisatoshis == a.millisatoshis * u64_param);
break;
}
case OP_MSAT_DIV:
{
if (u64_param == 0)
break;
out_ms = amount_msat_div(a, u64_param);
assert(out_ms.millisatoshis == a.millisatoshis / u64_param);
break;
}
case OP_MSAT_RATIO:
{
if (b.millisatoshis == 0)
break;
double ratio = amount_msat_ratio(a, b);
double expected = (double)a.millisatoshis / b.millisatoshis;
assert(ratio == expected);
break;
}
case OP_MSAT_RATIO_FLOOR:
{
if (b.millisatoshis == 0)
break;
u64 floor = amount_msat_ratio_floor(a, b);
assert(floor == a.millisatoshis / b.millisatoshis);
break;
}
case OP_MSAT_RATIO_CEIL:
{
if (b.millisatoshis == 0)
break;
u64 ceil = amount_msat_ratio_ceil(a, b);
u64 quotient = a.millisatoshis / b.millisatoshis;
u64 remainder = a.millisatoshis % b.millisatoshis;
assert(ceil == quotient + (remainder != 0));
break;
}
case OP_MSAT_SCALE:
{
if (amount_msat_scale(&out_ms, a, f)) {
double expect = (double)a.millisatoshis * f;
assert(fabs((double)out_ms.millisatoshis - expect) < 1.0);
}
break;
}
case OP_MSAT_ADD_SAT:
{
if (amount_msat_add_sat(&out_ms, a, sa))
assert(out_ms.millisatoshis == sa.satoshis * MSAT_PER_SAT + a.millisatoshis);
break;
}
case OP_MSAT_SUB_SAT:
{
if (amount_msat_sub_sat(&out_ms, a, sa))
assert(out_ms.millisatoshis + sa.satoshis * MSAT_PER_SAT == a.millisatoshis);
break;
}
case OP_SAT_ADD:
{
if (amount_sat_add(&out_s, sa, sb))
assert(out_s.satoshis == sa.satoshis + sb.satoshis);
break;
}
case OP_SAT_SUB:
{
if (amount_sat_sub(&out_s, sa, sb))
assert(out_s.satoshis + sb.satoshis == sa.satoshis);
break;
}
case OP_SAT_MUL:
{
if (amount_sat_mul(&out_s, sa, u64_param))
assert(out_s.satoshis == sa.satoshis * u64_param);
break;
}
case OP_SAT_DIV:
{
if (u64_param == 0)
break;
out_s = amount_sat_div(sa, u64_param);
assert(out_s.satoshis == sa.satoshis / u64_param);
break;
}
case OP_SAT_SCALE:
{
if (amount_sat_scale(&out_s, sa, f)) {
double expect = sa.satoshis * f;
assert(fabs((double)out_s.satoshis - expect) < 1.0);
}
break;
}
case OP_FEE:
{
if (amount_msat_fee(&out_ms, a, (u32)a.millisatoshis,
(u32)b.millisatoshis))
assert(out_ms.millisatoshis >= (u32)a.millisatoshis);
break;
}
case OP_ADD_FEE:
{
u32 fee_base = (u32)a.millisatoshis % MAX_FEE_BASE;
u32 fee_prop = (u32)b.millisatoshis % MAX_FEE_PROP;
struct amount_msat original = a;
struct amount_msat fee;
if (amount_msat_fee(&fee, original, fee_base, fee_prop)) {
struct amount_msat total;
assert(amount_msat_add(&total, original, fee));
assert(total.millisatoshis == original.millisatoshis + fee.millisatoshis);
}
break;
}
case OP_SUB_FEE:
{
u32 fee_base = (u32)a.millisatoshis % MAX_FEE_BASE;
u32 fee_prop = (u32)b.millisatoshis % MAX_FEE_PROP;
struct amount_msat input = a;
struct amount_msat output = amount_msat_sub_fee(input, fee_base, fee_prop);
struct amount_msat fee;
if (amount_msat_fee(&fee, output, fee_base, fee_prop)) {
struct amount_msat sum;
if (amount_msat_add(&sum, output, fee))
assert(amount_msat_less_eq(sum, input));
}
break;
}
case OP_TX_FEE:
{
u32 fee_per_kw = (u32)a.millisatoshis;
/* weights > 2^32 are not real tx and hence, discarded */
size_t weight = (size_t)(b.millisatoshis & UINT32_MAX);
struct amount_sat fee = amount_tx_fee(fee_per_kw, weight);
u64 expected = (fee_per_kw * weight) / MSAT_PER_SAT;
assert(fee.satoshis == expected);
break;
}
case OP_FEERATE:
{
struct amount_sat fee = amount_msat_to_sat_round_down(a);
size_t weight = (size_t)b.millisatoshis;
u32 feerate;
if (weight && amount_feerate(&feerate, fee, weight)) {
u64 expected = (fee.satoshis * MSAT_PER_SAT) / weight;
assert(feerate == expected);
}
break;
}
default:
assert(false && "Unknown operation!");
}
}