Skip to main content

anvil_core/eth/
block.rs

1use super::transaction::TransactionInfo;
2use alloy_consensus::{
3    BlockBody, EMPTY_OMMER_ROOT_HASH, Header, proofs::calculate_transaction_root,
4};
5use alloy_eips::eip2718::Encodable2718;
6use alloy_network::Network;
7use foundry_primitives::{FoundryNetwork, FoundryTxEnvelope};
8use std::fmt::Debug;
9
10use crate::eth::transaction::MaybeImpersonatedTransaction;
11
12/// Type alias for Ethereum Block with Anvil's transaction type, generic over the transaction
13/// envelope with a default of [`FoundryTxEnvelope`].
14pub type Block<T = FoundryTxEnvelope> = alloy_consensus::Block<MaybeImpersonatedTransaction<T>>;
15
16/// Anvil's concrete block info type.
17pub type BlockInfo = TypedBlockInfo<FoundryNetwork>;
18
19/// Container type that gathers all block data, generic over a [`Network`].
20#[derive(Clone, Debug)]
21pub struct TypedBlockInfo<N: Network> {
22    pub block: alloy_consensus::Block<MaybeImpersonatedTransaction<N::TxEnvelope>>,
23    pub transactions: Vec<TransactionInfo>,
24    pub receipts: Vec<N::ReceiptEnvelope>,
25}
26
27/// Helper function to create a new block with Header and Anvil transactions, generic over the
28/// transaction envelope with a default of [`FoundryTxEnvelope`].
29///
30/// Note: if the `impersonate-tx` feature is enabled this will also accept
31/// `MaybeImpersonatedTransaction`.
32pub fn create_block<T, Tx>(
33    mut header: Header,
34    transactions: impl IntoIterator<Item = T>,
35) -> Block<Tx>
36where
37    Tx: Encodable2718,
38    T: Into<MaybeImpersonatedTransaction<Tx>>,
39{
40    let transactions: Vec<_> = transactions.into_iter().map(Into::into).collect();
41    let transactions_root = calculate_transaction_root(&transactions);
42
43    header.transactions_root = transactions_root;
44    header.ommers_hash = EMPTY_OMMER_ROOT_HASH;
45
46    let body = BlockBody { transactions, ommers: Vec::new(), withdrawals: None };
47    Block::new(header, body)
48}
49
50/// Generic helper function to create a block with any transaction type that supports encoding.
51pub fn create_typed_block<T>(
52    mut header: Header,
53    transactions: impl IntoIterator<Item = T>,
54) -> alloy_consensus::Block<T>
55where
56    T: Encodable2718,
57{
58    let transactions: Vec<_> = transactions.into_iter().collect();
59    let transactions_root = calculate_transaction_root(&transactions);
60
61    header.transactions_root = transactions_root;
62    header.ommers_hash = EMPTY_OMMER_ROOT_HASH;
63
64    let body = BlockBody { transactions, ommers: Vec::new(), withdrawals: None };
65    alloy_consensus::Block::new(header, body)
66}
67
68#[cfg(test)]
69mod tests {
70    use alloy_primitives::{
71        Address, B64, B256, Bloom, U256, b256,
72        hex::{self, FromHex},
73    };
74    use alloy_rlp::Decodable;
75
76    use super::*;
77    use std::str::FromStr;
78
79    #[test]
80    fn header_rlp_roundtrip() {
81        let mut header = Header {
82            parent_hash: Default::default(),
83            ommers_hash: Default::default(),
84            beneficiary: Default::default(),
85            state_root: Default::default(),
86            transactions_root: Default::default(),
87            receipts_root: Default::default(),
88            logs_bloom: Default::default(),
89            difficulty: Default::default(),
90            number: 124u64,
91            gas_limit: Default::default(),
92            gas_used: 1337u64,
93            timestamp: 0,
94            extra_data: Default::default(),
95            mix_hash: Default::default(),
96            nonce: B64::with_last_byte(99),
97            withdrawals_root: Default::default(),
98            blob_gas_used: Default::default(),
99            excess_blob_gas: Default::default(),
100            parent_beacon_block_root: Default::default(),
101            base_fee_per_gas: None,
102            requests_hash: None,
103        };
104
105        let encoded = alloy_rlp::encode(&header);
106        let decoded: Header = Header::decode(&mut encoded.as_ref()).unwrap();
107        assert_eq!(header, decoded);
108
109        header.base_fee_per_gas = Some(12345u64);
110
111        let encoded = alloy_rlp::encode(&header);
112        let decoded: Header = Header::decode(&mut encoded.as_ref()).unwrap();
113        assert_eq!(header, decoded);
114    }
115
116    #[test]
117    fn test_encode_block_header() {
118        use alloy_rlp::Encodable;
119
120        let expected = hex::decode("f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000").unwrap();
121        let mut data = vec![];
122        let header = Header {
123            parent_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
124            ommers_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
125            beneficiary: Address::from_str("0000000000000000000000000000000000000000").unwrap(),
126            state_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
127            transactions_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
128            receipts_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
129            logs_bloom: Bloom::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(),
130            difficulty: U256::from(2222),
131            number: 0xd05u64,
132            gas_limit: 0x115cu64,
133            gas_used: 0x15b3u64,
134            timestamp: 0x1a0au64,
135            extra_data: hex::decode("7788").unwrap().into(),
136            mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
137            withdrawals_root: None,
138            blob_gas_used: None,
139            excess_blob_gas: None,
140            parent_beacon_block_root: None,
141            nonce: B64::ZERO,
142            base_fee_per_gas: None,
143            requests_hash: None,
144        };
145
146        header.encode(&mut data);
147        assert_eq!(hex::encode(&data), hex::encode(expected));
148        assert_eq!(header.length(), data.len());
149    }
150
151    #[test]
152    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
153    fn test_decode_block_header() {
154        let data = hex::decode("f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000").unwrap();
155        let expected = Header {
156            parent_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
157            ommers_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
158            beneficiary: Address::from_str("0000000000000000000000000000000000000000").unwrap(),
159            state_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
160            transactions_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
161            receipts_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
162            logs_bloom: <[u8; 256]>::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap().into(),
163            difficulty: U256::from(2222),
164            number: 0xd05u64,
165            gas_limit: 0x115cu64,
166            gas_used: 0x15b3u64,
167            timestamp: 0x1a0au64,
168            extra_data: hex::decode("7788").unwrap().into(),
169            mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
170            nonce: B64::ZERO,
171            withdrawals_root: None,
172            blob_gas_used: None,
173            excess_blob_gas: None,
174            parent_beacon_block_root: None,
175            base_fee_per_gas: None,
176            requests_hash: None,
177        };
178        let header = Header::decode(&mut data.as_slice()).unwrap();
179        assert_eq!(header, expected);
180    }
181
182    #[test]
183    // Test vector from: https://github.com/ethereum/tests/blob/f47bbef4da376a49c8fc3166f09ab8a6d182f765/BlockchainTests/ValidBlocks/bcEIP1559/baseFee.json#L15-L36
184    fn test_eip1559_block_header_hash() {
185        let expected_hash =
186            b256!("0x6a251c7c3c5dca7b42407a3752ff48f3bbca1fab7f9868371d9918daf1988d1f");
187        let header = Header {
188            parent_hash: B256::from_str("e0a94a7a3c9617401586b1a27025d2d9671332d22d540e0af72b069170380f2a").unwrap(),
189            ommers_hash: B256::from_str("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347").unwrap(),
190            beneficiary: Address::from_str("ba5e000000000000000000000000000000000000").unwrap(),
191            state_root: B256::from_str("ec3c94b18b8a1cff7d60f8d258ec723312932928626b4c9355eb4ab3568ec7f7").unwrap(),
192            transactions_root: B256::from_str("50f738580ed699f0469702c7ccc63ed2e51bc034be9479b7bff4e68dee84accf").unwrap(),
193            receipts_root: B256::from_str("29b0562f7140574dd0d50dee8a271b22e1a0a7b78fca58f7c60370d8317ba2a9").unwrap(),
194            logs_bloom: Bloom::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(),
195            difficulty: U256::from(0x020000),
196            number: 1u64,
197            gas_limit: U256::from(0x016345785d8a0000u128).to::<u64>(),
198            gas_used: U256::from(0x015534).to::<u64>(),
199            timestamp: 0x079e,
200            extra_data: hex::decode("42").unwrap().into(),
201            mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
202            nonce: B64::ZERO,
203            base_fee_per_gas: Some(875),
204            withdrawals_root: None,
205            blob_gas_used: None,
206            excess_blob_gas: None,
207            parent_beacon_block_root: None,
208            requests_hash: None,
209        };
210        assert_eq!(header.hash_slow(), expected_hash);
211    }
212
213    #[test]
214    // Test vector from network
215    fn block_network_roundtrip() {
216        use alloy_rlp::Encodable;
217
218        let data = hex::decode("f9034df90348a0fbdbd8d2d0ac5f14bd5fa90e547fe6f1d15019c724f8e7b60972d381cd5d9cf8a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794c9577e7945db22e38fc060909f2278c7746b0f9ba05017cfa3b0247e35197215ae8d610265ffebc8edca8ea66d6567eb0adecda867a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018355bb7b871fffffffffffff808462bd0e1ab9014bf90148a00000000000000000000000000000000000000000000000000000000000000000f85494319fa8f1bc4e53410e92d10d918659b16540e60a945a573efb304d04c1224cd012313e827eca5dce5d94a9c831c5a268031176ebf5f3de5051e8cba0dbfe94c9577e7945db22e38fc060909f2278c7746b0f9b808400000000f8c9b841a6946f2d16f68338cbcbd8b117374ab421128ce422467088456bceba9d70c34106128e6d4564659cf6776c08a4186063c0a05f7cffd695c10cf26a6f301b67f800b8412b782100c18c35102dc0a37ece1a152544f04ad7dc1868d18a9570f744ace60870f822f53d35e89a2ea9709ccbf1f4a25ee5003944faa845d02dde0a41d5704601b841d53caebd6c8a82456e85c2806a9e08381f959a31fb94a77e58f00e38ad97b2e0355b8519ab2122662cbe022f2a4ef7ff16adc0b2d5dcd123181ec79705116db300a063746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365880000000000000000c0c0").unwrap();
219
220        let block = <Block>::decode(&mut data.as_slice()).unwrap();
221
222        // encode and check that it matches the original data
223        let mut encoded = Vec::new();
224        block.encode(&mut encoded);
225        assert_eq!(data, encoded);
226
227        // check that length of encoding is the same as the output of `length`
228        assert_eq!(block.length(), encoded.len());
229    }
230}