morphit/packages/asset-registry/scripts/dai-trade-only-smoke.ts

243 lines
10 KiB
TypeScript

#!/usr/bin/env tsx
/**
* dai-trade-only-smoke.
*
* Part 122 cp31 sentinel: DAI must be `canPayListingFee: false`
* AND `canBeTraded: true` in BOTH the canonical and frontend
* asset registries. If a future contributor toggles either
* value the wrong way, this smoke fails loudly.
*
* Memory #23 invariant pinned from two directions (same as
* usdt-trade-only-smoke + usdc-trade-only-smoke):
* - Canonical registry's `canPayListingFee: true → ticker ∈
* {BLURT, BTC, XMR}` rule means a future contributor
* flipping DAI's flag to true would fail the
* asset-registry-smoke first (good defence-in-depth).
* - This smoke is the DAI-specific sentinel: DAI must be
* trade-only. If the frontend's canBeUsedForListingFee
* drifts from the canonical's canPayListingFee, the user-
* facing form would offer DAI as a fee option while the
* indexer would reject it — confusing UX.
*
* Also asserts DAI carries the four supported networks
* (ERC-20, Polygon, Base, Arbitrum), defaultNetwork: null
* (forcing explicit choice every trade), and
* privacyWarningKey: 'dai_partly_centralized' (distinct
* from USDT/USDC's `*_centralized` class — see ADR-0029 §2
* for the rationale on the more-nuanced warning).
*
* DAI-specific scenarios (not in USDT/USDC smokes):
* - SPL is INTENTIONALLY NOT in supportedNetworks (ADR-0029
* §1: no canonical Maker-issued DAI on Solana)
* - TRC-20 similarly excluded
* - BEP-20 similarly excluded (Binance-Peg DAI is wrapped,
* not Maker-native, same rationale as USDC's BEP-20
* exclusion under ADR-0028)
* - decimals === 18 (EVM-standard, different from USDT/USDC's
* 6 — a future contributor adding any non-EVM DAI would
* need to confront the precision divergence first)
* - privacyWarningKey is the distinct `dai_partly_centralized`
* class, NOT lumped with `*_centralized` — gives DAI credit
* for the design choice (no admin freeze power) while being
* honest about the PSM/USDC backing dependency.
*/
import { ASSETS as CANONICAL } from '../src/index';
import { ASSETS as FRONTEND } from '../../../apps/web/src/lib/assets/registry';
let failed = 0;
let passed = 0;
function pass(name: string): void {
console.log(`${name}`);
passed++;
}
function fail(name: string, detail: string): void {
console.error(`${name}`);
console.error(` ${detail}`);
failed++;
}
console.log('\n── dai-trade-only smoke ──────────────────────────────\n');
// ── Scenario 1 — DAI is in the canonical registry ────────────────
const canonDai = CANONICAL.find((a) => a.ticker === 'DAI');
if (canonDai) {
pass('DAI is in canonical registry');
} else {
fail('DAI is in canonical registry', 'no DAI entry found in @morphit/asset-registry ASSETS');
}
// ── Scenario 2 — canonical DAI.canPayListingFee === false ────────
if (canonDai && canonDai.canPayListingFee === false) {
pass('canonical DAI.canPayListingFee === false (memory #23)');
} else {
fail(
'canonical DAI.canPayListingFee === false (memory #23)',
`DAI must be trade-only. fee_method enum is frozen at BLURT/BTC/XMR; DAI must not pay listing fees. Got canPayListingFee=${canonDai?.canPayListingFee}`
);
}
// ── Scenario 3 — canonical DAI.canBeTraded === true ──────────────
if (canonDai && canonDai.canBeTraded === true) {
pass('canonical DAI.canBeTraded === true');
} else {
fail(
'canonical DAI.canBeTraded === true',
`DAI must be tradable. Got canBeTraded=${canonDai?.canBeTraded}`
);
}
// ── Scenario 4 — canonical DAI supports 4 networks ───────────────
if (canonDai) {
const networks = [...canonDai.supportedNetworks].sort();
const expected = ['arbitrum', 'base', 'erc20', 'polygon'];
if (JSON.stringify(networks) === JSON.stringify(expected)) {
pass('canonical DAI.supportedNetworks = [erc20, polygon, base, arbitrum]');
} else {
fail(
'canonical DAI.supportedNetworks = [erc20, polygon, base, arbitrum]',
`got ${JSON.stringify(networks)}, expected ${JSON.stringify(expected)}`
);
}
}
// ── Scenario 5 — SPL is NOT in supportedNetworks (ADR-0029 §1) ───
// Sentinel against a future contributor adding wrapped/bridged DAI
// on Solana. No canonical Maker-issued native DAI on Solana
// exists; existing Solana DAI variants (Wormhole DAI, etc.) are
// wrapper-custodian dependencies that defeat DAI's
// decentralization rationale. If you're here adding SPL DAI,
// first read ADR-0029 §1; the rationale for the exclusion is the
// SAME rationale that distinguishes DAI from USDT/USDC.
if (canonDai && !canonDai.supportedNetworks.includes('spl')) {
pass('canonical DAI excludes spl (ADR-0029 §1 — no canonical Maker DAI on Solana)');
} else {
fail(
'canonical DAI excludes spl (ADR-0029 §1 — no canonical Maker DAI on Solana)',
'SPL DAI variants are wrapper-bridged (Wormhole, Allbridge), not Maker-native — would defeat DAI decentralization rationale. Read ADR-0029 §1 before adding.'
);
}
// ── Scenario 6 — TRC-20 is NOT in supportedNetworks ──────────────
if (canonDai && !canonDai.supportedNetworks.includes('trc20')) {
pass('canonical DAI excludes trc20 (no native Maker issuance on Tron)');
} else {
fail(
'canonical DAI excludes trc20 (no native Maker issuance on Tron)',
'TRC-20 DAI is wrapped, not Maker-native'
);
}
// ── Scenario 7 — BEP-20 is NOT in supportedNetworks ──────────────
// Binance-Peg DAI is wrapped, not Maker-native; same exclusion
// rationale as USDC's BEP-20 exclusion under ADR-0028 §1.
if (canonDai && !canonDai.supportedNetworks.includes('bep20')) {
pass('canonical DAI excludes bep20 (Binance-Peg wrapped, not Maker-native)');
} else {
fail(
'canonical DAI excludes bep20 (Binance-Peg wrapped, not Maker-native)',
'BEP-20 DAI is Binance-Peg. Wrapper-custodian dependency; same exclusion rationale as USDC BEP-20.'
);
}
// ── Scenario 8 — defaultNetwork is null (forces explicit choice) ─
// CRITICAL FOR DAI: all 4 supported networks share the EVM 0x[40
// hex] address format — visually identical. This is the highest
// cross-network address-confusion surface of any asset on Morphit.
if (canonDai && canonDai.defaultNetwork === null) {
pass('canonical DAI.defaultNetwork === null (no default — forces explicit choice)');
} else {
fail(
'canonical DAI.defaultNetwork === null',
`Cross-network sends lose funds. CRITICAL HERE: all 4 DAI networks share EVM 0x[40 hex] shape — picker is the ONLY disambiguator. Got ${canonDai?.defaultNetwork}`
);
}
// ── Scenario 9 — privacyWarningKey is DAI-specific class ─────────
// Sentinel for the design decision in ADR-0029 §2: DAI gets the
// distinct `dai_partly_centralized` warning class, NOT lumped
// with USDT/USDC's `*_centralized`. This reflects the real
// difference (no admin freeze power at the token contract) while
// being honest about the PSM/USDC backing dependency. If a
// future contributor "simplifies" by lumping DAI into the
// `*_centralized` class, this smoke fires.
if (canonDai && canonDai.privacyWarningKey === 'dai_partly_centralized') {
pass('canonical DAI.privacyWarningKey === "dai_partly_centralized" (distinct from *_centralized)');
} else {
fail(
'canonical DAI.privacyWarningKey === "dai_partly_centralized"',
`DAI is not freeze-power-equivalent to USDT/USDC. ADR-0029 §2 requires a distinct warning class that gives DAI credit for the design choice while being honest about PSM/USDC backing. Got ${canonDai?.privacyWarningKey}`
);
}
// ── Scenario 10 — decimals === 18 (EVM standard) ─────────────────
// DAI uses 18-decimal precision on every supported network
// (EVM-standard), different from USDT/USDC's 6. Affects the
// underlying token math but not the user-visible amount-jitter
// resolution (jitter clamps to 6-decimal display precision).
if (canonDai && canonDai.decimals === 18) {
pass('canonical DAI.decimals === 18 (EVM standard across all 4 networks)');
} else {
fail(
'canonical DAI.decimals === 18 (EVM standard across all 4 networks)',
`Got decimals=${canonDai?.decimals}. All 4 supported DAI networks are EVM-family with 18-decimal precision.`
);
}
// ── Scenario 11 — frontend DAI entry mirrors canonical ───────────
const feDai = FRONTEND.find((a) => a.ticker === 'dai');
if (feDai) {
pass('frontend DAI entry exists');
} else {
fail('frontend DAI entry exists', 'no dai entry in apps/web/src/lib/assets/registry.ts');
}
// ── Scenario 12 — frontend canBeUsedForListingFee === false ──────
if (feDai && feDai.canBeUsedForListingFee === false) {
pass('frontend DAI.canBeUsedForListingFee === false (mirrors canonical)');
} else {
fail(
'frontend DAI.canBeUsedForListingFee === false',
`Frontend must mirror canonical. Drift = user-facing form offers DAI as a fee option while indexer rejects it. Got canBeUsedForListingFee=${feDai?.canBeUsedForListingFee}`
);
}
// ── Scenario 13 — frontend DAI.canBeTraded === true ──────────────
if (feDai && feDai.canBeTraded === true) {
pass('frontend DAI.canBeTraded === true');
} else {
fail(
'frontend DAI.canBeTraded === true',
`Frontend DAI must be tradable. Got canBeTraded=${feDai?.canBeTraded}`
);
}
// ── Scenario 14 — frontend DAI.defaultNetwork === null ───────────
if (feDai && feDai.defaultNetwork === null) {
pass('frontend DAI.defaultNetwork === null');
} else {
fail(
'frontend DAI.defaultNetwork === null',
`Got ${feDai?.defaultNetwork}`
);
}
// ── Scenario 15 — frontend DAI.privacyWarningKey set ─────────────
if (feDai && feDai.privacyWarningKey === 'dai_partly_centralized') {
pass('frontend DAI.privacyWarningKey === "dai_partly_centralized"');
} else {
fail(
'frontend DAI.privacyWarningKey === "dai_partly_centralized"',
`Got ${feDai?.privacyWarningKey}`
);
}
const total = passed + failed;
console.log(`\n${passed} passed, ${failed} failed (${total} total)`);
if (failed > 0) {
console.error('\ndai-trade-only smoke FAILED');
process.exit(1);
}
console.log(`✓ all ${total} dai-trade-only scenarios passed`);