diff --git a/crates/bytecode/src/bytecode.rs b/crates/bytecode/src/bytecode.rs index 259c3c1fa8..cf6489b232 100644 --- a/crates/bytecode/src/bytecode.rs +++ b/crates/bytecode/src/bytecode.rs @@ -1,26 +1,57 @@ -//! Module that contains the bytecode enum with all variants supported by Ethereum mainnet. +//! Module that contains the bytecode struct with all variants supported by Ethereum mainnet. //! //! Those are: -//! - Legacy bytecode with jump table analysis. Found in [`LegacyAnalyzedBytecode`] -//! - EIP-7702 bytecode, introduces in Prague and contains address to delegated account. +//! - Legacy bytecode with jump table analysis +//! - EIP-7702 bytecode, introduced in Prague and contains address to delegated account use crate::{ - eip7702::{Eip7702Bytecode, EIP7702_MAGIC_BYTES}, - BytecodeDecodeError, JumpTable, LegacyAnalyzedBytecode, LegacyRawBytecode, + eip7702::{Eip7702DecodeError, EIP7702_MAGIC_BYTES, EIP7702_VERSION}, + legacy::analyze_legacy, + opcode, BytecodeDecodeError, JumpTable, }; use primitives::{ alloy_primitives::Sealable, keccak256, Address, Bytes, OnceLock, B256, KECCAK_EMPTY, }; use std::sync::Arc; -/// Main bytecode structure with all variants. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] +/// Ethereum EVM bytecode. +#[derive(Clone, Debug)] +pub struct Bytecode(Arc); + +/// Inner bytecode representation. +/// +/// This struct is flattened to avoid nested allocations. The `kind` field determines +/// how the bytecode should be interpreted. +#[derive(Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum Bytecode { - /// EIP-7702 delegated bytecode - Eip7702(Arc), - /// The bytecode has been analyzed for valid jump destinations. - LegacyAnalyzed(Arc), +struct BytecodeInner { + /// The kind of bytecode (Legacy or EIP-7702). + kind: BytecodeKind, + /// The bytecode bytes. + /// + /// For legacy bytecode, this may be padded with zeros at the end. + /// For EIP-7702 bytecode, this is exactly 23 bytes. + bytecode: Bytes, + /// The original length of the bytecode before padding. + /// + /// For EIP-7702 bytecode, this is always 23. + original_len: usize, + /// The jump table for legacy bytecode. Empty for EIP-7702. + jump_table: JumpTable, + /// Cached hash of the original bytecode. + #[cfg_attr(feature = "serde", serde(skip, default))] + hash: OnceLock, +} + +/// The kind of bytecode. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum BytecodeKind { + /// Legacy analyzed bytecode with jump table. + #[default] + LegacyAnalyzed, + /// EIP-7702 delegated bytecode. + Eip7702, } impl Default for Bytecode { @@ -30,6 +61,39 @@ impl Default for Bytecode { } } +impl PartialEq for Bytecode { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.kind() == other.kind() && self.original_byte_slice() == other.original_byte_slice() + } +} + +impl Eq for Bytecode {} + +impl core::hash::Hash for Bytecode { + #[inline] + fn hash(&self, state: &mut H) { + self.kind().hash(state); + self.original_byte_slice().hash(state); + } +} + +impl PartialOrd for Bytecode { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Bytecode { + #[inline] + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.kind() + .cmp(&other.kind()) + .then_with(|| self.original_byte_slice().cmp(other.original_byte_slice())) + } +} + impl Sealable for Bytecode { #[inline] fn hash_slow(&self) -> B256 { @@ -37,45 +101,58 @@ impl Sealable for Bytecode { } } +#[cfg(feature = "serde")] +impl serde::Serialize for Bytecode { + fn serialize(&self, serializer: S) -> Result { + self.0.serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for Bytecode { + fn deserialize>(deserializer: D) -> Result { + BytecodeInner::deserialize(deserializer).map(|inner| Self(Arc::new(inner))) + } +} + impl Bytecode { /// Creates a new legacy analyzed [`Bytecode`] with exactly one STOP opcode. #[inline] pub fn new() -> Self { static DEFAULT_BYTECODE: OnceLock = OnceLock::new(); DEFAULT_BYTECODE - .get_or_init(|| Self::LegacyAnalyzed(Arc::new(LegacyAnalyzedBytecode::default()))) + .get_or_init(|| { + Self(Arc::new(BytecodeInner { + kind: BytecodeKind::LegacyAnalyzed, + bytecode: Bytes::from_static(&[opcode::STOP]), + original_len: 0, + jump_table: JumpTable::default(), + hash: { + let hash = OnceLock::new(); + let _ = hash.set(KECCAK_EMPTY); + hash + }, + })) + }) .clone() } - /// Returns jump table if bytecode is analyzed. + /// Creates a new legacy [`Bytecode`] by analyzing raw bytes. #[inline] - pub fn legacy_jump_table(&self) -> Option<&JumpTable> { - match &self { - Self::LegacyAnalyzed(analyzed) => Some(analyzed.jump_table()), - _ => None, - } - } - - /// Calculates hash of the bytecode. - #[inline] - pub fn hash_slow(&self) -> B256 { - if self.is_empty() { - KECCAK_EMPTY - } else { - keccak256(self.original_byte_slice()) + pub fn new_legacy(raw: Bytes) -> Self { + if raw.is_empty() { + return Self::new(); } - } - - /// Returns `true` if bytecode is EIP-7702. - #[inline] - pub const fn is_eip7702(&self) -> bool { - matches!(self, Self::Eip7702(_)) - } - /// Creates a new legacy [`Bytecode`]. - #[inline] - pub fn new_legacy(raw: Bytes) -> Self { - Self::LegacyAnalyzed(Arc::new(LegacyRawBytecode(raw).into_analyzed())) + let original_len = raw.len(); + let (jump_table, bytecode) = analyze_legacy(raw); + Self(Arc::new(BytecodeInner { + kind: BytecodeKind::LegacyAnalyzed, + original_len, + bytecode, + jump_table, + hash: OnceLock::new(), + })) } /// Creates a new raw [`Bytecode`]. @@ -91,7 +168,16 @@ impl Bytecode { /// Creates a new EIP-7702 [`Bytecode`] from [`Address`]. #[inline] pub fn new_eip7702(address: Address) -> Self { - Self::Eip7702(Arc::new(Eip7702Bytecode::new(address))) + let raw: Bytes = [EIP7702_MAGIC_BYTES, &[EIP7702_VERSION], &address[..]] + .concat() + .into(); + Self(Arc::new(BytecodeInner { + kind: BytecodeKind::Eip7702, + original_len: raw.len(), + bytecode: raw, + jump_table: JumpTable::default(), + hash: OnceLock::new(), + })) } /// Creates a new raw [`Bytecode`]. @@ -99,97 +185,264 @@ impl Bytecode { /// Returns an error on incorrect bytecode format. #[inline] pub fn new_raw_checked(bytes: Bytes) -> Result { - let prefix = bytes.get(..2); - match prefix { - Some(prefix) if prefix == &EIP7702_MAGIC_BYTES => { - let eip7702 = Eip7702Bytecode::new_raw(bytes)?; - Ok(Self::Eip7702(Arc::new(eip7702))) - } - _ => Ok(Self::new_legacy(bytes)), + if bytes.starts_with(EIP7702_MAGIC_BYTES) { + Self::new_eip7702_raw(bytes).map_err(Into::into) + } else { + Ok(Self::new_legacy(bytes)) } } - /// Create new checked bytecode. + /// Creates a new EIP-7702 [`Bytecode`] from raw bytes. + /// + /// Returns an error if the bytes are not valid EIP-7702 bytecode. + #[inline] + pub fn new_eip7702_raw(bytes: Bytes) -> Result { + if bytes.len() != 23 { + return Err(Eip7702DecodeError::InvalidLength); + } + if !bytes.starts_with(EIP7702_MAGIC_BYTES) { + return Err(Eip7702DecodeError::InvalidMagic); + } + if bytes[2] != EIP7702_VERSION { + return Err(Eip7702DecodeError::UnsupportedVersion); + } + Ok(Self(Arc::new(BytecodeInner { + kind: BytecodeKind::Eip7702, + original_len: bytes.len(), + bytecode: bytes, + jump_table: JumpTable::default(), + hash: OnceLock::new(), + }))) + } + + /// Create new checked bytecode from pre-analyzed components. /// /// # Panics /// - /// For possible panics see [`LegacyAnalyzedBytecode::new`]. + /// * If `original_len` is greater than `bytecode.len()` + /// * If jump table length is less than `original_len` + /// * If bytecode is empty #[inline] pub fn new_analyzed(bytecode: Bytes, original_len: usize, jump_table: JumpTable) -> Self { - Self::LegacyAnalyzed(Arc::new(LegacyAnalyzedBytecode::new( + assert!( + original_len <= bytecode.len(), + "original_len is greater than bytecode length" + ); + assert!( + original_len <= jump_table.len(), + "jump table length is less than original length" + ); + assert!(!bytecode.is_empty(), "bytecode cannot be empty"); + Self(Arc::new(BytecodeInner { + kind: BytecodeKind::LegacyAnalyzed, bytecode, original_len, jump_table, - ))) + hash: OnceLock::new(), + })) } - /// Returns a reference to the bytecode. + /// Returns the kind of bytecode. #[inline] - pub fn bytecode(&self) -> &Bytes { - match self { - Self::LegacyAnalyzed(analyzed) => analyzed.bytecode(), - Self::Eip7702(code) => code.raw(), + pub fn kind(&self) -> BytecodeKind { + self.0.kind + } + + /// Returns `true` if bytecode is legacy. + #[inline] + pub fn is_legacy(&self) -> bool { + self.kind() == BytecodeKind::LegacyAnalyzed + } + + /// Returns `true` if bytecode is EIP-7702. + #[inline] + pub fn is_eip7702(&self) -> bool { + self.kind() == BytecodeKind::Eip7702 + } + + /// Returns the EIP-7702 delegated address if this is EIP-7702 bytecode. + #[inline] + pub fn eip7702_address(&self) -> Option
{ + if self.is_eip7702() { + Some(Address::from_slice(&self.0.bytecode[3..23])) + } else { + None } } - /// Pointer to the executable bytecode. + /// Returns jump table if bytecode is legacy analyzed. + #[inline] + pub fn legacy_jump_table(&self) -> Option<&JumpTable> { + if self.is_legacy() { + Some(&self.0.jump_table) + } else { + None + } + } + + /// Calculates or returns cached hash of the bytecode. + #[inline] + pub fn hash_slow(&self) -> B256 { + *self + .0 + .hash + .get_or_init(|| keccak256(self.original_byte_slice())) + } + + /// Returns a reference to the bytecode bytes. + /// + /// For legacy bytecode, this includes padding. For EIP-7702, this is the raw bytes. + #[inline] + pub fn bytecode(&self) -> &Bytes { + &self.0.bytecode + } + + /// Pointer to the bytecode bytes. #[inline] pub fn bytecode_ptr(&self) -> *const u8 { - self.bytecode().as_ptr() + self.0.bytecode.as_ptr() } - /// Returns bytes. + /// Returns a clone of the bytecode bytes. #[inline] pub fn bytes(&self) -> Bytes { - self.bytes_ref().clone() + self.0.bytecode.clone() } - /// Returns raw bytes reference. + /// Returns a reference to the bytecode bytes. #[inline] pub fn bytes_ref(&self) -> &Bytes { - self.bytecode() + &self.0.bytecode } - /// Returns raw bytes slice. + /// Returns the bytecode as a slice. #[inline] pub fn bytes_slice(&self) -> &[u8] { - self.bytes_ref() + &self.0.bytecode } - /// Returns the original bytecode. + /// Returns the original bytecode without padding. #[inline] pub fn original_bytes(&self) -> Bytes { - match self { - Self::LegacyAnalyzed(analyzed) => analyzed.original_bytes(), - Self::Eip7702(eip7702) => eip7702.raw().clone(), - } + self.0.bytecode.slice(..self.0.original_len) } - /// Returns the original bytecode as a byte slice. + /// Returns the original bytecode as a byte slice without padding. #[inline] pub fn original_byte_slice(&self) -> &[u8] { - match self { - Self::LegacyAnalyzed(analyzed) => analyzed.original_byte_slice(), - Self::Eip7702(eip7702) => eip7702.raw(), - } + &self.0.bytecode[..self.0.original_len] } - /// Returns the length of the original bytes. + /// Returns the length of the original bytes (without padding). #[inline] pub fn len(&self) -> usize { - self.original_byte_slice().len() + self.0.original_len } /// Returns whether the bytecode is empty. #[inline] pub fn is_empty(&self) -> bool { - self.len() == 0 + self.0.original_len == 0 } /// Returns an iterator over the opcodes in this bytecode, skipping immediates. - /// This is useful if you want to ignore immediates and just see what opcodes are inside. #[inline] pub fn iter_opcodes(&self) -> crate::BytecodeIterator<'_> { crate::BytecodeIterator::new(self) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{eip7702::Eip7702DecodeError, opcode}; + use bitvec::{bitvec, order::Lsb0}; + use primitives::bytes; + + #[test] + fn test_new_empty() { + for bytecode in [ + Bytecode::default(), + Bytecode::new(), + Bytecode::new().clone(), + Bytecode::new_legacy(Bytes::new()), + ] { + assert_eq!(bytecode.kind(), BytecodeKind::LegacyAnalyzed); + assert_eq!(bytecode.len(), 0); + assert_eq!(bytecode.bytes_slice(), [opcode::STOP]); + } + } + + #[test] + fn test_new_analyzed() { + let raw = Bytes::from_static(&[opcode::PUSH1, 0x01]); + let bytecode = Bytecode::new_legacy(raw); + let _ = Bytecode::new_analyzed( + bytecode.bytecode().clone(), + bytecode.len(), + bytecode.legacy_jump_table().unwrap().clone(), + ); + } + + #[test] + #[should_panic(expected = "original_len is greater than bytecode length")] + fn test_panic_on_large_original_len() { + let bytecode = Bytecode::new_legacy(Bytes::from_static(&[opcode::PUSH1, 0x01])); + let _ = Bytecode::new_analyzed( + bytecode.bytecode().clone(), + 100, + bytecode.legacy_jump_table().unwrap().clone(), + ); + } + + #[test] + #[should_panic(expected = "jump table length is less than original length")] + fn test_panic_on_short_jump_table() { + let bytecode = Bytecode::new_legacy(Bytes::from_static(&[opcode::PUSH1, 0x01])); + let jump_table = JumpTable::new(bitvec![u8, Lsb0; 0; 1]); + let _ = Bytecode::new_analyzed(bytecode.bytecode().clone(), bytecode.len(), jump_table); + } + + #[test] + #[should_panic(expected = "bytecode cannot be empty")] + fn test_panic_on_empty_bytecode() { + let bytecode = Bytes::from_static(&[]); + let jump_table = JumpTable::new(bitvec![u8, Lsb0; 0; 0]); + let _ = Bytecode::new_analyzed(bytecode, 0, jump_table); + } + + #[test] + fn eip7702_sanity_decode() { + let raw = bytes!("ef01deadbeef"); + assert_eq!( + Bytecode::new_eip7702_raw(raw), + Err(Eip7702DecodeError::InvalidLength) + ); + + let raw = bytes!("ef0101deadbeef00000000000000000000000000000000"); + assert_eq!( + Bytecode::new_eip7702_raw(raw), + Err(Eip7702DecodeError::UnsupportedVersion) + ); + + let raw = bytes!("ef0100deadbeef00000000000000000000000000000000"); + let bytecode = Bytecode::new_eip7702_raw(raw.clone()).unwrap(); + assert!(bytecode.is_eip7702()); + assert_eq!( + bytecode.eip7702_address(), + Some(Address::from_slice(&raw[3..])) + ); + assert_eq!(bytecode.original_bytes(), raw); + } + + #[test] + fn eip7702_from_address() { + let address = Address::new([0x01; 20]); + let bytecode = Bytecode::new_eip7702(address); + assert_eq!(bytecode.eip7702_address(), Some(address)); + assert_eq!( + bytecode.original_bytes(), + bytes!("ef01000101010101010101010101010101010101010101") + ); + } +} diff --git a/crates/bytecode/src/eip7702.rs b/crates/bytecode/src/eip7702.rs index f2c7ef8855..0fd3031c0c 100644 --- a/crates/bytecode/src/eip7702.rs +++ b/crates/bytecode/src/eip7702.rs @@ -1,103 +1,39 @@ +//! EIP-7702 bytecode constants and error types. + use core::fmt; -use primitives::{b256, bytes, Address, Bytes, B256}; +use primitives::{b256, hex, B256}; /// Hash of EF01 bytes that is used for EXTCODEHASH when called from legacy bytecode. pub const EIP7702_MAGIC_HASH: B256 = b256!("0xeadcdba66a79ab5dce91622d1d75c8cff5cff0b96944c3bf1072cd08ce018329"); -/// EIP-7702 Version Magic in u16 form +/// EIP-7702 Version Magic in u16 form. pub const EIP7702_MAGIC: u16 = 0xEF01; -/// EIP-7702 magic number in array form -pub static EIP7702_MAGIC_BYTES: Bytes = bytes!("ef01"); +/// EIP-7702 magic number in array form. +pub const EIP7702_MAGIC_BYTES: &[u8] = &hex!("ef01"); -/// EIP-7702 first version of bytecode +/// EIP-7702 first version of bytecode. pub const EIP7702_VERSION: u8 = 0; -/// Bytecode of delegated account, specified in EIP-7702 -/// -/// Format of EIP-7702 bytecode consist of: -/// `0xEF01` (MAGIC) + `0x00` (VERSION) + 20 bytes of address. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Eip7702Bytecode { - /// Address of the delegated account. - pub delegated_address: Address, - /// Version of the EIP-7702 bytecode. Currently only version 0 is supported. - pub version: u8, - /// Raw bytecode. - pub raw: Bytes, -} - -impl Eip7702Bytecode { - /// Creates a new EIP-7702 bytecode or returns None if the raw bytecode is invalid. - #[inline] - pub fn new_raw(raw: Bytes) -> Result { - if raw.len() != 23 { - return Err(Eip7702DecodeError::InvalidLength); - } - if !raw.starts_with(&EIP7702_MAGIC_BYTES) { - return Err(Eip7702DecodeError::InvalidMagic); - } - - // Only supported version is version 0. - if raw[2] != EIP7702_VERSION { - return Err(Eip7702DecodeError::UnsupportedVersion); - } - - Ok(Self { - delegated_address: Address::new(raw[3..].try_into().unwrap()), - version: raw[2], - raw, - }) - } - - /// Creates a new EIP-7702 bytecode with the given address. - pub fn new(address: Address) -> Self { - let mut raw = EIP7702_MAGIC_BYTES.to_vec(); - raw.push(EIP7702_VERSION); - raw.extend(&address); - Self { - delegated_address: address, - version: EIP7702_VERSION, - raw: raw.into(), - } - } - - /// Returns the raw bytecode with version MAGIC number. - #[inline] - pub fn raw(&self) -> &Bytes { - &self.raw - } - - /// Returns the address of the delegated contract. - #[inline] - pub fn address(&self) -> Address { - self.delegated_address - } - - /// Returns the EIP7702 version of the delegated contract. - #[inline] - pub fn version(&self) -> u8 { - self.version - } -} +/// EIP-7702 bytecode length: 2 (magic) + 1 (version) + 20 (address) = 23 bytes. +pub const EIP7702_BYTECODE_LEN: usize = 23; -/// Bytecode errors +/// EIP-7702 decode errors. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Eip7702DecodeError { - /// Invalid length of the raw bytecode + /// Invalid length of the raw bytecode. /// /// It should be 23 bytes. InvalidLength, - /// Invalid magic number + /// Invalid magic number. /// - /// All Eip7702 bytecodes should start with the magic number 0xEF01. + /// All EIP-7702 bytecodes should start with the magic number 0xEF01. InvalidMagic, - /// Unsupported version + /// Unsupported version. /// - /// Only supported version is version 0x00 + /// Only supported version is version 0x00. UnsupportedVersion, } @@ -113,45 +49,3 @@ impl fmt::Display for Eip7702DecodeError { } impl core::error::Error for Eip7702DecodeError {} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn sanity_decode() { - let raw = bytes!("ef01deadbeef"); - assert_eq!( - Eip7702Bytecode::new_raw(raw), - Err(Eip7702DecodeError::InvalidLength) - ); - - let raw = bytes!("ef0101deadbeef00000000000000000000000000000000"); - assert_eq!( - Eip7702Bytecode::new_raw(raw), - Err(Eip7702DecodeError::UnsupportedVersion) - ); - - let raw = bytes!("ef0100deadbeef00000000000000000000000000000000"); - let address = raw[3..].try_into().unwrap(); - assert_eq!( - Eip7702Bytecode::new_raw(raw.clone()), - Ok(Eip7702Bytecode { - delegated_address: address, - version: 0, - raw, - }) - ); - } - - #[test] - fn create_eip7702_bytecode_from_address() { - let address = Address::new([0x01; 20]); - let bytecode = Eip7702Bytecode::new(address); - assert_eq!(bytecode.delegated_address, address); - assert_eq!( - bytecode.raw, - bytes!("ef01000101010101010101010101010101010101010101") - ); - } -} diff --git a/crates/bytecode/src/iter.rs b/crates/bytecode/src/iter.rs index e8f7275f87..ec27941e84 100644 --- a/crates/bytecode/src/iter.rs +++ b/crates/bytecode/src/iter.rs @@ -16,9 +16,10 @@ impl<'a> BytecodeIterator<'a> { /// Creates a new iterator from a bytecode reference. #[inline] pub fn new(bytecode: &'a Bytecode) -> Self { - let bytes = match bytecode { - Bytecode::LegacyAnalyzed(_) => &bytecode.bytecode()[..], - Bytecode::Eip7702(_) => &[], + let bytes = if bytecode.is_legacy() { + &bytecode.bytecode()[..] + } else { + &[] }; Self { bytes: bytes.iter(), @@ -104,24 +105,20 @@ impl core::iter::FusedIterator for BytecodeIterator<'_> {} #[cfg(test)] mod tests { use super::*; - use crate::LegacyRawBytecode; use primitives::Bytes; #[test] fn test_simple_bytecode_iteration() { // Create a simple bytecode: PUSH1 0x01 PUSH1 0x02 ADD STOP - let bytecode_data = vec![ + let bytecode = Bytecode::new_legacy(Bytes::from_static(&[ opcode::PUSH1, 0x01, opcode::PUSH1, 0x02, opcode::ADD, opcode::STOP, - ]; - let raw_bytecode = LegacyRawBytecode(Bytes::from(bytecode_data)); - let bytecode = Bytecode::LegacyAnalyzed(raw_bytecode.into_analyzed_arc()); + ])); let opcodes: Vec = bytecode.iter_opcodes().collect(); - // We should only see the opcodes, not the immediates assert_eq!( opcodes, vec![opcode::PUSH1, opcode::PUSH1, opcode::ADD, opcode::STOP] @@ -130,8 +127,7 @@ mod tests { #[test] fn test_bytecode_with_various_push_sizes() { - // PUSH1 0x01, PUSH2 0x0203, PUSH3 0x040506, STOP - let bytecode_data = vec![ + let bytecode = Bytecode::new_legacy(Bytes::from_static(&[ opcode::PUSH1, 0x01, opcode::PUSH2, @@ -142,9 +138,7 @@ mod tests { 0x05, 0x06, opcode::STOP, - ]; - let raw_bytecode = LegacyRawBytecode(Bytes::from(bytecode_data)); - let bytecode = Bytecode::LegacyAnalyzed(raw_bytecode.into_analyzed_arc()); + ])); let opcodes: Vec = bytecode.iter_opcodes().collect(); @@ -157,23 +151,22 @@ mod tests { #[test] fn test_bytecode_skips_immediates() { - // Create a bytecode with various PUSH operations - let bytecode_data = vec![ + let bytecode = Bytecode::new_legacy(Bytes::from_static(&[ opcode::PUSH1, - 0x01, // PUSH1 0x01 + 0x01, opcode::PUSH2, 0x02, - 0x03, // PUSH2 0x0203 - opcode::ADD, // ADD + 0x03, + opcode::ADD, opcode::PUSH3, 0x04, 0x05, - 0x06, // PUSH3 0x040506 + 0x06, opcode::PUSH32, 0x10, 0x11, 0x12, - 0x13, // PUSH32 with 32 bytes of immediate data + 0x13, 0x14, 0x15, 0x16, @@ -202,32 +195,10 @@ mod tests { 0x2d, 0x2e, 0x2f, - opcode::MUL, // MUL - opcode::STOP, // STOP - ]; - - let raw_bytecode = LegacyRawBytecode(Bytes::from(bytecode_data)); - let bytecode = Bytecode::LegacyAnalyzed(raw_bytecode.into_analyzed_arc()); - - // Use the iterator directly - let iter = BytecodeIterator::new(&bytecode); - let opcodes: Vec = iter.collect(); - - // Should only include the opcodes, not the immediates - assert_eq!( - opcodes, - vec![ - opcode::PUSH1, - opcode::PUSH2, - opcode::ADD, - opcode::PUSH3, - opcode::PUSH32, - opcode::MUL, - opcode::STOP, - ] - ); + opcode::MUL, + opcode::STOP, + ])); - // Use the method on the bytecode struct let opcodes: Vec = bytecode.iter_opcodes().collect(); assert_eq!( opcodes, @@ -245,50 +216,37 @@ mod tests { #[test] fn test_position_tracking() { - // PUSH1 0x01, PUSH1 0x02, ADD, STOP - let bytecode_data = vec![ + let bytecode = Bytecode::new_legacy(Bytes::from_static(&[ opcode::PUSH1, 0x01, opcode::PUSH1, 0x02, opcode::ADD, opcode::STOP, - ]; - let raw_bytecode = LegacyRawBytecode(Bytes::from(bytecode_data)); - let bytecode = Bytecode::LegacyAnalyzed(raw_bytecode.into_analyzed_arc()); + ])); let mut iter = bytecode.iter_opcodes(); - // Start at position 0 assert_eq!(iter.position(), 0); assert_eq!(iter.next(), Some(opcode::PUSH1)); - // After PUSH1, position should be 2 (PUSH1 + immediate) assert_eq!(iter.position(), 2); assert_eq!(iter.next(), Some(opcode::PUSH1)); - // After second PUSH1, position should be 4 (2 + PUSH1 + immediate) assert_eq!(iter.position(), 4); assert_eq!(iter.next(), Some(opcode::ADD)); - // After ADD, position should be 5 (4 + ADD) assert_eq!(iter.position(), 5); assert_eq!(iter.next(), Some(opcode::STOP)); - // After STOP, position should be 6 (5 + STOP) assert_eq!(iter.position(), 6); - // No more opcodes assert_eq!(iter.next(), None); assert_eq!(iter.position(), 6); } #[test] fn test_empty_bytecode() { - // Empty bytecode (just STOP) - let bytecode_data = vec![opcode::STOP]; - let raw_bytecode = LegacyRawBytecode(Bytes::from(bytecode_data)); - let bytecode = Bytecode::LegacyAnalyzed(raw_bytecode.into_analyzed_arc()); - + let bytecode = Bytecode::new_legacy(Bytes::from_static(&[opcode::STOP])); let opcodes: Vec = bytecode.iter_opcodes().collect(); assert_eq!(opcodes, vec![opcode::STOP]); } diff --git a/crates/bytecode/src/legacy.rs b/crates/bytecode/src/legacy.rs index 71a6117bb3..7b8ffa9cd8 100644 --- a/crates/bytecode/src/legacy.rs +++ b/crates/bytecode/src/legacy.rs @@ -1,9 +1,7 @@ +//! Legacy bytecode analysis and jump table. + mod analysis; -mod analyzed; mod jump_map; -mod raw; -pub use analysis::analyze_legacy; -pub use analyzed::LegacyAnalyzedBytecode; +pub(crate) use analysis::analyze_legacy; pub use jump_map::JumpTable; -pub use raw::LegacyRawBytecode; diff --git a/crates/bytecode/src/legacy/analysis.rs b/crates/bytecode/src/legacy/analysis.rs index b1d9f3a269..55bc1acc58 100644 --- a/crates/bytecode/src/legacy/analysis.rs +++ b/crates/bytecode/src/legacy/analysis.rs @@ -4,16 +4,10 @@ use bitvec::{bitvec, order::Lsb0, vec::BitVec}; use primitives::Bytes; use std::vec::Vec; -/// Analyzes the bytecode for use in [`LegacyAnalyzedBytecode`](crate::LegacyAnalyzedBytecode). +/// Analyzes the bytecode to produce a jump table and potentially padded bytecode. /// -/// See [`LegacyAnalyzedBytecode`](crate::LegacyAnalyzedBytecode) for more details. -/// -/// Prefer using [`LegacyAnalyzedBytecode::analyze`](crate::LegacyAnalyzedBytecode::analyze) instead. -pub fn analyze_legacy(bytecode: Bytes) -> (JumpTable, Bytes) { - if bytecode.is_empty() { - return (JumpTable::default(), Bytes::from_static(&[opcode::STOP])); - } - +/// Prefer using [`Bytecode::new_legacy`](crate::Bytecode::new_legacy) instead. +pub(crate) fn analyze_legacy(bytecode: Bytes) -> (JumpTable, Bytes) { let mut jumps: BitVec = bitvec![u8, Lsb0; 0; bytecode.len()]; let range = bytecode.as_ptr_range(); let start = range.start; @@ -111,13 +105,6 @@ mod tests { assert_eq!(padded_bytecode.len(), bytecode.len() + 2); } - #[test] - fn test_empty_bytecode_requires_stop() { - let bytecode = vec![]; - let (_, padded_bytecode) = analyze_legacy(bytecode.into()); - assert_eq!(padded_bytecode.len(), 1); // Just STOP - } - #[test] fn test_bytecode_with_jumpdest_at_start() { let bytecode = vec![opcode::JUMPDEST, opcode::PUSH1, 0x01, opcode::STOP]; diff --git a/crates/bytecode/src/legacy/analyzed.rs b/crates/bytecode/src/legacy/analyzed.rs deleted file mode 100644 index 72b0a43069..0000000000 --- a/crates/bytecode/src/legacy/analyzed.rs +++ /dev/null @@ -1,155 +0,0 @@ -use super::JumpTable; -use primitives::Bytes; - -/// Legacy analyzed bytecode represents the original bytecode format used in Ethereum. -/// -/// # Jump Table -/// -/// A jump table maps valid jump destinations in the bytecode. -/// -/// While other EVM implementations typically analyze bytecode and cache jump tables at runtime, -/// Revm requires the jump table to be pre-computed and contained alongside the code, -/// and present with the bytecode when executing. -/// -/// # Bytecode Padding -/// -/// Legacy bytecode can be padded with up to 33 zero bytes at the end. This padding ensures that: -/// - the bytecode always ends with a valid STOP (0x00) opcode. -/// - there aren't incomplete immediates, meaning we can skip bounds checks in `PUSH*` instructions. -/// -/// The non-padded length is stored in order to be able to copy the original bytecode. -/// -/// # Gas safety -/// -/// When bytecode is created through CREATE, CREATE2, or contract creation transactions, it undergoes -/// analysis to generate its jump table. This analysis is O(n) on side of bytecode that is expensive, -/// but the high gas cost required to store bytecode in the database is high enough to cover the -/// expense of doing analysis and generate the jump table. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct LegacyAnalyzedBytecode { - /// The potentially padded bytecode. - bytecode: Bytes, - /// The original bytecode length. - original_len: usize, - /// The jump table. - jump_table: JumpTable, -} - -impl Default for LegacyAnalyzedBytecode { - #[inline] - fn default() -> Self { - Self { - bytecode: Bytes::from_static(&[0]), - original_len: 0, - jump_table: JumpTable::default(), - } - } -} - -impl LegacyAnalyzedBytecode { - /// Analyzes the bytecode. - /// - /// See [`LegacyAnalyzedBytecode`] for more details. - pub fn analyze(bytecode: Bytes) -> Self { - let original_len = bytecode.len(); - let (jump_table, padded_bytecode) = super::analysis::analyze_legacy(bytecode); - Self::new(padded_bytecode, original_len, jump_table) - } - - /// Creates new analyzed bytecode. - /// - /// Prefer instantiating using [`analyze`](Self::analyze) instead. - /// - /// # Panics - /// - /// * If `original_len` is greater than `bytecode.len()` - /// * If jump table length is less than `original_len`. - /// * If bytecode is empty. - pub fn new(bytecode: Bytes, original_len: usize, jump_table: JumpTable) -> Self { - assert!( - original_len <= bytecode.len(), - "original_len is greater than bytecode length" - ); - assert!( - original_len <= jump_table.len(), - "jump table length is less than original length" - ); - assert!(!bytecode.is_empty(), "bytecode cannot be empty"); - Self { - bytecode, - original_len, - jump_table, - } - } - - /// Returns a reference to the bytecode. - /// - /// The bytecode is padded with 32 zero bytes. - pub fn bytecode(&self) -> &Bytes { - &self.bytecode - } - - /// Returns original bytes length. - pub fn original_len(&self) -> usize { - self.original_len - } - - /// Returns original bytes without padding. - pub fn original_bytes(&self) -> Bytes { - self.bytecode.slice(..self.original_len) - } - - /// Returns original bytes without padding. - pub fn original_byte_slice(&self) -> &[u8] { - &self.bytecode[..self.original_len] - } - - /// Returns [JumpTable] of analyzed bytes. - pub fn jump_table(&self) -> &JumpTable { - &self.jump_table - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{opcode, LegacyRawBytecode}; - use bitvec::{bitvec, order::Lsb0}; - - #[test] - fn test_bytecode_new() { - let bytecode = Bytes::from_static(&[opcode::PUSH1, 0x01]); - let bytecode = LegacyRawBytecode(bytecode).into_analyzed(); - let _ = LegacyAnalyzedBytecode::new( - bytecode.bytecode, - bytecode.original_len, - bytecode.jump_table, - ); - } - - #[test] - #[should_panic(expected = "original_len is greater than bytecode length")] - fn test_panic_on_large_original_len() { - let bytecode = Bytes::from_static(&[opcode::PUSH1, 0x01]); - let bytecode = LegacyRawBytecode(bytecode).into_analyzed(); - let _ = LegacyAnalyzedBytecode::new(bytecode.bytecode, 100, bytecode.jump_table); - } - - #[test] - #[should_panic(expected = "jump table length is less than original length")] - fn test_panic_on_short_jump_table() { - let bytecode = Bytes::from_static(&[opcode::PUSH1, 0x01]); - let bytecode = LegacyRawBytecode(bytecode).into_analyzed(); - let jump_table = JumpTable::new(bitvec![u8, Lsb0; 0; 1]); - let _ = LegacyAnalyzedBytecode::new(bytecode.bytecode, bytecode.original_len, jump_table); - } - - #[test] - #[should_panic(expected = "bytecode cannot be empty")] - fn test_panic_on_empty_bytecode() { - let bytecode = Bytes::from_static(&[]); - let jump_table = JumpTable::new(bitvec![u8, Lsb0; 0; 0]); - let _ = LegacyAnalyzedBytecode::new(bytecode, 0, jump_table); - } -} diff --git a/crates/bytecode/src/legacy/jump_map.rs b/crates/bytecode/src/legacy/jump_map.rs index 1347b44778..6037013b3c 100644 --- a/crates/bytecode/src/legacy/jump_map.rs +++ b/crates/bytecode/src/legacy/jump_map.rs @@ -1,56 +1,25 @@ use bitvec::vec::BitVec; -use core::{ - cmp::Ordering, - hash::{Hash, Hasher}, -}; -use primitives::{hex, Bytes, OnceLock}; -use std::{fmt::Debug, sync::Arc}; +use core::fmt; +use primitives::hex; +use std::boxed::Box; /// A table of valid `jump` destinations. /// -/// It is immutable, cheap to clone and memory efficient, with one bit per byte in the bytecode. -#[derive(Clone, Eq)] +/// It is immutable and memory efficient, with one bit per byte in the bytecode. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct JumpTable { - /// Cached pointer to table data to avoid Arc overhead on lookup - table_ptr: *const u8, - /// Number of bits in the table. - len: usize, - /// Actual bit vec - table: Arc, + table: Box<[u8]>, + bit_len: usize, } -// SAFETY: BitVec data is immutable through Arc, pointer won't be invalidated +// SAFETY: JumpTable is immutable, and just a simple Box<[u8]>, but len. unsafe impl Send for JumpTable {} unsafe impl Sync for JumpTable {} -impl PartialEq for JumpTable { - fn eq(&self, other: &Self) -> bool { - self.table.eq(&other.table) - } -} - -impl Hash for JumpTable { - fn hash(&self, state: &mut H) { - self.table.hash(state); - } -} - -impl PartialOrd for JumpTable { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for JumpTable { - fn cmp(&self, other: &Self) -> Ordering { - self.table.cmp(&other.table) - } -} - -impl Debug for JumpTable { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { +impl fmt::Debug for JumpTable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("JumpTable") - .field("map", &hex::encode(self.table.as_ref())) + .field("map", &hex::encode(self.as_slice())) .finish() } } @@ -58,8 +27,7 @@ impl Debug for JumpTable { impl Default for JumpTable { #[inline] fn default() -> Self { - static DEFAULT: OnceLock = OnceLock::new(); - DEFAULT.get_or_init(|| Self::new(BitVec::default())).clone() + Self::new(Default::default()) } } @@ -70,7 +38,7 @@ impl serde::Serialize for JumpTable { S: serde::Serializer, { let mut bitvec = BitVec::::from_vec(self.table.to_vec()); - bitvec.resize(self.len, false); + bitvec.resize(self.bit_len, false); bitvec.serialize(serializer) } } @@ -81,93 +49,66 @@ impl<'de> serde::Deserialize<'de> for JumpTable { where D: serde::Deserializer<'de>, { - let bitvec = BitVec::deserialize(deserializer)?; - Ok(Self::new(bitvec)) + BitVec::deserialize(deserializer).map(Self::new) } } impl JumpTable { /// Create new JumpTable directly from an existing BitVec. - /// - /// Uses [`Self::from_bytes`] internally. #[inline] pub fn new(jumps: BitVec) -> Self { let bit_len = jumps.len(); - let bytes = jumps.into_vec().into(); - Self::from_bytes(bytes, bit_len) - } - - /// Gets the raw bytes of the jump map. - #[inline] - pub fn as_slice(&self) -> &[u8] { - &self.table - } - - /// Gets the length of the jump map. - #[inline] - pub fn len(&self) -> usize { - self.len - } - - /// Returns true if the jump map is empty. - #[inline] - pub fn is_empty(&self) -> bool { - self.len == 0 + Self { + table: jumps.into_vec().into_boxed_slice(), + bit_len, + } } /// Constructs a jump map from raw bytes and length. /// /// Bit length represents number of used bits inside slice. /// - /// Uses [`Self::from_bytes`] internally. - /// /// # Panics /// /// Panics if number of bits in slice is less than bit_len. #[inline] pub fn from_slice(slice: &[u8], bit_len: usize) -> Self { - Self::from_bytes(Bytes::from(slice.to_vec()), bit_len) - } - - /// Create new JumpTable directly from an existing Bytes. - /// - /// Bit length represents number of used bits inside slice. - /// - /// Panics if bytes length is less than bit_len * 8. - #[inline] - pub fn from_bytes(bytes: Bytes, bit_len: usize) -> Self { - Self::from_bytes_arc(Arc::new(bytes), bit_len) - } - - /// Create new JumpTable directly from an existing Bytes. - /// - /// Bit length represents number of used bits inside slice. - /// - /// Panics if bytes length is less than bit_len * 8. - #[inline] - pub fn from_bytes_arc(table: Arc, bit_len: usize) -> Self { const BYTE_LEN: usize = 8; assert!( - table.len() * BYTE_LEN >= bit_len, + slice.len() * BYTE_LEN >= bit_len, "slice bit length {} is less than bit_len {}", - table.len() * BYTE_LEN, + slice.len() * BYTE_LEN, bit_len ); - let table_ptr = table.as_ptr(); - Self { - table_ptr, - table, - len: bit_len, + table: slice.into(), + bit_len, } } + /// Gets the raw bytes of the jump map. + #[inline] + pub fn as_slice(&self) -> &[u8] { + &self.table + } + + /// Gets the bit length of the jump map. + #[inline] + pub fn len(&self) -> usize { + self.bit_len + } + + /// Returns true if the jump map is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + /// Checks if `pc` is a valid jump destination. - /// Uses cached pointer and bit operations for faster access #[inline] pub fn is_valid(&self, pc: usize) -> bool { - pc < self.len && unsafe { *self.table_ptr.add(pc >> 3) & (1 << (pc & 7)) != 0 } + pc < self.bit_len && unsafe { *self.table.as_ptr().add(pc >> 3) & (1 << (pc & 7)) != 0 } } } @@ -185,15 +126,15 @@ mod tests { #[test] fn test_jump_table_from_slice() { let slice = &[0x00]; - let jumptable = JumpTable::from_slice(slice, 3); - assert_eq!(jumptable.len, 3); + let jump_table = JumpTable::from_slice(slice, 3); + assert_eq!(jump_table.len(), 3); } #[test] fn test_is_valid() { let jump_table = JumpTable::from_slice(&[0x0D, 0x06], 13); - assert_eq!(jump_table.len, 13); + assert_eq!(jump_table.len(), 13); assert!(jump_table.is_valid(0)); // valid assert!(!jump_table.is_valid(1)); @@ -225,7 +166,7 @@ mod tests { }"#; let table: JumpTable = serde_json::from_str(legacy_format).expect("Failed to deserialize"); - assert_eq!(table.len, 4); + assert_eq!(table.len(), 4); assert!(table.is_valid(0)); assert!(!table.is_valid(1)); assert!(table.is_valid(2)); @@ -245,8 +186,9 @@ mod tests { serde_json::from_str(&serialized).expect("Failed to deserialize"); // Check that the deserialized table matches the original - assert_eq!(original.len, deserialized.len); + assert_eq!(original.len(), deserialized.len()); assert_eq!(original.table, deserialized.table); + assert_eq!(original, deserialized); // Verify functionality is preserved for i in 0..13 { @@ -258,149 +200,3 @@ mod tests { } } } - -#[cfg(test)] -mod bench_is_valid { - use super::*; - use std::{sync::Arc, time::Instant}; - - const ITERATIONS: usize = 1_000_000; - const TEST_SIZE: usize = 10_000; - - fn create_test_table() -> BitVec { - let mut bitvec = BitVec::from_vec(vec![0u8; TEST_SIZE.div_ceil(8)]); - bitvec.resize(TEST_SIZE, false); - for i in (0..TEST_SIZE).step_by(3) { - bitvec.set(i, true); - } - bitvec - } - - #[derive(Clone)] - pub(super) struct JumpTableWithArcDeref(pub Arc>); - - impl JumpTableWithArcDeref { - #[inline] - pub(super) fn is_valid(&self, pc: usize) -> bool { - pc < self.0.len() && unsafe { *self.0.get_unchecked(pc) } - } - } - - fn benchmark_implementation(name: &str, table: &F, test_fn: impl Fn(&F, usize) -> bool) - where - F: Clone, - { - // Warmup - for i in 0..10_000 { - std::hint::black_box(test_fn(table, i % TEST_SIZE)); - } - - let start = Instant::now(); - let mut count = 0; - - for i in 0..ITERATIONS { - if test_fn(table, i % TEST_SIZE) { - count += 1; - } - } - - let duration = start.elapsed(); - let ns_per_op = duration.as_nanos() as f64 / ITERATIONS as f64; - let ops_per_sec = ITERATIONS as f64 / duration.as_secs_f64(); - - println!("{name} Performance:"); - println!(" Time per op: {ns_per_op:.2} ns"); - println!(" Ops per sec: {ops_per_sec:.0}"); - println!(" True count: {count}"); - println!(); - - std::hint::black_box(count); - } - - #[test] - fn bench_is_valid() { - println!("JumpTable is_valid() Benchmark Comparison"); - println!("========================================="); - - let bitvec = create_test_table(); - - // Test cached pointer implementation - let cached_table = JumpTable::new(bitvec.clone()); - benchmark_implementation("JumpTable (Cached Pointer)", &cached_table, |table, pc| { - table.is_valid(pc) - }); - - // Test Arc deref implementation - let arc_table = JumpTableWithArcDeref(Arc::new(bitvec)); - benchmark_implementation("JumpTableWithArcDeref (Arc)", &arc_table, |table, pc| { - table.is_valid(pc) - }); - - println!("Benchmark completed successfully!"); - } - - #[test] - fn bench_different_access_patterns() { - let bitvec = create_test_table(); - let cached_table = JumpTable::new(bitvec.clone()); - let arc_table = JumpTableWithArcDeref(Arc::new(bitvec)); - - println!("Access Pattern Comparison"); - println!("========================"); - - // Sequential access - let start = Instant::now(); - for i in 0..ITERATIONS { - std::hint::black_box(cached_table.is_valid(i % TEST_SIZE)); - } - let cached_sequential = start.elapsed(); - - let start = Instant::now(); - for i in 0..ITERATIONS { - std::hint::black_box(arc_table.is_valid(i % TEST_SIZE)); - } - let arc_sequential = start.elapsed(); - - // Random access - let start = Instant::now(); - for i in 0..ITERATIONS { - std::hint::black_box(cached_table.is_valid((i * 17) % TEST_SIZE)); - } - let cached_random = start.elapsed(); - - let start = Instant::now(); - for i in 0..ITERATIONS { - std::hint::black_box(arc_table.is_valid((i * 17) % TEST_SIZE)); - } - let arc_random = start.elapsed(); - - println!("Sequential Access:"); - println!( - " Cached: {:.2} ns/op", - cached_sequential.as_nanos() as f64 / ITERATIONS as f64 - ); - println!( - " Arc: {:.2} ns/op", - arc_sequential.as_nanos() as f64 / ITERATIONS as f64 - ); - println!( - " Speedup: {:.1}x", - arc_sequential.as_nanos() as f64 / cached_sequential.as_nanos() as f64 - ); - - println!(); - println!("Random Access:"); - println!( - " Cached: {:.2} ns/op", - cached_random.as_nanos() as f64 / ITERATIONS as f64 - ); - println!( - " Arc: {:.2} ns/op", - arc_random.as_nanos() as f64 / ITERATIONS as f64 - ); - println!( - " Speedup: {:.1}x", - arc_random.as_nanos() as f64 / cached_random.as_nanos() as f64 - ); - } -} diff --git a/crates/bytecode/src/legacy/raw.rs b/crates/bytecode/src/legacy/raw.rs deleted file mode 100644 index 00868430a2..0000000000 --- a/crates/bytecode/src/legacy/raw.rs +++ /dev/null @@ -1,43 +0,0 @@ -use super::LegacyAnalyzedBytecode; -use core::ops::Deref; -use primitives::Bytes; -use std::sync::Arc; - -/// Used only as intermediate representation for legacy bytecode. -/// -/// See [`LegacyAnalyzedBytecode`] for the main structure that is used in Revm. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct LegacyRawBytecode(pub Bytes); - -impl LegacyRawBytecode { - /// Analyzes the bytecode, instantiating a [`LegacyAnalyzedBytecode`]. - pub fn into_analyzed(self) -> LegacyAnalyzedBytecode { - LegacyAnalyzedBytecode::analyze(self.0) - } - - /// Analyzes the bytecode, instantiating a [`LegacyAnalyzedBytecode`] and wrapping it in an [`Arc`]. - pub fn into_analyzed_arc(self) -> Arc { - Arc::new(self.into_analyzed()) - } -} - -impl From for LegacyRawBytecode { - fn from(bytes: Bytes) -> Self { - Self(bytes) - } -} - -impl From<[u8; N]> for LegacyRawBytecode { - fn from(bytes: [u8; N]) -> Self { - Self(bytes.into()) - } -} - -impl Deref for LegacyRawBytecode { - type Target = Bytes; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/crates/bytecode/src/lib.rs b/crates/bytecode/src/lib.rs index 0a2435e186..76212d2410 100644 --- a/crates/bytecode/src/lib.rs +++ b/crates/bytecode/src/lib.rs @@ -15,15 +15,14 @@ mod decode_errors; pub mod eip7702; /// Iterator for the bytecode. mod iter; -/// Legacy bytecode. -pub mod legacy; +mod legacy; pub mod opcode; pub mod utils; /// Re-export of bitvec crate, used to store legacy bytecode jump table. pub use bitvec; -pub use bytecode::Bytecode; +pub use bytecode::{Bytecode, BytecodeKind}; pub use decode_errors::BytecodeDecodeError; pub use iter::BytecodeIterator; -pub use legacy::{JumpTable, LegacyAnalyzedBytecode, LegacyRawBytecode}; +pub use legacy::JumpTable; pub use opcode::OpCode; diff --git a/crates/context/interface/src/host.rs b/crates/context/interface/src/host.rs index 7924947073..cf90c08842 100644 --- a/crates/context/interface/src/host.rs +++ b/crates/context/interface/src/host.rs @@ -161,8 +161,7 @@ pub trait Host { ); // load delegate code if account is EIP-7702 - if let Some(Bytecode::Eip7702(code)) = &account.code { - let address = code.address(); + if let Some(address) = account.code.as_ref().and_then(Bytecode::eip7702_address) { let delegate_account = self .load_account_info_skip_cold_load(address, true, false) .ok()?; diff --git a/crates/context/src/journal/inner.rs b/crates/context/src/journal/inner.rs index b7969c2bc6..89795d691c 100644 --- a/crates/context/src/journal/inner.rs +++ b/crates/context/src/journal/inner.rs @@ -257,8 +257,8 @@ impl JournalInner { /// In case of EIP-7702 code with zero address, the bytecode will be erased. #[inline] pub fn set_code(&mut self, address: Address, code: Bytecode) { - if let Bytecode::Eip7702(eip7702_bytecode) = &code { - if eip7702_bytecode.address().is_zero() { + if let Some(eip7702_address) = code.eip7702_address() { + if eip7702_address.is_zero() { self.set_code_with_hash(address, Bytecode::default(), KECCAK_EMPTY); return; } @@ -635,8 +635,12 @@ impl JournalInner { ); // load delegate code if account is EIP-7702 - if let Some(Bytecode::Eip7702(code)) = &account.info.code { - let address = code.address(); + if let Some(address) = account + .info + .code + .as_ref() + .and_then(Bytecode::eip7702_address) + { let delegate_account = self .load_account_optional(db, address, true, false) .map_err(JournalLoadError::unwrap_db_error)?; diff --git a/crates/handler/src/handler.rs b/crates/handler/src/handler.rs index 02fed677a5..a3d4f45337 100644 --- a/crates/handler/src/handler.rs +++ b/crates/handler/src/handler.rs @@ -314,8 +314,9 @@ pub trait Handler { let bytecode = if let Some(&to) = tx.kind().to() { let account = &journal.load_account_with_code(to)?.info; - if let Some(Bytecode::Eip7702(eip7702_bytecode)) = &account.code { - let delegated_address = eip7702_bytecode.delegated_address; + if let Some(delegated_address) = + account.code.as_ref().and_then(Bytecode::eip7702_address) + { let account = &journal.load_account_with_code(delegated_address)?.info; Some(( account.code.clone().unwrap_or_default(), diff --git a/crates/interpreter/src/instructions/contract/call_helpers.rs b/crates/interpreter/src/instructions/contract/call_helpers.rs index 47d49305fc..a62149a880 100644 --- a/crates/interpreter/src/instructions/contract/call_helpers.rs +++ b/crates/interpreter/src/instructions/contract/call_helpers.rs @@ -163,13 +163,12 @@ pub fn load_account_delegated( } // load delegate code if account is EIP-7702 - if let Some(Bytecode::Eip7702(code)) = &account.code { + if let Some(address) = account.code.as_ref().and_then(Bytecode::eip7702_address) { // EIP-7702 is enabled after berlin hardfork. cost += warm_storage_read_cost; if cost > remaining_gas { return Err(LoadError::ColdLoadSkipped); } - let address = code.address(); // skip cold load if there is enough gas to cover the cost. let skip_cold_load = remaining_gas < cost + additional_cold_cost;