foundry_primitives/transaction/
optimism.rs1use super::{FoundryTransactionRequest, FoundryTxEnvelope};
4use alloy_consensus::{Sealed, Transaction as _, Typed2718};
5use alloy_evm::{FromRecoveredTx, FromTxWithEncoded};
6use alloy_op_evm::OpTx;
7use alloy_primitives::{Address, Bytes};
8use op_alloy_consensus::{
9 OpTransaction as OpTransactionTrait, OpTxEnvelope, TxDeposit, TxPostExec,
10};
11use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts};
12use revm::context::TxEnv;
13
14impl OpTransactionTrait for FoundryTxEnvelope {
15 fn is_deposit(&self) -> bool {
16 Self::is_deposit(self)
17 }
18
19 fn as_deposit(&self) -> Option<&Sealed<TxDeposit>> {
20 match self {
21 Self::Deposit(tx) => Some(tx),
22 _ => None,
23 }
24 }
25
26 fn as_post_exec(&self) -> Option<&Sealed<TxPostExec>> {
27 if let Self::PostExec(tx) = self { Some(tx) } else { None }
28 }
29}
30
31impl From<OpTxEnvelope> for FoundryTxEnvelope {
32 fn from(tx: OpTxEnvelope) -> Self {
33 match tx {
34 OpTxEnvelope::Legacy(tx) => Self::Legacy(tx),
35 OpTxEnvelope::Eip2930(tx) => Self::Eip2930(tx),
36 OpTxEnvelope::Eip1559(tx) => Self::Eip1559(tx),
37 OpTxEnvelope::Eip7702(tx) => Self::Eip7702(tx),
38 OpTxEnvelope::Deposit(tx) => Self::Deposit(tx),
39 OpTxEnvelope::PostExec(tx) => Self::PostExec(tx),
40 }
41 }
42}
43
44impl FromRecoveredTx<FoundryTxEnvelope> for OpTransaction<TxEnv> {
45 fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self {
46 match tx {
47 FoundryTxEnvelope::Legacy(signed_tx) => {
48 let base = TxEnv::from_recovered_tx(signed_tx, caller);
49 Self { base, enveloped_tx: None, deposit: Default::default() }
50 }
51 FoundryTxEnvelope::Eip2930(signed_tx) => {
52 let base = TxEnv::from_recovered_tx(signed_tx, caller);
53 Self { base, enveloped_tx: None, deposit: Default::default() }
54 }
55 FoundryTxEnvelope::Eip1559(signed_tx) => {
56 let base = TxEnv::from_recovered_tx(signed_tx, caller);
57 Self { base, enveloped_tx: None, deposit: Default::default() }
58 }
59 FoundryTxEnvelope::Eip4844(signed_tx) => {
60 let base = TxEnv::from_recovered_tx(signed_tx, caller);
61 Self { base, enveloped_tx: None, deposit: Default::default() }
62 }
63 FoundryTxEnvelope::Eip7702(signed_tx) => {
64 let base = TxEnv::from_recovered_tx(signed_tx, caller);
65 Self { base, enveloped_tx: None, deposit: Default::default() }
66 }
67 FoundryTxEnvelope::Deposit(sealed_tx) => {
68 let deposit_tx = sealed_tx.inner();
69 let base = TxEnv {
70 tx_type: deposit_tx.ty(),
71 caller,
72 gas_limit: deposit_tx.gas_limit,
73 kind: deposit_tx.to,
74 value: deposit_tx.value,
75 data: deposit_tx.input.clone(),
76 ..Default::default()
77 };
78 let deposit = DepositTransactionParts {
79 source_hash: deposit_tx.source_hash,
80 mint: Some(deposit_tx.mint),
81 is_system_transaction: deposit_tx.is_system_transaction,
82 };
83 Self { base, enveloped_tx: None, deposit }
84 }
85 FoundryTxEnvelope::PostExec(sealed_tx) => {
86 let tx = sealed_tx.inner();
87 let base = TxEnv {
88 tx_type: tx.ty(),
89 caller,
90 kind: tx.kind(),
91 data: tx.input.clone(),
92 ..Default::default()
93 };
94 Self { base, enveloped_tx: None, deposit: Default::default() }
95 }
96 #[cfg(feature = "base")]
97 FoundryTxEnvelope::Eip8130(_) => {
98 unreachable!("EIP-8130 transaction in Optimism context")
99 }
100 FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"),
101 }
102 }
103}
104
105impl FromRecoveredTx<FoundryTxEnvelope> for OpTx {
106 fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self {
107 Self(OpTransaction::<TxEnv>::from_recovered_tx(tx, caller))
108 }
109}
110
111impl FromTxWithEncoded<FoundryTxEnvelope> for OpTx {
112 fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self {
113 Self(OpTransaction::<TxEnv>::from_encoded_tx(tx, caller, encoded))
114 }
115}
116
117impl FromTxWithEncoded<FoundryTxEnvelope> for OpTransaction<TxEnv> {
118 fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self {
119 let mut tx = Self::from_recovered_tx(tx, caller);
120 tx.enveloped_tx = Some(encoded);
121 tx
122 }
123}
124
125impl From<op_alloy_rpc_types::Transaction<FoundryTxEnvelope>> for FoundryTransactionRequest {
126 fn from(tx: op_alloy_rpc_types::Transaction<FoundryTxEnvelope>) -> Self {
127 tx.inner.into_inner().into()
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use alloy_network::eip2718::Encodable2718;
135 use alloy_primitives::TxHash;
136 use alloy_rlp::Decodable;
137
138 #[test]
139 fn test_from_recovered_tx_legacy_op() {
140 use alloy_consensus::transaction::SignerRecoverable;
141
142 let tx = r#"
143 {
144 "type": "0x0",
145 "chainId": "0x1",
146 "nonce": "0x0",
147 "gas": "0x5208",
148 "gasPrice": "0x1",
149 "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
150 "value": "0x1",
151 "input": "0x",
152 "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0",
153 "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd",
154 "v": "0x1b",
155 "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"
156 }"#;
157
158 let typed_tx: FoundryTxEnvelope = serde_json::from_str(tx).unwrap();
159 let sender = typed_tx.recover_signer().unwrap();
160
161 let op_tx = OpTransaction::<TxEnv>::from_recovered_tx(&typed_tx, sender);
163 assert_eq!(op_tx.base.caller, sender);
164 assert_eq!(op_tx.base.gas_limit, 0x5208);
165 }
166
167 #[test]
168 fn test_decode_encode_deposit_tx() {
169 let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7"
171 .parse::<TxHash>()
172 .unwrap();
173
174 let raw_tx = alloy_primitives::hex::decode(
176 "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080",
177 )
178 .unwrap();
179 let dep_tx = FoundryTxEnvelope::decode(&mut raw_tx.as_slice()).unwrap();
180
181 let mut encoded = Vec::new();
182 dep_tx.encode_2718(&mut encoded);
183
184 assert_eq!(raw_tx, encoded);
185
186 assert_eq!(tx_hash, dep_tx.hash());
187 }
188}