alloy_evm/eth/
eip6110.rs

1//! EIP-6110 deposit requests parsing
2
3use super::spec::EthExecutorSpec;
4use crate::block::BlockValidationError;
5use alloc::{string::ToString, vec::Vec};
6use alloy_consensus::TxReceipt;
7use alloy_primitives::{Address, Bytes, Log};
8use alloy_sol_types::{sol, SolEvent};
9
10pub use alloy_eips::eip6110::*;
11
12/// The size of a deposit request in bytes. While the event fields emit
13/// bytestrings, those bytestrings are fixed size. The fields are: 48-byte
14/// pubkey, 32-byte withdrawal credentials, 8-byte amount, 96-byte signature,
15/// and 8-byte index.
16const DEPOSIT_BYTES_SIZE: usize = 48 + 32 + 8 + 96 + 8;
17
18sol! {
19    #[allow(missing_docs)]
20    event DepositEvent(
21        bytes pubkey,
22        bytes withdrawal_credentials,
23        bytes amount,
24        bytes signature,
25        bytes index
26    );
27}
28
29/// Accumulate a deposit request from a log. containing a [`DepositEvent`].
30pub fn accumulate_deposit_from_log(log: &Log<DepositEvent>, out: &mut Vec<u8>) {
31    out.reserve(DEPOSIT_BYTES_SIZE);
32    out.extend_from_slice(log.pubkey.as_ref());
33    out.extend_from_slice(log.withdrawal_credentials.as_ref());
34    out.extend_from_slice(log.amount.as_ref());
35    out.extend_from_slice(log.signature.as_ref());
36    out.extend_from_slice(log.index.as_ref());
37}
38
39/// Accumulate deposits from an iterator of logs.
40pub fn accumulate_deposits_from_logs<'a>(
41    address: Address,
42    logs: impl IntoIterator<Item = &'a Log>,
43    out: &mut Vec<u8>,
44) -> Result<(), BlockValidationError> {
45    logs.into_iter()
46        // filter logs by address
47        .filter(|log| log.address == address)
48        // explicitly filter logs by the DepositEvent's signature hash (first topic)
49        .filter(|log| {
50            // 0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5
51            log.topics().first() == Some(&DepositEvent::SIGNATURE_HASH)
52        })
53        .try_for_each(|log| {
54            // We assume that the log is valid because it was emitted by the
55            // deposit contract.
56            let decoded_log =
57                DepositEvent::decode_log(log).map_err(|err: alloy_sol_types::Error| {
58                    BlockValidationError::DepositRequestDecode(err.to_string())
59                })?;
60            accumulate_deposit_from_log(&decoded_log, out);
61            Ok(())
62        })
63}
64
65/// Accumulate deposits from a receipt. Iterates over the logs in the receipt
66/// and accumulates the deposit request bytestrings.
67pub fn accumulate_deposits_from_receipt(
68    address: Address,
69    receipt: impl TxReceipt<Log = Log>,
70    out: &mut Vec<u8>,
71) -> Result<(), BlockValidationError> {
72    accumulate_deposits_from_logs(address, receipt.logs(), out)
73}
74
75/// Accumulate deposits from a list of receipts. Iterates over the logs in the
76/// receipts and accumulates the deposit request bytestrings.
77pub fn accumulate_deposits_from_receipts<'a, I, R>(
78    address: Address,
79    receipts: I,
80    out: &mut Vec<u8>,
81) -> Result<(), BlockValidationError>
82where
83    I: IntoIterator<Item = &'a R>,
84    R: TxReceipt<Log = Log> + 'a,
85{
86    receipts
87        .into_iter()
88        .try_for_each(|receipt| accumulate_deposits_from_receipt(address, receipt, out))
89}
90
91/// Find deposit logs in a list of receipts, and return the concatenated
92/// deposit request bytestring.
93///
94/// The address of the deposit contract is taken from the chain spec, and
95/// defaults to [`MAINNET_DEPOSIT_CONTRACT_ADDRESS`] if not specified in
96/// the chain spec.
97pub fn parse_deposits_from_receipts<'a, I, R>(
98    spec: impl EthExecutorSpec,
99    receipts: I,
100) -> Result<Bytes, BlockValidationError>
101where
102    I: IntoIterator<Item = &'a R>,
103    R: TxReceipt<Log = Log> + 'a,
104{
105    let mut out = Vec::new();
106    accumulate_deposits_from_receipts(
107        spec.deposit_contract_address().unwrap_or(MAINNET_DEPOSIT_CONTRACT_ADDRESS),
108        receipts,
109        &mut out,
110    )?;
111    Ok(out.into())
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use crate::eth::spec::EthSpec;
118    use alloc::vec;
119    use alloy_consensus::Receipt;
120    use alloy_primitives::{b256, bytes};
121
122    #[test]
123    fn check_deposit_sig() {
124        let expected = b256!("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5");
125        assert_eq!(expected, DepositEvent::SIGNATURE_HASH);
126    }
127
128    #[test]
129    fn test_parse_deposit_from_log() {
130        let receipts = vec![
131            // https://etherscan.io/tx/0xa5239d4c542063d29022545835815b78b09f571f2bf1c8427f4765d6f5abbce9
132            Receipt {
133                status: true.into(),
134                cumulative_gas_used: 0,
135                logs: serde_json::from_str(
136                    r#"[{"address":"0x00000000219ab540356cbb839cbe05303d7705fa","topics":["0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"],"data":"0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030998c8086669bf65e24581cda47d8537966e9f5066fc6ffdcba910a1bfb91eae7a4873fcce166a1c4ea217e6b1afd396200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002001000000000000000000000001c340fb72ed14d4eaa71f7633ee9e33b88d4f3900000000000000000000000000000000000000000000000000000000000000080040597307000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006098ddbffd700c1aac324cfdf0492ff289223661eb26718ce3651ba2469b22f480d56efab432ed91af05a006bde0c1ea68134e0acd8cacca0c13ad1f716db874b44abfcc966368019753174753bca3af2ea84bc569c46f76592a91e97f311eddec0000000000000000000000000000000000000000000000000000000000000008e474160000000000000000000000000000000000000000000000000000000000","blockHash":"0x8d1289c5a7e0965b1d1bb75cdc4c3f73dda82d4ebb94ff5b98d1389cebd53b56","blockNumber":"0x12f0d8d","transactionHash":"0xa5239d4c542063d29022545835815b78b09f571f2bf1c8427f4765d6f5abbce9","transactionIndex":"0xc4","logIndex":"0x18f","removed":false}]"#
137                ).unwrap(),
138            },
139            // https://etherscan.io/tx/0xd9734d4e3953bcaa939fd1c1d80950ee54aeecc02eef6ae8179f47f5b7103338
140            Receipt {
141                status: true.into(),
142                cumulative_gas_used: 0,
143                logs: serde_json::from_str(
144                    r#"[{"address":"0x00000000219ab540356cbb839cbe05303d7705fa","topics":["0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"],"data":"0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030a1a2ba870a90e889aa594a0cc1c6feffb94c2d8f65646c937f1f456a315ef649533e25a4614d8f4f66ebdb06481b90af0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000a0f04a231efbc29e1db7d086300ff550211c2f6000000000000000000000000000000000000000000000000000000000000000800405973070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060ad416d590e1a7f52baff770a12835b68904efad22cc9f8ba531e50cbbd26f32b9c7373cf6538a0577f501e4d3e3e63e208767bcccaae94e1e3720bfb734a286f9c017d17af46536545ccb7ca94d71f295e71f6d25bf978c09ada6f8d3f7ba0390000000000000000000000000000000000000000000000000000000000000008e374160000000000000000000000000000000000000000000000000000000000","blockHash":"0x8d1289c5a7e0965b1d1bb75cdc4c3f73dda82d4ebb94ff5b98d1389cebd53b56","blockNumber":"0x12f0d8d","transactionHash":"0xd9734d4e3953bcaa939fd1c1d80950ee54aeecc02eef6ae8179f47f5b7103338","transactionIndex":"0x7c","logIndex":"0xe2","removed":false}]"#,
145                ).unwrap(),
146            },
147        ];
148
149        let request_data = parse_deposits_from_receipts(EthSpec::mainnet(), &receipts).unwrap();
150        assert_eq!(
151            request_data,
152            bytes!(
153                "998c8086669bf65e24581cda47d8537966e9f5066fc6ffdcba910a1bfb91eae7a4873fcce166a1c4ea217e6b1afd396201000000000000000000000001c340fb72ed14d4eaa71f7633ee9e33b88d4f39004059730700000098ddbffd700c1aac324cfdf0492ff289223661eb26718ce3651ba2469b22f480d56efab432ed91af05a006bde0c1ea68134e0acd8cacca0c13ad1f716db874b44abfcc966368019753174753bca3af2ea84bc569c46f76592a91e97f311eddece474160000000000a1a2ba870a90e889aa594a0cc1c6feffb94c2d8f65646c937f1f456a315ef649533e25a4614d8f4f66ebdb06481b90af0100000000000000000000000a0f04a231efbc29e1db7d086300ff550211c2f60040597307000000ad416d590e1a7f52baff770a12835b68904efad22cc9f8ba531e50cbbd26f32b9c7373cf6538a0577f501e4d3e3e63e208767bcccaae94e1e3720bfb734a286f9c017d17af46536545ccb7ca94d71f295e71f6d25bf978c09ada6f8d3f7ba039e374160000000000"
154            )
155        );
156    }
157
158    #[test]
159    fn test_parse_deposit_from_log_extra() {
160        let receipts = vec![
161            Receipt {
162                status: true.into(),
163                cumulative_gas_used: 0,
164                // Transfer + Deposit
165                logs: serde_json::from_str(
166                    r#"[
167                      {
168    "address": "0x00000000219ab540356cbb839cbe05303d7705fa",
169    "topics": [
170      "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
171      "0x0000000000000000000000008b0c2c4c8eb078bc6c01f48523764c8942c0c6c4",
172      "0x0000000000000000000000000000000000000000000000000000000000000000"
173    ],
174    "data": "0x0000000000000000000000000000000000000000000000000000000000000001",
175    "blockNumber": "0x158c89",
176    "transactionHash": "0xfcd133f57d5bab8cea211bab0361379456b9115a66bd242d62582aab0bb5fe71",
177    "transactionIndex": "0x0",
178    "blockHash": "0xe826da725061cdd2ab7638d4c4b5f25a07491f131d05b89628c4613cad07a246",
179    "logIndex": "0x0",
180    "removed": false
181  },
182                    {"address":"0x00000000219ab540356cbb839cbe05303d7705fa","topics":["0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"],"data":"0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030998c8086669bf65e24581cda47d8537966e9f5066fc6ffdcba910a1bfb91eae7a4873fcce166a1c4ea217e6b1afd396200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002001000000000000000000000001c340fb72ed14d4eaa71f7633ee9e33b88d4f3900000000000000000000000000000000000000000000000000000000000000080040597307000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006098ddbffd700c1aac324cfdf0492ff289223661eb26718ce3651ba2469b22f480d56efab432ed91af05a006bde0c1ea68134e0acd8cacca0c13ad1f716db874b44abfcc966368019753174753bca3af2ea84bc569c46f76592a91e97f311eddec0000000000000000000000000000000000000000000000000000000000000008e474160000000000000000000000000000000000000000000000000000000000","blockHash":"0x8d1289c5a7e0965b1d1bb75cdc4c3f73dda82d4ebb94ff5b98d1389cebd53b56","blockNumber":"0x12f0d8d","transactionHash":"0xa5239d4c542063d29022545835815b78b09f571f2bf1c8427f4765d6f5abbce9","transactionIndex":"0xc4","logIndex":"0x18f","removed":false}]"#
183                ).unwrap(),
184            },
185            // https://etherscan.io/tx/0xd9734d4e3953bcaa939fd1c1d80950ee54aeecc02eef6ae8179f47f5b7103338
186            Receipt {
187                status: true.into(),
188                cumulative_gas_used: 0,
189                logs: serde_json::from_str(
190                    r#"[{"address":"0x00000000219ab540356cbb839cbe05303d7705fa","topics":["0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"],"data":"0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030a1a2ba870a90e889aa594a0cc1c6feffb94c2d8f65646c937f1f456a315ef649533e25a4614d8f4f66ebdb06481b90af0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000a0f04a231efbc29e1db7d086300ff550211c2f6000000000000000000000000000000000000000000000000000000000000000800405973070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060ad416d590e1a7f52baff770a12835b68904efad22cc9f8ba531e50cbbd26f32b9c7373cf6538a0577f501e4d3e3e63e208767bcccaae94e1e3720bfb734a286f9c017d17af46536545ccb7ca94d71f295e71f6d25bf978c09ada6f8d3f7ba0390000000000000000000000000000000000000000000000000000000000000008e374160000000000000000000000000000000000000000000000000000000000","blockHash":"0x8d1289c5a7e0965b1d1bb75cdc4c3f73dda82d4ebb94ff5b98d1389cebd53b56","blockNumber":"0x12f0d8d","transactionHash":"0xd9734d4e3953bcaa939fd1c1d80950ee54aeecc02eef6ae8179f47f5b7103338","transactionIndex":"0x7c","logIndex":"0xe2","removed":false}]"#,
191                ).unwrap(),
192            },
193        ];
194
195        let request_data = parse_deposits_from_receipts(EthSpec::mainnet(), &receipts).unwrap();
196        assert_eq!(
197            request_data,
198            bytes!(
199                "998c8086669bf65e24581cda47d8537966e9f5066fc6ffdcba910a1bfb91eae7a4873fcce166a1c4ea217e6b1afd396201000000000000000000000001c340fb72ed14d4eaa71f7633ee9e33b88d4f39004059730700000098ddbffd700c1aac324cfdf0492ff289223661eb26718ce3651ba2469b22f480d56efab432ed91af05a006bde0c1ea68134e0acd8cacca0c13ad1f716db874b44abfcc966368019753174753bca3af2ea84bc569c46f76592a91e97f311eddece474160000000000a1a2ba870a90e889aa594a0cc1c6feffb94c2d8f65646c937f1f456a315ef649533e25a4614d8f4f66ebdb06481b90af0100000000000000000000000a0f04a231efbc29e1db7d086300ff550211c2f60040597307000000ad416d590e1a7f52baff770a12835b68904efad22cc9f8ba531e50cbbd26f32b9c7373cf6538a0577f501e4d3e3e63e208767bcccaae94e1e3720bfb734a286f9c017d17af46536545ccb7ca94d71f295e71f6d25bf978c09ada6f8d3f7ba039e374160000000000"
200            )
201        );
202    }
203}