reth_primitives_traits/
account.rs

1use alloy_consensus::constants::KECCAK_EMPTY;
2use alloy_genesis::GenesisAccount;
3use alloy_primitives::{keccak256, Bytes, B256, U256};
4use derive_more::Deref;
5use revm_primitives::{AccountInfo, Bytecode as RevmBytecode, BytecodeDecodeError};
6
7#[cfg(any(test, feature = "reth-codec"))]
8/// Identifiers used in [`Compact`](reth_codecs::Compact) encoding of [`Bytecode`].
9pub mod compact_ids {
10    /// Identifier for [`LegacyRaw`](revm_primitives::Bytecode::LegacyRaw).
11    pub const LEGACY_RAW_BYTECODE_ID: u8 = 0;
12
13    /// Identifier for removed bytecode variant.
14    pub const REMOVED_BYTECODE_ID: u8 = 1;
15
16    /// Identifier for [`LegacyAnalyzed`](revm_primitives::Bytecode::LegacyAnalyzed).
17    pub const LEGACY_ANALYZED_BYTECODE_ID: u8 = 2;
18
19    /// Identifier for [`Eof`](revm_primitives::Bytecode::Eof).
20    pub const EOF_BYTECODE_ID: u8 = 3;
21
22    /// Identifier for [`Eip7702`](revm_primitives::Bytecode::Eip7702).
23    pub const EIP7702_BYTECODE_ID: u8 = 4;
24}
25
26/// An Ethereum account.
27#[cfg_attr(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
62/// Bytecode for an account.
63///
64/// A wrapper around [`revm::primitives::Bytecode`][RevmBytecode] with encoding/decoding support.
65#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
66#[derive(Debug, Clone, Default, PartialEq, Eq, Deref)]
67pub struct Bytecode(pub RevmBytecode);
68
69impl Bytecode {
70    /// Create new bytecode from raw bytes.
71    ///
72    /// No analysis will be performed.
73    ///
74    /// # Panics
75    ///
76    /// Panics if bytecode is EOF and has incorrect format.
77    pub fn new_raw(bytes: Bytes) -> Self {
78        Self(RevmBytecode::new_raw(bytes))
79    }
80
81    /// Creates a new raw [`revm_primitives::Bytecode`].
82    ///
83    /// Returns an error on incorrect Bytecode format.
84    #[inline]
85    pub fn new_raw_checked(bytecode: Bytes) -> Result<Self, BytecodeDecodeError> {
86        RevmBytecode::new_raw_checked(bytecode).map(Self)
87    }
88}
89
90#[cfg(any(test, feature = "reth-codec"))]
91impl reth_codecs::Compact for Bytecode {
92    fn to_compact<B>(&self, buf: &mut B) -> usize
93    where
94        B: bytes::BufMut + AsMut<[u8]>,
95    {
96        use compact_ids::{
97            EIP7702_BYTECODE_ID, EOF_BYTECODE_ID, LEGACY_ANALYZED_BYTECODE_ID,
98            LEGACY_RAW_BYTECODE_ID,
99        };
100
101        let bytecode = match &self.0 {
102            RevmBytecode::LegacyRaw(bytes) => bytes,
103            RevmBytecode::LegacyAnalyzed(analyzed) => analyzed.bytecode(),
104            RevmBytecode::Eof(eof) => eof.raw(),
105            RevmBytecode::Eip7702(eip7702) => eip7702.raw(),
106        };
107        buf.put_u32(bytecode.len() as u32);
108        buf.put_slice(bytecode.as_ref());
109        let len = match &self.0 {
110            RevmBytecode::LegacyRaw(_) => {
111                buf.put_u8(LEGACY_RAW_BYTECODE_ID);
112                1
113            }
114            // [`REMOVED_BYTECODE_ID`] has been removed.
115            RevmBytecode::LegacyAnalyzed(analyzed) => {
116                buf.put_u8(LEGACY_ANALYZED_BYTECODE_ID);
117                buf.put_u64(analyzed.original_len() as u64);
118                let map = analyzed.jump_table().as_slice();
119                buf.put_slice(map);
120                1 + 8 + map.len()
121            }
122            RevmBytecode::Eof(_) => {
123                buf.put_u8(EOF_BYTECODE_ID);
124                1
125            }
126            RevmBytecode::Eip7702(_) => {
127                buf.put_u8(EIP7702_BYTECODE_ID);
128                1
129            }
130        };
131        len + bytecode.len() + 4
132    }
133
134    // # Panics
135    //
136    // A panic will be triggered if a bytecode variant of 1 or greater than 2 is passed from the
137    // database.
138    fn from_compact(mut buf: &[u8], _: usize) -> (Self, &[u8]) {
139        use byteorder::ReadBytesExt;
140        use bytes::Buf;
141
142        use compact_ids::*;
143
144        let len = buf.read_u32::<byteorder::BigEndian>().expect("could not read bytecode length");
145        let bytes = Bytes::from(buf.copy_to_bytes(len as usize));
146        let variant = buf.read_u8().expect("could not read bytecode variant");
147        let decoded = match variant {
148            LEGACY_RAW_BYTECODE_ID => Self(RevmBytecode::new_raw(bytes)),
149            REMOVED_BYTECODE_ID => {
150                unreachable!("Junk data in database: checked Bytecode variant was removed")
151            }
152            LEGACY_ANALYZED_BYTECODE_ID => Self(unsafe {
153                RevmBytecode::new_analyzed(
154                    bytes,
155                    buf.read_u64::<byteorder::BigEndian>().unwrap() as usize,
156                    revm_primitives::JumpTable::from_slice(buf),
157                )
158            }),
159            EOF_BYTECODE_ID | EIP7702_BYTECODE_ID => {
160                // EOF and EIP-7702 bytecode objects will be decoded from the raw bytecode
161                Self(RevmBytecode::new_raw(bytes))
162            }
163            _ => unreachable!("Junk data in database: unknown Bytecode variant"),
164        };
165        (decoded, &[])
166    }
167}
168
169impl From<&GenesisAccount> for Account {
170    fn from(value: &GenesisAccount) -> Self {
171        Self {
172            nonce: value.nonce.unwrap_or_default(),
173            balance: value.balance,
174            bytecode_hash: value.code.as_ref().map(keccak256),
175        }
176    }
177}
178
179impl From<AccountInfo> for Account {
180    fn from(revm_acc: AccountInfo) -> Self {
181        Self {
182            balance: revm_acc.balance,
183            nonce: revm_acc.nonce,
184            bytecode_hash: (!revm_acc.is_empty_code_hash()).then_some(revm_acc.code_hash),
185        }
186    }
187}
188
189impl From<&AccountInfo> for Account {
190    fn from(revm_acc: &AccountInfo) -> Self {
191        Self {
192            balance: revm_acc.balance,
193            nonce: revm_acc.nonce,
194            bytecode_hash: (!revm_acc.is_empty_code_hash()).then_some(revm_acc.code_hash),
195        }
196    }
197}
198
199impl From<Account> for AccountInfo {
200    fn from(reth_acc: Account) -> Self {
201        Self {
202            balance: reth_acc.balance,
203            nonce: reth_acc.nonce,
204            code_hash: reth_acc.bytecode_hash.unwrap_or(KECCAK_EMPTY),
205            code: None,
206        }
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use alloy_primitives::{hex_literal::hex, B256, U256};
213    use reth_codecs::Compact;
214    use revm_primitives::{JumpTable, LegacyAnalyzedBytecode};
215
216    use super::*;
217
218    #[test]
219    fn test_account() {
220        let mut buf = vec![];
221        let mut acc = Account::default();
222        let len = acc.to_compact(&mut buf);
223        assert_eq!(len, 2);
224
225        acc.balance = U256::from(2);
226        let len = acc.to_compact(&mut buf);
227        assert_eq!(len, 3);
228
229        acc.nonce = 2;
230        let len = acc.to_compact(&mut buf);
231        assert_eq!(len, 4);
232    }
233
234    #[test]
235    fn test_empty_account() {
236        let mut acc = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: None };
237        // Nonce 0, balance 0, and bytecode hash set to None is considered empty.
238        assert!(acc.is_empty());
239
240        acc.bytecode_hash = Some(KECCAK_EMPTY);
241        // Nonce 0, balance 0, and bytecode hash set to KECCAK_EMPTY is considered empty.
242        assert!(acc.is_empty());
243
244        acc.balance = U256::from(2);
245        // Non-zero balance makes it non-empty.
246        assert!(!acc.is_empty());
247
248        acc.balance = U256::ZERO;
249        acc.nonce = 10;
250        // Non-zero nonce makes it non-empty.
251        assert!(!acc.is_empty());
252
253        acc.nonce = 0;
254        acc.bytecode_hash = Some(B256::from(U256::ZERO));
255        // Non-empty bytecode hash makes it non-empty.
256        assert!(!acc.is_empty());
257    }
258
259    #[test]
260    fn test_bytecode() {
261        let mut buf = vec![];
262        let bytecode = Bytecode::new_raw(Bytes::default());
263        let len = bytecode.to_compact(&mut buf);
264        assert_eq!(len, 5);
265
266        let mut buf = vec![];
267        let bytecode = Bytecode::new_raw(Bytes::from(&hex!("ffff")));
268        let len = bytecode.to_compact(&mut buf);
269        assert_eq!(len, 7);
270
271        let mut buf = vec![];
272        let bytecode = Bytecode(RevmBytecode::LegacyAnalyzed(LegacyAnalyzedBytecode::new(
273            Bytes::from(&hex!("ffff")),
274            2,
275            JumpTable::from_slice(&[0]),
276        )));
277        let len = bytecode.to_compact(&mut buf);
278        assert_eq!(len, 16);
279
280        let (decoded, remainder) = Bytecode::from_compact(&buf, len);
281        assert_eq!(decoded, bytecode);
282        assert!(remainder.is_empty());
283    }
284
285    #[test]
286    fn test_account_has_bytecode() {
287        // Account with no bytecode (None)
288        let acc_no_bytecode = Account { nonce: 1, balance: U256::from(1000), bytecode_hash: None };
289        assert!(!acc_no_bytecode.has_bytecode(), "Account should not have bytecode");
290
291        // Account with bytecode hash set to KECCAK_EMPTY (should have bytecode)
292        let acc_empty_bytecode =
293            Account { nonce: 1, balance: U256::from(1000), bytecode_hash: Some(KECCAK_EMPTY) };
294        assert!(acc_empty_bytecode.has_bytecode(), "Account should have bytecode");
295
296        // Account with a non-empty bytecode hash
297        let acc_with_bytecode = Account {
298            nonce: 1,
299            balance: U256::from(1000),
300            bytecode_hash: Some(B256::from_slice(&[0x11u8; 32])),
301        };
302        assert!(acc_with_bytecode.has_bytecode(), "Account should have bytecode");
303    }
304
305    #[test]
306    fn test_account_get_bytecode_hash() {
307        // Account with no bytecode (should return KECCAK_EMPTY)
308        let acc_no_bytecode = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: None };
309        assert_eq!(acc_no_bytecode.get_bytecode_hash(), KECCAK_EMPTY, "Should return KECCAK_EMPTY");
310
311        // Account with bytecode hash set to KECCAK_EMPTY
312        let acc_empty_bytecode =
313            Account { nonce: 1, balance: U256::from(1000), bytecode_hash: Some(KECCAK_EMPTY) };
314        assert_eq!(
315            acc_empty_bytecode.get_bytecode_hash(),
316            KECCAK_EMPTY,
317            "Should return KECCAK_EMPTY"
318        );
319
320        // Account with a valid bytecode hash
321        let bytecode_hash = B256::from_slice(&[0x11u8; 32]);
322        let acc_with_bytecode =
323            Account { nonce: 1, balance: U256::from(1000), bytecode_hash: Some(bytecode_hash) };
324        assert_eq!(
325            acc_with_bytecode.get_bytecode_hash(),
326            bytecode_hash,
327            "Should return the bytecode hash"
328        );
329    }
330}