morphit/apps/indexer/scripts/treasury-repin-smoke.ts

257 lines
11 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Smoke — treasury auto-re-pin decision logic (cp372).
*
* Exercises the pure decideRepin() brain: drift detection in both
* directions, the re-pin threshold, the canonical fresh-amount
* computation, and — most importantly for a money path — the
* failsafes: feed-down skips, sanity-ceiling rejection, and the
* no-current-pin bootstrap case.
*
* Run: npx tsx --tsconfig ../../tsconfig.smoke.json scripts/treasury-repin-smoke.ts
*/
import {
decideRepin,
buildRepinnedTreasury,
parseReleaseTreasury,
DEFAULT_REPIN_DRIFT_THRESHOLD,
type PinnedAmounts,
type RepinPrices
} from '../src/lib/treasuryRepin.ts';
import {
LISTING_FEE_USD,
FEE_FALLBACK,
listingFeeSatoshis,
listingFeePiconero,
listingFeeBlurtBase,
FEE_PRICE_TOLERANCE
} from '@morphit/asset-registry';
let passed = 0;
let failed = 0;
function scenario(name: string, fn: () => void): void {
try {
fn();
passed++;
console.log(`${name}`);
} catch (err) {
failed++;
console.log(`${name}`);
console.log(` ${err instanceof Error ? err.message : String(err)}`);
}
}
function assert(cond: boolean, msg: string): void {
if (!cond) throw new Error(msg);
}
// Reference prices at which the canonical fallbacks are exactly on
// target ($0.25 / $0.25 / $0.125).
const REF: RepinPrices = { btcUsd: 60_000, xmrUsd: 320, blurtUsd: 0.002 };
// Pinned amounts that are exactly on-target at the reference prices.
const ON_TARGET: PinnedAmounts = {
btcSatoshis: FEE_FALLBACK.satoshis, // 417
xmrPiconero: FEE_FALLBACK.piconero, // 781250000n
blurtBase: FEE_FALLBACK.blurtBase // 62.5
};
scenario('threshold sits inside the verifier tolerance band (no mid-drift rejection)', () => {
assert(DEFAULT_REPIN_DRIFT_THRESHOLD < FEE_PRICE_TOLERANCE, 'threshold must be < tolerance');
});
scenario('on-target pins at reference prices → no re-pin', () => {
const d = decideRepin(ON_TARGET, REF);
assert(d.shouldRepin === false, 'should not re-pin when on target');
assert(d.btc.due === false && d.xmr.due === false && d.blurt.due === false, 'no asset due');
});
scenario('small drift within threshold → no re-pin', () => {
// +5% BTC price: pinned USD value drifts ~5% (< 10% threshold).
const d = decideRepin(ON_TARGET, { ...REF, btcUsd: 63_000 });
assert(d.btc.drift !== null && d.btc.drift > 0.04 && d.btc.drift < 0.06, 'btc drift ≈5%');
assert(d.btc.due === false, 'btc not due at 5%');
assert(d.shouldRepin === false, 'no re-pin overall');
});
scenario('BTC appreciated past threshold → re-pin due, fresh amount canonical', () => {
// +20% BTC: pinned 417 sats now worth ~$0.30 → drift ~20% > 10%.
const btcUsd = 72_000;
const d = decideRepin(ON_TARGET, { ...REF, btcUsd });
assert(d.btc.due === true, 'btc due at 20% drift');
assert(d.shouldRepin === true, 'should re-pin');
assert(d.btc.computed === listingFeeSatoshis(btcUsd), 'fresh sats are canonical');
// Fresh amount is fewer sats than the stale pin (BTC went up).
assert((d.btc.computed as number) < FEE_FALLBACK.satoshis, 'fewer sats after appreciation');
});
scenario('XMR depreciated past threshold → re-pin due (drift works downward too)', () => {
// 20% XMR: pinned piconero now worth ~$0.20 → drift ~20%.
const xmrUsd = 256;
const d = decideRepin(ON_TARGET, { ...REF, xmrUsd });
assert(d.xmr.due === true, 'xmr due at 20% drift');
assert(d.xmr.computed === listingFeePiconero(xmrUsd), 'fresh piconero canonical');
assert((d.xmr.computed as bigint) > FEE_FALLBACK.piconero, 'more piconero after depreciation');
});
scenario('BLURT appreciated past threshold → re-pin due', () => {
const blurtUsd = 0.0025; // +25%
const d = decideRepin(ON_TARGET, { ...REF, blurtUsd });
assert(d.blurt.due === true, 'blurt due');
assert(d.blurt.computed === listingFeeBlurtBase(blurtUsd), 'fresh base canonical');
});
// ── FAILSAFES ──────────────────────────────────────────────────
scenario('FAILSAFE: BTC feed down (null price) → never re-pin BTC', () => {
const d = decideRepin(ON_TARGET, { ...REF, btcUsd: null });
assert(d.btc.due === false, 'btc not due with no price');
assert(d.btc.computed === null, 'no computed amount');
assert(/unavailable/.test(d.btc.note), 'note explains skip');
});
scenario('FAILSAFE: zero / negative price → skipped, not re-pinned', () => {
const d0 = decideRepin(ON_TARGET, { ...REF, xmrUsd: 0 });
assert(d0.xmr.due === false && d0.xmr.computed === null, 'zero price skipped');
const dNeg = decideRepin(ON_TARGET, { ...REF, blurtUsd: -1 });
assert(dNeg.blurt.due === false && dNeg.blurt.computed === null, 'negative price skipped');
});
scenario('FAILSAFE: all feeds down → shouldRepin false', () => {
const d = decideRepin(ON_TARGET, { btcUsd: null, xmrUsd: null, blurtUsd: null });
assert(d.shouldRepin === false, 'nothing to do with no prices');
});
scenario('FAILSAFE: absurdly low BTC price → computed amount over ceiling → skipped', () => {
// $0.01 BTC would demand 0.25/0.01 × 1e8 = 2.5e9 sats — over the
// 1e11 ceiling? No, under. Use a price that pushes over 1e11:
// sats = 0.25/price × 1e8 > 1e11 ⟺ price < 0.25e-3 = 0.00025.
const d = decideRepin(ON_TARGET, { ...REF, btcUsd: 0.0001 });
assert(d.btc.due === false, 'absurd amount not proposed');
assert(d.btc.computed === null, 'computed suppressed');
assert(/ceiling/.test(d.btc.note), 'note explains ceiling rejection');
});
scenario('FAILSAFE: a bad price on one asset does not block re-pin of a healthy one', () => {
// BTC feed down, but XMR drifted 20% → XMR still re-pins.
const d = decideRepin(ON_TARGET, { btcUsd: null, xmrUsd: 256, blurtUsd: 0.002 });
assert(d.btc.due === false, 'btc skipped (no price)');
assert(d.xmr.due === true, 'xmr still due');
assert(d.shouldRepin === true, 'healthy asset re-pins independently');
});
scenario('BOOTSTRAP: no current pin → due, proposes canonical (first pin)', () => {
const empty: PinnedAmounts = { btcSatoshis: null, xmrPiconero: null, blurtBase: null };
const d = decideRepin(empty, REF);
assert(d.btc.due && d.xmr.due && d.blurt.due, 'all due for first pin');
assert(d.btc.computed === listingFeeSatoshis(REF.btcUsd!), 'canonical btc');
assert(d.blurt.computed === listingFeeBlurtBase(REF.blurtUsd!), 'canonical blurt');
assert(d.btc.drift === null, 'no drift without a prior pin');
});
scenario('custom threshold is honored', () => {
// 8% drift: due under a 5% threshold, not under 10%.
const eightPct = 64_800; // +8% BTC
assert(decideRepin(ON_TARGET, { ...REF, btcUsd: eightPct }, 0.05).btc.due === true, 'due @5%');
assert(decideRepin(ON_TARGET, { ...REF, btcUsd: eightPct }, 0.1).btc.due === false, 'not due @10%');
});
scenario('re-pinning resets drift to ~0 (computed amount is on-target)', () => {
const btcUsd = 72_000;
const d = decideRepin(ON_TARGET, { ...REF, btcUsd });
const freshSats = d.btc.computed as number;
// The fresh amount, valued at the same price, is back on target.
const freshUsdValue = (freshSats / 1e8) * btcUsd;
assert(Math.abs(freshUsdValue - LISTING_FEE_USD.btc) < 0.005, 'fresh amount on target');
});
// ── buildRepinnedTreasury (the new pin a re-pin would broadcast) ──
const ADDRS = {
btcAddress: 'bc1qexampleexampleexampleexampleexampleex',
xmrAddress: '4' + 'A'.repeat(94)
};
scenario('buildRepinnedTreasury: all due → fresh canonical amounts, addresses preserved', () => {
const prices = { ...REF, btcUsd: 72_000, xmrUsd: 256, blurtUsd: 0.0025 };
const d = decideRepin(ON_TARGET, prices);
const t = buildRepinnedTreasury(d, ADDRS, ON_TARGET);
assert(t.btc?.address === ADDRS.btcAddress, 'btc address preserved');
assert(t.btc?.satoshis === listingFeeSatoshis(72_000), 'btc fresh canonical');
assert(t.xmr?.piconero === listingFeePiconero(256)!.toString(), 'xmr fresh canonical');
assert(t.blurt?.base === listingFeeBlurtBase(0.0025), 'blurt fresh canonical');
});
scenario('buildRepinnedTreasury: feed-down asset keeps its CURRENT amount (never zeroed)', () => {
// BTC feed down → BTC must keep its current pin, not drop.
const prices = { btcUsd: null, xmrUsd: 256, blurtUsd: 0.002 };
const d = decideRepin(ON_TARGET, prices);
const t = buildRepinnedTreasury(d, ADDRS, ON_TARGET);
assert(t.btc?.satoshis === FEE_FALLBACK.satoshis, 'btc held at current pin');
assert(t.xmr?.piconero === listingFeePiconero(256)!.toString(), 'xmr re-pinned fresh');
});
scenario('buildRepinnedTreasury: no BTC/XMR address → those null, blurt still present', () => {
const prices = { ...REF, blurtUsd: 0.0025 };
const d = decideRepin(ON_TARGET, prices);
const t = buildRepinnedTreasury(d, { btcAddress: null, xmrAddress: null }, ON_TARGET);
assert(t.btc === null && t.xmr === null, 'no address → null');
assert(t.blurt?.base === listingFeeBlurtBase(0.0025), 'blurt pinned without address');
});
scenario('buildRepinnedTreasury: no blurt base anywhere → blurt omitted (legacy shape)', () => {
const noBlurt: PinnedAmounts = { ...ON_TARGET, blurtBase: null };
const d = decideRepin(noBlurt, { ...REF, blurtUsd: null }); // blurt feed down + no current
const t = buildRepinnedTreasury(d, ADDRS, noBlurt);
assert(!('blurt' in t) || t.blurt === undefined || t.blurt === null, 'blurt omitted');
});
// ── parseReleaseTreasury (the read side of the actuator) ──
scenario('parseReleaseTreasury: full block → addresses + pinned amounts', () => {
const p = parseReleaseTreasury({
btc: { address: ADDRS.btcAddress, satoshis: 417 },
xmr: { address: ADDRS.xmrAddress, piconero: '781250000' },
blurt: { base: 62.5 }
});
assert(p.addresses.btcAddress === ADDRS.btcAddress, 'btc addr');
assert(p.pinned.btcSatoshis === 417, 'btc sats');
assert(p.pinned.xmrPiconero === 781250000n, 'xmr piconero as bigint');
assert(p.pinned.blurtBase === 62.5, 'blurt base');
});
scenario('parseReleaseTreasury: legacy block (no blurt) → blurtBase null', () => {
const p = parseReleaseTreasury({
btc: { address: ADDRS.btcAddress, satoshis: 417 },
xmr: null
});
assert(p.pinned.blurtBase === null, 'no blurt pin');
assert(p.pinned.xmrPiconero === null && p.addresses.xmrAddress === null, 'xmr absent');
});
scenario('parseReleaseTreasury: garbage / null → all null (no throw)', () => {
for (const junk of [null, undefined, 42, 'x', [], { btc: 'nope', xmr: 5, blurt: [] }]) {
const p = parseReleaseTreasury(junk);
assert(p.pinned.btcSatoshis === null && p.pinned.blurtBase === null, `junk ${JSON.stringify(junk)} → null`);
}
});
scenario('parse → decide → build round-trips an on-target legacy pin into a BLURT-bearing one', () => {
// A legacy chain-pin (BTC/XMR only) + a BLURT feed → first BLURT
// pin gets proposed and built, addresses preserved.
const parsed = parseReleaseTreasury({
btc: { address: ADDRS.btcAddress, satoshis: 417 },
xmr: { address: ADDRS.xmrAddress, piconero: '781250000' }
});
const d = decideRepin(parsed.pinned, REF);
assert(d.blurt.due === true, 'blurt first-pin due (no current base)');
const t = buildRepinnedTreasury(d, parsed.addresses, parsed.pinned);
assert(t.blurt?.base === listingFeeBlurtBase(REF.blurtUsd!), 'blurt now pinned canonical');
assert(t.btc?.address === ADDRS.btcAddress, 'btc address preserved through round-trip');
});
if (failed > 0) {
console.log(`\n✗ ${failed}/${passed + failed} treasury-repin scenarios failed`);
process.exit(1);
}
console.log(`\n✓ all ${passed} treasury-repin scenarios passed`);