1#[cfg(feature = "parse")]
4pub mod parse;
5
6use core::{fmt, ptr::NonNull};
7
8#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
13#[repr(transparent)]
14pub struct OpCode(u8);
15
16impl fmt::Display for OpCode {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 let n = self.get();
19 if let Some(val) = OPCODE_INFO[n as usize] {
20 f.write_str(val.name())
21 } else {
22 write!(f, "UNKNOWN(0x{n:02X})")
23 }
24 }
25}
26
27impl OpCode {
28 #[inline]
30 pub const fn new(opcode: u8) -> Option<Self> {
31 match OPCODE_INFO[opcode as usize] {
32 Some(_) => Some(Self(opcode)),
33 None => None,
34 }
35 }
36
37 #[inline]
39 pub const fn is_jumpdest(&self) -> bool {
40 self.0 == JUMPDEST
41 }
42
43 #[inline]
45 pub const fn is_jumpdest_by_op(opcode: u8) -> bool {
46 if let Some(opcode) = Self::new(opcode) {
47 opcode.is_jumpdest()
48 } else {
49 false
50 }
51 }
52
53 #[inline]
55 pub const fn is_jump(self) -> bool {
56 self.0 == JUMP
57 }
58
59 #[inline]
61 pub const fn is_jump_by_op(opcode: u8) -> bool {
62 if let Some(opcode) = Self::new(opcode) {
63 opcode.is_jump()
64 } else {
65 false
66 }
67 }
68
69 #[inline]
71 pub const fn is_push(self) -> bool {
72 self.0 >= PUSH1 && self.0 <= PUSH32
73 }
74
75 #[inline]
77 pub fn is_push_by_op(opcode: u8) -> bool {
78 if let Some(opcode) = Self::new(opcode) {
79 opcode.is_push()
80 } else {
81 false
82 }
83 }
84
85 #[inline]
92 pub unsafe fn new_unchecked(opcode: u8) -> Self {
93 Self(opcode)
94 }
95
96 #[doc(alias = "name")]
98 #[inline]
99 pub const fn as_str(self) -> &'static str {
100 self.info().name()
101 }
102
103 #[inline]
105 pub const fn name_by_op(opcode: u8) -> &'static str {
106 if let Some(opcode) = Self::new(opcode) {
107 opcode.as_str()
108 } else {
109 "Unknown"
110 }
111 }
112
113 #[inline]
115 pub const fn inputs(&self) -> u8 {
116 self.info().inputs()
117 }
118
119 #[inline]
121 pub const fn outputs(&self) -> u8 {
122 self.info().outputs()
123 }
124
125 #[inline]
127 pub const fn io_diff(&self) -> i16 {
128 self.info().io_diff()
129 }
130
131 #[inline]
133 pub const fn info_by_op(opcode: u8) -> Option<OpCodeInfo> {
134 if let Some(opcode) = Self::new(opcode) {
135 Some(opcode.info())
136 } else {
137 None
138 }
139 }
140
141 #[inline]
143 pub const fn as_usize(&self) -> usize {
144 self.0 as usize
145 }
146
147 #[inline]
149 pub const fn info(&self) -> OpCodeInfo {
150 if let Some(t) = OPCODE_INFO[self.0 as usize] {
151 t
152 } else {
153 panic!("opcode not found")
154 }
155 }
156
157 pub const fn input_output(&self) -> (u8, u8) {
161 let info = self.info();
162 (info.inputs, info.outputs)
163 }
164
165 #[inline]
167 pub const fn get(self) -> u8 {
168 self.0
169 }
170
171 #[inline]
177 pub const fn modifies_memory(&self) -> bool {
178 matches!(
179 *self,
180 OpCode::EXTCODECOPY
181 | OpCode::MLOAD
182 | OpCode::MSTORE
183 | OpCode::MSTORE8
184 | OpCode::MCOPY
185 | OpCode::CODECOPY
186 | OpCode::CALLDATACOPY
187 | OpCode::RETURNDATACOPY
188 | OpCode::CALL
189 | OpCode::CALLCODE
190 | OpCode::DELEGATECALL
191 | OpCode::STATICCALL
192 | OpCode::DATACOPY
193 | OpCode::EOFCREATE
194 | OpCode::RETURNCONTRACT
195 | OpCode::EXTCALL
196 | OpCode::EXTDELEGATECALL
197 | OpCode::EXTSTATICCALL
198 )
199 }
200}
201
202#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
204pub struct OpCodeInfo {
205 name_ptr: NonNull<u8>,
210 name_len: u8,
211 inputs: u8,
213 outputs: u8,
215 immediate_size: u8,
220 not_eof: bool,
224 terminating: bool,
226}
227
228impl fmt::Debug for OpCodeInfo {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 f.debug_struct("OpCodeInfo")
231 .field("name", &self.name())
232 .field("inputs", &self.inputs())
233 .field("outputs", &self.outputs())
234 .field("not_eof", &self.is_disabled_in_eof())
235 .field("terminating", &self.is_terminating())
236 .field("immediate_size", &self.immediate_size())
237 .finish()
238 }
239}
240
241impl OpCodeInfo {
242 pub const fn new(name: &'static str) -> Self {
244 assert!(name.len() < 256, "opcode name is too long");
245 Self {
246 name_ptr: unsafe { NonNull::new_unchecked(name.as_ptr().cast_mut()) },
247 name_len: name.len() as u8,
248 inputs: 0,
249 outputs: 0,
250 not_eof: false,
251 terminating: false,
252 immediate_size: 0,
253 }
254 }
255
256 #[inline]
258 pub const fn name(&self) -> &'static str {
259 unsafe {
261 let slice = core::slice::from_raw_parts(self.name_ptr.as_ptr(), self.name_len as usize);
263 core::str::from_utf8_unchecked(slice)
264 }
265 }
266
267 #[inline]
269 pub const fn io_diff(&self) -> i16 {
270 self.outputs as i16 - self.inputs as i16
271 }
272
273 #[inline]
275 pub const fn inputs(&self) -> u8 {
276 self.inputs
277 }
278
279 #[inline]
281 pub const fn outputs(&self) -> u8 {
282 self.outputs
283 }
284
285 #[inline]
287 pub const fn is_disabled_in_eof(&self) -> bool {
288 self.not_eof
289 }
290
291 #[inline]
293 pub const fn is_terminating(&self) -> bool {
294 self.terminating
295 }
296
297 #[inline]
299 pub const fn immediate_size(&self) -> u8 {
300 self.immediate_size
301 }
302}
303
304#[inline]
306pub const fn not_eof(mut op: OpCodeInfo) -> OpCodeInfo {
307 op.not_eof = true;
308 op
309}
310
311#[inline]
316pub const fn immediate_size(mut op: OpCodeInfo, n: u8) -> OpCodeInfo {
317 op.immediate_size = n;
318 op
319}
320
321#[inline]
323pub const fn terminating(mut op: OpCodeInfo) -> OpCodeInfo {
324 op.terminating = true;
325 op
326}
327
328#[inline]
330pub const fn stack_io(mut op: OpCodeInfo, inputs: u8, outputs: u8) -> OpCodeInfo {
331 op.inputs = inputs;
332 op.outputs = outputs;
333 op
334}
335
336pub const NOP: u8 = JUMPDEST;
338
339macro_rules! opcodes {
340 ($($val:literal => $name:ident => $($modifier:ident $(( $($modifier_arg:expr),* ))?),*);* $(;)?) => {
341 $(
343 #[doc = concat!("The `", stringify!($val), "` (\"", stringify!($name),"\") opcode.")]
344 pub const $name: u8 = $val;
345 )*
346 impl OpCode {$(
347 #[doc = concat!("The `", stringify!($val), "` (\"", stringify!($name),"\") opcode.")]
348 pub const $name: Self = Self($val);
349 )*}
350
351 pub const OPCODE_INFO: [Option<OpCodeInfo>; 256] = {
353 let mut map = [None; 256];
354 let mut prev: u8 = 0;
355 $(
356 let val: u8 = $val;
357 assert!(val == 0 || val > prev, "opcodes must be sorted in ascending order");
358 prev = val;
359 let info = OpCodeInfo::new(stringify!($name));
360 $(
361 let info = $modifier(info, $($($modifier_arg),*)?);
362 )*
363 map[$val] = Some(info);
364 )*
365 let _ = prev;
366 map
367 };
368
369
370 #[cfg(feature = "parse")]
372 pub(crate) static NAME_TO_OPCODE: phf::Map<&'static str, OpCode> = stringify_with_cb! { phf_map_cb; $($name)* };
373 };
374}
375
376#[cfg(feature = "parse")]
378macro_rules! phf_map_cb {
379 ($(#[doc = $s:literal] $id:ident)*) => {
380 phf::phf_map! {
381 $($s => OpCode::$id),*
382 }
383 };
384}
385
386#[cfg(feature = "parse")]
390macro_rules! stringify_with_cb {
391 ($callback:ident; $($id:ident)*) => { paste::paste! {
392 $callback! { $(#[doc = "" $id ""] $id)* }
393 }};
394}
395
396opcodes! {
401 0x00 => STOP => stack_io(0, 0), terminating;
402 0x01 => ADD => stack_io(2, 1);
403 0x02 => MUL => stack_io(2, 1);
404 0x03 => SUB => stack_io(2, 1);
405 0x04 => DIV => stack_io(2, 1);
406 0x05 => SDIV => stack_io(2, 1);
407 0x06 => MOD => stack_io(2, 1);
408 0x07 => SMOD => stack_io(2, 1);
409 0x08 => ADDMOD => stack_io(3, 1);
410 0x09 => MULMOD => stack_io(3, 1);
411 0x0A => EXP => stack_io(2, 1);
412 0x0B => SIGNEXTEND => stack_io(2, 1);
413 0x10 => LT => stack_io(2, 1);
418 0x11 => GT => stack_io(2, 1);
419 0x12 => SLT => stack_io(2, 1);
420 0x13 => SGT => stack_io(2, 1);
421 0x14 => EQ => stack_io(2, 1);
422 0x15 => ISZERO => stack_io(1, 1);
423 0x16 => AND => stack_io(2, 1);
424 0x17 => OR => stack_io(2, 1);
425 0x18 => XOR => stack_io(2, 1);
426 0x19 => NOT => stack_io(1, 1);
427 0x1A => BYTE => stack_io(2, 1);
428 0x1B => SHL => stack_io(2, 1);
429 0x1C => SHR => stack_io(2, 1);
430 0x1D => SAR => stack_io(2, 1);
431 0x20 => KECCAK256 => stack_io(2, 1);
434 0x30 => ADDRESS => stack_io(0, 1);
450 0x31 => BALANCE => stack_io(1, 1);
451 0x32 => ORIGIN => stack_io(0, 1);
452 0x33 => CALLER => stack_io(0, 1);
453 0x34 => CALLVALUE => stack_io(0, 1);
454 0x35 => CALLDATALOAD => stack_io(1, 1);
455 0x36 => CALLDATASIZE => stack_io(0, 1);
456 0x37 => CALLDATACOPY => stack_io(3, 0);
457 0x38 => CODESIZE => stack_io(0, 1), not_eof;
458 0x39 => CODECOPY => stack_io(3, 0), not_eof;
459
460 0x3A => GASPRICE => stack_io(0, 1);
461 0x3B => EXTCODESIZE => stack_io(1, 1), not_eof;
462 0x3C => EXTCODECOPY => stack_io(4, 0), not_eof;
463 0x3D => RETURNDATASIZE => stack_io(0, 1);
464 0x3E => RETURNDATACOPY => stack_io(3, 0);
465 0x3F => EXTCODEHASH => stack_io(1, 1), not_eof;
466 0x40 => BLOCKHASH => stack_io(1, 1);
467 0x41 => COINBASE => stack_io(0, 1);
468 0x42 => TIMESTAMP => stack_io(0, 1);
469 0x43 => NUMBER => stack_io(0, 1);
470 0x44 => DIFFICULTY => stack_io(0, 1);
471 0x45 => GASLIMIT => stack_io(0, 1);
472 0x46 => CHAINID => stack_io(0, 1);
473 0x47 => SELFBALANCE => stack_io(0, 1);
474 0x48 => BASEFEE => stack_io(0, 1);
475 0x49 => BLOBHASH => stack_io(1, 1);
476 0x4A => BLOBBASEFEE => stack_io(0, 1);
477 0x50 => POP => stack_io(1, 0);
483 0x51 => MLOAD => stack_io(1, 1);
484 0x52 => MSTORE => stack_io(2, 0);
485 0x53 => MSTORE8 => stack_io(2, 0);
486 0x54 => SLOAD => stack_io(1, 1);
487 0x55 => SSTORE => stack_io(2, 0);
488 0x56 => JUMP => stack_io(1, 0), not_eof;
489 0x57 => JUMPI => stack_io(2, 0), not_eof;
490 0x58 => PC => stack_io(0, 1), not_eof;
491 0x59 => MSIZE => stack_io(0, 1);
492 0x5A => GAS => stack_io(0, 1), not_eof;
493 0x5B => JUMPDEST => stack_io(0, 0);
494 0x5C => TLOAD => stack_io(1, 1);
495 0x5D => TSTORE => stack_io(2, 0);
496 0x5E => MCOPY => stack_io(3, 0);
497
498 0x5F => PUSH0 => stack_io(0, 1);
499 0x60 => PUSH1 => stack_io(0, 1), immediate_size(1);
500 0x61 => PUSH2 => stack_io(0, 1), immediate_size(2);
501 0x62 => PUSH3 => stack_io(0, 1), immediate_size(3);
502 0x63 => PUSH4 => stack_io(0, 1), immediate_size(4);
503 0x64 => PUSH5 => stack_io(0, 1), immediate_size(5);
504 0x65 => PUSH6 => stack_io(0, 1), immediate_size(6);
505 0x66 => PUSH7 => stack_io(0, 1), immediate_size(7);
506 0x67 => PUSH8 => stack_io(0, 1), immediate_size(8);
507 0x68 => PUSH9 => stack_io(0, 1), immediate_size(9);
508 0x69 => PUSH10 => stack_io(0, 1), immediate_size(10);
509 0x6A => PUSH11 => stack_io(0, 1), immediate_size(11);
510 0x6B => PUSH12 => stack_io(0, 1), immediate_size(12);
511 0x6C => PUSH13 => stack_io(0, 1), immediate_size(13);
512 0x6D => PUSH14 => stack_io(0, 1), immediate_size(14);
513 0x6E => PUSH15 => stack_io(0, 1), immediate_size(15);
514 0x6F => PUSH16 => stack_io(0, 1), immediate_size(16);
515 0x70 => PUSH17 => stack_io(0, 1), immediate_size(17);
516 0x71 => PUSH18 => stack_io(0, 1), immediate_size(18);
517 0x72 => PUSH19 => stack_io(0, 1), immediate_size(19);
518 0x73 => PUSH20 => stack_io(0, 1), immediate_size(20);
519 0x74 => PUSH21 => stack_io(0, 1), immediate_size(21);
520 0x75 => PUSH22 => stack_io(0, 1), immediate_size(22);
521 0x76 => PUSH23 => stack_io(0, 1), immediate_size(23);
522 0x77 => PUSH24 => stack_io(0, 1), immediate_size(24);
523 0x78 => PUSH25 => stack_io(0, 1), immediate_size(25);
524 0x79 => PUSH26 => stack_io(0, 1), immediate_size(26);
525 0x7A => PUSH27 => stack_io(0, 1), immediate_size(27);
526 0x7B => PUSH28 => stack_io(0, 1), immediate_size(28);
527 0x7C => PUSH29 => stack_io(0, 1), immediate_size(29);
528 0x7D => PUSH30 => stack_io(0, 1), immediate_size(30);
529 0x7E => PUSH31 => stack_io(0, 1), immediate_size(31);
530 0x7F => PUSH32 => stack_io(0, 1), immediate_size(32);
531
532 0x80 => DUP1 => stack_io(1, 2);
533 0x81 => DUP2 => stack_io(2, 3);
534 0x82 => DUP3 => stack_io(3, 4);
535 0x83 => DUP4 => stack_io(4, 5);
536 0x84 => DUP5 => stack_io(5, 6);
537 0x85 => DUP6 => stack_io(6, 7);
538 0x86 => DUP7 => stack_io(7, 8);
539 0x87 => DUP8 => stack_io(8, 9);
540 0x88 => DUP9 => stack_io(9, 10);
541 0x89 => DUP10 => stack_io(10, 11);
542 0x8A => DUP11 => stack_io(11, 12);
543 0x8B => DUP12 => stack_io(12, 13);
544 0x8C => DUP13 => stack_io(13, 14);
545 0x8D => DUP14 => stack_io(14, 15);
546 0x8E => DUP15 => stack_io(15, 16);
547 0x8F => DUP16 => stack_io(16, 17);
548
549 0x90 => SWAP1 => stack_io(2, 2);
550 0x91 => SWAP2 => stack_io(3, 3);
551 0x92 => SWAP3 => stack_io(4, 4);
552 0x93 => SWAP4 => stack_io(5, 5);
553 0x94 => SWAP5 => stack_io(6, 6);
554 0x95 => SWAP6 => stack_io(7, 7);
555 0x96 => SWAP7 => stack_io(8, 8);
556 0x97 => SWAP8 => stack_io(9, 9);
557 0x98 => SWAP9 => stack_io(10, 10);
558 0x99 => SWAP10 => stack_io(11, 11);
559 0x9A => SWAP11 => stack_io(12, 12);
560 0x9B => SWAP12 => stack_io(13, 13);
561 0x9C => SWAP13 => stack_io(14, 14);
562 0x9D => SWAP14 => stack_io(15, 15);
563 0x9E => SWAP15 => stack_io(16, 16);
564 0x9F => SWAP16 => stack_io(17, 17);
565
566 0xA0 => LOG0 => stack_io(2, 0);
567 0xA1 => LOG1 => stack_io(3, 0);
568 0xA2 => LOG2 => stack_io(4, 0);
569 0xA3 => LOG3 => stack_io(5, 0);
570 0xA4 => LOG4 => stack_io(6, 0);
571 0xD0 => DATALOAD=> stack_io(1, 1);
615 0xD1 => DATALOADN => stack_io(0, 1), immediate_size(2);
616 0xD2 => DATASIZE=> stack_io(0, 1);
617 0xD3 => DATACOPY=> stack_io(3, 0);
618 0xE0 => RJUMP => stack_io(0, 0), immediate_size(2), terminating;
631 0xE1 => RJUMPI => stack_io(1, 0), immediate_size(2);
632 0xE2 => RJUMPV => stack_io(1, 0), immediate_size(1);
633 0xE3 => CALLF => stack_io(0, 0), immediate_size(2);
634 0xE4 => RETF => stack_io(0, 0), terminating;
635 0xE5 => JUMPF => stack_io(0, 0), immediate_size(2), terminating;
636 0xE6 => DUPN => stack_io(0, 1), immediate_size(1);
637 0xE7 => SWAPN => stack_io(0, 0), immediate_size(1);
638 0xE8 => EXCHANGE => stack_io(0, 0), immediate_size(1);
639 0xEC => EOFCREATE => stack_io(4, 1), immediate_size(1);
643 0xEE => RETURNCONTRACT => stack_io(2, 0), immediate_size(1), terminating;
645 0xF0 => CREATE => stack_io(3, 1), not_eof;
647 0xF1 => CALL => stack_io(7, 1), not_eof;
648 0xF2 => CALLCODE => stack_io(7, 1), not_eof;
649 0xF3 => RETURN => stack_io(2, 0), terminating;
650 0xF4 => DELEGATECALL => stack_io(6, 1), not_eof;
651 0xF5 => CREATE2 => stack_io(4, 1), not_eof;
652 0xF7 => RETURNDATALOAD => stack_io(1, 1);
654 0xF8 => EXTCALL => stack_io(4, 1);
655 0xF9 => EXTDELEGATECALL => stack_io(3, 1);
656 0xFA => STATICCALL => stack_io(6, 1), not_eof;
657 0xFB => EXTSTATICCALL => stack_io(3, 1);
658 0xFD => REVERT => stack_io(2, 0), terminating;
660 0xFE => INVALID => stack_io(0, 0), terminating;
661 0xFF => SELFDESTRUCT => stack_io(1, 0), not_eof, terminating;
662}
663
664#[cfg(test)]
665mod tests {
666 use super::*;
667
668 #[test]
669 fn test_opcode() {
670 let opcode = OpCode::new(0x00).unwrap();
671 assert!(!opcode.is_jumpdest());
672 assert!(!opcode.is_jump());
673 assert!(!opcode.is_push());
674 assert_eq!(opcode.as_str(), "STOP");
675 assert_eq!(opcode.get(), 0x00);
676 }
677
678 #[test]
679 fn test_eof_disable() {
680 const REJECTED_IN_EOF: &[u8] = &[
681 0x38, 0x39, 0x3b, 0x3c, 0x3f, 0x5a, 0xf1, 0xf2, 0xf4, 0xfa, 0xff,
682 ];
683
684 for opcode in REJECTED_IN_EOF {
685 let opcode = OpCode::new(*opcode).unwrap();
686 assert!(
687 opcode.info().is_disabled_in_eof(),
688 "not disabled in EOF: {opcode:#?}",
689 );
690 }
691 }
692
693 #[test]
694 fn test_immediate_size() {
695 let mut expected = [0u8; 256];
696 for push in PUSH1..=PUSH32 {
698 expected[push as usize] = push - PUSH1 + 1;
699 }
700 expected[DATALOADN as usize] = 2;
701 expected[RJUMP as usize] = 2;
702 expected[RJUMPI as usize] = 2;
703 expected[RJUMPV as usize] = 1;
704 expected[CALLF as usize] = 2;
705 expected[JUMPF as usize] = 2;
706 expected[DUPN as usize] = 1;
707 expected[SWAPN as usize] = 1;
708 expected[EXCHANGE as usize] = 1;
709 expected[EOFCREATE as usize] = 1;
710 expected[RETURNCONTRACT as usize] = 1;
711
712 for (i, opcode) in OPCODE_INFO.iter().enumerate() {
713 if let Some(opcode) = opcode {
714 assert_eq!(
715 opcode.immediate_size(),
716 expected[i],
717 "immediate_size check failed for {opcode:#?}",
718 );
719 }
720 }
721 }
722
723 #[test]
724 fn test_enabled_opcodes() {
725 let opcodes = [
727 0x10..=0x1d,
728 0x20..=0x20,
729 0x30..=0x3f,
730 0x40..=0x48,
731 0x50..=0x5b,
732 0x54..=0x5f,
733 0x60..=0x6f,
734 0x70..=0x7f,
735 0x80..=0x8f,
736 0x90..=0x9f,
737 0xa0..=0xa4,
738 0xf0..=0xf5,
739 0xfa..=0xfa,
740 0xfd..=0xfd,
741 0xff..=0xff,
743 ];
744 for i in opcodes {
745 for opcode in i {
746 OpCode::new(opcode).expect("Opcode should be valid and enabled");
747 }
748 }
749 }
750
751 #[test]
752 fn count_opcodes() {
753 let mut opcode_num = 0;
754 let mut eof_opcode_num = 0;
755 for opcode in OPCODE_INFO.into_iter().flatten() {
756 opcode_num += 1;
757 if !opcode.is_disabled_in_eof() {
758 eof_opcode_num += 1;
759 }
760 }
761 assert_eq!(opcode_num, 168);
762 assert_eq!(eof_opcode_num, 152);
763 }
764
765 #[test]
766 fn test_terminating_opcodes() {
767 let terminating = [
768 RETF,
769 REVERT,
770 RETURN,
771 INVALID,
772 SELFDESTRUCT,
773 RETURNCONTRACT,
774 STOP,
775 RJUMP,
776 JUMPF,
777 ];
778 let mut opcodes = [false; 256];
779 for terminating in terminating.iter() {
780 opcodes[*terminating as usize] = true;
781 }
782
783 for (i, opcode) in OPCODE_INFO.into_iter().enumerate() {
784 assert_eq!(
785 opcode.map(|opcode| opcode.terminating).unwrap_or_default(),
786 opcodes[i],
787 "Opcode {:?} terminating check failed.",
788 opcode
789 );
790 }
791 }
792
793 #[test]
794 #[cfg(feature = "parse")]
795 fn test_parsing() {
796 for i in 0..=u8::MAX {
797 if let Some(op) = OpCode::new(i) {
798 assert_eq!(OpCode::parse(op.as_str()), Some(op));
799 }
800 }
801 }
802}