Skip to main content

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 From<TrieAccount> for Account {
93    fn from(value: TrieAccount) -> Self {
94        Self { balance: value.balance, nonce: value.nonce, bytecode_hash: Some(value.code_hash) }
95    }
96}
97
98impl InMemorySize for Account {
99    fn size(&self) -> usize {
100        size_of::<Self>()
101    }
102}
103
104/// Bytecode for an account.
105///
106/// A wrapper around [`revm::primitives::Bytecode`][RevmBytecode] with encoding/decoding support.
107#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
108#[derive(Debug, Clone, Default, PartialEq, Eq, Deref)]
109pub struct Bytecode(pub RevmBytecode);
110
111impl Bytecode {
112    /// Create new bytecode from raw bytes.
113    ///
114    /// No analysis will be performed.
115    ///
116    /// # Panics
117    ///
118    /// Panics if bytecode is EOF and has incorrect format.
119    pub fn new_raw(bytes: Bytes) -> Self {
120        Self(RevmBytecode::new_raw(bytes))
121    }
122
123    /// Creates a new raw [`revm_bytecode::Bytecode`].
124    ///
125    /// Returns an error on incorrect Bytecode format.
126    #[inline]
127    pub fn new_raw_checked(bytecode: Bytes) -> Result<Self, BytecodeDecodeError> {
128        RevmBytecode::new_raw_checked(bytecode).map(Self)
129    }
130}
131
132#[cfg(any(test, feature = "reth-codec"))]
133impl reth_codecs::Compact for Bytecode {
134    fn to_compact<B>(&self, buf: &mut B) -> usize
135    where
136        B: bytes::BufMut + AsMut<[u8]>,
137    {
138        use compact_ids::{EIP7702_BYTECODE_ID, LEGACY_ANALYZED_BYTECODE_ID};
139
140        let bytecode = match &self.0 {
141            RevmBytecode::LegacyAnalyzed(analyzed) => analyzed.bytecode(),
142            RevmBytecode::Eip7702(eip7702) => eip7702.raw(),
143        };
144        buf.put_u32(bytecode.len() as u32);
145        buf.put_slice(bytecode.as_ref());
146        let len = match &self.0 {
147            // [`REMOVED_BYTECODE_ID`] has been removed.
148            RevmBytecode::LegacyAnalyzed(analyzed) => {
149                buf.put_u8(LEGACY_ANALYZED_BYTECODE_ID);
150                buf.put_u64(analyzed.original_len() as u64);
151                let map = analyzed.jump_table().as_slice();
152                buf.put_slice(map);
153                1 + 8 + map.len()
154            }
155            RevmBytecode::Eip7702(_) => {
156                buf.put_u8(EIP7702_BYTECODE_ID);
157                1
158            }
159        };
160        len + bytecode.len() + 4
161    }
162
163    // # Panics
164    //
165    // A panic will be triggered if a bytecode variant of 1 or greater than 2 is passed from the
166    // database.
167    fn from_compact(mut buf: &[u8], _: usize) -> (Self, &[u8]) {
168        use byteorder::ReadBytesExt;
169        use bytes::Buf;
170
171        use compact_ids::*;
172
173        let len = buf.read_u32::<byteorder::BigEndian>().expect("could not read bytecode length")
174            as usize;
175        let bytes = Bytes::from(buf.copy_to_bytes(len));
176        let variant = buf.read_u8().expect("could not read bytecode variant");
177        let decoded = match variant {
178            LEGACY_RAW_BYTECODE_ID => Self(RevmBytecode::new_raw(bytes)),
179            REMOVED_BYTECODE_ID => {
180                unreachable!("Junk data in database: checked Bytecode variant was removed")
181            }
182            LEGACY_ANALYZED_BYTECODE_ID => {
183                let original_len = buf.read_u64::<byteorder::BigEndian>().unwrap() as usize;
184                // When saving jumptable, its length is getting aligned to u8 boundary. Thus, we
185                // need to re-calculate the internal length of bitvec and truncate it when loading
186                // jumptables to avoid inconsistencies during `Compact` roundtrip.
187                let jump_table_len = if buf.len() * 8 >= bytes.len() {
188                    // Use length of padded bytecode if we can fit it
189                    bytes.len()
190                } else {
191                    // Otherwise, use original_len
192                    original_len
193                };
194                Self(RevmBytecode::new_analyzed(
195                    bytes,
196                    original_len,
197                    revm_bytecode::JumpTable::from_slice(buf, jump_table_len),
198                ))
199            }
200            EIP7702_BYTECODE_ID => {
201                // EIP-7702 bytecode objects will be decoded from the raw bytecode
202                Self(RevmBytecode::new_raw(bytes))
203            }
204            _ => unreachable!("Junk data in database: unknown Bytecode variant"),
205        };
206        (decoded, &[])
207    }
208}
209
210impl From<&GenesisAccount> for Account {
211    fn from(value: &GenesisAccount) -> Self {
212        Self {
213            nonce: value.nonce.unwrap_or_default(),
214            balance: value.balance,
215            bytecode_hash: value.code.as_ref().map(keccak256),
216        }
217    }
218}
219
220impl From<AccountInfo> for Account {
221    fn from(revm_acc: AccountInfo) -> Self {
222        Self {
223            balance: revm_acc.balance,
224            nonce: revm_acc.nonce,
225            bytecode_hash: (!revm_acc.is_empty_code_hash()).then_some(revm_acc.code_hash),
226        }
227    }
228}
229
230impl From<&AccountInfo> for Account {
231    fn from(revm_acc: &AccountInfo) -> Self {
232        Self {
233            balance: revm_acc.balance,
234            nonce: revm_acc.nonce,
235            bytecode_hash: (!revm_acc.is_empty_code_hash()).then_some(revm_acc.code_hash),
236        }
237    }
238}
239
240impl From<Account> for AccountInfo {
241    fn from(reth_acc: Account) -> Self {
242        Self {
243            balance: reth_acc.balance,
244            nonce: reth_acc.nonce,
245            code_hash: reth_acc.bytecode_hash.unwrap_or(KECCAK_EMPTY),
246            code: None,
247            account_id: None,
248        }
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use std::sync::Arc;
255
256    use super::*;
257    use alloy_primitives::{hex_literal::hex, B256, U256};
258    use reth_codecs::Compact;
259    use revm_bytecode::{JumpTable, LegacyAnalyzedBytecode};
260
261    #[test]
262    fn test_account() {
263        let mut buf = vec![];
264        let mut acc = Account::default();
265        let len = acc.to_compact(&mut buf);
266        assert_eq!(len, 2);
267
268        acc.balance = U256::from(2);
269        let len = acc.to_compact(&mut buf);
270        assert_eq!(len, 3);
271
272        acc.nonce = 2;
273        let len = acc.to_compact(&mut buf);
274        assert_eq!(len, 4);
275    }
276
277    #[test]
278    fn test_empty_account() {
279        let mut acc = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: None };
280        // Nonce 0, balance 0, and bytecode hash set to None is considered empty.
281        assert!(acc.is_empty());
282
283        acc.bytecode_hash = Some(KECCAK_EMPTY);
284        // Nonce 0, balance 0, and bytecode hash set to KECCAK_EMPTY is considered empty.
285        assert!(acc.is_empty());
286
287        acc.balance = U256::from(2);
288        // Non-zero balance makes it non-empty.
289        assert!(!acc.is_empty());
290
291        acc.balance = U256::ZERO;
292        acc.nonce = 10;
293        // Non-zero nonce makes it non-empty.
294        assert!(!acc.is_empty());
295
296        acc.nonce = 0;
297        acc.bytecode_hash = Some(B256::from(U256::ZERO));
298        // Non-empty bytecode hash makes it non-empty.
299        assert!(!acc.is_empty());
300    }
301
302    #[test]
303    #[ignore]
304    fn test_bytecode() {
305        let mut buf = vec![];
306        let bytecode = Bytecode::new_raw(Bytes::default());
307        let len = bytecode.to_compact(&mut buf);
308        assert_eq!(len, 14);
309
310        let mut buf = vec![];
311        let bytecode = Bytecode::new_raw(Bytes::from(&hex!("ffff")));
312        let len = bytecode.to_compact(&mut buf);
313        assert_eq!(len, 17);
314
315        let mut buf = vec![];
316        let bytecode =
317            Bytecode(RevmBytecode::LegacyAnalyzed(Arc::new(LegacyAnalyzedBytecode::new(
318                Bytes::from(&hex!("ff00")),
319                2,
320                JumpTable::from_slice(&[0], 2),
321            ))));
322        let len = bytecode.to_compact(&mut buf);
323        assert_eq!(len, 16);
324
325        let (decoded, remainder) = Bytecode::from_compact(&buf, len);
326        assert_eq!(decoded, bytecode);
327        assert!(remainder.is_empty());
328    }
329
330    #[test]
331    fn test_account_has_bytecode() {
332        // Account with no bytecode (None)
333        let acc_no_bytecode = Account { nonce: 1, balance: U256::from(1000), bytecode_hash: None };
334        assert!(!acc_no_bytecode.has_bytecode(), "Account should not have bytecode");
335
336        // Account with bytecode hash set to KECCAK_EMPTY (should have bytecode)
337        let acc_empty_bytecode =
338            Account { nonce: 1, balance: U256::from(1000), bytecode_hash: Some(KECCAK_EMPTY) };
339        assert!(acc_empty_bytecode.has_bytecode(), "Account should have bytecode");
340
341        // Account with a non-empty bytecode hash
342        let acc_with_bytecode = Account {
343            nonce: 1,
344            balance: U256::from(1000),
345            bytecode_hash: Some(B256::from_slice(&[0x11u8; 32])),
346        };
347        assert!(acc_with_bytecode.has_bytecode(), "Account should have bytecode");
348    }
349
350    #[test]
351    fn test_account_get_bytecode_hash() {
352        // Account with no bytecode (should return KECCAK_EMPTY)
353        let acc_no_bytecode = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: None };
354        assert_eq!(acc_no_bytecode.get_bytecode_hash(), KECCAK_EMPTY, "Should return KECCAK_EMPTY");
355
356        // Account with bytecode hash set to KECCAK_EMPTY
357        let acc_empty_bytecode =
358            Account { nonce: 1, balance: U256::from(1000), bytecode_hash: Some(KECCAK_EMPTY) };
359        assert_eq!(
360            acc_empty_bytecode.get_bytecode_hash(),
361            KECCAK_EMPTY,
362            "Should return KECCAK_EMPTY"
363        );
364
365        // Account with a valid bytecode hash
366        let bytecode_hash = B256::from_slice(&[0x11u8; 32]);
367        let acc_with_bytecode =
368            Account { nonce: 1, balance: U256::from(1000), bytecode_hash: Some(bytecode_hash) };
369        assert_eq!(
370            acc_with_bytecode.get_bytecode_hash(),
371            bytecode_hash,
372            "Should return the bytecode hash"
373        );
374    }
375}