@fluffylabs/anan-as 1.1.3-cc868cf → 1.1.3-e01d5ea

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +65 -7
  2. package/dist/bin/build-inline.js +70 -0
  3. package/dist/bin/fuzz.js +172 -0
  4. package/dist/bin/index.js +220 -0
  5. package/dist/bin/test-gas-cost.js +57 -0
  6. package/dist/bin/test-json.js +135 -0
  7. package/dist/bin/test-w3f.js +119 -0
  8. package/dist/bin/test.js +3 -0
  9. package/dist/build/compiler-inline.d.ts +11 -0
  10. package/dist/build/compiler-inline.js +22 -0
  11. package/dist/build/compiler.d.ts +26 -0
  12. package/dist/build/compiler.js +76 -0
  13. package/dist/build/compiler.wasm +0 -0
  14. package/{build → dist/build}/debug-inline.d.ts +1 -1
  15. package/dist/build/debug-inline.js +22 -0
  16. package/{build → dist/build}/debug-raw-inline.d.ts +1 -1
  17. package/dist/build/debug-raw-inline.js +22 -0
  18. package/{build → dist/build}/debug-raw.d.ts +100 -39
  19. package/{build → dist/build}/debug-raw.js +88 -37
  20. package/dist/build/debug-raw.wasm +0 -0
  21. package/{build → dist/build}/debug.d.ts +100 -39
  22. package/{build → dist/build}/debug.js +94 -39
  23. package/dist/build/debug.wasm +0 -0
  24. package/{build → dist/build}/release-inline.d.ts +1 -1
  25. package/dist/build/release-inline.js +22 -0
  26. package/{build → dist/build}/release-mini-inline.d.ts +1 -1
  27. package/dist/build/release-mini-inline.js +22 -0
  28. package/{build → dist/build}/release-mini.d.ts +100 -39
  29. package/{build → dist/build}/release-mini.js +94 -39
  30. package/dist/build/release-mini.wasm +0 -0
  31. package/{build → dist/build}/release-stub-inline.d.ts +1 -1
  32. package/dist/build/release-stub-inline.js +22 -0
  33. package/{build → dist/build}/release-stub.d.ts +100 -39
  34. package/{build → dist/build}/release-stub.js +94 -39
  35. package/dist/build/release-stub.wasm +0 -0
  36. package/{build → dist/build}/release.d.ts +100 -39
  37. package/{build → dist/build}/release.js +94 -39
  38. package/dist/build/release.wasm +0 -0
  39. package/{build → dist/build}/test-inline.d.ts +1 -1
  40. package/dist/build/test-inline.js +22 -0
  41. package/dist/build/test.wasm +0 -0
  42. package/dist/web/bump-version.js +7 -0
  43. package/package.json +46 -37
  44. package/build/debug-inline.js +0 -22
  45. package/build/debug-raw-inline.js +0 -22
  46. package/build/debug-raw.wasm +0 -0
  47. package/build/debug.wasm +0 -0
  48. package/build/release-inline.js +0 -22
  49. package/build/release-mini-inline.js +0 -22
  50. package/build/release-mini.wasm +0 -0
  51. package/build/release-stub-inline.js +0 -22
  52. package/build/release-stub.wasm +0 -0
  53. package/build/release.wasm +0 -0
  54. package/build/test-inline.js +0 -22
  55. package/build/test.wasm +0 -0
  56. /package/{build → dist/build}/test.d.ts +0 -0
  57. /package/{build → dist/build}/test.js +0 -0
package/README.md CHANGED
@@ -2,14 +2,11 @@
2
2
 
3
3
  AssemblyScript implementation of the JAM PVM (64-bit).
4
4
 
5
- [Demo](https://todr.me/anan-as)
5
+ Gray Paper compatibility:
6
6
 
7
- ## Todo
7
+ - [x] 0.7.2
8
8
 
9
- - [x] Memory
10
- - [x] [JAM tests](https://github.com/w3f/jamtestvectors/pull/3) compatibility
11
- - [x] 64-bit & new instructions ([GrayPaper v0.5.0](https://graypaper.fluffylabs.dev))
12
- - [x] GP 0.5.4 compatibility (ZBB extensions)
9
+ [Demo](https://todr.me/anan-as)
13
10
 
14
11
  ## Why?
15
12
 
@@ -19,7 +16,7 @@ AssemblyScript implementation of the JAM PVM (64-bit).
19
16
 
20
17
  ## Useful where?
21
18
 
22
- - Potentially as an alternative implementation for [`typeberry`](https://github.com/fluffylabs).
19
+ - Main PVM backend of [`typeberry`](https://github.com/fluffylabs) JAM client.
23
20
  - To test out the [PVM debugger](https://pvm.fluffylabs.dev).
24
21
 
25
22
  ## Installation
@@ -113,3 +110,64 @@ To run JSON test vectors.
113
110
  ```cmd
114
111
  npm start ./path/to/tests/*.json
115
112
  ```
113
+
114
+ ## CLI Usage
115
+
116
+ The package includes a CLI tool for disassembling and running PVM bytecode:
117
+
118
+ ```bash
119
+ # Disassemble bytecode to assembly
120
+ npx @fluffylabs/anan-as disassemble [--spi] [--no-metadata] <file.(jam|pvm|spi|bin)>
121
+
122
+ # Run JAM programs
123
+ npx @fluffylabs/anan-as run [--spi] [--no-logs] [--no-metadata] [--pc <number>] [--gas <number>] <file.jam> [spi-args.bin]
124
+
125
+ The `run` command executes PVM bytecode until it encounters a `halt` instruction or the first host call. For full execution including host call handling, use the disassemble command or other tooling.
126
+
127
+ # Show help
128
+ npx @fluffylabs/anan-as --help
129
+ npx @fluffylabs/anan-as disassemble --help
130
+ npx @fluffylabs/anan-as run --help
131
+ ```
132
+
133
+ ### Commands
134
+
135
+ - `disassemble`: Convert PVM bytecode to human-readable assembly
136
+ - `run`: Execute PVM bytecode and show results
137
+
138
+ ### Flags
139
+
140
+ - `--spi`: Treat input as JAM SPI format instead of generic PVM
141
+ - `--no-metadata`: Input does not start with metadata
142
+ - `--no-logs`: Disable execution logs (run command only)
143
+ - `--pc <number>`: Set initial program counter (default: 0)
144
+ - `--gas <number>`: Set initial gas amount (default: 0)
145
+ - `--help`, `-h`: Show help information
146
+
147
+ ### Examples
148
+
149
+ ```bash
150
+ # Disassemble a JAM file (includes metadata by default)
151
+ npx @fluffylabs/anan-as disassemble program.jam
152
+
153
+ # Disassemble without metadata
154
+ npx @fluffylabs/anan-as disassemble --no-metadata program.jam
155
+
156
+ # Disassemble SPI program
157
+ npx @fluffylabs/anan-as disassemble --spi program.spi
158
+
159
+ # Run a JAM program with logs (includes metadata by default)
160
+ npx @fluffylabs/anan-as run program.jam
161
+
162
+ # Run a JAM program without metadata
163
+ npx @fluffylabs/anan-as run --no-metadata program.jam
164
+
165
+ # Run a JAM program quietly
166
+ npx @fluffylabs/anan-as run --no-logs program.jam
167
+
168
+ # Run a JAM program with custom initial PC and gas
169
+ npx @fluffylabs/anan-as run --pc 100 --gas 10000 program.jam
170
+
171
+ # Run SPI program with arguments
172
+ npx @fluffylabs/anan-as run --spi program.spi args.bin
173
+ ```
@@ -0,0 +1,70 @@
1
+ import { readFileSync, writeFileSync } from "node:fs";
2
+ import { basename, dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ const projectRoot = resolve(__dirname, "..");
6
+ // Load asconfig.json
7
+ const asconfigPath = resolve(projectRoot, "asconfig.json");
8
+ const asconfig = JSON.parse(readFileSync(asconfigPath, "utf-8"));
9
+ console.log("Building inline JS files with base64-encoded WASM...\n");
10
+ // Process each target
11
+ for (const [targetName, config] of Object.entries(asconfig.targets)) {
12
+ const wasmPath = resolve(projectRoot, config.outFile);
13
+ try {
14
+ // Read the WASM file
15
+ const wasmBuffer = readFileSync(wasmPath);
16
+ // Base64 encode
17
+ const wasmBase64 = wasmBuffer.toString("base64");
18
+ // Generate the output JS file name
19
+ // e.g., "build/release.wasm" -> "build/release-inline.js"
20
+ const wasmFileName = basename(config.outFile, ".wasm");
21
+ const outputPath = resolve(projectRoot, dirname(config.outFile), `${wasmFileName}-inline.js`);
22
+ // Create the JS content
23
+ const jsContent = `// Auto-generated inline WASM module
24
+ // Target: ${targetName}
25
+ // Source: ${config.outFile}
26
+
27
+ import * as raw from './debug-raw.js';
28
+
29
+ export const wasmBase64 = "${wasmBase64}";
30
+ let compiledModulePromise = null;
31
+
32
+ // Helper function to decode and instantiate the module
33
+ export async function instantiate(imports) {
34
+ if (compiledModulePromise === null) {
35
+ compiledModulePromise = WebAssembly.compile(getWasmBytes());
36
+ }
37
+ const module = await compiledModulePromise;
38
+ return raw.instantiate(module, imports);
39
+ }
40
+
41
+ // Helper function to just get the bytes
42
+ export function getWasmBytes() {
43
+ return Uint8Array.from(atob(wasmBase64), c => c.charCodeAt(0));
44
+ }
45
+ `;
46
+ // Write the JS file
47
+ writeFileSync(outputPath, jsContent, "utf-8");
48
+ // Generate and write the .d.ts file
49
+ const dtsPath = outputPath.replace(/\.js$/, ".d.ts");
50
+ const dtsContent = `// Auto-generated type definitions for inline WASM module
51
+ // Target: ${targetName}
52
+ // Source: ${config.outFile}
53
+
54
+ import {__AdaptedExports} from "./debug-raw.d.ts";
55
+
56
+ export const wasmBase64: string;
57
+
58
+ export function instantiate(imports?: { env?: any }): Promise<typeof __AdaptedExports>;
59
+
60
+ export function getWasmBytes(): Uint8Array;
61
+ `;
62
+ writeFileSync(dtsPath, dtsContent, "utf-8");
63
+ console.log(`✓ ${targetName}: ${outputPath} (${Math.round(wasmBase64.length / 1024)} KB base64)`);
64
+ }
65
+ catch (error) {
66
+ console.error(`✗ ${targetName}: Failed to process ${wasmPath}`);
67
+ console.error(` ${error instanceof Error ? error.message : error}`);
68
+ }
69
+ }
70
+ console.log("\nDone!");
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ import "json-bigint-patch";
3
+ import fs from "node:fs";
4
+ import { tryAsGas } from "@typeberry/lib/pvm-interface";
5
+ import { Interpreter } from "@typeberry/lib/pvm-interpreter";
6
+ import { disassemble, HasMetadata, InputKind, prepareProgram, runProgram, wrapAsProgram } from "../build/release.js";
7
+ const runNumber = 0;
8
+ export function fuzz(data) {
9
+ const gas = 200n;
10
+ const pc = 0;
11
+ const vm = new Interpreter();
12
+ const program = wrapAsProgram(new Uint8Array(data));
13
+ if (program.length > 100) {
14
+ return;
15
+ }
16
+ try {
17
+ vm.resetGeneric(program, pc, tryAsGas(gas));
18
+ vm.runProgram();
19
+ const printDebugInfo = false;
20
+ const registers = Array(13)
21
+ .join(",")
22
+ .split(",")
23
+ .map(() => BigInt(0));
24
+ const exe = prepareProgram(InputKind.Generic, HasMetadata.No, Array.from(program), registers, [], [], []);
25
+ const output = runProgram(exe, gas, pc, printDebugInfo);
26
+ const vmRegisters = decodeRegistersFromTypeberry(vm);
27
+ collectErrors((assertFn) => {
28
+ assertFn(normalizeStatus(vm.getStatus()), normalizeStatus(output.status), "status");
29
+ assertFn(vm.gas.get(), output.gas, "gas");
30
+ assertFn(vmRegisters, output.registers, "registers");
31
+ assertFn(vm.getPC(), output.pc, "pc");
32
+ });
33
+ try {
34
+ if (runNumber % 100000 === 0) {
35
+ writeTestCase(program, {
36
+ pc,
37
+ gas,
38
+ registers,
39
+ }, {
40
+ status: normalizeStatus(vm.getStatus()),
41
+ gasLeft: vm.gas.get(),
42
+ pc: vm.getPC(),
43
+ registers: vmRegisters,
44
+ });
45
+ }
46
+ }
47
+ catch (e) {
48
+ console.warn("Unable to write file", e);
49
+ }
50
+ }
51
+ catch (e) {
52
+ const hex = programHex(program);
53
+ console.log(program);
54
+ console.log(linkTo(hex));
55
+ console.log(disassemble(Array.from(program), InputKind.Generic, HasMetadata.No));
56
+ throw e;
57
+ }
58
+ }
59
+ function programHex(program) {
60
+ return Array.from(program)
61
+ .map((x) => x.toString(16).padStart(2, "0"))
62
+ .join("");
63
+ }
64
+ function linkTo(programHex) {
65
+ return `https://pvm.fluffylabs.dev/?program=0x${programHex}#/`;
66
+ }
67
+ const REGISTER_BYTE_WIDTH = 8;
68
+ function _decodeRegisters(value) {
69
+ if (value.length === 0) {
70
+ return [];
71
+ }
72
+ if (value.length % REGISTER_BYTE_WIDTH !== 0) {
73
+ throw new Error(`Invalid register buffer size: ${value.length}`);
74
+ }
75
+ const view = new DataView(value.buffer, value.byteOffset, value.byteLength);
76
+ const registerCount = value.length / REGISTER_BYTE_WIDTH;
77
+ const registers = new Array(registerCount);
78
+ for (let i = 0; i < registerCount; i++) {
79
+ registers[i] = view.getBigUint64(i * REGISTER_BYTE_WIDTH, true);
80
+ }
81
+ return registers;
82
+ }
83
+ function decodeRegistersFromTypeberry(vm) {
84
+ const registers = [];
85
+ // Try to get up to 13 registers (common register count)
86
+ for (let i = 0; i < 13; i++) {
87
+ try {
88
+ registers.push(vm.registers.getU64(i));
89
+ }
90
+ catch (_e) {
91
+ // If we can't get a register, break
92
+ break;
93
+ }
94
+ }
95
+ return registers;
96
+ }
97
+ function normalizeStatus(status) {
98
+ if (status === 2) {
99
+ return 1;
100
+ }
101
+ return status;
102
+ }
103
+ function assert(tb, an, comment = "") {
104
+ let condition = tb !== an;
105
+ if (Array.isArray(tb) && Array.isArray(an)) {
106
+ condition = tb.toString() !== an.toString();
107
+ }
108
+ if (condition) {
109
+ const alsoAsHex = (f) => {
110
+ if (Array.isArray(f)) {
111
+ return `${f.map(alsoAsHex).join(", ")}`;
112
+ }
113
+ if (typeof f === "number" || typeof f === "bigint") {
114
+ if (BigInt(f) !== 0n) {
115
+ return `${f} | 0x${f.toString(16)}`;
116
+ }
117
+ return `${f}`;
118
+ }
119
+ return f;
120
+ };
121
+ throw new Error(`Diverging value: ${comment}
122
+ \t(typeberry) ${alsoAsHex(tb)}
123
+ \t(ananas) ${alsoAsHex(an)}`);
124
+ }
125
+ }
126
+ function collectErrors(cb) {
127
+ const errors = [];
128
+ cb((tb, an, comment = "") => {
129
+ try {
130
+ assert(tb, an, comment);
131
+ }
132
+ catch (e) {
133
+ errors.push(`${e}`);
134
+ }
135
+ });
136
+ if (errors.length > 0) {
137
+ throw new Error(errors.join("\n"));
138
+ }
139
+ }
140
+ function writeTestCase(program, initial, expected) {
141
+ const hex = programHex(program);
142
+ fs.mkdirSync(`../tests/length_${hex.length}`, { recursive: true });
143
+ fs.writeFileSync(`../tests/length_${hex.length}/${hex}.json`, JSON.stringify({
144
+ name: linkTo(hex),
145
+ "initial-regs": initial.registers,
146
+ "initial-pc": initial.pc,
147
+ "initial-page-map": [],
148
+ "initial-memory": [],
149
+ "initial-gas": initial.gas,
150
+ program: Array.from(program),
151
+ "expected-status": statusToStr(expected.status),
152
+ "expected-regs": Array.from(expected.registers),
153
+ "expected-pc": expected.pc,
154
+ "expected-gas": expected.gasLeft,
155
+ "expected-memory": [],
156
+ }));
157
+ }
158
+ function statusToStr(status) {
159
+ if (status === 0) {
160
+ return "halt";
161
+ }
162
+ if (status === 1) {
163
+ return "trap";
164
+ }
165
+ if (status === 4) {
166
+ return "oog";
167
+ }
168
+ if (status === 3) {
169
+ return "host";
170
+ }
171
+ throw new Error(`unexpected status: ${status}`);
172
+ }
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import minimist from "minimist";
4
+ import { disassemble, HasMetadata, InputKind, prepareProgram, runProgram } from "../build/release.js";
5
+ const HELP_TEXT = `Usage:
6
+ anan-as disassemble [--spi] [--no-metadata] <file.(jam|pvm|spi|bin)>
7
+ anan-as run [--spi] [--no-logs] [--no-metadata] [--pc <number>] [--gas <number>] <file.jam> [spi-args.bin]
8
+
9
+ Commands:
10
+ disassemble Disassemble PVM bytecode to assembly
11
+ run Execute PVM bytecode
12
+
13
+ Flags:
14
+ --spi Treat input as JAM SPI format
15
+ --no-metadata Input does not contain metadata
16
+ --no-logs Disable execution logs (run command only)
17
+ --pc <number> Set initial program counter (default: 0)
18
+ --gas <number> Set initial gas amount (default: 0)
19
+ --help, -h Show this help message`;
20
+ main();
21
+ function main() {
22
+ const args = process.argv.slice(2);
23
+ // Handle global help flags
24
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
25
+ console.log(HELP_TEXT);
26
+ return;
27
+ }
28
+ const subCommand = args[0];
29
+ switch (subCommand) {
30
+ case "disassemble":
31
+ handleDisassemble(args.slice(1));
32
+ break;
33
+ case "run":
34
+ handleRun(args.slice(1));
35
+ break;
36
+ default:
37
+ console.error(`Error: Unknown sub-command '${subCommand}'`);
38
+ console.error("");
39
+ console.error(HELP_TEXT);
40
+ process.exit(1);
41
+ }
42
+ }
43
+ function handleDisassemble(args) {
44
+ const parsed = minimist(args, {
45
+ boolean: ["spi", "no-metadata", "help"],
46
+ alias: { h: "help" },
47
+ });
48
+ if (parsed.help) {
49
+ console.log(HELP_TEXT);
50
+ return;
51
+ }
52
+ const files = parsed._;
53
+ if (files.length === 0) {
54
+ console.error("Error: No file provided for disassemble command.");
55
+ console.error("Usage: anan-as disassemble [--spi] [--no-metadata] <file.(jam|pvm|spi|bin)>");
56
+ process.exit(1);
57
+ }
58
+ if (files.length > 1) {
59
+ console.error("Error: Only one file can be disassembled at a time.");
60
+ console.error("Usage: anan-as disassemble [--spi] [--no-metadata] <file.(jam|pvm|spi|bin)>");
61
+ process.exit(1);
62
+ }
63
+ const file = files[0];
64
+ // Validate file extension for disassemble command
65
+ const validExtensions = [".jam", ".pvm", ".spi", ".bin"];
66
+ const dotIndex = file.lastIndexOf(".");
67
+ if (dotIndex === -1) {
68
+ console.error(`Error: File '${file}' has no extension.`);
69
+ console.error("Supported extensions: .jam, .pvm, .spi, .bin");
70
+ process.exit(1);
71
+ }
72
+ const ext = file.substring(dotIndex);
73
+ if (!validExtensions.includes(ext)) {
74
+ console.error(`Error: Invalid file extension '${ext}' for disassemble command.`);
75
+ console.error("Supported extensions: .jam, .pvm, .spi, .bin");
76
+ process.exit(1);
77
+ }
78
+ const kind = parsed.spi ? InputKind.SPI : InputKind.Generic;
79
+ const hasMetadata = parsed["no-metadata"] ? HasMetadata.No : HasMetadata.Yes;
80
+ const f = readFileSync(file);
81
+ const name = kind === InputKind.Generic ? "generic PVM" : "JAM SPI";
82
+ console.log(`🤖 Assembly of ${file} (as ${name})`);
83
+ console.log(disassemble(Array.from(f), kind, hasMetadata));
84
+ }
85
+ function handleRun(args) {
86
+ const parsed = minimist(args, {
87
+ boolean: ["spi", "no-logs", "no-metadata", "help"],
88
+ string: ["pc", "gas"],
89
+ alias: { h: "help" },
90
+ });
91
+ if (parsed.help) {
92
+ console.log(HELP_TEXT);
93
+ return;
94
+ }
95
+ const files = parsed._;
96
+ if (files.length === 0) {
97
+ console.error("Error: No file provided for run command.");
98
+ console.error("Usage: anan-as run [--spi] [--no-logs] [--no-metadata] [--pc <number>] [--gas <number>] <file.jam> [spi-args.bin]");
99
+ process.exit(1);
100
+ }
101
+ const kind = parsed.spi ? InputKind.SPI : InputKind.Generic;
102
+ let programFile;
103
+ let spiArgsFile;
104
+ if (kind === InputKind.SPI) {
105
+ // For SPI programs, expect: <program.spi> [spi-args.bin]
106
+ if (files.length > 2) {
107
+ console.error("Error: Too many arguments for SPI run command.");
108
+ console.error("Usage: anan-as run --spi [--no-logs] [--no-metadata] [--pc <number>] [--gas <number>] <program.spi> [spi-args.bin]");
109
+ process.exit(1);
110
+ }
111
+ programFile = files[0];
112
+ spiArgsFile = files[1]; // optional
113
+ }
114
+ else {
115
+ // For generic programs, expect exactly one file
116
+ if (files.length > 1) {
117
+ console.error("Error: Only one file can be run at a time.");
118
+ console.error("Usage: anan-as run [--no-logs] [--no-metadata] [--pc <number>] [--gas <number>] <file.jam>");
119
+ process.exit(1);
120
+ }
121
+ programFile = files[0];
122
+ }
123
+ // Validate program file extension
124
+ const expectedExt = kind === InputKind.SPI ? ".spi" : ".jam";
125
+ const dotIndex = programFile.lastIndexOf(".");
126
+ if (dotIndex === -1) {
127
+ console.error(`Error: File '${programFile}' has no extension.`);
128
+ console.error(`Expected: ${expectedExt}`);
129
+ process.exit(1);
130
+ }
131
+ const ext = programFile.substring(dotIndex);
132
+ if (ext !== expectedExt) {
133
+ console.error(`Error: Invalid file extension '${ext}' for run command.`);
134
+ console.error(`Expected: ${expectedExt}`);
135
+ process.exit(1);
136
+ }
137
+ // Validate SPI args file if provided
138
+ let spiArgs;
139
+ if (spiArgsFile) {
140
+ const argsDotIndex = spiArgsFile.lastIndexOf(".");
141
+ if (argsDotIndex === -1) {
142
+ console.error(`Error: SPI args file '${spiArgsFile}' has no extension.`);
143
+ console.error(`Expected: .bin`);
144
+ process.exit(1);
145
+ }
146
+ const argsExt = spiArgsFile.substring(argsDotIndex);
147
+ if (argsExt !== ".bin") {
148
+ console.error(`Error: SPI args file must have .bin extension, got '${argsExt}'.`);
149
+ process.exit(1);
150
+ }
151
+ spiArgs = new Uint8Array(readFileSync(spiArgsFile));
152
+ }
153
+ const logs = !parsed["no-logs"];
154
+ const hasMetadata = parsed["no-metadata"] ? HasMetadata.No : HasMetadata.Yes;
155
+ // Parse and validate PC and gas options
156
+ let initialPc = 0;
157
+ if (parsed.pc !== undefined) {
158
+ // Ensure it's a string/number, not boolean
159
+ if (typeof parsed.pc === "boolean") {
160
+ console.error("Error: --pc requires a value.");
161
+ process.exit(1);
162
+ }
163
+ const pcStr = String(parsed.pc);
164
+ // Reject floats and non-integer strings
165
+ if (pcStr.includes(".") || !/^-?\d+$/.test(pcStr)) {
166
+ console.error("Error: --pc must be a valid integer.");
167
+ process.exit(1);
168
+ }
169
+ const pcValue = parseInt(pcStr, 10);
170
+ if (!Number.isInteger(pcValue) || pcValue < 0 || pcValue > 0xffffffff) {
171
+ console.error("Error: --pc must be a non-negative integer <= 2^32-1.");
172
+ process.exit(1);
173
+ }
174
+ initialPc = pcValue;
175
+ }
176
+ let initialGas = BigInt(0);
177
+ if (parsed.gas !== undefined) {
178
+ // Ensure it's a string/number, not boolean
179
+ if (typeof parsed.gas === "boolean") {
180
+ console.error("Error: --gas requires a value.");
181
+ process.exit(1);
182
+ }
183
+ const gasStr = String(parsed.gas);
184
+ // Reject floats and non-integer strings
185
+ if (gasStr.includes(".") || !/^-?\d+$/.test(gasStr)) {
186
+ console.error("Error: --gas must be a valid integer.");
187
+ process.exit(1);
188
+ }
189
+ let gasValue;
190
+ try {
191
+ gasValue = BigInt(gasStr);
192
+ }
193
+ catch (_e) {
194
+ console.error("Error: --gas must be a valid integer.");
195
+ process.exit(1);
196
+ }
197
+ const MAX_I64 = (1n << 63n) - 1n;
198
+ if (gasValue < 0n || gasValue > MAX_I64) {
199
+ console.error("Error: --gas must be a non-negative integer <= 2^63-1.");
200
+ process.exit(1);
201
+ }
202
+ initialGas = gasValue;
203
+ }
204
+ const f = readFileSync(programFile);
205
+ const name = kind === InputKind.Generic ? "generic PVM" : "JAM SPI";
206
+ console.log(`🚀 Running ${programFile} (as ${name})`);
207
+ try {
208
+ const program = prepareProgram(kind, hasMetadata, Array.from(f), [], [], [], spiArgs ? Array.from(spiArgs) : []);
209
+ const result = runProgram(program, initialGas, initialPc, logs, false);
210
+ console.log(`Status: ${result.status}`);
211
+ console.log(`Exit code: ${result.exitCode}`);
212
+ console.log(`Program counter: ${result.pc}`);
213
+ console.log(`Gas remaining: ${result.gas}`);
214
+ console.log(`Registers: [${result.registers.join(", ")}]`);
215
+ }
216
+ catch (error) {
217
+ console.error(`Error running ${programFile}:`, error);
218
+ process.exit(1);
219
+ }
220
+ }
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ import "json-bigint-patch";
3
+ import * as assert from "node:assert";
4
+ import { disassemble, getGasCosts, HasMetadata, InputKind } from "../build/release.js";
5
+ import { ERR, OK, read, run } from "./test-json.js";
6
+ // Run the CLI application
7
+ main();
8
+ // Main function
9
+ function main() {
10
+ const options = {
11
+ isDebug: false,
12
+ isSilent: false,
13
+ useSbrkGas: false,
14
+ };
15
+ run(processGasCost, options);
16
+ }
17
+ function processGasCost(data, options, filePath) {
18
+ if (options.isDebug) {
19
+ console.info(`🤖 reading ${filePath}`);
20
+ }
21
+ // input
22
+ const input = {
23
+ program: read(data, "program"),
24
+ blockGasCosts: read(data, "block_gas_costs"),
25
+ };
26
+ if (options.isDebug) {
27
+ const assembly = disassemble(input.program, InputKind.Generic, HasMetadata.No);
28
+ console.info("===========");
29
+ console.info(assembly);
30
+ console.info("\n^^^^^^^^^^^\n");
31
+ }
32
+ const result = asMap(getGasCosts(input.program, InputKind.Generic, HasMetadata.No));
33
+ // silent mode - just put our vals into expected (comparison done externally)
34
+ if (options.isSilent) {
35
+ data.block_gas_costs = result;
36
+ if (filePath !== "-") {
37
+ console.log(JSON.stringify(data, null, 2));
38
+ }
39
+ return data;
40
+ }
41
+ try {
42
+ assert.deepStrictEqual(result, input.blockGasCosts);
43
+ console.log(`${OK} ${filePath}`);
44
+ }
45
+ catch (e) {
46
+ console.log(`${ERR} ${filePath}`);
47
+ throw e;
48
+ }
49
+ return data;
50
+ }
51
+ function asMap(costs) {
52
+ const obj = {};
53
+ for (const { pc, gas } of costs) {
54
+ obj[pc] = Number(gas);
55
+ }
56
+ return obj;
57
+ }