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