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