Skip to content

Commit cefe26f

Browse files
committed
wip
1 parent 0e90a80 commit cefe26f

17 files changed

Lines changed: 630 additions & 570 deletions

.npmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ ignore-scripts=true ; https://docs.npmjs.com/cli/v11/using-npm/config#ignore
88
allow-git=none ; https://docs.npmjs.com/cli/v11/using-npm/config#allow-git
99

1010
; https://github.com/lirantal/npm-security-best-practices?tab=readme-ov-file#2-install-with-cooldown
11-
min-release-age=7 ; https://docs.npmjs.com/cli/v11/using-npm/config#min-release-age
11+
min-release-age=14 ; https://docs.npmjs.com/cli/v11/using-npm/config#min-release-age
1212

1313
; Ensures `npm` version declared in `engines` field supports all features listed here.
1414
engine-strict=true ; https://docs.npmjs.com/cli/v11/using-npm/config#engine-strict
File renamed without changes.

bin/.address.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function isValidAddress(address) {
1717
const keccakHash = js_sha3.keccak256(address_);
1818

1919
for (let i = 0; i < address_.length; i++) {
20-
let output = parseInt(keccakHash[i], 16) >= 8
20+
const output = parseInt(keccakHash[i], 16) >= 8
2121
? address_[i].toUpperCase()
2222
: address_[i]
2323
if (address[i] !== output) return false

bin/sevm.mjs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#!/usr/bin/env node
2-
/* eslint-env node */
32

43
import c from 'ansi-colors';
54
import assert from 'assert';
@@ -173,7 +172,6 @@ function make(handler) {
173172
let lookup;
174173
if (cache && existsSync(abiPath)) {
175174
trace('Found ABI cache %s', abiPath);
176-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
177175
lookup = JSON.parse(readFileSync(abiPath, 'utf8'));
178176
} else {
179177
if (cache) trace('ABI cache %s not found', abiPath);
@@ -192,7 +190,6 @@ function make(handler) {
192190

193191
handler(contract, argv);
194192
} catch (err) {
195-
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
196193
console.error(`${err}`);
197194
process.exit(1);
198195
}
@@ -367,7 +364,7 @@ function cfg(contract) {
367364
for (const [pc, block] of contract.blocks) {
368365
write(`subgraph cluster_${pc} {`);
369366
write(` style=filled;`);
370-
let label = `pc ${pc}\\l`;
367+
const label = `pc ${pc}\\l`;
371368

372369
// for (let i = pc; i < chunk.pcend; i++) {
373370
// const opcode = evm.opcodes[i];

eslint.config.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
import { defineConfig } from "eslint/config";
1+
import { defineConfig, globalIgnores } from "eslint/config";
22
import tseslint from 'typescript-eslint';
33

44
export default defineConfig(
5-
tseslint.configs.recommended,
5+
globalIgnores([
6+
'.solc/',
7+
'coverage/',
8+
]),
9+
tseslint.configs.recommended,
10+
// tseslint.configs.recommendedTypeChecked,
11+
// tseslint.configs.strict,
12+
// tseslint.configs.strictTypeChecked,
13+
// tseslint.configs.stylisticTypeChecked,
614
{
715
rules: {
816
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: "^_" }],

src/decode.ts renamed to src/decode.ts32

Lines changed: 2 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,13 @@
11
import { arrayify, hexlify } from './bytes.ts';
22

3-
/**
4-
* Represents an opcode found in the bytecode augmented with
5-
* offset and operand information as defined by the EVM.
6-
*
7-
* It can be either a unary opcode, _which does not take any operand data_,
8-
* or either a `PUSHn` mnemonic augmented with its push `data`.
9-
* That is, all but `PUSHn` `n >= 1` opcodes are unary opcodes.
10-
*
11-
* `PUSHn` `n >= 1` opcodes takes an `n`-byte argument from the bytecode.
12-
* Note that `PUSH0`[^1] does not take any data argument from the bytecode (just pushes `0` onto the `Stack`).
13-
* Thus it can be considered as an unary opcode.
14-
*
15-
* [^1]: https://eips.ethereum.org/EIPS/eip-3855
16-
*/
17-
export interface IOpcode<M> {
18-
19-
/**
20-
* This is the offset in the bytecode where this `Opcode` was found.
21-
* Both jump instructions, _i.e._, `JUMP` and `JUMPI`,
22-
* expects a stack operand referencing this `offset` in the bytecode.
23-
*
24-
* The Program Counter of this `Opcode`.
25-
* The index in the `Opcode[]` where this `Opcode` is inserted.
26-
*/
27-
readonly pc: number;
28-
29-
/**
30-
* Any byte number, _i.e._, between 0 and 255 representing the opcode byte.
31-
* The `opcode` may not be a valid opcode according to the decoder definition.
32-
*/
3+
export interface IOpcode<M = string> {
334
readonly opcode: number;
34-
35-
/**
36-
* Represents a valid opcode.
37-
*
38-
* In https://www.evm.codes/ you can find an overview of each EVM opcode.
39-
*
40-
* If the `opcode` given is not a valid opcode,
41-
* you can provide `INVALID` as `mnemonic`.
42-
*
43-
* A `PUSHn` opcode only permits a `PUSHn` opcode.
44-
*/
455
readonly mnemonic: M;
46-
47-
/**
48-
* Where the next opcode should be located at.
49-
*/
506
readonly nextpc: number;
51-
52-
/**
53-
* A `Unary` opcode does not include any `data`. For these opcodes `data` is `null`.
54-
*
55-
* If this `Opcode` is a `PUSHn` instruction or contains any operand data,
56-
* then it contains the data attached to this instruction.
57-
*/
58-
// readonly data: null;
59-
60-
/**
61-
* Returns the hexadecimal representation of `this.data`.
62-
*/
63-
// hexData(): string | undefined;
647
}
658

66-
/**
67-
*
68-
*/
69-
export interface IPushOpcode<M> extends IOpcode<M> {
70-
71-
/**
72-
* A `Unary` opcode does not include any `data`. For these opcodes `data` is `null`.
73-
*
74-
* If this `Opcode` is a `PUSHn` instruction or contains any operand data,
75-
* then it contains the data attached to this instruction.
76-
*/
9+
export interface IPushOpcode<M = string> extends IOpcode<M> {
7710
readonly data: Uint8Array;
78-
79-
/**
80-
* Returns the hexadecimal representation of `this.data`.
81-
*/
8211
hexData(): string;
8312
};
8413

@@ -309,46 +238,11 @@ export class Opcodes<U extends string, P extends string> {
309238
return this._decode.opcodes[this.mnemonic];
310239
}
311240

312-
/**
313-
* Where the next opcode should be located at.
314-
*/
315-
get nextpc(): number {
316-
return this.pc + (this.data?.length ?? 0) + 1;
317-
}
318-
319-
/**
320-
* Returns the hexadecimal representation of `this.data`.
321-
*/
322-
hexData(): string | undefined {
323-
return this.data === null ? undefined : hexlify(this.data);
324-
}
325241

326242
// is<T extends X>(mnemonic: T): this is Op2<T & (U | P)> {
327243
// return this.mnemonic === mnemonic;
328244
// }
329245

330-
/**
331-
* Returns a `string` representation of `this` `Opcode`.
332-
* Usually used for debugging purposes.
333-
*
334-
* @param includeDataAsNumeric whether to include `data` as numeric.
335-
* @returns the `string` representation of `this` `Opcode`.
336-
*/
337-
format(includeDataAsNumeric = true): string {
338-
const pushData = this.data
339-
? ` 0x${this.hexData()}` + (includeDataAsNumeric
340-
? ` (${parseInt(this.hexData()!, 16)})`
341-
: '')
342-
: '';
343246

344-
return `${this.mnemonic}(0x${this.opcode.toString(16)})@${this.pc}${pushData}`;
345-
}
346-
347-
toString() {
348-
const pc = this.pc.toString().padStart(2, '0');
349-
const opcode = this.opcode.toString(16).padStart(2, '0');
350-
const pushData = this.data ? ` (${parseInt(this.hexData()!, 16)})` : '';
351-
return `${pc}: <${opcode}>${this.mnemonic}${pushData}`;
352-
}
353247
}
354248
}

src/dispatch.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { arrayify, hexlify } from "./bytes.ts";
2+
3+
/**
4+
*
5+
*/
6+
export type OpsDef = { readonly [m: string]: { op: number, size?: number } };
7+
8+
/**
9+
* Represents an opcode found in the bytecode augmented with
10+
* offset and operand information as defined by the EVM.
11+
*
12+
* It can be either a unary opcode, _which does not take any operand data_,
13+
* or either a `PUSHn` mnemonic augmented with its push `data`.
14+
* That is, all but `PUSHn` `n >= 1` opcodes are unary opcodes.
15+
*
16+
* `PUSHn` `n >= 1` opcodes takes an `n`-byte argument from the bytecode.
17+
* Note that `PUSH0`[^1] does not take any data argument from the bytecode (just pushes `0` onto the `Stack`).
18+
* Thus it can be considered as an unary opcode.
19+
*
20+
* [^1]: https://eips.ethereum.org/EIPS/eip-3855
21+
*/
22+
export class Opcode {
23+
/**
24+
* This is the offset in the bytecode where this `Opcode` was found.
25+
* Both jump instructions, _i.e._, `JUMP` and `JUMPI`,
26+
* expects a stack operand referencing this `offset` in the bytecode.
27+
*
28+
* The Program Counter of this `Opcode`.
29+
* The index in the `Opcode[]` where this `Opcode` is inserted.
30+
*/
31+
readonly pc: number;
32+
33+
/**
34+
* Any byte number, _i.e._, between 0 and 255 representing the opcode byte.
35+
* The `opcode` may not be a valid opcode according to the decoder definition.
36+
*/
37+
readonly opcode;
38+
39+
/**
40+
* Represents a valid opcode.
41+
*
42+
* In https://www.evm.codes/ you can find an overview of each EVM opcode.
43+
*
44+
* If the `opcode` given is not a valid opcode,
45+
* you can provide `INVALID` as `mnemonic`.
46+
*
47+
* A `PUSHn` opcode only permits a `PUSHn` opcode.
48+
*/
49+
readonly mnemonic;
50+
51+
/**
52+
* A `Unary` opcode does not include any `data`. For these opcodes `data` is `null`.
53+
*
54+
* If this `Opcode` is a `PUSHn` instruction or contains any operand data,
55+
* then it contains the data attached to this instruction.
56+
*/
57+
readonly data;
58+
constructor(pc: number, opcode: number, mnemonic: string, data?: Uint8Array) {
59+
this.pc = pc;
60+
this.opcode = opcode;
61+
this.mnemonic = mnemonic;
62+
this.data = data;
63+
}
64+
65+
/**
66+
* Where the next opcode should be located at.
67+
*/
68+
get nextpc(): number {
69+
return this.pc + (this.data?.length ?? 0) + 1;
70+
}
71+
72+
/**
73+
* Returns the hexadecimal representation of `this.data`.
74+
*/
75+
hexData(): string | undefined {
76+
return this.data === undefined ? undefined : hexlify(this.data);
77+
}
78+
79+
/**
80+
* Returns a `string` representation of `this` `Opcode`.
81+
* Usually used for debugging purposes.
82+
*
83+
* @returns the `string` representation of `this` `Opcode`.
84+
*/
85+
toString() {
86+
const pc = this.pc.toString().padStart(2, '0');
87+
const opcode = this.opcode.toString(16).padStart(2, '0');
88+
const pushData = this.data ? ` (${parseInt(this.hexData()!, 16)})` : '';
89+
return `${pc}: <${opcode}>${this.mnemonic}${pushData}`;
90+
}
91+
}
92+
/**
93+
*
94+
*/
95+
export class Dispatch<T extends OpsDef> {
96+
97+
/**
98+
*
99+
*/
100+
readonly dispatch: { size?: number, mnemonic: keyof T & string }[];
101+
102+
/**
103+
*
104+
*/
105+
readonly def: T;
106+
107+
constructor(def: T) {
108+
this.dispatch = Array(256).fill({ mnemonic: 'INVALID' });
109+
this.def = def;
110+
Object.assign(this.dispatch, Object.fromEntries(
111+
Object.entries(def).map(
112+
([mnemonic, entry]) => [entry.op, { size: entry.size, mnemonic }] as const
113+
)
114+
));
115+
}
116+
117+
fork<U extends OpsDef>(def: U): Dispatch<T & U> {
118+
return new Dispatch(Object.assign({ ...this.def }, def));
119+
}
120+
121+
*decode(bytecode: Parameters<typeof arrayify>[0], begin: number = 0) {
122+
const buffer = arrayify(bytecode);
123+
124+
for (let pc = begin; pc < buffer.length; pc++) {
125+
const opcode = buffer[pc];
126+
const { size, mnemonic } = this.dispatch[opcode];
127+
yield new Opcode(
128+
pc,
129+
opcode,
130+
mnemonic,
131+
size === undefined ? undefined : (() => {
132+
const data = buffer.subarray(pc + 1, pc + size + 1);
133+
if (data.length !== size) {
134+
const op = new Opcode(pc, opcode, mnemonic, data);
135+
throw new Error(`Trying to get \`${size}\` bytes but got only \`${data.length}\` while decoding \`${op}\` before reaching the end of bytecode`);
136+
}
137+
pc += size;
138+
return data;
139+
})(),
140+
);
141+
}
142+
}
143+
}

0 commit comments

Comments
 (0)