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