reth_primitives_traits/
account.rs

1use crate::InMemorySize;
2use alloy_consensus::constants::KECCAK_EMPTY;
3use alloy_genesis::GenesisAccount;
4use alloy_primitives::{keccak256, Bytes, B256, U256};
5use alloy_trie::TrieAccount;
6use derive_more::Deref;
7use revm_bytecode::{Bytecode as RevmBytecode, BytecodeDecodeError};
8use revm_state::AccountInfo;
9
10#[cfg(any(test, feature = "reth-codec"))]
11/// Identifiers used in [`Compact`](reth_codecs::Compact) encoding of [`Bytecode`].
12pub mod compact_ids {
13    /// Identifier for legacy raw bytecode.
14    pub const LEGACY_RAW_BYTECODE_ID: u8 = 0;
15
16    /// Identifier for removed bytecode variant.
17    pub const REMOVED_BYTECODE_ID: u8 = 1;
18
19    /// Identifier for [`LegacyAnalyzed`](revm_bytecode::Bytecode::LegacyAnalyzed).
20    pub const LEGACY_ANALYZED_BYTECODE_ID: u8 = 2;
21
22    /// Identifier for [`Eip7702`](revm_bytecode::Bytecode::Eip7702).
23    pub const EIP7702_BYTECODE_ID: u8 = 4;
24}
25
26/// An Ethereum account.
27#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
29#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
30#[cfg_attr(any(test, feature = "reth-codec"), derive(reth_codecs::Compact))]
31#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
32pub struct Account {
33    /// Account nonce.
34    pub nonce: u64,
35    /// Account balance.
36    pub balance: U256,
37    /// Hash of the account's bytecode.
38    pub bytecode_hash: Option<B256>,
39}
40
41impl Account {
42    /// Whether the account has bytecode.
43    pub const fn has_bytecode(&self) -> bool {
44        self.bytecode_hash.is_some()
45    }
46
47    /// After `SpuriousDragon` empty account is defined as account with nonce == 0 && balance == 0
48    /// && bytecode = None (or hash is [`KECCAK_EMPTY`]).
49    pub fn is_empty(&self) -> bool {
50        self.nonce == 0 &&
51            self.balance.is_zero() &&
52            self.bytecode_hash.is_none_or(|hash| hash == KECCAK_EMPTY)
53    }
54
55    /// Returns an account bytecode's hash.
56    /// In case of no bytecode, returns [`KECCAK_EMPTY`].
57    pub fn get_bytecode_hash(&self) -> B256 {
58        self.bytecode_hash.unwrap_or(KECCAK_EMPTY)
59    }
60
61    /// Converts the account into a trie account with the given storage root.
62    pub fn into_trie_account(self, storage_root: B256) -> TrieAccount {
63        let Self { nonce, balance, bytecode_hash } = self;
64        TrieAccount {
65            nonce,
66            balance,
67            storage_root,
68            code_hash: bytecode_hash.unwrap_or(KECCAK_EMPTY),
69        }
70    }
71
72    /// Extracts the account information from a [`revm_state::Account`]
73    pub fn from_revm_account(revm_account: &revm_state::Account) -> Self {
74        Self {
75            balance: revm_account.info.balance,
76            nonce: revm_account.info.nonce,
77            bytecode_hash: if revm_account.info.code_hash == revm_primitives::KECCAK_EMPTY {
78                None
79            } else {
80                Some(revm_account.info.code_hash)
81            },
82        }
83    }
84}
85
86impl From<revm_state::Account> for Account {
87    fn from(value: revm_state::Account) -> Self {
88        Self::from_revm_account(&value)
89    }
90}
91
92impl InMemorySize for Account {
93    fn size(&self) -> usize {
94        size_of::<Self>()
95    }
96}
97
98/// Bytecode for an account.
99///
100/// A wrapper around [`revm::primitives::Bytecode`][RevmBytecode] with encoding/decoding support.
101#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
102#[derive(Debug, Clone, Default, PartialEq, Eq, Deref)]
103pub struct Bytecode(pub RevmBytecode);
104
105impl Bytecode {
106    /// Create new bytecode from raw bytes.
107    ///
108    /// No analysis will be performed.
109    ///
110    /// # Panics
111    ///
112    /// Panics if bytecode is EOF and has incorrect format.
113    pub fn new_raw(bytes: Bytes) -> Self {
114        Self(RevmBytecode::new_raw(bytes))
115    }
116
117    /// Creates a new raw [`revm_bytecode::Bytecode`].
118    ///
119    /// Returns an error on incorrect Bytecode format.
120    #[inline]
121    pub fn new_raw_checked(bytecode: Bytes) -> Result<Self, BytecodeDecodeError> {
122        RevmBytecode::new_raw_checked(bytecode).map(Self)
123    }
124}
125
126#[cfg(any(test, feature = "reth-codec"))]
127impl reth_codecs::Compact for Bytecode {
128    fn to_compact<B>(&self, buf: &mut B) -> usize
129    where
130        B: bytes::BufMut + AsMut<[u8]>,
131    {
132        use compact_ids::{EIP7702_BYTECODE_ID, LEGACY_ANALYZED_BYTECODE_ID};
133
134        let bytecode = match &self.0 {
135            RevmBytecode::LegacyAnalyzed(analyzed) => analyzed.bytecode(),
136            RevmBytecode::Eip7702(eip7702) => eip7702.raw(),
137        };
138        buf.put_u32(bytecode.len() as u32);
139        buf.put_slice(bytecode.as_ref());
140        let len = match &self.0 {
141            // [`REMOVED_BYTECODE_ID`] has been removed.
142            RevmBytecode::LegacyAnalyzed(analyzed) => {
143                buf.put_u8(LEGACY_ANALYZED_BYTECODE_ID);
144                buf.put_u64(analyzed.original_len() as u64);
145                let map = analyzed.jump_table().as_slice();
146                buf.put_slice(map);
147                1 + 8 + map.len()
148            }
149            RevmBytecode::Eip7702(_) => {
150                buf.put_u8(EIP7702_BYTECODE_ID);
151                1
152            }
153        };
154        len + bytecode.len() + 4
155    }
156
157    // # Panics
158    //
159    // A panic will be triggered if a bytecode variant of 1 or greater than 2 is passed from the
160    // database.
161    fn from_compact(mut buf: &[u8], _: usize) -> (Self, &[u8]) {
162        use byteorder::ReadBytesExt;
163        use bytes::Buf;
164
165        use compact_ids::*;
166
167        let len = buf.read_u32::<byteorder::BigEndian>().expect("could not read bytecode length")
168            as usize;
169        let bytes = Bytes::from(buf.copy_to_bytes(len));
170        let variant = buf.read_u8().expect("could not read bytecode variant");
171        let decoded = match variant {
172            LEGACY_RAW_BYTECODE_ID => Self(RevmBytecode::new_raw(bytes)),
173            REMOVED_BYTECODE_ID => {
174                unreachable!("Junk data in database: checked Bytecode variant was removed")
175            }
176            LEGACY_ANALYZED_BYTECODE_ID => {
177                let original_len = buf.read_u64::<byteorder::BigEndian>().unwrap() as usize;
178                // When saving jumptable, its length is getting aligned to u8 boundary. Thus, we
179                // need to re-calculate the internal length of bitvec and truncate it when loading
180                // jumptables to avoid inconsistencies during `Compact` roundtrip.
181                let jump_table_len = if buf.len() * 8 >= bytes.len() {
182                    // Use length of padded bytecode if we can fit it
183                    bytes.len()
184                } else {
185                    // Otherwise, use original_len
186                    original_len
187                };
188                Self(RevmBytecode::new_analyzed(
189                    bytes,
190                    original_len,
191                    revm_bytecode::JumpTable::from_slice(buf, jump_table_len),
192                ))
193            }
194            EIP7702_BYTECODE_ID => {
195                // EIP-7702 bytecode objects will be decoded from the raw bytecode
196                Self(RevmBytecode::new_raw(bytes))
197            }
198            _ => unreachable!("Junk data in database: unknown Bytecode variant"),
199        };
200        (decoded, &[])
201    }
202}
203
204impl From<&GenesisAccount> for Account {
205    fn from(value: &GenesisAccount) -> Self {
206        Self {
207            nonce: value.nonce.unwrap_or_default(),
208            balance: value.balance,
209            bytecode_hash: value.code.as_ref().map(keccak256),
210        }
211    }
212}
213
214impl From<AccountInfo> for Account {
215    fn from(revm_acc: AccountInfo) -> Self {
216        Self {
217            balance: revm_acc.balance,
218            nonce: revm_acc.nonce,
219            bytecode_hash: (!revm_acc.is_empty_code_hash()).then_some(revm_acc.code_hash),
220        }
221    }
222}
223
224impl From<&AccountInfo> for Account {
225    fn from(revm_acc: &AccountInfo) -> Self {
226        Self {
227            balance: revm_acc.balance,
228            nonce: revm_acc.nonce,
229            bytecode_hash: (!revm_acc.is_empty_code_hash()).then_some(revm_acc.code_hash),
230        }
231    }
232}
233
234impl From<Account> for AccountInfo {
235    fn from(reth_acc: Account) -> Self {
236        Self {
237            balance: reth_acc.balance,
238            nonce: reth_acc.nonce,
239            code_hash: reth_acc.bytecode_hash.unwrap_or(KECCAK_EMPTY),
240            code: None,
241        }
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248    use alloy_primitives::{hex_literal::hex, B256, U256};
249    use reth_codecs::Compact;
250    use revm_bytecode::{JumpTable, LegacyAnalyzedBytecode};
251
252    #[test]
253    fn test_account() {
254        let mut buf = vec![];
255        let mut acc = Account::default();
256        let len = acc.to_compact(&mut buf);
257        assert_eq!(len, 2);
258
259        acc.balance = U256::from(2);
260        let len = acc.to_compact(&mut buf);
261        assert_eq!(len, 3);
262
263        acc.nonce = 2;
264        let len = acc.to_compact(&mut buf);
265        assert_eq!(len, 4);
266    }
267
268    #[test]
269    fn test_empty_account() {
270        let mut acc = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: None };
271        // Nonce 0, balance 0, and bytecode hash set to None is considered empty.
272        assert!(acc.is_empty());
273
274        acc.bytecode_hash = Some(KECCAK_EMPTY);
275        // Nonce 0, balance 0, and bytecode hash set to KECCAK_EMPTY is considered empty.
276        assert!(acc.is_empty());
277
278        acc.balance = U256::from(2);
279        // Non-zero balance makes it non-empty.
280        assert!(!acc.is_empty());
281
282        acc.balance = U256::ZERO;
283        acc.nonce = 10;
284        // Non-zero nonce makes it non-empty.
285        assert!(!acc.is_empty());
286
287        acc.nonce = 0;
288        acc.bytecode_hash = Some(B256::from(U256::ZERO));
289        // Non-empty bytecode hash makes it non-empty.
290        assert!(!acc.is_empty());
291    }
292
293    #[test]
294    #[ignore]
295    fn test_bytecode() {
296        let mut buf = vec![];
297        let bytecode = Bytecode::new_raw(Bytes::default());
298        let len = bytecode.to_compact(&mut buf);
299        assert_eq!(len, 14);
300
301        let mut buf = vec![];
302        let bytecode = Bytecode::new_raw(Bytes::from(&hex!("ffff")));
303        let len = bytecode.to_compact(&mut buf);
304        assert_eq!(len, 17);
305
306        let mut buf = vec![];
307        let bytecode = Bytecode(RevmBytecode::LegacyAnalyzed(LegacyAnalyzedBytecode::new(
308            Bytes::from(&hex!("ff00")),
309            2,
310            JumpTable::from_slice(&[0], 2),
311        )));
312        let len = bytecode.to_compact(&mut buf);
313        assert_eq!(len, 16);
314
315        let (decoded, remainder) = Bytecode::from_compact(&buf, len);
316        assert_eq!(decoded, bytecode);
317        assert!(remainder.is_empty());
318    }
319
320    #[test]
321    fn test_account_has_bytecode() {
322        // Account with no bytecode (None)
323        let acc_no_bytecode = Account { nonce: 1, balance: U256::from(1000), bytecode_hash: None };
324        assert!(!acc_no_bytecode.has_bytecode(), "Account should not have bytecode");
325
326        // Account with bytecode hash set to KECCAK_EMPTY (should have bytecode)
327        let acc_empty_bytecode =
328            Account { nonce: 1, balance: U256::from(1000), bytecode_hash: Some(KECCAK_EMPTY) };
329        assert!(acc_empty_bytecode.has_bytecode(), "Account should have bytecode");
330
331        // Account with a non-empty bytecode hash
332        let acc_with_bytecode = Account {
333            nonce: 1,
334            balance: U256::from(1000),
335            bytecode_hash: Some(B256::from_slice(&[0x11u8; 32])),
336        };
337        assert!(acc_with_bytecode.has_bytecode(), "Account should have bytecode");
338    }
339
340    #[test]
341    fn test_account_get_bytecode_hash() {
342        // Account with no bytecode (should return KECCAK_EMPTY)
343        let acc_no_bytecode = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: None };
344        assert_eq!(acc_no_bytecode.get_bytecode_hash(), KECCAK_EMPTY, "Should return KECCAK_EMPTY");
345
346        // Account with bytecode hash set to KECCAK_EMPTY
347        let acc_empty_bytecode =
348            Account { nonce: 1, balance: U256::from(1000), bytecode_hash: Some(KECCAK_EMPTY) };
349        assert_eq!(
350            acc_empty_bytecode.get_bytecode_hash(),
351            KECCAK_EMPTY,
352            "Should return KECCAK_EMPTY"
353        );
354
355        // Account with a valid bytecode hash
356        let bytecode_hash = B256::from_slice(&[0x11u8; 32]);
357        let acc_with_bytecode =
358            Account { nonce: 1, balance: U256::from(1000), bytecode_hash: Some(bytecode_hash) };
359        assert_eq!(
360            acc_with_bytecode.get_bytecode_hash(),
361            bytecode_hash,
362            "Should return the bytecode hash"
363        );
364    }
365}