reth_evm_ethereum/
eip6110.rs

1//! EIP-6110 deposit requests parsing
2use alloc::{string::ToString, vec::Vec};
3use alloy_eips::eip6110::MAINNET_DEPOSIT_CONTRACT_ADDRESS;
4use alloy_primitives::{Address, Bytes, Log};
5use alloy_sol_types::{sol, SolEvent};
6use reth_chainspec::{ChainSpec, EthChainSpec};
7use reth_evm::execute::BlockValidationError;
8use reth_primitives::Receipt;
9
10/// The size of a deposit request in bytes. While the event fields emit
11/// bytestrings, those bytestrings are fixed size. The fields are: 48-byte
12/// pubkey, 32-byte withdrawal credentials, 8-byte amount, 96-byte signature,
13/// and 8-byte index.
14const DEPOSIT_BYTES_SIZE: usize = 48 + 32 + 8 + 96 + 8;
15
16sol! {
17    #[allow(missing_docs)]
18    event DepositEvent(
19        bytes pubkey,
20        bytes withdrawal_credentials,
21        bytes amount,
22        bytes signature,
23        bytes index
24    );
25}
26
27/// Accumulate a deposit request from a log. containing a [`DepositEvent`].
28pub fn accumulate_deposit_from_log(log: &Log<DepositEvent>, out: &mut Vec<u8>) {
29    out.reserve(DEPOSIT_BYTES_SIZE);
30    out.extend_from_slice(log.pubkey.as_ref());
31    out.extend_from_slice(log.withdrawal_credentials.as_ref());
32    out.extend_from_slice(log.amount.as_ref());
33    out.extend_from_slice(log.signature.as_ref());
34    out.extend_from_slice(log.index.as_ref());
35}
36
37/// Accumulate deposits from an iterator of logs.
38pub fn accumulate_deposits_from_logs<'a>(
39    address: Address,
40    logs: impl IntoIterator<Item = &'a Log>,
41    out: &mut Vec<u8>,
42) -> Result<(), BlockValidationError> {
43    logs.into_iter().filter(|log| log.address == address).try_for_each(|log| {
44        // We assume that the log is valid because it was emitted by the
45        // deposit contract.
46        let decoded_log =
47            DepositEvent::decode_log(log, false).map_err(|err: alloy_sol_types::Error| {
48                BlockValidationError::DepositRequestDecode(err.to_string())
49            })?;
50        accumulate_deposit_from_log(&decoded_log, out);
51        Ok(())
52    })
53}
54
55/// Accumulate deposits from a receipt. Iterates over the logs in the receipt
56/// and accumulates the deposit request bytestrings.
57pub fn accumulate_deposits_from_receipt(
58    address: Address,
59    receipt: &Receipt,
60    out: &mut Vec<u8>,
61) -> Result<(), BlockValidationError> {
62    accumulate_deposits_from_logs(address, &receipt.logs, out)
63}
64
65/// Accumulate deposits from a list of receipts. Iterates over the logs in the
66/// receipts and accumulates the deposit request bytestrings.
67pub fn accumulate_deposits_from_receipts<'a, I>(
68    address: Address,
69    receipts: I,
70    out: &mut Vec<u8>,
71) -> Result<(), BlockValidationError>
72where
73    I: IntoIterator<Item = &'a Receipt>,
74{
75    receipts
76        .into_iter()
77        .try_for_each(|receipt| accumulate_deposits_from_receipt(address, receipt, out))
78}
79
80/// Find deposit logs in a list of receipts, and return the concatenated
81/// deposit request bytestring.
82///
83/// The address of the deposit contract is taken from the chain spec, and
84/// defaults to [`MAINNET_DEPOSIT_CONTRACT_ADDRESS`] if not specified in
85/// the chain spec.
86pub fn parse_deposits_from_receipts<'a, I>(
87    chainspec: &ChainSpec,
88    receipts: I,
89) -> Result<Bytes, BlockValidationError>
90where
91    I: IntoIterator<Item = &'a Receipt>,
92{
93    let mut out = Vec::new();
94    accumulate_deposits_from_receipts(
95        chainspec.deposit_contract().map(|c| c.address).unwrap_or(MAINNET_DEPOSIT_CONTRACT_ADDRESS),
96        receipts,
97        &mut out,
98    )?;
99    Ok(out.into())
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use alloy_primitives::bytes;
106    use reth_chainspec::MAINNET;
107    use reth_primitives::TxType;
108
109    #[test]
110    fn test_parse_deposit_from_log() {
111        let receipts = vec![
112            // https://etherscan.io/tx/0xa5239d4c542063d29022545835815b78b09f571f2bf1c8427f4765d6f5abbce9
113            #[allow(clippy::needless_update)] // side-effect of optimism fields
114            Receipt {
115                // these don't matter
116                tx_type: TxType::Legacy,
117                success: true,
118                cumulative_gas_used: 0,
119                logs: serde_json::from_str(
120                    r#"[{"address":"0x00000000219ab540356cbb839cbe05303d7705fa","topics":["0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"],"data":"0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030998c8086669bf65e24581cda47d8537966e9f5066fc6ffdcba910a1bfb91eae7a4873fcce166a1c4ea217e6b1afd396200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002001000000000000000000000001c340fb72ed14d4eaa71f7633ee9e33b88d4f3900000000000000000000000000000000000000000000000000000000000000080040597307000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006098ddbffd700c1aac324cfdf0492ff289223661eb26718ce3651ba2469b22f480d56efab432ed91af05a006bde0c1ea68134e0acd8cacca0c13ad1f716db874b44abfcc966368019753174753bca3af2ea84bc569c46f76592a91e97f311eddec0000000000000000000000000000000000000000000000000000000000000008e474160000000000000000000000000000000000000000000000000000000000","blockHash":"0x8d1289c5a7e0965b1d1bb75cdc4c3f73dda82d4ebb94ff5b98d1389cebd53b56","blockNumber":"0x12f0d8d","transactionHash":"0xa5239d4c542063d29022545835815b78b09f571f2bf1c8427f4765d6f5abbce9","transactionIndex":"0xc4","logIndex":"0x18f","removed":false}]"#
121                ).unwrap(),
122                ..Default::default()
123            },
124            // https://etherscan.io/tx/0xd9734d4e3953bcaa939fd1c1d80950ee54aeecc02eef6ae8179f47f5b7103338
125            #[allow(clippy::needless_update)] // side-effect of optimism fields
126            Receipt {
127                // these don't matter
128                tx_type: TxType::Legacy,
129                success: true,
130                cumulative_gas_used: 0,
131                logs: serde_json::from_str(
132                    r#"[{"address":"0x00000000219ab540356cbb839cbe05303d7705fa","topics":["0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"],"data":"0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030a1a2ba870a90e889aa594a0cc1c6feffb94c2d8f65646c937f1f456a315ef649533e25a4614d8f4f66ebdb06481b90af0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000a0f04a231efbc29e1db7d086300ff550211c2f6000000000000000000000000000000000000000000000000000000000000000800405973070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060ad416d590e1a7f52baff770a12835b68904efad22cc9f8ba531e50cbbd26f32b9c7373cf6538a0577f501e4d3e3e63e208767bcccaae94e1e3720bfb734a286f9c017d17af46536545ccb7ca94d71f295e71f6d25bf978c09ada6f8d3f7ba0390000000000000000000000000000000000000000000000000000000000000008e374160000000000000000000000000000000000000000000000000000000000","blockHash":"0x8d1289c5a7e0965b1d1bb75cdc4c3f73dda82d4ebb94ff5b98d1389cebd53b56","blockNumber":"0x12f0d8d","transactionHash":"0xd9734d4e3953bcaa939fd1c1d80950ee54aeecc02eef6ae8179f47f5b7103338","transactionIndex":"0x7c","logIndex":"0xe2","removed":false}]"#,
133                ).unwrap(),
134                ..Default::default()
135            },
136        ];
137
138        let request_data = parse_deposits_from_receipts(&MAINNET, &receipts).unwrap();
139        assert_eq!(
140            request_data,
141            bytes!(
142                "998c8086669bf65e24581cda47d8537966e9f5066fc6ffdcba910a1bfb91eae7a4873fcce166a1c4ea217e6b1afd396201000000000000000000000001c340fb72ed14d4eaa71f7633ee9e33b88d4f39004059730700000098ddbffd700c1aac324cfdf0492ff289223661eb26718ce3651ba2469b22f480d56efab432ed91af05a006bde0c1ea68134e0acd8cacca0c13ad1f716db874b44abfcc966368019753174753bca3af2ea84bc569c46f76592a91e97f311eddece474160000000000a1a2ba870a90e889aa594a0cc1c6feffb94c2d8f65646c937f1f456a315ef649533e25a4614d8f4f66ebdb06481b90af0100000000000000000000000a0f04a231efbc29e1db7d086300ff550211c2f60040597307000000ad416d590e1a7f52baff770a12835b68904efad22cc9f8ba531e50cbbd26f32b9c7373cf6538a0577f501e4d3e3e63e208767bcccaae94e1e3720bfb734a286f9c017d17af46536545ccb7ca94d71f295e71f6d25bf978c09ada6f8d3f7ba039e374160000000000"
143            )
144        );
145    }
146}