#[cfg(feature = "parse")]
pub mod parse;
use core::{fmt, ptr::NonNull};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct OpCode(u8);
impl fmt::Display for OpCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let n = self.get();
if let Some(val) = OPCODE_INFO[n as usize] {
f.write_str(val.name())
} else {
write!(f, "UNKNOWN(0x{n:02X})")
}
}
}
impl OpCode {
#[inline]
pub const fn new(opcode: u8) -> Option<Self> {
match OPCODE_INFO[opcode as usize] {
Some(_) => Some(Self(opcode)),
None => None,
}
}
#[inline]
pub const fn is_jumpdest(&self) -> bool {
self.0 == JUMPDEST
}
#[inline]
pub const fn is_jumpdest_by_op(opcode: u8) -> bool {
if let Some(opcode) = Self::new(opcode) {
opcode.is_jumpdest()
} else {
false
}
}
#[inline]
pub const fn is_jump(self) -> bool {
self.0 == JUMP
}
#[inline]
pub const fn is_jump_by_op(opcode: u8) -> bool {
if let Some(opcode) = Self::new(opcode) {
opcode.is_jump()
} else {
false
}
}
#[inline]
pub const fn is_push(self) -> bool {
self.0 >= PUSH1 && self.0 <= PUSH32
}
#[inline]
pub fn is_push_by_op(opcode: u8) -> bool {
if let Some(opcode) = Self::new(opcode) {
opcode.is_push()
} else {
false
}
}
#[inline]
pub unsafe fn new_unchecked(opcode: u8) -> Self {
Self(opcode)
}
#[doc(alias = "name")]
#[inline]
pub const fn as_str(self) -> &'static str {
self.info().name()
}
#[inline]
pub const fn name_by_op(opcode: u8) -> &'static str {
if let Some(opcode) = Self::new(opcode) {
opcode.as_str()
} else {
"Unknown"
}
}
#[inline]
pub const fn inputs(&self) -> u8 {
self.info().inputs()
}
#[inline]
pub const fn outputs(&self) -> u8 {
self.info().outputs()
}
#[inline]
pub const fn io_diff(&self) -> i16 {
self.info().io_diff()
}
#[inline]
pub const fn info_by_op(opcode: u8) -> Option<OpCodeInfo> {
if let Some(opcode) = Self::new(opcode) {
Some(opcode.info())
} else {
None
}
}
#[inline]
pub const fn info(&self) -> OpCodeInfo {
if let Some(t) = OPCODE_INFO[self.0 as usize] {
t
} else {
panic!("opcode not found")
}
}
pub const fn input_output(&self) -> (u8, u8) {
let info = self.info();
(info.inputs, info.outputs)
}
#[inline]
pub const fn get(self) -> u8 {
self.0
}
#[inline]
pub const fn modifies_memory(&self) -> bool {
matches!(
*self,
OpCode::EXTCODECOPY
| OpCode::MLOAD
| OpCode::MSTORE
| OpCode::MSTORE8
| OpCode::MCOPY
| OpCode::CODECOPY
| OpCode::CALLDATACOPY
| OpCode::RETURNDATACOPY
| OpCode::CALL
| OpCode::CALLCODE
| OpCode::DELEGATECALL
| OpCode::STATICCALL
| OpCode::DATACOPY
| OpCode::EOFCREATE
| OpCode::RETURNCONTRACT
| OpCode::EXTCALL
| OpCode::EXTDELEGATECALL
| OpCode::EXTSTATICCALL
)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OpCodeInfo {
name_ptr: NonNull<u8>,
name_len: u8,
inputs: u8,
outputs: u8,
immediate_size: u8,
not_eof: bool,
terminating: bool,
}
impl fmt::Debug for OpCodeInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OpCodeInfo")
.field("name", &self.name())
.field("inputs", &self.inputs())
.field("outputs", &self.outputs())
.field("not_eof", &self.is_disabled_in_eof())
.field("terminating", &self.is_terminating())
.field("immediate_size", &self.immediate_size())
.finish()
}
}
impl OpCodeInfo {
pub const fn new(name: &'static str) -> Self {
assert!(name.len() < 256, "opcode name is too long");
Self {
name_ptr: unsafe { NonNull::new_unchecked(name.as_ptr().cast_mut()) },
name_len: name.len() as u8,
inputs: 0,
outputs: 0,
not_eof: false,
terminating: false,
immediate_size: 0,
}
}
#[inline]
pub const fn name(&self) -> &'static str {
unsafe {
let slice = core::slice::from_raw_parts(self.name_ptr.as_ptr(), self.name_len as usize);
core::str::from_utf8_unchecked(slice)
}
}
#[inline]
pub const fn io_diff(&self) -> i16 {
self.outputs as i16 - self.inputs as i16
}
#[inline]
pub const fn inputs(&self) -> u8 {
self.inputs
}
#[inline]
pub const fn outputs(&self) -> u8 {
self.outputs
}
#[inline]
pub const fn is_disabled_in_eof(&self) -> bool {
self.not_eof
}
#[inline]
pub const fn is_terminating(&self) -> bool {
self.terminating
}
#[inline]
pub const fn immediate_size(&self) -> u8 {
self.immediate_size
}
}
#[inline]
pub const fn not_eof(mut op: OpCodeInfo) -> OpCodeInfo {
op.not_eof = true;
op
}
#[inline]
pub const fn immediate_size(mut op: OpCodeInfo, n: u8) -> OpCodeInfo {
op.immediate_size = n;
op
}
#[inline]
pub const fn terminating(mut op: OpCodeInfo) -> OpCodeInfo {
op.terminating = true;
op
}
#[inline]
pub const fn stack_io(mut op: OpCodeInfo, inputs: u8, outputs: u8) -> OpCodeInfo {
op.inputs = inputs;
op.outputs = outputs;
op
}
pub const NOP: u8 = JUMPDEST;
macro_rules! opcodes {
($($val:literal => $name:ident => $($modifier:ident $(( $($modifier_arg:expr),* ))?),*);* $(;)?) => {
$(
#[doc = concat!("The `", stringify!($val), "` (\"", stringify!($name),"\") opcode.")]
pub const $name: u8 = $val;
)*
impl OpCode {$(
#[doc = concat!("The `", stringify!($val), "` (\"", stringify!($name),"\") opcode.")]
pub const $name: Self = Self($val);
)*}
pub const OPCODE_INFO: [Option<OpCodeInfo>; 256] = {
let mut map = [None; 256];
let mut prev: u8 = 0;
$(
let val: u8 = $val;
assert!(val == 0 || val > prev, "opcodes must be sorted in ascending order");
prev = val;
let info = OpCodeInfo::new(stringify!($name));
$(
let info = $modifier(info, $($($modifier_arg),*)?);
)*
map[$val] = Some(info);
)*
let _ = prev;
map
};
#[cfg(feature = "parse")]
pub(crate) static NAME_TO_OPCODE: phf::Map<&'static str, OpCode> = stringify_with_cb! { phf_map_cb; $($name)* };
};
}
#[cfg(feature = "parse")]
macro_rules! phf_map_cb {
($(#[doc = $s:literal] $id:ident)*) => {
phf::phf_map! {
$($s => OpCode::$id),*
}
};
}
#[cfg(feature = "parse")]
macro_rules! stringify_with_cb {
($callback:ident; $($id:ident)*) => { paste::paste! {
$callback! { $(#[doc = "" $id ""] $id)* }
}};
}
opcodes! {
0x00 => STOP => stack_io(0, 0), terminating;
0x01 => ADD => stack_io(2, 1);
0x02 => MUL => stack_io(2, 1);
0x03 => SUB => stack_io(2, 1);
0x04 => DIV => stack_io(2, 1);
0x05 => SDIV => stack_io(2, 1);
0x06 => MOD => stack_io(2, 1);
0x07 => SMOD => stack_io(2, 1);
0x08 => ADDMOD => stack_io(3, 1);
0x09 => MULMOD => stack_io(3, 1);
0x0A => EXP => stack_io(2, 1);
0x0B => SIGNEXTEND => stack_io(2, 1);
0x10 => LT => stack_io(2, 1);
0x11 => GT => stack_io(2, 1);
0x12 => SLT => stack_io(2, 1);
0x13 => SGT => stack_io(2, 1);
0x14 => EQ => stack_io(2, 1);
0x15 => ISZERO => stack_io(1, 1);
0x16 => AND => stack_io(2, 1);
0x17 => OR => stack_io(2, 1);
0x18 => XOR => stack_io(2, 1);
0x19 => NOT => stack_io(1, 1);
0x1A => BYTE => stack_io(2, 1);
0x1B => SHL => stack_io(2, 1);
0x1C => SHR => stack_io(2, 1);
0x1D => SAR => stack_io(2, 1);
0x20 => KECCAK256 => stack_io(2, 1);
0x30 => ADDRESS => stack_io(0, 1);
0x31 => BALANCE => stack_io(1, 1);
0x32 => ORIGIN => stack_io(0, 1);
0x33 => CALLER => stack_io(0, 1);
0x34 => CALLVALUE => stack_io(0, 1);
0x35 => CALLDATALOAD => stack_io(1, 1);
0x36 => CALLDATASIZE => stack_io(0, 1);
0x37 => CALLDATACOPY => stack_io(3, 0);
0x38 => CODESIZE => stack_io(0, 1), not_eof;
0x39 => CODECOPY => stack_io(3, 0), not_eof;
0x3A => GASPRICE => stack_io(0, 1);
0x3B => EXTCODESIZE => stack_io(1, 1), not_eof;
0x3C => EXTCODECOPY => stack_io(4, 0), not_eof;
0x3D => RETURNDATASIZE => stack_io(0, 1);
0x3E => RETURNDATACOPY => stack_io(3, 0);
0x3F => EXTCODEHASH => stack_io(1, 1), not_eof;
0x40 => BLOCKHASH => stack_io(1, 1);
0x41 => COINBASE => stack_io(0, 1);
0x42 => TIMESTAMP => stack_io(0, 1);
0x43 => NUMBER => stack_io(0, 1);
0x44 => DIFFICULTY => stack_io(0, 1);
0x45 => GASLIMIT => stack_io(0, 1);
0x46 => CHAINID => stack_io(0, 1);
0x47 => SELFBALANCE => stack_io(0, 1);
0x48 => BASEFEE => stack_io(0, 1);
0x49 => BLOBHASH => stack_io(1, 1);
0x4A => BLOBBASEFEE => stack_io(0, 1);
0x50 => POP => stack_io(1, 0);
0x51 => MLOAD => stack_io(1, 1);
0x52 => MSTORE => stack_io(2, 0);
0x53 => MSTORE8 => stack_io(2, 0);
0x54 => SLOAD => stack_io(1, 1);
0x55 => SSTORE => stack_io(2, 0);
0x56 => JUMP => stack_io(1, 0), not_eof;
0x57 => JUMPI => stack_io(2, 0), not_eof;
0x58 => PC => stack_io(0, 1), not_eof;
0x59 => MSIZE => stack_io(0, 1);
0x5A => GAS => stack_io(0, 1), not_eof;
0x5B => JUMPDEST => stack_io(0, 0);
0x5C => TLOAD => stack_io(1, 1);
0x5D => TSTORE => stack_io(2, 0);
0x5E => MCOPY => stack_io(3, 0);
0x5F => PUSH0 => stack_io(0, 1);
0x60 => PUSH1 => stack_io(0, 1), immediate_size(1);
0x61 => PUSH2 => stack_io(0, 1), immediate_size(2);
0x62 => PUSH3 => stack_io(0, 1), immediate_size(3);
0x63 => PUSH4 => stack_io(0, 1), immediate_size(4);
0x64 => PUSH5 => stack_io(0, 1), immediate_size(5);
0x65 => PUSH6 => stack_io(0, 1), immediate_size(6);
0x66 => PUSH7 => stack_io(0, 1), immediate_size(7);
0x67 => PUSH8 => stack_io(0, 1), immediate_size(8);
0x68 => PUSH9 => stack_io(0, 1), immediate_size(9);
0x69 => PUSH10 => stack_io(0, 1), immediate_size(10);
0x6A => PUSH11 => stack_io(0, 1), immediate_size(11);
0x6B => PUSH12 => stack_io(0, 1), immediate_size(12);
0x6C => PUSH13 => stack_io(0, 1), immediate_size(13);
0x6D => PUSH14 => stack_io(0, 1), immediate_size(14);
0x6E => PUSH15 => stack_io(0, 1), immediate_size(15);
0x6F => PUSH16 => stack_io(0, 1), immediate_size(16);
0x70 => PUSH17 => stack_io(0, 1), immediate_size(17);
0x71 => PUSH18 => stack_io(0, 1), immediate_size(18);
0x72 => PUSH19 => stack_io(0, 1), immediate_size(19);
0x73 => PUSH20 => stack_io(0, 1), immediate_size(20);
0x74 => PUSH21 => stack_io(0, 1), immediate_size(21);
0x75 => PUSH22 => stack_io(0, 1), immediate_size(22);
0x76 => PUSH23 => stack_io(0, 1), immediate_size(23);
0x77 => PUSH24 => stack_io(0, 1), immediate_size(24);
0x78 => PUSH25 => stack_io(0, 1), immediate_size(25);
0x79 => PUSH26 => stack_io(0, 1), immediate_size(26);
0x7A => PUSH27 => stack_io(0, 1), immediate_size(27);
0x7B => PUSH28 => stack_io(0, 1), immediate_size(28);
0x7C => PUSH29 => stack_io(0, 1), immediate_size(29);
0x7D => PUSH30 => stack_io(0, 1), immediate_size(30);
0x7E => PUSH31 => stack_io(0, 1), immediate_size(31);
0x7F => PUSH32 => stack_io(0, 1), immediate_size(32);
0x80 => DUP1 => stack_io(1, 2);
0x81 => DUP2 => stack_io(2, 3);
0x82 => DUP3 => stack_io(3, 4);
0x83 => DUP4 => stack_io(4, 5);
0x84 => DUP5 => stack_io(5, 6);
0x85 => DUP6 => stack_io(6, 7);
0x86 => DUP7 => stack_io(7, 8);
0x87 => DUP8 => stack_io(8, 9);
0x88 => DUP9 => stack_io(9, 10);
0x89 => DUP10 => stack_io(10, 11);
0x8A => DUP11 => stack_io(11, 12);
0x8B => DUP12 => stack_io(12, 13);
0x8C => DUP13 => stack_io(13, 14);
0x8D => DUP14 => stack_io(14, 15);
0x8E => DUP15 => stack_io(15, 16);
0x8F => DUP16 => stack_io(16, 17);
0x90 => SWAP1 => stack_io(2, 2);
0x91 => SWAP2 => stack_io(3, 3);
0x92 => SWAP3 => stack_io(4, 4);
0x93 => SWAP4 => stack_io(5, 5);
0x94 => SWAP5 => stack_io(6, 6);
0x95 => SWAP6 => stack_io(7, 7);
0x96 => SWAP7 => stack_io(8, 8);
0x97 => SWAP8 => stack_io(9, 9);
0x98 => SWAP9 => stack_io(10, 10);
0x99 => SWAP10 => stack_io(11, 11);
0x9A => SWAP11 => stack_io(12, 12);
0x9B => SWAP12 => stack_io(13, 13);
0x9C => SWAP13 => stack_io(14, 14);
0x9D => SWAP14 => stack_io(15, 15);
0x9E => SWAP15 => stack_io(16, 16);
0x9F => SWAP16 => stack_io(17, 17);
0xA0 => LOG0 => stack_io(2, 0);
0xA1 => LOG1 => stack_io(3, 0);
0xA2 => LOG2 => stack_io(4, 0);
0xA3 => LOG3 => stack_io(5, 0);
0xA4 => LOG4 => stack_io(6, 0);
0xD0 => DATALOAD=> stack_io(1, 1);
0xD1 => DATALOADN => stack_io(0, 1), immediate_size(2);
0xD2 => DATASIZE=> stack_io(0, 1);
0xD3 => DATACOPY=> stack_io(3, 0);
0xE0 => RJUMP => stack_io(0, 0), immediate_size(2), terminating;
0xE1 => RJUMPI => stack_io(1, 0), immediate_size(2);
0xE2 => RJUMPV => stack_io(1, 0), immediate_size(1);
0xE3 => CALLF => stack_io(0, 0), immediate_size(2);
0xE4 => RETF => stack_io(0, 0), terminating;
0xE5 => JUMPF => stack_io(0, 0), immediate_size(2), terminating;
0xE6 => DUPN => stack_io(0, 1), immediate_size(1);
0xE7 => SWAPN => stack_io(0, 0), immediate_size(1);
0xE8 => EXCHANGE => stack_io(0, 0), immediate_size(1);
0xEC => EOFCREATE => stack_io(4, 1), immediate_size(1);
0xEE => RETURNCONTRACT => stack_io(2, 0), immediate_size(1), terminating;
0xF0 => CREATE => stack_io(3, 1), not_eof;
0xF1 => CALL => stack_io(7, 1), not_eof;
0xF2 => CALLCODE => stack_io(7, 1), not_eof;
0xF3 => RETURN => stack_io(2, 0), terminating;
0xF4 => DELEGATECALL => stack_io(6, 1), not_eof;
0xF5 => CREATE2 => stack_io(4, 1), not_eof;
0xF7 => RETURNDATALOAD => stack_io(1, 1);
0xF8 => EXTCALL => stack_io(4, 1);
0xF9 => EXTDELEGATECALL => stack_io(3, 1);
0xFA => STATICCALL => stack_io(6, 1), not_eof;
0xFB => EXTSTATICCALL => stack_io(3, 1);
0xFD => REVERT => stack_io(2, 0), terminating;
0xFE => INVALID => stack_io(0, 0), terminating;
0xFF => SELFDESTRUCT => stack_io(1, 0), not_eof, terminating;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_opcode() {
let opcode = OpCode::new(0x00).unwrap();
assert!(!opcode.is_jumpdest());
assert!(!opcode.is_jump());
assert!(!opcode.is_push());
assert_eq!(opcode.as_str(), "STOP");
assert_eq!(opcode.get(), 0x00);
}
#[test]
fn test_eof_disable() {
const REJECTED_IN_EOF: &[u8] = &[
0x38, 0x39, 0x3b, 0x3c, 0x3f, 0x5a, 0xf1, 0xf2, 0xf4, 0xfa, 0xff,
];
for opcode in REJECTED_IN_EOF {
let opcode = OpCode::new(*opcode).unwrap();
assert!(
opcode.info().is_disabled_in_eof(),
"not disabled in EOF: {opcode:#?}",
);
}
}
#[test]
fn test_immediate_size() {
let mut expected = [0u8; 256];
for push in PUSH1..=PUSH32 {
expected[push as usize] = push - PUSH1 + 1;
}
expected[DATALOADN as usize] = 2;
expected[RJUMP as usize] = 2;
expected[RJUMPI as usize] = 2;
expected[RJUMPV as usize] = 1;
expected[CALLF as usize] = 2;
expected[JUMPF as usize] = 2;
expected[DUPN as usize] = 1;
expected[SWAPN as usize] = 1;
expected[EXCHANGE as usize] = 1;
expected[EOFCREATE as usize] = 1;
expected[RETURNCONTRACT as usize] = 1;
for (i, opcode) in OPCODE_INFO.iter().enumerate() {
if let Some(opcode) = opcode {
assert_eq!(
opcode.immediate_size(),
expected[i],
"immediate_size check failed for {opcode:#?}",
);
}
}
}
#[test]
fn test_enabled_opcodes() {
let opcodes = [
0x10..=0x1d,
0x20..=0x20,
0x30..=0x3f,
0x40..=0x48,
0x50..=0x5b,
0x54..=0x5f,
0x60..=0x6f,
0x70..=0x7f,
0x80..=0x8f,
0x90..=0x9f,
0xa0..=0xa4,
0xf0..=0xf5,
0xfa..=0xfa,
0xfd..=0xfd,
0xff..=0xff,
];
for i in opcodes {
for opcode in i {
OpCode::new(opcode).expect("Opcode should be valid and enabled");
}
}
}
#[test]
fn count_opcodes() {
let mut opcode_num = 0;
let mut eof_opcode_num = 0;
for opcode in OPCODE_INFO.into_iter().flatten() {
opcode_num += 1;
if !opcode.is_disabled_in_eof() {
eof_opcode_num += 1;
}
}
assert_eq!(opcode_num, 168);
assert_eq!(eof_opcode_num, 152);
}
#[test]
fn test_terminating_opcodes() {
let terminating = [
RETF,
REVERT,
RETURN,
INVALID,
SELFDESTRUCT,
RETURNCONTRACT,
STOP,
RJUMP,
JUMPF,
];
let mut opcodes = [false; 256];
for terminating in terminating.iter() {
opcodes[*terminating as usize] = true;
}
for (i, opcode) in OPCODE_INFO.into_iter().enumerate() {
assert_eq!(
opcode.map(|opcode| opcode.terminating).unwrap_or_default(),
opcodes[i],
"Opcode {:?} terminating chack failed.",
opcode
);
}
}
#[test]
#[cfg(feature = "parse")]
fn test_parsing() {
for i in 0..=u8::MAX {
if let Some(op) = OpCode::new(i) {
assert_eq!(OpCode::parse(op.as_str()), Some(op));
}
}
}
}