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 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    let mint = u128::from_be_bytes(raw_mint);
203
204    // 0 mint is represented as nil to skip minting code
205    if mint == 0 {
206        tx.mint = None;
207    } else {
208        tx.mint = Some(mint);
209    }
210    offset += 32;
211
212    // uint256 value
213    tx.value = U256::from_be_slice(&data[offset..offset + 32]);
214    offset += 32;
215
216    // uint64 gas
217    let raw_gas: [u8; 8] = data[offset..offset + 8]
218        .try_into()
219        .map_err(|_| DepositError::GasDecode(Bytes::copy_from_slice(&data[offset..offset + 8])))?;
220    tx.gas_limit = u64::from_be_bytes(raw_gas);
221    offset += 8;
222
223    // uint8 isCreation
224    // isCreation: If the boolean byte is 1 then dep.To will stay nil,
225    // and it will create a contract using L2 account nonce to determine the created address.
226    if data[offset] == 0 {
227        tx.to = TxKind::Call(to);
228    } else {
229        tx.to = TxKind::Create;
230    }
231    offset += 1;
232
233    // The remainder of the opaqueData is the transaction data (without length prefix).
234    // The data may be padded to a multiple of 32 bytes
235    let tx_data_len = data.len() - offset;
236
237    // Remaining bytes fill the data
238    tx.input = Bytes::copy_from_slice(&data[offset..offset + tx_data_len]);
239
240    Ok(())
241}
242
243#[cfg(test)]
244mod test {
245    use super::*;
246    use alloc::vec;
247    use alloy_primitives::{LogData, address, b256, hex};
248
249    #[test]
250    fn test_decode_deposit_invalid_first_topic() {
251        let log = Log {
252            address: Address::default(),
253            data: LogData::new_unchecked(
254                vec![B256::default(), B256::default(), B256::default(), B256::default()],
255                Bytes::default(),
256            ),
257        };
258        let err: DepositError = decode_deposit(B256::default(), 0, &log).unwrap_err();
259        assert_eq!(err, DepositError::InvalidSelector(DEPOSIT_EVENT_ABI_HASH, B256::default()));
260    }
261
262    #[test]
263    fn test_decode_deposit_incomplete_data() {
264        let log = Log {
265            address: Address::default(),
266            data: LogData::new_unchecked(
267                vec![DEPOSIT_EVENT_ABI_HASH, B256::default(), B256::default(), B256::default()],
268                Bytes::from(vec![0u8; 63]),
269            ),
270        };
271        let err = decode_deposit(B256::default(), 0, &log).unwrap_err();
272        assert_eq!(err, DepositError::IncompleteOpaqueData(63));
273    }
274
275    #[test]
276    fn test_decode_deposit_unaligned_data() {
277        let log = Log {
278            address: Address::default(),
279            data: LogData::new_unchecked(
280                vec![DEPOSIT_EVENT_ABI_HASH, B256::default(), B256::default(), B256::default()],
281                Bytes::from(vec![0u8; 65]),
282            ),
283        };
284        let err = decode_deposit(B256::default(), 0, &log).unwrap_err();
285        assert_eq!(err, DepositError::UnalignedData(65));
286    }
287
288    #[test]
289    fn test_decode_deposit_invalid_from() {
290        let invalid_from =
291            b256!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
292        let log = Log {
293            address: Address::default(),
294            data: LogData::new_unchecked(
295                vec![DEPOSIT_EVENT_ABI_HASH, invalid_from, B256::default(), B256::default()],
296                Bytes::from(vec![0u8; 64]),
297            ),
298        };
299        let err = decode_deposit(B256::default(), 0, &log).unwrap_err();
300        assert_eq!(err, DepositError::FromDecode(invalid_from));
301    }
302
303    #[test]
304    fn test_decode_deposit_invalid_to() {
305        let invalid_to = b256!("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
306        let log = Log {
307            address: Address::default(),
308            data: LogData::new_unchecked(
309                vec![DEPOSIT_EVENT_ABI_HASH, B256::default(), invalid_to, B256::default()],
310                Bytes::from(vec![0u8; 64]),
311            ),
312        };
313        let err = decode_deposit(B256::default(), 0, &log).unwrap_err();
314        assert_eq!(err, DepositError::ToDecode(invalid_to));
315    }
316
317    #[test]
318    fn test_decode_deposit_invalid_opaque_data_offset() {
319        let log = Log {
320            address: Address::default(),
321            data: LogData::new_unchecked(
322                vec![DEPOSIT_EVENT_ABI_HASH, B256::default(), B256::default(), B256::default()],
323                Bytes::from(vec![0u8; 64]),
324            ),
325        };
326        let err = decode_deposit(B256::default(), 0, &log).unwrap_err();
327        assert_eq!(err, DepositError::InvalidOpaqueDataOffset(Bytes::from(vec![0u8; 8])));
328    }
329
330    #[test]
331    fn test_decode_deposit_opaque_data_overflow() {
332        let mut data = vec![0u8; 128];
333        let offset: [u8; 8] = U64::from(32).to_be_bytes();
334        data[24..32].copy_from_slice(&offset);
335        // The first 64 bytes of the data are identifiers so
336        // if this test was to be valid, the data length would be 64 not 128.
337        let len: [u8; 8] = U64::from(128).to_be_bytes();
338        data[56..64].copy_from_slice(&len);
339        let log = Log {
340            address: Address::default(),
341            data: LogData::new_unchecked(
342                vec![DEPOSIT_EVENT_ABI_HASH, B256::default(), B256::default(), B256::default()],
343                Bytes::from(data),
344            ),
345        };
346        let err = decode_deposit(B256::default(), 0, &log).unwrap_err();
347        assert_eq!(err, DepositError::OpaqueDataOverflow(128, 64));
348    }
349
350    #[test]
351    fn test_decode_deposit_padded_overflow() {
352        let mut data = vec![0u8; 256];
353        let offset: [u8; 8] = U64::from(32).to_be_bytes();
354        data[24..32].copy_from_slice(&offset);
355        let len: [u8; 8] = U64::from(64).to_be_bytes();
356        data[56..64].copy_from_slice(&len);
357        let log = Log {
358            address: Address::default(),
359            data: LogData::new_unchecked(
360                vec![DEPOSIT_EVENT_ABI_HASH, B256::default(), B256::default(), B256::default()],
361                Bytes::from(data),
362            ),
363        };
364        let err = decode_deposit(B256::default(), 0, &log).unwrap_err();
365        assert_eq!(err, DepositError::PaddedOpaqueDataOverflow(192, 64));
366    }
367
368    #[test]
369    fn test_decode_deposit_invalid_version() {
370        let mut data = vec![0u8; 128];
371        let offset: [u8; 8] = U64::from(32).to_be_bytes();
372        data[24..32].copy_from_slice(&offset);
373        let len: [u8; 8] = U64::from(64).to_be_bytes();
374        data[56..64].copy_from_slice(&len);
375        let version = b256!("0000000000000000000000000000000000000000000000000000000000000001");
376        let log = Log {
377            address: Address::default(),
378            data: LogData::new_unchecked(
379                vec![DEPOSIT_EVENT_ABI_HASH, B256::default(), B256::default(), version],
380                Bytes::from(data),
381            ),
382        };
383        let err = decode_deposit(B256::default(), 0, &log).unwrap_err();
384        assert_eq!(err, DepositError::InvalidVersion(version));
385    }
386
387    #[test]
388    fn test_decode_deposit_empty_succeeds() {
389        let valid_to = b256!("000000000000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
390        let valid_from = b256!("000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
391        let mut data = vec![0u8; 192];
392        let offset: [u8; 8] = U64::from(32).to_be_bytes();
393        data[24..32].copy_from_slice(&offset);
394        let len: [u8; 8] = U64::from(128).to_be_bytes();
395        data[56..64].copy_from_slice(&len);
396        let log = Log {
397            address: Address::default(),
398            data: LogData::new_unchecked(
399                vec![DEPOSIT_EVENT_ABI_HASH, valid_from, valid_to, B256::default()],
400                Bytes::from(data),
401            ),
402        };
403        let tx = decode_deposit(B256::default(), 0, &log).unwrap();
404        let raw_hex = hex!(
405            "7ef887a0ed428e1c45e1d9561b62834e1a2d3015a0caae3bfdc16b4da059ac885b01a14594ffffffffffffffffffffffffffffffffffffffff94bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb80808080b700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
406        );
407        let expected = Bytes::from(raw_hex);
408        assert_eq!(tx, expected);
409    }
410
411    #[test]
412    fn test_decode_deposit_full_succeeds() {
413        let mut data = vec![0u8; 192];
414        let offset: [u8; 8] = U64::from(32).to_be_bytes();
415        data[24..32].copy_from_slice(&offset);
416        let len: [u8; 8] = U64::from(128).to_be_bytes();
417        data[56..64].copy_from_slice(&len);
418        // Copy the u128 mint value
419        let mint: [u8; 16] = 10_u128.to_be_bytes();
420        data[80..96].copy_from_slice(&mint);
421        // Copy the tx value
422        let value: [u8; 32] = U256::from(100).to_be_bytes();
423        data[96..128].copy_from_slice(&value);
424        // Copy the gas limit
425        let gas: [u8; 8] = 1000_u64.to_be_bytes();
426        data[128..136].copy_from_slice(&gas);
427        // Copy the isCreation flag
428        data[136] = 1;
429        let from = address!("1111111111111111111111111111111111111111");
430        let mut from_bytes = vec![0u8; 32];
431        from_bytes[12..32].copy_from_slice(from.as_slice());
432        let to = address!("2222222222222222222222222222222222222222");
433        let mut to_bytes = vec![0u8; 32];
434        to_bytes[12..32].copy_from_slice(to.as_slice());
435        let log = Log {
436            address: Address::default(),
437            data: LogData::new_unchecked(
438                vec![
439                    DEPOSIT_EVENT_ABI_HASH,
440                    B256::from_slice(&from_bytes),
441                    B256::from_slice(&to_bytes),
442                    B256::default(),
443                ],
444                Bytes::from(data),
445            ),
446        };
447        let tx = decode_deposit(B256::default(), 0, &log).unwrap();
448        let raw_hex = hex!(
449            "7ef875a0ed428e1c45e1d9561b62834e1a2d3015a0caae3bfdc16b4da059ac885b01a145941111111111111111111111111111111111111111800a648203e880b700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
450        );
451        let expected = Bytes::from(raw_hex);
452        assert_eq!(tx, expected);
453    }
454
455    #[test]
456    fn test_unmarshal_deposit_version0_invalid_len() {
457        let data = vec![0u8; 72];
458        let mut tx = TxDeposit::default();
459        let to = address!("5555555555555555555555555555555555555555");
460        let err = unmarshal_deposit_version0(&mut tx, to, &data).unwrap_err();
461        assert_eq!(err, DepositError::UnexpectedOpaqueDataLen(72));
462
463        // Data must have at least length 73
464        let data = vec![0u8; 73];
465        let mut tx = TxDeposit::default();
466        let to = address!("5555555555555555555555555555555555555555");
467        unmarshal_deposit_version0(&mut tx, to, &data).unwrap();
468    }
469
470    #[test]
471    fn test_unmarshal_deposit_version0() {
472        let mut data = vec![0u8; 192];
473        let offset: [u8; 8] = U64::from(32).to_be_bytes();
474        data[24..32].copy_from_slice(&offset);
475        let len: [u8; 8] = U64::from(128).to_be_bytes();
476        data[56..64].copy_from_slice(&len);
477        // Copy the u128 mint value
478        let mint: [u8; 16] = 10_u128.to_be_bytes();
479        data[80..96].copy_from_slice(&mint);
480        // Copy the tx value
481        let value: [u8; 32] = U256::from(100).to_be_bytes();
482        data[96..128].copy_from_slice(&value);
483        // Copy the gas limit
484        let gas: [u8; 8] = 1000_u64.to_be_bytes();
485        data[128..136].copy_from_slice(&gas);
486        // Copy the isCreation flag
487        data[136] = 1;
488        let mut tx = TxDeposit {
489            from: address!("1111111111111111111111111111111111111111"),
490            to: TxKind::Call(address!("2222222222222222222222222222222222222222")),
491            value: U256::from(100),
492            gas_limit: 1000,
493            mint: Some(10),
494            ..Default::default()
495        };
496        let to = address!("5555555555555555555555555555555555555555");
497        unmarshal_deposit_version0(&mut tx, to, &data).unwrap();
498        assert_eq!(tx.to, TxKind::Call(address!("5555555555555555555555555555555555555555")));
499    }
500}