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 Self::is_deposit(self)
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 let mut tx = Self::from_recovered_tx(tx, caller);
118 tx.enveloped_tx = Some(encoded);
119 tx
120 }
121}
122
123impl From<op_alloy_rpc_types::Transaction<FoundryTxEnvelope>> for FoundryTransactionRequest {
124 fn from(tx: op_alloy_rpc_types::Transaction<FoundryTxEnvelope>) -> Self {
125 tx.inner.into_inner().into()
126 }
127}
128
129pub fn get_deposit_tx_parts(
131 other: &OtherFields,
132) -> Result<DepositTransactionParts, Vec<&'static str>> {
133 let mut missing = Vec::new();
134 let source_hash =
135 other.get_deserialized::<B256>("sourceHash").transpose().ok().flatten().unwrap_or_else(
136 || {
137 missing.push("sourceHash");
138 Default::default()
139 },
140 );
141 let mint = other
142 .get_deserialized::<U256>("mint")
143 .transpose()
144 .unwrap_or_else(|_| {
145 missing.push("mint");
146 Default::default()
147 })
148 .map(|value| value.saturating_to::<u128>());
149 let is_system_transaction =
150 other.get_deserialized::<bool>("isSystemTx").transpose().ok().flatten().unwrap_or_else(
151 || {
152 missing.push("isSystemTx");
153 Default::default()
154 },
155 );
156 if missing.is_empty() {
157 Ok(DepositTransactionParts { source_hash, mint, is_system_transaction })
158 } else {
159 Err(missing)
160 }
161}
162
163impl<T> FoundryReceiptEnvelope<T> {
165 pub fn deposit_nonce(&self) -> Option<u64> {
167 self.as_deposit_receipt().and_then(|r| r.deposit_nonce)
168 }
169
170 pub fn deposit_receipt_version(&self) -> Option<u64> {
172 self.as_deposit_receipt().and_then(|r| r.deposit_receipt_version)
173 }
174
175 pub const fn as_deposit_receipt(&self) -> Option<&OpDepositReceipt<T>> {
177 match self {
178 Self::Deposit(t) => Some(&t.receipt),
179 _ => None,
180 }
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use alloy_network::eip2718::Encodable2718;
187 use alloy_primitives::TxHash;
188 use alloy_rlp::Decodable;
189
190 use super::*;
191
192 #[test]
193 fn test_from_recovered_tx_legacy_op() {
194 use alloy_consensus::transaction::SignerRecoverable;
195
196 let tx = r#"
197 {
198 "type": "0x0",
199 "chainId": "0x1",
200 "nonce": "0x0",
201 "gas": "0x5208",
202 "gasPrice": "0x1",
203 "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
204 "value": "0x1",
205 "input": "0x",
206 "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0",
207 "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd",
208 "v": "0x1b",
209 "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"
210 }"#;
211
212 let typed_tx: FoundryTxEnvelope = serde_json::from_str(tx).unwrap();
213 let sender = typed_tx.recover_signer().unwrap();
214
215 let op_tx = OpTransaction::<TxEnv>::from_recovered_tx(&typed_tx, sender);
217 assert_eq!(op_tx.base.caller, sender);
218 assert_eq!(op_tx.base.gas_limit, 0x5208);
219 }
220
221 #[test]
222 fn test_decode_encode_deposit_tx() {
223 let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7"
225 .parse::<TxHash>()
226 .unwrap();
227
228 let raw_tx = alloy_primitives::hex::decode(
230 "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080",
231 )
232 .unwrap();
233 let dep_tx = FoundryTxEnvelope::decode(&mut raw_tx.as_slice()).unwrap();
234
235 let mut encoded = Vec::new();
236 dep_tx.encode_2718(&mut encoded);
237
238 assert_eq!(raw_tx, encoded);
239
240 assert_eq!(tx_hash, dep_tx.hash());
241 }
242}