#[cfg(feature = "serde")]
mod serde_impl;
use crate::{
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;
#[derive(Clone, Debug)]
pub struct Bytecode(Arc<BytecodeInner>);
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct BytecodeInner {
kind: BytecodeKind,
bytecode: Bytes,
original_len: usize,
jump_table: JumpTable,
#[cfg_attr(feature = "serde", serde(skip, default))]
hash: OnceLock<B256>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BytecodeKind {
#[default]
LegacyAnalyzed,
Eip7702,
}
impl Default for Bytecode {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl PartialEq for Bytecode {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.original_byte_slice() == other.original_byte_slice()
}
}
impl Eq for Bytecode {}
impl core::hash::Hash for Bytecode {
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.original_byte_slice().hash(state);
}
}
impl PartialOrd for Bytecode {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Bytecode {
#[inline]
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.original_byte_slice().cmp(other.original_byte_slice())
}
}
impl Sealable for Bytecode {
#[inline]
fn hash_slow(&self) -> B256 {
self.hash_slow()
}
}
impl Bytecode {
#[inline]
pub fn new() -> Self {
static DEFAULT_BYTECODE: OnceLock<Bytecode> = OnceLock::new();
DEFAULT_BYTECODE
.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()
}
#[inline]
pub fn new_legacy(raw: Bytes) -> Self {
if raw.is_empty() {
return Self::new();
}
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(),
}))
}
#[inline]
pub fn new_raw(bytecode: Bytes) -> Self {
Self::new_raw_checked(bytecode).expect("Expect correct bytecode")
}
#[inline]
pub fn new_eip7702(address: Address) -> Self {
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(),
}))
}
#[inline]
pub fn new_raw_checked(bytes: Bytes) -> Result<Self, BytecodeDecodeError> {
if bytes.starts_with(EIP7702_MAGIC_BYTES) {
Self::new_eip7702_raw(bytes).map_err(Into::into)
} else {
Ok(Self::new_legacy(bytes))
}
}
#[inline]
pub fn new_eip7702_raw(bytes: Bytes) -> Result<Self, Eip7702DecodeError> {
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(),
})))
}
#[inline]
pub fn new_analyzed(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(Arc::new(BytecodeInner {
kind: BytecodeKind::LegacyAnalyzed,
bytecode,
original_len,
jump_table,
hash: OnceLock::new(),
}))
}
#[inline]
pub fn kind(&self) -> BytecodeKind {
self.0.kind
}
#[inline]
pub fn is_legacy(&self) -> bool {
self.kind() == BytecodeKind::LegacyAnalyzed
}
#[inline]
pub fn is_eip7702(&self) -> bool {
self.kind() == BytecodeKind::Eip7702
}
#[inline]
pub fn eip7702_address(&self) -> Option<Address> {
if self.is_eip7702() {
Some(Address::from_slice(&self.0.bytecode[3..23]))
} else {
None
}
}
#[inline]
pub fn legacy_jump_table(&self) -> Option<&JumpTable> {
if self.is_legacy() {
Some(&self.0.jump_table)
} else {
None
}
}
#[inline]
pub fn hash_slow(&self) -> B256 {
*self
.0
.hash
.get_or_init(|| keccak256(self.original_byte_slice()))
}
#[inline]
pub fn bytecode(&self) -> &Bytes {
&self.0.bytecode
}
#[inline]
pub fn bytecode_ptr(&self) -> *const u8 {
self.0.bytecode.as_ptr()
}
#[inline]
pub fn bytes(&self) -> Bytes {
self.0.bytecode.clone()
}
#[inline]
pub fn bytes_ref(&self) -> &Bytes {
&self.0.bytecode
}
#[inline]
pub fn bytes_slice(&self) -> &[u8] {
&self.0.bytecode
}
#[inline]
pub fn original_bytes(&self) -> Bytes {
self.0.bytecode.slice(..self.0.original_len)
}
#[inline]
pub fn original_byte_slice(&self) -> &[u8] {
&self.0.bytecode[..self.0.original_len]
}
#[inline]
pub fn len(&self) -> usize {
self.0.original_len
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.original_len == 0
}
#[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")
);
}
}