#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
extern crate alloc as std;
mod account_info;
pub mod bal;
mod types;
pub use bytecode;
pub use account_info::AccountInfo;
pub use bytecode::Bytecode;
pub use primitives;
pub use types::{EvmState, EvmStorage, TransientStorage};
use bitflags::bitflags;
use primitives::{hardfork::SpecId, HashMap, OnceLock, StorageKey, StorageValue, U256};
use std::boxed::Box;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Account {
pub info: AccountInfo,
pub original_info: Box<AccountInfo>,
pub transaction_id: usize,
pub storage: EvmStorage,
pub status: AccountStatus,
}
impl Account {
pub fn new_not_existing(transaction_id: usize) -> Self {
static DEFAULT: OnceLock<Account> = OnceLock::new();
let mut account = DEFAULT
.get_or_init(|| Self {
info: AccountInfo::default(),
storage: HashMap::default(),
transaction_id: 0,
status: AccountStatus::LoadedAsNotExisting,
original_info: Box::new(AccountInfo::default()),
})
.clone();
account.transaction_id = transaction_id;
account
}
#[inline]
pub fn caller_initial_modification(&mut self, new_balance: U256, is_call: bool) -> U256 {
self.mark_touch();
if is_call {
self.info.nonce = self.info.nonce.saturating_add(1);
}
core::mem::replace(&mut self.info.balance, new_balance)
}
#[inline]
pub fn state_clear_aware_is_empty(&self, spec: SpecId) -> bool {
if SpecId::is_enabled_in(spec, SpecId::SPURIOUS_DRAGON) {
self.is_empty()
} else {
self.is_loaded_as_not_existing_not_touched()
}
}
#[inline]
pub fn mark_selfdestruct(&mut self) {
self.status |= AccountStatus::SelfDestructed;
}
#[inline]
pub fn unmark_selfdestruct(&mut self) {
self.status -= AccountStatus::SelfDestructed;
}
#[inline]
pub fn is_selfdestructed(&self) -> bool {
self.status.contains(AccountStatus::SelfDestructed)
}
#[inline]
pub fn mark_touch(&mut self) {
self.status |= AccountStatus::Touched;
}
#[inline]
pub fn unmark_touch(&mut self) {
self.status -= AccountStatus::Touched;
}
#[inline]
pub fn is_touched(&self) -> bool {
self.status.contains(AccountStatus::Touched)
}
#[inline]
pub fn mark_created(&mut self) {
self.status |= AccountStatus::Created;
}
#[inline]
pub fn unmark_created(&mut self) {
self.status -= AccountStatus::Created;
}
#[inline]
pub fn mark_cold(&mut self) {
self.status |= AccountStatus::Cold;
}
#[inline]
pub fn is_cold_transaction_id(&self, transaction_id: usize) -> bool {
self.transaction_id != transaction_id || self.status.contains(AccountStatus::Cold)
}
#[inline]
pub fn mark_warm_with_transaction_id(&mut self, transaction_id: usize) -> bool {
let is_cold = self.is_cold_transaction_id(transaction_id);
self.status -= AccountStatus::Cold;
self.transaction_id = transaction_id;
is_cold
}
#[inline]
pub fn is_created_locally(&self) -> bool {
self.status.contains(AccountStatus::CreatedLocal)
}
#[inline]
pub fn is_selfdestructed_locally(&self) -> bool {
self.status.contains(AccountStatus::SelfDestructedLocal)
}
#[inline]
pub fn selfdestruct(&mut self) {
self.storage.clear();
self.info = AccountInfo::default();
}
#[inline]
pub fn mark_created_locally(&mut self) -> bool {
self.mark_local_and_global(AccountStatus::CreatedLocal, AccountStatus::Created)
}
#[inline]
pub fn unmark_created_locally(&mut self) {
self.status -= AccountStatus::CreatedLocal;
}
#[inline]
pub fn mark_selfdestructed_locally(&mut self) -> bool {
self.mark_local_and_global(
AccountStatus::SelfDestructedLocal,
AccountStatus::SelfDestructed,
)
}
#[inline]
fn mark_local_and_global(
&mut self,
local_flag: AccountStatus,
global_flag: AccountStatus,
) -> bool {
self.status |= local_flag;
let is_global_first_time = !self.status.contains(global_flag);
self.status |= global_flag;
is_global_first_time
}
#[inline]
pub fn unmark_selfdestructed_locally(&mut self) {
self.status -= AccountStatus::SelfDestructedLocal;
}
pub fn is_loaded_as_not_existing(&self) -> bool {
self.status.contains(AccountStatus::LoadedAsNotExisting)
}
pub fn is_loaded_as_not_existing_not_touched(&self) -> bool {
self.is_loaded_as_not_existing() && !self.is_touched()
}
pub fn is_created(&self) -> bool {
self.status.contains(AccountStatus::Created)
}
pub fn is_empty(&self) -> bool {
self.info.is_empty()
}
pub fn changed_storage_slots(&self) -> impl Iterator<Item = (&StorageKey, &EvmStorageSlot)> {
self.storage.iter().filter(|(_, slot)| slot.is_changed())
}
pub fn with_info(mut self, info: AccountInfo) -> Self {
self.info = info;
self
}
pub fn with_storage<I>(mut self, storage_iter: I) -> Self
where
I: Iterator<Item = (StorageKey, EvmStorageSlot)>,
{
for (key, slot) in storage_iter {
self.storage.insert(key, slot);
}
self
}
pub fn with_selfdestruct_mark(mut self) -> Self {
self.mark_selfdestruct();
self
}
pub fn with_touched_mark(mut self) -> Self {
self.mark_touch();
self
}
pub fn with_created_mark(mut self) -> Self {
self.mark_created();
self
}
pub fn with_cold_mark(mut self) -> Self {
self.mark_cold();
self
}
pub fn with_warm_mark(mut self, transaction_id: usize) -> (Self, bool) {
let was_cold = self.mark_warm_with_transaction_id(transaction_id);
(self, was_cold)
}
pub fn with_warm(mut self, transaction_id: usize) -> Self {
self.mark_warm_with_transaction_id(transaction_id);
self
}
}
impl From<AccountInfo> for Account {
fn from(info: AccountInfo) -> Self {
let original_info = Box::new(info.clone());
Self {
info,
storage: HashMap::default(),
transaction_id: 0,
status: AccountStatus::empty(),
original_info,
}
}
}
#[cfg(feature = "serde")]
mod serde_impl {
use super::*;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct AccountSerde {
info: AccountInfo,
original_info: Option<AccountInfo>,
storage: EvmStorage,
transaction_id: usize,
status: AccountStatus,
}
impl<'de> Deserialize<'de> for super::Account {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let AccountSerde {
info,
original_info,
storage,
transaction_id,
status,
} = Deserialize::deserialize(deserializer)?;
let original_info = original_info.unwrap_or_else(|| info.clone());
Ok(Account {
info,
original_info: Box::new(original_info),
storage,
transaction_id,
status,
})
}
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct AccountStatus: u8 {
const Created = 0b00000001;
const CreatedLocal = 0b10000000;
const SelfDestructed = 0b00000010;
const SelfDestructedLocal = 0b01000000;
const Touched = 0b00000100;
const LoadedAsNotExisting = 0b00001000;
const Cold = 0b00010000;
}
}
impl AccountStatus {
#[inline]
pub fn is_touched(&self) -> bool {
self.contains(AccountStatus::Touched)
}
}
impl Default for AccountStatus {
fn default() -> Self {
AccountStatus::empty()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct EvmStorageSlot {
pub original_value: StorageValue,
pub present_value: StorageValue,
pub transaction_id: usize,
pub is_cold: bool,
}
impl EvmStorageSlot {
pub fn new(original: StorageValue, transaction_id: usize) -> Self {
Self {
original_value: original,
present_value: original,
transaction_id,
is_cold: false,
}
}
pub fn new_changed(
original_value: StorageValue,
present_value: StorageValue,
transaction_id: usize,
) -> Self {
Self {
original_value,
present_value,
transaction_id,
is_cold: false,
}
}
pub fn is_changed(&self) -> bool {
self.original_value != self.present_value
}
#[inline]
pub fn original_value(&self) -> StorageValue {
self.original_value
}
#[inline]
pub fn present_value(&self) -> StorageValue {
self.present_value
}
#[inline]
pub fn mark_cold(&mut self) {
self.is_cold = true;
}
#[inline]
pub fn is_cold_transaction_id(&self, transaction_id: usize) -> bool {
self.transaction_id != transaction_id || self.is_cold
}
#[inline]
pub fn mark_warm_with_transaction_id(&mut self, transaction_id: usize) -> bool {
let is_cold = self.is_cold_transaction_id(transaction_id);
if is_cold {
self.original_value = self.present_value;
}
self.transaction_id = transaction_id;
self.is_cold = false;
is_cold
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::EvmStorageSlot;
use primitives::{StorageKey, KECCAK_EMPTY, U256};
#[test]
fn account_is_empty_balance() {
let mut account = Account::default();
assert!(account.is_empty());
account.info.balance = U256::from(1);
assert!(!account.is_empty());
account.info.balance = U256::ZERO;
assert!(account.is_empty());
}
#[test]
fn account_is_empty_nonce() {
let mut account = Account::default();
assert!(account.is_empty());
account.info.nonce = 1;
assert!(!account.is_empty());
account.info.nonce = 0;
assert!(account.is_empty());
}
#[test]
fn account_is_empty_code_hash() {
let mut account = Account::default();
assert!(account.is_empty());
account.info.code_hash = [1; 32].into();
assert!(!account.is_empty());
account.info.code_hash = [0; 32].into();
assert!(account.is_empty());
account.info.code_hash = KECCAK_EMPTY;
assert!(account.is_empty());
}
#[test]
fn account_state() {
let mut account = Account::default();
assert!(!account.is_touched());
assert!(!account.is_selfdestructed());
account.mark_touch();
assert!(account.is_touched());
assert!(!account.is_selfdestructed());
account.mark_selfdestruct();
assert!(account.is_touched());
assert!(account.is_selfdestructed());
account.unmark_selfdestruct();
assert!(account.is_touched());
assert!(!account.is_selfdestructed());
}
#[test]
fn account_is_cold() {
let mut account = Account::default();
assert!(!account.status.contains(crate::AccountStatus::Cold));
assert!(!account.mark_warm_with_transaction_id(0));
account.mark_cold();
assert!(account.status.contains(crate::AccountStatus::Cold));
assert!(account.mark_warm_with_transaction_id(0));
}
#[test]
fn test_account_with_info() {
let info = AccountInfo::default();
let account = Account::default().with_info(info.clone());
assert_eq!(account.info, info);
assert_eq!(account.storage, HashMap::default());
assert_eq!(account.status, AccountStatus::empty());
}
#[test]
fn test_account_with_storage() {
let mut storage = HashMap::<StorageKey, EvmStorageSlot>::default();
let key1 = StorageKey::from(1);
let key2 = StorageKey::from(2);
let slot1 = EvmStorageSlot::new(StorageValue::from(10), 0);
let slot2 = EvmStorageSlot::new(StorageValue::from(20), 0);
storage.insert(key1, slot1.clone());
storage.insert(key2, slot2.clone());
let account = Account::default().with_storage(storage.clone().into_iter());
assert_eq!(account.storage.len(), 2);
assert_eq!(account.storage.get(&key1), Some(&slot1));
assert_eq!(account.storage.get(&key2), Some(&slot2));
}
#[test]
fn test_account_with_selfdestruct_mark() {
let account = Account::default().with_selfdestruct_mark();
assert!(account.is_selfdestructed());
assert!(!account.is_touched());
assert!(!account.is_created());
}
#[test]
#[cfg(feature = "serde")]
fn test_account_serialize_deserialize() {
let account = Account::default().with_selfdestruct_mark();
let serialized = serde_json::to_string(&account).unwrap();
let deserialized: Account = serde_json::from_str(&serialized).unwrap();
assert_eq!(account, deserialized);
}
#[test]
#[cfg(feature = "serde")]
fn test_account_serialize_deserialize_without_original_info() {
let deserialize_without_original_info = r#"
{"info":{"balance":"0x0","nonce":0,"code_hash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","storage_id":null,"code":{"LegacyAnalyzed":{"bytecode":"0x00","original_len":0,"jump_table":{"order":"bitvec::order::Lsb0","head":{"width":8,"index":0},"bits":0,"data":[]}}}},"transaction_id":0,"storage":{},"status":"SelfDestructed"}"#;
let account = Account::default().with_selfdestruct_mark();
let deserialized: Account =
serde_json::from_str(deserialize_without_original_info).unwrap();
assert_eq!(account, deserialized);
}
#[test]
fn test_account_with_touched_mark() {
let account = Account::default().with_touched_mark();
assert!(!account.is_selfdestructed());
assert!(account.is_touched());
assert!(!account.is_created());
}
#[test]
fn test_account_with_created_mark() {
let account = Account::default().with_created_mark();
assert!(!account.is_selfdestructed());
assert!(!account.is_touched());
assert!(account.is_created());
}
#[test]
fn test_account_with_cold_mark() {
let account = Account::default().with_cold_mark();
assert!(account.status.contains(AccountStatus::Cold));
}
#[test]
fn test_storage_mark_warm_with_transaction_id() {
let mut slot = EvmStorageSlot::new(U256::ZERO, 0);
slot.is_cold = true;
slot.transaction_id = 0;
assert!(slot.mark_warm_with_transaction_id(1));
slot.is_cold = false;
slot.transaction_id = 0;
assert!(slot.mark_warm_with_transaction_id(1));
slot.is_cold = true;
slot.transaction_id = 1;
assert!(slot.mark_warm_with_transaction_id(1));
slot.is_cold = false;
slot.transaction_id = 1;
assert!(!slot.mark_warm_with_transaction_id(1));
}
#[test]
fn test_account_with_warm_mark() {
let cold_account = Account::default().with_cold_mark();
assert!(cold_account.status.contains(AccountStatus::Cold));
let (warm_account, was_cold) = cold_account.with_warm_mark(0);
assert!(!warm_account.status.contains(AccountStatus::Cold));
assert!(was_cold);
let (still_warm_account, was_cold) = warm_account.with_warm_mark(0);
assert!(!still_warm_account.status.contains(AccountStatus::Cold));
assert!(!was_cold);
}
#[test]
fn test_account_with_warm() {
let cold_account = Account::default().with_cold_mark();
assert!(cold_account.status.contains(AccountStatus::Cold));
let warm_account = cold_account.with_warm(0);
assert!(!warm_account.status.contains(AccountStatus::Cold));
}
#[test]
fn test_account_builder_chaining() {
let info = AccountInfo {
nonce: 5,
..AccountInfo::default()
};
let slot_key = StorageKey::from(42);
let slot_value = EvmStorageSlot::new(StorageValue::from(123), 0);
let mut storage = HashMap::<StorageKey, EvmStorageSlot>::default();
storage.insert(slot_key, slot_value.clone());
let account = Account::default()
.with_info(info.clone())
.with_storage(storage.into_iter())
.with_created_mark()
.with_touched_mark()
.with_cold_mark()
.with_warm(0);
assert_eq!(account.info, info);
assert_eq!(account.storage.get(&slot_key), Some(&slot_value));
assert!(account.is_created());
assert!(account.is_touched());
assert!(!account.status.contains(AccountStatus::Cold));
}
#[test]
fn test_account_is_cold_transaction_id() {
let mut account = Account::default();
assert!(!account.is_cold_transaction_id(0));
assert!(account.is_cold_transaction_id(1));
account.mark_cold();
assert!(account.is_cold_transaction_id(0));
assert!(account.is_cold_transaction_id(1));
}
}