1#[cfg(feature = "optimism")]
2use alloy_consensus::{Sealed, Transaction as _};
3use alloy_consensus::{
4 Signed, TransactionEnvelope, TxEip1559, TxEip2930, TxEnvelope, TxLegacy, TxType, Typed2718,
5 crypto::RecoveryError,
6 transaction::{
7 SignerRecoverable, TxEip7702, TxHashRef,
8 eip4844::{TxEip4844Variant, TxEip4844WithSidecar},
9 },
10};
11use alloy_evm::{FromRecoveredTx, FromTxWithEncoded};
12use alloy_network::{AnyRpcTransaction, AnyTxEnvelope, TransactionResponse};
13use alloy_primitives::{Address, B256, Bytes, TxHash};
14use alloy_rpc_types::ConversionError;
15#[cfg(feature = "optimism")]
16use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, POST_EXEC_TX_TYPE_ID, TxDeposit, TxPostExec};
17use revm::context::TxEnv;
18use serde::{Deserialize, Serialize};
19use tempo_primitives::{AASigned, TempoTransaction};
20use tempo_revm::TempoTxEnv;
21
22#[allow(clippy::large_enum_variant)]
26#[derive(Clone, Debug, TransactionEnvelope)]
27#[envelope(
28 tx_type_name = FoundryTxType,
29 typed = FoundryTypedTx,
30)]
31pub enum FoundryTxEnvelope {
32 #[envelope(ty = 0)]
34 Legacy(Signed<TxLegacy>),
35 #[envelope(ty = 1)]
39 Eip2930(Signed<TxEip2930>),
40 #[envelope(ty = 2)]
44 Eip1559(Signed<TxEip1559>),
45 #[envelope(ty = 3)]
49 Eip4844(Signed<TxEip4844Variant>),
50 #[envelope(ty = 4)]
54 Eip7702(Signed<TxEip7702>),
55 #[cfg(feature = "optimism")]
59 #[envelope(ty = 126)]
60 Deposit(Sealed<TxDeposit>),
61 #[cfg(feature = "optimism")]
63 #[envelope(ty = 0x7D)]
64 PostExec(Sealed<TxPostExec>),
65 #[envelope(ty = 0x76, typed = TempoTransaction)]
69 Tempo(AASigned),
70}
71
72impl FoundryTxEnvelope {
73 pub fn try_into_eth(self) -> Result<TxEnvelope, Self> {
77 match self {
78 Self::Legacy(tx) => Ok(TxEnvelope::Legacy(tx)),
79 Self::Eip2930(tx) => Ok(TxEnvelope::Eip2930(tx)),
80 Self::Eip1559(tx) => Ok(TxEnvelope::Eip1559(tx)),
81 Self::Eip4844(tx) => Ok(TxEnvelope::Eip4844(tx)),
82 Self::Eip7702(tx) => Ok(TxEnvelope::Eip7702(tx)),
83 #[cfg(feature = "optimism")]
84 Self::Deposit(_) => Err(self),
85 #[cfg(feature = "optimism")]
86 Self::PostExec(_) => Err(self),
87 Self::Tempo(_) => Err(self),
88 }
89 }
90
91 pub const fn sidecar(&self) -> Option<&TxEip4844WithSidecar> {
92 match self {
93 Self::Eip4844(signed_variant) => match signed_variant.tx() {
94 TxEip4844Variant::TxEip4844WithSidecar(with_sidecar) => Some(with_sidecar),
95 _ => None,
96 },
97 _ => None,
98 }
99 }
100
101 pub fn hash(&self) -> B256 {
108 match self {
109 Self::Legacy(t) => *t.hash(),
110 Self::Eip2930(t) => *t.hash(),
111 Self::Eip1559(t) => *t.hash(),
112 Self::Eip4844(t) => *t.hash(),
113 Self::Eip7702(t) => *t.hash(),
114 #[cfg(feature = "optimism")]
115 Self::Deposit(t) => t.tx_hash(),
116 #[cfg(feature = "optimism")]
117 Self::PostExec(t) => t.tx_hash(),
118 Self::Tempo(t) => *t.hash(),
119 }
120 }
121
122 pub const fn is_tempo(&self) -> bool {
124 matches!(self, Self::Tempo(_))
125 }
126
127 pub fn has_nonzero_tempo_nonce_key(&self) -> bool {
129 matches!(self, Self::Tempo(tx) if !tx.tx().nonce_key.is_zero())
130 }
131
132 pub fn recover(&self) -> Result<Address, RecoveryError> {
134 Ok(match self {
135 Self::Legacy(tx) => tx.recover_signer()?,
136 Self::Eip2930(tx) => tx.recover_signer()?,
137 Self::Eip1559(tx) => tx.recover_signer()?,
138 Self::Eip4844(tx) => tx.recover_signer()?,
139 Self::Eip7702(tx) => tx.recover_signer()?,
140 #[cfg(feature = "optimism")]
141 Self::Deposit(tx) => tx.from,
142 #[cfg(feature = "optimism")]
143 Self::PostExec(tx) => tx.inner().signer_address(),
144 Self::Tempo(tx) => tx.signature().recover_signer(&tx.signature_hash())?,
145 })
146 }
147
148 pub fn clone_into_tempo_envelope(&self) -> Option<tempo_primitives::TempoTxEnvelope> {
150 Some(match self {
151 Self::Legacy(tx) => tempo_primitives::TempoTxEnvelope::Legacy(tx.clone()),
152 Self::Eip2930(tx) => tempo_primitives::TempoTxEnvelope::Eip2930(tx.clone()),
153 Self::Eip1559(tx) => tempo_primitives::TempoTxEnvelope::Eip1559(tx.clone()),
154 Self::Eip7702(tx) => tempo_primitives::TempoTxEnvelope::Eip7702(tx.clone()),
155 Self::Tempo(tx) => tempo_primitives::TempoTxEnvelope::AA(tx.clone()),
156 Self::Eip4844(_) => return None,
157 #[cfg(feature = "optimism")]
158 Self::Deposit(_) | Self::PostExec(_) => return None,
159 })
160 }
161
162 pub fn classify_t5_payment_lane(&self) -> PaymentLaneClassification {
164 let Some(tx) = self.clone_into_tempo_envelope() else {
165 return PaymentLaneClassification::general(
166 PaymentLaneReason::UnsupportedTransactionType,
167 );
168 };
169
170 if tx.is_payment_v2() {
171 PaymentLaneClassification::payment()
172 } else {
173 PaymentLaneClassification::general(PaymentLaneReason::NotPaymentLane)
174 }
175 }
176}
177
178#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
180#[serde(rename_all = "camelCase")]
181pub struct PaymentLaneClassification {
182 pub lane: PaymentLane,
184 pub payment: bool,
186 #[serde(skip_serializing_if = "Option::is_none")]
188 pub reason: Option<PaymentLaneReason>,
189}
190
191impl PaymentLaneClassification {
192 pub const fn payment() -> Self {
194 Self { lane: PaymentLane::Payment, payment: true, reason: None }
195 }
196
197 pub const fn general(reason: PaymentLaneReason) -> Self {
199 Self { lane: PaymentLane::General, payment: false, reason: Some(reason) }
200 }
201}
202
203#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
205#[serde(rename_all = "snake_case")]
206pub enum PaymentLane {
207 Payment,
208 General,
209}
210
211#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
213#[serde(rename_all = "snake_case")]
214pub enum PaymentLaneReason {
215 NotTempo,
217 T5NotActive,
219 UnsupportedTransactionType,
221 NotPaymentLane,
223}
224
225impl TxHashRef for FoundryTxEnvelope {
226 fn tx_hash(&self) -> &TxHash {
227 match self {
228 Self::Legacy(t) => t.hash(),
229 Self::Eip2930(t) => t.hash(),
230 Self::Eip1559(t) => t.hash(),
231 Self::Eip4844(t) => t.hash(),
232 Self::Eip7702(t) => t.hash(),
233 #[cfg(feature = "optimism")]
234 Self::Deposit(t) => t.hash_ref(),
235 #[cfg(feature = "optimism")]
236 Self::PostExec(t) => t.hash_ref(),
237 Self::Tempo(t) => t.hash(),
238 }
239 }
240}
241
242impl SignerRecoverable for FoundryTxEnvelope {
243 fn recover_signer(&self) -> Result<Address, RecoveryError> {
244 self.recover()
245 }
246
247 fn recover_signer_unchecked(&self) -> Result<Address, RecoveryError> {
248 self.recover()
249 }
250}
251
252impl TryFrom<FoundryTxEnvelope> for TxEnvelope {
253 type Error = FoundryTxEnvelope;
254
255 fn try_from(envelope: FoundryTxEnvelope) -> Result<Self, Self::Error> {
256 envelope.try_into_eth()
257 }
258}
259
260impl From<TxEnvelope> for FoundryTxEnvelope {
261 fn from(tx: TxEnvelope) -> Self {
262 match tx {
263 TxEnvelope::Legacy(tx) => Self::Legacy(tx),
264 TxEnvelope::Eip2930(tx) => Self::Eip2930(tx),
265 TxEnvelope::Eip1559(tx) => Self::Eip1559(tx),
266 TxEnvelope::Eip4844(tx) => Self::Eip4844(tx),
267 TxEnvelope::Eip7702(tx) => Self::Eip7702(tx),
268 }
269 }
270}
271
272impl From<tempo_primitives::TempoTxEnvelope> for FoundryTxEnvelope {
273 fn from(tx: tempo_primitives::TempoTxEnvelope) -> Self {
274 match tx {
275 tempo_primitives::TempoTxEnvelope::Legacy(tx) => Self::Legacy(tx),
276 tempo_primitives::TempoTxEnvelope::Eip2930(tx) => Self::Eip2930(tx),
277 tempo_primitives::TempoTxEnvelope::Eip1559(tx) => Self::Eip1559(tx),
278 tempo_primitives::TempoTxEnvelope::Eip7702(tx) => Self::Eip7702(tx),
279 tempo_primitives::TempoTxEnvelope::AA(tx) => Self::Tempo(tx),
280 }
281 }
282}
283
284impl TryFrom<AnyRpcTransaction> for FoundryTxEnvelope {
285 type Error = ConversionError;
286
287 fn try_from(value: AnyRpcTransaction) -> Result<Self, Self::Error> {
288 let transaction = value.into_inner();
289 let from = transaction.from();
290 match transaction.into_inner() {
291 AnyTxEnvelope::Ethereum(tx) => match tx {
292 TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
293 TxEnvelope::Eip2930(tx) => Ok(Self::Eip2930(tx)),
294 TxEnvelope::Eip1559(tx) => Ok(Self::Eip1559(tx)),
295 TxEnvelope::Eip4844(tx) => Ok(Self::Eip4844(tx)),
296 TxEnvelope::Eip7702(tx) => Ok(Self::Eip7702(tx)),
297 },
298 AnyTxEnvelope::Unknown(tx) => {
299 #[cfg(feature = "optimism")]
300 {
301 let mut tx = tx;
302 let _ = from;
303 if tx.ty() == DEPOSIT_TX_TYPE_ID {
305 tx.inner
306 .fields
307 .insert("from".to_string(), serde_json::to_value(from).unwrap());
308 let deposit_tx =
309 tx.inner.fields.deserialize_into::<TxDeposit>().map_err(|e| {
310 ConversionError::Custom(format!(
311 "Failed to deserialize deposit tx: {e}"
312 ))
313 })?;
314
315 return Ok(Self::Deposit(Sealed::new(deposit_tx)));
316 }
317
318 if tx.ty() == POST_EXEC_TX_TYPE_ID {
319 let post_exec_tx =
320 tx.inner.fields.deserialize_into::<TxPostExec>().map_err(|e| {
321 ConversionError::Custom(format!(
322 "Failed to deserialize post-exec tx: {e}"
323 ))
324 })?;
325
326 return Ok(Self::PostExec(Sealed::new(post_exec_tx)));
327 }
328
329 let tx_type = tx.ty();
330 Err(ConversionError::Custom(format!(
331 "Unknown transaction type: 0x{tx_type:02X}"
332 )))
333 }
334 #[cfg(not(feature = "optimism"))]
335 {
336 let _ = from;
337 let tx_type = tx.ty();
338 Err(ConversionError::Custom(format!(
339 "Unknown transaction type: 0x{tx_type:02X}"
340 )))
341 }
342 }
343 }
344 }
345}
346
347impl FromRecoveredTx<FoundryTxEnvelope> for TxEnv {
348 fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self {
349 match tx {
350 FoundryTxEnvelope::Legacy(signed_tx) => Self::from_recovered_tx(signed_tx, caller),
351 FoundryTxEnvelope::Eip2930(signed_tx) => Self::from_recovered_tx(signed_tx, caller),
352 FoundryTxEnvelope::Eip1559(signed_tx) => Self::from_recovered_tx(signed_tx, caller),
353 FoundryTxEnvelope::Eip4844(signed_tx) => Self::from_recovered_tx(signed_tx, caller),
354 FoundryTxEnvelope::Eip7702(signed_tx) => Self::from_recovered_tx(signed_tx, caller),
355 #[cfg(feature = "optimism")]
356 FoundryTxEnvelope::Deposit(sealed_tx) => {
357 let tx = sealed_tx.inner();
358 Self {
359 tx_type: tx.ty(),
360 caller,
361 gas_limit: tx.gas_limit,
362 kind: tx.to,
363 value: tx.value,
364 data: tx.input.clone(),
365 ..Default::default()
366 }
367 }
368 #[cfg(feature = "optimism")]
369 FoundryTxEnvelope::PostExec(sealed_tx) => {
370 let tx = sealed_tx.inner();
371 Self {
372 tx_type: tx.ty(),
373 caller,
374 kind: tx.kind(),
375 data: tx.input.clone(),
376 ..Default::default()
377 }
378 }
379 FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Ethereum context"),
380 }
381 }
382}
383
384impl FromTxWithEncoded<FoundryTxEnvelope> for TxEnv {
385 fn from_encoded_tx(tx: &FoundryTxEnvelope, sender: Address, _encoded: Bytes) -> Self {
386 Self::from_recovered_tx(tx, sender)
387 }
388}
389
390impl FromRecoveredTx<FoundryTxEnvelope> for TempoTxEnv {
391 fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self {
392 match tx {
393 FoundryTxEnvelope::Legacy(signed_tx) => {
394 Self::from(TxEnv::from_recovered_tx(signed_tx, caller))
395 }
396 FoundryTxEnvelope::Eip2930(signed_tx) => {
397 Self::from(TxEnv::from_recovered_tx(signed_tx, caller))
398 }
399 FoundryTxEnvelope::Eip1559(signed_tx) => {
400 Self::from(TxEnv::from_recovered_tx(signed_tx, caller))
401 }
402 FoundryTxEnvelope::Eip4844(signed_tx) => {
403 Self::from(TxEnv::from_recovered_tx(signed_tx, caller))
404 }
405 FoundryTxEnvelope::Eip7702(signed_tx) => {
406 Self::from(TxEnv::from_recovered_tx(signed_tx, caller))
407 }
408 #[cfg(feature = "optimism")]
409 FoundryTxEnvelope::Deposit(_) => unreachable!("Deposit tx in Tempo context"),
410 #[cfg(feature = "optimism")]
411 FoundryTxEnvelope::PostExec(_) => unreachable!("Post-exec tx in Tempo context"),
412 FoundryTxEnvelope::Tempo(aa_signed) => Self::from_recovered_tx(aa_signed, caller),
413 }
414 }
415}
416
417impl FromTxWithEncoded<FoundryTxEnvelope> for TempoTxEnv {
418 fn from_encoded_tx(tx: &FoundryTxEnvelope, sender: Address, _encoded: Bytes) -> Self {
419 Self::from_recovered_tx(tx, sender)
420 }
421}
422
423impl std::fmt::Display for FoundryTxType {
424 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
425 match self {
426 Self::Legacy => write!(f, "legacy"),
427 Self::Eip2930 => write!(f, "eip2930"),
428 Self::Eip1559 => write!(f, "eip1559"),
429 Self::Eip4844 => write!(f, "eip4844"),
430 Self::Eip7702 => write!(f, "eip7702"),
431 #[cfg(feature = "optimism")]
432 Self::Deposit => write!(f, "deposit"),
433 #[cfg(feature = "optimism")]
434 Self::PostExec => write!(f, "post-exec"),
435 Self::Tempo => write!(f, "tempo"),
436 }
437 }
438}
439
440impl From<TxType> for FoundryTxType {
441 fn from(tx: TxType) -> Self {
442 match tx {
443 TxType::Legacy => Self::Legacy,
444 TxType::Eip2930 => Self::Eip2930,
445 TxType::Eip1559 => Self::Eip1559,
446 TxType::Eip4844 => Self::Eip4844,
447 TxType::Eip7702 => Self::Eip7702,
448 }
449 }
450}
451
452impl From<FoundryTxEnvelope> for FoundryTypedTx {
453 fn from(envelope: FoundryTxEnvelope) -> Self {
454 match envelope {
455 FoundryTxEnvelope::Legacy(signed_tx) => Self::Legacy(signed_tx.strip_signature()),
456 FoundryTxEnvelope::Eip2930(signed_tx) => Self::Eip2930(signed_tx.strip_signature()),
457 FoundryTxEnvelope::Eip1559(signed_tx) => Self::Eip1559(signed_tx.strip_signature()),
458 FoundryTxEnvelope::Eip4844(signed_tx) => Self::Eip4844(signed_tx.strip_signature()),
459 FoundryTxEnvelope::Eip7702(signed_tx) => Self::Eip7702(signed_tx.strip_signature()),
460 #[cfg(feature = "optimism")]
461 FoundryTxEnvelope::Deposit(sealed_tx) => Self::Deposit(sealed_tx.into_inner()),
462 #[cfg(feature = "optimism")]
463 FoundryTxEnvelope::PostExec(sealed_tx) => Self::PostExec(sealed_tx.into_inner()),
464 FoundryTxEnvelope::Tempo(signed_tx) => Self::Tempo(signed_tx.strip_signature()),
465 }
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 use std::str::FromStr;
472
473 use alloy_primitives::{TxKind, U256, b256, hex};
474 use alloy_rlp::Decodable;
475 use alloy_signer::Signature;
476
477 use super::*;
478
479 #[test]
480 fn test_decode_call() {
481 let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..];
482 let decoded = FoundryTxEnvelope::decode(&mut &bytes_first[..]).unwrap();
483
484 let tx = TxLegacy {
485 nonce: 2u64,
486 gas_price: 1000000000u128,
487 gas_limit: 100000,
488 to: TxKind::Call(Address::from_slice(
489 &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..],
490 )),
491 value: U256::from(1000000000000000u64),
492 input: Bytes::default(),
493 chain_id: Some(4),
494 };
495
496 let signature = Signature::from_str("0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b").unwrap();
497
498 let tx = FoundryTxEnvelope::Legacy(Signed::new_unchecked(
499 tx,
500 signature,
501 b256!("0xa517b206d2223278f860ea017d3626cacad4f52ff51030dc9a96b432f17f8d34"),
502 ));
503
504 assert_eq!(tx, decoded);
505 }
506
507 #[test]
508 fn test_decode_create_goerli() {
509 let tx_bytes =
511 hex::decode("02f901ee05228459682f008459682f11830209bf8080b90195608060405234801561001057600080fd5b50610175806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80630c49c36c14610030575b600080fd5b61003861004e565b604051610045919061011d565b60405180910390f35b60606020600052600f6020527f68656c6c6f2073746174656d696e64000000000000000000000000000000000060405260406000f35b600081519050919050565b600082825260208201905092915050565b60005b838110156100be5780820151818401526020810190506100a3565b838111156100cd576000848401525b50505050565b6000601f19601f8301169050919050565b60006100ef82610084565b6100f9818561008f565b93506101098185602086016100a0565b610112816100d3565b840191505092915050565b6000602082019050818103600083015261013781846100e4565b90509291505056fea264697066735822122051449585839a4ea5ac23cae4552ef8a96b64ff59d0668f76bfac3796b2bdbb3664736f6c63430008090033c080a0136ebffaa8fc8b9fda9124de9ccb0b1f64e90fbd44251b4c4ac2501e60b104f9a07eb2999eec6d185ef57e91ed099afb0a926c5b536f0155dd67e537c7476e1471")
512 .unwrap();
513 let _decoded = FoundryTxEnvelope::decode(&mut &tx_bytes[..]).unwrap();
514 }
515
516 #[test]
517 fn can_recover_sender() {
518 let bytes = hex::decode("02f872018307910d808507204d2cb1827d0094388c818ca8b9251b393131c08a736a67ccb19297880320d04823e2701c80c001a0cf024f4815304df2867a1a74e9d2707b6abda0337d2d54a4438d453f4160f190a07ac0e6b3bc9395b5b9c8b9e6d77204a236577a5b18467b9175c01de4faa208d9").unwrap();
520
521 let Ok(FoundryTxEnvelope::Eip1559(tx)) = FoundryTxEnvelope::decode(&mut &bytes[..]) else {
522 panic!("decoding FoundryTxEnvelope failed");
523 };
524
525 assert_eq!(
526 tx.hash(),
527 &"0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f"
528 .parse::<B256>()
529 .unwrap()
530 );
531 assert_eq!(
532 tx.recover_signer().unwrap(),
533 "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5".parse::<Address>().unwrap()
534 );
535 }
536
537 #[test]
540 fn test_decode_live_4844_tx() {
541 use alloy_primitives::{address, b256};
542
543 let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
545 let res = FoundryTxEnvelope::decode(&mut raw_tx.as_slice()).unwrap();
546 assert!(res.is_type(3));
547
548 let tx = match res {
549 FoundryTxEnvelope::Eip4844(tx) => tx,
550 _ => unreachable!(),
551 };
552
553 assert_eq!(tx.tx().tx().to, address!("0x11E9CA82A3a762b4B5bd264d4173a242e7a77064"));
554
555 assert_eq!(
556 tx.tx().tx().blob_versioned_hashes,
557 vec![
558 b256!("0x012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"),
559 b256!("0x0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"),
560 b256!("0x013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"),
561 b256!("0x01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"),
562 b256!("0x011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549")
563 ]
564 );
565
566 let from = tx.recover_signer().unwrap();
567 assert_eq!(from, address!("0xA83C816D4f9b2783761a22BA6FADB0eB0606D7B2"));
568 }
569
570 #[test]
571 fn can_recover_sender_not_normalized() {
572 let bytes = hex::decode("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap();
573
574 let Ok(FoundryTxEnvelope::Legacy(tx)) = FoundryTxEnvelope::decode(&mut &bytes[..]) else {
575 panic!("decoding FoundryTxEnvelope failed");
576 };
577
578 assert_eq!(tx.tx().input, Bytes::from(b""));
579 assert_eq!(tx.tx().gas_price, 1);
580 assert_eq!(tx.tx().gas_limit, 21000);
581 assert_eq!(tx.tx().nonce, 0);
582 if let TxKind::Call(to) = tx.tx().to {
583 assert_eq!(
584 to,
585 "0x095e7baea6a6c7c4c2dfeb977efac326af552d87".parse::<Address>().unwrap()
586 );
587 } else {
588 panic!("expected a call transaction");
589 }
590 assert_eq!(tx.tx().value, U256::from(0x0au64));
591 assert_eq!(
592 tx.recover_signer().unwrap(),
593 "0f65fe9276bc9a24ae7083ae28e2660ef72df99e".parse::<Address>().unwrap()
594 );
595 }
596
597 #[test]
598 fn deser_to_type_tx() {
599 let tx = r#"
600 {
601 "type": "0x2",
602 "chainId": "0x7a69",
603 "nonce": "0x0",
604 "gas": "0x5209",
605 "maxFeePerGas": "0x77359401",
606 "maxPriorityFeePerGas": "0x1",
607 "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
608 "value": "0x0",
609 "accessList": [],
610 "input": "0x",
611 "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0",
612 "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd",
613 "yParity": "0x0",
614 "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"
615 }"#;
616
617 let _typed_tx: FoundryTxEnvelope = serde_json::from_str(tx).unwrap();
618 }
619
620 #[test]
621 fn test_from_recovered_tx_legacy() {
622 let tx = r#"
623 {
624 "type": "0x0",
625 "chainId": "0x1",
626 "nonce": "0x0",
627 "gas": "0x5208",
628 "gasPrice": "0x1",
629 "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
630 "value": "0x1",
631 "input": "0x",
632 "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0",
633 "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd",
634 "v": "0x1b",
635 "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"
636 }"#;
637
638 let typed_tx: FoundryTxEnvelope = serde_json::from_str(tx).unwrap();
639 let sender = typed_tx.recover().unwrap();
640
641 let tx_env = TxEnv::from_recovered_tx(&typed_tx, sender);
643 assert_eq!(tx_env.caller, sender);
644 assert_eq!(tx_env.gas_limit, 0x5208);
645 assert_eq!(tx_env.gas_price, 1);
646 }
647
648 #[test]
651 fn test_decode_encode_tempo_tx() {
652 use alloy_primitives::address;
653 use tempo_primitives::TEMPO_TX_TYPE_ID;
654
655 let tx_hash: TxHash = "0x6d6d8c102064e6dee44abad2024a8b1d37959230baab80e70efbf9b0c739c4fd"
656 .parse::<TxHash>()
657 .unwrap();
658
659 let raw_tx = hex::decode(
661 "76f9025e82a5bd808502cb4178008302d178f8fcf85c9420c000000000000000000000000000000000000080b844095ea7b3000000000000000000000000dec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000989680f89c94dec000000000000000000000000000000000000080b884f8856c0f00000000000000000000000020c000000000000000000000000000000000000000000000000000000000000020c00000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000097d330c0808080809420c000000000000000000000000000000000000180c0b90133027b98b7a8e6c68d7eac741a52e6fdae0560ce3c16ef5427ad46d7a54d0ed86dd41d000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2238453071464a7a50585167546e645473643649456659457776323173516e626966374c4741776e4b43626b222c226f726967696e223a2268747470733a2f2f74656d706f2d6465782e76657263656c2e617070222c2263726f73734f726967696e223a66616c73657dcfd45c3b19745a42f80b134dcb02a8ba099a0e4e7be1984da54734aa81d8f29f74bb9170ae6d25bd510c83fe35895ee5712efe13980a5edc8094c534e23af85eaacc80b21e45fb11f349424dce3a2f23547f60c0ff2f8bcaede2a247545ce8dd87abf0dbb7a5c9507efae2e43833356651b45ac576c2e61cec4e9c0f41fcbf6e",
662 )
663 .unwrap();
664
665 let tempo_tx = FoundryTxEnvelope::decode(&mut raw_tx.as_slice()).unwrap();
666
667 assert!(tempo_tx.is_type(TEMPO_TX_TYPE_ID));
669
670 let FoundryTxEnvelope::Tempo(ref aa_signed) = tempo_tx else {
671 panic!("Expected Tempo transaction");
672 };
673
674 assert_eq!(aa_signed.tx().chain_id, 42429);
676
677 assert_eq!(
679 aa_signed.tx().fee_token,
680 Some(address!("0x20C0000000000000000000000000000000000001"))
681 );
682
683 assert_eq!(aa_signed.tx().gas_limit, 184696);
685
686 assert_eq!(aa_signed.tx().calls.len(), 2);
688
689 assert_eq!(tx_hash, tempo_tx.hash());
691
692 let mut encoded = Vec::new();
694 tempo_tx.encode_2718(&mut encoded);
695 assert_eq!(raw_tx, encoded);
696
697 let sender = tempo_tx.recover().unwrap();
699 assert_eq!(sender, address!("0x566Ff0f4a6114F8072ecDC8A7A8A13d8d0C6B45F"));
700 }
701}