Skip to main content

foundry_primitives/transaction/
optimism.rs

1//! OP-stack-specific impls for [`FoundryTxEnvelope`] and [`FoundryTransactionRequest`].
2
3use 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        // Test OpTransaction<TxEnv> conversion via FromRecoveredTx trait
162        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        // https://sepolia-optimism.etherscan.io/tx/0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7
170        let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7"
171            .parse::<TxHash>()
172            .unwrap();
173
174        // https://sepolia-optimism.etherscan.io/getRawTx?tx=0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7
175        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}