alloy_evm/block/
state_changes.rs

1//! State changes that are not related to transactions.
2
3use super::{calc, BlockExecutionError};
4use alloy_consensus::BlockHeader;
5use alloy_eips::eip4895::{Withdrawal, Withdrawals};
6use alloy_hardforks::EthereumHardforks;
7use alloy_primitives::{map::HashMap, Address};
8use revm::{
9    context::Block,
10    database::State,
11    state::{Account, AccountStatus, EvmState},
12    Database,
13};
14
15/// Collect all balance changes at the end of the block.
16///
17/// Balance changes might include the block reward, uncle rewards, withdrawals, or irregular
18/// state changes (DAO fork).
19#[inline]
20pub fn post_block_balance_increments<H>(
21    spec: impl EthereumHardforks,
22    block_env: impl Block,
23    ommers: &[H],
24    withdrawals: Option<&Withdrawals>,
25) -> HashMap<Address, u128>
26where
27    H: BlockHeader,
28{
29    let mut balance_increments = HashMap::with_capacity_and_hasher(
30        withdrawals.map_or(ommers.len(), |w| w.len()),
31        Default::default(),
32    );
33
34    // Add block rewards if they are enabled.
35    if let Some(base_block_reward) =
36        calc::base_block_reward(&spec, block_env.number().saturating_to())
37    {
38        // Ommer rewards
39        for ommer in ommers {
40            *balance_increments.entry(ommer.beneficiary()).or_default() += calc::ommer_reward(
41                base_block_reward,
42                block_env.number().saturating_to(),
43                ommer.number(),
44            );
45        }
46
47        // Full block reward
48        *balance_increments.entry(block_env.beneficiary()).or_default() +=
49            calc::block_reward(base_block_reward, ommers.len());
50    }
51
52    // process withdrawals
53    insert_post_block_withdrawals_balance_increments(
54        spec,
55        block_env.timestamp().saturating_to(),
56        withdrawals.map(|w| w.as_slice()),
57        &mut balance_increments,
58    );
59
60    balance_increments
61}
62
63/// Returns a map of addresses to their balance increments if the Shanghai hardfork is active at the
64/// given timestamp.
65///
66/// Zero-valued withdrawals are filtered out.
67#[inline]
68pub fn post_block_withdrawals_balance_increments(
69    spec: impl EthereumHardforks,
70    block_timestamp: u64,
71    withdrawals: &[Withdrawal],
72) -> HashMap<Address, u128> {
73    let mut balance_increments =
74        HashMap::with_capacity_and_hasher(withdrawals.len(), Default::default());
75    insert_post_block_withdrawals_balance_increments(
76        spec,
77        block_timestamp,
78        Some(withdrawals),
79        &mut balance_increments,
80    );
81    balance_increments
82}
83
84/// Applies all withdrawal balance increments if shanghai is active at the given timestamp to the
85/// given `balance_increments` map.
86///
87/// Zero-valued withdrawals are filtered out.
88#[inline]
89pub fn insert_post_block_withdrawals_balance_increments(
90    spec: impl EthereumHardforks,
91    block_timestamp: u64,
92    withdrawals: Option<&[Withdrawal]>,
93    balance_increments: &mut HashMap<Address, u128>,
94) {
95    // Process withdrawals
96    if spec.is_shanghai_active_at_timestamp(block_timestamp) {
97        if let Some(withdrawals) = withdrawals {
98            for withdrawal in withdrawals {
99                if withdrawal.amount > 0 {
100                    *balance_increments.entry(withdrawal.address).or_default() +=
101                        withdrawal.amount_wei().to::<u128>();
102                }
103            }
104        }
105    }
106}
107
108/// Creates an `EvmState` from a map of balance increments and the current state
109/// to load accounts from. No balance increment is done in the function.
110/// Zero balance increments are ignored and won't create state entries.
111pub fn balance_increment_state<DB>(
112    balance_increments: &HashMap<Address, u128>,
113    state: &mut State<DB>,
114) -> Result<EvmState, BlockExecutionError>
115where
116    DB: Database,
117{
118    let mut load_account = |address: &Address| -> Result<(Address, Account), BlockExecutionError> {
119        let cache_account = state.load_cache_account(*address).map_err(|_| {
120            BlockExecutionError::msg("could not load account for balance increment")
121        })?;
122
123        let account = cache_account.account.as_ref().ok_or_else(|| {
124            BlockExecutionError::msg("could not load account for balance increment")
125        })?;
126
127        Ok((
128            *address,
129            Account {
130                info: account.info.clone(),
131                storage: Default::default(),
132                status: AccountStatus::Touched,
133                transaction_id: 0,
134            },
135        ))
136    };
137
138    balance_increments
139        .iter()
140        .filter(|(_, &balance)| balance != 0)
141        .map(|(addr, _)| load_account(addr))
142        .collect::<Result<EvmState, _>>()
143}