@fluffylabs/anan-as 1.2.0-abcb88c → 1.2.0-b6d0f93

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 CHANGED
@@ -91,7 +91,7 @@ function handleDisassemble(args) {
91
91
  console.error("Supported extensions: .jam, .pvm, .spi, .bin");
92
92
  process.exit(1);
93
93
  }
94
- const ext = file.substring(dotIndex);
94
+ const ext = file.slice(dotIndex);
95
95
  if (!validExtensions.includes(ext)) {
96
96
  console.error(`Error: Invalid file extension '${ext}' for disassemble command.`);
97
97
  console.error("Supported extensions: .jam, .pvm, .spi, .bin");
@@ -380,25 +380,26 @@ function parseMem(memStr) {
380
380
  if (colonIdx === -1) {
381
381
  throw new Error(`--mem entry ${i} ("${spec}") must be "addr:hexbytes".`);
382
382
  }
383
- const addrStr = spec.substring(0, colonIdx).trim();
384
- let hexStr = spec.substring(colonIdx + 1).trim();
383
+ const addrStr = spec.slice(0, colonIdx).trim();
384
+ let hexStr = spec.slice(colonIdx + 1).trim();
385
385
  const address = parseNum(addrStr);
386
386
  if (Number.isNaN(address)) {
387
387
  throw new Error(`--mem entry ${i} has invalid address "${addrStr}".`);
388
388
  }
389
389
  // Strip 0x prefix from hex data
390
390
  if (hexStr.startsWith("0x") || hexStr.startsWith("0X")) {
391
- hexStr = hexStr.substring(2);
391
+ hexStr = hexStr.slice(2);
392
392
  }
393
393
  if (hexStr.length % 2 !== 0) {
394
394
  throw new Error(`--mem entry ${i} hex data has odd length.`);
395
395
  }
396
396
  const data = [];
397
397
  for (let j = 0; j < hexStr.length; j += 2) {
398
- const byte = parseInt(hexStr.substring(j, j + 2), 16);
399
- if (Number.isNaN(byte)) {
400
- throw new Error(`--mem entry ${i} has invalid hex byte at position ${j}: "${hexStr.substring(j, j + 2)}".`);
398
+ const pair = hexStr.slice(j, j + 2);
399
+ if (!/^[0-9a-fA-F]{2}$/.test(pair)) {
400
+ throw new Error(`--mem entry ${i} has invalid hex byte at position ${j}: "${pair}".`);
401
401
  }
402
+ const byte = parseInt(pair, 16);
402
403
  data.push(byte);
403
404
  }
404
405
  return { address, data };
@@ -1,3 +1,4 @@
1
+ // Trace format: https://github.com/FluffyLabs/jam-ecalli-trace/blob/main/ecalli-trace-jip.md
1
2
  const NO_OF_REGISTERS = 13;
2
3
  // Access.Write = 2 from assembly/memory-page.ts
3
4
  const ACCESS_WRITE = 2;
@@ -1,7 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import * as defaultPvm from "../../build/release.js";
3
3
  import { LOG_HOST_CALL_INDEX, printLogHostCall } from "./log-host-call.js";
4
- import { ARGS_SEGMENT_START, buildInitialChunks, buildInitialPages, encodeRegistersFromDump, extractSpiArgs, isSpiTrace, parseTrace, STATUS, statusToTermination, } from "./trace-parse.js";
4
+ import { buildInitialChunks, buildInitialPages, encodeRegistersFromDump, extractSpiArgs, isSpiTrace, parseTrace, STATUS, statusToTermination, } from "./trace-parse.js";
5
5
  import { ConsoleTracer } from "./tracer.js";
6
6
  import { hexEncode } from "./utils.js";
7
7
  export function replayTraceFile(filePath, options) {
@@ -25,11 +25,12 @@ export function replayTraceFile(filePath, options) {
25
25
  try {
26
26
  let gas = start.gas;
27
27
  let pc = start.pc;
28
- // Print start line
29
- tracer.start(pc, gas, start.registers);
30
- if (spiArgs.length > 0) {
31
- tracer.spiArgs(ARGS_SEGMENT_START, spiArgs);
28
+ // Print prelude: program, initial memwrites, start
29
+ tracer.program(program);
30
+ for (const write of initialMemWrites) {
31
+ tracer.memwrite(write.address, write.data);
32
32
  }
33
+ tracer.start(pc, gas, start.registers);
33
34
  for (;;) {
34
35
  const pause = pvmResume(id, gas, pc, options.logs);
35
36
  if (!pause) {
@@ -1,29 +1,29 @@
1
- import { hexEncode } from "./utils.js";
1
+ // Trace output format: https://github.com/FluffyLabs/jam-ecalli-trace/blob/main/ecalli-trace-jip.md
2
2
  export class ConsoleTracer {
3
+ program(data) {
4
+ console.log(`program ${formatHex(data)}`);
5
+ }
3
6
  start(pc, gas, registers) {
4
7
  console.log(`start pc=${pc} gas=${gas} ${formatRegisters(registers)}`);
5
8
  }
6
- spiArgs(address, data) {
7
- console.log(` memwrite ${address} len=${data.length} <- ${hexEncode(data)}`);
8
- }
9
9
  ecalli(index, pc, gas, registers) {
10
- console.log(`\necalli=${index} pc=${pc} gas=${gas} ${formatRegisters(registers)}`);
10
+ console.log(`ecalli=${index} pc=${pc} gas=${gas} ${formatRegisters(registers)}`);
11
11
  }
12
12
  memread(address, data) {
13
- console.log(` memread 0x${address.toString(16)} len=${data.length} -> ${formatHex(data)}`);
13
+ console.log(` memread ${formatAddress(address)} len=${data.length} -> ${formatHex(data)}`);
14
14
  }
15
15
  memwrite(address, data) {
16
- console.log(` memwrite 0x${address.toString(16)} len=${data.length} <- ${formatHex(data)}`);
16
+ console.log(` memwrite ${formatAddress(address)} len=${data.length} <- ${formatHex(data)}`);
17
17
  }
18
18
  setreg(index, value) {
19
- console.log(` setreg r${index.toString().padStart(2, "0")} <- 0x${value.toString(16)}`);
19
+ console.log(` setreg r${index.toString().padStart(2, "0")} <- 0x${value.toString(16)}`);
20
20
  }
21
21
  setgas(gas) {
22
- console.log(` setgas <- ${gas}`);
22
+ console.log(` setgas <- ${gas}`);
23
23
  }
24
24
  termination(type, exitCode, pc, gas, registers) {
25
- let termLine = `\n------\n${type}`;
26
- if (type === "PANIC" && exitCode !== 0) {
25
+ let termLine = `${type}`;
26
+ if (type === "PANIC") {
27
27
  termLine += `=${exitCode}`;
28
28
  }
29
29
  termLine += ` pc=${pc} gas=${gas} ${formatRegisters(registers)}`;
@@ -31,8 +31,8 @@ export class ConsoleTracer {
31
31
  }
32
32
  }
33
33
  export class NoOpTracer {
34
+ program() { }
34
35
  start() { }
35
- spiArgs() { }
36
36
  ecalli() { }
37
37
  memread() { }
38
38
  memwrite() { }
@@ -56,9 +56,12 @@ function formatRegisters(registers) {
56
56
  }
57
57
  return entries
58
58
  .sort((a, b) => a.idx - b.idx)
59
- .map((e) => `r${e.idx}=0x${e.val.toString(16)}`)
59
+ .map((e) => `r${e.idx.toString().padStart(2, "0")}=0x${e.val.toString(16)}`)
60
60
  .join(" ");
61
61
  }
62
+ function formatAddress(address) {
63
+ return `0x${Number(address).toString(16).padStart(8, "0")}`;
64
+ }
62
65
  function formatHex(data) {
63
66
  return `0x${Buffer.from(data).toString("hex")}`;
64
67
  }
@@ -6,14 +6,14 @@ export function hexDecode(data) {
6
6
  if (!data.startsWith("0x")) {
7
7
  throw new Error("hex input must start with 0x");
8
8
  }
9
- const hex = data.substring(2);
9
+ const hex = data.slice(2);
10
10
  const len = hex.length;
11
11
  if (len % 2 === 1) {
12
12
  throw new Error("Odd number of nibbles");
13
13
  }
14
14
  const bytes = new Uint8Array(len / 2);
15
15
  for (let i = 0; i < len; i += 2) {
16
- const c = hex.substring(i, i + 2);
16
+ const c = hex.slice(i, i + 2);
17
17
  const byteIndex = i / 2;
18
18
  if (!/^[0-9a-fA-F]{2}$/.test(c)) {
19
19
  throw new Error(`hexDecode: invalid hex pair "${c}" in data "${data}" for bytes[${byteIndex}]`);
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  // Portable bootstrap for globals that must exist before assembly/portable executes.
2
3
  const g = globalThis;
3
4
  if (g.ASC_TARGET === undefined) {
@@ -376,7 +376,8 @@ if (typeof globalScope.ASC_TARGET === "undefined") {
376
376
  }, defaultComparator = function(a, b) {
377
377
  if (a == b) {
378
378
  if (a != 0) return 0;
379
- a = 1 / a, b = 1 / b;
379
+ a = 1 / a;
380
+ b = 1 / b;
380
381
  } else {
381
382
  let nanA = a != a, nanB = b != b;
382
383
  if (nanA | nanB) return nanA - nanB;
@@ -622,9 +623,9 @@ if (typeof globalScope.ASC_TARGET === "undefined") {
622
623
  }
623
624
  if (!String.prototype.replaceAll) {
624
625
  Object.defineProperty(String.prototype, "replaceAll", {
625
- value: function replaceAll(search, replacment) {
626
- let res = this.split(search).join(replacment);
627
- if (!search.length) res = replacment + res + replacment;
626
+ value: function replaceAll(search, replacement) {
627
+ let res = this.split(search).join(replacement);
628
+ if (!search.length) res = replacement + res + replacement;
628
629
  return res;
629
630
  },
630
631
  configurable: true
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ import * as assert from "node:assert";
3
+ import { parseTrace } from "../bin/src/trace-parse.js";
4
+ // Test: round-trip parse of a spec-compliant trace
5
+ {
6
+ const input = [
7
+ "program 0x0102aabbccddeeff",
8
+ "memwrite 0x00001000 len=8 <- 0x0000000000000001",
9
+ "start pc=0 gas=10000 r07=0x10 r09=0x10000",
10
+ "",
11
+ "ecalli=10 pc=42 gas=9980 r01=0x1 r03=0x1000",
12
+ "memread 0x00001000 len=4 -> 0x01020304",
13
+ "memread 0x00001020 len=8 -> 0x0000000000000040",
14
+ "memwrite 0x00002000 len=2 <- 0xffee",
15
+ "setreg r00 <- 0x100",
16
+ "setreg r02 <- 0x4",
17
+ "setgas <- 9950",
18
+ "",
19
+ "HALT pc=42 gas=9920 r00=0x100 r02=0x4",
20
+ ].join("\n");
21
+ const trace = parseTrace(input);
22
+ // Program
23
+ assert.deepStrictEqual(Array.from(trace.program), [0x01, 0x02, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff], "program bytes");
24
+ // Initial memwrite
25
+ assert.strictEqual(trace.initialMemWrites.length, 1, "one initial memwrite");
26
+ assert.strictEqual(trace.initialMemWrites[0].address, 0x00001000, "memwrite address");
27
+ assert.strictEqual(trace.initialMemWrites[0].data.length, 8, "memwrite data length");
28
+ // Start
29
+ assert.strictEqual(trace.start.pc, 0, "start pc");
30
+ assert.strictEqual(trace.start.gas, 10000n, "start gas");
31
+ assert.strictEqual(trace.start.registers.get(7), 0x10n, "start r07");
32
+ assert.strictEqual(trace.start.registers.get(9), 0x10000n, "start r09");
33
+ assert.strictEqual(trace.start.registers.has(0), false, "start r00 omitted (zero)");
34
+ // Ecalli
35
+ assert.strictEqual(trace.ecalliEntries.length, 1, "one ecalli");
36
+ const ecalli = trace.ecalliEntries[0];
37
+ assert.strictEqual(ecalli.index, 10, "ecalli index");
38
+ assert.strictEqual(ecalli.pc, 42, "ecalli pc");
39
+ assert.strictEqual(ecalli.gas, 9980n, "ecalli gas");
40
+ assert.strictEqual(ecalli.registers.get(1), 0x1n, "ecalli r01");
41
+ assert.strictEqual(ecalli.registers.get(3), 0x1000n, "ecalli r03");
42
+ // Memreads
43
+ assert.strictEqual(ecalli.memReads.length, 2, "two memreads");
44
+ assert.strictEqual(ecalli.memReads[0].address, 0x00001000, "memread 0 address");
45
+ assert.strictEqual(ecalli.memReads[0].data.length, 4, "memread 0 length");
46
+ assert.strictEqual(ecalli.memReads[1].address, 0x00001020, "memread 1 address");
47
+ // Memwrites
48
+ assert.strictEqual(ecalli.memWrites.length, 1, "one ecalli memwrite");
49
+ assert.strictEqual(ecalli.memWrites[0].address, 0x00002000, "ecalli memwrite address");
50
+ // Setregs
51
+ assert.strictEqual(ecalli.setRegs.length, 2, "two setregs");
52
+ assert.strictEqual(ecalli.setRegs[0].index, 0, "setreg 0 index");
53
+ assert.strictEqual(ecalli.setRegs[0].value, 0x100n, "setreg 0 value");
54
+ assert.strictEqual(ecalli.setRegs[1].index, 2, "setreg 1 index");
55
+ assert.strictEqual(ecalli.setRegs[1].value, 0x4n, "setreg 1 value");
56
+ // Setgas
57
+ assert.strictEqual(ecalli.setGas, 9950n, "setgas");
58
+ // Termination
59
+ assert.strictEqual(trace.termination.type, "HALT", "termination type");
60
+ assert.strictEqual(trace.termination.pc, 42, "termination pc");
61
+ assert.strictEqual(trace.termination.gas, 9920n, "termination gas");
62
+ assert.strictEqual(trace.termination.registers.get(0), 0x100n, "termination r00");
63
+ assert.strictEqual(trace.termination.registers.get(2), 0x4n, "termination r02");
64
+ console.log("PASS: spec example round-trip");
65
+ }
66
+ // Test: PANIC=0 (argument must always be present)
67
+ {
68
+ const input = ["program 0x00", "start pc=0 gas=100", "PANIC=0 pc=5 gas=50 r00=0x1"].join("\n");
69
+ const trace = parseTrace(input);
70
+ assert.strictEqual(trace.termination.type, "PANIC", "panic type");
71
+ assert.strictEqual(trace.termination.panicArg, 0, "panic arg is 0");
72
+ assert.strictEqual(trace.termination.pc, 5, "panic pc");
73
+ assert.strictEqual(trace.termination.gas, 50n, "panic gas");
74
+ console.log("PASS: PANIC=0");
75
+ }
76
+ // Test: PANIC with non-zero argument
77
+ {
78
+ const input = ["program 0x00", "start pc=0 gas=100", "PANIC=42 pc=10 gas=0"].join("\n");
79
+ const trace = parseTrace(input);
80
+ assert.strictEqual(trace.termination.panicArg, 42, "panic arg 42");
81
+ assert.strictEqual(trace.termination.registers.size, 0, "no registers");
82
+ console.log("PASS: PANIC=42");
83
+ }
84
+ // Test: OOG termination
85
+ {
86
+ const input = ["program 0x00", "start pc=0 gas=100", "OOG pc=99 gas=0"].join("\n");
87
+ const trace = parseTrace(input);
88
+ assert.strictEqual(trace.termination.type, "OOG", "OOG type");
89
+ assert.strictEqual(trace.termination.gas, 0n, "OOG gas is 0");
90
+ console.log("PASS: OOG");
91
+ }
92
+ // Test: lines with log prefixes are handled (extractPayload)
93
+ {
94
+ const input = [
95
+ "TRACE [ecalli] program 0xaa",
96
+ "TRACE [ecalli] start pc=0 gas=500 r00=0x1",
97
+ "TRACE [ecalli] HALT pc=10 gas=400",
98
+ ].join("\n");
99
+ const trace = parseTrace(input);
100
+ assert.deepStrictEqual(Array.from(trace.program), [0xaa], "prefixed program");
101
+ assert.strictEqual(trace.start.registers.get(0), 0x1n, "prefixed start r00");
102
+ assert.strictEqual(trace.termination.type, "HALT", "prefixed halt");
103
+ console.log("PASS: log-prefixed lines");
104
+ }
105
+ // Test: comment lines are ignored
106
+ {
107
+ const input = [
108
+ "comment implementation typeberry 0.8.3",
109
+ "comment chain-id fluffy-testnet",
110
+ "program 0xbb",
111
+ "comment accumulate",
112
+ "start pc=0 gas=100",
113
+ "HALT pc=5 gas=90",
114
+ ].join("\n");
115
+ const trace = parseTrace(input);
116
+ assert.deepStrictEqual(Array.from(trace.program), [0xbb], "comment lines ignored");
117
+ console.log("PASS: comment lines ignored");
118
+ }
119
+ // Test: zero-padded register indices in register dump
120
+ {
121
+ const input = ["program 0x00", "start pc=0 gas=100 r00=0xff r12=0x1", "HALT pc=5 gas=90 r00=0xff r12=0x1"].join("\n");
122
+ const trace = parseTrace(input);
123
+ assert.strictEqual(trace.start.registers.get(0), 0xffn, "r00 parsed");
124
+ assert.strictEqual(trace.start.registers.get(12), 0x1n, "r12 parsed");
125
+ console.log("PASS: zero-padded register indices");
126
+ }
127
+ // Test: empty register dump (all zeros)
128
+ {
129
+ const input = ["program 0x00", "start pc=0 gas=100", "HALT pc=5 gas=90"].join("\n");
130
+ const trace = parseTrace(input);
131
+ assert.strictEqual(trace.start.registers.size, 0, "empty register dump");
132
+ assert.strictEqual(trace.termination.registers.size, 0, "empty termination registers");
133
+ console.log("PASS: empty register dump");
134
+ }
135
+ // Test: multiple ecalli entries
136
+ {
137
+ const input = [
138
+ "program 0x00",
139
+ "start pc=0 gas=10000",
140
+ "ecalli=1 pc=10 gas=9000 r00=0x1",
141
+ "setreg r00 <- 0x2",
142
+ "ecalli=2 pc=20 gas=8000 r00=0x2",
143
+ "memwrite 0x00001000 len=1 <- 0xff",
144
+ "setgas <- 7900",
145
+ "HALT pc=30 gas=7800",
146
+ ].join("\n");
147
+ const trace = parseTrace(input);
148
+ assert.strictEqual(trace.ecalliEntries.length, 2, "two ecalli entries");
149
+ assert.strictEqual(trace.ecalliEntries[0].index, 1, "first ecalli index");
150
+ assert.strictEqual(trace.ecalliEntries[0].setRegs.length, 1, "first ecalli setregs");
151
+ assert.strictEqual(trace.ecalliEntries[0].setGas, undefined, "first ecalli no setgas");
152
+ assert.strictEqual(trace.ecalliEntries[1].index, 2, "second ecalli index");
153
+ assert.strictEqual(trace.ecalliEntries[1].memWrites.length, 1, "second ecalli memwrites");
154
+ assert.strictEqual(trace.ecalliEntries[1].setGas, 7900n, "second ecalli setgas");
155
+ console.log("PASS: multiple ecalli entries");
156
+ }
157
+ // Test: missing program throws
158
+ assert.throws(() => parseTrace("start pc=0 gas=100\nHALT pc=5 gas=90"), /Missing program/, "missing program");
159
+ console.log("PASS: missing program throws");
160
+ // Test: missing start throws
161
+ assert.throws(() => parseTrace("program 0x00\nHALT pc=5 gas=90"), /Missing start/, "missing start");
162
+ console.log("PASS: missing start throws");
163
+ // Test: missing termination throws
164
+ assert.throws(() => parseTrace("program 0x00\nstart pc=0 gas=100"), /Missing termination/, "missing termination");
165
+ console.log("PASS: missing termination throws");
166
+ console.log("\nAll trace format tests passed.");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fluffylabs/anan-as",
3
3
  "description": "AssemblyScript PVM interpreter.",
4
- "version": "1.2.0-abcb88c",
4
+ "version": "1.2.0-b6d0f93",
5
5
  "main": "./dist/bin/index.js",
6
6
  "bin": {
7
7
  "anan-as": "./dist/bin/index.js"
@@ -32,7 +32,7 @@
32
32
  "qa": "biome ci",
33
33
  "qa-fix": "npm run format; npm run lint",
34
34
  "start": "tsx ./bin/index.ts",
35
- "test": "npm run asbuild:test && tsx ./test/test-as.ts && tsx ./test/test-trace-replay.ts",
35
+ "test": "npm run asbuild:test && tsx ./test/test-as.ts && tsx ./test/test-trace-format.ts && tsx ./test/test-trace-replay.ts",
36
36
  "tsbuild": "tsc && npm run tsbuild:portable",
37
37
  "tsbuild:portable": "tsc --project portable/tsconfig.json && esbuild dist/build/js/portable/index.js --bundle --format=esm --platform=node --outfile=dist/build/js/portable-bundle.js",
38
38
  "test:w3f": "tsx ./test/test-w3f.ts",
@@ -53,14 +53,14 @@
53
53
  "node": ">=18.3.0"
54
54
  },
55
55
  "devDependencies": {
56
- "@biomejs/biome": "^2.4.5",
56
+ "@biomejs/biome": "^2.4.10",
57
57
  "@typeberry/lib": "^0.5.8",
58
58
  "@types/node": "^25.3.3",
59
59
  "assemblyscript": "^0.28.9",
60
- "esbuild": "^0.27.3",
60
+ "esbuild": "^0.28.0",
61
61
  "json-bigint-patch": "^0.0.8",
62
62
  "tsx": "^4.21.0",
63
- "typescript": "^5.9.3"
63
+ "typescript": "^6.0.2"
64
64
  },
65
65
  "files": [
66
66
  "dist/**/*.wasm",