Skip to main content

ethrex_common/types/
requests.rs

1use crate::H256;
2use bytes::Bytes;
3use ethereum_types::Address;
4use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode, error::RLPDecodeError};
5use serde::{Deserialize, Serialize};
6use sha2::{Digest, Sha256};
7use tracing::error;
8
9use super::{Bytes48, Receipt};
10use crate::constants::DEPOSIT_TOPIC;
11use crate::serde_utils;
12
13pub type Bytes32 = [u8; 32];
14pub type Bytes96 = [u8; 96];
15const DEPOSIT_TYPE: u8 = 0x00;
16const WITHDRAWAL_TYPE: u8 = 0x01;
17const CONSOLIDATION_TYPE: u8 = 0x02;
18
19#[derive(Clone, Debug)]
20pub struct EncodedRequests(pub Bytes);
21
22impl EncodedRequests {
23    pub fn is_empty(&self) -> bool {
24        self.0.len() <= 1
25    }
26}
27
28impl<'de> Deserialize<'de> for EncodedRequests {
29    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
30    where
31        D: serde::Deserializer<'de>,
32    {
33        Ok(EncodedRequests(serde_utils::bytes::deserialize(
34            deserializer,
35        )?))
36    }
37}
38
39impl Serialize for EncodedRequests {
40    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
41    where
42        S: serde::Serializer,
43    {
44        serde_utils::bytes::serialize(&self.0, serializer)
45    }
46}
47
48impl RLPEncode for EncodedRequests {
49    fn encode(&self, buf: &mut dyn bytes::BufMut) {
50        self.0.encode(buf)
51    }
52}
53
54impl RLPDecode for EncodedRequests {
55    fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
56        let (bytes, rest) = RLPDecode::decode_unfinished(rlp)?;
57        Ok((EncodedRequests(bytes), rest))
58    }
59}
60
61#[derive(Clone, Debug)]
62pub enum Requests {
63    Deposit(Vec<Deposit>),
64    Withdrawal(Vec<u8>),
65    Consolidation(Vec<u8>),
66}
67
68impl Requests {
69    pub fn encode(&self) -> EncodedRequests {
70        let bytes: Vec<u8> = match self {
71            Requests::Deposit(deposits) => {
72                let deposit_data = deposits.iter().flat_map(|d| d.to_summarized_byte_array());
73                std::iter::once(DEPOSIT_TYPE).chain(deposit_data).collect()
74            }
75            Requests::Withdrawal(data) => std::iter::once(WITHDRAWAL_TYPE)
76                .chain(data.iter().cloned())
77                .collect(),
78            Requests::Consolidation(data) => std::iter::once(CONSOLIDATION_TYPE)
79                .chain(data.iter().cloned())
80                .collect(),
81        };
82
83        EncodedRequests(Bytes::from(bytes))
84    }
85
86    /// Returns None if any of the deposit requests couldn't be parsed
87    pub fn from_deposit_receipts(
88        deposit_contract_address: Address,
89        receipts: &[Receipt],
90    ) -> Option<Requests> {
91        let mut deposits = vec![];
92
93        for r in receipts {
94            for log in &r.logs {
95                if log.address == deposit_contract_address
96                    && log
97                        .topics
98                        .first()
99                        .is_some_and(|topic| topic == &*DEPOSIT_TOPIC)
100                {
101                    deposits.push(Deposit::from_abi_byte_array(&log.data)?);
102                }
103            }
104        }
105        Some(Self::Deposit(deposits))
106    }
107
108    pub fn from_withdrawals_data(data: Vec<u8>) -> Requests {
109        Requests::Withdrawal(data)
110    }
111
112    pub fn from_consolidation_data(data: Vec<u8>) -> Requests {
113        Requests::Consolidation(data)
114    }
115}
116
117#[derive(Debug, Clone)]
118pub struct Deposit {
119    pub pub_key: Bytes48,
120    pub withdrawal_credentials: Bytes32,
121    pub amount: u64,
122    pub signature: Bytes96,
123    pub index: u64,
124}
125
126// Followed and ported implementation from:
127// https://github.com/lightclient/go-ethereum/blob/5c4d46f3614d26654241849da7dfd46b95eed1c6/core/types/deposit.go#L61
128impl Deposit {
129    pub fn from_abi_byte_array(data: &[u8]) -> Option<Deposit> {
130        if data.len() != 576 {
131            error!("Wrong data length when parsing deposit.");
132            return None;
133        }
134
135        // Encoding scheme:
136        //
137        // positional arguments -> 5 parameters with uint256 positional value for each -> 160b
138        // pub_key: 32b of len + 48b padded to 64b
139        // withdrawal_credentials: 32b of len + 32b
140        // amount: 32b of len + 8b padded to 32b
141        // signature: 32b of len + 96b
142        // index: 32b of len + 8b padded to 32b
143        //
144        // -> Total len: 576 bytes
145
146        const WORD: usize = 32;
147        const U32_TAIL: usize = WORD - 4;
148
149        const PUB_KEY_OFFSET: u32 = 160;
150        const WITHDRAWAL_CREDENTIALS_OFFSET: u32 = 256;
151        const AMOUNT_OFFSET: u32 = 320;
152        const SIGNATURE_OFFSET: u32 = 384;
153        const INDEX_OFFSET: u32 = 512;
154
155        const OFFSETS: [u32; 5] = [
156            PUB_KEY_OFFSET,
157            WITHDRAWAL_CREDENTIALS_OFFSET,
158            AMOUNT_OFFSET,
159            SIGNATURE_OFFSET,
160            INDEX_OFFSET,
161        ];
162
163        const PUB_KEY_SIZE: u32 = 48;
164        const WITHDRAWAL_CREDENTIALS_SIZE: u32 = 32;
165        const AMOUNT_SIZE: u32 = 8;
166        const SIGNATURE_SIZE: u32 = 96;
167        const INDEX_SIZE: u32 = 8;
168
169        const SIZES: [u32; 5] = [
170            PUB_KEY_SIZE,
171            WITHDRAWAL_CREDENTIALS_SIZE,
172            AMOUNT_SIZE,
173            SIGNATURE_SIZE,
174            INDEX_SIZE,
175        ];
176
177        // Validate Offsets & Sizes
178        for (i, (expected_offset, expected_size)) in
179            OFFSETS.into_iter().zip(SIZES.into_iter()).enumerate()
180        {
181            let offset = fixed_bytes::<WORD>(data, i * WORD)?;
182            let size = fixed_bytes::<WORD>(data, expected_offset as usize)?;
183            if offset[U32_TAIL..] != expected_offset.to_be_bytes()
184                || size[U32_TAIL..] != expected_size.to_be_bytes()
185            {
186                return None;
187            }
188        }
189
190        // Extract Data
191        let pub_key: Bytes48 =
192            fixed_bytes::<{ PUB_KEY_SIZE as usize }>(data, PUB_KEY_OFFSET as usize + WORD)?;
193        let withdrawal_credentials: Bytes32 =
194            fixed_bytes::<{ WITHDRAWAL_CREDENTIALS_SIZE as usize }>(
195                data,
196                WITHDRAWAL_CREDENTIALS_OFFSET as usize + WORD,
197            )?;
198        let amount: u64 = u64::from_le_bytes(fixed_bytes::<{ AMOUNT_SIZE as usize }>(
199            data,
200            AMOUNT_OFFSET as usize + WORD,
201        )?);
202        let signature: Bytes96 =
203            fixed_bytes::<{ SIGNATURE_SIZE as usize }>(data, SIGNATURE_OFFSET as usize + WORD)?;
204        let index: u64 = u64::from_le_bytes(fixed_bytes::<{ INDEX_SIZE as usize }>(
205            data,
206            INDEX_OFFSET as usize + WORD,
207        )?);
208
209        Some(Deposit {
210            pub_key,
211            withdrawal_credentials,
212            amount,
213            signature,
214            index,
215        })
216    }
217
218    pub fn to_summarized_byte_array(&self) -> [u8; 192] {
219        let mut buffer = [0u8; 192];
220        // pub_key + withdrawal_credentials + amount + signature + index
221        let mut p = 0;
222        buffer[p..48].clone_from_slice(&self.pub_key);
223        p += 48;
224        buffer[p..p + 32].clone_from_slice(&self.withdrawal_credentials);
225        p += 32;
226        buffer[p..p + 8].clone_from_slice(&self.amount.to_le_bytes());
227        p += 8;
228        buffer[p..p + 96].clone_from_slice(&self.signature);
229        p += 96;
230        buffer[p..p + 8].clone_from_slice(&self.index.to_le_bytes());
231
232        buffer
233    }
234}
235
236fn fixed_bytes<const N: usize>(data: &[u8], offset: usize) -> Option<[u8; N]> {
237    data.get(offset..offset + N)?.try_into().ok()
238}
239
240// See https://github.com/ethereum/EIPs/blob/2a6b6965e64787815f7fffb9a4c27660d9683846/EIPS/eip-7685.md?plain=1#L62.
241pub fn compute_requests_hash(requests: &[EncodedRequests]) -> H256 {
242    let mut hasher = Sha256::new();
243    for request in requests {
244        let request_bytes = request.0.as_ref();
245        if request_bytes.len() > 1 {
246            hasher.update(Sha256::digest(request_bytes));
247        }
248    }
249    H256::from_slice(&hasher.finalize())
250}