kona_protocol/
deposits.rs

1//! Contains deposit transaction types and helper methods.
2
3use alloc::vec::Vec;
4use alloy_eips::eip2718::Encodable2718;
5use alloy_primitives::{Address, B256, Bytes, Log, TxKind, U64, U256, b256};
6use op_alloy_consensus::{TxDeposit, UserDepositSource};
7
8/// Deposit log event abi signature.
9pub const DEPOSIT_EVENT_ABI: &str = "TransactionDeposited(address,address,uint256,bytes)";
10
11/// Deposit event abi hash.
12///
13/// This is the keccak256 hash of the deposit event ABI signature.
14/// `keccak256("TransactionDeposited(address,address,uint256,bytes)")`
15pub const DEPOSIT_EVENT_ABI_HASH: B256 =
16    b256!("b3813568d9991fc951961fcb4c784893574240a28925604d09fc577c55bb7c32");
17
18/// The initial version of the deposit event log.
19pub const DEPOSIT_EVENT_VERSION_0: B256 = B256::ZERO;
20
21/// An [`TxDeposit`] validation error.
22#[derive(Debug, thiserror::Error, PartialEq, Eq)]
23pub enum DepositError {
24    /// Unexpected number of deposit event log topics.
25    #[error("Unexpected number of deposit event log topics: {0}")]
26    UnexpectedTopicsLen(usize),
27    /// Invalid deposit event selector.
28    /// Expected: [`B256`] (deposit event selector), Actual: [`B256`] (event log topic).
29    #[error("Invalid deposit event selector: {1}, expected {0}")]
30    InvalidSelector(B256, B256),
31    /// Incomplete opaqueData slice header (incomplete length).
32    #[error("Incomplete opaqueData slice header (incomplete length): {0}")]
33    IncompleteOpaqueData(usize),
34    /// The log data is not aligned to 32 bytes.
35    #[error("Unaligned log data, expected multiple of 32 bytes, got: {0}")]
36    UnalignedData(usize),
37    /// Failed to decode the `from` field of the deposit event (the second topic).
38    #[error("Failed to decode the `from` address of the deposit log topic: {0}")]
39    FromDecode(B256),
40    /// Failed to decode the `to` field of the deposit event (the third topic).
41    #[error("Failed to decode the `to` address of the deposit log topic: {0}")]
42    ToDecode(B256),
43    /// Invalid opaque data content offset.
44    #[error("Invalid u64 opaque data content offset: {0}")]
45    InvalidOpaqueDataOffset(Bytes),
46    /// Invalid opaque data content length.
47    #[error("Invalid u64 opaque data content length: {0}")]
48    InvalidOpaqueDataLength(Bytes),
49    /// Opaque data length exceeds the deposit log event data length.
50    /// Specified: [usize] (data length), Actual: [usize] (opaque data length).
51    #[error("Specified opaque data length {1} exceeds the deposit log event data length {0}")]
52    OpaqueDataOverflow(usize, usize),
53    /// Opaque data with padding exceeds the specified data length.
54    /// Specified: [usize] (data length), Actual: [usize] (opaque data length).
55    #[error("Opaque data with padding exceeds the specified data length: {1} > {0}")]
56    PaddedOpaqueDataOverflow(usize, usize),
57    /// An invalid deposit version.
58    #[error("Invalid deposit version: {0}")]
59    InvalidVersion(B256),
60    /// Unexpected opaque data length.
61    #[error("Unexpected opaque data length: {0}")]
62    UnexpectedOpaqueDataLen(usize),
63    /// Failed to decode the deposit mint value.
64    #[error("Failed to decode the u128 deposit mint value: {0}")]
65    MintDecode(Bytes),
66    /// Failed to decode the deposit gas value.
67    #[error("Failed to decode the u64 deposit gas value: {0}")]
68    GasDecode(Bytes),
69}
70
71/// Derives a deposit transaction from an EVM log event emitted by the deposit contract.
72///
73/// The emitted log must be in format:
74/// ```solidity
75/// event TransactionDeposited(
76///    address indexed from,
77///    address indexed to,
78///    uint256 indexed version,
79///    bytes opaqueData
80/// );
81/// ```
82pub fn decode_deposit(block_hash: B256, index: usize, log: &Log) -> Result<Bytes, DepositError> {
83    let topics = log.data.topics();
84    if topics.len() != 4 {
85        return Err(DepositError::UnexpectedTopicsLen(topics.len()));
86    }
87    if topics[0] != DEPOSIT_EVENT_ABI_HASH {
88        return Err(DepositError::InvalidSelector(DEPOSIT_EVENT_ABI_HASH, topics[0]));
89    }
90    if log.data.data.len() < 64 {
91        return Err(DepositError::IncompleteOpaqueData(log.data.data.len()));
92    }
93    if log.data.data.len() % 32 != 0 {
94        return Err(DepositError::UnalignedData(log.data.data.len()));
95    }
96
97    // Validate the `from` address.
98    let mut from_bytes = [0u8; 20];
99    from_bytes.copy_from_slice(&topics[1].as_slice()[12..]);
100    if topics[1].iter().take(12).any(|&b| b != 0) {
101        return Err(DepositError::FromDecode(topics[1]));
102    }
103
104    // Validate the `to` address.
105    let mut to_bytes = [0u8; 20];
106    to_bytes.copy_from_slice(&topics[2].as_slice()[12..]);
107    if topics[2].iter().take(12).any(|&b| b != 0) {
108        return Err(DepositError::ToDecode(topics[2]));
109    }
110
111    let from = Address::from(from_bytes);
112    let to = Address::from(to_bytes);
113    let version = log.data.topics()[3];
114
115    // Solidity serializes the event's Data field as follows:
116    //
117    // ```solidity
118    // abi.encode(abi.encodPacked(uint256 mint, uint256 value, uint64 gasLimit, uint8 isCreation, bytes data))
119    // ```
120    //
121    // The opaqueData will be packed as shown below:
122    //
123    // ------------------------------------------------------------
124    // | offset | 256 byte content                                |
125    // ------------------------------------------------------------
126    // | 0      | [0; 24] . {U64 big endian, hex encoded offset}  |
127    // ------------------------------------------------------------
128    // | 32     | [0; 24] . {U64 big endian, hex encoded length}  |
129    // ------------------------------------------------------------
130
131    let opaque_content_offset: U64 = U64::try_from_be_slice(&log.data.data[24..32]).ok_or(
132        DepositError::InvalidOpaqueDataOffset(Bytes::copy_from_slice(&log.data.data[24..32])),
133    )?;
134    if opaque_content_offset != U64::from(32) {
135        return Err(DepositError::InvalidOpaqueDataOffset(Bytes::copy_from_slice(
136            &log.data.data[24..32],
137        )));
138    }
139
140    // The next 32 bytes indicate the length of the opaqueData content.
141    let opaque_content_len =
142        u64::from_be_bytes(log.data.data[56..64].try_into().map_err(|_| {
143            DepositError::InvalidOpaqueDataLength(Bytes::copy_from_slice(&log.data.data[56..64]))
144        })?);
145    if opaque_content_len as usize > log.data.data.len() - 64 {
146        return Err(DepositError::OpaqueDataOverflow(
147            opaque_content_len as usize,
148            log.data.data.len() - 64,
149        ));
150    }
151    let padded_len = opaque_content_len.checked_add(32).ok_or(DepositError::OpaqueDataOverflow(
152        opaque_content_len as usize,
153        log.data.data.len() - 64,
154    ))?;
155    if padded_len as usize <= log.data.data.len() - 64 {
156        return Err(DepositError::PaddedOpaqueDataOverflow(
157            log.data.data.len() - 64,
158            opaque_content_len as usize,
159        ));
160    }
161
162    // The remaining data is the opaqueData which is tightly packed and then padded to 32 bytes by
163    // the EVM.
164    let opaque_data = &log.data.data[64..64 + opaque_content_len as usize];
165    let source = UserDepositSource::new(block_hash, index as u64);
166
167    let mut deposit_tx = TxDeposit {
168        from,
169        is_system_transaction: false,
170        source_hash: source.source_hash(),
171        ..Default::default()
172    };
173
174    // Can only handle version 0 for now
175    if !version.is_zero() {
176        return Err(DepositError::InvalidVersion(version));
177    }
178
179    unmarshal_deposit_version0(&mut deposit_tx, to, opaque_data)?;
180
181    // Re-encode the deposit transaction
182    let mut buffer = Vec::with_capacity(deposit_tx.eip2718_encoded_length());
183    deposit_tx.encode_2718(&mut buffer);
184    Ok(Bytes::from(buffer))
185}
186
187/// Unmarshals a deposit transaction from the opaque data.
188pub(crate) fn unmarshal_deposit_version0(
189    tx: &mut TxDeposit,
190    to: Address,
191    data: &[u8],
192) -> Result<(), DepositError> {
193    if data.len() < 32 + 32 + 8 + 1 {
194        return Err(DepositError::UnexpectedOpaqueDataLen(data.len()));
195    }
196
197    let mut offset = 0;
198
199    let raw_mint: [u8; 16] = data[offset + 16..offset + 32].try_into().map_err(|_| {
200        DepositError::MintDecode(Bytes::copy_from_slice(&data[offset + 16..offset + 32]))
201    })?;
202    tx.mint = u128::from_be_bytes(raw_mint);
203    offset += 32;
204
205    // uint256 value
206    tx.value = U256::from_be_slice(&data[offset..offset + 32]);
207    offset += 32;
208
209    // uint64 gas
210    let raw_gas: [u8; 8] = data[offset..offset + 8]
211        .try_into()
212        .map_err(|_| DepositError::GasDecode(Bytes::copy_from_slice(&data[offset..offset + 8])))?;
213    tx.gas_limit = u64::from_be_bytes(raw_gas);
214    offset += 8;
215
216    // uint8 isCreation
217    // isCreation: If the boolean byte is 1 then dep.To will stay nil,
218    // and it will create a contract using L2 account nonce to determine the created address.
219    if data[offset] == 0 {
220        tx.to = TxKind::Call(to);
221    } else {
222        tx.to = TxKind::Create;
223    }
224    offset += 1;
225
226    // The remainder of the opaqueData is the transaction data (without length prefix).
227    // The data may be padded to a multiple of 32 bytes
228    let tx_data_len = data.len() - offset;
229
230    // Remaining bytes fill the data
231    tx.input = Bytes::copy_from_slice(&data[offset..offset + tx_data_len]);
232
233    Ok(())
234}
235
236#[cfg(test)]
237mod test {
238    use super::*;
239    use alloc::vec;
240    use alloy_primitives::{LogData, address, b256, hex};
241
242    #[test]
243    fn test_decode_deposit_invalid_first_topic() {
244        let log = Log {
245            address: Address::default(),
246            data: LogData::new_unchecked(
247                vec![B256::default(), B256::default(), B256::default(), B256::default()],
248                Bytes::default(),
249            ),
250        };
251        let err: DepositError = decode_deposit(B256::default(), 0, &log).unwrap_err();
252        assert_eq!(err, DepositError::InvalidSelector(DEPOSIT_EVENT_ABI_HASH, B256::default()));
253    }
254
255    #[test]
256    fn test_decode_deposit_incomplete_data() {
257        let log = Log {
258            address: Address::default(),
259            data: LogData::new_unchecked(
260                vec![DEPOSIT_EVENT_ABI_HASH, B256::default(), B256::default(), B256::default()],
261                Bytes::from(vec![0u8; 63]),
262            ),
263        };
264        let err = decode_deposit(B256::default(), 0, &log).unwrap_err();
265        assert_eq!(err, DepositError::IncompleteOpaqueData(63));
266    }
267
268    #[test]
269    fn test_decode_deposit_unaligned_data() {
270        let log = Log {
271            address: Address::default(),
272            data: LogData::new_unchecked(
273                vec![DEPOSIT_EVENT_ABI_HASH, B256::default(), B256::default(), B256::default()],
274                Bytes::from(vec![0u8; 65]),
275            ),
276        };
277        let err = decode_deposit(B256::default(), 0, &log).unwrap_err();
278        assert_eq!(err, DepositError::UnalignedData(65));
279    }
280
281    #[test]
282    fn test_decode_deposit_invalid_from() {
283        let invalid_from =
284            b256!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
285        let log = Log {
286            address: Address::default(),
287            data: LogData::new_unchecked(
288                vec![DEPOSIT_EVENT_ABI_HASH, invalid_from, B256::default(), B256::default()],
289                Bytes::from(vec![0u8; 64]),
290            ),
291        };
292        let err = decode_deposit(B256::default(), 0, &log).unwrap_err();
293        assert_eq!(err, DepositError::FromDecode(invalid_from));
294    }
295
296    #[test]
297    fn test_decode_deposit_invalid_to() {
298        let invalid_to = b256!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
299        let log = Log {
300            address: Address::default(),
301            data: LogData::new_unchecked(
302                vec![DEPOSIT_EVENT_ABI_HASH, B256::default(), invalid_to, B256::default()],
303                Bytes::from(vec![0u8; 64]),
304            ),
305        };
306        let err = decode_deposit(B256::default(), 0, &log).unwrap_err();
307        assert_eq!(err, DepositError::ToDecode(invalid_to));
308    }
309
310    #[test]
311    fn test_decode_deposit_invalid_opaque_data_offset() {
312        let log = Log {
313            address: Address::default(),
314            data: LogData::new_unchecked(
315                vec![DEPOSIT_EVENT_ABI_HASH, B256::default(), B256::default(), B256::default()],
316                Bytes::from(vec![0u8; 64]),
317            ),
318        };
319        let err = decode_deposit(B256::default(), 0, &log).unwrap_err();
320        assert_eq!(err, DepositError::InvalidOpaqueDataOffset(Bytes::from(vec![0u8; 8])));
321    }
322
323    #[test]
324    fn test_decode_deposit_opaque_data_overflow() {
325        let mut data = vec![0u8; 128];
326        let offset: [u8; 8] = U64::from(32).to_be_bytes();
327        data[24..32].copy_from_slice(&offset);
328        // The first 64 bytes of the data are identifiers so
329        // if this test was to be valid, the data length would be 64 not 128.
330        let len: [u8; 8] = U64::from(128).to_be_bytes();
331        data[56..64].copy_from_slice(&len);
332        let log = Log {
333            address: Address::default(),
334            data: LogData::new_unchecked(
335                vec![DEPOSIT_EVENT_ABI_HASH, B256::default(), B256::default(), B256::default()],
336                Bytes::from(data),
337            ),
338        };
339        let err = decode_deposit(B256::default(), 0, &log).unwrap_err();
340        assert_eq!(err, DepositError::OpaqueDataOverflow(128, 64));
341    }
342
343    #[test]
344    fn test_decode_deposit_padded_overflow() {
345        let mut data = vec![0u8; 256];
346        let offset: [u8; 8] = U64::from(32).to_be_bytes();
347        data[24..32].copy_from_slice(&offset);
348        let len: [u8; 8] = U64::from(64).to_be_bytes();
349        data[56..64].copy_from_slice(&len);
350        let log = Log {
351            address: Address::default(),
352            data: LogData::new_unchecked(
353                vec![DEPOSIT_EVENT_ABI_HASH, B256::default(), B256::default(), B256::default()],
354                Bytes::from(data),
355            ),
356        };
357        let err = decode_deposit(B256::default(), 0, &log).unwrap_err();
358        assert_eq!(err, DepositError::PaddedOpaqueDataOverflow(192, 64));
359    }
360
361    #[test]
362    fn test_decode_deposit_invalid_version() {
363        let mut data = vec![0u8; 128];
364        let offset: [u8; 8] = U64::from(32).to_be_bytes();
365        data[24..32].copy_from_slice(&offset);
366        let len: [u8; 8] = U64::from(64).to_be_bytes();
367        data[56..64].copy_from_slice(&len);
368        let version = b256!("0000000000000000000000000000000000000000000000000000000000000001");
369        let log = Log {
370            address: Address::default(),
371            data: LogData::new_unchecked(
372                vec![DEPOSIT_EVENT_ABI_HASH, B256::default(), B256::default(), version],
373                Bytes::from(data),
374            ),
375        };
376        let err = decode_deposit(B256::default(), 0, &log).unwrap_err();
377        assert_eq!(err, DepositError::InvalidVersion(version));
378    }
379
380    #[test]
381    fn test_decode_deposit_empty_succeeds() {
382        let valid_to = b256!("000000000000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
383        let valid_from = b256!("000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
384        let mut data = vec![0u8; 192];
385        let offset: [u8; 8] = U64::from(32).to_be_bytes();
386        data[24..32].copy_from_slice(&offset);
387        let len: [u8; 8] = U64::from(128).to_be_bytes();
388        data[56..64].copy_from_slice(&len);
389        let log = Log {
390            address: Address::default(),
391            data: LogData::new_unchecked(
392                vec![DEPOSIT_EVENT_ABI_HASH, valid_from, valid_to, B256::default()],
393                Bytes::from(data),
394            ),
395        };
396        let tx = decode_deposit(B256::default(), 0, &log).unwrap();
397        let raw_hex = hex!(
398            "7ef887a0ed428e1c45e1d9561b62834e1a2d3015a0caae3bfdc16b4da059ac885b01a14594ffffffffffffffffffffffffffffffffffffffff94bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb80808080b700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
399        );
400        let expected = Bytes::from(raw_hex);
401        assert_eq!(tx, expected);
402    }
403
404    #[test]
405    fn test_decode_deposit_full_succeeds() {
406        let mut data = vec![0u8; 192];
407        let offset: [u8; 8] = U64::from(32).to_be_bytes();
408        data[24..32].copy_from_slice(&offset);
409        let len: [u8; 8] = U64::from(128).to_be_bytes();
410        data[56..64].copy_from_slice(&len);
411        // Copy the u128 mint value
412        let mint: [u8; 16] = 10_u128.to_be_bytes();
413        data[80..96].copy_from_slice(&mint);
414        // Copy the tx value
415        let value: [u8; 32] = U256::from(100).to_be_bytes();
416        data[96..128].copy_from_slice(&value);
417        // Copy the gas limit
418        let gas: [u8; 8] = 1000_u64.to_be_bytes();
419        data[128..136].copy_from_slice(&gas);
420        // Copy the isCreation flag
421        data[136] = 1;
422        let from = address!("1111111111111111111111111111111111111111");
423        let mut from_bytes = vec![0u8; 32];
424        from_bytes[12..32].copy_from_slice(from.as_slice());
425        let to = address!("2222222222222222222222222222222222222222");
426        let mut to_bytes = vec![0u8; 32];
427        to_bytes[12..32].copy_from_slice(to.as_slice());
428        let log = Log {
429            address: Address::default(),
430            data: LogData::new_unchecked(
431                vec![
432                    DEPOSIT_EVENT_ABI_HASH,
433                    B256::from_slice(&from_bytes),
434                    B256::from_slice(&to_bytes),
435                    B256::default(),
436                ],
437                Bytes::from(data),
438            ),
439        };
440        let tx = decode_deposit(B256::default(), 0, &log).unwrap();
441        let raw_hex = hex!(
442            "7ef875a0ed428e1c45e1d9561b62834e1a2d3015a0caae3bfdc16b4da059ac885b01a145941111111111111111111111111111111111111111800a648203e880b700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
443        );
444        let expected = Bytes::from(raw_hex);
445        assert_eq!(tx, expected);
446    }
447
448    #[test]
449    fn test_unmarshal_deposit_version0_invalid_len() {
450        let data = vec![0u8; 72];
451        let mut tx = TxDeposit::default();
452        let to = address!("5555555555555555555555555555555555555555");
453        let err = unmarshal_deposit_version0(&mut tx, to, &data).unwrap_err();
454        assert_eq!(err, DepositError::UnexpectedOpaqueDataLen(72));
455
456        // Data must have at least length 73
457        let data = vec![0u8; 73];
458        let mut tx = TxDeposit::default();
459        let to = address!("5555555555555555555555555555555555555555");
460        unmarshal_deposit_version0(&mut tx, to, &data).unwrap();
461    }
462
463    #[test]
464    fn test_unmarshal_deposit_version0() {
465        let mut data = vec![0u8; 192];
466        let offset: [u8; 8] = U64::from(32).to_be_bytes();
467        data[24..32].copy_from_slice(&offset);
468        let len: [u8; 8] = U64::from(128).to_be_bytes();
469        data[56..64].copy_from_slice(&len);
470        // Copy the u128 mint value
471        let mint: [u8; 16] = 10_u128.to_be_bytes();
472        data[80..96].copy_from_slice(&mint);
473        // Copy the tx value
474        let value: [u8; 32] = U256::from(100).to_be_bytes();
475        data[96..128].copy_from_slice(&value);
476        // Copy the gas limit
477        let gas: [u8; 8] = 1000_u64.to_be_bytes();
478        data[128..136].copy_from_slice(&gas);
479        // Copy the isCreation flag
480        data[136] = 1;
481        let mut tx = TxDeposit {
482            from: address!("1111111111111111111111111111111111111111"),
483            to: TxKind::Call(address!("2222222222222222222222222222222222222222")),
484            value: U256::from(100),
485            gas_limit: 1000,
486            mint: 10,
487            ..Default::default()
488        };
489        let to = address!("5555555555555555555555555555555555555555");
490        unmarshal_deposit_version0(&mut tx, to, &data).unwrap();
491        assert_eq!(tx.to, TxKind::Call(address!("5555555555555555555555555555555555555555")));
492    }
493}