Files
wikideck/tests/xmr-amount.test.ts
2026-10-20 13:00:08 -04:00

113 lines
6.0 KiB
TypeScript
Executable File

import test from 'node:test';
import assert from 'node:assert/strict';
import { MAX_XMR_AMOUNT, XMR_ATOMIC_UNITS, XMR_RPC_UINT64_MAX, parseXmr, formatXmr,
parseAtomicXmr, atomicXmrString, assertXmrRpcAmount, xmrCommission } from '../shared/xmr-amount.ts';
import { allocateXmrSale, assessXmrSaleBudget } from '../shared/xmr-pricing.ts';
import { atomicXmr, fiatCents } from '../server/market/xmr.ts';
import { xmrString, mockQuote } from '../server/market/payments.ts';
test('exact XMR decimal input preserves legacy bounds and canonicalizes without floats', () => {
for (const [input, expected] of [['0', 0n], ['0.000000000001', 1n], ['1.2', 1_200_000_000_000n],
['9999999999.999999999999', MAX_XMR_AMOUNT]] as const) {
assert.equal(parseXmr(input), expected);
assert.equal(atomicXmr(input), expected);
assert.equal(parseXmr(formatXmr(expected)), expected);
assert.equal(xmrString(expected), formatXmr(expected));
}
assert.equal(formatXmr(0n), '0.000000000000');
assert.equal(formatXmr(1n), '0.000000000001');
assert.equal(formatXmr(XMR_ATOMIC_UNITS), '1.000000000000');
for (const bad of ['', ' ', ' 1', '1 ', '\n1', '1\n', '+1', '-0', '-1', '01', '.1', '1.',
'1e-12', '1_000', '1,000', 'NaN', 'Infinity', '12', '0.0000000000001', '10000000000',
'9'.repeat(100_000), 1, 1n, null, {}, ['1']] as unknown[]) {
assert.throws(() => parseXmr(bad as string));
assert.throws(() => atomicXmr(bad as string));
}
});
test('atomic wire strings reject alternate spellings, coercion and unsafe numbers', () => {
for (const value of [0n, 1n, BigInt(Number.MAX_SAFE_INTEGER) + 2n, XMR_RPC_UINT64_MAX, MAX_XMR_AMOUNT]) {
const wire = atomicXmrString(value);
assert.equal(parseAtomicXmr(JSON.parse(JSON.stringify(wire))), value);
}
for (const value of ['', '00', '01', '+1', '-1', '1.0', '1e12', '1\n', ' 1',
(MAX_XMR_AMOUNT + 1n).toString(), '9'.repeat(100_000), 1, null, {}]) {
assert.throws(() => parseAtomicXmr(value as string));
}
for (const bad of [-1n, MAX_XMR_AMOUNT + 1n, 1, null] as unknown[]) {
assert.throws(() => formatXmr(bad as bigint));
assert.throws(() => atomicXmrString(bad as bigint));
}
assert.doesNotThrow(() => assertXmrRpcAmount(XMR_RPC_UINT64_MAX));
assert.throws(() => assertXmrRpcAmount(XMR_RPC_UINT64_MAX + 1n));
});
test('legacy processor USD parser also requires absolute end-of-input', () => {
assert.equal(fiatCents('0'), 0);
assert.equal(fiatCents('1.2'), 120);
assert.equal(fiatCents('9999999.99'), 999999999);
for (const value of ['1\n', '1\r', '1\r\n', '1\u2028', '1\u2029', '1.00\n', '01', '1e2',
'10000000', '0.001', '9'.repeat(100_000), 1, null]) {
assert.throws(() => fiatCents(value as string));
}
});
test('tiny prices are representable and commissions round independently without a minimum charge', () => {
assert.deepEqual(allocateXmrSale(1n, 5000), { gross: 1n, hosting: 0n, author: 0n, sellerBeforeNetworkCosts: 1n });
assert.equal(allocateXmrSale(99n, 0).author, 0n);
assert.equal(allocateXmrSale(100n, 0).author, 1n);
assert.deepEqual(allocateXmrSale(199n, 250), { gross: 199n, hosting: 4n, author: 1n, sellerBeforeNetworkCosts: 194n });
assert.throws(() => allocateXmrSale(0n, 0));
for (const bad of [-1, 5001, 0.5, NaN, Infinity, '100', null] as unknown[]) {
assert.throws(() => allocateXmrSale(1n, bad as number));
}
for (const bad of [-1, 10001, 0.5, NaN]) assert.throws(() => xmrCommission(1n, bad));
});
test('seller network budget preflight distinguishes positive net, exact exhaustion and fee spikes', () => {
const gross = parseXmr('1'), available = allocateXmrSale(gross, 250).sellerBeforeNetworkCosts;
const good = assessXmrSaleBudget(gross, 250, available - 1n);
assert.equal(good.viable, true); assert.equal(good.sellerMinimumNet, 1n); assert.equal(good.fundingShortfall, 0n);
for (const extra of [0n, 1n, 1_000_000n]) {
const blocked = assessXmrSaleBudget(gross, 250, available + extra);
assert.equal(blocked.viable, false); assert.equal(blocked.sellerMinimumNet, 0n);
assert.equal(blocked.fundingShortfall, extra + 1n);
}
assert.equal(assessXmrSaleBudget(1n, 0, 0n).viable, true);
assert.equal(assessXmrSaleBudget(1n, 0, 1n).viable, false);
assert.throws(() => assessXmrSaleBudget(gross, 0, -1n));
assert.throws(() => assessXmrSaleBudget(gross, 0, MAX_XMR_AMOUNT + 1n));
});
test('deterministic generated amounts conserve value, bound rounding and round-trip exactly', () => {
let seed = 0x584d52n;
for (let i = 0; i < 4000; i++) {
seed = (seed * 6364136223846793005n + 1442695040888963407n) % (1n << 96n);
const gross = seed % MAX_XMR_AMOUNT + 1n, bps = i % 5001;
assert.equal(parseXmr(formatXmr(gross)), gross);
assert.equal(parseAtomicXmr(atomicXmrString(gross)), gross);
const a = allocateXmrSale(gross, bps);
assert.equal(a.hosting + a.author + a.sellerBeforeNetworkCosts, gross);
for (const [fee, rate] of [[a.hosting, BigInt(bps)], [a.author, 100n]]) {
assert.ok(fee * 10000n <= gross * rate);
assert.ok((fee + 1n) * 10000n > gross * rate);
}
const budget = seed % (a.sellerBeforeNetworkCosts + 2n);
const q = assessXmrSaleBudget(gross, bps, budget);
assert.equal(q.viable, a.sellerBeforeNetworkCosts > budget);
if (q.viable) assert.equal(q.hosting + q.author + budget + q.sellerMinimumNet, gross);
}
});
test('mock USD caller retains its original independently rounded amounts', () => {
for (const cents of [1, 19, 1500, 100_000_000]) for (const bps of [0, 250, 5000]) {
const quote = mockQuote(cents, bps, 19999);
const gross = (BigInt(cents) * XMR_ATOMIC_UNITS + 19998n) / 19999n;
assert.equal(parseXmr(quote.invoice.amountXmr), gross);
assert.equal(parseXmr(quote.feeXmr), gross * BigInt(bps) / 10000n);
assert.equal(parseXmr(quote.additionalFee.amountXmr), gross / 100n);
assert.equal(parseXmr(quote.sellerNetXmr), gross - gross * BigInt(bps) / 10000n - gross / 100n);
assert.equal(quote.invoice.address, null);
}
});