reth_evm/
state_change.rs

1//! State changes that are not related to transactions.
2
3use alloy_consensus::BlockHeader;
4use alloy_eips::eip4895::Withdrawal;
5use alloy_primitives::{map::HashMap, Address, U256};
6use reth_chainspec::EthereumHardforks;
7use reth_consensus_common::calc;
8use reth_primitives_traits::BlockBody;
9
10/// Collect all balance changes at the end of the block.
11///
12/// Balance changes might include the block reward, uncle rewards, withdrawals, or irregular
13/// state changes (DAO fork).
14#[inline]
15pub fn post_block_balance_increments<ChainSpec, Block>(
16    chain_spec: &ChainSpec,
17    block: &Block,
18    total_difficulty: U256,
19) -> HashMap<Address, u128>
20where
21    ChainSpec: EthereumHardforks,
22    Block: reth_primitives_traits::Block,
23{
24    let mut balance_increments = HashMap::default();
25
26    // Add block rewards if they are enabled.
27    if let Some(base_block_reward) = calc::base_block_reward(
28        chain_spec,
29        block.header().number(),
30        block.header().difficulty(),
31        total_difficulty,
32    ) {
33        // Ommer rewards
34        if let Some(ommers) = block.body().ommers() {
35            for ommer in ommers {
36                *balance_increments.entry(ommer.beneficiary()).or_default() +=
37                    calc::ommer_reward(base_block_reward, block.header().number(), ommer.number());
38            }
39        }
40
41        // Full block reward
42        *balance_increments.entry(block.header().beneficiary()).or_default() += calc::block_reward(
43            base_block_reward,
44            block.body().ommers().map(|s| s.len()).unwrap_or(0),
45        );
46    }
47
48    // process withdrawals
49    insert_post_block_withdrawals_balance_increments(
50        chain_spec,
51        block.header().timestamp(),
52        block.body().withdrawals().as_ref().map(|w| w.as_slice()),
53        &mut balance_increments,
54    );
55
56    balance_increments
57}
58
59/// Returns a map of addresses to their balance increments if the Shanghai hardfork is active at the
60/// given timestamp.
61///
62/// Zero-valued withdrawals are filtered out.
63#[inline]
64pub fn post_block_withdrawals_balance_increments<ChainSpec: EthereumHardforks>(
65    chain_spec: &ChainSpec,
66    block_timestamp: u64,
67    withdrawals: &[Withdrawal],
68) -> HashMap<Address, u128> {
69    let mut balance_increments =
70        HashMap::with_capacity_and_hasher(withdrawals.len(), Default::default());
71    insert_post_block_withdrawals_balance_increments(
72        chain_spec,
73        block_timestamp,
74        Some(withdrawals),
75        &mut balance_increments,
76    );
77    balance_increments
78}
79
80/// Applies all withdrawal balance increments if shanghai is active at the given timestamp to the
81/// given `balance_increments` map.
82///
83/// Zero-valued withdrawals are filtered out.
84#[inline]
85pub fn insert_post_block_withdrawals_balance_increments<ChainSpec: EthereumHardforks>(
86    chain_spec: &ChainSpec,
87    block_timestamp: u64,
88    withdrawals: Option<&[Withdrawal]>,
89    balance_increments: &mut HashMap<Address, u128>,
90) {
91    // Process withdrawals
92    if chain_spec.is_shanghai_active_at_timestamp(block_timestamp) {
93        if let Some(withdrawals) = withdrawals {
94            for withdrawal in withdrawals {
95                if withdrawal.amount > 0 {
96                    *balance_increments.entry(withdrawal.address).or_default() +=
97                        withdrawal.amount_wei().to::<u128>();
98                }
99            }
100        }
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use alloy_consensus::constants::GWEI_TO_WEI;
108    use reth_chainspec::ChainSpec;
109    use reth_ethereum_forks::{ChainHardforks, EthereumHardfork, ForkCondition};
110
111    /// Tests that the function correctly inserts balance increments when the Shanghai hardfork is
112    /// active and there are withdrawals.
113    #[test]
114    fn test_insert_post_block_withdrawals_balance_increments_shanghai_active_with_withdrawals() {
115        // Arrange
116        // Create a ChainSpec with the Shanghai hardfork active at timestamp 100
117        let chain_spec = ChainSpec {
118            hardforks: ChainHardforks::new(vec![(
119                Box::new(EthereumHardfork::Shanghai),
120                ForkCondition::Timestamp(100),
121            )]),
122            ..Default::default()
123        };
124
125        // Define the block timestamp and withdrawals
126        let block_timestamp = 1000;
127        let withdrawals = vec![
128            Withdrawal {
129                address: Address::from([1; 20]),
130                amount: 1000,
131                index: 45,
132                validator_index: 12,
133            },
134            Withdrawal {
135                address: Address::from([2; 20]),
136                amount: 500,
137                index: 412,
138                validator_index: 123,
139            },
140        ];
141
142        // Create an empty HashMap to hold the balance increments
143        let mut balance_increments = HashMap::default();
144
145        // Act
146        // Call the function with the prepared inputs
147        insert_post_block_withdrawals_balance_increments(
148            &chain_spec,
149            block_timestamp,
150            Some(&withdrawals),
151            &mut balance_increments,
152        );
153
154        // Assert
155        // Verify that the balance increments map has the correct number of entries
156        assert_eq!(balance_increments.len(), 2);
157        // Verify that the balance increments map contains the correct values for each address
158        assert_eq!(
159            *balance_increments.get(&Address::from([1; 20])).unwrap(),
160            (1000 * GWEI_TO_WEI).into()
161        );
162        assert_eq!(
163            *balance_increments.get(&Address::from([2; 20])).unwrap(),
164            (500 * GWEI_TO_WEI).into()
165        );
166    }
167
168    /// Tests that the function correctly handles the case when Shanghai is active but there are no
169    /// withdrawals.
170    #[test]
171    fn test_insert_post_block_withdrawals_balance_increments_shanghai_active_no_withdrawals() {
172        // Arrange
173        // Create a ChainSpec with the Shanghai hardfork active
174        let chain_spec = ChainSpec {
175            hardforks: ChainHardforks::new(vec![(
176                Box::new(EthereumHardfork::Shanghai),
177                ForkCondition::Timestamp(100),
178            )]),
179            ..Default::default()
180        };
181
182        // Define the block timestamp and an empty list of withdrawals
183        let block_timestamp = 1000;
184        let withdrawals = Vec::<Withdrawal>::new();
185
186        // Create an empty HashMap to hold the balance increments
187        let mut balance_increments = HashMap::default();
188
189        // Act
190        // Call the function with the prepared inputs
191        insert_post_block_withdrawals_balance_increments(
192            &chain_spec,
193            block_timestamp,
194            Some(&withdrawals),
195            &mut balance_increments,
196        );
197
198        // Assert
199        // Verify that the balance increments map is empty
200        assert!(balance_increments.is_empty());
201    }
202
203    /// Tests that the function correctly handles the case when Shanghai is not active even if there
204    /// are withdrawals.
205    #[test]
206    fn test_insert_post_block_withdrawals_balance_increments_shanghai_not_active_with_withdrawals()
207    {
208        // Arrange
209        // Create a ChainSpec without the Shanghai hardfork active
210        let chain_spec = ChainSpec::default(); // Mock chain spec with Shanghai not active
211
212        // Define the block timestamp and withdrawals
213        let block_timestamp = 1000;
214        let withdrawals = vec![
215            Withdrawal {
216                address: Address::from([1; 20]),
217                amount: 1000,
218                index: 45,
219                validator_index: 12,
220            },
221            Withdrawal {
222                address: Address::from([2; 20]),
223                amount: 500,
224                index: 412,
225                validator_index: 123,
226            },
227        ];
228
229        // Create an empty HashMap to hold the balance increments
230        let mut balance_increments = HashMap::default();
231
232        // Act
233        // Call the function with the prepared inputs
234        insert_post_block_withdrawals_balance_increments(
235            &chain_spec,
236            block_timestamp,
237            Some(&withdrawals),
238            &mut balance_increments,
239        );
240
241        // Assert
242        // Verify that the balance increments map is empty
243        assert!(balance_increments.is_empty());
244    }
245
246    /// Tests that the function correctly handles the case when Shanghai is active but all
247    /// withdrawals have zero amounts.
248    #[test]
249    fn test_insert_post_block_withdrawals_balance_increments_shanghai_active_with_zero_withdrawals()
250    {
251        // Arrange
252        // Create a ChainSpec with the Shanghai hardfork active
253        let chain_spec = ChainSpec {
254            hardforks: ChainHardforks::new(vec![(
255                Box::new(EthereumHardfork::Shanghai),
256                ForkCondition::Timestamp(100),
257            )]),
258            ..Default::default()
259        };
260
261        // Define the block timestamp and withdrawals with zero amounts
262        let block_timestamp = 1000;
263        let withdrawals = vec![
264            Withdrawal {
265                address: Address::from([1; 20]),
266                amount: 0, // Zero withdrawal amount
267                index: 45,
268                validator_index: 12,
269            },
270            Withdrawal {
271                address: Address::from([2; 20]),
272                amount: 0, // Zero withdrawal amount
273                index: 412,
274                validator_index: 123,
275            },
276        ];
277
278        // Create an empty HashMap to hold the balance increments
279        let mut balance_increments = HashMap::default();
280
281        // Act
282        // Call the function with the prepared inputs
283        insert_post_block_withdrawals_balance_increments(
284            &chain_spec,
285            block_timestamp,
286            Some(&withdrawals),
287            &mut balance_increments,
288        );
289
290        // Assert
291        // Verify that the balance increments map is empty
292        assert!(balance_increments.is_empty());
293    }
294
295    /// Tests that the function correctly handles the case when Shanghai is active but there are no
296    /// withdrawals provided.
297    #[test]
298    fn test_insert_post_block_withdrawals_balance_increments_shanghai_active_with_empty_withdrawals(
299    ) {
300        // Arrange
301        // Create a ChainSpec with the Shanghai hardfork active
302        let chain_spec = ChainSpec {
303            hardforks: ChainHardforks::new(vec![(
304                Box::new(EthereumHardfork::Shanghai),
305                ForkCondition::Timestamp(100),
306            )]),
307            ..Default::default()
308        };
309
310        // Define the block timestamp and no withdrawals
311        let block_timestamp = 1000;
312        let withdrawals = None; // No withdrawals provided
313
314        // Create an empty HashMap to hold the balance increments
315        let mut balance_increments = HashMap::default();
316
317        // Act
318        // Call the function with the prepared inputs
319        insert_post_block_withdrawals_balance_increments(
320            &chain_spec,
321            block_timestamp,
322            withdrawals,
323            &mut balance_increments,
324        );
325
326        // Assert
327        // Verify that the balance increments map is empty
328        assert!(balance_increments.is_empty());
329    }
330}