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