Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
409 changes: 331 additions & 78 deletions crates/bytecode/src/bytecode.rs

Large diffs are not rendered by default.

136 changes: 15 additions & 121 deletions crates/bytecode/src/eip7702.rs
Original file line number Diff line number Diff line change
@@ -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<Self, Eip7702DecodeError> {
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,
}

Expand All @@ -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")
);
}
}
82 changes: 20 additions & 62 deletions crates/bytecode/src/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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<u8> = 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]
Expand All @@ -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,
Expand All @@ -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<u8> = bytecode.iter_opcodes().collect();

Expand All @@ -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,
Expand Down Expand Up @@ -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<u8> = 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<u8> = bytecode.iter_opcodes().collect();
assert_eq!(
opcodes,
Expand All @@ -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<u8> = bytecode.iter_opcodes().collect();
assert_eq!(opcodes, vec![opcode::STOP]);
}
Expand Down
8 changes: 3 additions & 5 deletions crates/bytecode/src/legacy.rs
Original file line number Diff line number Diff line change
@@ -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;
19 changes: 3 additions & 16 deletions crates/bytecode/src/legacy/analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> = bitvec![u8, Lsb0; 0; bytecode.len()];
let range = bytecode.as_ptr_range();
let start = range.start;
Expand Down Expand Up @@ -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];
Expand Down
Loading
Loading