deviant/scripts/chipnet-mut-run.mjs

95 lines
4.2 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
/**
* chipnet-mut-run.mjs — orchestrator for the post-deployment-mutability test.
*
* Runs the full scenario the user described:
* 1) Founder issues MBA at fee 1%, rate 1000 (mut-deploy)
* 2) User B claims (mut-claim)
* 3) User B redeems (mut-redeem)
* 4) Local analysis — show no admin path exists (mut-analyze)
* 5) "Fee change to 2%" → fresh deployment (mut-deploy)
* 6) User B claims at 2% (mut-claim)
* 7) "Fee changes" to 3%, 4%, 50%, back to 1% (mut-analyze, local)
* 8) "Rate changes" to 100, 10000 (mut-analyze, local)
* 9) Final accounting (mut-analyze)
*
* Steps 7–9 are local-only because the empirical truth is identical to
* step 5: each "change" is structurally a fresh contract. Broadcasting
* 7 more identical-shape geneses would burn chipnet fees without adding
* new information. The analyze script reports the would-be addresses
* and validateMetadata verdicts for each.
*
* Each broadcast step is spawned as a sub-process — same code path as
* a manual `npm run chipnet:mut-deploy` etc. State is shared via
* `ChipnetTests/.chipnet-mut-state.json`. Failures in any step abort the run.
*/
import { spawn } from 'node:child_process';
import { join } from 'node:path';
const ROOT = join(import.meta.dirname, '..');
function runNode(script, env = {}) {
return new Promise((resolve, reject) => {
const args = ['--import', 'tsx', join(ROOT, 'scripts', script)];
const child = spawn(process.execPath, args, {
cwd: ROOT,
env: { ...process.env, ...env },
stdio: 'inherit',
});
child.on('exit', (code) => {
if (code === 0) resolve();
else reject(new Error(`${script} exited ${code}`));
});
child.on('error', reject);
});
}
function divider(ch = '#') { console.log(''); console.log(ch.repeat(80)); }
async function main() {
divider();
console.log('## MUTABILITY TEST — orchestrator');
console.log('## Founder = Wallet C (default), User = Wallet B (default)');
console.log('## Demonstrates that post-deployment fee/rate changes are structurally');
console.log('## impossible on the canonical contract.');
divider();
// Step 1: deploy MBA at 1%
console.log('\n### Step 1/9 — Genesis MBA@1% (rate=1000, feeBps=100)\n');
await runNode('chipnet-mut-deploy.mjs', { LABEL: 'mba-1pc', FEE_BPS: '100', RATE: '1000', FOUNDER: 'c' });
// Step 2: B claims 55 units (gross=55,000, fee=550 — clears 546 dust)
console.log('\n### Step 2/9 — User B claims 55 units from MBA@1%\n');
await runNode('chipnet-mut-claim.mjs', { LABEL: 'mba-1pc', USER: 'b', AMOUNT: '55' });
// Step 3: B redeems (P2PKH spend of FT UTXO, FT burned, BCH released)
console.log('\n### Step 3/9 — User B redeems FT UTXO\n');
await runNode('chipnet-mut-redeem.mjs', { LABEL: 'mba-1pc', USER: 'b' });
// Step 4: analyze — show no admin path
console.log('\n### Step 4/9 — Local analysis after baseline\n');
await runNode('chipnet-mut-analyze.mjs');
// Step 5: deploy MBA@2% (the "fee change" workaround — fresh contract)
console.log('\n### Step 5/9 — "Fee change to 2%" = fresh genesis (rate=1000, feeBps=200)\n');
await runNode('chipnet-mut-deploy.mjs', { LABEL: 'mba-2pc', FEE_BPS: '200', RATE: '1000', FOUNDER: 'c' });
// Step 6: B claims 28 units from MBA@2% (gross=28,000, fee=560)
console.log('\n### Step 6/9 — User B "continues to transact" — actually claims from MBA@2% (a different token)\n');
await runNode('chipnet-mut-claim.mjs', { LABEL: 'mba-2pc', USER: 'b', AMOUNT: '28' });
// Step 7+: rest is local — derive what each would-be change looks like
console.log('\n### Step 7-9/9 — Local proof for 3%, 4%, 50%, back-to-1%, rate=100, rate=10000 + final accounting\n');
await runNode('chipnet-mut-analyze.mjs');
divider();
console.log('## ORCHESTRATOR COMPLETE');
console.log('## See .chipnet-mut-state.json for the full event log.');
divider();
}
main().catch((err) => {
console.error('ORCHESTRATOR FAILED:', err?.message ?? err);
process.exit(1);
});