import assert from "node:assert/strict"; import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { extractAbi, renderContractsModule, syncArtifacts } from "./sync-web-artifacts.mjs"; import { publishManifest } from "./publish-web-manifest.mjs"; const address = "0x5FbDB2315678afecb367f032d93F642f64180aa3"; const owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; const bankAbi = [ ...["contractVersion", "paused", "asset", "owner", "totalLiabilities", "balanceOf"].map((name) => ({ type: "function", name, inputs: [], outputs: [], stateMutability: "view" })), ...["Deposited", "Withdrawn", "Paused", "Unpaused", "OwnershipTransferred", "Upgraded"].map((name) => ({ type: "event", name, inputs: [], anonymous: false })), ]; const tokenAbi = [{ type: "function", name: "balanceOf", inputs: [{ name: "account", type: "address" }], outputs: [{ type: "uint256" }], stateMutability: "view" }]; const manifest = { schemaVersion: 1, network: "anvil", chainId: 31337, deploymentBlock: 3, rpcUrl: "http://127.0.0.1:8545", token: address, proxy: address, implementation: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", owner, actors: [{ label: "owner", address: owner }], }; async function withFixture(run) { const root = await mkdtemp(join(tmpdir(), "uups-artifacts-")); try { await run(root); } finally { await rm(root, { recursive: true, force: true }); } } async function expectRejects(action, pattern) { await assert.rejects(action, pattern); } await withFixture(async (root) => { const bank = join(root, "BankV1.json"); const token = join(root, "MockUSDC.json"); const output = join(root, "contracts.ts"); // Catches a production bridge that silently produces an ABI module from incomplete artifacts. await expectRejects(() => syncArtifacts({ bankArtifactPath: bank, tokenArtifactPath: token, outputPath: output }), /BankV1 artifact/i); await writeFile(bank, JSON.stringify({ abi: bankAbi })); await expectRejects(() => syncArtifacts({ bankArtifactPath: bank, tokenArtifactPath: token, outputPath: output }), /MockUSDC artifact/i); await writeFile(token, JSON.stringify({ abi: tokenAbi })); // Catches a bridge that exports an ABI missing a V1 contract function or event. assert.throws(() => extractAbi({ abi: bankAbi.filter((entry) => entry.name !== "Withdrawn") }, "BankV1"), /Withdrawn/); const rendered = renderContractsModule(extractAbi({ abi: bankAbi }, "BankV1"), extractAbi({ abi: tokenAbi }, "MockUSDC")); assert.match(rendered, /export const bankV1Abi = .* as const;/s); assert.match(rendered, /export const mockUsdcAbi = .* as const;/s); assert.doesNotMatch(rendered, /bankV2Abi|BankV2/); // Catches a bridge that requires an active manifest or reads V2 as part of ABI generation. await syncArtifacts({ bankArtifactPath: bank, tokenArtifactPath: token, outputPath: output }); assert.equal(await readFile(output, "utf8"), rendered); await writeFile(output, "stale\n"); await expectRejects(() => syncArtifacts({ bankArtifactPath: bank, tokenArtifactPath: token, outputPath: output, check: true }), /stale/i); }); await withFixture(async (root) => { const activePath = join(root, "active.json"); const outputPath = join(root, "deployment.json"); await writeFile(activePath, JSON.stringify(manifest)); // Catches a publisher that copies pending, secret-bearing, or malformed live state into the browser bundle. await publishManifest({ activePath, outputPath }); assert.deepEqual(JSON.parse(await readFile(outputPath, "utf8")), manifest); for (const invalid of [ { ...manifest, deploymentBlock: 0 }, { ...manifest, rpcUrl: "https://token@example.test" }, { ...manifest, rpcUrl: "https://example.test/?secret=value" }, { ...manifest, privateKey: "not-public" }, { ...manifest, proxy: "0x0000000000000000000000000000000000000000" }, ]) { await writeFile(activePath, JSON.stringify(invalid)); await expectRejects(() => publishManifest({ activePath, outputPath }), /manifest|deployment|credential|secret|address/i); } }); console.log("artifact bridge tests passed");