Skip to main content

ethrex_common/types/
account.rs

1use std::collections::BTreeMap;
2use std::sync::{Arc, LazyLock};
3
4use bytes::{BufMut, Bytes};
5use ethereum_types::{H256, U256};
6use ethrex_crypto::{Crypto, NativeCrypto};
7use ethrex_trie::Trie;
8use rustc_hash::FxHashMap;
9use serde::{Deserialize, Serialize};
10
11use ethrex_rlp::{
12    decode::RLPDecode,
13    encode::RLPEncode,
14    error::RLPDecodeError,
15    structs::{Decoder, Encoder},
16};
17
18use super::GenesisAccount;
19use crate::constants::{EMPTY_KECCAK_HASH, EMPTY_TRIE_HASH};
20
21/// Shared empty jumpdest bitmap. `Code::default()` and any bytecode without a
22/// `JUMPDEST` clone this (a refcount bump) instead of allocating a fresh empty
23/// `Arc` header each time. This matters because the per-tx `Code::default()`
24/// placeholder and every EOA / empty-code load would otherwise each allocate.
25static EMPTY_JUMPDESTS: LazyLock<Arc<[u8]>> = LazyLock::new(|| Arc::from(Vec::new()));
26
27/// Trailing STOP bytes appended to every bytecode so the dispatch loop can read
28/// the next opcode without a bounds check. 33 is the widest single-opcode advance
29/// (PUSH32: 1 opcode byte + 32 immediate bytes), so `pc` can never step past the
30/// padding regardless of which opcode sits at the last real byte.
31pub const BYTECODE_PADDING: usize = 33;
32
33#[derive(Clone, Debug, PartialEq, Eq, Hash)]
34pub struct Code {
35    // hash is only used for bytecodes stored in the DB, either for reading it from the DB
36    // or with the CODEHASH opcode, which needs an account address as argument and
37    // thus only accessed persisted bytecodes.
38    // We use a bogus H256::zero() value for initcodes as there is no way for the VM or
39    // endpoints to access that hash, saving one expensive Keccak hash.
40    pub hash: H256,
41    /// bytecode padded with 33 zeroes (STOP opcodes, due to PUSH32) to avoid checks on the hot path.
42    bytecode: Bytes,
43    /// The real bytecode length, needed for some opcodes, `bytecode` is padded with 33 STOPs to avoid checked adds on hot loop.
44    bytecode_len: usize,
45    /// One bit per bytecode byte, set when that offset holds a `JUMPDEST` that is not
46    /// part of a `PUSH` immediate. Costs `ceil(len / 8)` bytes regardless of how dense
47    /// the jump destinations are, and validating a jump is a bit test rather than a
48    /// search.
49    ///
50    /// Bytecode with no jump destination stores a zero-length bitmap ([`EMPTY_JUMPDESTS`])
51    /// rather than an all-zero one, so this is not always `ceil(len / 8)` bytes long;
52    /// [`Code::is_valid_jumpdest`] reads a missing byte as "no jump destination".
53    //
54    // `Arc<[u8]>` so cloning `Code` (hot: every message-call resolves and clones
55    // the callee's code) is a refcount bump instead of deep-copying the bitmap.
56    // Serializes via serde's `rc` feature (enabled workspace-wide).
57    jumpdests: Arc<[u8]>,
58}
59
60impl Code {
61    // SAFETY: hash will be stored as-is, so it either needs to match
62    // the real code hash (i.e. it was precomputed and we're reusing)
63    // or never be read (e.g. for initcode).
64    //
65    // `code` is the logical, unpadded bytecode; `BYTECODE_PADDING` STOP bytes are
66    // appended internally by `from_parts_unchecked`.
67    pub fn from_bytecode_unchecked(code: Bytes, hash: H256) -> Self {
68        let jumpdests = Self::compute_jumpdests(&code);
69        Self::from_parts_unchecked(hash, &code, jumpdests)
70    }
71
72    /// `code` is the logical, unpadded bytecode; `BYTECODE_PADDING` STOP bytes are
73    /// appended internally by `from_parts_unchecked`.
74    pub fn from_bytecode(code: Bytes, crypto: &dyn Crypto) -> Self {
75        let jumpdests = Self::compute_jumpdests(&code);
76        let hash = H256(crypto.keccak256(code.as_ref()));
77        Self::from_parts_unchecked(hash, &code, jumpdests)
78    }
79
80    /// Builds a `Code` from precomputed parts. The caller must guarantee `hash`
81    /// and `jumpdests` correspond to `code`; neither is recomputed or validated.
82    ///
83    /// `code` is the logical, unpadded bytecode: this function appends
84    /// `BYTECODE_PADDING` STOP bytes and records the original length in
85    /// `bytecode_len`. Never pass a pre-padded buffer, or the logical length and
86    /// every `JUMPDEST`/`PUSH` offset derived from it would be wrong.
87    pub fn from_parts_unchecked(hash: H256, code: &[u8], jumpdests: Arc<[u8]>) -> Self {
88        let bytecode_len = code.len();
89        let mut padded_code = Vec::with_capacity(bytecode_len + BYTECODE_PADDING);
90        padded_code.extend_from_slice(code);
91        padded_code.extend_from_slice(&[0u8; BYTECODE_PADDING]);
92        Self {
93            hash,
94            bytecode: Bytes::from_owner(padded_code),
95            bytecode_len,
96            jumpdests,
97        }
98    }
99
100    /// Builds the [`Code::jumpdests`] bitmap: one pass over the bytecode, setting the
101    /// bit for every `JUMPDEST` while skipping `PUSH` immediates.
102    ///
103    /// The bits of a byte are accumulated in a register and written once the scan leaves
104    /// that byte, which the monotonic `i` makes safe. Reading the bitmap back inside the
105    /// loop instead would turn each `JUMPDEST` into a read-modify-write, and indexing it
106    /// would put a bounds-check panic path in the loop body, which inhibits optimization
107    /// of every iteration rather than only the ones that find a destination.
108    pub fn compute_jumpdests(code: &[u8]) -> Arc<[u8]> {
109        let mut bitmap = vec![0u8; code.len().div_ceil(8)];
110        let mut any = false;
111        let mut current_byte = usize::MAX;
112        let mut bits = 0u8;
113        let mut i = 0;
114        while i < code.len() {
115            // TODO: we don't use the constants from the vm module to avoid a circular dependency
116            match code[i] {
117                // OP_JUMPDEST
118                0x5B => {
119                    if i / 8 != current_byte {
120                        if let Some(byte) = bitmap.get_mut(current_byte) {
121                            *byte = bits;
122                        }
123                        current_byte = i / 8;
124                        bits = 0;
125                    }
126                    bits |= 1 << (i % 8);
127                    any = true;
128                }
129                // OP_PUSH1..32
130                c @ 0x60..0x80 => {
131                    // OP_PUSH0
132                    i += (c - 0x5F) as usize;
133                }
134                _ => (),
135            }
136            i += 1;
137        }
138        if let Some(byte) = bitmap.get_mut(current_byte) {
139            *byte = bits;
140        }
141        // Share the single empty bitmap for jumpless bytecode (very common: EOAs,
142        // tiny contracts) so we don't allocate for an all-zero map; `is_valid_jumpdest`
143        // reads a missing byte as "no jump destination".
144        if any {
145            Arc::from(bitmap)
146        } else {
147            EMPTY_JUMPDESTS.clone()
148        }
149    }
150
151    /// Whether `offset` is a valid jump destination, i.e. it holds a `JUMPDEST` that is
152    /// not part of a `PUSH` immediate. Offsets past the bytecode are not valid.
153    #[inline]
154    pub fn is_valid_jumpdest(&self, offset: usize) -> bool {
155        self.jumpdests
156            .get(offset / 8)
157            .is_some_and(|byte| byte & (1 << (offset % 8)) != 0)
158    }
159
160    /// The raw [`Code::jumpdests`] bitmap, for persisting it alongside the bytecode.
161    #[inline]
162    pub fn jumpdests(&self) -> &[u8] {
163        &self.jumpdests
164    }
165
166    #[inline]
167    pub fn code(&self) -> &[u8] {
168        self.bytecode.get(..self.bytecode_len).unwrap_or_default()
169    }
170
171    #[inline]
172    pub fn code_bytes(&self) -> Bytes {
173        self.bytecode.slice(..self.bytecode_len)
174    }
175
176    #[inline]
177    pub fn len(&self) -> usize {
178        self.bytecode_len
179    }
180
181    #[inline]
182    pub fn is_empty(&self) -> bool {
183        self.bytecode_len == 0
184    }
185
186    /// Returns the padded bytecode buffer (real code + [`BYTECODE_PADDING`] trailing
187    /// STOPs) used by the opcode dispatch loop to read opcodes without bounds checks.
188    /// Use [`Code::code`] for the real, unpadded bytecode.
189    #[inline]
190    pub fn dispatch_buf(&self) -> &[u8] {
191        &self.bytecode
192    }
193
194    /// Estimates the size of the Code struct in bytes
195    /// (including stack size and heap allocation).
196    ///
197    /// Note: an estimate. It ignores allocator overhead and the `Arc`/`Bytes` control
198    /// blocks, so it slightly under-counts, and a shared allocation is attributed in
199    /// full to every entry holding it.
200    ///
201    /// # Returns
202    ///
203    /// usize - Estimated size in bytes
204    pub fn size(&self) -> usize {
205        let hash_size = size_of::<H256>();
206        let bytes_size = size_of::<Bytes>() + self.bytecode.len();
207        let bitmap_size = size_of::<Arc<[u8]>>() + self.jumpdests.len();
208        hash_size + bytes_size + bitmap_size
209    }
210}
211
212/// Serde shadow for [`Code`]. Stores the *logical* (unpadded) bytecode so the
213/// padding is never part of the serialized form. Deserialization re-pads through
214/// [`Code::from_parts_unchecked`], which keeps the dispatch-loop invariant (every
215/// `Code` is padded with [`BYTECODE_PADDING`] trailing STOPs) sound regardless of
216/// where the bytes came from. Deserializing the padded buffer directly would
217/// otherwise let unpadded input through and cause OOB reads during execution.
218/// The jump destinations are deliberately absent: they are a pure function of the
219/// bytecode, so carrying them would put a derived value in the wire format of everything
220/// that embeds a `Code` (notably `AccountUpdate`, which the L2 rollup store persists with
221/// bincode) and couple that format to how they happen to be represented.
222/// Wire form of [`Code`]. Jump destinations are recomputed from `code` on
223/// deserialize, never read from here — but the `jump_targets` field is kept so
224/// the layout stays byte-identical to the pre-bitmap `Code` serialization.
225/// `Code` is embedded in `AccountUpdate`, which is persisted with bincode (a
226/// non-self-describing, positional codec) in the L2 rollup store; dropping the
227/// field would shift every following field and make existing rows undecodable.
228/// It is always serialized empty and ignored on read.
229#[derive(Serialize, Deserialize)]
230struct CodeSerde {
231    hash: H256,
232    code: Bytes,
233    jump_targets: Arc<[u32]>,
234}
235
236impl Serialize for Code {
237    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
238        CodeSerde {
239            hash: self.hash,
240            code: self.code_bytes(),
241            jump_targets: Arc::from([]),
242        }
243        .serialize(serializer)
244    }
245}
246
247impl<'de> Deserialize<'de> for Code {
248    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
249        // `jump_targets` is read only to consume its bytes (layout compatibility);
250        // the bitmap is always recomputed from `code`.
251        let CodeSerde {
252            hash,
253            code,
254            jump_targets: _,
255        } = CodeSerde::deserialize(deserializer)?;
256        Ok(Self::from_parts_unchecked(
257            hash,
258            &code,
259            Self::compute_jumpdests(&code),
260        ))
261    }
262}
263
264#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
265pub struct CodeMetadata {
266    pub length: u64,
267}
268
269#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
270pub struct Account {
271    pub info: AccountInfo,
272    pub code: Code,
273    pub storage: FxHashMap<H256, U256>,
274}
275
276#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
277pub struct AccountInfo {
278    pub code_hash: H256,
279    pub balance: U256,
280    pub nonce: u64,
281}
282
283#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
284pub struct AccountState {
285    pub nonce: u64,
286    pub balance: U256,
287    pub storage_root: H256,
288    pub code_hash: H256,
289}
290
291/// A slim codec for an [`AccountState`].
292///
293/// The slim codec will optimize both the [storage root](AccountState::storage_root) and the
294/// [code hash](AccountState::code_hash)'s encoding so that it does not take space when empty.
295///
296/// The correct way to use it is to wrap the [`AccountState`] and encode it using this codec, and
297/// not to store the codec as a field in a struct.
298#[derive(Clone, Copy, Debug, Default, PartialEq)]
299pub struct AccountStateSlimCodec(pub AccountState);
300
301impl Default for AccountInfo {
302    fn default() -> Self {
303        Self {
304            code_hash: *EMPTY_KECCAK_HASH,
305            balance: Default::default(),
306            nonce: Default::default(),
307        }
308    }
309}
310
311impl Default for AccountState {
312    fn default() -> Self {
313        Self {
314            nonce: Default::default(),
315            balance: Default::default(),
316            storage_root: *EMPTY_TRIE_HASH,
317            code_hash: *EMPTY_KECCAK_HASH,
318        }
319    }
320}
321
322impl Default for Code {
323    fn default() -> Self {
324        Self {
325            bytecode: Bytes::from_static(&[0u8; BYTECODE_PADDING]),
326            bytecode_len: 0,
327            hash: *EMPTY_KECCAK_HASH,
328            jumpdests: EMPTY_JUMPDESTS.clone(),
329        }
330    }
331}
332
333impl From<GenesisAccount> for Account {
334    fn from(genesis: GenesisAccount) -> Self {
335        let code = Code::from_bytecode(genesis.code, &NativeCrypto);
336        Self {
337            info: AccountInfo {
338                code_hash: code.hash,
339                balance: genesis.balance,
340                nonce: genesis.nonce,
341            },
342            code,
343            storage: genesis
344                .storage
345                .iter()
346                .map(|(k, v)| (H256(k.to_big_endian()), *v))
347                .collect(),
348        }
349    }
350}
351
352pub fn code_hash(code: &Bytes, crypto: &dyn Crypto) -> H256 {
353    H256(crypto.keccak256(code.as_ref()))
354}
355
356/// EIP-7702 delegation designation: an EOA whose code is `0xef0100 || address`.
357/// See <https://eips.ethereum.org/EIPS/eip-7702>.
358pub const EIP7702_DELEGATION_PREFIX: [u8; 3] = [0xef, 0x01, 0x00];
359/// Total byte length of an EIP-7702 delegation designation: 3-byte prefix
360/// plus the 20-byte target address.
361pub const EIP7702_DELEGATED_CODE_LEN: usize = 23;
362
363/// Returns true iff `code` is a valid EIP-7702 delegation designation
364/// (exactly 23 bytes, prefixed with `0xef0100`).
365pub fn is_eip7702_delegation(code: &[u8]) -> bool {
366    code.len() == EIP7702_DELEGATED_CODE_LEN && code.starts_with(&EIP7702_DELEGATION_PREFIX)
367}
368
369impl RLPEncode for AccountInfo {
370    fn encode(&self, buf: &mut dyn bytes::BufMut) {
371        Encoder::new(buf)
372            .encode_field(&self.code_hash)
373            .encode_field(&self.balance)
374            .encode_field(&self.nonce)
375            .finish();
376    }
377}
378
379impl RLPDecode for AccountInfo {
380    fn decode_unfinished(rlp: &[u8]) -> Result<(AccountInfo, &[u8]), RLPDecodeError> {
381        let decoder = Decoder::new(rlp)?;
382        let (code_hash, decoder) = decoder.decode_field("code_hash")?;
383        let (balance, decoder) = decoder.decode_field("balance")?;
384        let (nonce, decoder) = decoder.decode_field("nonce")?;
385        let account_info = AccountInfo {
386            code_hash,
387            balance,
388            nonce,
389        };
390        Ok((account_info, decoder.finish()?))
391    }
392}
393
394impl RLPEncode for AccountState {
395    fn encode(&self, buf: &mut dyn bytes::BufMut) {
396        Encoder::new(buf)
397            .encode_field(&self.nonce)
398            .encode_field(&self.balance)
399            .encode_field(&self.storage_root)
400            .encode_field(&self.code_hash)
401            .finish();
402    }
403}
404
405impl RLPDecode for AccountState {
406    fn decode_unfinished(rlp: &[u8]) -> Result<(AccountState, &[u8]), RLPDecodeError> {
407        let decoder = Decoder::new(rlp)?;
408        let (nonce, decoder) = decoder.decode_field("nonce")?;
409        let (balance, decoder) = decoder.decode_field("balance")?;
410        let (storage_root, decoder) = decoder.decode_field("storage_root")?;
411        let (code_hash, decoder) = decoder.decode_field("code_hash")?;
412        let state = AccountState {
413            nonce,
414            balance,
415            storage_root,
416            code_hash,
417        };
418        Ok((state, decoder.finish()?))
419    }
420}
421
422impl RLPEncode for AccountStateSlimCodec {
423    fn encode(&self, buf: &mut dyn BufMut) {
424        struct StorageRootCodec<'a>(&'a H256);
425        impl RLPEncode for StorageRootCodec<'_> {
426            fn encode(&self, buf: &mut dyn BufMut) {
427                let data = if *self.0 != *EMPTY_TRIE_HASH {
428                    self.0.as_bytes()
429                } else {
430                    &[]
431                };
432
433                data.encode(buf);
434            }
435        }
436
437        struct CodeHashCodec<'a>(&'a H256);
438        impl RLPEncode for CodeHashCodec<'_> {
439            fn encode(&self, buf: &mut dyn BufMut) {
440                let data = if *self.0 != *EMPTY_KECCAK_HASH {
441                    self.0.as_bytes()
442                } else {
443                    &[]
444                };
445
446                data.encode(buf);
447            }
448        }
449
450        Encoder::new(buf)
451            .encode_field(&self.0.nonce)
452            .encode_field(&self.0.balance)
453            .encode_field(&StorageRootCodec(&self.0.storage_root))
454            .encode_field(&CodeHashCodec(&self.0.code_hash))
455            .finish();
456    }
457}
458
459impl RLPDecode for AccountStateSlimCodec {
460    fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
461        struct StorageRootCodec(H256);
462        impl RLPDecode for StorageRootCodec {
463            fn decode_unfinished(mut rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
464                let value = match rlp.split_off_first() {
465                    Some(0x80) => *EMPTY_TRIE_HASH,
466                    Some(0xA0) => {
467                        let data;
468                        (data, rlp) = rlp
469                            .split_first_chunk::<32>()
470                            .ok_or(RLPDecodeError::InvalidLength)?;
471                        H256(*data)
472                    }
473                    _ => return Err(RLPDecodeError::InvalidLength),
474                };
475
476                Ok((Self(value), rlp))
477            }
478        }
479
480        struct CodeHashCodec(H256);
481        impl RLPDecode for CodeHashCodec {
482            fn decode_unfinished(mut rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
483                let value = match rlp.split_off_first() {
484                    Some(0x80) => *EMPTY_KECCAK_HASH,
485                    Some(0xA0) => {
486                        let data;
487                        (data, rlp) = rlp
488                            .split_first_chunk::<32>()
489                            .ok_or(RLPDecodeError::InvalidLength)?;
490                        H256(*data)
491                    }
492                    _ => return Err(RLPDecodeError::InvalidLength),
493                };
494
495                Ok((Self(value), rlp))
496            }
497        }
498
499        let decoder = Decoder::new(rlp)?;
500        let (nonce, decoder) = decoder.decode_field("nonce")?;
501        let (balance, decoder) = decoder.decode_field("balance")?;
502        let (StorageRootCodec(storage_root), decoder) = decoder.decode_field("storage_root")?;
503        let (CodeHashCodec(code_hash), decoder) = decoder.decode_field("code_hash")?;
504
505        Ok((
506            Self(AccountState {
507                nonce,
508                balance,
509                storage_root,
510                code_hash,
511            }),
512            decoder.finish()?,
513        ))
514    }
515}
516
517pub fn compute_storage_root(storage: &BTreeMap<U256, U256>, crypto: &dyn Crypto) -> H256 {
518    let iter = storage.iter().filter_map(|(k, v)| {
519        (!v.is_zero()).then_some((
520            crypto.keccak256(&k.to_big_endian()).to_vec(),
521            v.encode_to_vec(),
522        ))
523    });
524    Trie::compute_hash_from_unsorted_iter(iter, crypto)
525}
526
527impl From<&GenesisAccount> for AccountState {
528    fn from(value: &GenesisAccount) -> Self {
529        AccountState {
530            nonce: value.nonce,
531            balance: value.balance,
532            storage_root: compute_storage_root(&value.storage, &NativeCrypto),
533            code_hash: code_hash(&value.code, &NativeCrypto),
534        }
535    }
536}
537
538impl Account {
539    pub fn new(balance: U256, code: Code, nonce: u64, storage: FxHashMap<H256, U256>) -> Self {
540        Self {
541            info: AccountInfo {
542                balance,
543                code_hash: code.hash,
544                nonce,
545            },
546            code,
547            storage,
548        }
549    }
550}
551
552impl AccountInfo {
553    pub fn is_empty(&self) -> bool {
554        self.balance.is_zero() && self.nonce == 0 && self.code_hash == *EMPTY_KECCAK_HASH
555    }
556}
557
558#[cfg(test)]
559mod test {
560    use std::str::FromStr;
561
562    use super::*;
563
564    // Pre-bitmap on-wire layout of `Code`: `{hash, code, jump_targets}`. `AccountUpdate`
565    // embeds `Code` and is persisted with bincode (positional) in the L2 rollup store,
566    // so a row written before the bitmap change carries a non-empty `jump_targets`.
567    #[derive(serde::Serialize)]
568    struct OldCodeSerde {
569        hash: H256,
570        code: Bytes,
571        jump_targets: Vec<u32>,
572    }
573
574    // `Code` is never persisted alone — it sits inside `AccountUpdate`, which has fields
575    // AFTER `code`. These wrappers mirror that: a field following the code. A standalone
576    // `Code` would tolerate trailing bytes, hiding the bug; only an embedded field
577    // exposes the positional shift.
578    #[derive(serde::Serialize)]
579    struct OldWrap {
580        code: OldCodeSerde,
581        tail: u64,
582    }
583    #[derive(serde::Deserialize)]
584    struct NewWrap {
585        code: Code,
586        tail: u64,
587    }
588
589    // A row serialized under the old three-field layout must still decode with the field
590    // following `code` intact: the current `Code` deserializer reads and discards
591    // `jump_targets`, keeping the layout aligned. Without the retained field, bincode
592    // reads `tail` from the `jump_targets` bytes and every following field is corrupt —
593    // exactly the `store_account_updates_by_block_number` break.
594    #[test]
595    fn embedded_code_decodes_an_old_layout_bincode_row() {
596        // PUSH1 0x00 JUMPDEST STOP — offset 2 is a real JUMPDEST, offset 1 a PUSH immediate.
597        let bytecode = Bytes::from(vec![0x60, 0x00, 0x5b, 0x00]);
598        let hash = H256::from_low_u64_be(0xabc);
599        const SENTINEL: u64 = 0x0123_4567_89ab_cdef;
600        let old = OldWrap {
601            code: OldCodeSerde {
602                hash,
603                code: bytecode.clone(),
604                jump_targets: vec![2], // non-empty, as an old row would hold
605            },
606            tail: SENTINEL,
607        };
608        let bytes = bincode::serialize(&old).expect("serialize old layout");
609
610        let decoded: NewWrap = bincode::deserialize(&bytes).expect("decode old row under new Code");
611        assert_eq!(decoded.code.hash, hash);
612        assert_eq!(decoded.code.code_bytes(), bytecode);
613        // The field after `code` must survive — this is what breaks if the layout shifts.
614        assert_eq!(
615            decoded.tail, SENTINEL,
616            "field after Code corrupted by layout shift"
617        );
618        // Jump destinations recomputed from code, not read from the row.
619        assert!(decoded.code.is_valid_jumpdest(2));
620        assert!(!decoded.code.is_valid_jumpdest(1));
621    }
622
623    #[test]
624    fn test_code_hash() {
625        let empty_code = Bytes::new();
626        let hash = code_hash(&empty_code, &NativeCrypto);
627        assert_eq!(
628            hash,
629            H256::from_str("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")
630                .unwrap()
631        )
632    }
633
634    #[test]
635    fn test_is_eip7702_delegation_valid() {
636        // 0xef0100 || 20-byte address
637        let mut code = Vec::with_capacity(23);
638        code.extend_from_slice(&EIP7702_DELEGATION_PREFIX);
639        code.extend_from_slice(&[0x42; 20]);
640        assert!(is_eip7702_delegation(&code));
641    }
642
643    #[test]
644    fn test_is_eip7702_delegation_rejects_empty() {
645        assert!(!is_eip7702_delegation(&[]));
646    }
647
648    #[test]
649    fn test_is_eip7702_delegation_rejects_short() {
650        // Prefix only, no address.
651        assert!(!is_eip7702_delegation(&EIP7702_DELEGATION_PREFIX));
652    }
653
654    #[test]
655    fn test_is_eip7702_delegation_rejects_long() {
656        // Correct prefix but 24 bytes total.
657        let mut code = Vec::with_capacity(24);
658        code.extend_from_slice(&EIP7702_DELEGATION_PREFIX);
659        code.extend_from_slice(&[0x42; 21]);
660        assert!(!is_eip7702_delegation(&code));
661    }
662
663    #[test]
664    fn test_is_eip7702_delegation_rejects_wrong_prefix() {
665        // Right length, wrong magic.
666        let mut code = Vec::with_capacity(23);
667        code.extend_from_slice(&[0xef, 0x01, 0x01]); // off by one in the last prefix byte
668        code.extend_from_slice(&[0x42; 20]);
669        assert!(!is_eip7702_delegation(&code));
670    }
671
672    #[test]
673    fn test_is_eip7702_delegation_rejects_arbitrary_contract_code() {
674        // Real contract code starting with anything else.
675        let code = vec![0x60, 0x60, 0x60, 0x40, 0x52 /* ... */];
676        assert!(!is_eip7702_delegation(&code));
677    }
678}