1use crate::eth::transaction::optimism::DepositTransaction;
4use alloy_consensus::{
5 transaction::{
6 eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar},
7 Recovered, TxEip7702,
8 },
9 Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, TxEip1559, TxEip2930, TxEnvelope, TxLegacy,
10 TxReceipt, Typed2718,
11};
12use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Encodable2718};
13use alloy_network::{AnyReceiptEnvelope, AnyRpcTransaction, AnyTransactionReceipt, AnyTxEnvelope};
14use alloy_primitives::{
15 Address, Bloom, Bytes, Log, PrimitiveSignature, TxHash, TxKind, B256, U256, U64,
16};
17use alloy_rlp::{length_of_length, Decodable, Encodable, Header};
18use alloy_rpc_types::{
19 request::TransactionRequest, trace::otterscan::OtsReceipt, AccessList, ConversionError,
20 Transaction as RpcTransaction, TransactionReceipt,
21};
22use alloy_serde::{OtherFields, WithOtherFields};
23use bytes::BufMut;
24use foundry_evm::traces::CallTraceNode;
25use op_alloy_consensus::{TxDeposit, DEPOSIT_TX_TYPE_ID};
26use revm::{
27 interpreter::InstructionResult,
28 primitives::{OptimismFields, TxEnv},
29};
30use serde::{Deserialize, Serialize};
31use std::ops::{Deref, Mul};
32
33pub mod optimism;
34
35pub fn transaction_request_to_typed(
38 tx: WithOtherFields<TransactionRequest>,
39) -> Option<TypedTransactionRequest> {
40 let WithOtherFields::<TransactionRequest> {
41 inner:
42 TransactionRequest {
43 from,
44 to,
45 gas_price,
46 max_fee_per_gas,
47 max_priority_fee_per_gas,
48 max_fee_per_blob_gas,
49 blob_versioned_hashes,
50 gas,
51 value,
52 input,
53 nonce,
54 access_list,
55 sidecar,
56 transaction_type,
57 ..
58 },
59 other,
60 } = tx;
61
62 if transaction_type == Some(0x7E) || has_optimism_fields(&other) {
64 let mint = other.get_deserialized::<U256>("mint")?.map(|m| m.to::<u128>()).ok()?;
65
66 return Some(TypedTransactionRequest::Deposit(TxDeposit {
67 from: from.unwrap_or_default(),
68 source_hash: other.get_deserialized::<B256>("sourceHash")?.ok()?,
69 to: to.unwrap_or_default(),
70 mint: Some(mint),
71 value: value.unwrap_or_default(),
72 gas_limit: gas.unwrap_or_default(),
73 is_system_transaction: other.get_deserialized::<bool>("isSystemTx")?.ok()?,
74 input: input.into_input().unwrap_or_default(),
75 }));
76 }
77
78 match (
79 transaction_type,
80 gas_price,
81 max_fee_per_gas,
82 max_priority_fee_per_gas,
83 access_list.as_ref(),
84 max_fee_per_blob_gas,
85 blob_versioned_hashes.as_ref(),
86 sidecar.as_ref(),
87 to,
88 ) {
89 (Some(0), _, None, None, None, None, None, None, _) |
91 (None, Some(_), None, None, None, None, None, None, _) => {
92 Some(TypedTransactionRequest::Legacy(TxLegacy {
93 nonce: nonce.unwrap_or_default(),
94 gas_price: gas_price.unwrap_or_default(),
95 gas_limit: gas.unwrap_or_default(),
96 value: value.unwrap_or(U256::ZERO),
97 input: input.into_input().unwrap_or_default(),
98 to: to.unwrap_or_default(),
99 chain_id: None,
100 }))
101 }
102 (Some(1), _, None, None, _, None, None, None, _) |
104 (None, _, None, None, Some(_), None, None, None, _) => {
105 Some(TypedTransactionRequest::EIP2930(TxEip2930 {
106 nonce: nonce.unwrap_or_default(),
107 gas_price: gas_price.unwrap_or_default(),
108 gas_limit: gas.unwrap_or_default(),
109 value: value.unwrap_or(U256::ZERO),
110 input: input.into_input().unwrap_or_default(),
111 to: to.unwrap_or_default(),
112 chain_id: 0,
113 access_list: access_list.unwrap_or_default(),
114 }))
115 }
116 (Some(2), None, _, _, _, _, None, None, _) |
118 (None, None, Some(_), _, _, _, None, None, _) |
119 (None, None, _, Some(_), _, _, None, None, _) |
120 (None, None, None, None, None, _, None, None, _) => {
121 Some(TypedTransactionRequest::EIP1559(TxEip1559 {
123 nonce: nonce.unwrap_or_default(),
124 max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
125 max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),
126 gas_limit: gas.unwrap_or_default(),
127 value: value.unwrap_or(U256::ZERO),
128 input: input.into_input().unwrap_or_default(),
129 to: to.unwrap_or_default(),
130 chain_id: 0,
131 access_list: access_list.unwrap_or_default(),
132 }))
133 }
134 (Some(3), None, _, _, _, _, Some(_), _, to) => {
136 let tx = TxEip4844 {
137 nonce: nonce.unwrap_or_default(),
138 max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
139 max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),
140 max_fee_per_blob_gas: max_fee_per_blob_gas.unwrap_or_default(),
141 gas_limit: gas.unwrap_or_default(),
142 value: value.unwrap_or(U256::ZERO),
143 input: input.into_input().unwrap_or_default(),
144 to: match to.unwrap_or(TxKind::Create) {
145 TxKind::Call(to) => to,
146 TxKind::Create => Address::ZERO,
147 },
148 chain_id: 0,
149 access_list: access_list.unwrap_or_default(),
150 blob_versioned_hashes: blob_versioned_hashes.unwrap_or_default(),
151 };
152
153 if let Some(sidecar) = sidecar {
154 Some(TypedTransactionRequest::EIP4844(TxEip4844Variant::TxEip4844WithSidecar(
155 TxEip4844WithSidecar::from_tx_and_sidecar(tx, sidecar),
156 )))
157 } else {
158 Some(TypedTransactionRequest::EIP4844(TxEip4844Variant::TxEip4844(tx)))
159 }
160 }
161 _ => None,
162 }
163}
164
165fn has_optimism_fields(other: &OtherFields) -> bool {
166 other.contains_key("sourceHash") &&
167 other.contains_key("mint") &&
168 other.contains_key("isSystemTx")
169}
170
171#[derive(Clone, Debug, PartialEq, Eq)]
172pub enum TypedTransactionRequest {
173 Legacy(TxLegacy),
174 EIP2930(TxEip2930),
175 EIP1559(TxEip1559),
176 EIP4844(TxEip4844Variant),
177 Deposit(TxDeposit),
178}
179
180#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
185pub struct MaybeImpersonatedTransaction {
186 pub transaction: TypedTransaction,
187 pub impersonated_sender: Option<Address>,
188}
189
190impl MaybeImpersonatedTransaction {
191 pub fn new(transaction: TypedTransaction) -> Self {
193 Self { transaction, impersonated_sender: None }
194 }
195
196 pub fn impersonated(transaction: TypedTransaction, impersonated_sender: Address) -> Self {
198 Self { transaction, impersonated_sender: Some(impersonated_sender) }
199 }
200
201 pub fn recover(&self) -> Result<Address, alloy_primitives::SignatureError> {
203 if let Some(sender) = self.impersonated_sender {
204 return Ok(sender);
205 }
206 self.transaction.recover()
207 }
208
209 pub fn is_impersonated(&self) -> bool {
211 self.impersonated_sender.is_some()
212 }
213
214 pub fn hash(&self) -> B256 {
216 if let Some(sender) = self.impersonated_sender {
217 return self.transaction.impersonated_hash(sender)
218 }
219 self.transaction.hash()
220 }
221}
222
223impl Encodable for MaybeImpersonatedTransaction {
224 fn encode(&self, out: &mut dyn bytes::BufMut) {
225 self.transaction.encode(out)
226 }
227}
228
229impl From<MaybeImpersonatedTransaction> for TypedTransaction {
230 fn from(value: MaybeImpersonatedTransaction) -> Self {
231 value.transaction
232 }
233}
234
235impl From<TypedTransaction> for MaybeImpersonatedTransaction {
236 fn from(value: TypedTransaction) -> Self {
237 Self::new(value)
238 }
239}
240
241impl Decodable for MaybeImpersonatedTransaction {
242 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
243 TypedTransaction::decode(buf).map(Self::new)
244 }
245}
246
247impl AsRef<TypedTransaction> for MaybeImpersonatedTransaction {
248 fn as_ref(&self) -> &TypedTransaction {
249 &self.transaction
250 }
251}
252
253impl Deref for MaybeImpersonatedTransaction {
254 type Target = TypedTransaction;
255
256 fn deref(&self) -> &Self::Target {
257 &self.transaction
258 }
259}
260
261impl From<MaybeImpersonatedTransaction> for RpcTransaction {
262 fn from(value: MaybeImpersonatedTransaction) -> Self {
263 let hash = value.hash();
264 let sender = value.recover().unwrap_or_default();
265 to_alloy_transaction_with_hash_and_sender(value.transaction, hash, sender)
266 }
267}
268
269pub fn to_alloy_transaction_with_hash_and_sender(
270 transaction: TypedTransaction,
271 hash: B256,
272 from: Address,
273) -> RpcTransaction {
274 match transaction {
275 TypedTransaction::Legacy(t) => {
276 let (tx, sig, _) = t.into_parts();
277 RpcTransaction {
278 block_hash: None,
279 block_number: None,
280 transaction_index: None,
281 effective_gas_price: None,
282 inner: Recovered::new_unchecked(
283 TxEnvelope::Legacy(Signed::new_unchecked(tx, sig, hash)),
284 from,
285 ),
286 }
287 }
288 TypedTransaction::EIP2930(t) => {
289 let (tx, sig, _) = t.into_parts();
290 RpcTransaction {
291 block_hash: None,
292 block_number: None,
293 transaction_index: None,
294 effective_gas_price: None,
295 inner: Recovered::new_unchecked(
296 TxEnvelope::Eip2930(Signed::new_unchecked(tx, sig, hash)),
297 from,
298 ),
299 }
300 }
301 TypedTransaction::EIP1559(t) => {
302 let (tx, sig, _) = t.into_parts();
303 RpcTransaction {
304 block_hash: None,
305 block_number: None,
306 transaction_index: None,
307 effective_gas_price: None,
308 inner: Recovered::new_unchecked(
309 TxEnvelope::Eip1559(Signed::new_unchecked(tx, sig, hash)),
310 from,
311 ),
312 }
313 }
314 TypedTransaction::EIP4844(t) => {
315 let (tx, sig, _) = t.into_parts();
316 RpcTransaction {
317 block_hash: None,
318 block_number: None,
319 transaction_index: None,
320 effective_gas_price: None,
321 inner: Recovered::new_unchecked(
322 TxEnvelope::Eip4844(Signed::new_unchecked(tx, sig, hash)),
323 from,
324 ),
325 }
326 }
327 TypedTransaction::EIP7702(t) => {
328 let (tx, sig, _) = t.into_parts();
329 RpcTransaction {
330 block_hash: None,
331 block_number: None,
332 transaction_index: None,
333 effective_gas_price: None,
334 inner: Recovered::new_unchecked(
335 TxEnvelope::Eip7702(Signed::new_unchecked(tx, sig, hash)),
336 from,
337 ),
338 }
339 }
340 TypedTransaction::Deposit(_t) => {
341 unreachable!("cannot reach here, handled in `transaction_build` ")
342 }
343 }
344}
345
346#[derive(Clone, Debug, PartialEq, Eq)]
348pub struct PendingTransaction {
349 pub transaction: MaybeImpersonatedTransaction,
351 sender: Address,
353 hash: TxHash,
355}
356
357impl PendingTransaction {
358 pub fn new(transaction: TypedTransaction) -> Result<Self, alloy_primitives::SignatureError> {
359 let sender = transaction.recover()?;
360 let hash = transaction.hash();
361 Ok(Self { transaction: MaybeImpersonatedTransaction::new(transaction), sender, hash })
362 }
363
364 pub fn with_impersonated(transaction: TypedTransaction, sender: Address) -> Self {
365 let hash = transaction.impersonated_hash(sender);
366 Self {
367 transaction: MaybeImpersonatedTransaction::impersonated(transaction, sender),
368 sender,
369 hash,
370 }
371 }
372
373 pub fn nonce(&self) -> u64 {
374 self.transaction.nonce()
375 }
376
377 pub fn hash(&self) -> &TxHash {
378 &self.hash
379 }
380
381 pub fn sender(&self) -> &Address {
382 &self.sender
383 }
384
385 pub fn to_revm_tx_env(&self) -> TxEnv {
388 fn transact_to(kind: &TxKind) -> TxKind {
389 match kind {
390 TxKind::Call(c) => TxKind::Call(*c),
391 TxKind::Create => TxKind::Create,
392 }
393 }
394
395 let caller = *self.sender();
396 match &self.transaction.transaction {
397 TypedTransaction::Legacy(tx) => {
398 let chain_id = tx.tx().chain_id;
399 let TxLegacy { nonce, gas_price, gas_limit, value, to, input, .. } = tx.tx();
400 TxEnv {
401 caller,
402 transact_to: transact_to(to),
403 data: input.clone(),
404 chain_id,
405 nonce: Some(*nonce),
406 value: (*value),
407 gas_price: U256::from(*gas_price),
408 gas_priority_fee: None,
409 gas_limit: *gas_limit,
410 access_list: vec![],
411 ..Default::default()
412 }
413 }
414 TypedTransaction::EIP2930(tx) => {
415 let TxEip2930 {
416 chain_id,
417 nonce,
418 gas_price,
419 gas_limit,
420 to,
421 value,
422 input,
423 access_list,
424 ..
425 } = tx.tx();
426 TxEnv {
427 caller,
428 transact_to: transact_to(to),
429 data: input.clone(),
430 chain_id: Some(*chain_id),
431 nonce: Some(*nonce),
432 value: *value,
433 gas_price: U256::from(*gas_price),
434 gas_priority_fee: None,
435 gas_limit: *gas_limit,
436 access_list: access_list.clone().into(),
437 ..Default::default()
438 }
439 }
440 TypedTransaction::EIP1559(tx) => {
441 let TxEip1559 {
442 chain_id,
443 nonce,
444 max_priority_fee_per_gas,
445 max_fee_per_gas,
446 gas_limit,
447 to,
448 value,
449 input,
450 access_list,
451 ..
452 } = tx.tx();
453 TxEnv {
454 caller,
455 transact_to: transact_to(to),
456 data: input.clone(),
457 chain_id: Some(*chain_id),
458 nonce: Some(*nonce),
459 value: *value,
460 gas_price: U256::from(*max_fee_per_gas),
461 gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)),
462 gas_limit: *gas_limit,
463 access_list: access_list.clone().into(),
464 ..Default::default()
465 }
466 }
467 TypedTransaction::EIP4844(tx) => {
468 let TxEip4844 {
469 chain_id,
470 nonce,
471 max_fee_per_blob_gas,
472 max_fee_per_gas,
473 max_priority_fee_per_gas,
474 gas_limit,
475 to,
476 value,
477 input,
478 access_list,
479 blob_versioned_hashes,
480 ..
481 } = tx.tx().tx();
482 TxEnv {
483 caller,
484 transact_to: TxKind::Call(*to),
485 data: input.clone(),
486 chain_id: Some(*chain_id),
487 nonce: Some(*nonce),
488 value: *value,
489 gas_price: U256::from(*max_fee_per_gas),
490 gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)),
491 max_fee_per_blob_gas: Some(U256::from(*max_fee_per_blob_gas)),
492 blob_hashes: blob_versioned_hashes.clone(),
493 gas_limit: *gas_limit,
494 access_list: access_list.clone().into(),
495 ..Default::default()
496 }
497 }
498 TypedTransaction::EIP7702(tx) => {
499 let TxEip7702 {
500 chain_id,
501 nonce,
502 gas_limit,
503 max_fee_per_gas,
504 max_priority_fee_per_gas,
505 to,
506 value,
507 access_list,
508 authorization_list,
509 input,
510 } = tx.tx();
511 TxEnv {
512 caller,
513 transact_to: TxKind::Call(*to),
514 data: input.clone(),
515 chain_id: Some(*chain_id),
516 nonce: Some(*nonce),
517 value: *value,
518 gas_price: U256::from(*max_fee_per_gas),
519 gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)),
520 gas_limit: *gas_limit,
521 access_list: access_list.clone().into(),
522 authorization_list: Some(authorization_list.clone().into()),
523 ..Default::default()
524 }
525 }
526 TypedTransaction::Deposit(tx) => {
527 let chain_id = tx.chain_id();
528 let DepositTransaction {
529 nonce,
530 source_hash,
531 gas_limit,
532 value,
533 kind,
534 mint,
535 input,
536 is_system_tx,
537 ..
538 } = tx;
539 TxEnv {
540 caller,
541 transact_to: transact_to(kind),
542 data: input.clone(),
543 chain_id,
544 nonce: Some(*nonce),
545 value: *value,
546 gas_price: U256::ZERO,
547 gas_priority_fee: None,
548 gas_limit: { *gas_limit },
549 access_list: vec![],
550 optimism: OptimismFields {
551 source_hash: Some(*source_hash),
552 mint: Some(mint.to::<u128>()),
553 is_system_transaction: Some(*is_system_tx),
554 enveloped_tx: None,
555 },
556 ..Default::default()
557 }
558 }
559 }
560 }
561}
562
563#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
565pub enum TypedTransaction {
566 Legacy(Signed<TxLegacy>),
568 EIP2930(Signed<TxEip2930>),
570 EIP1559(Signed<TxEip1559>),
572 EIP4844(Signed<TxEip4844Variant>),
574 EIP7702(Signed<TxEip7702>),
576 Deposit(DepositTransaction),
578}
579
580impl TryFrom<TypedTransaction> for TransactionRequest {
586 type Error = ConversionError;
587
588 fn try_from(value: TypedTransaction) -> Result<Self, Self::Error> {
589 let from =
590 value.recover().map_err(|_| ConversionError::Custom("InvalidSignature".to_string()))?;
591 let essentials = value.essentials();
592 let tx_type = value.r#type();
593 Ok(Self {
594 from: Some(from),
595 to: Some(value.kind()),
596 gas_price: essentials.gas_price,
597 max_fee_per_gas: essentials.max_fee_per_gas,
598 max_priority_fee_per_gas: essentials.max_priority_fee_per_gas,
599 max_fee_per_blob_gas: essentials.max_fee_per_blob_gas,
600 gas: Some(essentials.gas_limit),
601 value: Some(essentials.value),
602 input: essentials.input.into(),
603 nonce: Some(essentials.nonce),
604 chain_id: essentials.chain_id,
605 transaction_type: tx_type,
606 ..Default::default()
607 })
608 }
609}
610
611impl TryFrom<AnyRpcTransaction> for TypedTransaction {
612 type Error = ConversionError;
613
614 fn try_from(value: AnyRpcTransaction) -> Result<Self, Self::Error> {
615 let WithOtherFields { inner, .. } = value.0;
616 let from = inner.inner.signer();
617 match inner.inner.into_inner() {
618 AnyTxEnvelope::Ethereum(tx) => match tx {
619 TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
620 TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)),
621 TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)),
622 TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)),
623 TxEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)),
624 },
625 AnyTxEnvelope::Unknown(mut tx) => {
626 if tx.ty() == DEPOSIT_TX_TYPE_ID {
628 let nonce = get_field::<U64>(&tx.inner.fields, "nonce")?;
629 tx.inner.fields.insert("from".to_string(), serde_json::to_value(from).unwrap());
630 let deposit_tx =
631 tx.inner.fields.deserialize_into::<TxDeposit>().map_err(|e| {
632 ConversionError::Custom(format!(
633 "Failed to deserialize deposit tx: {e}"
634 ))
635 })?;
636
637 let TxDeposit {
638 source_hash,
639 is_system_transaction,
640 value,
641 gas_limit,
642 input,
643 mint,
644 from,
645 to,
646 } = deposit_tx;
647
648 let deposit_tx = DepositTransaction {
649 nonce: nonce.to(),
650 source_hash,
651 from,
652 kind: to,
653 mint: mint.map(|m| U256::from(m)).unwrap_or_default(),
654 value,
655 gas_limit,
656 is_system_tx: is_system_transaction,
657 input,
658 };
659
660 return Ok(Self::Deposit(deposit_tx));
661 };
662
663 Err(ConversionError::Custom("UnknownTxType".to_string()))
664 }
665 }
666 }
667}
668
669fn get_field<T: serde::de::DeserializeOwned>(
670 fields: &OtherFields,
671 key: &str,
672) -> Result<T, ConversionError> {
673 fields
674 .get_deserialized::<T>(key)
675 .ok_or_else(|| ConversionError::Custom(format!("Missing{key}")))?
676 .map_err(|e| ConversionError::Custom(format!("Failed to deserialize {key}: {e}")))
677}
678
679impl TypedTransaction {
680 pub fn is_dynamic_fee(&self) -> bool {
682 matches!(self, Self::EIP1559(_) | Self::EIP4844(_) | Self::EIP7702(_))
683 }
684
685 pub fn gas_price(&self) -> u128 {
686 match self {
687 Self::Legacy(tx) => tx.tx().gas_price,
688 Self::EIP2930(tx) => tx.tx().gas_price,
689 Self::EIP1559(tx) => tx.tx().max_fee_per_gas,
690 Self::EIP4844(tx) => tx.tx().tx().max_fee_per_gas,
691 Self::EIP7702(tx) => tx.tx().max_fee_per_gas,
692 Self::Deposit(_) => 0,
693 }
694 }
695
696 pub fn gas_limit(&self) -> u64 {
697 match self {
698 Self::Legacy(tx) => tx.tx().gas_limit,
699 Self::EIP2930(tx) => tx.tx().gas_limit,
700 Self::EIP1559(tx) => tx.tx().gas_limit,
701 Self::EIP4844(tx) => tx.tx().tx().gas_limit,
702 Self::EIP7702(tx) => tx.tx().gas_limit,
703 Self::Deposit(tx) => tx.gas_limit,
704 }
705 }
706
707 pub fn value(&self) -> U256 {
708 U256::from(match self {
709 Self::Legacy(tx) => tx.tx().value,
710 Self::EIP2930(tx) => tx.tx().value,
711 Self::EIP1559(tx) => tx.tx().value,
712 Self::EIP4844(tx) => tx.tx().tx().value,
713 Self::EIP7702(tx) => tx.tx().value,
714 Self::Deposit(tx) => tx.value,
715 })
716 }
717
718 pub fn data(&self) -> &Bytes {
719 match self {
720 Self::Legacy(tx) => &tx.tx().input,
721 Self::EIP2930(tx) => &tx.tx().input,
722 Self::EIP1559(tx) => &tx.tx().input,
723 Self::EIP4844(tx) => &tx.tx().tx().input,
724 Self::EIP7702(tx) => &tx.tx().input,
725 Self::Deposit(tx) => &tx.input,
726 }
727 }
728
729 pub fn r#type(&self) -> Option<u8> {
731 match self {
732 Self::Legacy(_) => None,
733 Self::EIP2930(_) => Some(1),
734 Self::EIP1559(_) => Some(2),
735 Self::EIP4844(_) => Some(3),
736 Self::EIP7702(_) => Some(4),
737 Self::Deposit(_) => Some(0x7E),
738 }
739 }
740
741 pub fn max_cost(&self) -> u128 {
746 let mut max_cost = (self.gas_limit() as u128).saturating_mul(self.gas_price());
747
748 if self.is_eip4844() {
749 max_cost = max_cost.saturating_add(
750 self.blob_gas()
751 .map(|g| g as u128)
752 .unwrap_or(0)
753 .mul(self.max_fee_per_blob_gas().unwrap_or(0)),
754 )
755 }
756
757 max_cost
758 }
759
760 pub fn blob_gas(&self) -> Option<u64> {
761 match self {
762 Self::EIP4844(tx) => Some(tx.tx().tx().blob_gas()),
763 _ => None,
764 }
765 }
766
767 pub fn max_fee_per_blob_gas(&self) -> Option<u128> {
768 match self {
769 Self::EIP4844(tx) => Some(tx.tx().tx().max_fee_per_blob_gas),
770 _ => None,
771 }
772 }
773
774 pub fn essentials(&self) -> TransactionEssentials {
776 match self {
777 Self::Legacy(t) => TransactionEssentials {
778 kind: t.tx().to,
779 input: t.tx().input.clone(),
780 nonce: t.tx().nonce,
781 gas_limit: t.tx().gas_limit,
782 gas_price: Some(t.tx().gas_price),
783 max_fee_per_gas: None,
784 max_priority_fee_per_gas: None,
785 max_fee_per_blob_gas: None,
786 blob_versioned_hashes: None,
787 value: t.tx().value,
788 chain_id: t.tx().chain_id,
789 access_list: Default::default(),
790 },
791 Self::EIP2930(t) => TransactionEssentials {
792 kind: t.tx().to,
793 input: t.tx().input.clone(),
794 nonce: t.tx().nonce,
795 gas_limit: t.tx().gas_limit,
796 gas_price: Some(t.tx().gas_price),
797 max_fee_per_gas: None,
798 max_priority_fee_per_gas: None,
799 max_fee_per_blob_gas: None,
800 blob_versioned_hashes: None,
801 value: t.tx().value,
802 chain_id: Some(t.tx().chain_id),
803 access_list: t.tx().access_list.clone(),
804 },
805 Self::EIP1559(t) => TransactionEssentials {
806 kind: t.tx().to,
807 input: t.tx().input.clone(),
808 nonce: t.tx().nonce,
809 gas_limit: t.tx().gas_limit,
810 gas_price: None,
811 max_fee_per_gas: Some(t.tx().max_fee_per_gas),
812 max_priority_fee_per_gas: Some(t.tx().max_priority_fee_per_gas),
813 max_fee_per_blob_gas: None,
814 blob_versioned_hashes: None,
815 value: t.tx().value,
816 chain_id: Some(t.tx().chain_id),
817 access_list: t.tx().access_list.clone(),
818 },
819 Self::EIP4844(t) => TransactionEssentials {
820 kind: TxKind::Call(t.tx().tx().to),
821 input: t.tx().tx().input.clone(),
822 nonce: t.tx().tx().nonce,
823 gas_limit: t.tx().tx().gas_limit,
824 gas_price: Some(t.tx().tx().max_fee_per_blob_gas),
825 max_fee_per_gas: Some(t.tx().tx().max_fee_per_gas),
826 max_priority_fee_per_gas: Some(t.tx().tx().max_priority_fee_per_gas),
827 max_fee_per_blob_gas: Some(t.tx().tx().max_fee_per_blob_gas),
828 blob_versioned_hashes: Some(t.tx().tx().blob_versioned_hashes.clone()),
829 value: t.tx().tx().value,
830 chain_id: Some(t.tx().tx().chain_id),
831 access_list: t.tx().tx().access_list.clone(),
832 },
833 Self::EIP7702(t) => TransactionEssentials {
834 kind: TxKind::Call(t.tx().to),
835 input: t.tx().input.clone(),
836 nonce: t.tx().nonce,
837 gas_limit: t.tx().gas_limit,
838 gas_price: Some(t.tx().max_fee_per_gas),
839 max_fee_per_gas: Some(t.tx().max_fee_per_gas),
840 max_priority_fee_per_gas: Some(t.tx().max_priority_fee_per_gas),
841 max_fee_per_blob_gas: None,
842 blob_versioned_hashes: None,
843 value: t.tx().value,
844 chain_id: Some(t.tx().chain_id),
845 access_list: t.tx().access_list.clone(),
846 },
847 Self::Deposit(t) => TransactionEssentials {
848 kind: t.kind,
849 input: t.input.clone(),
850 nonce: t.nonce,
851 gas_limit: t.gas_limit,
852 gas_price: Some(0),
853 max_fee_per_gas: None,
854 max_priority_fee_per_gas: None,
855 max_fee_per_blob_gas: None,
856 blob_versioned_hashes: None,
857 value: t.value,
858 chain_id: t.chain_id(),
859 access_list: Default::default(),
860 },
861 }
862 }
863
864 pub fn nonce(&self) -> u64 {
865 match self {
866 Self::Legacy(t) => t.tx().nonce,
867 Self::EIP2930(t) => t.tx().nonce,
868 Self::EIP1559(t) => t.tx().nonce,
869 Self::EIP4844(t) => t.tx().tx().nonce,
870 Self::EIP7702(t) => t.tx().nonce,
871 Self::Deposit(t) => t.nonce,
872 }
873 }
874
875 pub fn chain_id(&self) -> Option<u64> {
876 match self {
877 Self::Legacy(t) => t.tx().chain_id,
878 Self::EIP2930(t) => Some(t.tx().chain_id),
879 Self::EIP1559(t) => Some(t.tx().chain_id),
880 Self::EIP4844(t) => Some(t.tx().tx().chain_id),
881 Self::EIP7702(t) => Some(t.tx().chain_id),
882 Self::Deposit(t) => t.chain_id(),
883 }
884 }
885
886 pub fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
887 match self {
888 Self::Legacy(tx) => Some(tx),
889 _ => None,
890 }
891 }
892
893 pub fn is_legacy(&self) -> bool {
895 matches!(self, Self::Legacy(_))
896 }
897
898 pub fn is_eip1559(&self) -> bool {
900 matches!(self, Self::EIP1559(_))
901 }
902
903 pub fn is_eip2930(&self) -> bool {
905 matches!(self, Self::EIP2930(_))
906 }
907
908 pub fn is_eip4844(&self) -> bool {
910 matches!(self, Self::EIP4844(_))
911 }
912
913 pub fn hash(&self) -> B256 {
918 match self {
919 Self::Legacy(t) => *t.hash(),
920 Self::EIP2930(t) => *t.hash(),
921 Self::EIP1559(t) => *t.hash(),
922 Self::EIP4844(t) => *t.hash(),
923 Self::EIP7702(t) => *t.hash(),
924 Self::Deposit(t) => t.hash(),
925 }
926 }
927
928 pub fn impersonated_hash(&self, sender: Address) -> B256 {
932 let mut buffer = Vec::new();
933 Encodable::encode(self, &mut buffer);
934 buffer.extend_from_slice(sender.as_ref());
935 B256::from_slice(alloy_primitives::utils::keccak256(&buffer).as_slice())
936 }
937
938 pub fn recover(&self) -> Result<Address, alloy_primitives::SignatureError> {
940 match self {
941 Self::Legacy(tx) => tx.recover_signer(),
942 Self::EIP2930(tx) => tx.recover_signer(),
943 Self::EIP1559(tx) => tx.recover_signer(),
944 Self::EIP4844(tx) => tx.recover_signer(),
945 Self::EIP7702(tx) => tx.recover_signer(),
946 Self::Deposit(tx) => tx.recover(),
947 }
948 }
949
950 pub fn kind(&self) -> TxKind {
952 match self {
953 Self::Legacy(tx) => tx.tx().to,
954 Self::EIP2930(tx) => tx.tx().to,
955 Self::EIP1559(tx) => tx.tx().to,
956 Self::EIP4844(tx) => TxKind::Call(tx.tx().tx().to),
957 Self::EIP7702(tx) => TxKind::Call(tx.tx().to),
958 Self::Deposit(tx) => tx.kind,
959 }
960 }
961
962 pub fn to(&self) -> Option<Address> {
964 self.kind().to().copied()
965 }
966
967 pub fn signature(&self) -> PrimitiveSignature {
969 match self {
970 Self::Legacy(tx) => *tx.signature(),
971 Self::EIP2930(tx) => *tx.signature(),
972 Self::EIP1559(tx) => *tx.signature(),
973 Self::EIP4844(tx) => *tx.signature(),
974 Self::EIP7702(tx) => *tx.signature(),
975 Self::Deposit(_) => PrimitiveSignature::from_scalars_and_parity(
976 B256::with_last_byte(1),
977 B256::with_last_byte(1),
978 false,
979 ),
980 }
981 }
982}
983
984impl Encodable for TypedTransaction {
985 fn encode(&self, out: &mut dyn bytes::BufMut) {
986 if !self.is_legacy() {
987 Header { list: false, payload_length: self.encode_2718_len() }.encode(out);
988 }
989
990 self.encode_2718(out);
991 }
992}
993
994impl Decodable for TypedTransaction {
995 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
996 let mut h_decode_copy = *buf;
997 let header = alloy_rlp::Header::decode(&mut h_decode_copy)?;
998
999 if header.list {
1001 return Ok(TxEnvelope::decode(buf)?.into());
1002 }
1003
1004 let ty = *h_decode_copy.first().ok_or(alloy_rlp::Error::Custom("empty slice"))?;
1006
1007 if ty != 0x7E {
1008 Ok(TxEnvelope::decode(buf)?.into())
1009 } else {
1010 Ok(Self::Deposit(DepositTransaction::decode_2718(buf)?))
1011 }
1012 }
1013}
1014
1015impl Typed2718 for TypedTransaction {
1016 fn ty(&self) -> u8 {
1017 self.r#type().unwrap_or(0)
1018 }
1019}
1020
1021impl Encodable2718 for TypedTransaction {
1022 fn encode_2718_len(&self) -> usize {
1023 match self {
1024 Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
1025 Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
1026 Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
1027 Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
1028 Self::EIP7702(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
1029 Self::Deposit(tx) => 1 + tx.length(),
1030 }
1031 }
1032
1033 fn encode_2718(&self, out: &mut dyn BufMut) {
1034 match self {
1035 Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1036 Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1037 Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1038 Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1039 Self::EIP7702(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1040 Self::Deposit(tx) => {
1041 tx.encode_2718(out);
1042 }
1043 }
1044 }
1045}
1046
1047impl Decodable2718 for TypedTransaction {
1048 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1049 if ty == 0x7E {
1050 return Ok(Self::Deposit(DepositTransaction::decode(buf)?))
1051 }
1052 match TxEnvelope::typed_decode(ty, buf)? {
1053 TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)),
1054 TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)),
1055 TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)),
1056 TxEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)),
1057 _ => unreachable!(),
1058 }
1059 }
1060
1061 fn fallback_decode(buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1062 match TxEnvelope::fallback_decode(buf)? {
1063 TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
1064 _ => unreachable!(),
1065 }
1066 }
1067}
1068
1069impl From<TxEnvelope> for TypedTransaction {
1070 fn from(value: TxEnvelope) -> Self {
1071 match value {
1072 TxEnvelope::Legacy(tx) => Self::Legacy(tx),
1073 TxEnvelope::Eip2930(tx) => Self::EIP2930(tx),
1074 TxEnvelope::Eip1559(tx) => Self::EIP1559(tx),
1075 TxEnvelope::Eip4844(tx) => Self::EIP4844(tx),
1076 _ => unreachable!(),
1077 }
1078 }
1079}
1080
1081#[derive(Clone, Debug, PartialEq, Eq)]
1082pub struct TransactionEssentials {
1083 pub kind: TxKind,
1084 pub input: Bytes,
1085 pub nonce: u64,
1086 pub gas_limit: u64,
1087 pub gas_price: Option<u128>,
1088 pub max_fee_per_gas: Option<u128>,
1089 pub max_priority_fee_per_gas: Option<u128>,
1090 pub max_fee_per_blob_gas: Option<u128>,
1091 pub blob_versioned_hashes: Option<Vec<B256>>,
1092 pub value: U256,
1093 pub chain_id: Option<u64>,
1094 pub access_list: AccessList,
1095}
1096
1097#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
1099pub struct TransactionInfo {
1100 pub transaction_hash: B256,
1101 pub transaction_index: u64,
1102 pub from: Address,
1103 pub to: Option<Address>,
1104 pub contract_address: Option<Address>,
1105 pub traces: Vec<CallTraceNode>,
1106 pub exit: InstructionResult,
1107 pub out: Option<Bytes>,
1108 pub nonce: u64,
1109 pub gas_used: u64,
1110}
1111
1112#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
1113#[serde(rename_all = "camelCase")]
1114pub struct DepositReceipt<T = Receipt<alloy_primitives::Log>> {
1115 #[serde(flatten)]
1116 pub inner: ReceiptWithBloom<T>,
1117 #[serde(default, with = "alloy_serde::quantity::opt")]
1118 pub deposit_nonce: Option<u64>,
1119 #[serde(default, with = "alloy_serde::quantity::opt")]
1120 pub deposit_receipt_version: Option<u64>,
1121}
1122
1123impl DepositReceipt {
1124 fn payload_len(&self) -> usize {
1125 self.inner.receipt.status.length() +
1126 self.inner.receipt.cumulative_gas_used.length() +
1127 self.inner.logs_bloom.length() +
1128 self.inner.receipt.logs.length() +
1129 self.deposit_nonce.map_or(0, |n| n.length()) +
1130 self.deposit_receipt_version.map_or(0, |n| n.length())
1131 }
1132
1133 fn receipt_rlp_header(&self) -> alloy_rlp::Header {
1135 alloy_rlp::Header { list: true, payload_length: self.payload_len() }
1136 }
1137
1138 fn encode_fields(&self, out: &mut dyn BufMut) {
1140 self.receipt_rlp_header().encode(out);
1141 self.inner.status().encode(out);
1142 self.inner.receipt.cumulative_gas_used.encode(out);
1143 self.inner.logs_bloom.encode(out);
1144 self.inner.receipt.logs.encode(out);
1145 if let Some(n) = self.deposit_nonce {
1146 n.encode(out);
1147 }
1148 if let Some(n) = self.deposit_receipt_version {
1149 n.encode(out);
1150 }
1151 }
1152
1153 fn decode_receipt(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1155 let b: &mut &[u8] = &mut &**buf;
1156 let rlp_head = alloy_rlp::Header::decode(b)?;
1157 if !rlp_head.list {
1158 return Err(alloy_rlp::Error::UnexpectedString);
1159 }
1160 let started_len = b.len();
1161 let remaining = |b: &[u8]| rlp_head.payload_length - (started_len - b.len()) > 0;
1162
1163 let status = Decodable::decode(b)?;
1164 let cumulative_gas_used = Decodable::decode(b)?;
1165 let logs_bloom = Decodable::decode(b)?;
1166 let logs: Vec<Log> = Decodable::decode(b)?;
1167 let deposit_nonce = remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?;
1168 let deposit_nonce_version =
1169 remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?;
1170
1171 let this = Self {
1172 inner: ReceiptWithBloom {
1173 receipt: Receipt { status, cumulative_gas_used, logs },
1174 logs_bloom,
1175 },
1176 deposit_nonce,
1177 deposit_receipt_version: deposit_nonce_version,
1178 };
1179
1180 let consumed = started_len - b.len();
1181 if consumed != rlp_head.payload_length {
1182 return Err(alloy_rlp::Error::ListLengthMismatch {
1183 expected: rlp_head.payload_length,
1184 got: consumed,
1185 });
1186 }
1187
1188 *buf = *b;
1189 Ok(this)
1190 }
1191}
1192
1193impl alloy_rlp::Encodable for DepositReceipt {
1194 fn encode(&self, out: &mut dyn BufMut) {
1195 self.encode_fields(out);
1196 }
1197
1198 fn length(&self) -> usize {
1199 let payload_length = self.payload_len();
1200 payload_length + length_of_length(payload_length)
1201 }
1202}
1203
1204impl alloy_rlp::Decodable for DepositReceipt {
1205 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1206 Self::decode_receipt(buf)
1207 }
1208}
1209
1210#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1211#[serde(tag = "type")]
1212pub enum TypedReceipt<T = Receipt<alloy_primitives::Log>> {
1213 #[serde(rename = "0x0", alias = "0x00")]
1214 Legacy(ReceiptWithBloom<T>),
1215 #[serde(rename = "0x1", alias = "0x01")]
1216 EIP2930(ReceiptWithBloom<T>),
1217 #[serde(rename = "0x2", alias = "0x02")]
1218 EIP1559(ReceiptWithBloom<T>),
1219 #[serde(rename = "0x3", alias = "0x03")]
1220 EIP4844(ReceiptWithBloom<T>),
1221 #[serde(rename = "0x4", alias = "0x04")]
1222 EIP7702(ReceiptWithBloom<T>),
1223 #[serde(rename = "0x7E", alias = "0x7e")]
1224 Deposit(DepositReceipt<T>),
1225}
1226
1227impl<T> TypedReceipt<T> {
1228 pub fn as_receipt_with_bloom(&self) -> &ReceiptWithBloom<T> {
1229 match self {
1230 Self::Legacy(r) |
1231 Self::EIP1559(r) |
1232 Self::EIP2930(r) |
1233 Self::EIP4844(r) |
1234 Self::EIP7702(r) => r,
1235 Self::Deposit(r) => &r.inner,
1236 }
1237 }
1238}
1239
1240impl<T> From<TypedReceipt<T>> for ReceiptWithBloom<T> {
1241 fn from(value: TypedReceipt<T>) -> Self {
1242 match value {
1243 TypedReceipt::Legacy(r) |
1244 TypedReceipt::EIP1559(r) |
1245 TypedReceipt::EIP2930(r) |
1246 TypedReceipt::EIP4844(r) |
1247 TypedReceipt::EIP7702(r) => r,
1248 TypedReceipt::Deposit(r) => r.inner,
1249 }
1250 }
1251}
1252
1253impl From<TypedReceipt<Receipt<alloy_rpc_types::Log>>> for OtsReceipt {
1254 fn from(value: TypedReceipt<Receipt<alloy_rpc_types::Log>>) -> Self {
1255 let r#type = match value {
1256 TypedReceipt::Legacy(_) => 0x00,
1257 TypedReceipt::EIP2930(_) => 0x01,
1258 TypedReceipt::EIP1559(_) => 0x02,
1259 TypedReceipt::EIP4844(_) => 0x03,
1260 TypedReceipt::EIP7702(_) => 0x04,
1261 TypedReceipt::Deposit(_) => 0x7E,
1262 } as u8;
1263 let receipt = ReceiptWithBloom::<Receipt<alloy_rpc_types::Log>>::from(value);
1264 let status = receipt.status();
1265 let cumulative_gas_used = receipt.cumulative_gas_used();
1266 let logs = receipt.logs().to_vec();
1267 let logs_bloom = receipt.logs_bloom;
1268
1269 Self { status, cumulative_gas_used, logs: Some(logs), logs_bloom: Some(logs_bloom), r#type }
1270 }
1271}
1272
1273impl TypedReceipt {
1274 pub fn cumulative_gas_used(&self) -> u64 {
1275 self.as_receipt_with_bloom().cumulative_gas_used()
1276 }
1277
1278 pub fn logs_bloom(&self) -> &Bloom {
1279 &self.as_receipt_with_bloom().logs_bloom
1280 }
1281
1282 pub fn logs(&self) -> &[Log] {
1283 self.as_receipt_with_bloom().logs()
1284 }
1285}
1286
1287impl From<ReceiptEnvelope<alloy_rpc_types::Log>> for TypedReceipt<Receipt<alloy_rpc_types::Log>> {
1288 fn from(value: ReceiptEnvelope<alloy_rpc_types::Log>) -> Self {
1289 match value {
1290 ReceiptEnvelope::Legacy(r) => Self::Legacy(r),
1291 ReceiptEnvelope::Eip2930(r) => Self::EIP2930(r),
1292 ReceiptEnvelope::Eip1559(r) => Self::EIP1559(r),
1293 ReceiptEnvelope::Eip4844(r) => Self::EIP4844(r),
1294 _ => unreachable!(),
1295 }
1296 }
1297}
1298
1299impl Encodable for TypedReceipt {
1300 fn encode(&self, out: &mut dyn bytes::BufMut) {
1301 match self {
1302 Self::Legacy(r) => r.encode(out),
1303 receipt => {
1304 let payload_len = match receipt {
1305 Self::EIP2930(r) => r.length() + 1,
1306 Self::EIP1559(r) => r.length() + 1,
1307 Self::EIP4844(r) => r.length() + 1,
1308 Self::Deposit(r) => r.length() + 1,
1309 _ => unreachable!("receipt already matched"),
1310 };
1311
1312 match receipt {
1313 Self::EIP2930(r) => {
1314 Header { list: true, payload_length: payload_len }.encode(out);
1315 1u8.encode(out);
1316 r.encode(out);
1317 }
1318 Self::EIP1559(r) => {
1319 Header { list: true, payload_length: payload_len }.encode(out);
1320 2u8.encode(out);
1321 r.encode(out);
1322 }
1323 Self::EIP4844(r) => {
1324 Header { list: true, payload_length: payload_len }.encode(out);
1325 3u8.encode(out);
1326 r.encode(out);
1327 }
1328 Self::Deposit(r) => {
1329 Header { list: true, payload_length: payload_len }.encode(out);
1330 0x7Eu8.encode(out);
1331 r.encode(out);
1332 }
1333 _ => unreachable!("receipt already matched"),
1334 }
1335 }
1336 }
1337 }
1338}
1339
1340impl Decodable for TypedReceipt {
1341 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1342 use bytes::Buf;
1343 use std::cmp::Ordering;
1344
1345 let rlp_type = *buf
1349 .first()
1350 .ok_or(alloy_rlp::Error::Custom("cannot decode a receipt from empty bytes"))?;
1351
1352 match rlp_type.cmp(&alloy_rlp::EMPTY_LIST_CODE) {
1353 Ordering::Less => {
1354 let _header = Header::decode(buf)?;
1356 let receipt_type = *buf.first().ok_or(alloy_rlp::Error::Custom(
1357 "typed receipt cannot be decoded from an empty slice",
1358 ))?;
1359 if receipt_type == 0x01 {
1360 buf.advance(1);
1361 <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::EIP2930)
1362 } else if receipt_type == 0x02 {
1363 buf.advance(1);
1364 <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::EIP1559)
1365 } else if receipt_type == 0x03 {
1366 buf.advance(1);
1367 <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::EIP4844)
1368 } else if receipt_type == 0x7E {
1369 buf.advance(1);
1370 <DepositReceipt as Decodable>::decode(buf).map(TypedReceipt::Deposit)
1371 } else {
1372 Err(alloy_rlp::Error::Custom("invalid receipt type"))
1373 }
1374 }
1375 Ordering::Equal => {
1376 Err(alloy_rlp::Error::Custom("an empty list is not a valid receipt encoding"))
1377 }
1378 Ordering::Greater => {
1379 <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::Legacy)
1380 }
1381 }
1382 }
1383}
1384
1385impl Typed2718 for TypedReceipt {
1386 fn ty(&self) -> u8 {
1387 match self {
1388 Self::Legacy(_) => alloy_consensus::constants::LEGACY_TX_TYPE_ID,
1389 Self::EIP2930(_) => alloy_consensus::constants::EIP2930_TX_TYPE_ID,
1390 Self::EIP1559(_) => alloy_consensus::constants::EIP1559_TX_TYPE_ID,
1391 Self::EIP4844(_) => alloy_consensus::constants::EIP4844_TX_TYPE_ID,
1392 Self::EIP7702(_) => alloy_consensus::constants::EIP7702_TX_TYPE_ID,
1393 Self::Deposit(_) => DEPOSIT_TX_TYPE_ID,
1394 }
1395 }
1396}
1397
1398impl Encodable2718 for TypedReceipt {
1399 fn encode_2718_len(&self) -> usize {
1400 match self {
1401 Self::Legacy(r) => ReceiptEnvelope::Legacy(r.clone()).encode_2718_len(),
1402 Self::EIP2930(r) => ReceiptEnvelope::Eip2930(r.clone()).encode_2718_len(),
1403 Self::EIP1559(r) => ReceiptEnvelope::Eip1559(r.clone()).encode_2718_len(),
1404 Self::EIP4844(r) => ReceiptEnvelope::Eip4844(r.clone()).encode_2718_len(),
1405 Self::EIP7702(r) => 1 + r.length(),
1406 Self::Deposit(r) => 1 + r.length(),
1407 }
1408 }
1409
1410 fn encode_2718(&self, out: &mut dyn BufMut) {
1411 if let Some(ty) = self.type_flag() {
1412 out.put_u8(ty);
1413 }
1414 match self {
1415 Self::Legacy(r) |
1416 Self::EIP2930(r) |
1417 Self::EIP1559(r) |
1418 Self::EIP4844(r) |
1419 Self::EIP7702(r) => r.encode(out),
1420 Self::Deposit(r) => r.encode(out),
1421 }
1422 }
1423}
1424
1425impl Decodable2718 for TypedReceipt {
1426 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1427 if ty == 0x7E {
1428 return Ok(Self::Deposit(DepositReceipt::decode(buf)?));
1429 }
1430 match ReceiptEnvelope::typed_decode(ty, buf)? {
1431 ReceiptEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)),
1432 ReceiptEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)),
1433 ReceiptEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)),
1434 _ => unreachable!(),
1435 }
1436 }
1437
1438 fn fallback_decode(buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1439 match ReceiptEnvelope::fallback_decode(buf)? {
1440 ReceiptEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
1441 _ => unreachable!(),
1442 }
1443 }
1444}
1445
1446pub type ReceiptResponse = TransactionReceipt<TypedReceipt<Receipt<alloy_rpc_types::Log>>>;
1447
1448pub fn convert_to_anvil_receipt(receipt: AnyTransactionReceipt) -> Option<ReceiptResponse> {
1449 let WithOtherFields {
1450 inner:
1451 TransactionReceipt {
1452 transaction_hash,
1453 transaction_index,
1454 block_hash,
1455 block_number,
1456 gas_used,
1457 contract_address,
1458 effective_gas_price,
1459 from,
1460 to,
1461 blob_gas_price,
1462 blob_gas_used,
1463 inner: AnyReceiptEnvelope { inner: receipt_with_bloom, r#type },
1464 },
1465 other,
1466 } = receipt;
1467
1468 Some(TransactionReceipt {
1469 transaction_hash,
1470 transaction_index,
1471 block_hash,
1472 block_number,
1473 gas_used,
1474 contract_address,
1475 effective_gas_price,
1476 from,
1477 to,
1478 blob_gas_price,
1479 blob_gas_used,
1480 inner: match r#type {
1481 0x00 => TypedReceipt::Legacy(receipt_with_bloom),
1482 0x01 => TypedReceipt::EIP2930(receipt_with_bloom),
1483 0x02 => TypedReceipt::EIP1559(receipt_with_bloom),
1484 0x03 => TypedReceipt::EIP4844(receipt_with_bloom),
1485 0x7E => TypedReceipt::Deposit(DepositReceipt {
1486 inner: receipt_with_bloom,
1487 deposit_nonce: other
1488 .get_deserialized::<U64>("depositNonce")
1489 .transpose()
1490 .ok()?
1491 .map(|v| v.to()),
1492 deposit_receipt_version: other
1493 .get_deserialized::<U64>("depositReceiptVersion")
1494 .transpose()
1495 .ok()?
1496 .map(|v| v.to()),
1497 }),
1498 _ => return None,
1499 },
1500 })
1501}
1502
1503#[cfg(test)]
1504mod tests {
1505 use alloy_primitives::{b256, hex, LogData};
1506 use std::str::FromStr;
1507
1508 use super::*;
1509
1510 #[test]
1511 fn test_decode_call() {
1512 let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..];
1513 let decoded = TypedTransaction::decode(&mut &bytes_first[..]).unwrap();
1514
1515 let tx = TxLegacy {
1516 nonce: 2u64,
1517 gas_price: 1000000000u128,
1518 gas_limit: 100000,
1519 to: TxKind::Call(Address::from_slice(
1520 &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..],
1521 )),
1522 value: U256::from(1000000000000000u64),
1523 input: Bytes::default(),
1524 chain_id: Some(4),
1525 };
1526
1527 let signature = PrimitiveSignature::from_str("0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b").unwrap();
1528
1529 let tx = TypedTransaction::Legacy(Signed::new_unchecked(
1530 tx,
1531 signature,
1532 b256!("0xa517b206d2223278f860ea017d3626cacad4f52ff51030dc9a96b432f17f8d34"),
1533 ));
1534
1535 assert_eq!(tx, decoded);
1536 }
1537
1538 #[test]
1539 fn test_decode_create_goerli() {
1540 let tx_bytes =
1542 hex::decode("02f901ee05228459682f008459682f11830209bf8080b90195608060405234801561001057600080fd5b50610175806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80630c49c36c14610030575b600080fd5b61003861004e565b604051610045919061011d565b60405180910390f35b60606020600052600f6020527f68656c6c6f2073746174656d696e64000000000000000000000000000000000060405260406000f35b600081519050919050565b600082825260208201905092915050565b60005b838110156100be5780820151818401526020810190506100a3565b838111156100cd576000848401525b50505050565b6000601f19601f8301169050919050565b60006100ef82610084565b6100f9818561008f565b93506101098185602086016100a0565b610112816100d3565b840191505092915050565b6000602082019050818103600083015261013781846100e4565b90509291505056fea264697066735822122051449585839a4ea5ac23cae4552ef8a96b64ff59d0668f76bfac3796b2bdbb3664736f6c63430008090033c080a0136ebffaa8fc8b9fda9124de9ccb0b1f64e90fbd44251b4c4ac2501e60b104f9a07eb2999eec6d185ef57e91ed099afb0a926c5b536f0155dd67e537c7476e1471")
1543 .unwrap();
1544 let _decoded = TypedTransaction::decode(&mut &tx_bytes[..]).unwrap();
1545 }
1546
1547 #[test]
1548 fn can_recover_sender() {
1549 let bytes = hex::decode("02f872018307910d808507204d2cb1827d0094388c818ca8b9251b393131c08a736a67ccb19297880320d04823e2701c80c001a0cf024f4815304df2867a1a74e9d2707b6abda0337d2d54a4438d453f4160f190a07ac0e6b3bc9395b5b9c8b9e6d77204a236577a5b18467b9175c01de4faa208d9").unwrap();
1551
1552 let Ok(TypedTransaction::EIP1559(tx)) = TypedTransaction::decode(&mut &bytes[..]) else {
1553 panic!("decoding TypedTransaction failed");
1554 };
1555
1556 assert_eq!(
1557 tx.hash(),
1558 &"0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f"
1559 .parse::<B256>()
1560 .unwrap()
1561 );
1562 assert_eq!(
1563 tx.recover_signer().unwrap(),
1564 "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5".parse::<Address>().unwrap()
1565 );
1566 }
1567
1568 #[test]
1571 fn test_decode_live_4844_tx() {
1572 use alloy_primitives::{address, b256};
1573
1574 let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
1576 let res = TypedTransaction::decode(&mut raw_tx.as_slice()).unwrap();
1577 assert_eq!(res.r#type(), Some(3));
1578
1579 let tx = match res {
1580 TypedTransaction::EIP4844(tx) => tx,
1581 _ => unreachable!(),
1582 };
1583
1584 assert_eq!(tx.tx().tx().to, address!("0x11E9CA82A3a762b4B5bd264d4173a242e7a77064"));
1585
1586 assert_eq!(
1587 tx.tx().tx().blob_versioned_hashes,
1588 vec![
1589 b256!("0x012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"),
1590 b256!("0x0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"),
1591 b256!("0x013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"),
1592 b256!("0x01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"),
1593 b256!("0x011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549")
1594 ]
1595 );
1596
1597 let from = tx.recover_signer().unwrap();
1598 assert_eq!(from, address!("0xA83C816D4f9b2783761a22BA6FADB0eB0606D7B2"));
1599 }
1600
1601 #[test]
1602 fn test_decode_encode_deposit_tx() {
1603 let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7"
1605 .parse::<TxHash>()
1606 .unwrap();
1607
1608 let raw_tx = alloy_primitives::hex::decode(
1610 "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080",
1611 )
1612 .unwrap();
1613 let dep_tx = TypedTransaction::decode(&mut raw_tx.as_slice()).unwrap();
1614
1615 let mut encoded = Vec::new();
1616 dep_tx.encode_2718(&mut encoded);
1617
1618 assert_eq!(raw_tx, encoded);
1619
1620 assert_eq!(tx_hash, dep_tx.hash());
1621 }
1622
1623 #[test]
1624 fn can_recover_sender_not_normalized() {
1625 let bytes = hex::decode("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap();
1626
1627 let Ok(TypedTransaction::Legacy(tx)) = TypedTransaction::decode(&mut &bytes[..]) else {
1628 panic!("decoding TypedTransaction failed");
1629 };
1630
1631 assert_eq!(tx.tx().input, Bytes::from(b""));
1632 assert_eq!(tx.tx().gas_price, 1);
1633 assert_eq!(tx.tx().gas_limit, 21000);
1634 assert_eq!(tx.tx().nonce, 0);
1635 if let TxKind::Call(to) = tx.tx().to {
1636 assert_eq!(
1637 to,
1638 "0x095e7baea6a6c7c4c2dfeb977efac326af552d87".parse::<Address>().unwrap()
1639 );
1640 } else {
1641 panic!("expected a call transaction");
1642 }
1643 assert_eq!(tx.tx().value, U256::from(0x0au64));
1644 assert_eq!(
1645 tx.recover_signer().unwrap(),
1646 "0f65fe9276bc9a24ae7083ae28e2660ef72df99e".parse::<Address>().unwrap()
1647 );
1648 }
1649
1650 #[test]
1651 fn encode_legacy_receipt() {
1652 let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap();
1653
1654 let mut data = vec![];
1655 let receipt = TypedReceipt::Legacy(ReceiptWithBloom {
1656 receipt: Receipt {
1657 status: false.into(),
1658 cumulative_gas_used: 0x1,
1659 logs: vec![Log {
1660 address: Address::from_str("0000000000000000000000000000000000000011").unwrap(),
1661 data: LogData::new_unchecked(
1662 vec![
1663 B256::from_str(
1664 "000000000000000000000000000000000000000000000000000000000000dead",
1665 )
1666 .unwrap(),
1667 B256::from_str(
1668 "000000000000000000000000000000000000000000000000000000000000beef",
1669 )
1670 .unwrap(),
1671 ],
1672 Bytes::from_str("0100ff").unwrap(),
1673 ),
1674 }],
1675 },
1676 logs_bloom: [0; 256].into(),
1677 });
1678
1679 receipt.encode(&mut data);
1680
1681 assert_eq!(receipt.length(), expected.len());
1683 assert_eq!(data, expected);
1684 }
1685
1686 #[test]
1687 fn decode_legacy_receipt() {
1688 let data = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap();
1689
1690 let expected = TypedReceipt::Legacy(ReceiptWithBloom {
1691 receipt: Receipt {
1692 status: false.into(),
1693 cumulative_gas_used: 0x1,
1694 logs: vec![Log {
1695 address: Address::from_str("0000000000000000000000000000000000000011").unwrap(),
1696 data: LogData::new_unchecked(
1697 vec![
1698 B256::from_str(
1699 "000000000000000000000000000000000000000000000000000000000000dead",
1700 )
1701 .unwrap(),
1702 B256::from_str(
1703 "000000000000000000000000000000000000000000000000000000000000beef",
1704 )
1705 .unwrap(),
1706 ],
1707 Bytes::from_str("0100ff").unwrap(),
1708 ),
1709 }],
1710 },
1711 logs_bloom: [0; 256].into(),
1712 });
1713
1714 let receipt = TypedReceipt::decode(&mut &data[..]).unwrap();
1715
1716 assert_eq!(receipt, expected);
1717 }
1718
1719 #[test]
1720 fn deser_to_type_tx() {
1721 let tx = r#"
1722 {
1723 "EIP1559": {
1724 "chainId": "0x7a69",
1725 "nonce": "0x0",
1726 "gas": "0x5209",
1727 "maxFeePerGas": "0x77359401",
1728 "maxPriorityFeePerGas": "0x1",
1729 "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
1730 "value": "0x0",
1731 "accessList": [],
1732 "input": "0x",
1733 "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0",
1734 "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd",
1735 "yParity": "0x0",
1736 "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"
1737 }
1738 }"#;
1739
1740 let _typed_tx: TypedTransaction = serde_json::from_str(tx).unwrap();
1741 }
1742}