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