1use crate::{SignableTransaction, Signed, Transaction, TxType};
2
3use alloc::vec::Vec;
4use alloy_eips::{
5 eip2930::AccessList, eip4844::DATA_GAS_PER_BLOB, eip7702::SignedAuthorization, Typed2718,
6};
7use alloy_primitives::{Address, Bytes, ChainId, Signature, TxKind, B256, U256};
8use alloy_rlp::{BufMut, Decodable, Encodable, Header};
9use core::mem;
10
11#[doc(inline)]
12pub use alloy_eips::eip4844::BlobTransactionSidecar;
13
14#[cfg(feature = "kzg")]
15#[doc(inline)]
16pub use alloy_eips::eip4844::BlobTransactionValidationError;
17
18use super::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx};
19
20#[derive(Clone, Debug, PartialEq, Eq, Hash)]
27#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
28#[cfg_attr(feature = "serde", derive(serde::Serialize))]
29#[cfg_attr(feature = "serde", serde(untagged))]
30#[doc(alias = "Eip4844TransactionVariant")]
31pub enum TxEip4844Variant {
32 TxEip4844(TxEip4844),
34 TxEip4844WithSidecar(TxEip4844WithSidecar),
36}
37
38#[cfg(feature = "serde")]
39impl<'de> serde::Deserialize<'de> for TxEip4844Variant {
40 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
41 where
42 D: serde::Deserializer<'de>,
43 {
44 #[derive(serde::Deserialize)]
45 struct TxEip4844SerdeHelper {
46 #[serde(flatten)]
47 #[doc(alias = "transaction")]
48 tx: TxEip4844,
49 #[serde(flatten)]
50 sidecar: Option<BlobTransactionSidecar>,
51 }
52
53 let tx = TxEip4844SerdeHelper::deserialize(deserializer)?;
54
55 if let Some(sidecar) = tx.sidecar {
56 Ok(TxEip4844WithSidecar::from_tx_and_sidecar(tx.tx, sidecar).into())
57 } else {
58 Ok(tx.tx.into())
59 }
60 }
61}
62
63impl From<Signed<TxEip4844>> for Signed<TxEip4844Variant> {
64 fn from(value: Signed<TxEip4844>) -> Self {
65 let (tx, signature, hash) = value.into_parts();
66 Self::new_unchecked(TxEip4844Variant::TxEip4844(tx), signature, hash)
67 }
68}
69
70impl From<Signed<TxEip4844WithSidecar>> for Signed<TxEip4844Variant> {
71 fn from(value: Signed<TxEip4844WithSidecar>) -> Self {
72 let (tx, signature, hash) = value.into_parts();
73 Self::new_unchecked(TxEip4844Variant::TxEip4844WithSidecar(tx), signature, hash)
74 }
75}
76
77impl From<TxEip4844WithSidecar> for TxEip4844Variant {
78 fn from(tx: TxEip4844WithSidecar) -> Self {
79 Self::TxEip4844WithSidecar(tx)
80 }
81}
82
83impl From<TxEip4844> for TxEip4844Variant {
84 fn from(tx: TxEip4844) -> Self {
85 Self::TxEip4844(tx)
86 }
87}
88
89impl From<(TxEip4844, BlobTransactionSidecar)> for TxEip4844Variant {
90 fn from((tx, sidecar): (TxEip4844, BlobTransactionSidecar)) -> Self {
91 TxEip4844WithSidecar::from_tx_and_sidecar(tx, sidecar).into()
92 }
93}
94
95impl From<TxEip4844Variant> for TxEip4844 {
96 fn from(tx: TxEip4844Variant) -> Self {
97 match tx {
98 TxEip4844Variant::TxEip4844(tx) => tx,
99 TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx,
100 }
101 }
102}
103
104impl AsRef<TxEip4844> for TxEip4844Variant {
105 fn as_ref(&self) -> &TxEip4844 {
106 match self {
107 Self::TxEip4844(tx) => tx,
108 Self::TxEip4844WithSidecar(tx) => &tx.tx,
109 }
110 }
111}
112
113impl AsMut<TxEip4844> for TxEip4844Variant {
114 fn as_mut(&mut self) -> &mut TxEip4844 {
115 match self {
116 Self::TxEip4844(tx) => tx,
117 Self::TxEip4844WithSidecar(tx) => &mut tx.tx,
118 }
119 }
120}
121
122impl AsRef<Self> for TxEip4844 {
123 fn as_ref(&self) -> &Self {
124 self
125 }
126}
127
128impl AsMut<Self> for TxEip4844 {
129 fn as_mut(&mut self) -> &mut Self {
130 self
131 }
132}
133
134impl TxEip4844Variant {
135 #[cfg(feature = "kzg")]
139 pub fn validate(
140 &self,
141 proof_settings: &c_kzg::KzgSettings,
142 ) -> Result<(), BlobTransactionValidationError> {
143 match self {
144 Self::TxEip4844(_) => Err(BlobTransactionValidationError::MissingSidecar),
145 Self::TxEip4844WithSidecar(tx) => tx.validate_blob(proof_settings),
146 }
147 }
148
149 #[doc(alias = "transaction_type")]
151 pub const fn tx_type() -> TxType {
152 TxType::Eip4844
153 }
154
155 #[doc(alias = "transaction")]
157 pub const fn tx(&self) -> &TxEip4844 {
158 match self {
159 Self::TxEip4844(tx) => tx,
160 Self::TxEip4844WithSidecar(tx) => tx.tx(),
161 }
162 }
163
164 #[inline]
166 pub fn size(&self) -> usize {
167 match self {
168 Self::TxEip4844(tx) => tx.size(),
169 Self::TxEip4844WithSidecar(tx) => tx.size(),
170 }
171 }
172
173 pub fn try_into_4844_with_sidecar(self) -> Result<TxEip4844WithSidecar, Self> {
176 match self {
177 Self::TxEip4844WithSidecar(tx) => Ok(tx),
178 _ => Err(self),
179 }
180 }
181}
182
183impl Transaction for TxEip4844Variant {
184 #[inline]
185 fn chain_id(&self) -> Option<ChainId> {
186 match self {
187 Self::TxEip4844(tx) => Some(tx.chain_id),
188 Self::TxEip4844WithSidecar(tx) => Some(tx.tx().chain_id),
189 }
190 }
191
192 #[inline]
193 fn nonce(&self) -> u64 {
194 match self {
195 Self::TxEip4844(tx) => tx.nonce,
196 Self::TxEip4844WithSidecar(tx) => tx.tx().nonce,
197 }
198 }
199
200 #[inline]
201 fn gas_limit(&self) -> u64 {
202 match self {
203 Self::TxEip4844(tx) => tx.gas_limit,
204 Self::TxEip4844WithSidecar(tx) => tx.tx().gas_limit,
205 }
206 }
207
208 #[inline]
209 fn gas_price(&self) -> Option<u128> {
210 None
211 }
212
213 #[inline]
214 fn max_fee_per_gas(&self) -> u128 {
215 match self {
216 Self::TxEip4844(tx) => tx.max_fee_per_gas(),
217 Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_gas(),
218 }
219 }
220
221 #[inline]
222 fn max_priority_fee_per_gas(&self) -> Option<u128> {
223 match self {
224 Self::TxEip4844(tx) => tx.max_priority_fee_per_gas(),
225 Self::TxEip4844WithSidecar(tx) => tx.max_priority_fee_per_gas(),
226 }
227 }
228
229 #[inline]
230 fn max_fee_per_blob_gas(&self) -> Option<u128> {
231 match self {
232 Self::TxEip4844(tx) => tx.max_fee_per_blob_gas(),
233 Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_blob_gas(),
234 }
235 }
236
237 #[inline]
238 fn priority_fee_or_price(&self) -> u128 {
239 match self {
240 Self::TxEip4844(tx) => tx.priority_fee_or_price(),
241 Self::TxEip4844WithSidecar(tx) => tx.priority_fee_or_price(),
242 }
243 }
244
245 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
246 match self {
247 Self::TxEip4844(tx) => tx.effective_gas_price(base_fee),
248 Self::TxEip4844WithSidecar(tx) => tx.effective_gas_price(base_fee),
249 }
250 }
251
252 #[inline]
253 fn is_dynamic_fee(&self) -> bool {
254 match self {
255 Self::TxEip4844(tx) => tx.is_dynamic_fee(),
256 Self::TxEip4844WithSidecar(tx) => tx.is_dynamic_fee(),
257 }
258 }
259
260 #[inline]
261 fn kind(&self) -> TxKind {
262 match self {
263 Self::TxEip4844(tx) => tx.to,
264 Self::TxEip4844WithSidecar(tx) => tx.tx.to,
265 }
266 .into()
267 }
268
269 #[inline]
270 fn is_create(&self) -> bool {
271 false
272 }
273
274 #[inline]
275 fn value(&self) -> U256 {
276 match self {
277 Self::TxEip4844(tx) => tx.value,
278 Self::TxEip4844WithSidecar(tx) => tx.tx.value,
279 }
280 }
281
282 #[inline]
283 fn input(&self) -> &Bytes {
284 match self {
285 Self::TxEip4844(tx) => tx.input(),
286 Self::TxEip4844WithSidecar(tx) => tx.tx().input(),
287 }
288 }
289
290 #[inline]
291 fn access_list(&self) -> Option<&AccessList> {
292 match self {
293 Self::TxEip4844(tx) => tx.access_list(),
294 Self::TxEip4844WithSidecar(tx) => tx.access_list(),
295 }
296 }
297
298 #[inline]
299 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
300 match self {
301 Self::TxEip4844(tx) => tx.blob_versioned_hashes(),
302 Self::TxEip4844WithSidecar(tx) => tx.blob_versioned_hashes(),
303 }
304 }
305
306 #[inline]
307 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
308 None
309 }
310}
311impl Typed2718 for TxEip4844 {
312 fn ty(&self) -> u8 {
313 TxType::Eip4844 as u8
314 }
315}
316
317impl RlpEcdsaEncodableTx for TxEip4844Variant {
318 fn rlp_encoded_fields_length(&self) -> usize {
319 match self {
320 Self::TxEip4844(inner) => inner.rlp_encoded_fields_length(),
321 Self::TxEip4844WithSidecar(inner) => inner.rlp_encoded_fields_length(),
322 }
323 }
324
325 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
326 match self {
327 Self::TxEip4844(inner) => inner.rlp_encode_fields(out),
328 Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_fields(out),
329 }
330 }
331
332 fn rlp_header_signed(&self, signature: &Signature) -> Header {
333 match self {
334 Self::TxEip4844(inner) => inner.rlp_header_signed(signature),
335 Self::TxEip4844WithSidecar(inner) => inner.rlp_header_signed(signature),
336 }
337 }
338
339 fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
340 match self {
341 Self::TxEip4844(inner) => inner.rlp_encode_signed(signature, out),
342 Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_signed(signature, out),
343 }
344 }
345
346 fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
347 match self {
348 Self::TxEip4844(inner) => inner.tx_hash_with_type(signature, ty),
349 Self::TxEip4844WithSidecar(inner) => inner.tx_hash_with_type(signature, ty),
350 }
351 }
352}
353
354impl RlpEcdsaDecodableTx for TxEip4844Variant {
355 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
356
357 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
358 let needle = &mut &**buf;
359
360 let trial = &mut &**buf;
363
364 if Header::decode(needle).is_ok_and(|h| h.list) {
371 if let Ok(tx) = TxEip4844WithSidecar::rlp_decode_fields(trial) {
372 *buf = *trial;
373 return Ok(tx.into());
374 }
375 }
376 TxEip4844::rlp_decode_fields(buf).map(Into::into)
377 }
378
379 fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
380 let needle = &mut &**buf;
383
384 let trial = &mut &**buf;
387
388 Header::decode(needle)?;
390
391 if Header::decode(needle).is_ok_and(|h| h.list) {
398 if let Ok((tx, signature)) = TxEip4844WithSidecar::rlp_decode_with_signature(trial) {
399 *buf = *trial;
402 return Ok((tx.into(), signature));
403 }
404 }
405 TxEip4844::rlp_decode_with_signature(buf).map(|(tx, signature)| (tx.into(), signature))
406 }
407}
408
409impl Typed2718 for TxEip4844Variant {
410 fn ty(&self) -> u8 {
411 TxType::Eip4844 as u8
412 }
413}
414
415impl SignableTransaction<Signature> for TxEip4844Variant {
416 fn set_chain_id(&mut self, chain_id: ChainId) {
417 match self {
418 Self::TxEip4844(inner) => {
419 inner.set_chain_id(chain_id);
420 }
421 Self::TxEip4844WithSidecar(inner) => {
422 inner.set_chain_id(chain_id);
423 }
424 }
425 }
426
427 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
428 self.tx().encode_for_signing(out);
434 }
435
436 fn payload_len_for_signature(&self) -> usize {
437 self.tx().payload_len_for_signature()
438 }
439}
440
441#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
445#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
446#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
447#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
448#[doc(alias = "Eip4844Transaction", alias = "TransactionEip4844", alias = "Eip4844Tx")]
449pub struct TxEip4844 {
450 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
452 pub chain_id: ChainId,
453 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
455 pub nonce: u64,
456 #[cfg_attr(
462 feature = "serde",
463 serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
464 )]
465 pub gas_limit: u64,
466 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
478 pub max_fee_per_gas: u128,
479 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
487 pub max_priority_fee_per_gas: u128,
488 pub to: Address,
490 pub value: U256,
495 pub access_list: AccessList,
501
502 pub blob_versioned_hashes: Vec<B256>,
504
505 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
509 pub max_fee_per_blob_gas: u128,
510
511 pub input: Bytes,
517}
518
519impl TxEip4844 {
520 #[inline]
522 pub fn blob_gas(&self) -> u64 {
523 self.blob_versioned_hashes.len() as u64 * DATA_GAS_PER_BLOB
525 }
526
527 #[cfg(feature = "kzg")]
541 pub fn validate_blob(
542 &self,
543 sidecar: &BlobTransactionSidecar,
544 proof_settings: &c_kzg::KzgSettings,
545 ) -> Result<(), BlobTransactionValidationError> {
546 sidecar.validate(&self.blob_versioned_hashes, proof_settings)
547 }
548
549 #[doc(alias = "transaction_type")]
551 pub const fn tx_type() -> TxType {
552 TxType::Eip4844
553 }
554
555 #[inline]
557 pub fn size(&self) -> usize {
558 mem::size_of::<ChainId>() + mem::size_of::<u64>() + mem::size_of::<u64>() + mem::size_of::<u128>() + mem::size_of::<u128>() + mem::size_of::<Address>() + mem::size_of::<U256>() + self.access_list.size() + self.input.len() + self.blob_versioned_hashes.capacity() * mem::size_of::<B256>() + mem::size_of::<u128>() }
570}
571
572impl RlpEcdsaEncodableTx for TxEip4844 {
573 fn rlp_encoded_fields_length(&self) -> usize {
574 self.chain_id.length()
575 + self.nonce.length()
576 + self.gas_limit.length()
577 + self.max_fee_per_gas.length()
578 + self.max_priority_fee_per_gas.length()
579 + self.to.length()
580 + self.value.length()
581 + self.access_list.length()
582 + self.blob_versioned_hashes.length()
583 + self.max_fee_per_blob_gas.length()
584 + self.input.0.length()
585 }
586
587 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
588 self.chain_id.encode(out);
589 self.nonce.encode(out);
590 self.max_priority_fee_per_gas.encode(out);
591 self.max_fee_per_gas.encode(out);
592 self.gas_limit.encode(out);
593 self.to.encode(out);
594 self.value.encode(out);
595 self.input.0.encode(out);
596 self.access_list.encode(out);
597 self.max_fee_per_blob_gas.encode(out);
598 self.blob_versioned_hashes.encode(out);
599 }
600}
601
602impl RlpEcdsaDecodableTx for TxEip4844 {
603 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
604
605 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
606 Ok(Self {
607 chain_id: Decodable::decode(buf)?,
608 nonce: Decodable::decode(buf)?,
609 max_priority_fee_per_gas: Decodable::decode(buf)?,
610 max_fee_per_gas: Decodable::decode(buf)?,
611 gas_limit: Decodable::decode(buf)?,
612 to: Decodable::decode(buf)?,
613 value: Decodable::decode(buf)?,
614 input: Decodable::decode(buf)?,
615 access_list: Decodable::decode(buf)?,
616 max_fee_per_blob_gas: Decodable::decode(buf)?,
617 blob_versioned_hashes: Decodable::decode(buf)?,
618 })
619 }
620}
621
622impl SignableTransaction<Signature> for TxEip4844 {
623 fn set_chain_id(&mut self, chain_id: ChainId) {
624 self.chain_id = chain_id;
625 }
626
627 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
628 out.put_u8(Self::tx_type() as u8);
629 self.encode(out);
630 }
631
632 fn payload_len_for_signature(&self) -> usize {
633 self.length() + 1
634 }
635}
636
637impl Transaction for TxEip4844 {
638 #[inline]
639 fn chain_id(&self) -> Option<ChainId> {
640 Some(self.chain_id)
641 }
642
643 #[inline]
644 fn nonce(&self) -> u64 {
645 self.nonce
646 }
647
648 #[inline]
649 fn gas_limit(&self) -> u64 {
650 self.gas_limit
651 }
652
653 #[inline]
654 fn gas_price(&self) -> Option<u128> {
655 None
656 }
657
658 #[inline]
659 fn max_fee_per_gas(&self) -> u128 {
660 self.max_fee_per_gas
661 }
662
663 #[inline]
664 fn max_priority_fee_per_gas(&self) -> Option<u128> {
665 Some(self.max_priority_fee_per_gas)
666 }
667
668 #[inline]
669 fn max_fee_per_blob_gas(&self) -> Option<u128> {
670 Some(self.max_fee_per_blob_gas)
671 }
672
673 #[inline]
674 fn priority_fee_or_price(&self) -> u128 {
675 self.max_priority_fee_per_gas
676 }
677
678 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
679 base_fee.map_or(self.max_fee_per_gas, |base_fee| {
680 let tip = self.max_fee_per_gas.saturating_sub(base_fee as u128);
683 if tip > self.max_priority_fee_per_gas {
684 self.max_priority_fee_per_gas + base_fee as u128
685 } else {
686 self.max_fee_per_gas
688 }
689 })
690 }
691
692 #[inline]
693 fn is_dynamic_fee(&self) -> bool {
694 true
695 }
696
697 #[inline]
698 fn kind(&self) -> TxKind {
699 self.to.into()
700 }
701
702 #[inline]
703 fn is_create(&self) -> bool {
704 false
705 }
706
707 #[inline]
708 fn value(&self) -> U256 {
709 self.value
710 }
711
712 #[inline]
713 fn input(&self) -> &Bytes {
714 &self.input
715 }
716
717 #[inline]
718 fn access_list(&self) -> Option<&AccessList> {
719 Some(&self.access_list)
720 }
721
722 #[inline]
723 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
724 Some(&self.blob_versioned_hashes)
725 }
726
727 #[inline]
728 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
729 None
730 }
731}
732
733impl Encodable for TxEip4844 {
734 fn encode(&self, out: &mut dyn BufMut) {
735 self.rlp_encode(out);
736 }
737
738 fn length(&self) -> usize {
739 self.rlp_encoded_length()
740 }
741}
742
743impl Decodable for TxEip4844 {
744 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
745 Self::rlp_decode(buf)
746 }
747}
748
749impl From<TxEip4844WithSidecar> for TxEip4844 {
750 fn from(tx_with_sidecar: TxEip4844WithSidecar) -> Self {
752 tx_with_sidecar.tx
753 }
754}
755
756#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
766#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
767#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
768#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
769#[doc(alias = "Eip4844TransactionWithSidecar", alias = "Eip4844TxWithSidecar")]
770pub struct TxEip4844WithSidecar {
771 #[cfg_attr(feature = "serde", serde(flatten))]
773 #[doc(alias = "transaction")]
774 pub tx: TxEip4844,
775 #[cfg_attr(feature = "serde", serde(flatten))]
777 pub sidecar: BlobTransactionSidecar,
778}
779
780impl TxEip4844WithSidecar {
781 #[doc(alias = "from_transaction_and_sidecar")]
783 pub const fn from_tx_and_sidecar(tx: TxEip4844, sidecar: BlobTransactionSidecar) -> Self {
784 Self { tx, sidecar }
785 }
786
787 #[cfg(feature = "kzg")]
791 pub fn validate_blob(
792 &self,
793 proof_settings: &c_kzg::KzgSettings,
794 ) -> Result<(), BlobTransactionValidationError> {
795 self.tx.validate_blob(&self.sidecar, proof_settings)
796 }
797
798 #[doc(alias = "transaction_type")]
800 pub const fn tx_type() -> TxType {
801 TxEip4844::tx_type()
802 }
803
804 #[doc(alias = "transaction")]
806 pub const fn tx(&self) -> &TxEip4844 {
807 &self.tx
808 }
809
810 pub const fn sidecar(&self) -> &BlobTransactionSidecar {
812 &self.sidecar
813 }
814
815 pub fn into_sidecar(self) -> BlobTransactionSidecar {
817 self.sidecar
818 }
819
820 pub fn into_parts(self) -> (TxEip4844, BlobTransactionSidecar) {
823 (self.tx, self.sidecar)
824 }
825
826 #[inline]
828 pub fn size(&self) -> usize {
829 self.tx.size() + self.sidecar.size()
830 }
831}
832
833impl SignableTransaction<Signature> for TxEip4844WithSidecar {
834 fn set_chain_id(&mut self, chain_id: ChainId) {
835 self.tx.chain_id = chain_id;
836 }
837
838 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
839 self.tx.encode_for_signing(out);
845 }
846
847 fn payload_len_for_signature(&self) -> usize {
848 self.tx.payload_len_for_signature()
851 }
852}
853
854impl Transaction for TxEip4844WithSidecar {
855 #[inline]
856 fn chain_id(&self) -> Option<ChainId> {
857 self.tx.chain_id()
858 }
859
860 #[inline]
861 fn nonce(&self) -> u64 {
862 self.tx.nonce()
863 }
864
865 #[inline]
866 fn gas_limit(&self) -> u64 {
867 self.tx.gas_limit()
868 }
869
870 #[inline]
871 fn gas_price(&self) -> Option<u128> {
872 self.tx.gas_price()
873 }
874
875 #[inline]
876 fn max_fee_per_gas(&self) -> u128 {
877 self.tx.max_fee_per_gas()
878 }
879
880 #[inline]
881 fn max_priority_fee_per_gas(&self) -> Option<u128> {
882 self.tx.max_priority_fee_per_gas()
883 }
884
885 #[inline]
886 fn max_fee_per_blob_gas(&self) -> Option<u128> {
887 self.tx.max_fee_per_blob_gas()
888 }
889
890 #[inline]
891 fn priority_fee_or_price(&self) -> u128 {
892 self.tx.priority_fee_or_price()
893 }
894
895 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
896 self.tx.effective_gas_price(base_fee)
897 }
898
899 #[inline]
900 fn is_dynamic_fee(&self) -> bool {
901 self.tx.is_dynamic_fee()
902 }
903
904 #[inline]
905 fn kind(&self) -> TxKind {
906 self.tx.kind()
907 }
908
909 #[inline]
910 fn is_create(&self) -> bool {
911 false
912 }
913
914 #[inline]
915 fn value(&self) -> U256 {
916 self.tx.value()
917 }
918
919 #[inline]
920 fn input(&self) -> &Bytes {
921 self.tx.input()
922 }
923
924 #[inline]
925 fn access_list(&self) -> Option<&AccessList> {
926 Some(&self.tx.access_list)
927 }
928
929 #[inline]
930 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
931 self.tx.blob_versioned_hashes()
932 }
933
934 #[inline]
935 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
936 None
937 }
938}
939
940impl Typed2718 for TxEip4844WithSidecar {
941 fn ty(&self) -> u8 {
942 TxType::Eip4844 as u8
943 }
944}
945
946impl RlpEcdsaEncodableTx for TxEip4844WithSidecar {
947 fn rlp_encoded_fields_length(&self) -> usize {
948 self.sidecar.rlp_encoded_fields_length() + self.tx.rlp_encoded_length()
949 }
950
951 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
952 self.tx.rlp_encode(out);
953 self.sidecar.rlp_encode_fields(out);
954 }
955
956 fn rlp_header_signed(&self, signature: &Signature) -> Header {
957 let payload_length = self.tx.rlp_encoded_length_with_signature(signature)
958 + self.sidecar.rlp_encoded_fields_length();
959 Header { list: true, payload_length }
960 }
961
962 fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
963 self.rlp_header_signed(signature).encode(out);
964 self.tx.rlp_encode_signed(signature, out);
965 self.sidecar.rlp_encode_fields(out);
966 }
967
968 fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
969 self.tx.tx_hash_with_type(signature, ty)
971 }
972}
973
974impl RlpEcdsaDecodableTx for TxEip4844WithSidecar {
975 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
976
977 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
978 let tx = TxEip4844::rlp_decode(buf)?;
979 let sidecar = BlobTransactionSidecar::rlp_decode_fields(buf)?;
980 Ok(Self { tx, sidecar })
981 }
982
983 fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
984 let header = Header::decode(buf)?;
985 if !header.list {
986 return Err(alloy_rlp::Error::UnexpectedString);
987 }
988 let remaining = buf.len();
989
990 let (tx, signature) = TxEip4844::rlp_decode_with_signature(buf)?;
991 let sidecar = BlobTransactionSidecar::rlp_decode_fields(buf)?;
992
993 if buf.len() + header.payload_length != remaining {
994 return Err(alloy_rlp::Error::UnexpectedLength);
995 }
996
997 Ok((Self { tx, sidecar }, signature))
998 }
999}
1000
1001#[cfg(test)]
1002mod tests {
1003 use super::{BlobTransactionSidecar, TxEip4844, TxEip4844WithSidecar};
1004 use crate::{transaction::eip4844::TxEip4844Variant, SignableTransaction, TxEnvelope};
1005 use alloy_eips::eip2930::AccessList;
1006 use alloy_primitives::{address, b256, bytes, Signature, U256};
1007 use alloy_rlp::{Decodable, Encodable};
1008
1009 #[test]
1010 fn different_sidecar_same_hash() {
1011 let tx = TxEip4844 {
1014 chain_id: 1,
1015 nonce: 1,
1016 max_priority_fee_per_gas: 1,
1017 max_fee_per_gas: 1,
1018 gas_limit: 1,
1019 to: Default::default(),
1020 value: U256::from(1),
1021 access_list: Default::default(),
1022 blob_versioned_hashes: vec![Default::default()],
1023 max_fee_per_blob_gas: 1,
1024 input: Default::default(),
1025 };
1026 let sidecar = BlobTransactionSidecar {
1027 blobs: vec![[2; 131072].into()],
1028 commitments: vec![[3; 48].into()],
1029 proofs: vec![[4; 48].into()],
1030 };
1031 let mut tx = TxEip4844WithSidecar { tx, sidecar };
1032 let signature = Signature::test_signature();
1033
1034 let expected_signed = tx.clone().into_signed(signature);
1036
1037 tx.sidecar = BlobTransactionSidecar {
1039 blobs: vec![[1; 131072].into()],
1040 commitments: vec![[1; 48].into()],
1041 proofs: vec![[1; 48].into()],
1042 };
1043
1044 let actual_signed = tx.into_signed(signature);
1046
1047 assert_eq!(expected_signed.hash(), actual_signed.hash());
1049
1050 let expected_envelope: TxEnvelope = expected_signed.into();
1052 let actual_envelope: TxEnvelope = actual_signed.into();
1053
1054 let len = expected_envelope.length();
1056 let mut buf = Vec::with_capacity(len);
1057 expected_envelope.encode(&mut buf);
1058 assert_eq!(buf.len(), len);
1059
1060 assert_eq!(buf.len(), actual_envelope.length());
1063
1064 let decoded = TxEnvelope::decode(&mut &buf[..]).unwrap();
1066 assert_eq!(decoded, expected_envelope);
1067 }
1068
1069 #[test]
1070 fn test_4844_variant_into_signed_correct_hash() {
1071 let tx =
1073 TxEip4844 {
1074 chain_id: 1,
1075 nonce: 15435,
1076 gas_limit: 8000000,
1077 max_fee_per_gas: 10571233596,
1078 max_priority_fee_per_gas: 1000000000,
1079 to: address!("a8cb082a5a689e0d594d7da1e2d72a3d63adc1bd"),
1080 value: U256::ZERO,
1081 access_list: AccessList::default(),
1082 blob_versioned_hashes: vec![
1083 b256!("01e5276d91ac1ddb3b1c2d61295211220036e9a04be24c00f76916cc2659d004"),
1084 b256!("0128eb58aff09fd3a7957cd80aa86186d5849569997cdfcfa23772811b706cc2"),
1085 ],
1086 max_fee_per_blob_gas: 1,
1087 input: bytes!("701f58c50000000000000000000000000000000000000000000000000000000000073fb1ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000000000000000000000000000000000000000000000000000000123971da000000000000000000000000000000000000000000000000000000000000000ac39b2a24e1dbdd11a1e7bd7c0f4dfd7d9b9cfa0997d033ad05f961ba3b82c6c83312c967f10daf5ed2bffe309249416e03ee0b101f2b84d2102b9e38b0e4dfdf0000000000000000000000000000000000000000000000000000000066254c8b538dcc33ecf5334bbd294469f9d4fd084a3090693599a46d6c62567747cbc8660000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000073fb20000000000000000000000000000000000000000000000000000000066254da10000000000000000000000000000000000000000000000000000000012397d5e20b09b263779fda4171c341e720af8fa469621ff548651f8dbbc06c2d320400c000000000000000000000000000000000000000000000000000000000000000b50a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d8fc3c411b99159939ac1c16d21d3057ddc8b2333d1331ab34c938cff0eb29ce2e43241c170344db6819f76b1f1e0ab8206f3ec34120312d275c4f5bbea7f5c55700000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000031800000000000000000000000000000000000000000000800b0000000000000000000000000000000000000000000000000000000000000004ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000ca8000000000000000000000000000000000000800b000000000000000000000000000000000000000000000000000000000000000300000000000000000000000066254da100000000000000000000000066254e9d00010ca80000000000000000000000000000000000008001000000000000000000000000000000000000000000000000000000000000000550a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d800010ca800000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000b00010ca8000000000000000000000000000000000000801100000000000000000000000000000000000000000000000000000000000000075c1cd5bd0fd333ce9d7c8edfc79f43b8f345b4a394f6aba12a2cc78ce4012ed700010ca80000000000000000000000000000000000008011000000000000000000000000000000000000000000000000000000000000000845392775318aa47beaafbdc827da38c9f1e88c3bdcabba2cb493062e17cbf21e00010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000000c094e20e7ac9b433f44a5885e3bdc07e51b309aeb993caa24ba84a661ac010c100010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000001ab42db8f4ed810bdb143368a2b641edf242af6e3d0de8b1486e2b0e7880d431100010ca8000000000000000000000000000000000000800800000000000000000000000000000000000000000000000000000000000000022d94e4cc4525e4e2d81e8227b6172e97076431a2cf98792d978035edd6e6f3100000000000000000000000000000000000000000000000000000000000000000000000000000012101c74dfb80a80fccb9a4022b2406f79f56305e6a7c931d30140f5d372fe793837e93f9ec6b8d89a9d0ab222eeb27547f66b90ec40fbbdd2a4936b0b0c19ca684ff78888fbf5840d7c8dc3c493b139471750938d7d2c443e2d283e6c5ee9fde3765a756542c42f002af45c362b4b5b1687a8fc24cbf16532b903f7bb289728170dcf597f5255508c623ba247735538376f494cdcdd5bd0c4cb067526eeda0f4745a28d8baf8893ecc1b8cee80690538d66455294a028da03ff2add9d8a88e6ee03ba9ffe3ad7d91d6ac9c69a1f28c468f00fe55eba5651a2b32dc2458e0d14b4dd6d0173df255cd56aa01e8e38edec17ea8933f68543cbdc713279d195551d4211bed5c91f77259a695e6768f6c4b110b2158fcc42423a96dcc4e7f6fddb3e2369d00000000000000000000000000000000000000000000000000000000000000") };
1088 let variant = TxEip4844Variant::TxEip4844(tx);
1089
1090 let signature = Signature::new(
1091 b256!("6c173c3c8db3e3299f2f728d293b912c12e75243e3aa66911c2329b58434e2a4").into(),
1092 b256!("7dd4d1c228cedc5a414a668ab165d9e888e61e4c3b44cd7daf9cdcc4cec5d6b2").into(),
1093 false,
1094 );
1095
1096 let signed = variant.into_signed(signature);
1097 assert_eq!(
1098 *signed.hash(),
1099 b256!("93fc9daaa0726c3292a2e939df60f7e773c6a6a726a61ce43f4a217c64d85e87")
1100 );
1101 }
1102}