1use alloy_consensus::Transaction;
2use alloy_eips::eip2930::AccessList;
3use alloy_evm::tx::{FromRecoveredTx, FromTxWithEncoded, IntoTxEnv};
4use alloy_primitives::{Address, Bytes, U256};
5use arb_primitives::{ArbTransactionSigned, tx_types::ArbTxType};
6use reth_ethereum_primitives::TransactionSigned;
7use revm::context::TxEnv;
8
9#[derive(Clone, Debug, Default, PartialEq, Eq)]
16pub struct ArbTransaction(pub TxEnv);
17
18impl ArbTransaction {
19 pub fn from_parts(
21 sender: Address,
22 tx_type: ArbTxType,
23 gas_limit: u64,
24 gas_price: u128,
25 value: U256,
26 to: revm::primitives::TxKind,
27 data: alloy_primitives::Bytes,
28 nonce: u64,
29 chain_id: Option<u64>,
30 ) -> Self {
31 let mut tx = TxEnv {
32 caller: sender,
33 gas_limit,
34 ..Default::default()
35 };
36
37 if matches!(
39 tx_type,
40 ArbTxType::ArbitrumInternalTx | ArbTxType::ArbitrumDepositTx
41 ) && gas_limit == 0
42 {
43 tx.gas_limit = 1_000_000;
44 }
45
46 tx.gas_priority_fee = Some(0);
47
48 match tx_type {
49 ArbTxType::ArbitrumDepositTx | ArbTxType::ArbitrumInternalTx => {
50 tx.value = U256::ZERO;
51 tx.gas_price = 0;
52 }
53 ArbTxType::ArbitrumSubmitRetryableTx => {
54 tx.value = U256::ZERO;
55 tx.gas_price = 0;
56 }
57 _ => {
58 tx.value = value;
59 tx.gas_price = gas_price;
60 }
61 }
62
63 tx.kind = to;
64 tx.data = data;
65 tx.nonce = nonce;
66 tx.chain_id = chain_id;
67
68 ArbTransaction(tx)
69 }
70
71 pub fn into_inner(self) -> TxEnv {
73 self.0
74 }
75}
76
77impl From<ArbTransaction> for TxEnv {
78 fn from(arb_tx: ArbTransaction) -> Self {
79 arb_tx.0
80 }
81}
82
83impl IntoTxEnv<ArbTransaction> for ArbTransaction {
84 fn into_tx_env(self) -> ArbTransaction {
85 self
86 }
87}
88
89impl revm::context_interface::Transaction for ArbTransaction {
90 type AccessListItem<'a>
91 = <TxEnv as revm::context_interface::Transaction>::AccessListItem<'a>
92 where
93 Self: 'a;
94 type Authorization<'a>
95 = <TxEnv as revm::context_interface::Transaction>::Authorization<'a>
96 where
97 Self: 'a;
98
99 fn tx_type(&self) -> u8 {
100 revm::context_interface::Transaction::tx_type(&self.0)
101 }
102 fn caller(&self) -> Address {
103 revm::context_interface::Transaction::caller(&self.0)
104 }
105 fn gas_limit(&self) -> u64 {
106 revm::context_interface::Transaction::gas_limit(&self.0)
107 }
108 fn value(&self) -> U256 {
109 revm::context_interface::Transaction::value(&self.0)
110 }
111 fn input(&self) -> &alloy_primitives::Bytes {
112 revm::context_interface::Transaction::input(&self.0)
113 }
114 fn nonce(&self) -> u64 {
115 revm::context_interface::Transaction::nonce(&self.0)
116 }
117 fn kind(&self) -> alloy_primitives::TxKind {
118 revm::context_interface::Transaction::kind(&self.0)
119 }
120 fn chain_id(&self) -> Option<u64> {
121 revm::context_interface::Transaction::chain_id(&self.0)
122 }
123 fn gas_price(&self) -> u128 {
124 revm::context_interface::Transaction::gas_price(&self.0)
125 }
126 fn access_list(&self) -> Option<impl Iterator<Item = Self::AccessListItem<'_>>> {
127 revm::context_interface::Transaction::access_list(&self.0)
128 }
129 fn blob_versioned_hashes(&self) -> &[alloy_primitives::B256] {
130 revm::context_interface::Transaction::blob_versioned_hashes(&self.0)
131 }
132 fn max_fee_per_blob_gas(&self) -> u128 {
133 revm::context_interface::Transaction::max_fee_per_blob_gas(&self.0)
134 }
135 fn authorization_list_len(&self) -> usize {
136 revm::context_interface::Transaction::authorization_list_len(&self.0)
137 }
138 fn authorization_list(&self) -> impl Iterator<Item = Self::Authorization<'_>> {
139 revm::context_interface::Transaction::authorization_list(&self.0)
140 }
141 fn max_priority_fee_per_gas(&self) -> Option<u128> {
142 revm::context_interface::Transaction::max_priority_fee_per_gas(&self.0)
143 }
144}
145
146impl reth_evm::TransactionEnv for ArbTransaction {
147 fn set_gas_limit(&mut self, gas_limit: u64) {
148 self.0.gas_limit = gas_limit;
149 }
150
151 fn nonce(&self) -> u64 {
152 self.0.nonce
153 }
154
155 fn set_nonce(&mut self, nonce: u64) {
156 self.0.nonce = nonce;
157 }
158
159 fn set_access_list(&mut self, access_list: AccessList) {
160 self.0.access_list = access_list;
161 }
162}
163
164impl crate::build::ArbTransactionEnv for ArbTransaction {
165 fn set_gas_price(&mut self, gas_price: u128) {
166 self.0.gas_price = gas_price;
167 }
168 fn set_gas_priority_fee(&mut self, fee: Option<u128>) {
169 self.0.gas_priority_fee = fee;
170 }
171 fn set_value(&mut self, value: alloy_primitives::U256) {
172 self.0.value = value;
173 }
174}
175
176impl FromRecoveredTx<TransactionSigned> for ArbTransaction {
177 fn from_recovered_tx(tx: &TransactionSigned, sender: Address) -> Self {
178 ArbTransaction(TxEnv::from_recovered_tx(tx, sender))
179 }
180}
181
182impl FromTxWithEncoded<TransactionSigned> for ArbTransaction {
183 fn from_encoded_tx(tx: &TransactionSigned, sender: Address, encoded: Bytes) -> Self {
184 ArbTransaction(TxEnv::from_encoded_tx(tx, sender, encoded))
185 }
186}
187
188fn arb_tx_to_tx_env(tx: &ArbTransactionSigned, sender: Address) -> TxEnv {
190 use alloy_consensus::Typed2718;
191 let arb_type = ArbTxType::from_u8(Typed2718::ty(tx)).ok();
192 let is_system_tx = matches!(
193 arb_type,
194 Some(ArbTxType::ArbitrumInternalTx | ArbTxType::ArbitrumDepositTx)
195 );
196 let is_submit_retryable = arb_type == Some(ArbTxType::ArbitrumSubmitRetryableTx);
197
198 let mut env = TxEnv::default();
199 let raw_type = Typed2718::ty(tx);
204 env.tx_type = if raw_type < 0x64 { raw_type } else { 0 };
205 env.caller = sender;
206 env.gas_limit = tx.gas_limit();
207 env.nonce = tx.nonce();
208 env.chain_id = tx.chain_id();
209 env.kind = tx.to().map_or(
210 revm::primitives::TxKind::Create,
211 revm::primitives::TxKind::Call,
212 );
213 env.data = tx.input().clone();
214
215 if is_system_tx {
216 env.gas_price = 0;
217 env.gas_priority_fee = Some(0);
218 env.value = U256::ZERO;
219 if env.gas_limit == 0 {
220 env.gas_limit = 1_000_000;
221 }
222 } else if is_submit_retryable {
223 env.gas_price = 0;
224 env.gas_priority_fee = Some(0);
225 env.value = U256::ZERO;
226 } else {
227 env.gas_price = tx.max_fee_per_gas();
228 env.gas_priority_fee = tx.max_priority_fee_per_gas();
229 env.value = tx.value();
230 }
231
232 if let Some(al) = tx.access_list() {
233 env.access_list = al.clone();
234 }
235
236 if let Some(auths) = tx.authorization_list() {
240 use alloy_consensus::transaction::Either;
241 env.authorization_list = auths.iter().map(|a| Either::Left(a.clone())).collect();
242 }
243
244 env
245}
246
247impl FromRecoveredTx<ArbTransactionSigned> for ArbTransaction {
248 fn from_recovered_tx(tx: &ArbTransactionSigned, sender: Address) -> Self {
249 ArbTransaction(arb_tx_to_tx_env(tx, sender))
250 }
251}
252
253impl FromTxWithEncoded<ArbTransactionSigned> for ArbTransaction {
254 fn from_encoded_tx(tx: &ArbTransactionSigned, sender: Address, _encoded: Bytes) -> Self {
255 ArbTransaction(arb_tx_to_tx_env(tx, sender))
256 }
257}