foundry_primitives/transaction/
optimism.rs1use alloy_consensus::{Sealed, Transaction as _, Typed2718};
4use alloy_evm::{FromRecoveredTx, FromTxWithEncoded};
5use alloy_op_evm::OpTx;
6use alloy_primitives::{Address, B256, Bytes, U256};
7use alloy_serde::OtherFields;
8use op_alloy_consensus::{
9 OpDepositReceipt, OpTransaction as OpTransactionTrait, OpTxEnvelope, TxDeposit, TxPostExec,
10};
11use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts};
12use revm::context::TxEnv;
13
14use super::{FoundryReceiptEnvelope, FoundryTransactionRequest, FoundryTxEnvelope};
15
16impl OpTransactionTrait for FoundryTxEnvelope {
17 fn is_deposit(&self) -> bool {
18 matches!(self, Self::Deposit(_))
19 }
20
21 fn as_deposit(&self) -> Option<&Sealed<TxDeposit>> {
22 match self {
23 Self::Deposit(tx) => Some(tx),
24 _ => None,
25 }
26 }
27
28 fn as_post_exec(&self) -> Option<&Sealed<TxPostExec>> {
29 if let Self::PostExec(tx) = self { Some(tx) } else { None }
30 }
31}
32
33impl From<OpTxEnvelope> for FoundryTxEnvelope {
34 fn from(tx: OpTxEnvelope) -> Self {
35 match tx {
36 OpTxEnvelope::Legacy(tx) => Self::Legacy(tx),
37 OpTxEnvelope::Eip2930(tx) => Self::Eip2930(tx),
38 OpTxEnvelope::Eip1559(tx) => Self::Eip1559(tx),
39 OpTxEnvelope::Eip7702(tx) => Self::Eip7702(tx),
40 OpTxEnvelope::Deposit(tx) => Self::Deposit(tx),
41 OpTxEnvelope::PostExec(tx) => Self::PostExec(tx),
42 }
43 }
44}
45
46impl FromRecoveredTx<FoundryTxEnvelope> for OpTransaction<TxEnv> {
47 fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self {
48 match tx {
49 FoundryTxEnvelope::Legacy(signed_tx) => {
50 let base = TxEnv::from_recovered_tx(signed_tx, caller);
51 Self { base, enveloped_tx: None, deposit: Default::default() }
52 }
53 FoundryTxEnvelope::Eip2930(signed_tx) => {
54 let base = TxEnv::from_recovered_tx(signed_tx, caller);
55 Self { base, enveloped_tx: None, deposit: Default::default() }
56 }
57 FoundryTxEnvelope::Eip1559(signed_tx) => {
58 let base = TxEnv::from_recovered_tx(signed_tx, caller);
59 Self { base, enveloped_tx: None, deposit: Default::default() }
60 }
61 FoundryTxEnvelope::Eip4844(signed_tx) => {
62 let base = TxEnv::from_recovered_tx(signed_tx, caller);
63 Self { base, enveloped_tx: None, deposit: Default::default() }
64 }
65 FoundryTxEnvelope::Eip7702(signed_tx) => {
66 let base = TxEnv::from_recovered_tx(signed_tx, caller);
67 Self { base, enveloped_tx: None, deposit: Default::default() }
68 }
69 FoundryTxEnvelope::Deposit(sealed_tx) => {
70 let deposit_tx = sealed_tx.inner();
71 let base = TxEnv {
72 tx_type: deposit_tx.ty(),
73 caller,
74 gas_limit: deposit_tx.gas_limit,
75 kind: deposit_tx.to,
76 value: deposit_tx.value,
77 data: deposit_tx.input.clone(),
78 ..Default::default()
79 };
80 let deposit = DepositTransactionParts {
81 source_hash: deposit_tx.source_hash,
82 mint: Some(deposit_tx.mint),
83 is_system_transaction: deposit_tx.is_system_transaction,
84 };
85 Self { base, enveloped_tx: None, deposit }
86 }
87 FoundryTxEnvelope::PostExec(sealed_tx) => {
88 let tx = sealed_tx.inner();
89 let base = TxEnv {
90 tx_type: tx.ty(),
91 caller,
92 kind: tx.kind(),
93 data: tx.input.clone(),
94 ..Default::default()
95 };
96 Self { base, enveloped_tx: None, deposit: Default::default() }
97 }
98 FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"),
99 }
100 }
101}
102
103impl FromRecoveredTx<FoundryTxEnvelope> for OpTx {
104 fn from_recovered_tx(tx: &FoundryTxEnvelope, caller: Address) -> Self {
105 Self(OpTransaction::<TxEnv>::from_recovered_tx(tx, caller))
106 }
107}
108
109impl FromTxWithEncoded<FoundryTxEnvelope> for OpTx {
110 fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self {
111 Self(OpTransaction::<TxEnv>::from_encoded_tx(tx, caller, encoded))
112 }
113}
114
115impl FromTxWithEncoded<FoundryTxEnvelope> for OpTransaction<TxEnv> {
116 fn from_encoded_tx(tx: &FoundryTxEnvelope, caller: Address, encoded: Bytes) -> Self {
117 match tx {
118 FoundryTxEnvelope::Legacy(signed_tx) => {
119 let base = TxEnv::from_recovered_tx(signed_tx, caller);
120 Self { base, enveloped_tx: Some(encoded), deposit: Default::default() }
121 }
122 FoundryTxEnvelope::Eip2930(signed_tx) => {
123 let base = TxEnv::from_recovered_tx(signed_tx, caller);
124 Self { base, enveloped_tx: Some(encoded), deposit: Default::default() }
125 }
126 FoundryTxEnvelope::Eip1559(signed_tx) => {
127 let base = TxEnv::from_recovered_tx(signed_tx, caller);
128 Self { base, enveloped_tx: Some(encoded), deposit: Default::default() }
129 }
130 FoundryTxEnvelope::Eip4844(signed_tx) => {
131 let base = TxEnv::from_recovered_tx(signed_tx, caller);
132 Self { base, enveloped_tx: Some(encoded), deposit: Default::default() }
133 }
134 FoundryTxEnvelope::Eip7702(signed_tx) => {
135 let base = TxEnv::from_recovered_tx(signed_tx, caller);
136 Self { base, enveloped_tx: Some(encoded), deposit: Default::default() }
137 }
138 FoundryTxEnvelope::Deposit(sealed_tx) => {
139 let deposit_tx = sealed_tx.inner();
140 let base = TxEnv {
141 tx_type: deposit_tx.ty(),
142 caller,
143 gas_limit: deposit_tx.gas_limit,
144 kind: deposit_tx.to,
145 value: deposit_tx.value,
146 data: deposit_tx.input.clone(),
147 ..Default::default()
148 };
149 let deposit = DepositTransactionParts {
150 source_hash: deposit_tx.source_hash,
151 mint: Some(deposit_tx.mint),
152 is_system_transaction: deposit_tx.is_system_transaction,
153 };
154 Self { base, enveloped_tx: Some(encoded), deposit }
155 }
156 FoundryTxEnvelope::PostExec(sealed_tx) => {
157 let tx = sealed_tx.inner();
158 let base = TxEnv {
159 tx_type: tx.ty(),
160 caller,
161 kind: tx.kind(),
162 data: tx.input.clone(),
163 ..Default::default()
164 };
165 Self { base, enveloped_tx: Some(encoded), deposit: Default::default() }
166 }
167 FoundryTxEnvelope::Tempo(_) => unreachable!("Tempo tx in Optimism context"),
168 }
169 }
170}
171
172impl From<op_alloy_rpc_types::Transaction<FoundryTxEnvelope>> for FoundryTransactionRequest {
173 fn from(tx: op_alloy_rpc_types::Transaction<FoundryTxEnvelope>) -> Self {
174 tx.inner.into_inner().into()
175 }
176}
177
178pub fn get_deposit_tx_parts(
180 other: &OtherFields,
181) -> Result<DepositTransactionParts, Vec<&'static str>> {
182 let mut missing = Vec::new();
183 let source_hash =
184 other.get_deserialized::<B256>("sourceHash").transpose().ok().flatten().unwrap_or_else(
185 || {
186 missing.push("sourceHash");
187 Default::default()
188 },
189 );
190 let mint = other
191 .get_deserialized::<U256>("mint")
192 .transpose()
193 .unwrap_or_else(|_| {
194 missing.push("mint");
195 Default::default()
196 })
197 .map(|value| value.saturating_to::<u128>());
198 let is_system_transaction =
199 other.get_deserialized::<bool>("isSystemTx").transpose().ok().flatten().unwrap_or_else(
200 || {
201 missing.push("isSystemTx");
202 Default::default()
203 },
204 );
205 if missing.is_empty() {
206 Ok(DepositTransactionParts { source_hash, mint, is_system_transaction })
207 } else {
208 Err(missing)
209 }
210}
211
212impl<T> FoundryReceiptEnvelope<T> {
214 pub fn deposit_nonce(&self) -> Option<u64> {
216 self.as_deposit_receipt().and_then(|r| r.deposit_nonce)
217 }
218
219 pub fn deposit_receipt_version(&self) -> Option<u64> {
221 self.as_deposit_receipt().and_then(|r| r.deposit_receipt_version)
222 }
223
224 pub const fn as_deposit_receipt(&self) -> Option<&OpDepositReceipt<T>> {
226 match self {
227 Self::Deposit(t) => Some(&t.receipt),
228 _ => None,
229 }
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use alloy_network::eip2718::Encodable2718;
236 use alloy_primitives::TxHash;
237 use alloy_rlp::Decodable;
238
239 use super::*;
240
241 #[test]
242 fn test_from_recovered_tx_legacy_op() {
243 use alloy_consensus::transaction::SignerRecoverable;
244
245 let tx = r#"
246 {
247 "type": "0x0",
248 "chainId": "0x1",
249 "nonce": "0x0",
250 "gas": "0x5208",
251 "gasPrice": "0x1",
252 "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
253 "value": "0x1",
254 "input": "0x",
255 "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0",
256 "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd",
257 "v": "0x1b",
258 "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"
259 }"#;
260
261 let typed_tx: FoundryTxEnvelope = serde_json::from_str(tx).unwrap();
262 let sender = typed_tx.recover_signer().unwrap();
263
264 let op_tx = OpTransaction::<TxEnv>::from_recovered_tx(&typed_tx, sender);
266 assert_eq!(op_tx.base.caller, sender);
267 assert_eq!(op_tx.base.gas_limit, 0x5208);
268 }
269
270 #[test]
271 fn test_decode_encode_deposit_tx() {
272 let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7"
274 .parse::<TxHash>()
275 .unwrap();
276
277 let raw_tx = alloy_primitives::hex::decode(
279 "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080",
280 )
281 .unwrap();
282 let dep_tx = FoundryTxEnvelope::decode(&mut raw_tx.as_slice()).unwrap();
283
284 let mut encoded = Vec::new();
285 dep_tx.encode_2718(&mut encoded);
286
287 assert_eq!(raw_tx, encoded);
288
289 assert_eq!(tx_hash, dep_tx.hash());
290 }
291}