113 lines
6.0 KiB
TypeScript
Executable File
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);
|
|
}
|
|
});
|