1use super::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, TxEip4844Sidecar};
2use crate::{SignableTransaction, Signed, Transaction, TxType};
3use alloc::vec::Vec;
4use alloy_eips::{
5 eip2718::IsTyped2718,
6 eip2930::AccessList,
7 eip4844::{BlobTransactionSidecar, DATA_GAS_PER_BLOB},
8 eip7594::{Decodable7594, Encodable7594},
9 eip7702::SignedAuthorization,
10 Typed2718,
11};
12use alloy_primitives::{Address, Bytes, ChainId, Signature, TxKind, B256, U256};
13use alloy_rlp::{BufMut, Decodable, Encodable, Header};
14use core::{fmt, mem};
15
16#[cfg(feature = "kzg")]
17use alloy_eips::eip4844::BlobTransactionValidationError;
18
19#[derive(Clone, Debug, PartialEq, Eq, Hash)]
26#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
27#[cfg_attr(feature = "serde", derive(serde::Serialize))]
28#[cfg_attr(feature = "serde", serde(untagged))]
29#[doc(alias = "Eip4844TransactionVariant")]
30pub enum TxEip4844Variant<T = BlobTransactionSidecar> {
31 TxEip4844(TxEip4844),
33 TxEip4844WithSidecar(TxEip4844WithSidecar<T>),
35}
36
37#[cfg(feature = "serde")]
38impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for TxEip4844Variant<T> {
39 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
40 where
41 D: serde::Deserializer<'de>,
42 {
43 #[derive(serde::Deserialize)]
44 struct TxEip4844SerdeHelper<Sidecar> {
45 #[serde(flatten)]
46 #[doc(alias = "transaction")]
47 tx: TxEip4844,
48 #[serde(flatten)]
49 sidecar: Option<Sidecar>,
50 }
51
52 let tx = TxEip4844SerdeHelper::<T>::deserialize(deserializer)?;
53
54 if let Some(sidecar) = tx.sidecar {
55 Ok(TxEip4844WithSidecar::from_tx_and_sidecar(tx.tx, sidecar).into())
56 } else {
57 Ok(tx.tx.into())
58 }
59 }
60}
61
62impl<T> From<Signed<TxEip4844>> for Signed<TxEip4844Variant<T>> {
63 fn from(value: Signed<TxEip4844>) -> Self {
64 let (tx, signature, hash) = value.into_parts();
65 Self::new_unchecked(TxEip4844Variant::TxEip4844(tx), signature, hash)
66 }
67}
68
69impl<T: Encodable7594> From<Signed<TxEip4844WithSidecar<T>>> for Signed<TxEip4844Variant<T>> {
70 fn from(value: Signed<TxEip4844WithSidecar<T>>) -> Self {
71 let (tx, signature, hash) = value.into_parts();
72 Self::new_unchecked(TxEip4844Variant::TxEip4844WithSidecar(tx), signature, hash)
73 }
74}
75
76impl<T> From<TxEip4844WithSidecar<T>> for TxEip4844Variant<T> {
77 fn from(tx: TxEip4844WithSidecar<T>) -> Self {
78 Self::TxEip4844WithSidecar(tx)
79 }
80}
81
82impl<T> From<TxEip4844> for TxEip4844Variant<T> {
83 fn from(tx: TxEip4844) -> Self {
84 Self::TxEip4844(tx)
85 }
86}
87
88impl From<(TxEip4844, BlobTransactionSidecar)> for TxEip4844Variant<BlobTransactionSidecar> {
89 fn from((tx, sidecar): (TxEip4844, BlobTransactionSidecar)) -> Self {
90 TxEip4844WithSidecar::from_tx_and_sidecar(tx, sidecar).into()
91 }
92}
93
94impl<T> From<TxEip4844Variant<T>> for TxEip4844 {
95 fn from(tx: TxEip4844Variant<T>) -> Self {
96 match tx {
97 TxEip4844Variant::TxEip4844(tx) => tx,
98 TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx,
99 }
100 }
101}
102
103impl<T> AsRef<TxEip4844> for TxEip4844Variant<T> {
104 fn as_ref(&self) -> &TxEip4844 {
105 match self {
106 Self::TxEip4844(tx) => tx,
107 Self::TxEip4844WithSidecar(tx) => &tx.tx,
108 }
109 }
110}
111
112impl<T> AsMut<TxEip4844> for TxEip4844Variant<T> {
113 fn as_mut(&mut self) -> &mut TxEip4844 {
114 match self {
115 Self::TxEip4844(tx) => tx,
116 Self::TxEip4844WithSidecar(tx) => &mut tx.tx,
117 }
118 }
119}
120
121impl AsRef<Self> for TxEip4844 {
122 fn as_ref(&self) -> &Self {
123 self
124 }
125}
126
127impl AsMut<Self> for TxEip4844 {
128 fn as_mut(&mut self) -> &mut Self {
129 self
130 }
131}
132
133impl<T> TxEip4844Variant<T> {
134 #[doc(alias = "transaction_type")]
136 pub const fn tx_type() -> TxType {
137 TxType::Eip4844
138 }
139
140 #[doc(alias = "transaction")]
142 pub const fn tx(&self) -> &TxEip4844 {
143 match self {
144 Self::TxEip4844(tx) => tx,
145 Self::TxEip4844WithSidecar(tx) => tx.tx(),
146 }
147 }
148
149 pub fn try_into_4844_with_sidecar(self) -> Result<TxEip4844WithSidecar<T>, Self> {
152 match self {
153 Self::TxEip4844WithSidecar(tx) => Ok(tx),
154 _ => Err(self),
155 }
156 }
157}
158
159impl<T: TxEip4844Sidecar> TxEip4844Variant<T> {
160 #[cfg(feature = "kzg")]
164 pub fn validate(
165 &self,
166 proof_settings: &c_kzg::KzgSettings,
167 ) -> Result<(), BlobTransactionValidationError> {
168 match self {
169 Self::TxEip4844(_) => Err(BlobTransactionValidationError::MissingSidecar),
170 Self::TxEip4844WithSidecar(tx) => tx.validate_blob(proof_settings),
171 }
172 }
173
174 #[inline]
176 pub fn size(&self) -> usize {
177 match self {
178 Self::TxEip4844(tx) => tx.size(),
179 Self::TxEip4844WithSidecar(tx) => tx.size(),
180 }
181 }
182}
183
184impl<T> Transaction for TxEip4844Variant<T>
185where
186 T: fmt::Debug + Send + Sync + 'static,
187{
188 #[inline]
189 fn chain_id(&self) -> Option<ChainId> {
190 match self {
191 Self::TxEip4844(tx) => Some(tx.chain_id),
192 Self::TxEip4844WithSidecar(tx) => Some(tx.tx().chain_id),
193 }
194 }
195
196 #[inline]
197 fn nonce(&self) -> u64 {
198 match self {
199 Self::TxEip4844(tx) => tx.nonce,
200 Self::TxEip4844WithSidecar(tx) => tx.tx().nonce,
201 }
202 }
203
204 #[inline]
205 fn gas_limit(&self) -> u64 {
206 match self {
207 Self::TxEip4844(tx) => tx.gas_limit,
208 Self::TxEip4844WithSidecar(tx) => tx.tx().gas_limit,
209 }
210 }
211
212 #[inline]
213 fn gas_price(&self) -> Option<u128> {
214 None
215 }
216
217 #[inline]
218 fn max_fee_per_gas(&self) -> u128 {
219 match self {
220 Self::TxEip4844(tx) => tx.max_fee_per_gas(),
221 Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_gas(),
222 }
223 }
224
225 #[inline]
226 fn max_priority_fee_per_gas(&self) -> Option<u128> {
227 match self {
228 Self::TxEip4844(tx) => tx.max_priority_fee_per_gas(),
229 Self::TxEip4844WithSidecar(tx) => tx.max_priority_fee_per_gas(),
230 }
231 }
232
233 #[inline]
234 fn max_fee_per_blob_gas(&self) -> Option<u128> {
235 match self {
236 Self::TxEip4844(tx) => tx.max_fee_per_blob_gas(),
237 Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_blob_gas(),
238 }
239 }
240
241 #[inline]
242 fn priority_fee_or_price(&self) -> u128 {
243 match self {
244 Self::TxEip4844(tx) => tx.priority_fee_or_price(),
245 Self::TxEip4844WithSidecar(tx) => tx.priority_fee_or_price(),
246 }
247 }
248
249 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
250 match self {
251 Self::TxEip4844(tx) => tx.effective_gas_price(base_fee),
252 Self::TxEip4844WithSidecar(tx) => tx.effective_gas_price(base_fee),
253 }
254 }
255
256 #[inline]
257 fn is_dynamic_fee(&self) -> bool {
258 match self {
259 Self::TxEip4844(tx) => tx.is_dynamic_fee(),
260 Self::TxEip4844WithSidecar(tx) => tx.is_dynamic_fee(),
261 }
262 }
263
264 #[inline]
265 fn kind(&self) -> TxKind {
266 match self {
267 Self::TxEip4844(tx) => tx.to,
268 Self::TxEip4844WithSidecar(tx) => tx.tx.to,
269 }
270 .into()
271 }
272
273 #[inline]
274 fn is_create(&self) -> bool {
275 false
276 }
277
278 #[inline]
279 fn value(&self) -> U256 {
280 match self {
281 Self::TxEip4844(tx) => tx.value,
282 Self::TxEip4844WithSidecar(tx) => tx.tx.value,
283 }
284 }
285
286 #[inline]
287 fn input(&self) -> &Bytes {
288 match self {
289 Self::TxEip4844(tx) => tx.input(),
290 Self::TxEip4844WithSidecar(tx) => tx.tx().input(),
291 }
292 }
293
294 #[inline]
295 fn access_list(&self) -> Option<&AccessList> {
296 match self {
297 Self::TxEip4844(tx) => tx.access_list(),
298 Self::TxEip4844WithSidecar(tx) => tx.access_list(),
299 }
300 }
301
302 #[inline]
303 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
304 match self {
305 Self::TxEip4844(tx) => tx.blob_versioned_hashes(),
306 Self::TxEip4844WithSidecar(tx) => tx.blob_versioned_hashes(),
307 }
308 }
309
310 #[inline]
311 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
312 None
313 }
314}
315impl Typed2718 for TxEip4844 {
316 fn ty(&self) -> u8 {
317 TxType::Eip4844 as u8
318 }
319}
320
321impl<T: Encodable7594> RlpEcdsaEncodableTx for TxEip4844Variant<T> {
322 fn rlp_encoded_fields_length(&self) -> usize {
323 match self {
324 Self::TxEip4844(inner) => inner.rlp_encoded_fields_length(),
325 Self::TxEip4844WithSidecar(inner) => inner.rlp_encoded_fields_length(),
326 }
327 }
328
329 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
330 match self {
331 Self::TxEip4844(inner) => inner.rlp_encode_fields(out),
332 Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_fields(out),
333 }
334 }
335
336 fn rlp_header_signed(&self, signature: &Signature) -> Header {
337 match self {
338 Self::TxEip4844(inner) => inner.rlp_header_signed(signature),
339 Self::TxEip4844WithSidecar(inner) => inner.rlp_header_signed(signature),
340 }
341 }
342
343 fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
344 match self {
345 Self::TxEip4844(inner) => inner.rlp_encode_signed(signature, out),
346 Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_signed(signature, out),
347 }
348 }
349
350 fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
351 match self {
352 Self::TxEip4844(inner) => inner.tx_hash_with_type(signature, ty),
353 Self::TxEip4844WithSidecar(inner) => inner.tx_hash_with_type(signature, ty),
354 }
355 }
356}
357
358impl<T: Encodable7594 + Decodable7594> RlpEcdsaDecodableTx for TxEip4844Variant<T> {
359 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
360
361 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
362 let needle = &mut &**buf;
363
364 let trial = &mut &**buf;
367
368 if Header::decode(needle).is_ok_and(|h| h.list) {
375 if let Ok(tx) = TxEip4844WithSidecar::rlp_decode_fields(trial) {
376 *buf = *trial;
377 return Ok(tx.into());
378 }
379 }
380 TxEip4844::rlp_decode_fields(buf).map(Into::into)
381 }
382
383 fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
384 let needle = &mut &**buf;
387
388 let trial = &mut &**buf;
391
392 Header::decode(needle)?;
394
395 if Header::decode(needle).is_ok_and(|h| h.list) {
402 if let Ok((tx, signature)) = TxEip4844WithSidecar::rlp_decode_with_signature(trial) {
403 *buf = *trial;
406 return Ok((tx.into(), signature));
407 }
408 }
409 TxEip4844::rlp_decode_with_signature(buf).map(|(tx, signature)| (tx.into(), signature))
410 }
411}
412
413impl<T> Typed2718 for TxEip4844Variant<T> {
414 fn ty(&self) -> u8 {
415 TxType::Eip4844 as u8
416 }
417}
418
419impl IsTyped2718 for TxEip4844 {
420 fn is_type(type_id: u8) -> bool {
421 matches!(type_id, 0x03)
422 }
423}
424
425impl<T> SignableTransaction<Signature> for TxEip4844Variant<T>
426where
427 T: fmt::Debug + Send + Sync + 'static,
428{
429 fn set_chain_id(&mut self, chain_id: ChainId) {
430 match self {
431 Self::TxEip4844(inner) => {
432 inner.set_chain_id(chain_id);
433 }
434 Self::TxEip4844WithSidecar(inner) => {
435 inner.set_chain_id(chain_id);
436 }
437 }
438 }
439
440 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
441 self.tx().encode_for_signing(out);
447 }
448
449 fn payload_len_for_signature(&self) -> usize {
450 self.tx().payload_len_for_signature()
451 }
452}
453
454#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
458#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
459#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
460#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
461#[doc(alias = "Eip4844Transaction", alias = "TransactionEip4844", alias = "Eip4844Tx")]
462pub struct TxEip4844 {
463 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
465 pub chain_id: ChainId,
466 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
468 pub nonce: u64,
469 #[cfg_attr(
475 feature = "serde",
476 serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
477 )]
478 pub gas_limit: u64,
479 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
491 pub max_fee_per_gas: u128,
492 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
500 pub max_priority_fee_per_gas: u128,
501 pub to: Address,
503 pub value: U256,
508 pub access_list: AccessList,
514
515 pub blob_versioned_hashes: Vec<B256>,
517
518 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
522 pub max_fee_per_blob_gas: u128,
523
524 pub input: Bytes,
530}
531
532impl TxEip4844 {
533 #[inline]
535 pub fn blob_gas(&self) -> u64 {
536 self.blob_versioned_hashes.len() as u64 * DATA_GAS_PER_BLOB
538 }
539
540 #[cfg(feature = "kzg")]
554 pub fn validate_blob<T: TxEip4844Sidecar>(
555 &self,
556 sidecar: &T,
557 proof_settings: &c_kzg::KzgSettings,
558 ) -> Result<(), BlobTransactionValidationError> {
559 sidecar.validate(&self.blob_versioned_hashes, proof_settings)
560 }
561
562 #[doc(alias = "transaction_type")]
564 pub const fn tx_type() -> TxType {
565 TxType::Eip4844
566 }
567
568 pub const fn with_sidecar<T>(self, sidecar: T) -> TxEip4844WithSidecar<T> {
570 TxEip4844WithSidecar::from_tx_and_sidecar(self, sidecar)
571 }
572
573 #[inline]
575 pub fn size(&self) -> usize {
576 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>() }
588}
589
590impl RlpEcdsaEncodableTx for TxEip4844 {
591 fn rlp_encoded_fields_length(&self) -> usize {
592 self.chain_id.length()
593 + self.nonce.length()
594 + self.gas_limit.length()
595 + self.max_fee_per_gas.length()
596 + self.max_priority_fee_per_gas.length()
597 + self.to.length()
598 + self.value.length()
599 + self.access_list.length()
600 + self.blob_versioned_hashes.length()
601 + self.max_fee_per_blob_gas.length()
602 + self.input.0.length()
603 }
604
605 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
606 self.chain_id.encode(out);
607 self.nonce.encode(out);
608 self.max_priority_fee_per_gas.encode(out);
609 self.max_fee_per_gas.encode(out);
610 self.gas_limit.encode(out);
611 self.to.encode(out);
612 self.value.encode(out);
613 self.input.0.encode(out);
614 self.access_list.encode(out);
615 self.max_fee_per_blob_gas.encode(out);
616 self.blob_versioned_hashes.encode(out);
617 }
618}
619
620impl RlpEcdsaDecodableTx for TxEip4844 {
621 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
622
623 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
624 Ok(Self {
625 chain_id: Decodable::decode(buf)?,
626 nonce: Decodable::decode(buf)?,
627 max_priority_fee_per_gas: Decodable::decode(buf)?,
628 max_fee_per_gas: Decodable::decode(buf)?,
629 gas_limit: Decodable::decode(buf)?,
630 to: Decodable::decode(buf)?,
631 value: Decodable::decode(buf)?,
632 input: Decodable::decode(buf)?,
633 access_list: Decodable::decode(buf)?,
634 max_fee_per_blob_gas: Decodable::decode(buf)?,
635 blob_versioned_hashes: Decodable::decode(buf)?,
636 })
637 }
638}
639
640impl SignableTransaction<Signature> for TxEip4844 {
641 fn set_chain_id(&mut self, chain_id: ChainId) {
642 self.chain_id = chain_id;
643 }
644
645 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
646 out.put_u8(Self::tx_type() as u8);
647 self.encode(out);
648 }
649
650 fn payload_len_for_signature(&self) -> usize {
651 self.length() + 1
652 }
653}
654
655impl Transaction for TxEip4844 {
656 #[inline]
657 fn chain_id(&self) -> Option<ChainId> {
658 Some(self.chain_id)
659 }
660
661 #[inline]
662 fn nonce(&self) -> u64 {
663 self.nonce
664 }
665
666 #[inline]
667 fn gas_limit(&self) -> u64 {
668 self.gas_limit
669 }
670
671 #[inline]
672 fn gas_price(&self) -> Option<u128> {
673 None
674 }
675
676 #[inline]
677 fn max_fee_per_gas(&self) -> u128 {
678 self.max_fee_per_gas
679 }
680
681 #[inline]
682 fn max_priority_fee_per_gas(&self) -> Option<u128> {
683 Some(self.max_priority_fee_per_gas)
684 }
685
686 #[inline]
687 fn max_fee_per_blob_gas(&self) -> Option<u128> {
688 Some(self.max_fee_per_blob_gas)
689 }
690
691 #[inline]
692 fn priority_fee_or_price(&self) -> u128 {
693 self.max_priority_fee_per_gas
694 }
695
696 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
697 base_fee.map_or(self.max_fee_per_gas, |base_fee| {
698 let tip = self.max_fee_per_gas.saturating_sub(base_fee as u128);
701 if tip > self.max_priority_fee_per_gas {
702 self.max_priority_fee_per_gas + base_fee as u128
703 } else {
704 self.max_fee_per_gas
706 }
707 })
708 }
709
710 #[inline]
711 fn is_dynamic_fee(&self) -> bool {
712 true
713 }
714
715 #[inline]
716 fn kind(&self) -> TxKind {
717 self.to.into()
718 }
719
720 #[inline]
721 fn is_create(&self) -> bool {
722 false
723 }
724
725 #[inline]
726 fn value(&self) -> U256 {
727 self.value
728 }
729
730 #[inline]
731 fn input(&self) -> &Bytes {
732 &self.input
733 }
734
735 #[inline]
736 fn access_list(&self) -> Option<&AccessList> {
737 Some(&self.access_list)
738 }
739
740 #[inline]
741 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
742 Some(&self.blob_versioned_hashes)
743 }
744
745 #[inline]
746 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
747 None
748 }
749}
750
751impl Encodable for TxEip4844 {
752 fn encode(&self, out: &mut dyn BufMut) {
753 self.rlp_encode(out);
754 }
755
756 fn length(&self) -> usize {
757 self.rlp_encoded_length()
758 }
759}
760
761impl Decodable for TxEip4844 {
762 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
763 Self::rlp_decode(buf)
764 }
765}
766
767impl<T> From<TxEip4844WithSidecar<T>> for TxEip4844 {
768 fn from(tx_with_sidecar: TxEip4844WithSidecar<T>) -> Self {
770 tx_with_sidecar.tx
771 }
772}
773
774#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
784#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
785#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
786#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
787#[doc(alias = "Eip4844TransactionWithSidecar", alias = "Eip4844TxWithSidecar")]
788pub struct TxEip4844WithSidecar<T = BlobTransactionSidecar> {
789 #[cfg_attr(feature = "serde", serde(flatten))]
791 #[doc(alias = "transaction")]
792 pub tx: TxEip4844,
793 #[cfg_attr(feature = "serde", serde(flatten))]
795 pub sidecar: T,
796}
797
798impl<T> TxEip4844WithSidecar<T> {
799 #[doc(alias = "from_transaction_and_sidecar")]
801 pub const fn from_tx_and_sidecar(tx: TxEip4844, sidecar: T) -> Self {
802 Self { tx, sidecar }
803 }
804
805 #[doc(alias = "transaction_type")]
807 pub const fn tx_type() -> TxType {
808 TxEip4844::tx_type()
809 }
810
811 #[doc(alias = "transaction")]
813 pub const fn tx(&self) -> &TxEip4844 {
814 &self.tx
815 }
816
817 pub const fn sidecar(&self) -> &T {
819 &self.sidecar
820 }
821
822 pub fn into_sidecar(self) -> T {
824 self.sidecar
825 }
826
827 pub fn into_parts(self) -> (TxEip4844, T) {
829 (self.tx, self.sidecar)
830 }
831}
832
833impl<T: TxEip4844Sidecar> TxEip4844WithSidecar<T> {
834 #[cfg(feature = "kzg")]
838 pub fn validate_blob(
839 &self,
840 proof_settings: &c_kzg::KzgSettings,
841 ) -> Result<(), BlobTransactionValidationError> {
842 self.tx.validate_blob(&self.sidecar, proof_settings)
843 }
844
845 #[inline]
847 pub fn size(&self) -> usize {
848 self.tx.size() + self.sidecar.size()
849 }
850}
851
852impl<T> SignableTransaction<Signature> for TxEip4844WithSidecar<T>
853where
854 T: fmt::Debug + Send + Sync + 'static,
855{
856 fn set_chain_id(&mut self, chain_id: ChainId) {
857 self.tx.chain_id = chain_id;
858 }
859
860 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
861 self.tx.encode_for_signing(out);
867 }
868
869 fn payload_len_for_signature(&self) -> usize {
870 self.tx.payload_len_for_signature()
873 }
874}
875
876impl<T> Transaction for TxEip4844WithSidecar<T>
877where
878 T: fmt::Debug + Send + Sync + 'static,
879{
880 #[inline]
881 fn chain_id(&self) -> Option<ChainId> {
882 self.tx.chain_id()
883 }
884
885 #[inline]
886 fn nonce(&self) -> u64 {
887 self.tx.nonce()
888 }
889
890 #[inline]
891 fn gas_limit(&self) -> u64 {
892 self.tx.gas_limit()
893 }
894
895 #[inline]
896 fn gas_price(&self) -> Option<u128> {
897 self.tx.gas_price()
898 }
899
900 #[inline]
901 fn max_fee_per_gas(&self) -> u128 {
902 self.tx.max_fee_per_gas()
903 }
904
905 #[inline]
906 fn max_priority_fee_per_gas(&self) -> Option<u128> {
907 self.tx.max_priority_fee_per_gas()
908 }
909
910 #[inline]
911 fn max_fee_per_blob_gas(&self) -> Option<u128> {
912 self.tx.max_fee_per_blob_gas()
913 }
914
915 #[inline]
916 fn priority_fee_or_price(&self) -> u128 {
917 self.tx.priority_fee_or_price()
918 }
919
920 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
921 self.tx.effective_gas_price(base_fee)
922 }
923
924 #[inline]
925 fn is_dynamic_fee(&self) -> bool {
926 self.tx.is_dynamic_fee()
927 }
928
929 #[inline]
930 fn kind(&self) -> TxKind {
931 self.tx.kind()
932 }
933
934 #[inline]
935 fn is_create(&self) -> bool {
936 false
937 }
938
939 #[inline]
940 fn value(&self) -> U256 {
941 self.tx.value()
942 }
943
944 #[inline]
945 fn input(&self) -> &Bytes {
946 self.tx.input()
947 }
948
949 #[inline]
950 fn access_list(&self) -> Option<&AccessList> {
951 Some(&self.tx.access_list)
952 }
953
954 #[inline]
955 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
956 self.tx.blob_versioned_hashes()
957 }
958
959 #[inline]
960 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
961 None
962 }
963}
964
965impl<T> Typed2718 for TxEip4844WithSidecar<T> {
966 fn ty(&self) -> u8 {
967 TxType::Eip4844 as u8
968 }
969}
970
971impl<T: Encodable7594> RlpEcdsaEncodableTx for TxEip4844WithSidecar<T> {
972 fn rlp_encoded_fields_length(&self) -> usize {
973 self.sidecar.encode_7594_len() + self.tx.rlp_encoded_length()
974 }
975
976 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
977 self.tx.rlp_encode(out);
978 self.sidecar.encode_7594(out);
979 }
980
981 fn rlp_header_signed(&self, signature: &Signature) -> Header {
982 let payload_length =
983 self.tx.rlp_encoded_length_with_signature(signature) + self.sidecar.encode_7594_len();
984 Header { list: true, payload_length }
985 }
986
987 fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
988 self.rlp_header_signed(signature).encode(out);
989 self.tx.rlp_encode_signed(signature, out);
990 self.sidecar.encode_7594(out);
991 }
992
993 fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
994 self.tx.tx_hash_with_type(signature, ty)
996 }
997}
998
999impl<T: Encodable7594 + Decodable7594> RlpEcdsaDecodableTx for TxEip4844WithSidecar<T> {
1000 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
1001
1002 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1003 let tx = TxEip4844::rlp_decode(buf)?;
1004 let sidecar = T::decode_7594(buf)?;
1005 Ok(Self { tx, sidecar })
1006 }
1007
1008 fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
1009 let header = Header::decode(buf)?;
1010 if !header.list {
1011 return Err(alloy_rlp::Error::UnexpectedString);
1012 }
1013 let remaining = buf.len();
1014
1015 let (tx, signature) = TxEip4844::rlp_decode_with_signature(buf)?;
1016 let sidecar = T::decode_7594(buf)?;
1017
1018 if buf.len() + header.payload_length != remaining {
1019 return Err(alloy_rlp::Error::UnexpectedLength);
1020 }
1021
1022 Ok((Self { tx, sidecar }, signature))
1023 }
1024}
1025
1026#[cfg(test)]
1027mod tests {
1028 use super::{BlobTransactionSidecar, TxEip4844, TxEip4844WithSidecar};
1029 use crate::{
1030 transaction::{eip4844::TxEip4844Variant, RlpEcdsaDecodableTx},
1031 SignableTransaction, TxEnvelope,
1032 };
1033 use alloy_eips::{
1034 eip2930::AccessList, eip4844::env_settings::EnvKzgSettings,
1035 eip7594::BlobTransactionSidecarVariant, Encodable2718 as _,
1036 };
1037 use alloy_primitives::{address, b256, bytes, hex, Signature, U256};
1038 use alloy_rlp::{Decodable, Encodable};
1039 use assert_matches::assert_matches;
1040 use std::path::PathBuf;
1041
1042 #[test]
1043 fn different_sidecar_same_hash() {
1044 let tx = TxEip4844 {
1047 chain_id: 1,
1048 nonce: 1,
1049 max_priority_fee_per_gas: 1,
1050 max_fee_per_gas: 1,
1051 gas_limit: 1,
1052 to: Default::default(),
1053 value: U256::from(1),
1054 access_list: Default::default(),
1055 blob_versioned_hashes: vec![Default::default()],
1056 max_fee_per_blob_gas: 1,
1057 input: Default::default(),
1058 };
1059 let sidecar = BlobTransactionSidecar {
1060 blobs: vec![[2; 131072].into()],
1061 commitments: vec![[3; 48].into()],
1062 proofs: vec![[4; 48].into()],
1063 };
1064 let mut tx = TxEip4844WithSidecar { tx, sidecar };
1065 let signature = Signature::test_signature();
1066
1067 let expected_signed = tx.clone().into_signed(signature);
1069
1070 tx.sidecar = BlobTransactionSidecar {
1072 blobs: vec![[1; 131072].into()],
1073 commitments: vec![[1; 48].into()],
1074 proofs: vec![[1; 48].into()],
1075 };
1076
1077 let actual_signed = tx.into_signed(signature);
1079
1080 assert_eq!(expected_signed.hash(), actual_signed.hash());
1082
1083 let expected_envelope: TxEnvelope = expected_signed.into();
1085 let actual_envelope: TxEnvelope = actual_signed.into();
1086
1087 let len = expected_envelope.length();
1089 let mut buf = Vec::with_capacity(len);
1090 expected_envelope.encode(&mut buf);
1091 assert_eq!(buf.len(), len);
1092
1093 assert_eq!(buf.len(), actual_envelope.length());
1096
1097 let decoded = TxEnvelope::decode(&mut &buf[..]).unwrap();
1099 assert_eq!(decoded, expected_envelope);
1100 }
1101
1102 #[test]
1103 fn test_4844_variant_into_signed_correct_hash() {
1104 let tx =
1106 TxEip4844 {
1107 chain_id: 1,
1108 nonce: 15435,
1109 gas_limit: 8000000,
1110 max_fee_per_gas: 10571233596,
1111 max_priority_fee_per_gas: 1000000000,
1112 to: address!("a8cb082a5a689e0d594d7da1e2d72a3d63adc1bd"),
1113 value: U256::ZERO,
1114 access_list: AccessList::default(),
1115 blob_versioned_hashes: vec![
1116 b256!("01e5276d91ac1ddb3b1c2d61295211220036e9a04be24c00f76916cc2659d004"),
1117 b256!("0128eb58aff09fd3a7957cd80aa86186d5849569997cdfcfa23772811b706cc2"),
1118 ],
1119 max_fee_per_blob_gas: 1,
1120 input: bytes!("701f58c50000000000000000000000000000000000000000000000000000000000073fb1ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000000000000000000000000000000000000000000000000000000123971da000000000000000000000000000000000000000000000000000000000000000ac39b2a24e1dbdd11a1e7bd7c0f4dfd7d9b9cfa0997d033ad05f961ba3b82c6c83312c967f10daf5ed2bffe309249416e03ee0b101f2b84d2102b9e38b0e4dfdf0000000000000000000000000000000000000000000000000000000066254c8b538dcc33ecf5334bbd294469f9d4fd084a3090693599a46d6c62567747cbc8660000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000073fb20000000000000000000000000000000000000000000000000000000066254da10000000000000000000000000000000000000000000000000000000012397d5e20b09b263779fda4171c341e720af8fa469621ff548651f8dbbc06c2d320400c000000000000000000000000000000000000000000000000000000000000000b50a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d8fc3c411b99159939ac1c16d21d3057ddc8b2333d1331ab34c938cff0eb29ce2e43241c170344db6819f76b1f1e0ab8206f3ec34120312d275c4f5bbea7f5c55700000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000031800000000000000000000000000000000000000000000800b0000000000000000000000000000000000000000000000000000000000000004ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000ca8000000000000000000000000000000000000800b000000000000000000000000000000000000000000000000000000000000000300000000000000000000000066254da100000000000000000000000066254e9d00010ca80000000000000000000000000000000000008001000000000000000000000000000000000000000000000000000000000000000550a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d800010ca800000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000b00010ca8000000000000000000000000000000000000801100000000000000000000000000000000000000000000000000000000000000075c1cd5bd0fd333ce9d7c8edfc79f43b8f345b4a394f6aba12a2cc78ce4012ed700010ca80000000000000000000000000000000000008011000000000000000000000000000000000000000000000000000000000000000845392775318aa47beaafbdc827da38c9f1e88c3bdcabba2cb493062e17cbf21e00010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000000c094e20e7ac9b433f44a5885e3bdc07e51b309aeb993caa24ba84a661ac010c100010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000001ab42db8f4ed810bdb143368a2b641edf242af6e3d0de8b1486e2b0e7880d431100010ca8000000000000000000000000000000000000800800000000000000000000000000000000000000000000000000000000000000022d94e4cc4525e4e2d81e8227b6172e97076431a2cf98792d978035edd6e6f3100000000000000000000000000000000000000000000000000000000000000000000000000000012101c74dfb80a80fccb9a4022b2406f79f56305e6a7c931d30140f5d372fe793837e93f9ec6b8d89a9d0ab222eeb27547f66b90ec40fbbdd2a4936b0b0c19ca684ff78888fbf5840d7c8dc3c493b139471750938d7d2c443e2d283e6c5ee9fde3765a756542c42f002af45c362b4b5b1687a8fc24cbf16532b903f7bb289728170dcf597f5255508c623ba247735538376f494cdcdd5bd0c4cb067526eeda0f4745a28d8baf8893ecc1b8cee80690538d66455294a028da03ff2add9d8a88e6ee03ba9ffe3ad7d91d6ac9c69a1f28c468f00fe55eba5651a2b32dc2458e0d14b4dd6d0173df255cd56aa01e8e38edec17ea8933f68543cbdc713279d195551d4211bed5c91f77259a695e6768f6c4b110b2158fcc42423a96dcc4e7f6fddb3e2369d00000000000000000000000000000000000000000000000000000000000000") };
1121 let variant = TxEip4844Variant::<BlobTransactionSidecar>::TxEip4844(tx);
1122
1123 let signature = Signature::new(
1124 b256!("6c173c3c8db3e3299f2f728d293b912c12e75243e3aa66911c2329b58434e2a4").into(),
1125 b256!("7dd4d1c228cedc5a414a668ab165d9e888e61e4c3b44cd7daf9cdcc4cec5d6b2").into(),
1126 false,
1127 );
1128
1129 let signed = variant.into_signed(signature);
1130 assert_eq!(
1131 *signed.hash(),
1132 b256!("93fc9daaa0726c3292a2e939df60f7e773c6a6a726a61ce43f4a217c64d85e87")
1133 );
1134 }
1135
1136 #[test]
1137 fn decode_raw_7594_rlp() {
1138 let kzg_settings = EnvKzgSettings::default();
1139 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/7594rlp");
1140 let dir = std::fs::read_dir(path).expect("Unable to read folder");
1141 for entry in dir {
1142 let entry = entry.unwrap();
1143 let content = std::fs::read_to_string(entry.path()).unwrap();
1144 let raw = hex::decode(content.trim()).unwrap();
1145 let tx = TxEip4844WithSidecar::<BlobTransactionSidecarVariant>::eip2718_decode(
1146 &mut raw.as_ref(),
1147 )
1148 .map_err(|err| {
1149 panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
1150 })
1151 .unwrap();
1152
1153 let encoded = tx.encoded_2718();
1155 assert_eq!(encoded.as_slice(), &raw[..], "{:?}", entry.path());
1156
1157 let TxEip4844WithSidecar { tx, sidecar } = tx.tx();
1158 assert_matches!(sidecar, BlobTransactionSidecarVariant::Eip7594(_));
1159
1160 let result = sidecar.validate(&tx.blob_versioned_hashes, kzg_settings.get());
1161 assert_matches!(result, Ok(()));
1162 }
1163 }
1164
1165 #[test]
1166 fn decode_raw_7594_rlp_invalid() {
1167 let kzg_settings = EnvKzgSettings::default();
1168 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/7594rlp-invalid");
1169 let dir = std::fs::read_dir(path).expect("Unable to read folder");
1170 for entry in dir {
1171 let entry = entry.unwrap();
1172
1173 if entry.path().file_name().and_then(|f| f.to_str()) == Some("0.rlp") {
1174 continue;
1175 }
1176
1177 let content = std::fs::read_to_string(entry.path()).unwrap();
1178 let raw = hex::decode(content.trim()).unwrap();
1179 let tx = TxEip4844WithSidecar::<BlobTransactionSidecarVariant>::eip2718_decode(
1180 &mut raw.as_ref(),
1181 )
1182 .map_err(|err| {
1183 panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
1184 })
1185 .unwrap();
1186
1187 let encoded = tx.encoded_2718();
1189 assert_eq!(encoded.as_slice(), &raw[..], "{:?}", entry.path());
1190
1191 let TxEip4844WithSidecar { tx, sidecar } = tx.tx();
1192 assert_matches!(sidecar, BlobTransactionSidecarVariant::Eip7594(_));
1193
1194 let result = sidecar.validate(&tx.blob_versioned_hashes, kzg_settings.get());
1195 assert_matches!(result, Err(_));
1196 }
1197 }
1198}