1use alloy_consensus::{
2 Sealed, Signed, TransactionEnvelope, TxEip1559, TxEip2930, TxEnvelope, TxLegacy, TxType,
3 Typed2718,
4 crypto::RecoveryError,
5 transaction::{
6 SignerRecoverable, TxEip7702, TxHashRef,
7 eip4844::{TxEip4844Variant, TxEip4844WithSidecar},
8 },
9};
10use alloy_evm::{FromRecoveredTx, FromTxWithEncoded};
11use alloy_network::{AnyRpcTransaction, AnyTxEnvelope, TransactionResponse};
12use alloy_primitives::{Address, B256, Bytes, TxHash};
13use alloy_rpc_types::ConversionError;
14use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, OpTransaction as OpTransactionTrait, TxDeposit};
15use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts};
16use revm::context::TxEnv;
17use tempo_primitives::{AASigned, TempoTransaction};
18use tempo_revm::TempoTxEnv;
19
20#[allow(clippy::large_enum_variant)]
24#[derive(Clone, Debug, TransactionEnvelope)]
25#[envelope(
26 tx_type_name = FoundryTxType,
27 typed = FoundryTypedTx,
28)]
29pub enum FoundryTxEnvelope {
30 #[envelope(ty = 0)]
32 Legacy(Signed<TxLegacy>),
33 #[envelope(ty = 1)]
37 Eip2930(Signed<TxEip2930>),
38 #[envelope(ty = 2)]
42 Eip1559(Signed<TxEip1559>),
43 #[envelope(ty = 3)]
47 Eip4844(Signed<TxEip4844Variant>),
48 #[envelope(ty = 4)]
52 Eip7702(Signed<TxEip7702>),
53 #[envelope(ty = 126)]
57 Deposit(Sealed<TxDeposit>),
58 #[envelope(ty = 0x76, typed = TempoTransaction)]
62 Tempo(AASigned),
63}
64
65impl FoundryTxEnvelope {
66 pub fn try_into_eth(self) -> Result<TxEnvelope, Self> {
70 match self {
71 Self::Legacy(tx) => Ok(TxEnvelope::Legacy(tx)),
72 Self::Eip2930(tx) => Ok(TxEnvelope::Eip2930(tx)),
73 Self::Eip1559(tx) => Ok(TxEnvelope::Eip1559(tx)),
74 Self::Eip4844(tx) => Ok(TxEnvelope::Eip4844(tx)),
75 Self::Eip7702(tx) => Ok(TxEnvelope::Eip7702(tx)),
76 Self::Deposit(_) => Err(self),
77 Self::Tempo(_) => Err(self),
78 }
79 }
80
81 pub fn sidecar(&self) -> Option<&TxEip4844WithSidecar> {
82 match self {
83 Self::Eip4844(signed_variant) => match signed_variant.tx() {
84 TxEip4844Variant::TxEip4844WithSidecar(with_sidecar) => Some(with_sidecar),
85 _ => None,
86 },
87 _ => None,
88 }
89 }
90
91 pub fn hash(&self) -> B256 {
98 match self {
99 Self::Legacy(t) => *t.hash(),
100 Self::Eip2930(t) => *t.hash(),
101 Self::Eip1559(t) => *t.hash(),
102 Self::Eip4844(t) => *t.hash(),
103 Self::Eip7702(t) => *t.hash(),
104 Self::Deposit(t) => t.tx_hash(),
105 Self::Tempo(t) => *t.hash(),
106 }
107 }
108
109 pub fn is_tempo(&self) -> bool {
111 matches!(self, Self::Tempo(_))
112 }
113
114 pub fn recover(&self) -> Result<Address, RecoveryError> {
116 Ok(match self {
117 Self::Legacy(tx) => tx.recover_signer()?,
118 Self::Eip2930(tx) => tx.recover_signer()?,
119 Self::Eip1559(tx) => tx.recover_signer()?,
120 Self::Eip4844(tx) => tx.recover_signer()?,
121 Self::Eip7702(tx) => tx.recover_signer()?,
122 Self::Deposit(tx) => tx.from,
123 Self::Tempo(tx) => tx.signature().recover_signer(&tx.signature_hash())?,
124 })
125 }
126}
127
128impl TxHashRef for FoundryTxEnvelope {
129 fn tx_hash(&self) -> &TxHash {
130 match self {
131 Self::Legacy(t) => t.hash(),
132 Self::Eip2930(t) => t.hash(),
133 Self::Eip1559(t) => t.hash(),
134 Self::Eip4844(t) => t.hash(),
135 Self::Eip7702(t) => t.hash(),
136 Self::Deposit(t) => t.hash_ref(),
137 Self::Tempo(t) => t.hash(),
138 }
139 }
140}
141
142impl SignerRecoverable for FoundryTxEnvelope {
143 fn recover_signer(&self) -> Result<Address, RecoveryError> {
144 self.recover()
145 }
146
147 fn recover_signer_unchecked(&self) -> Result<Address, RecoveryError> {
148 self.recover()
149 }
150}
151
152impl OpTransactionTrait for FoundryTxEnvelope {
153 fn is_deposit(&self) -> bool {
154 matches!(self, Self::Deposit(_))
155 }
156
157 fn as_deposit(&self) -> Option<&Sealed<TxDeposit>> {
158 match self {
159 Self::Deposit(tx) => Some(tx),
160 _ => None,
161 }
162 }
163}
164
165impl TryFrom<FoundryTxEnvelope> for TxEnvelope {
166 type Error = FoundryTxEnvelope;
167
168 fn try_from(envelope: FoundryTxEnvelope) -> Result<Self, Self::Error> {
169 envelope.try_into_eth()
170 }
171}
172
173impl TryFrom<AnyRpcTransaction> for FoundryTxEnvelope {
174 type Error = ConversionError;
175
176 fn try_from(value: AnyRpcTransaction) -> Result<Self, Self::Error> {
177 let transaction = value.into_inner();
178 let from = transaction.from();
179 match transaction.into_inner() {
180 AnyTxEnvelope::Ethereum(tx) => match tx {
181 TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
182 TxEnvelope::Eip2930(tx) => Ok(Self::Eip2930(tx)),
183 TxEnvelope::Eip1559(tx) => Ok(Self::Eip1559(tx)),
184 TxEnvelope::Eip4844(tx) => Ok(Self::Eip4844(tx)),
185 TxEnvelope::Eip7702(tx) => Ok(Self::Eip7702(tx)),
186 },
187 AnyTxEnvelope::Unknown(mut tx) => {
188 if tx.ty() == DEPOSIT_TX_TYPE_ID {
190 tx.inner.fields.insert("from".to_string(), serde_json::to_value(from).unwrap());
191 let deposit_tx =
192 tx.inner.fields.deserialize_into::<TxDeposit>().map_err(|e| {
193 ConversionError::Custom(format!(
194 "Failed to deserialize deposit tx: {e}"
195 ))
196 })?;
197
198 return Ok(Self::Deposit(Sealed::new(deposit_tx)));
199 };
200
201 let tx_type = tx.ty();
202 Err(ConversionError::Custom(format!("Unknown transaction type: 0x{tx_type:02X}")))
203 }
204 }
205 }
206}
207
208impl FromRecoveredTx<FoundryTxEnvelope> for TxEnv {
209 fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self {
210 match tx {
211 FoundryTxEnvelope::Legacy(signed_tx) => Self::from_recovered_tx(signed_tx, caller),
212 FoundryTxEnvelope::Eip2930(signed_tx) => Self::from_recovered_tx(signed_tx, caller),
213 FoundryTxEnvelope::Eip1559(signed_tx) => Self::from_recovered_tx(signed_tx, caller),
214 FoundryTxEnvelope::Eip4844(signed_tx) => Self::from_recovered_tx(signed_tx, caller),
215 FoundryTxEnvelope::Eip7702(signed_tx) => Self::from_recovered_tx(signed_tx, caller),
216 FoundryTxEnvelope::Deposit(sealed_tx) => {
217 Self::from_recovered_tx(sealed_tx.inner(), caller)
218 }
219 FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Ethereum context"),
220 }
221 }
222}
223
224impl FromRecoveredTx<FoundryTxEnvelope> for OpTransaction<TxEnv> {
225 fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self {
226 match tx {
227 FoundryTxEnvelope::Legacy(signed_tx) => Self::from_recovered_tx(signed_tx, caller),
228 FoundryTxEnvelope::Eip2930(signed_tx) => Self::from_recovered_tx(signed_tx, caller),
229 FoundryTxEnvelope::Eip1559(signed_tx) => Self::from_recovered_tx(signed_tx, caller),
230 FoundryTxEnvelope::Eip4844(signed_tx) => Self::from_recovered_tx(signed_tx, caller),
231 FoundryTxEnvelope::Eip7702(signed_tx) => Self::from_recovered_tx(signed_tx, caller),
232 FoundryTxEnvelope::Deposit(sealed_tx) => {
233 Self::from_recovered_tx(sealed_tx.inner(), caller)
234 }
235 FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"),
236 }
237 }
238}
239
240impl FromTxWithEncoded<FoundryTxEnvelope> for TxEnv {
241 fn from_encoded_tx(tx: &FoundryTxEnvelope, sender: Address, _encoded: Bytes) -> Self {
242 Self::from_recovered_tx(tx, sender)
243 }
244}
245
246impl FromRecoveredTx<FoundryTxEnvelope> for TempoTxEnv {
247 fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self {
248 match tx {
249 FoundryTxEnvelope::Legacy(signed_tx) => {
250 Self::from(TxEnv::from_recovered_tx(signed_tx, caller))
251 }
252 FoundryTxEnvelope::Eip2930(signed_tx) => {
253 Self::from(TxEnv::from_recovered_tx(signed_tx, caller))
254 }
255 FoundryTxEnvelope::Eip1559(signed_tx) => {
256 Self::from(TxEnv::from_recovered_tx(signed_tx, caller))
257 }
258 FoundryTxEnvelope::Eip4844(signed_tx) => {
259 Self::from(TxEnv::from_recovered_tx(signed_tx, caller))
260 }
261 FoundryTxEnvelope::Eip7702(signed_tx) => {
262 Self::from(TxEnv::from_recovered_tx(signed_tx, caller))
263 }
264 FoundryTxEnvelope::Deposit(_) => unreachable!("Deposit tx in Tempo context"),
265 FoundryTxEnvelope::Tempo(aa_signed) => Self::from_recovered_tx(aa_signed, caller),
266 }
267 }
268}
269
270impl FromTxWithEncoded<FoundryTxEnvelope> for TempoTxEnv {
271 fn from_encoded_tx(tx: &FoundryTxEnvelope, sender: Address, _encoded: Bytes) -> Self {
272 Self::from_recovered_tx(tx, sender)
273 }
274}
275
276impl FromTxWithEncoded<FoundryTxEnvelope> for OpTransaction<TxEnv> {
277 fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self {
278 match tx {
279 FoundryTxEnvelope::Legacy(signed_tx) => {
280 let base = TxEnv::from_recovered_tx(signed_tx, caller);
281 Self { base, enveloped_tx: Some(encoded), deposit: Default::default() }
282 }
283 FoundryTxEnvelope::Eip2930(signed_tx) => {
284 let base = TxEnv::from_recovered_tx(signed_tx, caller);
285 Self { base, enveloped_tx: Some(encoded), deposit: Default::default() }
286 }
287 FoundryTxEnvelope::Eip1559(signed_tx) => {
288 let base = TxEnv::from_recovered_tx(signed_tx, caller);
289 Self { base, enveloped_tx: Some(encoded), deposit: Default::default() }
290 }
291 FoundryTxEnvelope::Eip4844(signed_tx) => {
292 let base = TxEnv::from_recovered_tx(signed_tx, caller);
293 Self { base, enveloped_tx: Some(encoded), deposit: Default::default() }
294 }
295 FoundryTxEnvelope::Eip7702(signed_tx) => {
296 let base = TxEnv::from_recovered_tx(signed_tx, caller);
297 Self { base, enveloped_tx: Some(encoded), deposit: Default::default() }
298 }
299 FoundryTxEnvelope::Deposit(sealed_tx) => {
300 let deposit_tx = sealed_tx.inner();
301 let base = TxEnv::from_recovered_tx(deposit_tx, caller);
302 let deposit = DepositTransactionParts {
303 source_hash: deposit_tx.source_hash,
304 mint: Some(deposit_tx.mint),
305 is_system_transaction: deposit_tx.is_system_transaction,
306 };
307 Self { base, enveloped_tx: Some(encoded), deposit }
308 }
309 FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"),
310 }
311 }
312}
313
314impl std::fmt::Display for FoundryTxType {
315 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
316 match self {
317 Self::Legacy => write!(f, "legacy"),
318 Self::Eip2930 => write!(f, "eip2930"),
319 Self::Eip1559 => write!(f, "eip1559"),
320 Self::Eip4844 => write!(f, "eip4844"),
321 Self::Eip7702 => write!(f, "eip7702"),
322 Self::Deposit => write!(f, "deposit"),
323 Self::Tempo => write!(f, "tempo"),
324 }
325 }
326}
327
328impl From<TxType> for FoundryTxType {
329 fn from(tx: TxType) -> Self {
330 match tx {
331 TxType::Legacy => Self::Legacy,
332 TxType::Eip2930 => Self::Eip2930,
333 TxType::Eip1559 => Self::Eip1559,
334 TxType::Eip4844 => Self::Eip4844,
335 TxType::Eip7702 => Self::Eip7702,
336 }
337 }
338}
339
340impl From<FoundryTxEnvelope> for FoundryTypedTx {
341 fn from(envelope: FoundryTxEnvelope) -> Self {
342 match envelope {
343 FoundryTxEnvelope::Legacy(signed_tx) => Self::Legacy(signed_tx.strip_signature()),
344 FoundryTxEnvelope::Eip2930(signed_tx) => Self::Eip2930(signed_tx.strip_signature()),
345 FoundryTxEnvelope::Eip1559(signed_tx) => Self::Eip1559(signed_tx.strip_signature()),
346 FoundryTxEnvelope::Eip4844(signed_tx) => Self::Eip4844(signed_tx.strip_signature()),
347 FoundryTxEnvelope::Eip7702(signed_tx) => Self::Eip7702(signed_tx.strip_signature()),
348 FoundryTxEnvelope::Deposit(sealed_tx) => Self::Deposit(sealed_tx.into_inner()),
349 FoundryTxEnvelope::Tempo(signed_tx) => Self::Tempo(signed_tx.strip_signature()),
350 }
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use std::str::FromStr;
357
358 use alloy_primitives::{TxKind, U256, b256, hex};
359 use alloy_rlp::Decodable;
360 use alloy_signer::Signature;
361
362 use super::*;
363
364 #[test]
365 fn test_decode_call() {
366 let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..];
367 let decoded = FoundryTxEnvelope::decode(&mut &bytes_first[..]).unwrap();
368
369 let tx = TxLegacy {
370 nonce: 2u64,
371 gas_price: 1000000000u128,
372 gas_limit: 100000,
373 to: TxKind::Call(Address::from_slice(
374 &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..],
375 )),
376 value: U256::from(1000000000000000u64),
377 input: Bytes::default(),
378 chain_id: Some(4),
379 };
380
381 let signature = Signature::from_str("0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b").unwrap();
382
383 let tx = FoundryTxEnvelope::Legacy(Signed::new_unchecked(
384 tx,
385 signature,
386 b256!("0xa517b206d2223278f860ea017d3626cacad4f52ff51030dc9a96b432f17f8d34"),
387 ));
388
389 assert_eq!(tx, decoded);
390 }
391
392 #[test]
393 fn test_decode_create_goerli() {
394 let tx_bytes =
396 hex::decode("02f901ee05228459682f008459682f11830209bf8080b90195608060405234801561001057600080fd5b50610175806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80630c49c36c14610030575b600080fd5b61003861004e565b604051610045919061011d565b60405180910390f35b60606020600052600f6020527f68656c6c6f2073746174656d696e64000000000000000000000000000000000060405260406000f35b600081519050919050565b600082825260208201905092915050565b60005b838110156100be5780820151818401526020810190506100a3565b838111156100cd576000848401525b50505050565b6000601f19601f8301169050919050565b60006100ef82610084565b6100f9818561008f565b93506101098185602086016100a0565b610112816100d3565b840191505092915050565b6000602082019050818103600083015261013781846100e4565b90509291505056fea264697066735822122051449585839a4ea5ac23cae4552ef8a96b64ff59d0668f76bfac3796b2bdbb3664736f6c63430008090033c080a0136ebffaa8fc8b9fda9124de9ccb0b1f64e90fbd44251b4c4ac2501e60b104f9a07eb2999eec6d185ef57e91ed099afb0a926c5b536f0155dd67e537c7476e1471")
397 .unwrap();
398 let _decoded = FoundryTxEnvelope::decode(&mut &tx_bytes[..]).unwrap();
399 }
400
401 #[test]
402 fn can_recover_sender() {
403 let bytes = hex::decode("02f872018307910d808507204d2cb1827d0094388c818ca8b9251b393131c08a736a67ccb19297880320d04823e2701c80c001a0cf024f4815304df2867a1a74e9d2707b6abda0337d2d54a4438d453f4160f190a07ac0e6b3bc9395b5b9c8b9e6d77204a236577a5b18467b9175c01de4faa208d9").unwrap();
405
406 let Ok(FoundryTxEnvelope::Eip1559(tx)) = FoundryTxEnvelope::decode(&mut &bytes[..]) else {
407 panic!("decoding FoundryTxEnvelope failed");
408 };
409
410 assert_eq!(
411 tx.hash(),
412 &"0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f"
413 .parse::<B256>()
414 .unwrap()
415 );
416 assert_eq!(
417 tx.recover_signer().unwrap(),
418 "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5".parse::<Address>().unwrap()
419 );
420 }
421
422 #[test]
425 fn test_decode_live_4844_tx() {
426 use alloy_primitives::{address, b256};
427
428 let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
430 let res = FoundryTxEnvelope::decode(&mut raw_tx.as_slice()).unwrap();
431 assert!(res.is_type(3));
432
433 let tx = match res {
434 FoundryTxEnvelope::Eip4844(tx) => tx,
435 _ => unreachable!(),
436 };
437
438 assert_eq!(tx.tx().tx().to, address!("0x11E9CA82A3a762b4B5bd264d4173a242e7a77064"));
439
440 assert_eq!(
441 tx.tx().tx().blob_versioned_hashes,
442 vec![
443 b256!("0x012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"),
444 b256!("0x0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"),
445 b256!("0x013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"),
446 b256!("0x01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"),
447 b256!("0x011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549")
448 ]
449 );
450
451 let from = tx.recover_signer().unwrap();
452 assert_eq!(from, address!("0xA83C816D4f9b2783761a22BA6FADB0eB0606D7B2"));
453 }
454
455 #[test]
456 fn test_decode_encode_deposit_tx() {
457 let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7"
459 .parse::<TxHash>()
460 .unwrap();
461
462 let raw_tx = alloy_primitives::hex::decode(
464 "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080",
465 )
466 .unwrap();
467 let dep_tx = FoundryTxEnvelope::decode(&mut raw_tx.as_slice()).unwrap();
468
469 let mut encoded = Vec::new();
470 dep_tx.encode_2718(&mut encoded);
471
472 assert_eq!(raw_tx, encoded);
473
474 assert_eq!(tx_hash, dep_tx.hash());
475 }
476
477 #[test]
478 fn can_recover_sender_not_normalized() {
479 let bytes = hex::decode("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap();
480
481 let Ok(FoundryTxEnvelope::Legacy(tx)) = FoundryTxEnvelope::decode(&mut &bytes[..]) else {
482 panic!("decoding FoundryTxEnvelope failed");
483 };
484
485 assert_eq!(tx.tx().input, Bytes::from(b""));
486 assert_eq!(tx.tx().gas_price, 1);
487 assert_eq!(tx.tx().gas_limit, 21000);
488 assert_eq!(tx.tx().nonce, 0);
489 if let TxKind::Call(to) = tx.tx().to {
490 assert_eq!(
491 to,
492 "0x095e7baea6a6c7c4c2dfeb977efac326af552d87".parse::<Address>().unwrap()
493 );
494 } else {
495 panic!("expected a call transaction");
496 }
497 assert_eq!(tx.tx().value, U256::from(0x0au64));
498 assert_eq!(
499 tx.recover_signer().unwrap(),
500 "0f65fe9276bc9a24ae7083ae28e2660ef72df99e".parse::<Address>().unwrap()
501 );
502 }
503
504 #[test]
505 fn deser_to_type_tx() {
506 let tx = r#"
507 {
508 "type": "0x2",
509 "chainId": "0x7a69",
510 "nonce": "0x0",
511 "gas": "0x5209",
512 "maxFeePerGas": "0x77359401",
513 "maxPriorityFeePerGas": "0x1",
514 "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
515 "value": "0x0",
516 "accessList": [],
517 "input": "0x",
518 "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0",
519 "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd",
520 "yParity": "0x0",
521 "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"
522 }"#;
523
524 let _typed_tx: FoundryTxEnvelope = serde_json::from_str(tx).unwrap();
525 }
526
527 #[test]
528 fn test_from_recovered_tx_legacy() {
529 let tx = r#"
530 {
531 "type": "0x0",
532 "chainId": "0x1",
533 "nonce": "0x0",
534 "gas": "0x5208",
535 "gasPrice": "0x1",
536 "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
537 "value": "0x1",
538 "input": "0x",
539 "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0",
540 "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd",
541 "v": "0x1b",
542 "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"
543 }"#;
544
545 let typed_tx: FoundryTxEnvelope = serde_json::from_str(tx).unwrap();
546 let sender = typed_tx.recover().unwrap();
547
548 let tx_env = TxEnv::from_recovered_tx(&typed_tx, sender);
550 assert_eq!(tx_env.caller, sender);
551 assert_eq!(tx_env.gas_limit, 0x5208);
552 assert_eq!(tx_env.gas_price, 1);
553
554 let op_tx = OpTransaction::<TxEnv>::from_recovered_tx(&typed_tx, sender);
556 assert_eq!(op_tx.base.caller, sender);
557 assert_eq!(op_tx.base.gas_limit, 0x5208);
558 }
559
560 #[test]
563 fn test_decode_encode_tempo_tx() {
564 use alloy_primitives::address;
565 use tempo_primitives::TEMPO_TX_TYPE_ID;
566
567 let tx_hash: TxHash = "0x6d6d8c102064e6dee44abad2024a8b1d37959230baab80e70efbf9b0c739c4fd"
568 .parse::<TxHash>()
569 .unwrap();
570
571 let raw_tx = hex::decode(
573 "76f9025e82a5bd808502cb4178008302d178f8fcf85c9420c000000000000000000000000000000000000080b844095ea7b3000000000000000000000000dec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000989680f89c94dec000000000000000000000000000000000000080b884f8856c0f00000000000000000000000020c000000000000000000000000000000000000000000000000000000000000020c00000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000097d330c0808080809420c000000000000000000000000000000000000180c0b90133027b98b7a8e6c68d7eac741a52e6fdae0560ce3c16ef5427ad46d7a54d0ed86dd41d000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2238453071464a7a50585167546e645473643649456659457776323173516e626966374c4741776e4b43626b222c226f726967696e223a2268747470733a2f2f74656d706f2d6465782e76657263656c2e617070222c2263726f73734f726967696e223a66616c73657dcfd45c3b19745a42f80b134dcb02a8ba099a0e4e7be1984da54734aa81d8f29f74bb9170ae6d25bd510c83fe35895ee5712efe13980a5edc8094c534e23af85eaacc80b21e45fb11f349424dce3a2f23547f60c0ff2f8bcaede2a247545ce8dd87abf0dbb7a5c9507efae2e43833356651b45ac576c2e61cec4e9c0f41fcbf6e",
574 )
575 .unwrap();
576
577 let tempo_tx = FoundryTxEnvelope::decode(&mut raw_tx.as_slice()).unwrap();
578
579 assert!(tempo_tx.is_type(TEMPO_TX_TYPE_ID));
581
582 let FoundryTxEnvelope::Tempo(ref aa_signed) = tempo_tx else {
583 panic!("Expected Tempo transaction");
584 };
585
586 assert_eq!(aa_signed.tx().chain_id, 42429);
588
589 assert_eq!(
591 aa_signed.tx().fee_token,
592 Some(address!("0x20C0000000000000000000000000000000000001"))
593 );
594
595 assert_eq!(aa_signed.tx().gas_limit, 184696);
597
598 assert_eq!(aa_signed.tx().calls.len(), 2);
600
601 assert_eq!(tx_hash, tempo_tx.hash());
603
604 let mut encoded = Vec::new();
606 tempo_tx.encode_2718(&mut encoded);
607 assert_eq!(raw_tx, encoded);
608
609 let sender = tempo_tx.recover().unwrap();
611 assert_eq!(sender, address!("0x566Ff0f4a6114F8072ecDC8A7A8A13d8d0C6B45F"));
612 }
613}