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