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;
15
16#[cfg(feature = "kzg")]
17use alloy_eips::eip4844::BlobTransactionValidationError;
18use alloy_eips::eip7594::{BlobTransactionSidecarEip7594, BlobTransactionSidecarVariant};
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#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
31#[doc(alias = "Eip4844TransactionVariant")]
32pub enum TxEip4844Variant<T = BlobTransactionSidecarVariant> {
33 TxEip4844(TxEip4844),
35 TxEip4844WithSidecar(TxEip4844WithSidecar<T>),
37}
38
39#[cfg(feature = "serde")]
40impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for TxEip4844Variant<T> {
41 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
42 where
43 D: serde::Deserializer<'de>,
44 {
45 #[derive(serde::Deserialize)]
46 struct TxEip4844SerdeHelper<Sidecar> {
47 #[serde(flatten)]
48 #[doc(alias = "transaction")]
49 tx: TxEip4844,
50 #[serde(flatten)]
51 sidecar: Option<Sidecar>,
52 }
53
54 let tx = TxEip4844SerdeHelper::<T>::deserialize(deserializer)?;
55
56 if let Some(sidecar) = tx.sidecar {
57 Ok(TxEip4844WithSidecar::from_tx_and_sidecar(tx.tx, sidecar).into())
58 } else {
59 Ok(tx.tx.into())
60 }
61 }
62}
63
64impl<T> From<Signed<TxEip4844>> for Signed<TxEip4844Variant<T>> {
65 fn from(value: Signed<TxEip4844>) -> Self {
66 let (tx, signature, hash) = value.into_parts();
67 Self::new_unchecked(TxEip4844Variant::TxEip4844(tx), signature, hash)
68 }
69}
70
71impl<T: Encodable7594> From<Signed<TxEip4844WithSidecar<T>>> for Signed<TxEip4844Variant<T>> {
72 fn from(value: Signed<TxEip4844WithSidecar<T>>) -> Self {
73 let (tx, signature, hash) = value.into_parts();
74 Self::new_unchecked(TxEip4844Variant::TxEip4844WithSidecar(tx), signature, hash)
75 }
76}
77
78impl From<TxEip4844WithSidecar<BlobTransactionSidecar>>
79 for TxEip4844Variant<BlobTransactionSidecarVariant>
80{
81 fn from(tx: TxEip4844WithSidecar<BlobTransactionSidecar>) -> Self {
82 Self::TxEip4844WithSidecar(tx.map_sidecar(Into::into))
83 }
84}
85
86impl From<TxEip4844Variant<BlobTransactionSidecar>>
87 for TxEip4844Variant<BlobTransactionSidecarVariant>
88{
89 fn from(value: TxEip4844Variant<BlobTransactionSidecar>) -> Self {
90 value.map_sidecar(Into::into)
91 }
92}
93
94impl From<TxEip4844Variant<BlobTransactionSidecarEip7594>>
95 for TxEip4844Variant<BlobTransactionSidecarVariant>
96{
97 fn from(value: TxEip4844Variant<BlobTransactionSidecarEip7594>) -> Self {
98 value.map_sidecar(Into::into)
99 }
100}
101
102impl<T> From<TxEip4844WithSidecar<T>> for TxEip4844Variant<T> {
103 fn from(tx: TxEip4844WithSidecar<T>) -> Self {
104 Self::TxEip4844WithSidecar(tx)
105 }
106}
107
108impl<T> From<TxEip4844> for TxEip4844Variant<T> {
109 fn from(tx: TxEip4844) -> Self {
110 Self::TxEip4844(tx)
111 }
112}
113
114impl From<(TxEip4844, BlobTransactionSidecar)> for TxEip4844Variant<BlobTransactionSidecar> {
115 fn from((tx, sidecar): (TxEip4844, BlobTransactionSidecar)) -> Self {
116 TxEip4844WithSidecar::from_tx_and_sidecar(tx, sidecar).into()
117 }
118}
119
120impl<T> From<TxEip4844Variant<T>> for TxEip4844 {
121 fn from(tx: TxEip4844Variant<T>) -> Self {
122 match tx {
123 TxEip4844Variant::TxEip4844(tx) => tx,
124 TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx,
125 }
126 }
127}
128
129impl<T> AsRef<TxEip4844> for TxEip4844Variant<T> {
130 fn as_ref(&self) -> &TxEip4844 {
131 match self {
132 Self::TxEip4844(tx) => tx,
133 Self::TxEip4844WithSidecar(tx) => &tx.tx,
134 }
135 }
136}
137
138impl<T> AsMut<TxEip4844> for TxEip4844Variant<T> {
139 fn as_mut(&mut self) -> &mut TxEip4844 {
140 match self {
141 Self::TxEip4844(tx) => tx,
142 Self::TxEip4844WithSidecar(tx) => &mut tx.tx,
143 }
144 }
145}
146
147impl AsRef<Self> for TxEip4844 {
148 fn as_ref(&self) -> &Self {
149 self
150 }
151}
152
153impl AsMut<Self> for TxEip4844 {
154 fn as_mut(&mut self) -> &mut Self {
155 self
156 }
157}
158
159impl<T> TxEip4844Variant<T> {
160 #[doc(alias = "transaction_type")]
162 pub const fn tx_type() -> TxType {
163 TxType::Eip4844
164 }
165
166 #[doc(alias = "transaction")]
168 pub const fn tx(&self) -> &TxEip4844 {
169 match self {
170 Self::TxEip4844(tx) => tx,
171 Self::TxEip4844WithSidecar(tx) => tx.tx(),
172 }
173 }
174
175 pub fn take_sidecar(&mut self) -> Option<T> {
179 let placeholder = Self::TxEip4844(TxEip4844::default());
181 match core::mem::replace(self, placeholder) {
182 tx @ Self::TxEip4844(_) => {
183 *self = tx;
185 None
186 }
187 Self::TxEip4844WithSidecar(tx) => {
188 let (tx, sidecar) = tx.into_parts();
189 *self = Self::TxEip4844(tx);
190 Some(sidecar)
191 }
192 }
193 }
194
195 pub fn strip_sidecar(self) -> (Self, Option<T>) {
222 self.strip_sidecar_into()
223 }
224
225 pub fn strip_sidecar_into<U>(self) -> (TxEip4844Variant<U>, Option<T>) {
256 match self {
257 Self::TxEip4844(tx) => (TxEip4844Variant::TxEip4844(tx), None),
258 Self::TxEip4844WithSidecar(tx) => {
259 let (tx, sidecar) = tx.into_parts();
260 (TxEip4844Variant::TxEip4844(tx), Some(sidecar))
261 }
262 }
263 }
264
265 pub fn drop_sidecar(self) -> Self {
285 self.strip_sidecar().0
286 }
287
288 pub fn drop_sidecar_into<U>(self) -> TxEip4844Variant<U> {
310 self.strip_sidecar_into().0
311 }
312
313 pub const fn as_with_sidecar(&self) -> Option<&TxEip4844WithSidecar<T>> {
315 match self {
316 Self::TxEip4844WithSidecar(tx) => Some(tx),
317 _ => None,
318 }
319 }
320
321 pub fn try_into_4844_with_sidecar(self) -> Result<TxEip4844WithSidecar<T>, Self> {
324 match self {
325 Self::TxEip4844WithSidecar(tx) => Ok(tx),
326 _ => Err(self),
327 }
328 }
329
330 pub const fn sidecar(&self) -> Option<&T> {
332 match self {
333 Self::TxEip4844WithSidecar(tx) => Some(tx.sidecar()),
334 _ => None,
335 }
336 }
337
338 pub fn map_sidecar<U>(self, f: impl FnOnce(T) -> U) -> TxEip4844Variant<U> {
340 match self {
341 Self::TxEip4844(tx) => TxEip4844Variant::TxEip4844(tx),
342 Self::TxEip4844WithSidecar(tx) => {
343 TxEip4844Variant::TxEip4844WithSidecar(tx.map_sidecar(f))
344 }
345 }
346 }
347
348 pub fn try_map_sidecar<U, E>(
350 self,
351 f: impl FnOnce(T) -> Result<U, E>,
352 ) -> Result<TxEip4844Variant<U>, E> {
353 match self {
354 Self::TxEip4844(tx) => Ok(TxEip4844Variant::TxEip4844(tx)),
355 Self::TxEip4844WithSidecar(tx) => {
356 tx.try_map_sidecar(f).map(TxEip4844Variant::TxEip4844WithSidecar)
357 }
358 }
359 }
360}
361
362impl<T: TxEip4844Sidecar> TxEip4844Variant<T> {
363 #[cfg(feature = "kzg")]
367 pub fn validate(
368 &self,
369 proof_settings: &c_kzg::KzgSettings,
370 ) -> Result<(), BlobTransactionValidationError> {
371 match self {
372 Self::TxEip4844(_) => Err(BlobTransactionValidationError::MissingSidecar),
373 Self::TxEip4844WithSidecar(tx) => tx.validate_blob(proof_settings),
374 }
375 }
376
377 #[inline]
379 pub fn size(&self) -> usize {
380 match self {
381 Self::TxEip4844(tx) => tx.size(),
382 Self::TxEip4844WithSidecar(tx) => tx.size(),
383 }
384 }
385}
386
387impl TxEip4844Variant<BlobTransactionSidecar> {
388 #[cfg(feature = "kzg")]
393 pub fn try_into_7594(
394 self,
395 ) -> Result<TxEip4844Variant<alloy_eips::eip7594::BlobTransactionSidecarEip7594>, c_kzg::Error>
396 {
397 self.try_into_7594_with_settings(
398 alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
399 )
400 }
401
402 #[cfg(feature = "kzg")]
407 pub fn try_into_7594_with_settings(
408 self,
409 settings: &c_kzg::KzgSettings,
410 ) -> Result<TxEip4844Variant<alloy_eips::eip7594::BlobTransactionSidecarEip7594>, c_kzg::Error>
411 {
412 self.try_map_sidecar(|sidecar| sidecar.try_into_7594(settings))
413 }
414}
415
416#[cfg(feature = "kzg")]
417impl TxEip4844Variant<alloy_eips::eip7594::BlobTransactionSidecarVariant> {
418 pub fn try_convert_into_eip7594(self) -> Result<Self, c_kzg::Error> {
430 self.try_convert_into_eip7594_with_settings(
431 alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
432 )
433 }
434
435 pub fn try_convert_into_eip7594_with_settings(
452 self,
453 settings: &c_kzg::KzgSettings,
454 ) -> Result<Self, c_kzg::Error> {
455 self.try_map_sidecar(|sidecar| sidecar.try_convert_into_eip7594_with_settings(settings))
456 }
457}
458
459impl<T> Transaction for TxEip4844Variant<T>
460where
461 T: fmt::Debug + Send + Sync + 'static,
462{
463 #[inline]
464 fn chain_id(&self) -> Option<ChainId> {
465 match self {
466 Self::TxEip4844(tx) => Some(tx.chain_id),
467 Self::TxEip4844WithSidecar(tx) => Some(tx.tx().chain_id),
468 }
469 }
470
471 #[inline]
472 fn nonce(&self) -> u64 {
473 match self {
474 Self::TxEip4844(tx) => tx.nonce,
475 Self::TxEip4844WithSidecar(tx) => tx.tx().nonce,
476 }
477 }
478
479 #[inline]
480 fn gas_limit(&self) -> u64 {
481 match self {
482 Self::TxEip4844(tx) => tx.gas_limit,
483 Self::TxEip4844WithSidecar(tx) => tx.tx().gas_limit,
484 }
485 }
486
487 #[inline]
488 fn gas_price(&self) -> Option<u128> {
489 None
490 }
491
492 #[inline]
493 fn max_fee_per_gas(&self) -> u128 {
494 match self {
495 Self::TxEip4844(tx) => tx.max_fee_per_gas(),
496 Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_gas(),
497 }
498 }
499
500 #[inline]
501 fn max_priority_fee_per_gas(&self) -> Option<u128> {
502 match self {
503 Self::TxEip4844(tx) => tx.max_priority_fee_per_gas(),
504 Self::TxEip4844WithSidecar(tx) => tx.max_priority_fee_per_gas(),
505 }
506 }
507
508 #[inline]
509 fn max_fee_per_blob_gas(&self) -> Option<u128> {
510 match self {
511 Self::TxEip4844(tx) => tx.max_fee_per_blob_gas(),
512 Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_blob_gas(),
513 }
514 }
515
516 #[inline]
517 fn priority_fee_or_price(&self) -> u128 {
518 match self {
519 Self::TxEip4844(tx) => tx.priority_fee_or_price(),
520 Self::TxEip4844WithSidecar(tx) => tx.priority_fee_or_price(),
521 }
522 }
523
524 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
525 match self {
526 Self::TxEip4844(tx) => tx.effective_gas_price(base_fee),
527 Self::TxEip4844WithSidecar(tx) => tx.effective_gas_price(base_fee),
528 }
529 }
530
531 #[inline]
532 fn is_dynamic_fee(&self) -> bool {
533 match self {
534 Self::TxEip4844(tx) => tx.is_dynamic_fee(),
535 Self::TxEip4844WithSidecar(tx) => tx.is_dynamic_fee(),
536 }
537 }
538
539 #[inline]
540 fn kind(&self) -> TxKind {
541 match self {
542 Self::TxEip4844(tx) => tx.to,
543 Self::TxEip4844WithSidecar(tx) => tx.tx.to,
544 }
545 .into()
546 }
547
548 #[inline]
549 fn is_create(&self) -> bool {
550 false
551 }
552
553 #[inline]
554 fn value(&self) -> U256 {
555 match self {
556 Self::TxEip4844(tx) => tx.value,
557 Self::TxEip4844WithSidecar(tx) => tx.tx.value,
558 }
559 }
560
561 #[inline]
562 fn input(&self) -> &Bytes {
563 match self {
564 Self::TxEip4844(tx) => tx.input(),
565 Self::TxEip4844WithSidecar(tx) => tx.tx().input(),
566 }
567 }
568
569 #[inline]
570 fn access_list(&self) -> Option<&AccessList> {
571 match self {
572 Self::TxEip4844(tx) => tx.access_list(),
573 Self::TxEip4844WithSidecar(tx) => tx.access_list(),
574 }
575 }
576
577 #[inline]
578 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
579 match self {
580 Self::TxEip4844(tx) => tx.blob_versioned_hashes(),
581 Self::TxEip4844WithSidecar(tx) => tx.blob_versioned_hashes(),
582 }
583 }
584
585 #[inline]
586 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
587 None
588 }
589}
590impl Typed2718 for TxEip4844 {
591 fn ty(&self) -> u8 {
592 TxType::Eip4844 as u8
593 }
594}
595
596impl<T: Encodable7594> RlpEcdsaEncodableTx for TxEip4844Variant<T> {
597 fn rlp_encoded_fields_length(&self) -> usize {
598 match self {
599 Self::TxEip4844(inner) => inner.rlp_encoded_fields_length(),
600 Self::TxEip4844WithSidecar(inner) => inner.rlp_encoded_fields_length(),
601 }
602 }
603
604 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
605 match self {
606 Self::TxEip4844(inner) => inner.rlp_encode_fields(out),
607 Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_fields(out),
608 }
609 }
610
611 fn rlp_header_signed(&self, signature: &Signature) -> Header {
612 match self {
613 Self::TxEip4844(inner) => inner.rlp_header_signed(signature),
614 Self::TxEip4844WithSidecar(inner) => inner.rlp_header_signed(signature),
615 }
616 }
617
618 fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
619 match self {
620 Self::TxEip4844(inner) => inner.rlp_encode_signed(signature, out),
621 Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_signed(signature, out),
622 }
623 }
624
625 fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
626 match self {
627 Self::TxEip4844(inner) => inner.tx_hash_with_type(signature, ty),
628 Self::TxEip4844WithSidecar(inner) => inner.tx_hash_with_type(signature, ty),
629 }
630 }
631}
632
633impl<T: Encodable7594 + Decodable7594> RlpEcdsaDecodableTx for TxEip4844Variant<T> {
634 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
635
636 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
637 let needle = &mut &**buf;
638
639 let trial = &mut &**buf;
642
643 if Header::decode(needle).is_ok_and(|h| h.list) {
650 if let Ok(tx) = TxEip4844WithSidecar::rlp_decode_fields(trial) {
651 *buf = *trial;
652 return Ok(tx.into());
653 }
654 }
655 TxEip4844::rlp_decode_fields(buf).map(Into::into)
656 }
657
658 fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
659 let needle = &mut &**buf;
662
663 let trial = &mut &**buf;
666
667 Header::decode(needle)?;
669
670 if Header::decode(needle).is_ok_and(|h| h.list) {
677 if let Ok((tx, signature)) = TxEip4844WithSidecar::rlp_decode_with_signature(trial) {
678 *buf = *trial;
681 return Ok((tx.into(), signature));
682 }
683 }
684 TxEip4844::rlp_decode_with_signature(buf).map(|(tx, signature)| (tx.into(), signature))
685 }
686}
687
688impl<T> Typed2718 for TxEip4844Variant<T> {
689 fn ty(&self) -> u8 {
690 TxType::Eip4844 as u8
691 }
692}
693
694impl IsTyped2718 for TxEip4844 {
695 fn is_type(type_id: u8) -> bool {
696 matches!(type_id, 0x03)
697 }
698}
699
700impl<T> SignableTransaction<Signature> for TxEip4844Variant<T>
701where
702 T: fmt::Debug + Send + Sync + 'static,
703{
704 fn set_chain_id(&mut self, chain_id: ChainId) {
705 match self {
706 Self::TxEip4844(inner) => {
707 inner.set_chain_id(chain_id);
708 }
709 Self::TxEip4844WithSidecar(inner) => {
710 inner.set_chain_id(chain_id);
711 }
712 }
713 }
714
715 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
716 self.tx().encode_for_signing(out);
722 }
723
724 fn payload_len_for_signature(&self) -> usize {
725 self.tx().payload_len_for_signature()
726 }
727}
728
729#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
733#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
734#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
735#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
736#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
737#[doc(alias = "Eip4844Transaction", alias = "TransactionEip4844", alias = "Eip4844Tx")]
738pub struct TxEip4844 {
739 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
741 pub chain_id: ChainId,
742 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
744 pub nonce: u64,
745 #[cfg_attr(
751 feature = "serde",
752 serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
753 )]
754 pub gas_limit: u64,
755 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
765 pub max_fee_per_gas: u128,
766 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
774 pub max_priority_fee_per_gas: u128,
775 pub to: Address,
777 pub value: U256,
782 pub access_list: AccessList,
788
789 pub blob_versioned_hashes: Vec<B256>,
791
792 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
796 pub max_fee_per_blob_gas: u128,
797
798 pub input: Bytes,
804}
805
806impl TxEip4844 {
807 #[inline]
809 pub const fn blob_gas(&self) -> u64 {
810 self.blob_versioned_hashes.len() as u64 * DATA_GAS_PER_BLOB
812 }
813
814 #[cfg(feature = "kzg")]
828 pub fn validate_blob<T: TxEip4844Sidecar>(
829 &self,
830 sidecar: &T,
831 proof_settings: &c_kzg::KzgSettings,
832 ) -> Result<(), BlobTransactionValidationError> {
833 sidecar.validate(&self.blob_versioned_hashes, proof_settings)
834 }
835
836 #[doc(alias = "transaction_type")]
838 pub const fn tx_type() -> TxType {
839 TxType::Eip4844
840 }
841
842 pub const fn with_sidecar<T>(self, sidecar: T) -> TxEip4844WithSidecar<T> {
844 TxEip4844WithSidecar::from_tx_and_sidecar(self, sidecar)
845 }
846
847 #[inline]
849 pub fn size(&self) -> usize {
850 size_of::<Self>()
851 + self.access_list.size()
852 + self.input.len()
853 + self.blob_versioned_hashes.capacity() * size_of::<B256>()
854 }
855}
856
857impl RlpEcdsaEncodableTx for TxEip4844 {
858 fn rlp_encoded_fields_length(&self) -> usize {
859 self.chain_id.length()
860 + self.nonce.length()
861 + self.gas_limit.length()
862 + self.max_fee_per_gas.length()
863 + self.max_priority_fee_per_gas.length()
864 + self.to.length()
865 + self.value.length()
866 + self.access_list.length()
867 + self.blob_versioned_hashes.length()
868 + self.max_fee_per_blob_gas.length()
869 + self.input.0.length()
870 }
871
872 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
873 self.chain_id.encode(out);
874 self.nonce.encode(out);
875 self.max_priority_fee_per_gas.encode(out);
876 self.max_fee_per_gas.encode(out);
877 self.gas_limit.encode(out);
878 self.to.encode(out);
879 self.value.encode(out);
880 self.input.0.encode(out);
881 self.access_list.encode(out);
882 self.max_fee_per_blob_gas.encode(out);
883 self.blob_versioned_hashes.encode(out);
884 }
885}
886
887impl RlpEcdsaDecodableTx for TxEip4844 {
888 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
889
890 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
891 Ok(Self {
892 chain_id: Decodable::decode(buf)?,
893 nonce: Decodable::decode(buf)?,
894 max_priority_fee_per_gas: Decodable::decode(buf)?,
895 max_fee_per_gas: Decodable::decode(buf)?,
896 gas_limit: Decodable::decode(buf)?,
897 to: Decodable::decode(buf)?,
898 value: Decodable::decode(buf)?,
899 input: Decodable::decode(buf)?,
900 access_list: Decodable::decode(buf)?,
901 max_fee_per_blob_gas: Decodable::decode(buf)?,
902 blob_versioned_hashes: Decodable::decode(buf)?,
903 })
904 }
905}
906
907impl SignableTransaction<Signature> for TxEip4844 {
908 fn set_chain_id(&mut self, chain_id: ChainId) {
909 self.chain_id = chain_id;
910 }
911
912 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
913 out.put_u8(Self::tx_type() as u8);
914 self.encode(out);
915 }
916
917 fn payload_len_for_signature(&self) -> usize {
918 self.length() + 1
919 }
920}
921
922impl Transaction for TxEip4844 {
923 #[inline]
924 fn chain_id(&self) -> Option<ChainId> {
925 Some(self.chain_id)
926 }
927
928 #[inline]
929 fn nonce(&self) -> u64 {
930 self.nonce
931 }
932
933 #[inline]
934 fn gas_limit(&self) -> u64 {
935 self.gas_limit
936 }
937
938 #[inline]
939 fn gas_price(&self) -> Option<u128> {
940 None
941 }
942
943 #[inline]
944 fn max_fee_per_gas(&self) -> u128 {
945 self.max_fee_per_gas
946 }
947
948 #[inline]
949 fn max_priority_fee_per_gas(&self) -> Option<u128> {
950 Some(self.max_priority_fee_per_gas)
951 }
952
953 #[inline]
954 fn max_fee_per_blob_gas(&self) -> Option<u128> {
955 Some(self.max_fee_per_blob_gas)
956 }
957
958 #[inline]
959 fn priority_fee_or_price(&self) -> u128 {
960 self.max_priority_fee_per_gas
961 }
962
963 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
964 alloy_eips::eip1559::calc_effective_gas_price(
965 self.max_fee_per_gas,
966 self.max_priority_fee_per_gas,
967 base_fee,
968 )
969 }
970
971 #[inline]
972 fn is_dynamic_fee(&self) -> bool {
973 true
974 }
975
976 #[inline]
977 fn kind(&self) -> TxKind {
978 self.to.into()
979 }
980
981 #[inline]
982 fn is_create(&self) -> bool {
983 false
984 }
985
986 #[inline]
987 fn value(&self) -> U256 {
988 self.value
989 }
990
991 #[inline]
992 fn input(&self) -> &Bytes {
993 &self.input
994 }
995
996 #[inline]
997 fn access_list(&self) -> Option<&AccessList> {
998 Some(&self.access_list)
999 }
1000
1001 #[inline]
1002 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
1003 Some(&self.blob_versioned_hashes)
1004 }
1005
1006 #[inline]
1007 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
1008 None
1009 }
1010}
1011
1012impl Encodable for TxEip4844 {
1013 fn encode(&self, out: &mut dyn BufMut) {
1014 self.rlp_encode(out);
1015 }
1016
1017 fn length(&self) -> usize {
1018 self.rlp_encoded_length()
1019 }
1020}
1021
1022impl Decodable for TxEip4844 {
1023 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1024 Self::rlp_decode(buf)
1025 }
1026}
1027
1028impl<T> From<TxEip4844WithSidecar<T>> for TxEip4844 {
1029 fn from(tx_with_sidecar: TxEip4844WithSidecar<T>) -> Self {
1031 tx_with_sidecar.tx
1032 }
1033}
1034
1035#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
1045#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
1046#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1047#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1048#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
1049#[doc(alias = "Eip4844TransactionWithSidecar", alias = "Eip4844TxWithSidecar")]
1050pub struct TxEip4844WithSidecar<T = BlobTransactionSidecarVariant> {
1051 #[cfg_attr(feature = "serde", serde(flatten))]
1053 #[doc(alias = "transaction")]
1054 pub tx: TxEip4844,
1055 #[cfg_attr(feature = "serde", serde(flatten))]
1057 pub sidecar: T,
1058}
1059
1060impl<T> TxEip4844WithSidecar<T> {
1061 #[doc(alias = "from_transaction_and_sidecar")]
1063 pub const fn from_tx_and_sidecar(tx: TxEip4844, sidecar: T) -> Self {
1064 Self { tx, sidecar }
1065 }
1066
1067 #[doc(alias = "transaction_type")]
1069 pub const fn tx_type() -> TxType {
1070 TxEip4844::tx_type()
1071 }
1072
1073 #[doc(alias = "transaction")]
1075 pub const fn tx(&self) -> &TxEip4844 {
1076 &self.tx
1077 }
1078
1079 pub const fn sidecar(&self) -> &T {
1081 &self.sidecar
1082 }
1083
1084 pub fn into_sidecar(self) -> T {
1086 self.sidecar
1087 }
1088
1089 pub fn into_parts(self) -> (TxEip4844, T) {
1091 (self.tx, self.sidecar)
1092 }
1093
1094 pub fn map_sidecar<U>(self, f: impl FnOnce(T) -> U) -> TxEip4844WithSidecar<U> {
1096 TxEip4844WithSidecar { tx: self.tx, sidecar: f(self.sidecar) }
1097 }
1098
1099 pub fn try_map_sidecar<U, E>(
1101 self,
1102 f: impl FnOnce(T) -> Result<U, E>,
1103 ) -> Result<TxEip4844WithSidecar<U>, E> {
1104 Ok(TxEip4844WithSidecar { tx: self.tx, sidecar: f(self.sidecar)? })
1105 }
1106}
1107
1108impl TxEip4844WithSidecar<BlobTransactionSidecar> {
1109 #[cfg(feature = "kzg")]
1114 pub fn try_into_7594(
1115 self,
1116 ) -> Result<
1117 TxEip4844WithSidecar<alloy_eips::eip7594::BlobTransactionSidecarEip7594>,
1118 c_kzg::Error,
1119 > {
1120 self.try_into_7594_with_settings(
1121 alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
1122 )
1123 }
1124
1125 #[cfg(feature = "kzg")]
1130 pub fn try_into_7594_with_settings(
1131 self,
1132 settings: &c_kzg::KzgSettings,
1133 ) -> Result<
1134 TxEip4844WithSidecar<alloy_eips::eip7594::BlobTransactionSidecarEip7594>,
1135 c_kzg::Error,
1136 > {
1137 self.try_map_sidecar(|sidecar| sidecar.try_into_7594(settings))
1138 }
1139}
1140
1141impl<T: TxEip4844Sidecar> TxEip4844WithSidecar<T> {
1142 #[cfg(feature = "kzg")]
1146 pub fn validate_blob(
1147 &self,
1148 proof_settings: &c_kzg::KzgSettings,
1149 ) -> Result<(), BlobTransactionValidationError> {
1150 self.tx.validate_blob(&self.sidecar, proof_settings)
1151 }
1152
1153 #[inline]
1155 pub fn size(&self) -> usize {
1156 self.tx.size() + self.sidecar.size()
1157 }
1158}
1159
1160impl<T> SignableTransaction<Signature> for TxEip4844WithSidecar<T>
1161where
1162 T: fmt::Debug + Send + Sync + 'static,
1163{
1164 fn set_chain_id(&mut self, chain_id: ChainId) {
1165 self.tx.chain_id = chain_id;
1166 }
1167
1168 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
1169 self.tx.encode_for_signing(out);
1175 }
1176
1177 fn payload_len_for_signature(&self) -> usize {
1178 self.tx.payload_len_for_signature()
1181 }
1182}
1183
1184impl<T> Transaction for TxEip4844WithSidecar<T>
1185where
1186 T: fmt::Debug + Send + Sync + 'static,
1187{
1188 #[inline]
1189 fn chain_id(&self) -> Option<ChainId> {
1190 self.tx.chain_id()
1191 }
1192
1193 #[inline]
1194 fn nonce(&self) -> u64 {
1195 self.tx.nonce()
1196 }
1197
1198 #[inline]
1199 fn gas_limit(&self) -> u64 {
1200 self.tx.gas_limit()
1201 }
1202
1203 #[inline]
1204 fn gas_price(&self) -> Option<u128> {
1205 self.tx.gas_price()
1206 }
1207
1208 #[inline]
1209 fn max_fee_per_gas(&self) -> u128 {
1210 self.tx.max_fee_per_gas()
1211 }
1212
1213 #[inline]
1214 fn max_priority_fee_per_gas(&self) -> Option<u128> {
1215 self.tx.max_priority_fee_per_gas()
1216 }
1217
1218 #[inline]
1219 fn max_fee_per_blob_gas(&self) -> Option<u128> {
1220 self.tx.max_fee_per_blob_gas()
1221 }
1222
1223 #[inline]
1224 fn priority_fee_or_price(&self) -> u128 {
1225 self.tx.priority_fee_or_price()
1226 }
1227
1228 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
1229 self.tx.effective_gas_price(base_fee)
1230 }
1231
1232 #[inline]
1233 fn is_dynamic_fee(&self) -> bool {
1234 self.tx.is_dynamic_fee()
1235 }
1236
1237 #[inline]
1238 fn kind(&self) -> TxKind {
1239 self.tx.kind()
1240 }
1241
1242 #[inline]
1243 fn is_create(&self) -> bool {
1244 false
1245 }
1246
1247 #[inline]
1248 fn value(&self) -> U256 {
1249 self.tx.value()
1250 }
1251
1252 #[inline]
1253 fn input(&self) -> &Bytes {
1254 self.tx.input()
1255 }
1256
1257 #[inline]
1258 fn access_list(&self) -> Option<&AccessList> {
1259 Some(&self.tx.access_list)
1260 }
1261
1262 #[inline]
1263 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
1264 self.tx.blob_versioned_hashes()
1265 }
1266
1267 #[inline]
1268 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
1269 None
1270 }
1271}
1272
1273impl<T> Typed2718 for TxEip4844WithSidecar<T> {
1274 fn ty(&self) -> u8 {
1275 TxType::Eip4844 as u8
1276 }
1277}
1278
1279impl<T: Encodable7594> RlpEcdsaEncodableTx for TxEip4844WithSidecar<T> {
1280 fn rlp_encoded_fields_length(&self) -> usize {
1281 self.sidecar.encode_7594_len() + self.tx.rlp_encoded_length()
1282 }
1283
1284 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
1285 self.tx.rlp_encode(out);
1286 self.sidecar.encode_7594(out);
1287 }
1288
1289 fn rlp_header_signed(&self, signature: &Signature) -> Header {
1290 let payload_length =
1291 self.tx.rlp_encoded_length_with_signature(signature) + self.sidecar.encode_7594_len();
1292 Header { list: true, payload_length }
1293 }
1294
1295 fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
1296 self.rlp_header_signed(signature).encode(out);
1297 self.tx.rlp_encode_signed(signature, out);
1298 self.sidecar.encode_7594(out);
1299 }
1300
1301 fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
1302 self.tx.tx_hash_with_type(signature, ty)
1304 }
1305}
1306
1307impl<T: Encodable7594 + Decodable7594> RlpEcdsaDecodableTx for TxEip4844WithSidecar<T> {
1308 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
1309
1310 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1311 let tx = TxEip4844::rlp_decode(buf)?;
1312 let sidecar = T::decode_7594(buf)?;
1313 Ok(Self { tx, sidecar })
1314 }
1315
1316 fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
1317 let header = Header::decode(buf)?;
1318 if !header.list {
1319 return Err(alloy_rlp::Error::UnexpectedString);
1320 }
1321 let remaining = buf.len();
1322
1323 let (tx, signature) = TxEip4844::rlp_decode_with_signature(buf)?;
1324 let sidecar = T::decode_7594(buf)?;
1325
1326 if buf.len() + header.payload_length != remaining {
1327 return Err(alloy_rlp::Error::UnexpectedLength);
1328 }
1329
1330 Ok((Self { tx, sidecar }, signature))
1331 }
1332}
1333
1334#[cfg(test)]
1335mod tests {
1336 use super::{BlobTransactionSidecar, TxEip4844, TxEip4844WithSidecar};
1337 use crate::{
1338 transaction::{eip4844::TxEip4844Variant, RlpEcdsaDecodableTx},
1339 SignableTransaction, TxEnvelope,
1340 };
1341 use alloy_eips::{
1342 eip2930::AccessList, eip4844::env_settings::EnvKzgSettings,
1343 eip7594::BlobTransactionSidecarVariant, Encodable2718 as _,
1344 };
1345 use alloy_primitives::{address, b256, bytes, hex, Signature, U256};
1346 use alloy_rlp::{Decodable, Encodable};
1347 use assert_matches::assert_matches;
1348 use std::path::PathBuf;
1349
1350 #[test]
1351 fn different_sidecar_same_hash() {
1352 let tx = TxEip4844 {
1355 chain_id: 1,
1356 nonce: 1,
1357 max_priority_fee_per_gas: 1,
1358 max_fee_per_gas: 1,
1359 gas_limit: 1,
1360 to: Default::default(),
1361 value: U256::from(1),
1362 access_list: Default::default(),
1363 blob_versioned_hashes: vec![Default::default()],
1364 max_fee_per_blob_gas: 1,
1365 input: Default::default(),
1366 };
1367 let sidecar = BlobTransactionSidecar {
1368 blobs: vec![[2; 131072].into()],
1369 commitments: vec![[3; 48].into()],
1370 proofs: vec![[4; 48].into()],
1371 };
1372 let mut tx = TxEip4844WithSidecar { tx, sidecar };
1373 let signature = Signature::test_signature();
1374
1375 let expected_signed = tx.clone().into_signed(signature);
1377
1378 tx.sidecar = BlobTransactionSidecar {
1380 blobs: vec![[1; 131072].into()],
1381 commitments: vec![[1; 48].into()],
1382 proofs: vec![[1; 48].into()],
1383 };
1384
1385 let actual_signed = tx.into_signed(signature);
1387
1388 assert_eq!(expected_signed.hash(), actual_signed.hash());
1390
1391 let expected_envelope: TxEnvelope = expected_signed.into();
1393 let actual_envelope: TxEnvelope = actual_signed.into();
1394
1395 let len = expected_envelope.length();
1397 let mut buf = Vec::with_capacity(len);
1398 expected_envelope.encode(&mut buf);
1399 assert_eq!(buf.len(), len);
1400
1401 assert_eq!(buf.len(), actual_envelope.length());
1404
1405 let decoded = TxEnvelope::decode(&mut &buf[..]).unwrap();
1407 assert_eq!(decoded, expected_envelope);
1408 }
1409
1410 #[test]
1411 fn test_4844_variant_into_signed_correct_hash() {
1412 let tx =
1414 TxEip4844 {
1415 chain_id: 1,
1416 nonce: 15435,
1417 gas_limit: 8000000,
1418 max_fee_per_gas: 10571233596,
1419 max_priority_fee_per_gas: 1000000000,
1420 to: address!("a8cb082a5a689e0d594d7da1e2d72a3d63adc1bd"),
1421 value: U256::ZERO,
1422 access_list: AccessList::default(),
1423 blob_versioned_hashes: vec![
1424 b256!("01e5276d91ac1ddb3b1c2d61295211220036e9a04be24c00f76916cc2659d004"),
1425 b256!("0128eb58aff09fd3a7957cd80aa86186d5849569997cdfcfa23772811b706cc2"),
1426 ],
1427 max_fee_per_blob_gas: 1,
1428 input: bytes!("701f58c50000000000000000000000000000000000000000000000000000000000073fb1ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000000000000000000000000000000000000000000000000000000123971da000000000000000000000000000000000000000000000000000000000000000ac39b2a24e1dbdd11a1e7bd7c0f4dfd7d9b9cfa0997d033ad05f961ba3b82c6c83312c967f10daf5ed2bffe309249416e03ee0b101f2b84d2102b9e38b0e4dfdf0000000000000000000000000000000000000000000000000000000066254c8b538dcc33ecf5334bbd294469f9d4fd084a3090693599a46d6c62567747cbc8660000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000073fb20000000000000000000000000000000000000000000000000000000066254da10000000000000000000000000000000000000000000000000000000012397d5e20b09b263779fda4171c341e720af8fa469621ff548651f8dbbc06c2d320400c000000000000000000000000000000000000000000000000000000000000000b50a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d8fc3c411b99159939ac1c16d21d3057ddc8b2333d1331ab34c938cff0eb29ce2e43241c170344db6819f76b1f1e0ab8206f3ec34120312d275c4f5bbea7f5c55700000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000031800000000000000000000000000000000000000000000800b0000000000000000000000000000000000000000000000000000000000000004ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000ca8000000000000000000000000000000000000800b000000000000000000000000000000000000000000000000000000000000000300000000000000000000000066254da100000000000000000000000066254e9d00010ca80000000000000000000000000000000000008001000000000000000000000000000000000000000000000000000000000000000550a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d800010ca800000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000b00010ca8000000000000000000000000000000000000801100000000000000000000000000000000000000000000000000000000000000075c1cd5bd0fd333ce9d7c8edfc79f43b8f345b4a394f6aba12a2cc78ce4012ed700010ca80000000000000000000000000000000000008011000000000000000000000000000000000000000000000000000000000000000845392775318aa47beaafbdc827da38c9f1e88c3bdcabba2cb493062e17cbf21e00010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000000c094e20e7ac9b433f44a5885e3bdc07e51b309aeb993caa24ba84a661ac010c100010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000001ab42db8f4ed810bdb143368a2b641edf242af6e3d0de8b1486e2b0e7880d431100010ca8000000000000000000000000000000000000800800000000000000000000000000000000000000000000000000000000000000022d94e4cc4525e4e2d81e8227b6172e97076431a2cf98792d978035edd6e6f3100000000000000000000000000000000000000000000000000000000000000000000000000000012101c74dfb80a80fccb9a4022b2406f79f56305e6a7c931d30140f5d372fe793837e93f9ec6b8d89a9d0ab222eeb27547f66b90ec40fbbdd2a4936b0b0c19ca684ff78888fbf5840d7c8dc3c493b139471750938d7d2c443e2d283e6c5ee9fde3765a756542c42f002af45c362b4b5b1687a8fc24cbf16532b903f7bb289728170dcf597f5255508c623ba247735538376f494cdcdd5bd0c4cb067526eeda0f4745a28d8baf8893ecc1b8cee80690538d66455294a028da03ff2add9d8a88e6ee03ba9ffe3ad7d91d6ac9c69a1f28c468f00fe55eba5651a2b32dc2458e0d14b4dd6d0173df255cd56aa01e8e38edec17ea8933f68543cbdc713279d195551d4211bed5c91f77259a695e6768f6c4b110b2158fcc42423a96dcc4e7f6fddb3e2369d00000000000000000000000000000000000000000000000000000000000000") };
1429 let variant = TxEip4844Variant::<BlobTransactionSidecar>::TxEip4844(tx);
1430
1431 let signature = Signature::new(
1432 b256!("6c173c3c8db3e3299f2f728d293b912c12e75243e3aa66911c2329b58434e2a4").into(),
1433 b256!("7dd4d1c228cedc5a414a668ab165d9e888e61e4c3b44cd7daf9cdcc4cec5d6b2").into(),
1434 false,
1435 );
1436
1437 let signed = variant.into_signed(signature);
1438 assert_eq!(
1439 *signed.hash(),
1440 b256!("93fc9daaa0726c3292a2e939df60f7e773c6a6a726a61ce43f4a217c64d85e87")
1441 );
1442 }
1443
1444 #[test]
1445 fn decode_raw_7594_rlp() {
1446 let kzg_settings = EnvKzgSettings::default();
1447 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/7594rlp");
1448 let dir = std::fs::read_dir(path).expect("Unable to read folder");
1449 for entry in dir {
1450 let entry = entry.unwrap();
1451 let content = std::fs::read_to_string(entry.path()).unwrap();
1452 let raw = hex::decode(content.trim()).unwrap();
1453 let tx = TxEip4844WithSidecar::<BlobTransactionSidecarVariant>::eip2718_decode(
1454 &mut raw.as_ref(),
1455 )
1456 .map_err(|err| {
1457 panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
1458 })
1459 .unwrap();
1460
1461 let encoded = tx.encoded_2718();
1463 assert_eq!(encoded.as_slice(), &raw[..], "{:?}", entry.path());
1464
1465 let TxEip4844WithSidecar { tx, sidecar } = tx.tx();
1466 assert_matches!(sidecar, BlobTransactionSidecarVariant::Eip7594(_));
1467
1468 let result = sidecar.validate(&tx.blob_versioned_hashes, kzg_settings.get());
1469 assert_matches!(result, Ok(()));
1470 }
1471 }
1472
1473 #[test]
1474 fn decode_raw_7594_rlp_invalid() {
1475 let kzg_settings = EnvKzgSettings::default();
1476 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/7594rlp-invalid");
1477 let dir = std::fs::read_dir(path).expect("Unable to read folder");
1478 for entry in dir {
1479 let entry = entry.unwrap();
1480
1481 if entry.path().file_name().and_then(|f| f.to_str()) == Some("0.rlp") {
1482 continue;
1483 }
1484
1485 let content = std::fs::read_to_string(entry.path()).unwrap();
1486 let raw = hex::decode(content.trim()).unwrap();
1487 let tx = TxEip4844WithSidecar::<BlobTransactionSidecarVariant>::eip2718_decode(
1488 &mut raw.as_ref(),
1489 )
1490 .map_err(|err| {
1491 panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
1492 })
1493 .unwrap();
1494
1495 let encoded = tx.encoded_2718();
1497 assert_eq!(encoded.as_slice(), &raw[..], "{:?}", entry.path());
1498
1499 let TxEip4844WithSidecar { tx, sidecar } = tx.tx();
1500 assert_matches!(sidecar, BlobTransactionSidecarVariant::Eip7594(_));
1501
1502 let result = sidecar.validate(&tx.blob_versioned_hashes, kzg_settings.get());
1503 assert_matches!(result, Err(_));
1504 }
1505 }
1506}