reth_primitives_traits/
account.rs

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