1use alloc::vec::Vec;
2use core::{
3 hash::{Hash, Hasher},
4 ops::Deref,
5};
6
7use alloy_consensus::{
8 transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, TxHashRef},
9 SignableTransaction, Transaction as ConsensusTx, TxLegacy, Typed2718,
10};
11use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718, IsTyped2718};
12use alloy_primitives::{keccak256, Address, Bytes, Signature, TxHash, TxKind, B256, U256};
13use alloy_rlp::{Decodable, Encodable};
14use reth_primitives_traits::{
15 crypto::secp256k1::{recover_signer, recover_signer_unchecked},
16 InMemorySize, SignedTransaction,
17};
18
19use arb_alloy_consensus::tx::{
20 ArbContractTx, ArbDepositTx, ArbInternalTx, ArbRetryTx, ArbSubmitRetryableTx, ArbTxType,
21 ArbUnsignedTx,
22};
23
24const ARBOS_ADDRESS: Address =
26 alloy_primitives::address!("00000000000000000000000000000000000A4B05");
27
28const RETRYABLE_ADDRESS: Address =
30 alloy_primitives::address!("000000000000000000000000000000000000006e");
31
32#[derive(Clone, Debug, Eq, PartialEq)]
34pub enum ArbTypedTransaction {
35 Deposit(ArbDepositTx),
36 Unsigned(ArbUnsignedTx),
37 Contract(ArbContractTx),
38 Retry(ArbRetryTx),
39 SubmitRetryable(ArbSubmitRetryableTx),
40 Internal(ArbInternalTx),
41
42 Legacy(TxLegacy),
43 Eip2930(alloy_consensus::TxEip2930),
44 Eip1559(alloy_consensus::TxEip1559),
45 Eip4844(alloy_consensus::TxEip4844),
46 Eip7702(alloy_consensus::TxEip7702),
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub enum ArbTxTypeLocal {
52 Deposit,
53 Unsigned,
54 Contract,
55 Retry,
56 SubmitRetryable,
57 Internal,
58 Legacy,
59 Eip2930,
60 Eip1559,
61 Eip4844,
62 Eip7702,
63}
64
65impl ArbTxTypeLocal {
66 pub fn as_u8(self) -> u8 {
68 match self {
69 Self::Legacy => 0x00,
70 Self::Eip2930 => 0x01,
71 Self::Eip1559 => 0x02,
72 Self::Eip4844 => 0x03,
73 Self::Eip7702 => 0x04,
74 Self::Deposit => ArbTxType::ArbitrumDepositTx.as_u8(),
75 Self::Unsigned => ArbTxType::ArbitrumUnsignedTx.as_u8(),
76 Self::Contract => ArbTxType::ArbitrumContractTx.as_u8(),
77 Self::Retry => ArbTxType::ArbitrumRetryTx.as_u8(),
78 Self::SubmitRetryable => ArbTxType::ArbitrumSubmitRetryableTx.as_u8(),
79 Self::Internal => ArbTxType::ArbitrumInternalTx.as_u8(),
80 }
81 }
82}
83
84impl Typed2718 for ArbTxTypeLocal {
85 fn is_legacy(&self) -> bool {
86 matches!(self, Self::Legacy)
87 }
88
89 fn ty(&self) -> u8 {
90 self.as_u8()
91 }
92}
93
94impl alloy_consensus::TransactionEnvelope for ArbTransactionSigned {
95 type TxType = ArbTxTypeLocal;
96
97 fn tx_type(&self) -> Self::TxType {
98 match &self.transaction {
99 ArbTypedTransaction::Legacy(_) => ArbTxTypeLocal::Legacy,
100 ArbTypedTransaction::Eip2930(_) => ArbTxTypeLocal::Eip2930,
101 ArbTypedTransaction::Eip1559(_) => ArbTxTypeLocal::Eip1559,
102 ArbTypedTransaction::Eip4844(_) => ArbTxTypeLocal::Eip4844,
103 ArbTypedTransaction::Eip7702(_) => ArbTxTypeLocal::Eip7702,
104 ArbTypedTransaction::Deposit(_) => ArbTxTypeLocal::Deposit,
105 ArbTypedTransaction::Unsigned(_) => ArbTxTypeLocal::Unsigned,
106 ArbTypedTransaction::Contract(_) => ArbTxTypeLocal::Contract,
107 ArbTypedTransaction::Retry(_) => ArbTxTypeLocal::Retry,
108 ArbTypedTransaction::SubmitRetryable(_) => ArbTxTypeLocal::SubmitRetryable,
109 ArbTypedTransaction::Internal(_) => ArbTxTypeLocal::Internal,
110 }
111 }
112}
113
114#[derive(Clone, Debug, Eq)]
116pub struct ArbTransactionSigned {
117 hash: reth_primitives_traits::sync::OnceLock<TxHash>,
118 signature: Signature,
119 transaction: ArbTypedTransaction,
120 input_cache: reth_primitives_traits::sync::OnceLock<Bytes>,
121}
122
123impl Deref for ArbTransactionSigned {
124 type Target = ArbTypedTransaction;
125 fn deref(&self) -> &Self::Target {
126 &self.transaction
127 }
128}
129
130impl ArbTransactionSigned {
131 pub fn new(transaction: ArbTypedTransaction, signature: Signature, hash: B256) -> Self {
132 Self {
133 hash: hash.into(),
134 signature,
135 transaction,
136 input_cache: Default::default(),
137 }
138 }
139
140 pub fn new_unhashed(transaction: ArbTypedTransaction, signature: Signature) -> Self {
141 Self {
142 hash: Default::default(),
143 signature,
144 transaction,
145 input_cache: Default::default(),
146 }
147 }
148
149 pub fn from_envelope(
151 envelope: alloy_consensus::EthereumTxEnvelope<alloy_consensus::TxEip4844>,
152 ) -> Self {
153 use alloy_consensus::EthereumTxEnvelope;
154 match envelope {
155 EthereumTxEnvelope::Legacy(signed) => {
156 let (tx, sig, hash) = signed.into_parts();
157 Self::new(ArbTypedTransaction::Legacy(tx), sig, hash)
158 }
159 EthereumTxEnvelope::Eip2930(signed) => {
160 let (tx, sig, hash) = signed.into_parts();
161 Self::new(ArbTypedTransaction::Eip2930(tx), sig, hash)
162 }
163 EthereumTxEnvelope::Eip1559(signed) => {
164 let (tx, sig, hash) = signed.into_parts();
165 Self::new(ArbTypedTransaction::Eip1559(tx), sig, hash)
166 }
167 EthereumTxEnvelope::Eip4844(signed) => {
168 let (tx, sig, hash) = signed.into_parts();
169 Self::new(ArbTypedTransaction::Eip4844(tx), sig, hash)
170 }
171 EthereumTxEnvelope::Eip7702(signed) => {
172 let (tx, sig, hash) = signed.into_parts();
173 Self::new(ArbTypedTransaction::Eip7702(tx), sig, hash)
174 }
175 }
176 }
177
178 pub const fn signature(&self) -> &Signature {
179 &self.signature
180 }
181
182 pub fn inner(&self) -> &ArbTypedTransaction {
184 &self.transaction
185 }
186
187 pub fn split(self) -> (ArbTypedTransaction, Signature, B256) {
189 let hash = *self.hash.get_or_init(|| self.compute_hash());
190 (self.transaction, self.signature, hash)
191 }
192
193 pub const fn tx_type(&self) -> ArbTxTypeLocal {
194 match &self.transaction {
195 ArbTypedTransaction::Deposit(_) => ArbTxTypeLocal::Deposit,
196 ArbTypedTransaction::Unsigned(_) => ArbTxTypeLocal::Unsigned,
197 ArbTypedTransaction::Contract(_) => ArbTxTypeLocal::Contract,
198 ArbTypedTransaction::Retry(_) => ArbTxTypeLocal::Retry,
199 ArbTypedTransaction::SubmitRetryable(_) => ArbTxTypeLocal::SubmitRetryable,
200 ArbTypedTransaction::Internal(_) => ArbTxTypeLocal::Internal,
201 ArbTypedTransaction::Legacy(_) => ArbTxTypeLocal::Legacy,
202 ArbTypedTransaction::Eip2930(_) => ArbTxTypeLocal::Eip2930,
203 ArbTypedTransaction::Eip1559(_) => ArbTxTypeLocal::Eip1559,
204 ArbTypedTransaction::Eip4844(_) => ArbTxTypeLocal::Eip4844,
205 ArbTypedTransaction::Eip7702(_) => ArbTxTypeLocal::Eip7702,
206 }
207 }
208
209 fn compute_hash(&self) -> B256 {
210 keccak256(self.encoded_2718())
211 }
212
213 fn zero_sig() -> Signature {
214 Signature::new(U256::ZERO, U256::ZERO, false)
215 }
216}
217
218impl Hash for ArbTransactionSigned {
223 fn hash<H: Hasher>(&self, state: &mut H) {
224 self.tx_hash().hash(state)
225 }
226}
227
228impl PartialEq for ArbTransactionSigned {
229 fn eq(&self, other: &Self) -> bool {
230 self.tx_hash() == other.tx_hash()
231 }
232}
233
234impl InMemorySize for ArbTransactionSigned {
235 fn size(&self) -> usize {
236 core::mem::size_of::<TxHash>() + core::mem::size_of::<Signature>()
237 }
238}
239
240impl TxHashRef for ArbTransactionSigned {
245 fn tx_hash(&self) -> &TxHash {
246 self.hash.get_or_init(|| self.compute_hash())
247 }
248}
249
250impl SignedTransaction for ArbTransactionSigned {
255 fn recalculate_hash(&self) -> B256 {
256 keccak256(self.encoded_2718())
257 }
258}
259
260impl alloy_consensus::transaction::SignerRecoverable for ArbTransactionSigned {
265 fn recover_signer(
266 &self,
267 ) -> Result<Address, reth_primitives_traits::transaction::signed::RecoveryError> {
268 match &self.transaction {
269 ArbTypedTransaction::Deposit(tx) => Ok(tx.from),
271 ArbTypedTransaction::Unsigned(tx) => Ok(tx.from),
272 ArbTypedTransaction::Contract(tx) => Ok(tx.from),
273 ArbTypedTransaction::Retry(tx) => Ok(tx.from),
274 ArbTypedTransaction::SubmitRetryable(tx) => Ok(tx.from),
275 ArbTypedTransaction::Internal(_) => Ok(ARBOS_ADDRESS),
276 ArbTypedTransaction::Legacy(tx) => {
278 let mut buf = Vec::new();
279 tx.encode_for_signing(&mut buf);
280 recover_signer(&self.signature, keccak256(&buf))
281 }
282 ArbTypedTransaction::Eip2930(tx) => {
283 let mut buf = Vec::new();
284 tx.encode_for_signing(&mut buf);
285 recover_signer(&self.signature, keccak256(&buf))
286 }
287 ArbTypedTransaction::Eip1559(tx) => {
288 let mut buf = Vec::new();
289 tx.encode_for_signing(&mut buf);
290 recover_signer(&self.signature, keccak256(&buf))
291 }
292 ArbTypedTransaction::Eip4844(tx) => {
293 let mut buf = Vec::new();
294 tx.encode_for_signing(&mut buf);
295 recover_signer(&self.signature, keccak256(&buf))
296 }
297 ArbTypedTransaction::Eip7702(tx) => {
298 let mut buf = Vec::new();
299 tx.encode_for_signing(&mut buf);
300 recover_signer(&self.signature, keccak256(&buf))
301 }
302 }
303 }
304
305 fn recover_signer_unchecked(
306 &self,
307 ) -> Result<Address, reth_primitives_traits::transaction::signed::RecoveryError> {
308 match &self.transaction {
309 ArbTypedTransaction::Deposit(tx) => Ok(tx.from),
310 ArbTypedTransaction::Unsigned(tx) => Ok(tx.from),
311 ArbTypedTransaction::Contract(tx) => Ok(tx.from),
312 ArbTypedTransaction::Retry(tx) => Ok(tx.from),
313 ArbTypedTransaction::SubmitRetryable(tx) => Ok(tx.from),
314 ArbTypedTransaction::Internal(_) => Ok(ARBOS_ADDRESS),
315 ArbTypedTransaction::Legacy(tx) => {
316 let mut buf = Vec::new();
317 tx.encode_for_signing(&mut buf);
318 recover_signer_unchecked(&self.signature, keccak256(&buf))
319 }
320 ArbTypedTransaction::Eip2930(tx) => {
321 let mut buf = Vec::new();
322 tx.encode_for_signing(&mut buf);
323 recover_signer_unchecked(&self.signature, keccak256(&buf))
324 }
325 ArbTypedTransaction::Eip1559(tx) => {
326 let mut buf = Vec::new();
327 tx.encode_for_signing(&mut buf);
328 recover_signer_unchecked(&self.signature, keccak256(&buf))
329 }
330 ArbTypedTransaction::Eip4844(tx) => {
331 let mut buf = Vec::new();
332 tx.encode_for_signing(&mut buf);
333 recover_signer_unchecked(&self.signature, keccak256(&buf))
334 }
335 ArbTypedTransaction::Eip7702(tx) => {
336 let mut buf = Vec::new();
337 tx.encode_for_signing(&mut buf);
338 recover_signer_unchecked(&self.signature, keccak256(&buf))
339 }
340 }
341 }
342}
343
344impl Typed2718 for ArbTransactionSigned {
349 fn is_legacy(&self) -> bool {
350 matches!(self.transaction, ArbTypedTransaction::Legacy(_))
351 }
352
353 fn ty(&self) -> u8 {
354 match &self.transaction {
355 ArbTypedTransaction::Legacy(_) => 0u8,
356 ArbTypedTransaction::Deposit(_) => ArbTxType::ArbitrumDepositTx.as_u8(),
357 ArbTypedTransaction::Unsigned(_) => ArbTxType::ArbitrumUnsignedTx.as_u8(),
358 ArbTypedTransaction::Contract(_) => ArbTxType::ArbitrumContractTx.as_u8(),
359 ArbTypedTransaction::Retry(_) => ArbTxType::ArbitrumRetryTx.as_u8(),
360 ArbTypedTransaction::SubmitRetryable(_) => ArbTxType::ArbitrumSubmitRetryableTx.as_u8(),
361 ArbTypedTransaction::Internal(_) => ArbTxType::ArbitrumInternalTx.as_u8(),
362 ArbTypedTransaction::Eip2930(_) => 0x01,
363 ArbTypedTransaction::Eip1559(_) => 0x02,
364 ArbTypedTransaction::Eip4844(_) => 0x03,
365 ArbTypedTransaction::Eip7702(_) => 0x04,
366 }
367 }
368}
369
370impl IsTyped2718 for ArbTransactionSigned {
375 fn is_type(type_id: u8) -> bool {
376 matches!(type_id, 0x01..=0x04) || ArbTxType::from_u8(type_id).is_ok()
378 }
379}
380
381impl Encodable2718 for ArbTransactionSigned {
386 fn type_flag(&self) -> Option<u8> {
387 if self.is_legacy() {
388 None
389 } else {
390 Some(self.ty())
391 }
392 }
393
394 fn encode_2718_len(&self) -> usize {
395 match &self.transaction {
396 ArbTypedTransaction::Legacy(tx) => tx.eip2718_encoded_length(&self.signature),
397 ArbTypedTransaction::Deposit(tx) => tx.length() + 1,
398 ArbTypedTransaction::Unsigned(tx) => tx.length() + 1,
399 ArbTypedTransaction::Contract(tx) => tx.length() + 1,
400 ArbTypedTransaction::Retry(tx) => tx.length() + 1,
401 ArbTypedTransaction::SubmitRetryable(tx) => tx.length() + 1,
402 ArbTypedTransaction::Internal(tx) => tx.length() + 1,
403 ArbTypedTransaction::Eip2930(tx) => tx.eip2718_encoded_length(&self.signature),
404 ArbTypedTransaction::Eip1559(tx) => tx.eip2718_encoded_length(&self.signature),
405 ArbTypedTransaction::Eip4844(tx) => tx.eip2718_encoded_length(&self.signature),
406 ArbTypedTransaction::Eip7702(tx) => tx.eip2718_encoded_length(&self.signature),
407 }
408 }
409
410 fn encode_2718(&self, out: &mut dyn alloy_rlp::bytes::BufMut) {
411 match &self.transaction {
412 ArbTypedTransaction::Legacy(tx) => tx.eip2718_encode(&self.signature, out),
413 ArbTypedTransaction::Deposit(tx) => {
414 out.put_u8(ArbTxType::ArbitrumDepositTx.as_u8());
415 tx.encode(out);
416 }
417 ArbTypedTransaction::Unsigned(tx) => {
418 out.put_u8(ArbTxType::ArbitrumUnsignedTx.as_u8());
419 tx.encode(out);
420 }
421 ArbTypedTransaction::Contract(tx) => {
422 out.put_u8(ArbTxType::ArbitrumContractTx.as_u8());
423 tx.encode(out);
424 }
425 ArbTypedTransaction::Retry(tx) => {
426 out.put_u8(ArbTxType::ArbitrumRetryTx.as_u8());
427 tx.encode(out);
428 }
429 ArbTypedTransaction::SubmitRetryable(tx) => {
430 out.put_u8(ArbTxType::ArbitrumSubmitRetryableTx.as_u8());
431 tx.encode(out);
432 }
433 ArbTypedTransaction::Internal(tx) => {
434 out.put_u8(ArbTxType::ArbitrumInternalTx.as_u8());
435 tx.encode(out);
436 }
437 ArbTypedTransaction::Eip2930(tx) => tx.eip2718_encode(&self.signature, out),
438 ArbTypedTransaction::Eip1559(tx) => tx.eip2718_encode(&self.signature, out),
439 ArbTypedTransaction::Eip4844(tx) => tx.eip2718_encode(&self.signature, out),
440 ArbTypedTransaction::Eip7702(tx) => tx.eip2718_encode(&self.signature, out),
441 }
442 }
443}
444
445impl Decodable2718 for ArbTransactionSigned {
450 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
451 if let Ok(kind) = ArbTxType::from_u8(ty) {
453 return Ok(match kind {
454 ArbTxType::ArbitrumDepositTx => {
455 let tx = ArbDepositTx::decode(buf)?;
456 Self::new_unhashed(ArbTypedTransaction::Deposit(tx), Self::zero_sig())
457 }
458 ArbTxType::ArbitrumUnsignedTx => {
459 let tx = ArbUnsignedTx::decode(buf)?;
460 Self::new_unhashed(ArbTypedTransaction::Unsigned(tx), Self::zero_sig())
461 }
462 ArbTxType::ArbitrumContractTx => {
463 let tx = ArbContractTx::decode(buf)?;
464 Self::new_unhashed(ArbTypedTransaction::Contract(tx), Self::zero_sig())
465 }
466 ArbTxType::ArbitrumRetryTx => {
467 let tx = ArbRetryTx::decode(buf)?;
468 Self::new_unhashed(ArbTypedTransaction::Retry(tx), Self::zero_sig())
469 }
470 ArbTxType::ArbitrumSubmitRetryableTx => {
471 let tx = ArbSubmitRetryableTx::decode(buf)?;
472 Self::new_unhashed(ArbTypedTransaction::SubmitRetryable(tx), Self::zero_sig())
473 }
474 ArbTxType::ArbitrumInternalTx => {
475 let tx = ArbInternalTx::decode(buf)?;
476 Self::new_unhashed(ArbTypedTransaction::Internal(tx), Self::zero_sig())
477 }
478 ArbTxType::ArbitrumLegacyTx => return Err(Eip2718Error::UnexpectedType(0x78)),
479 });
480 }
481
482 match alloy_consensus::TxType::try_from(ty).map_err(|_| Eip2718Error::UnexpectedType(ty))? {
484 alloy_consensus::TxType::Legacy => Err(Eip2718Error::UnexpectedType(0)),
485 alloy_consensus::TxType::Eip2930 => {
486 let (tx, sig) = alloy_consensus::TxEip2930::rlp_decode_with_signature(buf)?;
487 Ok(Self::new_unhashed(ArbTypedTransaction::Eip2930(tx), sig))
488 }
489 alloy_consensus::TxType::Eip1559 => {
490 let (tx, sig) = alloy_consensus::TxEip1559::rlp_decode_with_signature(buf)?;
491 Ok(Self::new_unhashed(ArbTypedTransaction::Eip1559(tx), sig))
492 }
493 alloy_consensus::TxType::Eip4844 => {
494 let (tx, sig) = alloy_consensus::TxEip4844::rlp_decode_with_signature(buf)?;
495 Ok(Self::new_unhashed(ArbTypedTransaction::Eip4844(tx), sig))
496 }
497 alloy_consensus::TxType::Eip7702 => {
498 let (tx, sig) = alloy_consensus::TxEip7702::rlp_decode_with_signature(buf)?;
499 Ok(Self::new_unhashed(ArbTypedTransaction::Eip7702(tx), sig))
500 }
501 }
502 }
503
504 fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
505 let (tx, sig, hash) = TxLegacy::rlp_decode_signed(buf)?.into_parts();
506 let signed_tx = Self::new_unhashed(ArbTypedTransaction::Legacy(tx), sig);
507 signed_tx.hash.get_or_init(|| hash);
508 Ok(signed_tx)
509 }
510}
511
512impl Encodable for ArbTransactionSigned {
517 fn encode(&self, out: &mut dyn alloy_rlp::bytes::BufMut) {
518 self.network_encode(out);
519 }
520 fn length(&self) -> usize {
521 let mut payload_length = self.encode_2718_len();
522 if !self.is_legacy() {
523 payload_length += alloy_rlp::Header {
524 list: false,
525 payload_length,
526 }
527 .length();
528 }
529 payload_length
530 }
531}
532
533impl Decodable for ArbTransactionSigned {
534 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
535 Self::network_decode(buf).map_err(Into::into)
536 }
537}
538
539impl ConsensusTx for ArbTransactionSigned {
544 fn chain_id(&self) -> Option<u64> {
545 match &self.transaction {
546 ArbTypedTransaction::Legacy(tx) => tx.chain_id,
547 ArbTypedTransaction::Deposit(tx) => Some(tx.chain_id.to::<u64>()),
548 ArbTypedTransaction::Unsigned(tx) => Some(tx.chain_id.to::<u64>()),
549 ArbTypedTransaction::Contract(tx) => Some(tx.chain_id.to::<u64>()),
550 ArbTypedTransaction::Retry(tx) => Some(tx.chain_id.to::<u64>()),
551 ArbTypedTransaction::SubmitRetryable(tx) => Some(tx.chain_id.to::<u64>()),
552 ArbTypedTransaction::Internal(tx) => Some(tx.chain_id.to::<u64>()),
553 ArbTypedTransaction::Eip2930(tx) => Some(tx.chain_id),
554 ArbTypedTransaction::Eip1559(tx) => Some(tx.chain_id),
555 ArbTypedTransaction::Eip4844(tx) => Some(tx.chain_id),
556 ArbTypedTransaction::Eip7702(tx) => Some(tx.chain_id),
557 }
558 }
559
560 fn nonce(&self) -> u64 {
561 match &self.transaction {
562 ArbTypedTransaction::Legacy(tx) => tx.nonce,
563 ArbTypedTransaction::Deposit(_) => 0,
564 ArbTypedTransaction::Unsigned(tx) => tx.nonce,
565 ArbTypedTransaction::Contract(_) => 0,
566 ArbTypedTransaction::Retry(tx) => tx.nonce,
567 ArbTypedTransaction::SubmitRetryable(_) => 0,
568 ArbTypedTransaction::Internal(_) => 0,
569 ArbTypedTransaction::Eip2930(tx) => tx.nonce,
570 ArbTypedTransaction::Eip1559(tx) => tx.nonce,
571 ArbTypedTransaction::Eip4844(tx) => tx.nonce,
572 ArbTypedTransaction::Eip7702(tx) => tx.nonce,
573 }
574 }
575
576 fn gas_limit(&self) -> u64 {
577 match &self.transaction {
578 ArbTypedTransaction::Legacy(tx) => tx.gas_limit,
579 ArbTypedTransaction::Deposit(_) => 0,
580 ArbTypedTransaction::Unsigned(tx) => tx.gas,
581 ArbTypedTransaction::Contract(tx) => tx.gas,
582 ArbTypedTransaction::Retry(tx) => tx.gas,
583 ArbTypedTransaction::SubmitRetryable(tx) => tx.gas,
584 ArbTypedTransaction::Internal(_) => 0,
585 ArbTypedTransaction::Eip2930(tx) => tx.gas_limit,
586 ArbTypedTransaction::Eip1559(tx) => tx.gas_limit,
587 ArbTypedTransaction::Eip4844(tx) => tx.gas_limit,
588 ArbTypedTransaction::Eip7702(tx) => tx.gas_limit,
589 }
590 }
591
592 fn gas_price(&self) -> Option<u128> {
593 match &self.transaction {
594 ArbTypedTransaction::Legacy(tx) => Some(tx.gas_price),
595 ArbTypedTransaction::Eip2930(tx) => Some(tx.gas_price),
596 _ => None,
597 }
598 }
599
600 fn max_fee_per_gas(&self) -> u128 {
601 match &self.transaction {
602 ArbTypedTransaction::Legacy(tx) => tx.gas_price,
603 ArbTypedTransaction::Eip2930(tx) => tx.gas_price,
604 ArbTypedTransaction::Unsigned(tx) => tx.gas_fee_cap.to::<u128>(),
605 ArbTypedTransaction::Contract(tx) => tx.gas_fee_cap.to::<u128>(),
606 ArbTypedTransaction::Retry(tx) => tx.gas_fee_cap.to::<u128>(),
607 ArbTypedTransaction::SubmitRetryable(tx) => tx.gas_fee_cap.to::<u128>(),
608 ArbTypedTransaction::Eip1559(tx) => tx.max_fee_per_gas,
609 ArbTypedTransaction::Eip4844(tx) => tx.max_fee_per_gas,
610 ArbTypedTransaction::Eip7702(tx) => tx.max_fee_per_gas,
611 _ => 0,
612 }
613 }
614
615 fn max_priority_fee_per_gas(&self) -> Option<u128> {
616 Some(0)
617 }
618
619 fn max_fee_per_blob_gas(&self) -> Option<u128> {
620 Some(0)
621 }
622
623 fn priority_fee_or_price(&self) -> u128 {
624 self.gas_price().unwrap_or(0)
625 }
626
627 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
628 match &self.transaction {
629 ArbTypedTransaction::Legacy(tx) => tx.gas_price,
630 ArbTypedTransaction::Eip2930(tx) => tx.gas_price,
631 _ => base_fee.unwrap_or(0) as u128,
633 }
634 }
635
636 fn effective_tip_per_gas(&self, _base_fee: u64) -> Option<u128> {
637 Some(0)
638 }
639
640 fn is_dynamic_fee(&self) -> bool {
641 !matches!(
642 self.transaction,
643 ArbTypedTransaction::Legacy(_) | ArbTypedTransaction::Eip2930(_)
644 )
645 }
646
647 fn kind(&self) -> TxKind {
648 match &self.transaction {
649 ArbTypedTransaction::Legacy(tx) => tx.to,
650 ArbTypedTransaction::Deposit(tx) => {
651 if tx.to == Address::ZERO {
652 TxKind::Create
653 } else {
654 TxKind::Call(tx.to)
655 }
656 }
657 ArbTypedTransaction::Unsigned(tx) => match tx.to {
658 Some(to) => TxKind::Call(to),
659 None => TxKind::Create,
660 },
661 ArbTypedTransaction::Contract(tx) => match tx.to {
662 Some(to) => TxKind::Call(to),
663 None => TxKind::Create,
664 },
665 ArbTypedTransaction::Retry(tx) => match tx.to {
666 Some(to) => TxKind::Call(to),
667 None => TxKind::Create,
668 },
669 ArbTypedTransaction::SubmitRetryable(_) => TxKind::Call(RETRYABLE_ADDRESS),
670 ArbTypedTransaction::Internal(_) => TxKind::Call(ARBOS_ADDRESS),
671 ArbTypedTransaction::Eip2930(tx) => tx.to,
672 ArbTypedTransaction::Eip1559(tx) => tx.to,
673 ArbTypedTransaction::Eip4844(tx) => TxKind::Call(tx.to),
674 ArbTypedTransaction::Eip7702(tx) => TxKind::Call(tx.to),
675 }
676 }
677
678 fn is_create(&self) -> bool {
679 matches!(self.kind(), TxKind::Create)
680 }
681
682 fn value(&self) -> U256 {
683 match &self.transaction {
684 ArbTypedTransaction::Legacy(tx) => tx.value,
685 ArbTypedTransaction::Deposit(tx) => tx.value,
686 ArbTypedTransaction::Unsigned(tx) => tx.value,
687 ArbTypedTransaction::Contract(tx) => tx.value,
688 ArbTypedTransaction::Retry(tx) => tx.value,
689 ArbTypedTransaction::SubmitRetryable(tx) => tx.retry_value,
690 ArbTypedTransaction::Internal(_) => U256::ZERO,
691 ArbTypedTransaction::Eip2930(tx) => tx.value,
692 ArbTypedTransaction::Eip1559(tx) => tx.value,
693 ArbTypedTransaction::Eip4844(tx) => tx.value,
694 ArbTypedTransaction::Eip7702(tx) => tx.value,
695 }
696 }
697
698 fn input(&self) -> &Bytes {
699 match &self.transaction {
700 ArbTypedTransaction::Legacy(tx) => &tx.input,
701 ArbTypedTransaction::Deposit(_) => self.input_cache.get_or_init(Bytes::new),
702 ArbTypedTransaction::Unsigned(tx) => self.input_cache.get_or_init(|| tx.data.clone()),
703 ArbTypedTransaction::Contract(tx) => self.input_cache.get_or_init(|| tx.data.clone()),
704 ArbTypedTransaction::Retry(tx) => self.input_cache.get_or_init(|| tx.data.clone()),
705 ArbTypedTransaction::SubmitRetryable(tx) => self.input_cache.get_or_init(|| {
706 let sel = arb_alloy_predeploys::selector(
707 arb_alloy_predeploys::SIG_RETRY_SUBMIT_RETRYABLE,
708 );
709 let mut out = Vec::with_capacity(4 + tx.retry_data.len());
710 out.extend_from_slice(&sel);
711 out.extend_from_slice(&tx.retry_data);
712 Bytes::from(out)
713 }),
714 ArbTypedTransaction::Internal(tx) => self.input_cache.get_or_init(|| tx.data.clone()),
715 ArbTypedTransaction::Eip2930(tx) => &tx.input,
716 ArbTypedTransaction::Eip1559(tx) => &tx.input,
717 ArbTypedTransaction::Eip4844(tx) => &tx.input,
718 ArbTypedTransaction::Eip7702(tx) => &tx.input,
719 }
720 }
721
722 fn access_list(&self) -> Option<&alloy_eips::eip2930::AccessList> {
723 match &self.transaction {
724 ArbTypedTransaction::Eip2930(tx) => Some(&tx.access_list),
725 ArbTypedTransaction::Eip1559(tx) => Some(&tx.access_list),
726 ArbTypedTransaction::Eip4844(tx) => Some(&tx.access_list),
727 ArbTypedTransaction::Eip7702(tx) => Some(&tx.access_list),
728 _ => None,
729 }
730 }
731
732 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
733 None
734 }
735
736 fn authorization_list(&self) -> Option<&[alloy_eips::eip7702::SignedAuthorization]> {
737 None
738 }
739}
740
741impl serde::Serialize for ArbTransactionSigned {
746 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
747 where
748 S: serde::Serializer,
749 {
750 use serde::ser::SerializeStruct;
751 let mut state = serializer.serialize_struct("ArbTransactionSigned", 2)?;
752 state.serialize_field("signature", &self.signature)?;
753 state.serialize_field("hash", self.tx_hash())?;
754 state.end()
755 }
756}
757
758impl<'de> serde::Deserialize<'de> for ArbTransactionSigned {
759 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
760 where
761 D: serde::Deserializer<'de>,
762 {
763 #[derive(serde::Deserialize)]
764 struct Helper {
765 signature: Signature,
766 #[serde(default)]
767 transaction_encoded_2718: Option<alloy_primitives::Bytes>,
768 }
769 let helper = Helper::deserialize(deserializer)?;
770 if let Some(encoded) = helper.transaction_encoded_2718 {
771 let mut slice: &[u8] = encoded.as_ref();
772 let parsed = Self::network_decode(&mut slice).map_err(serde::de::Error::custom)?;
773 Ok(parsed)
774 } else {
775 Ok(Self::new_unhashed(
777 ArbTypedTransaction::Legacy(TxLegacy::default()),
778 helper.signature,
779 ))
780 }
781 }
782}
783
784impl reth_primitives_traits::serde_bincode_compat::RlpBincode for ArbTransactionSigned {}
789
790impl reth_codecs::Compact for ArbTransactionSigned {
795 fn to_compact<B>(&self, buf: &mut B) -> usize
796 where
797 B: bytes::BufMut + AsMut<[u8]>,
798 {
799 let encoded = self.encoded_2718();
801 let len = encoded.len() as u32;
802 buf.put_u32(len);
803 buf.put_slice(&encoded);
804 let sig_bytes = self.signature.as_bytes();
806 buf.put_slice(&sig_bytes);
807 0
808 }
809
810 fn from_compact(buf: &[u8], _len: usize) -> (Self, &[u8]) {
811 use bytes::Buf;
812 let mut slice = buf;
813 let tx_len = slice.get_u32() as usize;
814 let tx_bytes = &slice[..tx_len];
815 slice = &slice[tx_len..];
816
817 let mut tx_buf = tx_bytes;
818 let tx = Self::network_decode(&mut tx_buf).unwrap_or_else(|_| {
819 Self::new_unhashed(
820 ArbTypedTransaction::Legacy(TxLegacy::default()),
821 Signature::new(U256::ZERO, U256::ZERO, false),
822 )
823 });
824
825 if slice.len() >= 65 {
827 let _sig_bytes = &slice[..65];
828 slice = &slice[65..];
829 }
830
831 (tx, slice)
832 }
833}
834
835impl reth_db_api::table::Compress for ArbTransactionSigned {
840 type Compressed = Vec<u8>;
841
842 fn compress_to_buf<B: bytes::BufMut + AsMut<[u8]>>(&self, buf: &mut B) {
843 let _ = reth_codecs::Compact::to_compact(self, buf);
844 }
845}
846
847impl reth_db_api::table::Decompress for ArbTransactionSigned {
848 fn decompress(value: &[u8]) -> Result<Self, reth_db_api::DatabaseError> {
849 let (obj, _) = reth_codecs::Compact::from_compact(value, value.len());
850 Ok(obj)
851 }
852}
853
854#[derive(Debug, Clone)]
860pub struct SubmitRetryableInfo {
861 pub from: Address,
862 pub deposit_value: U256,
863 pub retry_value: U256,
864 pub gas_fee_cap: U256,
865 pub gas: u64,
866 pub retry_to: Option<Address>,
867 pub retry_data: Vec<u8>,
868 pub beneficiary: Address,
869 pub max_submission_fee: U256,
870 pub fee_refund_addr: Address,
871 pub l1_base_fee: U256,
872 pub request_id: B256,
873}
874
875#[derive(Debug, Clone)]
877pub struct RetryTxInfo {
878 pub from: Address,
879 pub ticket_id: B256,
880 pub refund_to: Address,
881 pub gas_fee_cap: U256,
882 pub max_refund: U256,
883 pub submission_fee_refund: U256,
884}
885
886pub trait ArbTransactionExt {
889 fn submit_retryable_info(&self) -> Option<SubmitRetryableInfo> {
890 None
891 }
892 fn retry_tx_info(&self) -> Option<RetryTxInfo> {
893 None
894 }
895}
896
897impl ArbTransactionExt for ArbTransactionSigned {
898 fn submit_retryable_info(&self) -> Option<SubmitRetryableInfo> {
899 match &self.transaction {
900 ArbTypedTransaction::SubmitRetryable(tx) => Some(SubmitRetryableInfo {
901 from: tx.from,
902 deposit_value: tx.deposit_value,
903 retry_value: tx.retry_value,
904 gas_fee_cap: tx.gas_fee_cap,
905 gas: tx.gas,
906 retry_to: tx.retry_to,
907 retry_data: tx.retry_data.to_vec(),
908 beneficiary: tx.beneficiary,
909 max_submission_fee: tx.max_submission_fee,
910 fee_refund_addr: tx.fee_refund_addr,
911 l1_base_fee: tx.l1_base_fee,
912 request_id: tx.request_id,
913 }),
914 _ => None,
915 }
916 }
917
918 fn retry_tx_info(&self) -> Option<RetryTxInfo> {
919 match &self.transaction {
920 ArbTypedTransaction::Retry(tx) => Some(RetryTxInfo {
921 from: tx.from,
922 ticket_id: tx.ticket_id,
923 refund_to: tx.refund_to,
924 gas_fee_cap: tx.gas_fee_cap,
925 max_refund: tx.max_refund,
926 submission_fee_refund: tx.submission_fee_refund,
927 }),
928 _ => None,
929 }
930 }
931}
932
933impl<T> ArbTransactionExt for alloy_consensus::EthereumTxEnvelope<T> {}
935
936#[cfg(test)]
937mod tests {
938 use super::*;
939
940 #[test]
941 fn roundtrip_unsigned_tx() {
942 let tx = ArbUnsignedTx {
943 chain_id: U256::from(42161u64),
944 from: alloy_primitives::address!("00000000000000000000000000000000000000aa"),
945 nonce: 7,
946 gas_fee_cap: U256::from(1_000_000u64),
947 gas: 21000,
948 to: Some(alloy_primitives::address!(
949 "00000000000000000000000000000000000000bb"
950 )),
951 value: U256::from(123u64),
952 data: Vec::new().into(),
953 };
954
955 let mut enc = Vec::with_capacity(1 + tx.length());
956 enc.push(ArbTxType::ArbitrumUnsignedTx.as_u8());
957 tx.encode(&mut enc);
958
959 let signed =
960 ArbTransactionSigned::decode_2718_exact(enc.as_slice()).expect("typed decode ok");
961 assert_eq!(signed.tx_type(), ArbTxTypeLocal::Unsigned);
962 assert_eq!(signed.chain_id(), Some(42161));
963 assert_eq!(signed.nonce(), 7);
964 assert_eq!(signed.gas_limit(), 21000);
965 assert_eq!(signed.value(), U256::from(123u64));
966 }
967
968 #[test]
969 fn deposit_tx_has_zero_gas() {
970 let tx = ArbDepositTx {
971 chain_id: U256::from(42161u64),
972 l1_request_id: B256::ZERO,
973 from: Address::ZERO,
974 to: Address::ZERO,
975 value: U256::from(100u64),
976 };
977
978 let signed = ArbTransactionSigned::new_unhashed(
979 ArbTypedTransaction::Deposit(tx),
980 ArbTransactionSigned::zero_sig(),
981 );
982
983 assert_eq!(signed.gas_limit(), 0);
984 assert_eq!(signed.nonce(), 0);
985 assert_eq!(signed.tx_type(), ArbTxTypeLocal::Deposit);
986 }
987}