1mod envelope;
4pub use envelope::DepositTxEnvelope;
5
6mod source;
7pub use source::{
8 DepositContextDepositSource, DepositSourceDomain, DepositSourceDomainIdentifier,
9 L1InfoDepositSource, UpgradeDepositSource, UserDepositSource,
10};
11
12use alloc::vec::Vec;
13use alloy_consensus::{Sealable, Transaction, Typed2718};
14use alloy_eips::{
15 eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718},
16 eip2930::AccessList,
17};
18use alloy_primitives::{
19 keccak256, Address, Bytes, ChainId, PrimitiveSignature as Signature, TxHash, TxKind, B256, U256,
20};
21use alloy_rlp::{
22 Buf, BufMut, Decodable, Encodable, Error as DecodeError, Header, EMPTY_STRING_CODE,
23};
24use core::mem;
25
26pub const DEPOSIT_TX_TYPE_ID: u8 = 126; pub trait DepositTransaction: Transaction {
31 fn source_hash(&self) -> Option<B256>;
36
37 fn mint(&self) -> Option<u128>;
42
43 fn is_system_transaction(&self) -> bool;
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
52#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
53#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
54#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
55pub struct TxDeposit {
56 pub source_hash: B256,
58 pub from: Address,
60 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "TxKind::is_create"))]
63 pub to: TxKind,
64 #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity::opt"))]
66 pub mint: Option<u128>,
67 pub value: U256,
69 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity", rename = "gas"))]
71 pub gas_limit: u64,
72 #[cfg_attr(
74 feature = "serde",
75 serde(
76 default,
77 with = "alloy_serde::quantity",
78 rename = "isSystemTx",
79 skip_serializing_if = "core::ops::Not::not"
80 )
81 )]
82 pub is_system_transaction: bool,
83 pub input: Bytes,
86}
87
88impl DepositTransaction for TxDeposit {
89 #[inline]
90 fn source_hash(&self) -> Option<B256> {
91 Some(self.source_hash)
92 }
93
94 #[inline]
95 fn mint(&self) -> Option<u128> {
96 self.mint
97 }
98
99 #[inline]
100 fn is_system_transaction(&self) -> bool {
101 self.is_system_transaction
102 }
103}
104
105impl TxDeposit {
106 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
120 Ok(Self {
121 source_hash: Decodable::decode(buf)?,
122 from: Decodable::decode(buf)?,
123 to: Decodable::decode(buf)?,
124 mint: if *buf.first().ok_or(DecodeError::InputTooShort)? == EMPTY_STRING_CODE {
125 buf.advance(1);
126 None
127 } else {
128 Some(Decodable::decode(buf)?)
129 },
130 value: Decodable::decode(buf)?,
131 gas_limit: Decodable::decode(buf)?,
132 is_system_transaction: Decodable::decode(buf)?,
133 input: Decodable::decode(buf)?,
134 })
135 }
136
137 pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
139 let header = Header::decode(buf)?;
140 if !header.list {
141 return Err(alloy_rlp::Error::UnexpectedString);
142 }
143 let remaining = buf.len();
144
145 if header.payload_length > remaining {
146 return Err(alloy_rlp::Error::InputTooShort);
147 }
148
149 let this = Self::rlp_decode_fields(buf)?;
150
151 if buf.len() + header.payload_length != remaining {
152 return Err(alloy_rlp::Error::UnexpectedLength);
153 }
154
155 Ok(this)
156 }
157
158 pub(crate) fn rlp_encoded_fields_length(&self) -> usize {
161 self.source_hash.length()
162 + self.from.length()
163 + self.to.length()
164 + self.mint.map_or(1, |mint| mint.length())
165 + self.value.length()
166 + self.gas_limit.length()
167 + self.is_system_transaction.length()
168 + self.input.0.length()
169 }
170
171 pub(crate) fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
174 self.source_hash.encode(out);
175 self.from.encode(out);
176 self.to.encode(out);
177 if let Some(mint) = self.mint {
178 mint.encode(out);
179 } else {
180 out.put_u8(EMPTY_STRING_CODE);
181 }
182 self.value.encode(out);
183 self.gas_limit.encode(out);
184 self.is_system_transaction.encode(out);
185 self.input.encode(out);
186 }
187
188 #[inline]
190 pub fn size(&self) -> usize {
191 mem::size_of::<B256>() + mem::size_of::<Address>() + self.to.size() + mem::size_of::<Option<u128>>() + mem::size_of::<U256>() + mem::size_of::<u128>() + mem::size_of::<bool>() + self.input.len() }
200
201 fn rlp_header(&self) -> Header {
203 Header { list: true, payload_length: self.rlp_encoded_fields_length() }
204 }
205
206 pub fn rlp_encode(&self, out: &mut dyn BufMut) {
208 self.rlp_header().encode(out);
209 self.rlp_encode_fields(out);
210 }
211
212 pub fn rlp_encoded_length(&self) -> usize {
214 self.rlp_header().length_with_payload()
215 }
216
217 pub fn eip2718_encoded_length(&self) -> usize {
220 self.rlp_encoded_length() + 1
221 }
222
223 fn network_header(&self) -> Header {
224 Header { list: false, payload_length: self.eip2718_encoded_length() }
225 }
226
227 pub fn network_encoded_length(&self) -> usize {
230 self.network_header().length_with_payload()
231 }
232
233 pub fn network_encode(&self, out: &mut dyn BufMut) {
235 self.network_header().encode(out);
236 self.encode_2718(out);
237 }
238
239 pub fn tx_hash(&self) -> TxHash {
241 let mut buf = Vec::with_capacity(self.eip2718_encoded_length());
242 self.encode_2718(&mut buf);
243 keccak256(&buf)
244 }
245
246 pub fn signature() -> Signature {
249 Signature::new(U256::ZERO, U256::ZERO, false)
250 }
251}
252
253impl Typed2718 for TxDeposit {
254 fn ty(&self) -> u8 {
255 DEPOSIT_TX_TYPE_ID
256 }
257}
258
259impl Transaction for TxDeposit {
260 fn chain_id(&self) -> Option<ChainId> {
261 None
262 }
263
264 fn nonce(&self) -> u64 {
265 0u64
266 }
267
268 fn gas_limit(&self) -> u64 {
269 self.gas_limit
270 }
271
272 fn gas_price(&self) -> Option<u128> {
273 None
274 }
275
276 fn max_fee_per_gas(&self) -> u128 {
277 0
278 }
279
280 fn max_priority_fee_per_gas(&self) -> Option<u128> {
281 None
282 }
283
284 fn max_fee_per_blob_gas(&self) -> Option<u128> {
285 None
286 }
287
288 fn priority_fee_or_price(&self) -> u128 {
289 0
290 }
291
292 fn effective_gas_price(&self, _: Option<u64>) -> u128 {
293 0
294 }
295
296 fn is_dynamic_fee(&self) -> bool {
297 false
298 }
299
300 fn kind(&self) -> TxKind {
301 self.to
302 }
303
304 fn is_create(&self) -> bool {
305 self.to.is_create()
306 }
307
308 fn value(&self) -> U256 {
309 self.value
310 }
311
312 fn input(&self) -> &Bytes {
313 &self.input
314 }
315
316 fn access_list(&self) -> Option<&AccessList> {
317 None
318 }
319
320 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
321 None
322 }
323
324 fn authorization_list(&self) -> Option<&[alloy_eips::eip7702::SignedAuthorization]> {
325 None
326 }
327}
328
329impl Encodable2718 for TxDeposit {
330 fn type_flag(&self) -> Option<u8> {
331 Some(DEPOSIT_TX_TYPE_ID)
332 }
333
334 fn encode_2718_len(&self) -> usize {
335 self.eip2718_encoded_length()
336 }
337
338 fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
339 out.put_u8(DEPOSIT_TX_TYPE_ID);
340 self.rlp_encode(out);
341 }
342}
343
344impl Decodable2718 for TxDeposit {
345 fn typed_decode(ty: u8, data: &mut &[u8]) -> Eip2718Result<Self> {
346 if ty != DEPOSIT_TX_TYPE_ID {
347 return Err(Eip2718Error::UnexpectedType(ty));
348 }
349 let tx = Self::decode(data)?;
350 Ok(tx)
351 }
352
353 fn fallback_decode(data: &mut &[u8]) -> Eip2718Result<Self> {
354 let tx = Self::decode(data)?;
355 Ok(tx)
356 }
357}
358
359impl alloy_rlp::Encodable for TxDeposit {
360 fn encode(&self, out: &mut dyn BufMut) {
361 Header { list: true, payload_length: self.rlp_encoded_fields_length() }.encode(out);
362 self.rlp_encode_fields(out);
363 }
364
365 fn length(&self) -> usize {
366 let payload_length = self.rlp_encoded_fields_length();
367 Header { list: true, payload_length }.length() + payload_length
368 }
369}
370
371impl alloy_rlp::Decodable for TxDeposit {
372 fn decode(data: &mut &[u8]) -> alloy_rlp::Result<Self> {
373 Self::rlp_decode(data)
374 }
375}
376
377impl Sealable for TxDeposit {
378 fn hash_slow(&self) -> B256 {
379 self.tx_hash()
380 }
381}
382
383#[cfg(feature = "serde")]
389pub fn serde_deposit_tx_rpc<T: serde::Serialize, S: serde::Serializer>(
390 value: &T,
391 serializer: S,
392) -> Result<S::Ok, S::Error> {
393 use serde::Serialize;
394
395 #[derive(Serialize)]
396 struct SerdeHelper<'a, T> {
397 #[serde(flatten)]
398 value: &'a T,
399 #[serde(flatten)]
400 signature: Signature,
401 }
402
403 SerdeHelper { value, signature: TxDeposit::signature() }.serialize(serializer)
404}
405
406#[cfg(feature = "serde-bincode-compat")]
408pub mod serde_bincode_compat {
409 use alloc::borrow::Cow;
410 use alloy_primitives::{Address, Bytes, TxKind, B256, U256};
411 use serde::{Deserialize, Deserializer, Serialize, Serializer};
412 use serde_with::{DeserializeAs, SerializeAs};
413
414 #[derive(Debug, Serialize, Deserialize)]
430 pub struct TxDeposit<'a> {
431 source_hash: B256,
432 from: Address,
433 #[serde(default)]
434 to: TxKind,
435 #[serde(default)]
436 mint: Option<u128>,
437 value: U256,
438 gas_limit: u64,
439 is_system_transaction: bool,
440 input: Cow<'a, Bytes>,
441 }
442
443 impl<'a> From<&'a super::TxDeposit> for TxDeposit<'a> {
444 fn from(value: &'a super::TxDeposit) -> Self {
445 Self {
446 source_hash: value.source_hash,
447 from: value.from,
448 to: value.to,
449 mint: value.mint,
450 value: value.value,
451 gas_limit: value.gas_limit,
452 is_system_transaction: value.is_system_transaction,
453 input: Cow::Borrowed(&value.input),
454 }
455 }
456 }
457
458 impl<'a> From<TxDeposit<'a>> for super::TxDeposit {
459 fn from(value: TxDeposit<'a>) -> Self {
460 Self {
461 source_hash: value.source_hash,
462 from: value.from,
463 to: value.to,
464 mint: value.mint,
465 value: value.value,
466 gas_limit: value.gas_limit,
467 is_system_transaction: value.is_system_transaction,
468 input: value.input.into_owned(),
469 }
470 }
471 }
472
473 impl SerializeAs<super::TxDeposit> for TxDeposit<'_> {
474 fn serialize_as<S>(source: &super::TxDeposit, serializer: S) -> Result<S::Ok, S::Error>
475 where
476 S: Serializer,
477 {
478 TxDeposit::from(source).serialize(serializer)
479 }
480 }
481
482 impl<'de> DeserializeAs<'de, super::TxDeposit> for TxDeposit<'de> {
483 fn deserialize_as<D>(deserializer: D) -> Result<super::TxDeposit, D::Error>
484 where
485 D: Deserializer<'de>,
486 {
487 TxDeposit::deserialize(deserializer).map(Into::into)
488 }
489 }
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495 use alloy_primitives::hex;
496 use alloy_rlp::BytesMut;
497
498 #[test]
499 fn test_deposit_transaction_trait() {
500 let tx = TxDeposit {
501 source_hash: B256::with_last_byte(42),
502 from: Address::default(),
503 to: TxKind::default(),
504 mint: Some(100),
505 value: U256::from(1000),
506 gas_limit: 50000,
507 is_system_transaction: true,
508 input: Bytes::default(),
509 };
510
511 assert_eq!(tx.source_hash(), Some(B256::with_last_byte(42)));
512 assert_eq!(tx.mint(), Some(100));
513 assert!(tx.is_system_transaction());
514 }
515
516 #[test]
517 fn test_deposit_transaction_without_mint() {
518 let tx = TxDeposit {
519 source_hash: B256::default(),
520 from: Address::default(),
521 to: TxKind::default(),
522 mint: None,
523 value: U256::default(),
524 gas_limit: 50000,
525 is_system_transaction: false,
526 input: Bytes::default(),
527 };
528
529 assert_eq!(tx.source_hash(), Some(B256::default()));
530 assert_eq!(tx.mint(), None);
531 assert!(!tx.is_system_transaction());
532 }
533
534 #[test]
535 fn test_deposit_transaction_to_contract() {
536 let contract_address = Address::with_last_byte(0xFF);
537 let tx = TxDeposit {
538 source_hash: B256::default(),
539 from: Address::default(),
540 to: TxKind::Call(contract_address),
541 mint: Some(200),
542 value: U256::from(500),
543 gas_limit: 100000,
544 is_system_transaction: false,
545 input: Bytes::from_static(&[1, 2, 3]),
546 };
547
548 assert_eq!(tx.source_hash(), Some(B256::default()));
549 assert_eq!(tx.mint(), Some(200));
550 assert!(!tx.is_system_transaction());
551 assert_eq!(tx.kind(), TxKind::Call(contract_address));
552 }
553
554 #[test]
555 fn test_rlp_roundtrip() {
556 let bytes = Bytes::from_static(&hex!("7ef9015aa044bae9d41b8380d781187b426c6fe43df5fb2fb57bd4466ef6a701e1f01e015694deaddeaddeaddeaddeaddeaddeaddeaddead000194420000000000000000000000000000000000001580808408f0d18001b90104015d8eb900000000000000000000000000000000000000000000000000000000008057650000000000000000000000000000000000000000000000000000000063d96d10000000000000000000000000000000000000000000000000000000000009f35273d89754a1e0387b89520d989d3be9c37c1f32495a88faf1ea05c61121ab0d1900000000000000000000000000000000000000000000000000000000000000010000000000000000000000002d679b567db6187c0c8323fa982cfb88b74dbcc7000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240"));
557 let tx_a = TxDeposit::decode(&mut bytes[1..].as_ref()).unwrap();
558 let mut buf_a = BytesMut::default();
559 tx_a.encode(&mut buf_a);
560 assert_eq!(&buf_a[..], &bytes[1..]);
561 }
562
563 #[test]
564 fn test_encode_decode_fields() {
565 let original = TxDeposit {
566 source_hash: B256::default(),
567 from: Address::default(),
568 to: TxKind::default(),
569 mint: Some(100),
570 value: U256::default(),
571 gas_limit: 50000,
572 is_system_transaction: true,
573 input: Bytes::default(),
574 };
575
576 let mut buffer = BytesMut::new();
577 original.rlp_encode_fields(&mut buffer);
578 let decoded = TxDeposit::rlp_decode_fields(&mut &buffer[..]).expect("Failed to decode");
579
580 assert_eq!(original, decoded);
581 }
582
583 #[test]
584 fn test_encode_with_and_without_header() {
585 let tx_deposit = TxDeposit {
586 source_hash: B256::default(),
587 from: Address::default(),
588 to: TxKind::default(),
589 mint: Some(100),
590 value: U256::default(),
591 gas_limit: 50000,
592 is_system_transaction: true,
593 input: Bytes::default(),
594 };
595
596 let mut buffer_with_header = BytesMut::new();
597 tx_deposit.encode(&mut buffer_with_header);
598
599 let mut buffer_without_header = BytesMut::new();
600 tx_deposit.rlp_encode_fields(&mut buffer_without_header);
601
602 assert!(buffer_with_header.len() > buffer_without_header.len());
603 }
604
605 #[test]
606 fn test_payload_length() {
607 let tx_deposit = TxDeposit {
608 source_hash: B256::default(),
609 from: Address::default(),
610 to: TxKind::default(),
611 mint: Some(100),
612 value: U256::default(),
613 gas_limit: 50000,
614 is_system_transaction: true,
615 input: Bytes::default(),
616 };
617
618 assert!(tx_deposit.size() > tx_deposit.rlp_encoded_fields_length());
619 }
620
621 #[test]
622 fn test_encode_inner_with_and_without_header() {
623 let tx_deposit = TxDeposit {
624 source_hash: B256::default(),
625 from: Address::default(),
626 to: TxKind::default(),
627 mint: Some(100),
628 value: U256::default(),
629 gas_limit: 50000,
630 is_system_transaction: true,
631 input: Bytes::default(),
632 };
633
634 let mut buffer_with_header = BytesMut::new();
635 tx_deposit.network_encode(&mut buffer_with_header);
636
637 let mut buffer_without_header = BytesMut::new();
638 tx_deposit.encode_2718(&mut buffer_without_header);
639
640 assert!(buffer_with_header.len() > buffer_without_header.len());
641 }
642
643 #[test]
644 fn test_payload_length_header() {
645 let tx_deposit = TxDeposit {
646 source_hash: B256::default(),
647 from: Address::default(),
648 to: TxKind::default(),
649 mint: Some(100),
650 value: U256::default(),
651 gas_limit: 50000,
652 is_system_transaction: true,
653 input: Bytes::default(),
654 };
655
656 let total_len = tx_deposit.network_encoded_length();
657 let len_without_header = tx_deposit.eip2718_encoded_length();
658
659 assert!(total_len > len_without_header);
660 }
661
662 #[cfg(feature = "serde-bincode-compat")]
663 #[test]
664 fn test_tx_deposit_bincode_roundtrip() {
665 use arbitrary::Arbitrary;
666 use rand::Rng;
667
668 #[serde_with::serde_as]
669 #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
670 struct Data {
671 #[serde_as(as = "serde_bincode_compat::TxDeposit")]
672 transaction: TxDeposit,
673 }
674
675 let mut bytes = [0u8; 1024];
676 rand::thread_rng().fill(bytes.as_mut_slice());
677 let data = Data {
678 transaction: TxDeposit::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(),
679 };
680
681 let encoded = bincode::serialize(&data).unwrap();
682 let decoded: Data = bincode::deserialize(&encoded).unwrap();
683 assert_eq!(decoded, data);
684 }
685}