Skip to main content

revm_state/
account_info.rs

1#[cfg(feature = "account-ext")]
2use crate::AccountExtension;
3use bytecode::Bytecode;
4use core::{
5    cmp::Ordering,
6    hash::{Hash, Hasher},
7};
8use primitives::{B256, KECCAK_EMPTY, U256};
9
10use nonmax::NonMaxU32;
11
12/// Account ID is a custom type that wraps a `NonMaxU32`
13#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub struct AccountId(NonMaxU32);
16
17impl AccountId {
18    /// Creates a new AccountId.
19    ///
20    /// Returns `None` if the value does not fit in the internal representation.
21    #[inline]
22    pub fn new(id: usize) -> Option<Self> {
23        let id = u32::try_from(id).ok()?;
24        NonMaxU32::new(id).map(Self)
25    }
26
27    /// Gets the account ID as a usize.
28    #[inline]
29    pub const fn get(self) -> usize {
30        self.0.get() as usize
31    }
32}
33
34/// Account information that contains balance, nonce, code hash and code
35///
36/// Code is set as optional.
37///
38/// The opt-in `account-ext` feature adds a shared, ThinArc-backed extension payload.
39/// Without it, the account layout and serialization have no extension field.
40#[derive(Clone, Debug, Eq)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42pub struct AccountInfo {
43    /// Account balance.
44    pub balance: U256,
45    /// Account nonce.
46    pub nonce: u64,
47    /// Hash of the raw bytes in `code`, or [`KECCAK_EMPTY`].
48    pub code_hash: B256,
49    /// Used as a hint to optimize the access to the storage of account.
50    ///
51    /// It is set when account is loaded from the database, and if it is `Some` it will called
52    /// by journal to ask database the storage with this account_id (It will still send the address to the database).
53    #[cfg_attr(feature = "serde", serde(skip))]
54    pub account_id: Option<AccountId>,
55    /// [`Bytecode`] data associated with this account.
56    ///
57    /// If [`None`], `code_hash` will be used to fetch it from the database, if code needs to be
58    /// loaded from inside `revm`.
59    ///
60    /// By default, this is `Some(Bytecode::default())`.
61    pub code: Option<Bytecode>,
62    /// Chain-specific account data carried through execution and state transitions.
63    #[cfg_attr(
64        feature = "serde",
65        serde(default, skip_serializing_if = "AccountExtension::is_empty")
66    )]
67    #[cfg(feature = "account-ext")]
68    pub extension: AccountExtension,
69}
70
71impl Default for AccountInfo {
72    #[inline]
73    fn default() -> Self {
74        Self {
75            balance: U256::ZERO,
76            code_hash: KECCAK_EMPTY,
77            account_id: None,
78            nonce: 0,
79            code: Some(Bytecode::default()),
80            #[cfg(feature = "account-ext")]
81            extension: AccountExtension::new(),
82        }
83    }
84}
85
86impl PartialEq for AccountInfo {
87    #[inline]
88    fn eq(&self, other: &Self) -> bool {
89        let equal = self.balance == other.balance
90            && self.nonce == other.nonce
91            && self.code_hash == other.code_hash;
92        #[cfg(feature = "account-ext")]
93        let equal = equal && self.extension == other.extension;
94        equal
95    }
96}
97
98impl Hash for AccountInfo {
99    #[inline]
100    fn hash<H: Hasher>(&self, state: &mut H) {
101        self.balance.hash(state);
102        self.nonce.hash(state);
103        self.code_hash.hash(state);
104    }
105}
106
107impl PartialOrd for AccountInfo {
108    #[inline]
109    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
110        Some(self.cmp(other))
111    }
112}
113
114impl Ord for AccountInfo {
115    #[inline]
116    fn cmp(&self, other: &Self) -> Ordering {
117        let order = self
118            .balance
119            .cmp(&other.balance)
120            .then_with(|| self.nonce.cmp(&other.nonce))
121            .then_with(|| self.code_hash.cmp(&other.code_hash));
122        #[cfg(feature = "account-ext")]
123        let order = order.then_with(|| self.extension.cmp(&other.extension));
124        order
125    }
126}
127
128impl AccountInfo {
129    /// Creates a new [`AccountInfo`] with the given fields.
130    #[inline]
131    pub const fn new(balance: U256, nonce: u64, code_hash: B256, code: Bytecode) -> Self {
132        Self {
133            balance,
134            nonce,
135            code: Some(code),
136            code_hash,
137            account_id: None,
138            #[cfg(feature = "account-ext")]
139            extension: AccountExtension::new(),
140        }
141    }
142
143    /// Creates a new [`AccountInfo`] with the given code.
144    ///
145    /// # Note
146    ///
147    /// As code hash is calculated with [`Bytecode::hash_slow`] there will be performance penalty if used frequently.
148    #[inline]
149    pub fn with_code(self, code: Bytecode) -> Self {
150        Self {
151            code_hash: code.hash_slow(),
152            code: Some(code),
153            ..self
154        }
155    }
156
157    /// Creates a new [`AccountInfo`] with the given code hash.
158    ///
159    /// # Note
160    ///
161    /// Resets code to `None`. Not guaranteed to maintain invariant `code` and `code_hash`. See
162    /// also [Self::with_code_and_hash].
163    #[inline]
164    pub fn with_code_hash(self, code_hash: B256) -> Self {
165        Self {
166            code_hash,
167            code: None,
168            ..self
169        }
170    }
171
172    /// Creates a new [`AccountInfo`] with the given code and code hash.
173    ///
174    /// # Note
175    ///
176    /// In debug mode panics if [`Bytecode::hash_slow`] called on `code` is not equivalent to
177    /// `code_hash`. See also [`Self::with_code`].
178    #[inline]
179    pub fn with_code_and_hash(self, code: Bytecode, code_hash: B256) -> Self {
180        debug_assert_eq!(code.hash_slow(), code_hash);
181        Self {
182            code_hash,
183            code: Some(code),
184            ..self
185        }
186    }
187
188    /// Creates a new [`AccountInfo`] with the given balance.
189    #[inline]
190    pub const fn with_balance(mut self, balance: U256) -> Self {
191        self.balance = balance;
192        self
193    }
194
195    /// Creates a new [`AccountInfo`] with the given nonce.
196    #[inline]
197    pub const fn with_nonce(mut self, nonce: u64) -> Self {
198        self.nonce = nonce;
199        self
200    }
201
202    /// Sets the [`AccountInfo`] `balance`.
203    #[inline]
204    pub const fn set_balance(&mut self, balance: U256) -> &mut Self {
205        self.balance = balance;
206        self
207    }
208
209    /// Sets the [`AccountInfo`] `nonce`.
210    #[inline]
211    pub const fn set_nonce(&mut self, nonce: u64) -> &mut Self {
212        self.nonce = nonce;
213        self
214    }
215
216    /// Sets the [`AccountInfo`] `code_hash` and clears any cached bytecode.
217    ///
218    /// # Note
219    ///
220    /// Calling this after `set_code(...)` will remove the bytecode you just set.
221    /// If you intend to mutate the code, use only `set_code`.
222    #[inline]
223    pub fn set_code_hash(&mut self, code_hash: B256) -> &mut Self {
224        self.code = None;
225        self.code_hash = code_hash;
226        self
227    }
228
229    /// Replaces the [`AccountInfo`] bytecode and recalculates `code_hash`.
230    ///
231    /// # Note
232    ///
233    /// As code hash is calculated with [`Bytecode::hash_slow`] there will be performance penalty if used frequently.
234    #[inline]
235    pub fn set_code(&mut self, code: Bytecode) -> &mut Self {
236        self.code_hash = code.hash_slow();
237        self.code = Some(code);
238        self
239    }
240    /// Sets the bytecode and its hash, returning the previous code and hash.
241    ///
242    /// # Note
243    ///
244    /// It is on the caller's responsibility to ensure that the bytecode hash is correct.
245    pub const fn set_code_and_hash(
246        &mut self,
247        code: Bytecode,
248        code_hash: B256,
249    ) -> (B256, Option<Bytecode>) {
250        let previous_hash = core::mem::replace(&mut self.code_hash, code_hash);
251        let previous_code = self.code.replace(code);
252        (previous_hash, previous_code)
253    }
254    /// Returns a copy of this account with the [`Bytecode`] removed.
255    ///
256    /// This is useful when creating journals or snapshots of the state, where it is
257    /// desirable to store the code blobs elsewhere.
258    ///
259    /// ## Note
260    ///
261    /// This is distinct from [`without_code`][Self::without_code] in that it returns
262    /// a new [`AccountInfo`] instance with the code removed.
263    ///
264    /// [`without_code`][Self::without_code] will modify and return the same instance.
265    #[inline]
266    #[cfg(feature = "account-ext")]
267    pub fn copy_without_code(&self) -> Self {
268        Self {
269            balance: self.balance,
270            nonce: self.nonce,
271            code_hash: self.code_hash,
272            account_id: self.account_id,
273            code: None,
274            extension: self.extension.clone(),
275        }
276    }
277
278    /// Returns a copy of this account with the bytecode removed.
279    #[inline]
280    #[cfg(not(feature = "account-ext"))]
281    pub const fn copy_without_code(&self) -> Self {
282        Self {
283            balance: self.balance,
284            nonce: self.nonce,
285            code_hash: self.code_hash,
286            account_id: self.account_id,
287            code: None,
288        }
289    }
290
291    /// Strips the [`Bytecode`] from this account and drop it.
292    ///
293    /// This is useful when creating journals or snapshots of the state, where it is
294    /// desirable to store the code blobs elsewhere.
295    ///
296    /// ## Note
297    ///
298    /// This is distinct from [`copy_without_code`][Self::copy_without_code] in that it
299    /// modifies the account in place.
300    ///
301    /// [`copy_without_code`][Self::copy_without_code]
302    /// will copy the non-code fields and return a new [`AccountInfo`] instance.
303    #[inline]
304    pub fn without_code(mut self) -> Self {
305        self.take_bytecode();
306        self
307    }
308
309    /// Returns if an account is empty.
310    ///
311    /// An account is empty if the following conditions are met.
312    /// - code hash is zero or set to the Keccak256 hash of the empty string `""`
313    /// - balance is zero
314    /// - nonce is zero
315    #[inline]
316    pub fn is_empty(&self) -> bool {
317        let empty = self.is_code_hash_empty_or_zero() && self.balance.is_zero() && self.nonce == 0;
318        #[cfg(feature = "account-ext")]
319        let empty = empty && self.extension.is_empty();
320        empty
321    }
322
323    /// Optimization hint.
324    #[inline]
325    pub(crate) fn is_default(&self) -> bool {
326        self.is_empty() && self.code.as_ref().is_some_and(Bytecode::is_default)
327    }
328
329    /// Returns `true` if the account is not empty.
330    #[inline]
331    pub fn exists(&self) -> bool {
332        !self.is_empty()
333    }
334
335    /// Returns `true` if account has no nonce and code.
336    #[inline]
337    pub fn has_no_code_and_nonce(&self) -> bool {
338        self.is_empty_code_hash() && self.nonce == 0
339    }
340
341    /// Returns bytecode hash associated with this account.
342    ///
343    /// If account does not have code, it returns `KECCAK_EMPTY` hash.
344    #[inline]
345    pub const fn code_hash(&self) -> B256 {
346        self.code_hash
347    }
348
349    /// Returns this account with chain-specific extension data.
350    #[inline]
351    #[cfg(feature = "account-ext")]
352    pub fn with_extension(mut self, extension: impl Into<AccountExtension>) -> Self {
353        self.extension = extension.into();
354        self
355    }
356
357    /// Replaces the chain-specific extension data.
358    #[inline]
359    #[cfg(feature = "account-ext")]
360    pub const fn set_extension(&mut self, extension: AccountExtension) -> AccountExtension {
361        core::mem::replace(&mut self.extension, extension)
362    }
363
364    /// Returns true if the code hash is the Keccak256 hash of the empty string `""`.
365    #[inline]
366    pub fn is_empty_code_hash(&self) -> bool {
367        self.code_hash == KECCAK_EMPTY
368    }
369
370    /// Returns true if the code hash is the Keccak256 hash of the empty string `""` or is zero.
371    #[inline]
372    pub fn is_code_hash_empty_or_zero(&self) -> bool {
373        self.is_empty_code_hash() || self.code_hash.is_zero()
374    }
375
376    /// Takes bytecode from account.
377    ///
378    /// Code will be set to [None].
379    #[inline]
380    pub const fn take_bytecode(&mut self) -> Option<Bytecode> {
381        self.code.take()
382    }
383
384    /// Initializes an [`AccountInfo`] with the given balance, setting all other fields to their
385    /// default values.
386    #[inline]
387    pub fn from_balance(balance: U256) -> Self {
388        AccountInfo {
389            balance,
390            ..Default::default()
391        }
392    }
393
394    /// Initializes an [`AccountInfo`] with the given bytecode, setting its balance to zero, its
395    /// nonce to `1`, and calculating the code hash from the given bytecode.
396    #[inline]
397    pub fn from_bytecode(bytecode: Bytecode) -> Self {
398        let hash = bytecode.hash_slow();
399
400        AccountInfo {
401            balance: U256::ZERO,
402            nonce: 1,
403            code: Some(bytecode),
404            code_hash: hash,
405            account_id: None,
406            #[cfg(feature = "account-ext")]
407            extension: AccountExtension::new(),
408        }
409    }
410}
411
412#[cfg(test)]
413mod tests {
414    use super::*;
415    use std::collections::BTreeSet;
416
417    #[test]
418    #[cfg(target_pointer_width = "64")]
419    fn account_info_inline_size() {
420        assert_eq!(
421            size_of::<AccountInfo>(),
422            if cfg!(feature = "account-ext") {
423                96
424            } else {
425                88
426            }
427        );
428    }
429
430    #[test]
431    #[cfg(feature = "serde")]
432    fn empty_extension_preserves_legacy_serde() {
433        #[derive(serde::Serialize, serde::Deserialize)]
434        struct LegacyAccountInfo {
435            balance: U256,
436            nonce: u64,
437            code_hash: B256,
438            code: Option<Bytecode>,
439        }
440        let account = AccountInfo::new(U256::from(42), 7, KECCAK_EMPTY, Bytecode::default());
441        let legacy = LegacyAccountInfo {
442            balance: account.balance,
443            nonce: account.nonce,
444            code_hash: account.code_hash,
445            code: account.code.clone(),
446        };
447        let json = serde_json::to_vec(&legacy).unwrap();
448        assert_eq!(serde_json::to_vec(&account).unwrap(), json);
449        assert_eq!(
450            serde_json::from_slice::<AccountInfo>(&json).unwrap(),
451            account
452        );
453        let encoded = rmp_serde::to_vec(&account).unwrap();
454        assert_eq!(encoded, rmp_serde::to_vec(&legacy).unwrap());
455        assert_eq!(
456            rmp_serde::from_slice::<AccountInfo>(&encoded).unwrap(),
457            account
458        );
459        let decoded: LegacyAccountInfo = rmp_serde::from_slice(&encoded).unwrap();
460        assert_eq!(rmp_serde::to_vec(&decoded).unwrap(), encoded);
461        let binary = postcard::to_allocvec(&legacy).unwrap();
462        assert_eq!(postcard::to_allocvec(&account).unwrap(), binary);
463        #[cfg(not(feature = "account-ext"))]
464        assert_eq!(
465            postcard::from_bytes::<AccountInfo>(&binary).unwrap(),
466            account
467        );
468    }
469
470    #[test]
471    #[cfg(all(feature = "serde", feature = "account-ext"))]
472    fn account_info_messagepack_roundtrip() {
473        let accounts = vec![
474            AccountInfo::default(),
475            AccountInfo::default().with_extension(vec![0x82, 0xaa]),
476            AccountInfo::default(),
477        ];
478        let record = (accounts, 99u64);
479        let encoded = rmp_serde::to_vec(&record).unwrap();
480        let decoded: (Vec<AccountInfo>, u64) = rmp_serde::from_slice(&encoded).unwrap();
481        assert_eq!(decoded, record);
482    }
483
484    #[test]
485    fn test_account_info_trait_consistency() {
486        let bytecode = Bytecode::default();
487        let account1 = AccountInfo {
488            code: Some(bytecode),
489            ..AccountInfo::default()
490        };
491
492        let account2 = AccountInfo::default();
493
494        assert_eq!(account1, account2, "Accounts should be equal ignoring code");
495
496        assert_eq!(
497            account1.cmp(&account2),
498            Ordering::Equal,
499            "Ordering should be equal after ignoring code in Ord"
500        );
501
502        #[expect(clippy::mutable_key_type)] // Not observable
503        let mut set = BTreeSet::new();
504        assert!(set.insert(account1.clone()), "Inserted account1");
505        assert!(
506            !set.insert(account2.clone()),
507            "account2 not inserted (treated as duplicate)"
508        );
509
510        assert_eq!(set.len(), 1, "Set should have only one unique account");
511        assert!(set.contains(&account1), "Set contains account1");
512        assert!(
513            set.contains(&account2),
514            "Set contains account2 (since equal)"
515        );
516
517        let mut accounts = [account2, account1];
518        accounts.sort();
519        assert_eq!(accounts[0], accounts[1], "Sorted vec treats them as equal");
520    }
521
522    #[test]
523    fn is_default() {
524        assert!(AccountInfo::default().is_default())
525    }
526
527    #[test]
528    #[cfg(feature = "serde")]
529    fn is_default_after_serde() {
530        let info = AccountInfo::default();
531        let json = serde_json::to_string(&info).unwrap();
532        let deser: AccountInfo = serde_json::from_str(&json).unwrap();
533        assert!(deser.is_default());
534    }
535
536    #[test]
537    #[cfg(feature = "account-ext")]
538    fn extension_participates_in_account_identity() {
539        let base = AccountInfo::default();
540        let extended = base
541            .clone()
542            .with_extension(AccountExtension::copy_from_slice(b"extension"));
543
544        assert_ne!(base, extended);
545        assert_ne!(base.cmp(&extended), Ordering::Equal);
546        assert!(base.is_empty());
547        assert!(!extended.is_empty());
548    }
549
550    #[test]
551    #[cfg(feature = "serde")]
552    #[cfg(feature = "account-ext")]
553    fn missing_extension_decodes_as_empty() {
554        let mut json = serde_json::to_value(AccountInfo::default()).unwrap();
555        json.as_object_mut().unwrap().remove("extension");
556        let decoded: AccountInfo = serde_json::from_value(json).unwrap();
557        assert!(decoded.extension.is_empty());
558    }
559}