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 jump-target table. `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_JUMP_TARGETS: LazyLock<Arc<[u32]>> = 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    // `Arc<[u32]>` so cloning `Code` (hot: every message-call resolves and clones
46    // the callee's code) is a refcount bump instead of deep-copying the table.
47    // Serializes via serde's `rc` feature (enabled workspace-wide).
48    // The valid addresses are 32-bit because, despite EIP-3860 restricting initcode size,
49    // this does not apply to previous forks. This is tested in the EEST tests, which would
50    // panic in debug mode.
51    pub jump_targets: Arc<[u32]>,
52}
53
54impl Code {
55    // SAFETY: hash will be stored as-is, so it either needs to match
56    // the real code hash (i.e. it was precomputed and we're reusing)
57    // or never be read (e.g. for initcode).
58    //
59    // `code` is the logical, unpadded bytecode; `BYTECODE_PADDING` STOP bytes are
60    // appended internally by `from_parts_unchecked`.
61    pub fn from_bytecode_unchecked(code: Bytes, hash: H256) -> Self {
62        let jump_targets = Self::compute_jump_targets(&code);
63        Self::from_parts_unchecked(hash, &code, jump_targets)
64    }
65
66    /// `code` is the logical, unpadded bytecode; `BYTECODE_PADDING` STOP bytes are
67    /// appended internally by `from_parts_unchecked`.
68    pub fn from_bytecode(code: Bytes, crypto: &dyn Crypto) -> Self {
69        let jump_targets = Self::compute_jump_targets(&code);
70        let hash = H256(crypto.keccak256(code.as_ref()));
71        Self::from_parts_unchecked(hash, &code, jump_targets)
72    }
73
74    /// Builds a `Code` from precomputed parts. The caller must guarantee `hash`
75    /// and `jump_targets` correspond to `code`; neither is recomputed or validated.
76    ///
77    /// `code` is the logical, unpadded bytecode: this function appends
78    /// `BYTECODE_PADDING` STOP bytes and records the original length in
79    /// `bytecode_len`. Never pass a pre-padded buffer, or the logical length and
80    /// every `JUMPDEST`/`PUSH` offset derived from it would be wrong.
81    pub fn from_parts_unchecked(hash: H256, code: &[u8], jump_targets: Arc<[u32]>) -> Self {
82        let bytecode_len = code.len();
83        let mut padded_code = Vec::with_capacity(bytecode_len + BYTECODE_PADDING);
84        padded_code.extend_from_slice(code);
85        padded_code.extend_from_slice(&[0u8; BYTECODE_PADDING]);
86        Self {
87            hash,
88            bytecode: Bytes::from_owner(padded_code),
89            bytecode_len,
90            jump_targets,
91        }
92    }
93
94    fn compute_jump_targets(code: &[u8]) -> Arc<[u32]> {
95        debug_assert!(code.len() <= u32::MAX as usize);
96        let mut targets = Vec::new();
97        let mut i = 0;
98        while i < code.len() {
99            // TODO: we don't use the constants from the vm module to avoid a circular dependency
100            match code[i] {
101                // OP_JUMPDEST
102                0x5B => {
103                    targets.push(i as u32);
104                }
105                // OP_PUSH1..32
106                c @ 0x60..0x80 => {
107                    // OP_PUSH0
108                    i += (c - 0x5F) as usize;
109                }
110                _ => (),
111            }
112            i += 1;
113        }
114        // Share the single empty table for jumpless bytecode (very common: EOAs,
115        // tiny contracts) so we don't allocate an `Arc` header for an empty slice.
116        if targets.is_empty() {
117            EMPTY_JUMP_TARGETS.clone()
118        } else {
119            Arc::from(targets)
120        }
121    }
122
123    #[inline]
124    pub fn code(&self) -> &[u8] {
125        self.bytecode.get(..self.bytecode_len).unwrap_or_default()
126    }
127
128    #[inline]
129    pub fn code_bytes(&self) -> Bytes {
130        self.bytecode.slice(..self.bytecode_len)
131    }
132
133    #[inline]
134    pub fn len(&self) -> usize {
135        self.bytecode_len
136    }
137
138    #[inline]
139    pub fn is_empty(&self) -> bool {
140        self.bytecode_len == 0
141    }
142
143    /// Returns the padded bytecode buffer (real code + [`BYTECODE_PADDING`] trailing
144    /// STOPs) used by the opcode dispatch loop to read opcodes without bounds checks.
145    /// Use [`Code::code`] for the real, unpadded bytecode.
146    #[inline]
147    pub fn dispatch_buf(&self) -> &[u8] {
148        &self.bytecode
149    }
150
151    /// Estimates the size of the Code struct in bytes
152    /// (including stack size and heap allocation).
153    ///
154    /// Note: This is an estimation and may not be exact.
155    ///
156    /// # Returns
157    ///
158    /// usize - Estimated size in bytes
159    pub fn size(&self) -> usize {
160        let hash_size = size_of::<H256>();
161        let bytes_size = size_of::<Bytes>();
162        let vec_size = size_of::<Arc<[u32]>>() + self.jump_targets.len() * size_of::<u32>();
163        hash_size + bytes_size + vec_size
164    }
165}
166
167/// Serde shadow for [`Code`]. Stores the *logical* (unpadded) bytecode so the
168/// padding is never part of the serialized form. Deserialization re-pads through
169/// [`Code::from_parts_unchecked`], which keeps the dispatch-loop invariant (every
170/// `Code` is padded with [`BYTECODE_PADDING`] trailing STOPs) sound regardless of
171/// where the bytes came from. Deserializing the padded buffer directly would
172/// otherwise let unpadded input through and cause OOB reads during execution.
173#[derive(Serialize, Deserialize)]
174struct CodeSerde {
175    hash: H256,
176    code: Bytes,
177    jump_targets: Arc<[u32]>,
178}
179
180impl Serialize for Code {
181    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
182        CodeSerde {
183            hash: self.hash,
184            code: self.code_bytes(),
185            jump_targets: self.jump_targets.clone(),
186        }
187        .serialize(serializer)
188    }
189}
190
191impl<'de> Deserialize<'de> for Code {
192    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
193        let CodeSerde {
194            hash,
195            code,
196            jump_targets,
197        } = CodeSerde::deserialize(deserializer)?;
198        Ok(Self::from_parts_unchecked(hash, &code, jump_targets))
199    }
200}
201
202#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
203pub struct CodeMetadata {
204    pub length: u64,
205}
206
207#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
208pub struct Account {
209    pub info: AccountInfo,
210    pub code: Code,
211    pub storage: FxHashMap<H256, U256>,
212}
213
214#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
215pub struct AccountInfo {
216    pub code_hash: H256,
217    pub balance: U256,
218    pub nonce: u64,
219}
220
221#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
222pub struct AccountState {
223    pub nonce: u64,
224    pub balance: U256,
225    pub storage_root: H256,
226    pub code_hash: H256,
227}
228
229/// A slim codec for an [`AccountState`].
230///
231/// The slim codec will optimize both the [storage root](AccountState::storage_root) and the
232/// [code hash](AccountState::code_hash)'s encoding so that it does not take space when empty.
233///
234/// The correct way to use it is to wrap the [`AccountState`] and encode it using this codec, and
235/// not to store the codec as a field in a struct.
236#[derive(Clone, Copy, Debug, Default, PartialEq)]
237pub struct AccountStateSlimCodec(pub AccountState);
238
239impl Default for AccountInfo {
240    fn default() -> Self {
241        Self {
242            code_hash: *EMPTY_KECCAK_HASH,
243            balance: Default::default(),
244            nonce: Default::default(),
245        }
246    }
247}
248
249impl Default for AccountState {
250    fn default() -> Self {
251        Self {
252            nonce: Default::default(),
253            balance: Default::default(),
254            storage_root: *EMPTY_TRIE_HASH,
255            code_hash: *EMPTY_KECCAK_HASH,
256        }
257    }
258}
259
260impl Default for Code {
261    fn default() -> Self {
262        Self {
263            bytecode: Bytes::from_static(&[0u8; BYTECODE_PADDING]),
264            bytecode_len: 0,
265            hash: *EMPTY_KECCAK_HASH,
266            jump_targets: EMPTY_JUMP_TARGETS.clone(),
267        }
268    }
269}
270
271impl From<GenesisAccount> for Account {
272    fn from(genesis: GenesisAccount) -> Self {
273        let code = Code::from_bytecode(genesis.code, &NativeCrypto);
274        Self {
275            info: AccountInfo {
276                code_hash: code.hash,
277                balance: genesis.balance,
278                nonce: genesis.nonce,
279            },
280            code,
281            storage: genesis
282                .storage
283                .iter()
284                .map(|(k, v)| (H256(k.to_big_endian()), *v))
285                .collect(),
286        }
287    }
288}
289
290pub fn code_hash(code: &Bytes, crypto: &dyn Crypto) -> H256 {
291    H256(crypto.keccak256(code.as_ref()))
292}
293
294/// EIP-7702 delegation designation: an EOA whose code is `0xef0100 || address`.
295/// See <https://eips.ethereum.org/EIPS/eip-7702>.
296pub const EIP7702_DELEGATION_PREFIX: [u8; 3] = [0xef, 0x01, 0x00];
297/// Total byte length of an EIP-7702 delegation designation: 3-byte prefix
298/// plus the 20-byte target address.
299pub const EIP7702_DELEGATED_CODE_LEN: usize = 23;
300
301/// Returns true iff `code` is a valid EIP-7702 delegation designation
302/// (exactly 23 bytes, prefixed with `0xef0100`).
303pub fn is_eip7702_delegation(code: &[u8]) -> bool {
304    code.len() == EIP7702_DELEGATED_CODE_LEN && code.starts_with(&EIP7702_DELEGATION_PREFIX)
305}
306
307impl RLPEncode for AccountInfo {
308    fn encode(&self, buf: &mut dyn bytes::BufMut) {
309        Encoder::new(buf)
310            .encode_field(&self.code_hash)
311            .encode_field(&self.balance)
312            .encode_field(&self.nonce)
313            .finish();
314    }
315}
316
317impl RLPDecode for AccountInfo {
318    fn decode_unfinished(rlp: &[u8]) -> Result<(AccountInfo, &[u8]), RLPDecodeError> {
319        let decoder = Decoder::new(rlp)?;
320        let (code_hash, decoder) = decoder.decode_field("code_hash")?;
321        let (balance, decoder) = decoder.decode_field("balance")?;
322        let (nonce, decoder) = decoder.decode_field("nonce")?;
323        let account_info = AccountInfo {
324            code_hash,
325            balance,
326            nonce,
327        };
328        Ok((account_info, decoder.finish()?))
329    }
330}
331
332impl RLPEncode for AccountState {
333    fn encode(&self, buf: &mut dyn bytes::BufMut) {
334        Encoder::new(buf)
335            .encode_field(&self.nonce)
336            .encode_field(&self.balance)
337            .encode_field(&self.storage_root)
338            .encode_field(&self.code_hash)
339            .finish();
340    }
341}
342
343impl RLPDecode for AccountState {
344    fn decode_unfinished(rlp: &[u8]) -> Result<(AccountState, &[u8]), RLPDecodeError> {
345        let decoder = Decoder::new(rlp)?;
346        let (nonce, decoder) = decoder.decode_field("nonce")?;
347        let (balance, decoder) = decoder.decode_field("balance")?;
348        let (storage_root, decoder) = decoder.decode_field("storage_root")?;
349        let (code_hash, decoder) = decoder.decode_field("code_hash")?;
350        let state = AccountState {
351            nonce,
352            balance,
353            storage_root,
354            code_hash,
355        };
356        Ok((state, decoder.finish()?))
357    }
358}
359
360impl RLPEncode for AccountStateSlimCodec {
361    fn encode(&self, buf: &mut dyn BufMut) {
362        struct StorageRootCodec<'a>(&'a H256);
363        impl RLPEncode for StorageRootCodec<'_> {
364            fn encode(&self, buf: &mut dyn BufMut) {
365                let data = if *self.0 != *EMPTY_TRIE_HASH {
366                    self.0.as_bytes()
367                } else {
368                    &[]
369                };
370
371                data.encode(buf);
372            }
373        }
374
375        struct CodeHashCodec<'a>(&'a H256);
376        impl RLPEncode for CodeHashCodec<'_> {
377            fn encode(&self, buf: &mut dyn BufMut) {
378                let data = if *self.0 != *EMPTY_KECCAK_HASH {
379                    self.0.as_bytes()
380                } else {
381                    &[]
382                };
383
384                data.encode(buf);
385            }
386        }
387
388        Encoder::new(buf)
389            .encode_field(&self.0.nonce)
390            .encode_field(&self.0.balance)
391            .encode_field(&StorageRootCodec(&self.0.storage_root))
392            .encode_field(&CodeHashCodec(&self.0.code_hash))
393            .finish();
394    }
395}
396
397impl RLPDecode for AccountStateSlimCodec {
398    fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
399        struct StorageRootCodec(H256);
400        impl RLPDecode for StorageRootCodec {
401            fn decode_unfinished(mut rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
402                let value = match rlp.split_off_first() {
403                    Some(0x80) => *EMPTY_TRIE_HASH,
404                    Some(0xA0) => {
405                        let data;
406                        (data, rlp) = rlp
407                            .split_first_chunk::<32>()
408                            .ok_or(RLPDecodeError::InvalidLength)?;
409                        H256(*data)
410                    }
411                    _ => return Err(RLPDecodeError::InvalidLength),
412                };
413
414                Ok((Self(value), rlp))
415            }
416        }
417
418        struct CodeHashCodec(H256);
419        impl RLPDecode for CodeHashCodec {
420            fn decode_unfinished(mut rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
421                let value = match rlp.split_off_first() {
422                    Some(0x80) => *EMPTY_KECCAK_HASH,
423                    Some(0xA0) => {
424                        let data;
425                        (data, rlp) = rlp
426                            .split_first_chunk::<32>()
427                            .ok_or(RLPDecodeError::InvalidLength)?;
428                        H256(*data)
429                    }
430                    _ => return Err(RLPDecodeError::InvalidLength),
431                };
432
433                Ok((Self(value), rlp))
434            }
435        }
436
437        let decoder = Decoder::new(rlp)?;
438        let (nonce, decoder) = decoder.decode_field("nonce")?;
439        let (balance, decoder) = decoder.decode_field("balance")?;
440        let (StorageRootCodec(storage_root), decoder) = decoder.decode_field("storage_root")?;
441        let (CodeHashCodec(code_hash), decoder) = decoder.decode_field("code_hash")?;
442
443        Ok((
444            Self(AccountState {
445                nonce,
446                balance,
447                storage_root,
448                code_hash,
449            }),
450            decoder.finish()?,
451        ))
452    }
453}
454
455pub fn compute_storage_root(storage: &BTreeMap<U256, U256>, crypto: &dyn Crypto) -> H256 {
456    let iter = storage.iter().filter_map(|(k, v)| {
457        (!v.is_zero()).then_some((
458            crypto.keccak256(&k.to_big_endian()).to_vec(),
459            v.encode_to_vec(),
460        ))
461    });
462    Trie::compute_hash_from_unsorted_iter(iter, crypto)
463}
464
465impl From<&GenesisAccount> for AccountState {
466    fn from(value: &GenesisAccount) -> Self {
467        AccountState {
468            nonce: value.nonce,
469            balance: value.balance,
470            storage_root: compute_storage_root(&value.storage, &NativeCrypto),
471            code_hash: code_hash(&value.code, &NativeCrypto),
472        }
473    }
474}
475
476impl Account {
477    pub fn new(balance: U256, code: Code, nonce: u64, storage: FxHashMap<H256, U256>) -> Self {
478        Self {
479            info: AccountInfo {
480                balance,
481                code_hash: code.hash,
482                nonce,
483            },
484            code,
485            storage,
486        }
487    }
488}
489
490impl AccountInfo {
491    pub fn is_empty(&self) -> bool {
492        self.balance.is_zero() && self.nonce == 0 && self.code_hash == *EMPTY_KECCAK_HASH
493    }
494}
495
496#[cfg(test)]
497mod test {
498    use std::str::FromStr;
499
500    use super::*;
501
502    #[test]
503    fn test_code_hash() {
504        let empty_code = Bytes::new();
505        let hash = code_hash(&empty_code, &NativeCrypto);
506        assert_eq!(
507            hash,
508            H256::from_str("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")
509                .unwrap()
510        )
511    }
512
513    #[test]
514    fn test_is_eip7702_delegation_valid() {
515        // 0xef0100 || 20-byte address
516        let mut code = Vec::with_capacity(23);
517        code.extend_from_slice(&EIP7702_DELEGATION_PREFIX);
518        code.extend_from_slice(&[0x42; 20]);
519        assert!(is_eip7702_delegation(&code));
520    }
521
522    #[test]
523    fn test_is_eip7702_delegation_rejects_empty() {
524        assert!(!is_eip7702_delegation(&[]));
525    }
526
527    #[test]
528    fn test_is_eip7702_delegation_rejects_short() {
529        // Prefix only, no address.
530        assert!(!is_eip7702_delegation(&EIP7702_DELEGATION_PREFIX));
531    }
532
533    #[test]
534    fn test_is_eip7702_delegation_rejects_long() {
535        // Correct prefix but 24 bytes total.
536        let mut code = Vec::with_capacity(24);
537        code.extend_from_slice(&EIP7702_DELEGATION_PREFIX);
538        code.extend_from_slice(&[0x42; 21]);
539        assert!(!is_eip7702_delegation(&code));
540    }
541
542    #[test]
543    fn test_is_eip7702_delegation_rejects_wrong_prefix() {
544        // Right length, wrong magic.
545        let mut code = Vec::with_capacity(23);
546        code.extend_from_slice(&[0xef, 0x01, 0x01]); // off by one in the last prefix byte
547        code.extend_from_slice(&[0x42; 20]);
548        assert!(!is_eip7702_delegation(&code));
549    }
550
551    #[test]
552    fn test_is_eip7702_delegation_rejects_arbitrary_contract_code() {
553        // Real contract code starting with anything else.
554        let code = vec![0x60, 0x60, 0x60, 0x40, 0x52 /* ... */];
555        assert!(!is_eip7702_delegation(&code));
556    }
557}