1use alloy_chains::NamedChain;
4use alloy_consensus::{Transaction as _, Typed2718};
5use alloy_evm::FromRecoveredTx;
6use alloy_network::{AnyRpcTransaction, AnyTxEnvelope, TransactionResponse};
7use foundry_evm_networks::celo::CELO_DYNAMIC_FEE_TX_TYPE;
8use revm::context::TxEnv;
9use tempo_alloy::primitives::{TEMPO_TX_TYPE_ID, TempoTxEnvelope};
10use tempo_revm::TempoTxEnv;
11
12pub trait FromAnyRpcTransaction: Sized {
17 fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result<Self>;
19}
20
21fn unknown_transaction_type(tx: &AnyRpcTransaction, target: &str) -> eyre::Report {
23 eyre::eyre!("cannot convert unknown transaction type {:#04x} to {target}", tx.inner.inner.ty())
24}
25
26impl FromAnyRpcTransaction for TxEnv {
27 fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result<Self> {
28 if let Some(envelope) = tx.as_envelope() {
29 return Ok(Self::from_recovered_tx(envelope, tx.from()));
30 }
31
32 if let AnyTxEnvelope::Unknown(unknown) = &*tx.inner.inner
38 && unknown.ty() == CELO_DYNAMIC_FEE_TX_TYPE
39 && matches!(
40 unknown.chain_id().and_then(NamedChain::from_chain_id),
41 Some(NamedChain::Celo | NamedChain::CeloSepolia)
42 )
43 {
44 return Ok(Self {
45 tx_type: CELO_DYNAMIC_FEE_TX_TYPE,
46 caller: tx.from(),
47 gas_limit: unknown.gas_limit(),
48 gas_price: unknown.max_fee_per_gas(),
49 gas_priority_fee: unknown.max_priority_fee_per_gas(),
50 kind: unknown.kind(),
51 value: unknown.value(),
52 data: unknown.input().clone(),
53 nonce: unknown.nonce(),
54 chain_id: unknown.chain_id(),
55 access_list: unknown.access_list().cloned().unwrap_or_default(),
56 ..Default::default()
57 });
58 }
59
60 Err(unknown_transaction_type(tx, "TxEnv"))
61 }
62}
63
64impl FromAnyRpcTransaction for TempoTxEnv {
65 fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result<Self> {
66 let envelope = match &*tx.inner.inner {
70 AnyTxEnvelope::Ethereum(envelope) => TempoTxEnvelope::try_from(envelope.clone())
71 .map_err(|_| unknown_transaction_type(tx, "TempoTxEnv"))?,
72 AnyTxEnvelope::Unknown(unknown) if unknown.ty() == TEMPO_TX_TYPE_ID => {
73 serde_json::from_value::<alloy_rpc_types::Transaction<TempoTxEnvelope>>(
74 serde_json::to_value(tx)?,
75 )
76 .map_err(|err| eyre::eyre!("cannot decode Tempo AA transaction: {err}"))?
77 .inner
78 .into_inner()
79 }
80 AnyTxEnvelope::Unknown(_) => return Err(unknown_transaction_type(tx, "TempoTxEnv")),
81 };
82 Ok(Self::from_recovered_tx(&envelope, tx.from()))
83 }
84}
85
86#[cfg(feature = "base")]
87mod base {
88 use super::*;
89 use base_common_consensus::BaseTxEnvelope;
90 use base_common_evm::{BaseTransaction, EIP8130_TRANSACTION_TYPE};
91 use base_common_rpc_types::Transaction as BaseRpcTransaction;
92
93 impl FromAnyRpcTransaction for BaseTransaction<TxEnv> {
94 fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result<Self> {
95 let envelope = match BaseTxEnvelope::try_from(tx.clone()) {
96 Ok(envelope) => envelope,
97 Err(_) if tx.ty() == EIP8130_TRANSACTION_TYPE => {
98 let rpc_tx =
99 serde_json::from_value::<BaseRpcTransaction>(serde_json::to_value(tx)?)
100 .map_err(|err| {
101 eyre::eyre!(
102 "cannot convert RPC transaction to Base envelope: {err}"
103 )
104 })?;
105 rpc_tx.inner.into_inner()
106 }
107 Err(_) => eyre::bail!("cannot convert transaction to BaseTxEnvelope"),
108 };
109 Ok(Self::from_recovered_tx(&envelope, tx.from()))
110 }
111 }
112}
113
114#[cfg(feature = "optimism")]
115mod optimism {
116 use super::*;
117 use alloy_eips::eip2718::Encodable2718;
118 use alloy_op_evm::OpTx;
119 use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, TxDeposit};
120 use op_revm::OpTransaction;
121
122 impl FromAnyRpcTransaction for OpTx {
123 fn from_any_rpc_transaction(tx: &AnyRpcTransaction) -> eyre::Result<Self> {
124 if let Some(envelope) = tx.as_envelope() {
125 return Ok(Self(OpTransaction::<TxEnv> {
126 base: TxEnv::from_recovered_tx(envelope, tx.from()),
127 enveloped_tx: Some(envelope.encoded_2718().into()),
130 deposit: Default::default(),
131 }));
132 }
133
134 if let AnyTxEnvelope::Unknown(unknown) = &*tx.inner.inner
136 && unknown.ty() == DEPOSIT_TX_TYPE_ID
137 {
138 let mut fields = unknown.inner.fields.clone();
139 fields.insert("from".to_string(), serde_json::to_value(tx.from())?);
140 let deposit_tx: TxDeposit = fields
141 .deserialize_into()
142 .map_err(|e| eyre::eyre!("failed to deserialize deposit tx: {e}"))?;
143 return Ok(Self::from_recovered_tx(&deposit_tx, deposit_tx.from));
144 }
145
146 Err(unknown_transaction_type(tx, "OpTransaction"))
147 }
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use alloy_consensus::{Signed, TxEip1559, transaction::Recovered};
155 use alloy_network::{AnyTxType, UnknownTxEnvelope, UnknownTypedTransaction};
156 use alloy_primitives::{Address, B256, Bytes, Signature, U256, address, b256, bytes};
157 use alloy_rpc_types::{Transaction as RpcTransaction, TransactionInfo};
158 use alloy_serde::WithOtherFields;
159 use alloy_signer::SignerSync;
160 use alloy_signer_local::PrivateKeySigner;
161 use revm::primitives::TxKind;
162 use std::num::NonZeroU64;
163 use tempo_alloy::primitives::{TempoSignature, TempoTransaction, transaction::Call};
164 use tempo_revm::ExecutionContext;
165
166 #[cfg(feature = "base")]
167 use base_common_evm::BaseTransaction;
168
169 fn make_signed_eip1559() -> Signed<TxEip1559> {
170 Signed::new_unchecked(
171 TxEip1559 {
172 chain_id: 1,
173 nonce: 42,
174 gas_limit: 21001,
175 to: TxKind::Call(Address::with_last_byte(0xBB)),
176 value: U256::from(101),
177 ..Default::default()
178 },
179 Signature::new(U256::ZERO, U256::ZERO, false),
180 B256::ZERO,
181 )
182 }
183
184 #[test]
185 fn from_any_rpc_transaction_for_eth() {
186 let from = Address::random();
187 let any_tx = eth_rpc_transaction(from);
188 let tx_env = TxEnv::from_any_rpc_transaction(&any_tx).unwrap();
189
190 assert_eq!(tx_env.caller, from);
191 assert_eq!(tx_env.nonce, 42);
192 assert_eq!(tx_env.gas_limit, 21001);
193 assert_eq!(tx_env.value, U256::from(101));
194 assert_eq!(tx_env.kind, TxKind::Call(Address::with_last_byte(0xBB)));
195 }
196
197 #[cfg(feature = "base")]
198 #[test]
199 fn from_any_rpc_transaction_for_base_eth_envelope() {
200 let from = Address::random();
201 let any_tx = eth_rpc_transaction(from);
202
203 let tx_env = BaseTransaction::<TxEnv>::from_any_rpc_transaction(&any_tx).unwrap();
204 assert_eq!(tx_env.base.caller, from);
205 assert_eq!(tx_env.base.nonce, 42);
206 assert_eq!(tx_env.base.gas_limit, 21001);
207 assert_eq!(tx_env.base.value, U256::from(101));
208 assert!(tx_env.enveloped_tx.is_some());
209 }
210
211 #[test]
212 fn from_any_rpc_transaction_unknown_envelope_errors() {
213 let unknown = AnyTxEnvelope::Unknown(UnknownTxEnvelope {
214 hash: B256::ZERO,
215 inner: UnknownTypedTransaction {
216 ty: AnyTxType(0xFF),
217 fields: Default::default(),
218 memo: Default::default(),
219 },
220 });
221 let from = Address::random();
222 let any_tx = AnyRpcTransaction::new(WithOtherFields::new(RpcTransaction {
223 inner: Recovered::new_unchecked(unknown, from),
224 block_hash: None,
225 block_number: None,
226 transaction_index: None,
227 effective_gas_price: None,
228 block_timestamp: None,
229 }));
230
231 let result = TxEnv::from_any_rpc_transaction(&any_tx).unwrap_err();
232 assert_eq!(result.to_string(), "cannot convert unknown transaction type 0xff to TxEnv");
233 }
234
235 #[test]
236 fn from_any_rpc_transaction_for_celo_dynamic_fee() {
237 let from = Address::with_last_byte(0xAA);
238 let to = Address::with_last_byte(0xBB);
239 let fee_currency = Address::with_last_byte(0xCC);
240 let json = serde_json::json!({
241 "accessList": [],
242 "blockHash": B256::ZERO,
243 "blockNumber": "0x1",
244 "chainId": "0xa4ec",
245 "feeCurrency": fee_currency,
246 "from": from,
247 "gas": "0x5208",
248 "gasPrice": "0x3",
249 "hash": B256::ZERO,
250 "input": "0x1234",
251 "maxFeePerGas": "0x3",
252 "maxPriorityFeePerGas": "0x1",
253 "nonce": "0x2a",
254 "r": B256::ZERO,
255 "s": B256::ZERO,
256 "to": to,
257 "transactionIndex": "0x0",
258 "type": "0x7b",
259 "v": "0x0",
260 "value": "0x65",
261 "yParity": "0x0"
262 });
263 let mut non_celo_json = json.clone();
264 non_celo_json["chainId"] = serde_json::json!("0x1");
265 let non_celo_tx: AnyRpcTransaction = serde_json::from_value(non_celo_json).unwrap();
266 assert!(TxEnv::from_any_rpc_transaction(&non_celo_tx).is_err());
267
268 let any_tx: AnyRpcTransaction = serde_json::from_value(json).unwrap();
269
270 let tx_env = TxEnv::from_any_rpc_transaction(&any_tx).unwrap();
271
272 assert_eq!(tx_env.tx_type, CELO_DYNAMIC_FEE_TX_TYPE);
273 assert_eq!(tx_env.caller, from);
274 assert_eq!(tx_env.nonce, 42);
275 assert_eq!(tx_env.gas_limit, 21000);
276 assert_eq!(tx_env.gas_price, 3);
277 assert_eq!(tx_env.gas_priority_fee, Some(1));
278 assert_eq!(tx_env.kind, TxKind::Call(to));
279 assert_eq!(tx_env.value, U256::from(101));
280 assert_eq!(tx_env.data, Bytes::from_static(&[0x12, 0x34]));
281 assert_eq!(tx_env.chain_id, Some(42_220));
282 }
283
284 #[test]
285 fn from_any_rpc_transaction_for_tempo_eth_envelope() {
286 let from = Address::random();
287 let signed_tx = make_signed_eip1559();
288 let tempo_envelope = TempoTxEnvelope::Eip1559(signed_tx.clone());
289 let any_tx = eth_rpc_transaction(from);
290
291 let tx_env = TempoTxEnv::from_any_rpc_transaction(&any_tx).unwrap();
292 assert_eq!(tx_env.inner.caller, from);
293 assert_eq!(tx_env.inner.nonce, 42);
294 assert_eq!(tx_env.inner.gas_limit, 21001);
295 assert_eq!(tx_env.inner.value, U256::from(101));
296 assert_eq!(tx_env.fee_token, None);
297 assert_eq!(
298 tx_env.execution_context(),
299 ExecutionContext::Transaction { tx_hash: *signed_tx.hash() }
300 );
301 assert_eq!(tx_env.unique_tx_identifier(), Some(tempo_envelope.unique_tx_identifier(from)));
302 assert!(tx_env.tempo_tx_env.is_none());
303 }
304
305 #[test]
306 fn from_any_rpc_transaction_for_tempo_aa() {
307 let sender = PrivateKeySigner::random();
308 let sponsor = PrivateKeySigner::random();
309 let fee_token = Some(Address::random());
310 let calls = vec![
311 Call {
312 to: TxKind::Call(Address::with_last_byte(0x11)),
313 value: U256::ZERO,
314 input: Bytes::from_static(&[0xaa, 0xbb]),
315 },
316 Call {
317 to: TxKind::Call(Address::with_last_byte(0x22)),
318 value: U256::ZERO,
319 input: Bytes::from_static(&[0xcc, 0xdd]),
320 },
321 ];
322 let mut tempo_tx = TempoTransaction {
323 chain_id: 42431,
324 nonce: 42,
325 gas_limit: 424242,
326 fee_token,
327 nonce_key: U256::from(4242),
328 valid_after: NonZeroU64::new(1800000000),
329 valid_before: NonZeroU64::new(1900000000),
330 calls: calls.clone(),
331 ..Default::default()
332 };
333 tempo_tx.fee_payer_signature = Some(
334 sponsor.sign_hash_sync(&tempo_tx.fee_payer_signature_hash(sender.address())).unwrap(),
335 );
336 let signature = sender.sign_hash_sync(&tempo_tx.signature_hash()).unwrap();
337 let aa_signed = tempo_tx.into_signed(signature.into());
338 let tx_hash = *aa_signed.hash();
339 let unique_tx_identifier = aa_signed.expiring_nonce_hash(sender.address());
340
341 let rpc_tx = RpcTransaction::from_transaction(
343 Recovered::new_unchecked(TempoTxEnvelope::AA(aa_signed), sender.address()),
344 TransactionInfo::default(),
345 );
346 let any_tx: AnyRpcTransaction =
347 serde_json::from_value(serde_json::to_value(&rpc_tx).unwrap()).unwrap();
348 assert!((*any_tx.inner.inner).is_unknown());
349
350 let tx_env = TempoTxEnv::from_any_rpc_transaction(&any_tx).unwrap();
351 assert_eq!(tx_env.inner.tx_type, TEMPO_TX_TYPE_ID);
352 assert_eq!(tx_env.inner.caller, sender.address());
353 assert_eq!(tx_env.inner.nonce, 42);
354 assert_eq!(tx_env.inner.gas_limit, 424242);
355 assert_eq!(tx_env.inner.chain_id, Some(42431));
356 assert_eq!(tx_env.fee_token, fee_token);
357 assert_eq!(tx_env.fee_payer, Some(Some(sponsor.address())));
358 assert_eq!(tx_env.execution_context(), ExecutionContext::Transaction { tx_hash });
359 assert_eq!(tx_env.unique_tx_identifier(), Some(unique_tx_identifier));
360
361 let aa = tx_env.tempo_tx_env.as_deref().unwrap();
362 assert_eq!(aa.aa_calls, calls);
363 assert_eq!(aa.nonce_key, U256::from(4242));
364 assert_eq!(aa.valid_after, Some(1800000000));
365 assert_eq!(aa.valid_before, Some(1900000000));
366 assert_eq!(aa.tx_hash, tx_hash);
367 }
368
369 fn captured_tempo_aa(json: &str) -> TempoTxEnv {
371 let any_tx: AnyRpcTransaction = serde_json::from_str(json).unwrap();
372 let tx_env = TempoTxEnv::from_any_rpc_transaction(&any_tx).unwrap();
373 let aa = tx_env.tempo_tx_env.as_deref().unwrap();
374 assert_eq!(aa.signature.recover_signer(&aa.signature_hash).unwrap(), tx_env.inner.caller);
376 assert_eq!(tx_env.inner.tx_type, TEMPO_TX_TYPE_ID);
377 tx_env
378 }
379
380 #[test]
381 fn from_any_rpc_transaction_for_captured_sponsored_tempo_aa_batch() {
382 let tx_env =
383 captured_tempo_aa(include_str!("../../test-data/tempo-aa-sponsored-batch.json"));
384 assert_eq!(tx_env.inner.caller, address!("0x0a0d9bc4dda3a659699ce05153ffe9298f99ce89"));
385 assert_eq!(tx_env.inner.nonce, 0);
386 assert_eq!(tx_env.inner.gas_limit, 1403789);
387 assert_eq!(tx_env.inner.chain_id, Some(4217));
388 assert_eq!(tx_env.fee_token, Some(address!("0x20c0000000000000000000006a37da5c996874be")));
389 assert_eq!(
390 tx_env.fee_payer,
391 Some(Some(address!("0x58aa7ce42e1d13b2919e2ac7e006c4fbc171442c")))
392 );
393 assert_eq!(
394 tx_env.execution_context(),
395 ExecutionContext::Transaction {
396 tx_hash: b256!(
397 "0xbb23551c35bc2c2539ac3637c7464a22b5fb38bbb9cf39fa1f533ca6c2c981a3"
398 )
399 }
400 );
401 assert_eq!(
403 tx_env.channel_open_context_hash(),
404 Some(b256!("0xadb35ee9830a691a8dbd8f42208a1553d9a242b8750a218d841cc78a0dea20ca"))
405 );
406
407 let aa = tx_env.tempo_tx_env.as_deref().unwrap();
408 assert_eq!(
409 aa.aa_calls,
410 [
411 Call {
412 to: TxKind::Call(address!("0x20c0000000000000000000000000000000000000")),
413 value: U256::ZERO,
414 input: bytes!(
415 "0x095ea7b300000000000000000000000083a1491f3e7f8daab8f787a631334b9ca7a870230000000000000000000000000000000000000000000000000000000077359400"
416 ),
417 },
418 Call {
419 to: TxKind::Call(address!("0x83a1491f3e7f8daab8f787a631334b9ca7a87023")),
420 value: U256::ZERO,
421 input: bytes!(
422 "0x6e553f6500000000000000000000000000000000000000000000000000000000773594000000000000000000000000000a0d9bc4dda3a659699ce05153ffe9298f99ce89"
423 ),
424 },
425 ]
426 );
427 assert_eq!(aa.nonce_key, U256::MAX);
428 assert_eq!(aa.valid_after, Some(542204929));
429 assert_eq!(aa.valid_before, Some(1790813692));
430 }
431
432 #[test]
433 fn from_any_rpc_transaction_for_captured_nonce_lane_tempo_aa() {
434 let tx_env = captured_tempo_aa(include_str!("../../test-data/tempo-aa-nonce-lane.json"));
435 assert_eq!(tx_env.inner.caller, address!("0xfdd1c606b498f5fcaaf27bd318b14caf52e8f6c2"));
436 assert_eq!(tx_env.inner.nonce, 22496);
437 assert_eq!(tx_env.inner.gas_limit, 99408);
438 assert_eq!(tx_env.inner.chain_id, Some(42431));
439 assert_eq!(tx_env.fee_token, None);
440 assert_eq!(tx_env.fee_payer, None);
441 assert_eq!(
442 tx_env.execution_context(),
443 ExecutionContext::Transaction {
444 tx_hash: b256!(
445 "0x4df0a9b9e859be6c91c00edf8478939d0b91f6f9eb39dd9c8a93946ac70ff740"
446 )
447 }
448 );
449
450 let aa = tx_env.tempo_tx_env.as_deref().unwrap();
451 let [call] = aa.aa_calls.as_slice() else { panic!("expected one call") };
452 assert_eq!(call.to, TxKind::Call(address!("0x5ad0000000000000000000000000000000000003")));
453 assert_eq!(call.value, U256::ZERO);
454 assert_eq!(call.input.len(), 868);
455 assert_eq!(aa.nonce_key, U256::ONE);
456 assert_eq!(aa.valid_after, None);
457 assert_eq!(aa.valid_before, None);
458 }
459
460 #[test]
461 fn from_any_rpc_transaction_for_captured_keychain_tempo_aa() {
462 let tx_env = captured_tempo_aa(include_str!("../../test-data/tempo-aa-keychain.json"));
463 let sender = address!("0x5f704c6c7075acd14ee36527f03b5b5dcb4a966f");
464 let key_id = address!("0x240a31713e851acdc0c590fe45ef72baeff55f91");
465 assert_eq!(tx_env.inner.caller, sender);
466 assert_eq!(tx_env.inner.nonce, 0);
467 assert_eq!(tx_env.inner.gas_limit, 322821);
468 assert_eq!(tx_env.inner.chain_id, Some(42431));
469 assert_eq!(tx_env.fee_token, None);
470 assert_eq!(
471 tx_env.fee_payer,
472 Some(Some(address!("0x133d7736f290fa2758cf0d1e0862f5a93ae1dcbf")))
473 );
474 assert_eq!(
475 tx_env.execution_context(),
476 ExecutionContext::Transaction {
477 tx_hash: b256!(
478 "0xd4d372ad5cfa4cdf476aeb25d4a76fb80fe2d6169eb9354c544500c0496a19d6"
479 )
480 }
481 );
482 assert_eq!(
484 tx_env.channel_open_context_hash(),
485 Some(b256!("0xdb2722c83d8893c1637bbabfe2ad924bcc044653f6c9fa91d472d70ddfa97de3"))
486 );
487
488 let aa = tx_env.tempo_tx_env.as_deref().unwrap();
489 assert_eq!(
490 aa.aa_calls,
491 [Call { to: TxKind::Call(Address::ZERO), value: U256::ZERO, input: Bytes::new() }]
492 );
493 assert_eq!(aa.nonce_key, U256::MAX);
494 assert_eq!(aa.valid_after, Some(949691873));
495 assert_eq!(aa.valid_before, Some(1790811579));
496 let TempoSignature::Keychain(keychain) = &aa.signature else { panic!("expected keychain") };
497 assert_eq!(keychain.user_address, sender);
498 assert_eq!(keychain.key_id(&aa.signature_hash).unwrap(), key_id);
499 let key_authorization = aa.key_authorization.as_ref().unwrap();
500 assert_eq!(key_authorization.key_id, key_id);
501 assert_eq!(key_authorization.recover_signer().unwrap(), sender);
502 }
503
504 #[test]
505 fn from_any_rpc_transaction_for_captured_authorization_list_tempo_aa() {
506 let tx_env =
507 captured_tempo_aa(include_str!("../../test-data/tempo-aa-authorization-list.json"));
508 assert_eq!(tx_env.inner.caller, address!("0xd410bcea80de9214e0e49df7d19c4c5e3db60f2a"));
509 assert_eq!(tx_env.inner.nonce, 0);
510 assert_eq!(tx_env.inner.gas_limit, 2000000);
511 assert_eq!(tx_env.inner.chain_id, Some(42431));
512 assert_eq!(tx_env.fee_token, Some(address!("0x20c0000000000000000000000000000000000000")));
513 assert_eq!(tx_env.fee_payer, None);
514 assert_eq!(
515 tx_env.execution_context(),
516 ExecutionContext::Transaction {
517 tx_hash: b256!(
518 "0xfff5a6fa344fa9d6d7410374f40b6f711735500892cccdf480670fd7c5adcf93"
519 )
520 }
521 );
522
523 let aa = tx_env.tempo_tx_env.as_deref().unwrap();
524 let call = Call {
525 to: TxKind::Call(address!("0xa98d41b22c17ae4f4a94420a29a3095464c037e3")),
526 value: U256::ZERO,
527 input: Bytes::new(),
528 };
529 assert_eq!(aa.aa_calls, [call.clone(), call]);
530 assert_eq!(aa.nonce_key, U256::ZERO);
531 let delegate = address!("0xaaaaaaaa00000000000000000000000000000000");
533 assert_eq!(
534 aa.tempo_authorization_list
535 .iter()
536 .map(|auth| (auth.address, auth.authority()))
537 .collect::<Vec<_>>(),
538 [
539 (delegate, Some(address!("0xd9aa283bc5643587f9623a0e9683df78588404a8"))),
540 (delegate, Some(address!("0x883b644ccaa219845187fd0932a8ef6e7d484bd6"))),
541 (delegate, Some(address!("0x732c32ec8e029d8989b9d1db9914376b17e20676"))),
542 ]
543 );
544 }
545
546 #[test]
547 fn from_any_rpc_transaction_for_tempo_aa_without_signature_errors() {
548 let mut json: serde_json::Value =
549 serde_json::from_str(include_str!("../../test-data/tempo-aa-sponsored-batch.json"))
550 .unwrap();
551 json.as_object_mut().unwrap().remove("signature");
552 let any_tx: AnyRpcTransaction = serde_json::from_value(json).unwrap();
553
554 let err = TempoTxEnv::from_any_rpc_transaction(&any_tx).unwrap_err();
555 assert_eq!(
556 err.to_string(),
557 "cannot decode Tempo AA transaction: missing field `signature`"
558 );
559 }
560
561 #[cfg(feature = "optimism")]
562 mod optimism {
563 use super::*;
564 use alloy_consensus::Sealed;
565 use alloy_eips::eip2718::Encodable2718;
566 use alloy_op_evm::OpTx;
567 use op_alloy_consensus::{OpTxEnvelope, TxDeposit, transaction::OpTransactionInfo};
568 use op_alloy_rpc_types::Transaction as OpRpcTransaction;
569
570 #[test]
571 fn from_any_rpc_transaction_for_op() {
572 let from = Address::random();
573 let any_tx = eth_rpc_transaction(from);
574 let expected_base = TxEnv::from_any_rpc_transaction(&any_tx).unwrap();
575
576 let op_tx_env = OpTx::from_any_rpc_transaction(&any_tx).unwrap();
577 assert_eq!(op_tx_env.base, expected_base);
578 assert_eq!(
581 op_tx_env.enveloped_tx,
582 Some(any_tx.as_envelope().unwrap().encoded_2718().into())
583 );
584 }
585
586 #[test]
587 fn from_any_rpc_transaction_for_op_deposit() {
588 let from = Address::random();
589 let source_hash = B256::random();
590 let deposit = TxDeposit {
591 source_hash,
592 from,
593 to: TxKind::Call(Address::with_last_byte(0xCC)),
594 mint: 1111,
595 value: U256::from(200),
596 gas_limit: 21000,
597 is_system_transaction: true,
598 input: Default::default(),
599 };
600
601 let op_rpc_tx = OpRpcTransaction::from_transaction(
604 Recovered::new_unchecked(OpTxEnvelope::Deposit(Sealed::new(deposit)), from),
605 OpTransactionInfo::default(),
606 );
607 let json = serde_json::to_value(&op_rpc_tx).unwrap();
608 let any_tx: AnyRpcTransaction = serde_json::from_value(json).unwrap();
609
610 let op_tx_env = OpTx::from_any_rpc_transaction(&any_tx).unwrap();
611 assert_eq!(op_tx_env.base.caller, from);
612 assert_eq!(op_tx_env.base.kind, TxKind::Call(Address::with_last_byte(0xCC)));
613 assert_eq!(op_tx_env.base.value, U256::from(200));
614 assert_eq!(op_tx_env.base.gas_limit, 21000);
615 assert_eq!(op_tx_env.deposit.source_hash, source_hash);
616 assert_eq!(op_tx_env.deposit.mint, Some(1111));
617 assert!(op_tx_env.deposit.is_system_transaction);
618 }
619 }
620
621 fn eth_rpc_transaction(from: Address) -> AnyRpcTransaction {
622 RpcTransaction::from_transaction(
623 Recovered::new_unchecked(make_signed_eip1559().into(), from),
624 TransactionInfo::default(),
625 )
626 .into()
627 }
628}