@fluffylabs/anan-as 1.1.3-c185e54 → 1.1.3-c81d96c
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/dist/bin/index.js +50 -21
- package/dist/bin/src/fuzz.js +1 -1
- package/dist/bin/src/log-host-call.js +41 -0
- package/dist/bin/src/trace-replay.js +7 -2
- package/dist/bin/src/utils.js +1 -1
- package/package.json +1 -1
package/dist/bin/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
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";
|
|
5
7
|
import { replayTraceFile } from "./src/trace-replay.js";
|
|
6
8
|
import { hexDecode, hexEncode } from "./src/utils.js";
|
|
7
9
|
const HELP_TEXT = `Usage:
|
|
8
10
|
anan-as disassemble [--spi] [--no-metadata] <file.(jam|pvm|spi|bin)>
|
|
9
|
-
anan-as run [--spi] [--no-logs] [--no-metadata] [--pc <number>] [--gas <number>] <file.jam> [spi-args.bin or hex]
|
|
10
|
-
anan-as replay-trace [--no-metadata] [--no-verify] [--no-logs] <trace.log>
|
|
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>
|
|
11
13
|
|
|
12
14
|
Commands:
|
|
13
15
|
disassemble Disassemble PVM bytecode to assembly
|
|
@@ -15,13 +17,14 @@ Commands:
|
|
|
15
17
|
replay-trace Re-execute a ecalli IO trace
|
|
16
18
|
|
|
17
19
|
Flags:
|
|
18
|
-
--spi
|
|
19
|
-
--no-metadata
|
|
20
|
-
--no-logs
|
|
21
|
-
--no-
|
|
22
|
-
--
|
|
23
|
-
--
|
|
24
|
-
--
|
|
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`;
|
|
25
28
|
main();
|
|
26
29
|
function main() {
|
|
27
30
|
const args = process.argv.slice(2);
|
|
@@ -93,11 +96,11 @@ function handleDisassemble(args) {
|
|
|
93
96
|
}
|
|
94
97
|
function handleRun(args) {
|
|
95
98
|
const parsed = minimist(args, {
|
|
96
|
-
boolean: ["spi", "logs", "metadata", "help"],
|
|
99
|
+
boolean: ["spi", "logs", "metadata", "help", "log-host-call"],
|
|
97
100
|
/** Prevents parsing hex values as numbers. */
|
|
98
101
|
string: ["pc", "gas", "_"],
|
|
99
102
|
alias: { h: "help" },
|
|
100
|
-
default: { metadata: true, logs: true },
|
|
103
|
+
default: { metadata: true, logs: true, "log-host-call": true },
|
|
101
104
|
});
|
|
102
105
|
if (parsed.help) {
|
|
103
106
|
console.log(HELP_TEXT);
|
|
@@ -134,6 +137,7 @@ function handleRun(args) {
|
|
|
134
137
|
// Validate SPI args file if provided
|
|
135
138
|
const spiArgs = parseSpiArgs(spiArgsStr);
|
|
136
139
|
const logs = parsed.logs;
|
|
140
|
+
const logHostCall = parsed["log-host-call"];
|
|
137
141
|
const hasMetadata = parsed.metadata ? HasMetadata.Yes : HasMetadata.No;
|
|
138
142
|
// Parse and validate PC and gas options
|
|
139
143
|
const initialPc = parsePc(parsed);
|
|
@@ -143,13 +147,36 @@ function handleRun(args) {
|
|
|
143
147
|
console.log(`🚀 Running ${programFile} (as ${name})`);
|
|
144
148
|
try {
|
|
145
149
|
const program = prepareProgram(kind, hasMetadata, programCode, [], [], [], spiArgs);
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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 ?? [])}]`);
|
|
153
180
|
}
|
|
154
181
|
catch (error) {
|
|
155
182
|
console.error(`Error running ${programFile}:`, error);
|
|
@@ -158,9 +185,9 @@ function handleRun(args) {
|
|
|
158
185
|
}
|
|
159
186
|
function handleReplayTrace(args) {
|
|
160
187
|
const parsed = minimist(args, {
|
|
161
|
-
boolean: ["metadata", "verify", "logs", "help"],
|
|
188
|
+
boolean: ["metadata", "verify", "logs", "help", "log-host-call"],
|
|
162
189
|
alias: { h: "help" },
|
|
163
|
-
default: { metadata: true, logs: true, verify: true },
|
|
190
|
+
default: { metadata: true, logs: true, verify: true, "log-host-call": true },
|
|
164
191
|
});
|
|
165
192
|
if (parsed.help) {
|
|
166
193
|
console.log(HELP_TEXT);
|
|
@@ -181,11 +208,13 @@ function handleReplayTrace(args) {
|
|
|
181
208
|
const hasMetadata = parsed.metadata ? HasMetadata.Yes : HasMetadata.No;
|
|
182
209
|
const verify = parsed.verify;
|
|
183
210
|
const logs = parsed.logs;
|
|
211
|
+
const logHostCall = parsed["log-host-call"];
|
|
184
212
|
try {
|
|
185
213
|
const summary = replayTraceFile(file, {
|
|
186
214
|
logs,
|
|
187
215
|
hasMetadata,
|
|
188
216
|
verify,
|
|
217
|
+
logHostCall,
|
|
189
218
|
});
|
|
190
219
|
console.log(`✅ Replay complete: ${summary.ecalliCount} ecalli entries`);
|
|
191
220
|
console.log(`Status: ${summary.termination.type}`);
|
package/dist/bin/src/fuzz.js
CHANGED
|
@@ -58,7 +58,7 @@ export function fuzz(data) {
|
|
|
58
58
|
}
|
|
59
59
|
import { hexEncode } from "./utils.js";
|
|
60
60
|
function programHex(program) {
|
|
61
|
-
return hexEncode(
|
|
61
|
+
return hexEncode(program, false);
|
|
62
62
|
}
|
|
63
63
|
function linkTo(programHex) {
|
|
64
64
|
return `https://pvm.fluffylabs.dev/?program=0x${programHex}#/`;
|
|
@@ -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
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { InputKind, prepareProgram, pvmDestroy, pvmReadMemory, pvmResume, pvmSetRegisters, pvmStart, pvmWriteMemory, } from "../../build/release.js";
|
|
3
|
+
import { LOG_HOST_CALL_INDEX, printLogHostCall } from "./log-host-call.js";
|
|
3
4
|
import { ARGS_SEGMENT_START, buildInitialChunks, buildInitialPages, encodeRegistersFromDump, extractSpiArgs, isSpiTrace, parseTrace, STATUS, statusToTermination, } from "./trace-parse.js";
|
|
4
5
|
import { ConsoleTracer } from "./tracer.js";
|
|
5
6
|
import { hexEncode } from "./utils.js";
|
|
@@ -37,6 +38,10 @@ export function replayTraceFile(filePath, options) {
|
|
|
37
38
|
}
|
|
38
39
|
// Print ecalli line
|
|
39
40
|
tracer.ecalli(expectedEcalli.index, pause.pc, pause.gas, pause.registers);
|
|
41
|
+
// Print log message for JIP-1 log host call
|
|
42
|
+
if (pause.exitCode === LOG_HOST_CALL_INDEX && options.logHostCall) {
|
|
43
|
+
printLogHostCall(id, pause.registers);
|
|
44
|
+
}
|
|
40
45
|
if (options.verify) {
|
|
41
46
|
assertEq(pause.exitCode, expectedEcalli.index, "ecalli index");
|
|
42
47
|
assertEq(pause.pc, expectedEcalli.pc, "ecalli pc");
|
|
@@ -119,7 +124,7 @@ function assertRegisters(actual, expected) {
|
|
|
119
124
|
}
|
|
120
125
|
}
|
|
121
126
|
function assertMemEq(actual, expected, label) {
|
|
122
|
-
const actualString = hexEncode(
|
|
123
|
-
const expectedString = hexEncode(
|
|
127
|
+
const actualString = hexEncode(actual);
|
|
128
|
+
const expectedString = hexEncode(expected);
|
|
124
129
|
assertEq(actualString, expectedString, label);
|
|
125
130
|
}
|
package/dist/bin/src/utils.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export function hexEncode(result, includePrefix = true) {
|
|
2
|
-
const hex =
|
|
2
|
+
const hex = Array.from(result, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
3
3
|
return includePrefix ? `0x${hex}` : hex;
|
|
4
4
|
}
|
|
5
5
|
export function hexDecode(data) {
|