@fluffylabs/anan-as 1.1.3-e3864a9 → 1.1.3-eee81bd
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.
- package/README.md +47 -10
- package/dist/bin/index.js +174 -102
- package/dist/bin/{fuzz.js → src/fuzz.js} +3 -20
- package/dist/bin/src/log-host-call.js +41 -0
- package/dist/bin/src/trace-parse.js +315 -0
- package/dist/bin/src/trace-replay.js +130 -0
- package/dist/bin/src/tracer.js +64 -0
- package/dist/bin/src/utils.js +25 -0
- package/dist/bin/test.js +1 -3
- package/dist/build/compiler-inline.js +1 -1
- package/dist/build/compiler.d.ts +2 -2
- package/dist/build/compiler.js +1 -1
- package/dist/build/compiler.wasm +0 -0
- package/dist/build/debug-inline.js +1 -1
- package/dist/build/debug-raw-inline.js +1 -1
- package/dist/build/debug-raw.d.ts +113 -57
- package/dist/build/debug-raw.js +97 -61
- package/dist/build/debug-raw.wasm +0 -0
- package/dist/build/debug.d.ts +113 -57
- package/dist/build/debug.js +107 -64
- package/dist/build/debug.wasm +0 -0
- package/dist/build/release-inline.js +1 -1
- package/dist/build/release-mini-inline.js +1 -1
- package/dist/build/release-mini.d.ts +113 -57
- package/dist/build/release-mini.js +107 -64
- package/dist/build/release-mini.wasm +0 -0
- package/dist/build/release-stub-inline.js +1 -1
- package/dist/build/release-stub.d.ts +113 -57
- package/dist/build/release-stub.js +107 -64
- package/dist/build/release-stub.wasm +0 -0
- package/dist/build/release.d.ts +113 -57
- package/dist/build/release.js +107 -64
- package/dist/build/release.wasm +0 -0
- package/dist/build/test-inline.js +1 -1
- package/dist/build/test.wasm +0 -0
- package/dist/test/test-as.js +3 -0
- package/dist/{bin → test}/test-gas-cost.js +1 -1
- package/dist/test/test-trace-replay.js +19 -0
- package/dist/{bin → test}/test-w3f.js +3 -1
- package/package.json +5 -5
- /package/dist/bin/{test-json.js → src/test-json.js} +0 -0
package/README.md
CHANGED
|
@@ -48,9 +48,26 @@ import ananAs from '@fluffylabs/anan-as/release-mini';
|
|
|
48
48
|
// make sure to call GC after multiple independent runs
|
|
49
49
|
ananAs.__collect();
|
|
50
50
|
|
|
51
|
+
// Release build with stub host functions (for standalone testing)
|
|
52
|
+
import ananAs from '@fluffylabs/anan-as/release-stub';
|
|
53
|
+
|
|
54
|
+
// Compiler module (for PVM bytecode compilation)
|
|
55
|
+
import ananAs from '@fluffylabs/anan-as/compiler';
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Inline Builds
|
|
59
|
+
|
|
60
|
+
Inline builds bundle the WASM binary directly into the JavaScript module (base64 encoded),
|
|
61
|
+
eliminating the need to fetch a separate `.wasm` file:
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
import ananAs from '@fluffylabs/anan-as/debug-inline';
|
|
65
|
+
import ananAs from '@fluffylabs/anan-as/release-inline';
|
|
66
|
+
import ananAs from '@fluffylabs/anan-as/release-mini-inline';
|
|
67
|
+
import ananAs from '@fluffylabs/anan-as/release-stub-inline';
|
|
51
68
|
```
|
|
52
69
|
|
|
53
|
-
|
|
70
|
+
### Raw Bindings
|
|
54
71
|
|
|
55
72
|
Raw bindings give you direct access to WebAssembly exports
|
|
56
73
|
without the JavaScript wrapper layer.
|
|
@@ -69,7 +86,6 @@ const module = await WebAssembly.instantiateStreaming(
|
|
|
69
86
|
imports
|
|
70
87
|
);
|
|
71
88
|
const ananAs = await instantiate(module);
|
|
72
|
-
|
|
73
89
|
```
|
|
74
90
|
|
|
75
91
|
## Version Tags
|
|
@@ -105,24 +121,33 @@ To run the example in the browser at [http://localhost:3000](http://localhost:30
|
|
|
105
121
|
npm run web
|
|
106
122
|
```
|
|
107
123
|
|
|
108
|
-
To run
|
|
124
|
+
To run tests:
|
|
109
125
|
|
|
110
126
|
```cmd
|
|
111
|
-
|
|
127
|
+
# Run AssemblyScript unit tests and trace replay tests
|
|
128
|
+
npm test
|
|
129
|
+
|
|
130
|
+
# Run W3F test vectors
|
|
131
|
+
npm run test:w3f
|
|
132
|
+
|
|
133
|
+
# Run gas cost tests
|
|
134
|
+
npm run test:gas-cost
|
|
112
135
|
```
|
|
113
136
|
|
|
114
137
|
## CLI Usage
|
|
115
138
|
|
|
116
|
-
The package includes a CLI tool for disassembling and
|
|
139
|
+
The package includes a CLI tool for disassembling, running, and replaying PVM bytecode:
|
|
117
140
|
|
|
118
141
|
```bash
|
|
119
142
|
# Disassemble bytecode to assembly
|
|
120
143
|
npx @fluffylabs/anan-as disassemble [--spi] [--no-metadata] <file.(jam|pvm|spi|bin)>
|
|
121
144
|
|
|
122
145
|
# Run JAM programs
|
|
123
|
-
npx @fluffylabs/anan-as run [--spi] [--no-logs] [--no-metadata] [--pc <number>] [--gas <number>] <file.jam> [spi-args.bin]
|
|
146
|
+
npx @fluffylabs/anan-as run [--spi] [--no-logs] [--no-metadata] [--pc <number>] [--gas <number>] <file.jam> [spi-args.bin or hex]
|
|
124
147
|
|
|
125
|
-
|
|
148
|
+
# Replay an ecalli trace
|
|
149
|
+
# Learn more: https://github.com/tomusdrw/JIPs/blob/td-jip6-ecalliloggin/JIP-6.md
|
|
150
|
+
npx @fluffylabs/anan-as replay-trace [--no-metadata] [--no-verify] [--no-logs] <trace.log>
|
|
126
151
|
|
|
127
152
|
# Show help
|
|
128
153
|
npx @fluffylabs/anan-as --help
|
|
@@ -130,18 +155,23 @@ npx @fluffylabs/anan-as disassemble --help
|
|
|
130
155
|
npx @fluffylabs/anan-as run --help
|
|
131
156
|
```
|
|
132
157
|
|
|
158
|
+
The `run` command executes PVM bytecode until it encounters a `halt` instruction or a host call.
|
|
159
|
+
The `replay-trace` command re-executes an ecalli trace, replaying recorded host call responses.
|
|
160
|
+
|
|
133
161
|
### Commands
|
|
134
162
|
|
|
135
163
|
- `disassemble`: Convert PVM bytecode to human-readable assembly
|
|
136
164
|
- `run`: Execute PVM bytecode and show results
|
|
165
|
+
- `replay-trace`: Re-execute an ecalli trace with recorded host call responses
|
|
137
166
|
|
|
138
167
|
### Flags
|
|
139
168
|
|
|
140
169
|
- `--spi`: Treat input as JAM SPI format instead of generic PVM
|
|
141
170
|
- `--no-metadata`: Input does not start with metadata
|
|
142
|
-
- `--no-logs`: Disable execution logs (run
|
|
171
|
+
- `--no-logs`: Disable execution logs (run and replay-trace commands)
|
|
172
|
+
- `--no-verify`: Skip verification against trace data (replay-trace only)
|
|
143
173
|
- `--pc <number>`: Set initial program counter (default: 0)
|
|
144
|
-
- `--gas <number>`: Set initial gas amount (default:
|
|
174
|
+
- `--gas <number>`: Set initial gas amount (default: 10,000)
|
|
145
175
|
- `--help`, `-h`: Show help information
|
|
146
176
|
|
|
147
177
|
### Examples
|
|
@@ -168,6 +198,13 @@ npx @fluffylabs/anan-as run --no-logs program.jam
|
|
|
168
198
|
# Run a JAM program with custom initial PC and gas
|
|
169
199
|
npx @fluffylabs/anan-as run --pc 100 --gas 10000 program.jam
|
|
170
200
|
|
|
171
|
-
# Run SPI program with arguments
|
|
201
|
+
# Run SPI program with arguments (file or hex)
|
|
172
202
|
npx @fluffylabs/anan-as run --spi program.spi args.bin
|
|
203
|
+
npx @fluffylabs/anan-as run --spi program.spi 0xdeadbeef
|
|
204
|
+
|
|
205
|
+
# Replay an ecalli trace
|
|
206
|
+
npx @fluffylabs/anan-as replay-trace trace.log
|
|
207
|
+
|
|
208
|
+
# Replay without verification
|
|
209
|
+
npx @fluffylabs/anan-as replay-trace --no-verify trace.log
|
|
173
210
|
```
|
package/dist/bin/index.js
CHANGED
|
@@ -1,22 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import minimist from "minimist";
|
|
4
|
-
import { disassemble, HasMetadata, InputKind, prepareProgram,
|
|
4
|
+
import { disassemble, HasMetadata, InputKind, prepareProgram, pvmDestroy, pvmResume, pvmSetRegisters, pvmStart, } from "../build/release.js";
|
|
5
|
+
import { LOG_GAS_COST, LOG_HOST_CALL_INDEX, printLogHostCall, WHAT } from "./src/log-host-call.js";
|
|
6
|
+
import { STATUS } from "./src/trace-parse.js";
|
|
7
|
+
import { replayTraceFile } from "./src/trace-replay.js";
|
|
8
|
+
import { hexDecode, hexEncode } from "./src/utils.js";
|
|
5
9
|
const HELP_TEXT = `Usage:
|
|
6
10
|
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]
|
|
11
|
+
anan-as run [--spi] [--no-logs] [--no-metadata] [--no-log-host-call] [--pc <number>] [--gas <number>] <file.jam> [spi-args.bin or hex]
|
|
12
|
+
anan-as replay-trace [--no-metadata] [--no-verify] [--no-logs] [--no-log-host-call] <trace.log>
|
|
8
13
|
|
|
9
14
|
Commands:
|
|
10
15
|
disassemble Disassemble PVM bytecode to assembly
|
|
11
16
|
run Execute PVM bytecode
|
|
17
|
+
replay-trace Re-execute a ecalli IO trace
|
|
12
18
|
|
|
13
19
|
Flags:
|
|
14
|
-
--spi
|
|
15
|
-
--no-metadata
|
|
16
|
-
--no-logs
|
|
17
|
-
--
|
|
18
|
-
--
|
|
19
|
-
--
|
|
20
|
+
--spi Treat input as JAM SPI format
|
|
21
|
+
--no-metadata Input does not contain metadata
|
|
22
|
+
--no-logs Disable execution logs
|
|
23
|
+
--no-log-host-call Disable built-in handling of JIP-1 log host call (ecalli 100)
|
|
24
|
+
--no-verify Skip verification against trace data (replay-trace only)
|
|
25
|
+
--pc <number> Set initial program counter (default: 0)
|
|
26
|
+
--gas <number> Set initial gas amount (default: 10_000)
|
|
27
|
+
--help, -h Show this help message`;
|
|
20
28
|
main();
|
|
21
29
|
function main() {
|
|
22
30
|
const args = process.argv.slice(2);
|
|
@@ -33,6 +41,9 @@ function main() {
|
|
|
33
41
|
case "run":
|
|
34
42
|
handleRun(args.slice(1));
|
|
35
43
|
break;
|
|
44
|
+
case "replay-trace":
|
|
45
|
+
handleReplayTrace(args.slice(1));
|
|
46
|
+
break;
|
|
36
47
|
default:
|
|
37
48
|
console.error(`Error: Unknown sub-command '${subCommand}'`);
|
|
38
49
|
console.error("");
|
|
@@ -42,8 +53,9 @@ function main() {
|
|
|
42
53
|
}
|
|
43
54
|
function handleDisassemble(args) {
|
|
44
55
|
const parsed = minimist(args, {
|
|
45
|
-
boolean: ["spi", "
|
|
56
|
+
boolean: ["spi", "metadata", "help"],
|
|
46
57
|
alias: { h: "help" },
|
|
58
|
+
default: { metadata: true },
|
|
47
59
|
});
|
|
48
60
|
if (parsed.help) {
|
|
49
61
|
console.log(HELP_TEXT);
|
|
@@ -76,7 +88,7 @@ function handleDisassemble(args) {
|
|
|
76
88
|
process.exit(1);
|
|
77
89
|
}
|
|
78
90
|
const kind = parsed.spi ? InputKind.SPI : InputKind.Generic;
|
|
79
|
-
const hasMetadata = parsed
|
|
91
|
+
const hasMetadata = parsed.metadata ? HasMetadata.Yes : HasMetadata.No;
|
|
80
92
|
const f = readFileSync(file);
|
|
81
93
|
const name = kind === InputKind.Generic ? "generic PVM" : "JAM SPI";
|
|
82
94
|
console.log(`🤖 Assembly of ${file} (as ${name})`);
|
|
@@ -84,9 +96,11 @@ function handleDisassemble(args) {
|
|
|
84
96
|
}
|
|
85
97
|
function handleRun(args) {
|
|
86
98
|
const parsed = minimist(args, {
|
|
87
|
-
boolean: ["spi", "
|
|
88
|
-
|
|
99
|
+
boolean: ["spi", "logs", "metadata", "help", "log-host-call"],
|
|
100
|
+
/** Prevents parsing hex values as numbers. */
|
|
101
|
+
string: ["pc", "gas", "_"],
|
|
89
102
|
alias: { h: "help" },
|
|
103
|
+
default: { metadata: true, logs: true, "log-host-call": true },
|
|
90
104
|
});
|
|
91
105
|
if (parsed.help) {
|
|
92
106
|
console.log(HELP_TEXT);
|
|
@@ -100,16 +114,16 @@ function handleRun(args) {
|
|
|
100
114
|
}
|
|
101
115
|
const kind = parsed.spi ? InputKind.SPI : InputKind.Generic;
|
|
102
116
|
let programFile;
|
|
103
|
-
let
|
|
117
|
+
let spiArgsStr;
|
|
104
118
|
if (kind === InputKind.SPI) {
|
|
105
|
-
// For SPI programs, expect: <program.spi> [spi-args.bin]
|
|
119
|
+
// For SPI programs, expect: <program.spi> [spi-args.bin or hex]
|
|
106
120
|
if (files.length > 2) {
|
|
107
121
|
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]");
|
|
122
|
+
console.error("Usage: anan-as run --spi [--no-logs] [--no-metadata] [--pc <number>] [--gas <number>] <program.spi> [spi-args.bin or hex]");
|
|
109
123
|
process.exit(1);
|
|
110
124
|
}
|
|
111
125
|
programFile = files[0];
|
|
112
|
-
|
|
126
|
+
spiArgsStr = files[1]; // optional
|
|
113
127
|
}
|
|
114
128
|
else {
|
|
115
129
|
// For generic programs, expect exactly one file
|
|
@@ -120,101 +134,159 @@ function handleRun(args) {
|
|
|
120
134
|
}
|
|
121
135
|
programFile = files[0];
|
|
122
136
|
}
|
|
123
|
-
// Validate
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
137
|
+
// Validate SPI args file if provided
|
|
138
|
+
const spiArgs = parseSpiArgs(spiArgsStr);
|
|
139
|
+
const logs = parsed.logs;
|
|
140
|
+
const logHostCall = parsed["log-host-call"];
|
|
141
|
+
const hasMetadata = parsed.metadata ? HasMetadata.Yes : HasMetadata.No;
|
|
142
|
+
// Parse and validate PC and gas options
|
|
143
|
+
const initialPc = parsePc(parsed);
|
|
144
|
+
const initialGas = parseGas(parsed);
|
|
145
|
+
const programCode = Array.from(readFileSync(programFile));
|
|
146
|
+
const name = kind === InputKind.Generic ? "generic PVM" : "JAM SPI";
|
|
147
|
+
console.log(`🚀 Running ${programFile} (as ${name})`);
|
|
148
|
+
try {
|
|
149
|
+
const program = prepareProgram(kind, hasMetadata, programCode, [], [], [], spiArgs);
|
|
150
|
+
const id = pvmStart(program, false);
|
|
151
|
+
let gas = initialGas;
|
|
152
|
+
let pc = initialPc;
|
|
153
|
+
for (;;) {
|
|
154
|
+
const pause = pvmResume(id, gas, pc, logs);
|
|
155
|
+
if (!pause) {
|
|
156
|
+
throw new Error("pvmResume returned null");
|
|
157
|
+
}
|
|
158
|
+
if (pause.status === STATUS.HOST && pause.exitCode === LOG_HOST_CALL_INDEX && logHostCall) {
|
|
159
|
+
printLogHostCall(id, pause.registers);
|
|
160
|
+
// Set r7 = WHAT
|
|
161
|
+
const regs = pause.registers;
|
|
162
|
+
regs[7] = WHAT;
|
|
163
|
+
pvmSetRegisters(id, regs);
|
|
164
|
+
// Deduct gas and advance PC
|
|
165
|
+
gas = pause.gas >= LOG_GAS_COST ? pause.gas - LOG_GAS_COST : 0n;
|
|
166
|
+
pc = pause.nextPc;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
console.warn(`Unhandled host call: ecalli ${pause.exitCode}. Finishing.`);
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const result = pvmDestroy(id);
|
|
174
|
+
console.log(`Status: ${result?.status}`);
|
|
175
|
+
console.log(`Exit code: ${result?.exitCode}`);
|
|
176
|
+
console.log(`Program counter: ${result?.pc}`);
|
|
177
|
+
console.log(`Gas remaining: ${result?.gas}`);
|
|
178
|
+
console.log(`Registers: [${result?.registers.join(", ")}]`);
|
|
179
|
+
console.log(`Result: [${hexEncode(result?.result ?? [])}]`);
|
|
130
180
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
console.error(`Error: Invalid file extension '${ext}' for run command.`);
|
|
134
|
-
console.error(`Expected: ${expectedExt}`);
|
|
181
|
+
catch (error) {
|
|
182
|
+
console.error(`Error running ${programFile}:`, error);
|
|
135
183
|
process.exit(1);
|
|
136
184
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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));
|
|
185
|
+
}
|
|
186
|
+
function handleReplayTrace(args) {
|
|
187
|
+
const parsed = minimist(args, {
|
|
188
|
+
boolean: ["metadata", "verify", "logs", "help", "log-host-call"],
|
|
189
|
+
alias: { h: "help" },
|
|
190
|
+
default: { metadata: true, logs: true, verify: true, "log-host-call": true },
|
|
191
|
+
});
|
|
192
|
+
if (parsed.help) {
|
|
193
|
+
console.log(HELP_TEXT);
|
|
194
|
+
return;
|
|
152
195
|
}
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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;
|
|
196
|
+
const files = parsed._;
|
|
197
|
+
if (files.length === 0) {
|
|
198
|
+
console.error("Error: No trace file provided for replay-trace command.");
|
|
199
|
+
console.error("Usage: anan-as replay-trace [--no-metadata] [--no-verify] [--no-logs] <trace.log>");
|
|
200
|
+
process.exit(1);
|
|
175
201
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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;
|
|
202
|
+
if (files.length > 1) {
|
|
203
|
+
console.error("Error: Only one trace file can be replayed at a time.");
|
|
204
|
+
console.error("Usage: anan-as replay-trace [--no-metadata] [--no-verify] [--no-logs] <trace.log>");
|
|
205
|
+
process.exit(1);
|
|
203
206
|
}
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
+
const file = files[0];
|
|
208
|
+
const hasMetadata = parsed.metadata ? HasMetadata.Yes : HasMetadata.No;
|
|
209
|
+
const verify = parsed.verify;
|
|
210
|
+
const logs = parsed.logs;
|
|
211
|
+
const logHostCall = parsed["log-host-call"];
|
|
207
212
|
try {
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
console.log(
|
|
213
|
+
const summary = replayTraceFile(file, {
|
|
214
|
+
logs,
|
|
215
|
+
hasMetadata,
|
|
216
|
+
verify,
|
|
217
|
+
logHostCall,
|
|
218
|
+
});
|
|
219
|
+
console.log(`✅ Replay complete: ${summary.ecalliCount} ecalli entries`);
|
|
220
|
+
console.log(`Status: ${summary.termination.type}`);
|
|
221
|
+
console.log(`Program counter: ${summary.termination.pc}`);
|
|
222
|
+
console.log(`Gas remaining: ${summary.termination.gas}`);
|
|
215
223
|
}
|
|
216
224
|
catch (error) {
|
|
217
|
-
console.error(`Error
|
|
225
|
+
console.error(`Error replaying trace ${file}:`, error);
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function parseGas(parsed) {
|
|
230
|
+
if (parsed.gas === undefined) {
|
|
231
|
+
return BigInt(10_000);
|
|
232
|
+
}
|
|
233
|
+
// Ensure it's a string/number, not boolean
|
|
234
|
+
if (typeof parsed.gas === "boolean") {
|
|
235
|
+
console.error("Error: --gas requires a value.");
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
const gasStr = String(parsed.gas);
|
|
239
|
+
// Reject floats and non-integer strings
|
|
240
|
+
if (gasStr.includes(".") || !/^-?\d+$/.test(gasStr)) {
|
|
241
|
+
console.error("Error: --gas must be a valid integer.");
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
let gasValue;
|
|
245
|
+
try {
|
|
246
|
+
gasValue = BigInt(gasStr);
|
|
247
|
+
}
|
|
248
|
+
catch (_e) {
|
|
249
|
+
console.error("Error: --gas must be a valid integer.");
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
const MAX_I64 = (1n << 63n) - 1n;
|
|
253
|
+
if (gasValue < 0n || gasValue > MAX_I64) {
|
|
254
|
+
console.error("Error: --gas must be a non-negative integer <= 2^63-1.");
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
return gasValue;
|
|
258
|
+
}
|
|
259
|
+
function parseSpiArgs(spiArgsStr) {
|
|
260
|
+
if (!spiArgsStr) {
|
|
261
|
+
return [];
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
return Array.from(hexDecode(spiArgsStr));
|
|
265
|
+
}
|
|
266
|
+
catch (e) {
|
|
267
|
+
console.log(`Attempting to read ${spiArgsStr} as a file, since it's not a hex value: ${e}`);
|
|
268
|
+
return Array.from(readFileSync(spiArgsStr));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function parsePc(parsed) {
|
|
272
|
+
if (parsed.pc === undefined) {
|
|
273
|
+
return 0;
|
|
274
|
+
}
|
|
275
|
+
// Ensure it's a string/number, not boolean
|
|
276
|
+
if (typeof parsed.pc === "boolean") {
|
|
277
|
+
console.error("Error: --pc requires a value.");
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
280
|
+
const pcStr = String(parsed.pc);
|
|
281
|
+
// Reject floats and non-integer strings
|
|
282
|
+
if (pcStr.includes(".") || !/^-?\d+$/.test(pcStr)) {
|
|
283
|
+
console.error("Error: --pc must be a valid integer.");
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
const pcValue = parseInt(pcStr, 10);
|
|
287
|
+
if (!Number.isInteger(pcValue) || pcValue < 0 || pcValue > 0xffffffff) {
|
|
288
|
+
console.error("Error: --pc must be a non-negative integer <= 2^32-1.");
|
|
218
289
|
process.exit(1);
|
|
219
290
|
}
|
|
291
|
+
return pcValue;
|
|
220
292
|
}
|
|
@@ -3,7 +3,7 @@ import "json-bigint-patch";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import { tryAsGas } from "@typeberry/lib/pvm-interface";
|
|
5
5
|
import { Interpreter } from "@typeberry/lib/pvm-interpreter";
|
|
6
|
-
import { disassemble, HasMetadata, InputKind, prepareProgram, runProgram, wrapAsProgram } from "
|
|
6
|
+
import { disassemble, HasMetadata, InputKind, prepareProgram, runProgram, wrapAsProgram } from "../../build/release.js";
|
|
7
7
|
const runNumber = 0;
|
|
8
8
|
export function fuzz(data) {
|
|
9
9
|
const gas = 200n;
|
|
@@ -56,30 +56,13 @@ export function fuzz(data) {
|
|
|
56
56
|
throw e;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
import { hexEncode } from "./utils.js";
|
|
59
60
|
function programHex(program) {
|
|
60
|
-
return
|
|
61
|
-
.map((x) => x.toString(16).padStart(2, "0"))
|
|
62
|
-
.join("");
|
|
61
|
+
return hexEncode(program, false);
|
|
63
62
|
}
|
|
64
63
|
function linkTo(programHex) {
|
|
65
64
|
return `https://pvm.fluffylabs.dev/?program=0x${programHex}#/`;
|
|
66
65
|
}
|
|
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
66
|
function decodeRegistersFromTypeberry(vm) {
|
|
84
67
|
const registers = [];
|
|
85
68
|
// Try to get up to 13 registers (common register count)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { pvmReadMemory } from "../../build/release.js";
|
|
2
|
+
export const LOG_HOST_CALL_INDEX = 100;
|
|
3
|
+
export const LOG_GAS_COST = 10n;
|
|
4
|
+
/** The WHAT return value - indicates the host call is not implemented / acknowledged. */
|
|
5
|
+
export const WHAT = 0xfffffffffffffffen;
|
|
6
|
+
const LOG_LEVELS = ["FATAL", "ERROR", "WARN", "INFO", "DEBUG"];
|
|
7
|
+
const MAX_LOG_LEN = 8192;
|
|
8
|
+
/**
|
|
9
|
+
* Print the log message from a JIP-1 log host call (ecalli 100).
|
|
10
|
+
*
|
|
11
|
+
* Reads the level, target, and message from the PVM registers and memory,
|
|
12
|
+
* then prints via console.info.
|
|
13
|
+
*/
|
|
14
|
+
export function printLogHostCall(pvmId, registers) {
|
|
15
|
+
const level = Number(registers[7]);
|
|
16
|
+
const targetPtr = Number(registers[8] & 0xffffffffn);
|
|
17
|
+
const targetLen = Math.min(Math.max(0, Number(registers[9] & 0xffffffffn)), MAX_LOG_LEN);
|
|
18
|
+
const messagePtr = Number(registers[10] & 0xffffffffn);
|
|
19
|
+
const messageLen = Math.min(Math.max(0, Number(registers[11] & 0xffffffffn)), MAX_LOG_LEN);
|
|
20
|
+
const levelStr = LOG_LEVELS[level] ?? `LEVEL(${level})`;
|
|
21
|
+
let target = "";
|
|
22
|
+
if (targetPtr !== 0 && targetLen > 0) {
|
|
23
|
+
const targetBytes = pvmReadMemory(pvmId, targetPtr, targetLen);
|
|
24
|
+
if (targetBytes) {
|
|
25
|
+
target = new TextDecoder().decode(targetBytes);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
let message = "";
|
|
29
|
+
if (messagePtr !== 0 && messageLen > 0) {
|
|
30
|
+
const messageBytes = pvmReadMemory(pvmId, messagePtr, messageLen);
|
|
31
|
+
if (messageBytes) {
|
|
32
|
+
message = new TextDecoder().decode(messageBytes);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (target) {
|
|
36
|
+
console.info(`[${levelStr}] ${target}: ${message}`);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.info(`[${levelStr}] ${message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|