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#[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 #[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 #[inline]
29 pub const fn get(self) -> usize {
30 self.0.get() as usize
31 }
32}
33
34#[derive(Clone, Debug, Eq)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42pub struct AccountInfo {
43 pub balance: U256,
45 pub nonce: u64,
47 pub code_hash: B256,
49 #[cfg_attr(feature = "serde", serde(skip))]
54 pub account_id: Option<AccountId>,
55 pub code: Option<Bytecode>,
62 #[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 #[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 #[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 #[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 #[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 #[inline]
190 pub const fn with_balance(mut self, balance: U256) -> Self {
191 self.balance = balance;
192 self
193 }
194
195 #[inline]
197 pub const fn with_nonce(mut self, nonce: u64) -> Self {
198 self.nonce = nonce;
199 self
200 }
201
202 #[inline]
204 pub const fn set_balance(&mut self, balance: U256) -> &mut Self {
205 self.balance = balance;
206 self
207 }
208
209 #[inline]
211 pub const fn set_nonce(&mut self, nonce: u64) -> &mut Self {
212 self.nonce = nonce;
213 self
214 }
215
216 #[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 #[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 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 #[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 #[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 #[inline]
304 pub fn without_code(mut self) -> Self {
305 self.take_bytecode();
306 self
307 }
308
309 #[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 #[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 #[inline]
331 pub fn exists(&self) -> bool {
332 !self.is_empty()
333 }
334
335 #[inline]
337 pub fn has_no_code_and_nonce(&self) -> bool {
338 self.is_empty_code_hash() && self.nonce == 0
339 }
340
341 #[inline]
345 pub const fn code_hash(&self) -> B256 {
346 self.code_hash
347 }
348
349 #[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 #[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 #[inline]
366 pub fn is_empty_code_hash(&self) -> bool {
367 self.code_hash == KECCAK_EMPTY
368 }
369
370 #[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 #[inline]
380 pub const fn take_bytecode(&mut self) -> Option<Bytecode> {
381 self.code.take()
382 }
383
384 #[inline]
387 pub fn from_balance(balance: U256) -> Self {
388 AccountInfo {
389 balance,
390 ..Default::default()
391 }
392 }
393
394 #[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)] 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}