use std::collections::BTreeMap;
use std::sync::{Arc, LazyLock};
use bytes::{BufMut, Bytes};
use ethereum_types::{H256, U256};
use ethrex_crypto::{Crypto, NativeCrypto};
use ethrex_trie::Trie;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use ethrex_rlp::{
decode::RLPDecode,
encode::RLPEncode,
error::RLPDecodeError,
structs::{Decoder, Encoder},
};
use super::GenesisAccount;
use crate::constants::{EMPTY_KECCAK_HASH, EMPTY_TRIE_HASH};
static EMPTY_JUMPDESTS: LazyLock<Arc<[u8]>> = LazyLock::new(|| Arc::from(Vec::new()));
pub const BYTECODE_PADDING: usize = 33;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Code {
pub hash: H256,
bytecode: Bytes,
bytecode_len: usize,
jumpdests: Arc<[u8]>,
}
impl Code {
pub fn from_bytecode_unchecked(code: Bytes, hash: H256) -> Self {
let jumpdests = Self::compute_jumpdests(&code);
Self::from_parts_unchecked(hash, &code, jumpdests)
}
pub fn from_bytecode(code: Bytes, crypto: &dyn Crypto) -> Self {
let jumpdests = Self::compute_jumpdests(&code);
let hash = H256(crypto.keccak256(code.as_ref()));
Self::from_parts_unchecked(hash, &code, jumpdests)
}
pub fn from_parts_unchecked(hash: H256, code: &[u8], jumpdests: Arc<[u8]>) -> Self {
let bytecode_len = code.len();
let mut padded_code = Vec::with_capacity(bytecode_len + BYTECODE_PADDING);
padded_code.extend_from_slice(code);
padded_code.extend_from_slice(&[0u8; BYTECODE_PADDING]);
Self {
hash,
bytecode: Bytes::from_owner(padded_code),
bytecode_len,
jumpdests,
}
}
pub fn compute_jumpdests(code: &[u8]) -> Arc<[u8]> {
let mut bitmap = vec![0u8; code.len().div_ceil(8)];
#[cfg(any(
target_arch = "aarch64",
all(target_arch = "x86_64", target_feature = "ssse3")
))]
let any = by_block::jumpdests(code, &mut bitmap);
#[cfg(not(any(
target_arch = "aarch64",
all(target_arch = "x86_64", target_feature = "ssse3")
)))]
let any = jumpdests_by_opcode(code, &mut bitmap);
if any {
Arc::from(bitmap)
} else {
EMPTY_JUMPDESTS.clone()
}
}
#[inline]
pub fn is_valid_jumpdest(&self, offset: usize) -> bool {
self.jumpdests
.get(offset / 8)
.is_some_and(|byte| byte & (1 << (offset % 8)) != 0)
}
#[inline]
pub fn jumpdests(&self) -> &[u8] {
&self.jumpdests
}
#[inline]
pub fn code(&self) -> &[u8] {
self.bytecode.get(..self.bytecode_len).unwrap_or_default()
}
#[inline]
pub fn code_bytes(&self) -> Bytes {
self.bytecode.slice(..self.bytecode_len)
}
#[inline]
pub fn len(&self) -> usize {
self.bytecode_len
}
#[inline]
pub fn is_empty(&self) -> bool {
self.bytecode_len == 0
}
#[inline]
pub fn dispatch_buf(&self) -> &[u8] {
&self.bytecode
}
pub fn size(&self) -> usize {
let hash_size = size_of::<H256>();
let bytes_size = size_of::<Bytes>() + self.bytecode.len();
let bitmap_size = size_of::<Arc<[u8]>>() + self.jumpdests.len();
hash_size + bytes_size + bitmap_size
}
}
const OP_JUMPDEST: u8 = 0x5B;
const OP_PUSH1: u8 = 0x60;
const OP_PUSH32: u8 = 0x7F;
#[cfg_attr(
any(
target_arch = "aarch64",
all(target_arch = "x86_64", target_feature = "ssse3")
),
allow(dead_code)
)]
fn jumpdests_by_opcode(code: &[u8], bitmap: &mut [u8]) -> bool {
let mut any = false;
let mut current_byte = usize::MAX;
let mut bits = 0u8;
let mut i = 0;
while i < code.len() {
match code[i] {
OP_JUMPDEST => {
if i / 8 != current_byte {
if let Some(byte) = bitmap.get_mut(current_byte) {
*byte = bits;
}
current_byte = i / 8;
bits = 0;
}
bits |= 1 << (i % 8);
any = true;
}
c @ OP_PUSH1..=OP_PUSH32 => i += usize::from(c - OP_PUSH1) + 1,
_ => (),
}
i += 1;
}
if let Some(byte) = bitmap.get_mut(current_byte) {
*byte = bits;
}
any
}
#[cfg(any(
target_arch = "aarch64",
all(target_arch = "x86_64", target_feature = "ssse3")
))]
mod by_block {
use super::{OP_JUMPDEST, OP_PUSH1};
use lanes::Lanes;
const BLOCK: usize = 64;
const GROUP: usize = 16;
pub(super) fn jumpdests(code: &[u8], bitmap: &mut [u8]) -> bool {
let mut walks = Walks::new();
let (blocks, tail) = code.as_chunks::<BLOCK>();
let mut carry = 0;
let mut any = 0;
for (block, word) in blocks.iter().zip(bitmap.as_chunks_mut::<8>().0) {
let valid = block_jumpdests(block, &mut carry, &mut walks);
*word = valid.to_le_bytes();
any |= valid;
}
if !tail.is_empty() {
let mut padded = [0; BLOCK];
padded[..tail.len()].copy_from_slice(tail);
let valid = block_jumpdests(&padded, &mut carry, &mut walks);
let rest = &mut bitmap[blocks.len() * 8..];
rest.copy_from_slice(&valid.to_le_bytes()[..rest.len()]);
any |= valid;
}
any != 0
}
struct Walks {
exits: [u8; 2 * BLOCK],
low_stops: [u8; 2 * BLOCK],
high_stops: [u8; 2 * BLOCK],
}
impl Walks {
fn new() -> Self {
Self {
exits: core::array::from_fn(|i| i as u8),
low_stops: [0; 2 * BLOCK],
high_stops: [0; 2 * BLOCK],
}
}
}
#[inline(always)]
fn block_jumpdests(block: &[u8; BLOCK], carry: &mut usize, walks: &mut Walks) -> u64 {
let groups = block.as_chunks::<GROUP>().0;
let groups: [Lanes; BLOCK / GROUP] = core::array::from_fn(|g| Lanes::load(&groups[g]));
let jumpdests = Lanes::bits(groups.map(|bytes| bytes.eq(Lanes::splat(OP_JUMPDEST))));
for (g, bytes) in groups.into_iter().enumerate() {
let (exits, low_stops, high_stops) = group_walks(bytes);
let exits = exits.add(Lanes::splat((g * GROUP) as u8));
exits.store(&mut walks.exits.as_chunks_mut::<GROUP>().0[g]);
low_stops.store(&mut walks.low_stops.as_chunks_mut::<GROUP>().0[g]);
high_stops.store(&mut walks.high_stops.as_chunks_mut::<GROUP>().0[g]);
}
let mut at = *carry;
let mut opcodes = 0;
for _ in 0..BLOCK / GROUP {
let i = at % (2 * BLOCK);
let stops = u64::from(walks.low_stops[i]) | (u64::from(walks.high_stops[i]) << 8);
let group_start = i & (BLOCK - GROUP);
opcodes |= stops << group_start;
at = usize::from(walks.exits[i]);
}
*carry = at - BLOCK;
jumpdests & opcodes
}
#[inline(always)]
fn group_walks(bytes: Lanes) -> (Lanes, Lanes, Lanes) {
const POSITIONS_PLUS_ONE: [u8; GROUP] =
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
const LOW_POSITION_BITS: [u8; GROUP] =
[1, 2, 4, 8, 16, 32, 64, 128, 0, 0, 0, 0, 0, 0, 0, 0];
const HIGH_POSITION_BITS: [u8; GROUP] =
[0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 8, 16, 32, 64, 128];
let is_push = bytes.and(Lanes::splat(0xE0)).eq(Lanes::splat(OP_PUSH1));
let immediates = is_push.and(bytes.sub(Lanes::splat(OP_PUSH1 - 1)));
let mut exits = Lanes::load(&POSITIONS_PLUS_ONE).add(immediates);
let mut low_stops = Lanes::load(&LOW_POSITION_BITS);
let mut high_stops = Lanes::load(&HIGH_POSITION_BITS);
for round in 0..4 {
if round < 3 {
low_stops = low_stops.or(low_stops.lookup(exits));
}
high_stops = high_stops.or(high_stops.lookup(exits));
exits = exits.max(exits.lookup(exits));
}
(exits, low_stops, high_stops)
}
#[cfg(target_arch = "aarch64")]
mod lanes {
use core::arch::aarch64::*;
#[derive(Clone, Copy)]
pub(super) struct Lanes(uint8x16_t);
impl Lanes {
#[inline(always)]
pub(super) fn load(bytes: &[u8; 16]) -> Self {
unsafe { Self(vld1q_u8(bytes.as_ptr())) }
}
#[inline(always)]
pub(super) fn store(self, out: &mut [u8; 16]) {
unsafe { vst1q_u8(out.as_mut_ptr(), self.0) }
}
#[inline(always)]
pub(super) fn splat(byte: u8) -> Self {
unsafe { Self(vdupq_n_u8(byte)) }
}
#[inline(always)]
pub(super) fn and(self, other: Self) -> Self {
unsafe { Self(vandq_u8(self.0, other.0)) }
}
#[inline(always)]
pub(super) fn or(self, other: Self) -> Self {
unsafe { Self(vorrq_u8(self.0, other.0)) }
}
#[inline(always)]
pub(super) fn add(self, other: Self) -> Self {
unsafe { Self(vaddq_u8(self.0, other.0)) }
}
#[inline(always)]
pub(super) fn sub(self, other: Self) -> Self {
unsafe { Self(vsubq_u8(self.0, other.0)) }
}
#[inline(always)]
pub(super) fn eq(self, other: Self) -> Self {
unsafe { Self(vceqq_u8(self.0, other.0)) }
}
#[inline(always)]
pub(super) fn max(self, other: Self) -> Self {
unsafe { Self(vmaxq_u8(self.0, other.0)) }
}
#[inline(always)]
pub(super) fn lookup(self, indices: Self) -> Self {
unsafe { Self(vqtbl1q_u8(self.0, indices.0)) }
}
#[inline(always)]
pub(super) fn bits(masks: [Self; 4]) -> u64 {
unsafe {
let position_bits = vreinterpretq_u8_u64(vdupq_n_u64(0x8040_2010_0804_0201));
let [a, b, c, d] = masks.map(|m| vandq_u8(m.0, position_bits));
let quarters = vpaddq_u8(vpaddq_u8(a, b), vpaddq_u8(c, d));
vgetq_lane_u64::<0>(vreinterpretq_u64_u8(vpaddq_u8(quarters, quarters)))
}
}
}
}
#[cfg(target_arch = "x86_64")]
mod lanes {
use core::arch::x86_64::*;
#[derive(Clone, Copy)]
pub(super) struct Lanes(__m128i);
impl Lanes {
#[inline(always)]
pub(super) fn load(bytes: &[u8; 16]) -> Self {
unsafe { Self(_mm_loadu_si128(bytes.as_ptr().cast())) }
}
#[inline(always)]
pub(super) fn store(self, out: &mut [u8; 16]) {
unsafe { _mm_storeu_si128(out.as_mut_ptr().cast(), self.0) }
}
#[inline(always)]
pub(super) fn splat(byte: u8) -> Self {
unsafe { Self(_mm_set1_epi8(byte as i8)) }
}
#[inline(always)]
pub(super) fn and(self, other: Self) -> Self {
unsafe { Self(_mm_and_si128(self.0, other.0)) }
}
#[inline(always)]
pub(super) fn or(self, other: Self) -> Self {
unsafe { Self(_mm_or_si128(self.0, other.0)) }
}
#[inline(always)]
pub(super) fn add(self, other: Self) -> Self {
unsafe { Self(_mm_add_epi8(self.0, other.0)) }
}
#[inline(always)]
pub(super) fn sub(self, other: Self) -> Self {
unsafe { Self(_mm_sub_epi8(self.0, other.0)) }
}
#[inline(always)]
pub(super) fn eq(self, other: Self) -> Self {
unsafe { Self(_mm_cmpeq_epi8(self.0, other.0)) }
}
#[inline(always)]
pub(super) fn max(self, other: Self) -> Self {
unsafe { Self(_mm_max_epu8(self.0, other.0)) }
}
#[inline(always)]
pub(super) fn lookup(self, indices: Self) -> Self {
unsafe {
let indices = _mm_add_epi8(indices.0, _mm_set1_epi8(0x70));
Self(_mm_shuffle_epi8(self.0, indices))
}
}
#[inline(always)]
pub(super) fn bits(masks: [Self; 4]) -> u64 {
masks.iter().enumerate().fold(0, |bits, (i, m)| {
let lanes = unsafe { _mm_movemask_epi8(m.0) } as u16;
bits | (u64::from(lanes) << (16 * i))
})
}
}
}
}
#[derive(Serialize, Deserialize)]
struct CodeSerde {
hash: H256,
code: Bytes,
jump_targets: Arc<[u32]>,
}
impl Serialize for Code {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
CodeSerde {
hash: self.hash,
code: self.code_bytes(),
jump_targets: Arc::from([]),
}
.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Code {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let CodeSerde {
hash,
code,
jump_targets: _,
} = CodeSerde::deserialize(deserializer)?;
Ok(Self::from_parts_unchecked(
hash,
&code,
Self::compute_jumpdests(&code),
))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodeMetadata {
pub length: u64,
}
#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Account {
pub info: AccountInfo,
pub code: Code,
pub storage: FxHashMap<H256, U256>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct AccountInfo {
pub code_hash: H256,
pub balance: U256,
pub nonce: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct AccountState {
pub nonce: u64,
pub balance: U256,
pub storage_root: H256,
pub code_hash: H256,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct AccountStateSlimCodec(pub AccountState);
impl Default for AccountInfo {
fn default() -> Self {
Self {
code_hash: *EMPTY_KECCAK_HASH,
balance: Default::default(),
nonce: Default::default(),
}
}
}
impl Default for AccountState {
fn default() -> Self {
Self {
nonce: Default::default(),
balance: Default::default(),
storage_root: *EMPTY_TRIE_HASH,
code_hash: *EMPTY_KECCAK_HASH,
}
}
}
impl Default for Code {
fn default() -> Self {
Self {
bytecode: Bytes::from_static(&[0u8; BYTECODE_PADDING]),
bytecode_len: 0,
hash: *EMPTY_KECCAK_HASH,
jumpdests: EMPTY_JUMPDESTS.clone(),
}
}
}
impl From<GenesisAccount> for Account {
fn from(genesis: GenesisAccount) -> Self {
let code = Code::from_bytecode(genesis.code, &NativeCrypto);
Self {
info: AccountInfo {
code_hash: code.hash,
balance: genesis.balance,
nonce: genesis.nonce,
},
code,
storage: genesis
.storage
.iter()
.map(|(k, v)| (H256(k.to_big_endian()), *v))
.collect(),
}
}
}
pub fn code_hash(code: &Bytes, crypto: &dyn Crypto) -> H256 {
H256(crypto.keccak256(code.as_ref()))
}
pub const EIP7702_DELEGATION_PREFIX: [u8; 3] = [0xef, 0x01, 0x00];
pub const EIP7702_DELEGATED_CODE_LEN: usize = 23;
pub fn is_eip7702_delegation(code: &[u8]) -> bool {
code.len() == EIP7702_DELEGATED_CODE_LEN && code.starts_with(&EIP7702_DELEGATION_PREFIX)
}
impl RLPEncode for AccountInfo {
fn encode(&self, buf: &mut dyn bytes::BufMut) {
Encoder::new(buf)
.encode_field(&self.code_hash)
.encode_field(&self.balance)
.encode_field(&self.nonce)
.finish();
}
}
impl RLPDecode for AccountInfo {
fn decode_unfinished(rlp: &[u8]) -> Result<(AccountInfo, &[u8]), RLPDecodeError> {
let decoder = Decoder::new(rlp)?;
let (code_hash, decoder) = decoder.decode_field("code_hash")?;
let (balance, decoder) = decoder.decode_field("balance")?;
let (nonce, decoder) = decoder.decode_field("nonce")?;
let account_info = AccountInfo {
code_hash,
balance,
nonce,
};
Ok((account_info, decoder.finish()?))
}
}
impl RLPEncode for AccountState {
fn encode(&self, buf: &mut dyn bytes::BufMut) {
Encoder::new(buf)
.encode_field(&self.nonce)
.encode_field(&self.balance)
.encode_field(&self.storage_root)
.encode_field(&self.code_hash)
.finish();
}
}
impl RLPDecode for AccountState {
fn decode_unfinished(rlp: &[u8]) -> Result<(AccountState, &[u8]), RLPDecodeError> {
let decoder = Decoder::new(rlp)?;
let (nonce, decoder) = decoder.decode_field("nonce")?;
let (balance, decoder) = decoder.decode_field("balance")?;
let (storage_root, decoder) = decoder.decode_field("storage_root")?;
let (code_hash, decoder) = decoder.decode_field("code_hash")?;
let state = AccountState {
nonce,
balance,
storage_root,
code_hash,
};
Ok((state, decoder.finish()?))
}
}
impl RLPEncode for AccountStateSlimCodec {
fn encode(&self, buf: &mut dyn BufMut) {
struct StorageRootCodec<'a>(&'a H256);
impl RLPEncode for StorageRootCodec<'_> {
fn encode(&self, buf: &mut dyn BufMut) {
let data = if *self.0 != *EMPTY_TRIE_HASH {
self.0.as_bytes()
} else {
&[]
};
data.encode(buf);
}
}
struct CodeHashCodec<'a>(&'a H256);
impl RLPEncode for CodeHashCodec<'_> {
fn encode(&self, buf: &mut dyn BufMut) {
let data = if *self.0 != *EMPTY_KECCAK_HASH {
self.0.as_bytes()
} else {
&[]
};
data.encode(buf);
}
}
Encoder::new(buf)
.encode_field(&self.0.nonce)
.encode_field(&self.0.balance)
.encode_field(&StorageRootCodec(&self.0.storage_root))
.encode_field(&CodeHashCodec(&self.0.code_hash))
.finish();
}
}
impl RLPDecode for AccountStateSlimCodec {
fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
struct StorageRootCodec(H256);
impl RLPDecode for StorageRootCodec {
fn decode_unfinished(mut rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
let value = match rlp.split_off_first() {
Some(0x80) => *EMPTY_TRIE_HASH,
Some(0xA0) => {
let data;
(data, rlp) = rlp
.split_first_chunk::<32>()
.ok_or(RLPDecodeError::InvalidLength)?;
H256(*data)
}
_ => return Err(RLPDecodeError::InvalidLength),
};
Ok((Self(value), rlp))
}
}
struct CodeHashCodec(H256);
impl RLPDecode for CodeHashCodec {
fn decode_unfinished(mut rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
let value = match rlp.split_off_first() {
Some(0x80) => *EMPTY_KECCAK_HASH,
Some(0xA0) => {
let data;
(data, rlp) = rlp
.split_first_chunk::<32>()
.ok_or(RLPDecodeError::InvalidLength)?;
H256(*data)
}
_ => return Err(RLPDecodeError::InvalidLength),
};
Ok((Self(value), rlp))
}
}
let decoder = Decoder::new(rlp)?;
let (nonce, decoder) = decoder.decode_field("nonce")?;
let (balance, decoder) = decoder.decode_field("balance")?;
let (StorageRootCodec(storage_root), decoder) = decoder.decode_field("storage_root")?;
let (CodeHashCodec(code_hash), decoder) = decoder.decode_field("code_hash")?;
Ok((
Self(AccountState {
nonce,
balance,
storage_root,
code_hash,
}),
decoder.finish()?,
))
}
}
pub fn compute_storage_root(storage: &BTreeMap<U256, U256>, crypto: &dyn Crypto) -> H256 {
let iter = storage.iter().filter_map(|(k, v)| {
(!v.is_zero()).then_some((
crypto.keccak256(&k.to_big_endian()).to_vec(),
v.encode_to_vec(),
))
});
Trie::compute_hash_from_unsorted_iter(iter, crypto)
}
impl From<&GenesisAccount> for AccountState {
fn from(value: &GenesisAccount) -> Self {
AccountState {
nonce: value.nonce,
balance: value.balance,
storage_root: compute_storage_root(&value.storage, &NativeCrypto),
code_hash: code_hash(&value.code, &NativeCrypto),
}
}
}
impl Account {
pub fn new(balance: U256, code: Code, nonce: u64, storage: FxHashMap<H256, U256>) -> Self {
Self {
info: AccountInfo {
balance,
code_hash: code.hash,
nonce,
},
code,
storage,
}
}
}
impl AccountInfo {
pub fn is_empty(&self) -> bool {
self.balance.is_zero() && self.nonce == 0 && self.code_hash == *EMPTY_KECCAK_HASH
}
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use super::*;
#[derive(serde::Serialize)]
struct OldCodeSerde {
hash: H256,
code: Bytes,
jump_targets: Vec<u32>,
}
#[derive(serde::Serialize)]
struct OldWrap {
code: OldCodeSerde,
tail: u64,
}
#[derive(serde::Deserialize)]
struct NewWrap {
code: Code,
tail: u64,
}
#[test]
fn embedded_code_decodes_an_old_layout_bincode_row() {
let bytecode = Bytes::from(vec![0x60, 0x00, 0x5b, 0x00]);
let hash = H256::from_low_u64_be(0xabc);
const SENTINEL: u64 = 0x0123_4567_89ab_cdef;
let old = OldWrap {
code: OldCodeSerde {
hash,
code: bytecode.clone(),
jump_targets: vec![2], },
tail: SENTINEL,
};
let bytes = bincode::serialize(&old).expect("serialize old layout");
let decoded: NewWrap = bincode::deserialize(&bytes).expect("decode old row under new Code");
assert_eq!(decoded.code.hash, hash);
assert_eq!(decoded.code.code_bytes(), bytecode);
assert_eq!(
decoded.tail, SENTINEL,
"field after Code corrupted by layout shift"
);
assert!(decoded.code.is_valid_jumpdest(2));
assert!(!decoded.code.is_valid_jumpdest(1));
}
#[test]
fn test_code_hash() {
let empty_code = Bytes::new();
let hash = code_hash(&empty_code, &NativeCrypto);
assert_eq!(
hash,
H256::from_str("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")
.unwrap()
)
}
#[test]
fn test_is_eip7702_delegation_valid() {
let mut code = Vec::with_capacity(23);
code.extend_from_slice(&EIP7702_DELEGATION_PREFIX);
code.extend_from_slice(&[0x42; 20]);
assert!(is_eip7702_delegation(&code));
}
#[test]
fn test_is_eip7702_delegation_rejects_empty() {
assert!(!is_eip7702_delegation(&[]));
}
#[test]
fn test_is_eip7702_delegation_rejects_short() {
assert!(!is_eip7702_delegation(&EIP7702_DELEGATION_PREFIX));
}
#[test]
fn test_is_eip7702_delegation_rejects_long() {
let mut code = Vec::with_capacity(24);
code.extend_from_slice(&EIP7702_DELEGATION_PREFIX);
code.extend_from_slice(&[0x42; 21]);
assert!(!is_eip7702_delegation(&code));
}
#[test]
fn test_is_eip7702_delegation_rejects_wrong_prefix() {
let mut code = Vec::with_capacity(23);
code.extend_from_slice(&[0xef, 0x01, 0x01]); code.extend_from_slice(&[0x42; 20]);
assert!(!is_eip7702_delegation(&code));
}
#[test]
fn test_is_eip7702_delegation_rejects_arbitrary_contract_code() {
let code = vec![0x60, 0x60, 0x60, 0x40, 0x52 ];
assert!(!is_eip7702_delegation(&code));
}
#[cfg(any(
target_arch = "aarch64",
all(target_arch = "x86_64", target_feature = "ssse3")
))]
mod by_block_against_the_scan {
use super::*;
fn assert_same_jumpdests(code: &[u8]) {
let mut want = vec![0; code.len().div_ceil(8)];
let mut got = want.clone();
let want_any = jumpdests_by_opcode(code, &mut want);
let got_any = by_block::jumpdests(code, &mut got);
assert_eq!((got_any, got), (want_any, want), "code {code:02x?}");
}
#[test]
fn matches_around_group_and_block_boundaries() {
const BYTES: [u8; 9] = [
0x00,
OP_JUMPDEST,
OP_PUSH1,
OP_PUSH1 + 1,
OP_PUSH1 + 14,
OP_PUSH1 + 15,
OP_PUSH1 + 16,
OP_PUSH32 - 1,
OP_PUSH32,
];
let lengths = [
1, 15, 16, 17, 31, 32, 33, 63, 64, 65, 96, 127, 128, 129, 193,
];
for filler in [OP_JUMPDEST, OP_PUSH1] {
for len in lengths {
for at in 0..len {
for first in BYTES {
for second in [None].into_iter().chain(BYTES.map(Some)) {
let mut code = vec![filler; len];
code[at] = first;
if let (Some(second), Some(byte)) = (second, code.get_mut(at + 1)) {
*byte = second;
}
assert_same_jumpdests(&code);
}
}
}
}
}
}
#[test]
fn matches_on_random_code() {
let mut state = 0x9E37_79B9_7F4A_7C15_u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
let jumpdest_and_pushes: Vec<u8> = std::iter::once(OP_JUMPDEST)
.chain(OP_PUSH1..=OP_PUSH32)
.collect();
let all_bytes: Vec<u8> = (0..=u8::MAX).collect();
let mixes: [&[u8]; 4] = [
&[0x00, OP_JUMPDEST, OP_PUSH1],
&[OP_JUMPDEST, OP_PUSH1, OP_PUSH1 + 1],
&jumpdest_and_pushes,
&all_bytes,
];
for mix in mixes {
for _ in 0..200 {
let len = (next() % 4096) as usize;
let code: Vec<u8> = (0..len)
.map(|_| mix[(next() % mix.len() as u64) as usize])
.collect();
assert_same_jumpdests(&code);
}
}
}
}
}