1use super::{fee_token::TxFeeToken, tt_signed::AASigned};
2use crate::{TempoTransaction, subblock::PartialValidatorKey};
3use alloy_consensus::{
4 EthereumTxEnvelope, SignableTransaction, Signed, Transaction, TxEip1559, TxEip2930, TxEip7702,
5 TxLegacy, TxType, TypedTransaction,
6 error::{UnsupportedTransactionType, ValueError},
7 transaction::Either,
8};
9use alloy_primitives::{Address, B256, Bytes, Signature, SignatureError, TxKind, U256, hex};
10use core::fmt;
11use reth_primitives_traits::InMemorySize;
12
13pub const TIP20_PAYMENT_PREFIX: [u8; 14] = hex!("20C0000000000000000000000000");
16
17pub const TEMPO_SYSTEM_TX_SIGNATURE: Signature = Signature::new(U256::ZERO, U256::ZERO, false);
19
20pub const TEMPO_SYSTEM_TX_SENDER: Address = Address::ZERO;
22
23#[derive(Clone, Debug, alloy_consensus::TransactionEnvelope)]
32#[envelope(
33 tx_type_name = TempoTxType,
34 typed = TempoTypedTransaction,
35 arbitrary_cfg(any(test, feature = "arbitrary")),
36 serde_cfg(feature = "serde")
37)]
38#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact, rlp))]
39#[expect(clippy::large_enum_variant)]
40pub enum TempoTxEnvelope {
41 #[envelope(ty = 0)]
43 Legacy(Signed<TxLegacy>),
44
45 #[envelope(ty = 1)]
47 Eip2930(Signed<TxEip2930>),
48
49 #[envelope(ty = 2)]
51 Eip1559(Signed<TxEip1559>),
52
53 #[envelope(ty = 4)]
55 Eip7702(Signed<TxEip7702>),
56
57 #[envelope(ty = 0x76, typed = TempoTransaction)]
59 AA(AASigned),
60
61 #[envelope(ty = 0x77)]
63 FeeToken(Signed<TxFeeToken>),
64}
65
66impl TryFrom<TxType> for TempoTxType {
67 type Error = UnsupportedTransactionType<TxType>;
68
69 fn try_from(value: TxType) -> Result<Self, Self::Error> {
70 Ok(match value {
71 TxType::Legacy => Self::Legacy,
72 TxType::Eip2930 => Self::Eip2930,
73 TxType::Eip1559 => Self::Eip1559,
74 TxType::Eip4844 => return Err(UnsupportedTransactionType::new(TxType::Eip4844)),
75 TxType::Eip7702 => Self::Eip7702,
76 })
77 }
78}
79
80impl TryFrom<TempoTxType> for TxType {
81 type Error = UnsupportedTransactionType<TempoTxType>;
82
83 fn try_from(value: TempoTxType) -> Result<Self, Self::Error> {
84 Ok(match value {
85 TempoTxType::Legacy => Self::Legacy,
86 TempoTxType::Eip2930 => Self::Eip2930,
87 TempoTxType::Eip1559 => Self::Eip1559,
88 TempoTxType::Eip7702 => Self::Eip7702,
89 TempoTxType::FeeToken => {
90 return Err(UnsupportedTransactionType::new(TempoTxType::FeeToken));
91 }
92 TempoTxType::AA => {
93 return Err(UnsupportedTransactionType::new(TempoTxType::AA));
94 }
95 })
96 }
97}
98
99impl TempoTxEnvelope {
100 pub fn fee_token(&self) -> Option<Address> {
102 match self {
103 Self::FeeToken(tx) => tx.tx().fee_token,
104 Self::AA(tx) => tx.tx().fee_token,
105 _ => None,
106 }
107 }
108
109 pub fn fee_payer(&self, sender: Address) -> Result<Address, SignatureError> {
111 match self {
112 Self::FeeToken(tx) => {
113 if let Some(fee_payer_signature) = tx.tx().fee_payer_signature {
114 fee_payer_signature
115 .recover_address_from_prehash(&tx.tx().fee_payer_signature_hash(sender))
116 } else {
117 Ok(sender)
118 }
119 }
120 Self::AA(tx) => {
121 if let Some(fee_payer_signature) = tx.tx().fee_payer_signature {
122 fee_payer_signature
123 .recover_address_from_prehash(&tx.tx().fee_payer_signature_hash(sender))
124 } else {
125 Ok(sender)
126 }
127 }
128 _ => Ok(sender),
129 }
130 }
131
132 pub const fn tx_type(&self) -> TempoTxType {
134 match self {
135 Self::Legacy(_) => TempoTxType::Legacy,
136 Self::Eip2930(_) => TempoTxType::Eip2930,
137 Self::Eip1559(_) => TempoTxType::Eip1559,
138 Self::Eip7702(_) => TempoTxType::Eip7702,
139 Self::AA(_) => TempoTxType::AA,
140 Self::FeeToken(_) => TempoTxType::FeeToken,
141 }
142 }
143
144 pub fn is_fee_token(&self) -> bool {
146 matches!(self, Self::FeeToken(_) | Self::AA(_))
147 }
148
149 pub fn authorization_list(&self) -> Option<&[alloy_eips::eip7702::SignedAuthorization]> {
151 match self {
152 Self::Eip7702(tx) => Some(&tx.tx().authorization_list),
153 Self::FeeToken(tx) => Some(&tx.tx().authorization_list),
154 _ => None,
155 }
156 }
157
158 pub fn tempo_authorization_list(
160 &self,
161 ) -> Option<&[crate::transaction::TempoSignedAuthorization]> {
162 match self {
163 Self::AA(tx) => Some(&tx.tx().tempo_authorization_list),
164 _ => None,
165 }
166 }
167
168 pub fn is_system_tx(&self) -> bool {
170 matches!(self, Self::Legacy(tx) if tx.signature() == &TEMPO_SYSTEM_TX_SIGNATURE)
171 }
172
173 pub fn is_valid_system_tx(&self, chain_id: u64) -> bool {
175 self.max_fee_per_gas() == 0
176 && self.gas_limit() == 0
177 && self.value().is_zero()
178 && self.chain_id() == Some(chain_id)
179 && self.nonce() == 0
180 }
181
182 pub fn is_payment(&self) -> bool {
186 match self {
187 Self::Legacy(tx) => tx
188 .tx()
189 .to
190 .to()
191 .is_some_and(|to| to.starts_with(&TIP20_PAYMENT_PREFIX)),
192 Self::Eip2930(tx) => tx
193 .tx()
194 .to
195 .to()
196 .is_some_and(|to| to.starts_with(&TIP20_PAYMENT_PREFIX)),
197 Self::Eip1559(tx) => tx
198 .tx()
199 .to
200 .to()
201 .is_some_and(|to| to.starts_with(&TIP20_PAYMENT_PREFIX)),
202 Self::Eip7702(tx) => tx.tx().to.starts_with(&TIP20_PAYMENT_PREFIX),
203 Self::FeeToken(tx) => tx
204 .tx()
205 .to
206 .to()
207 .is_some_and(|to| to.starts_with(&TIP20_PAYMENT_PREFIX)),
208 Self::AA(tx) => tx.tx().calls.iter().all(|call| {
209 call.to
210 .to()
211 .is_some_and(|to| to.starts_with(&TIP20_PAYMENT_PREFIX))
212 }),
213 }
214 }
215
216 pub fn subblock_proposer(&self) -> Option<PartialValidatorKey> {
218 let Self::AA(tx) = &self else { return None };
219 tx.tx().subblock_proposer()
220 }
221
222 pub fn as_aa(&self) -> Option<&AASigned> {
224 match self {
225 Self::AA(tx) => Some(tx),
226 _ => None,
227 }
228 }
229
230 pub fn nonce_key(&self) -> Option<U256> {
232 self.as_aa().map(|tx| tx.tx().nonce_key)
233 }
234
235 pub fn is_aa(&self) -> bool {
237 matches!(self, Self::AA(_))
238 }
239
240 pub fn calls(&self) -> impl Iterator<Item = (TxKind, &Bytes)> {
242 if let Some(aa) = self.as_aa() {
243 Either::Left(aa.tx().calls.iter().map(|call| (call.to, &call.input)))
244 } else {
245 Either::Right(core::iter::once((self.kind(), self.input())))
246 }
247 }
248}
249
250impl alloy_consensus::transaction::SignerRecoverable for TempoTxEnvelope {
251 fn recover_signer(
252 &self,
253 ) -> Result<alloy_primitives::Address, alloy_consensus::crypto::RecoveryError> {
254 match self {
255 Self::Legacy(tx) if tx.signature() == &TEMPO_SYSTEM_TX_SIGNATURE => Ok(Address::ZERO),
256 Self::Legacy(tx) => alloy_consensus::transaction::SignerRecoverable::recover_signer(tx),
257 Self::Eip2930(tx) => {
258 alloy_consensus::transaction::SignerRecoverable::recover_signer(tx)
259 }
260 Self::Eip1559(tx) => {
261 alloy_consensus::transaction::SignerRecoverable::recover_signer(tx)
262 }
263 Self::Eip7702(tx) => {
264 alloy_consensus::transaction::SignerRecoverable::recover_signer(tx)
265 }
266 Self::FeeToken(tx) => {
267 alloy_consensus::transaction::SignerRecoverable::recover_signer(tx)
268 }
269 Self::AA(tx) => alloy_consensus::transaction::SignerRecoverable::recover_signer(tx),
270 }
271 }
272
273 fn recover_signer_unchecked(
274 &self,
275 ) -> Result<alloy_primitives::Address, alloy_consensus::crypto::RecoveryError> {
276 match self {
277 Self::Legacy(tx) if tx.signature() == &TEMPO_SYSTEM_TX_SIGNATURE => Ok(Address::ZERO),
278 Self::Legacy(tx) => {
279 alloy_consensus::transaction::SignerRecoverable::recover_signer_unchecked(tx)
280 }
281 Self::Eip2930(tx) => {
282 alloy_consensus::transaction::SignerRecoverable::recover_signer_unchecked(tx)
283 }
284 Self::Eip1559(tx) => {
285 alloy_consensus::transaction::SignerRecoverable::recover_signer_unchecked(tx)
286 }
287 Self::Eip7702(tx) => {
288 alloy_consensus::transaction::SignerRecoverable::recover_signer_unchecked(tx)
289 }
290 Self::FeeToken(tx) => {
291 alloy_consensus::transaction::SignerRecoverable::recover_signer_unchecked(tx)
292 }
293 Self::AA(tx) => {
294 alloy_consensus::transaction::SignerRecoverable::recover_signer_unchecked(tx)
295 }
296 }
297 }
298}
299
300impl reth_primitives_traits::InMemorySize for TempoTxEnvelope {
301 fn size(&self) -> usize {
302 match self {
303 Self::Legacy(tx) => reth_primitives_traits::InMemorySize::size(tx),
304 Self::Eip2930(tx) => reth_primitives_traits::InMemorySize::size(tx),
305 Self::Eip1559(tx) => reth_primitives_traits::InMemorySize::size(tx),
306 Self::Eip7702(tx) => reth_primitives_traits::InMemorySize::size(tx),
307 Self::AA(tx) => reth_primitives_traits::InMemorySize::size(tx),
308 Self::FeeToken(tx) => reth_primitives_traits::InMemorySize::size(tx),
309 }
310 }
311}
312
313impl alloy_consensus::transaction::TxHashRef for TempoTxEnvelope {
314 fn tx_hash(&self) -> &B256 {
315 match self {
316 Self::Legacy(tx) => tx.hash(),
317 Self::Eip2930(tx) => tx.hash(),
318 Self::Eip1559(tx) => tx.hash(),
319 Self::Eip7702(tx) => tx.hash(),
320 Self::AA(tx) => tx.hash(),
321 Self::FeeToken(tx) => tx.hash(),
322 }
323 }
324}
325
326impl reth_primitives_traits::SignedTransaction for TempoTxEnvelope {}
327
328impl InMemorySize for TempoTxType {
329 fn size(&self) -> usize {
330 core::mem::size_of::<Self>()
331 }
332}
333
334impl fmt::Display for TempoTxType {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 match self {
337 Self::Legacy => write!(f, "Legacy"),
338 Self::Eip2930 => write!(f, "EIP-2930"),
339 Self::Eip1559 => write!(f, "EIP-1559"),
340 Self::Eip7702 => write!(f, "EIP-7702"),
341 Self::AA => write!(f, "AA"),
342 Self::FeeToken => write!(f, "FeeToken"),
343 }
344 }
345}
346
347impl<Eip4844> TryFrom<EthereumTxEnvelope<Eip4844>> for TempoTxEnvelope {
348 type Error = ValueError<EthereumTxEnvelope<Eip4844>>;
349
350 fn try_from(value: EthereumTxEnvelope<Eip4844>) -> Result<Self, Self::Error> {
351 match value {
352 EthereumTxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
353 EthereumTxEnvelope::Eip2930(tx) => Ok(Self::Eip2930(tx)),
354 tx @ EthereumTxEnvelope::Eip4844(_) => Err(ValueError::new_static(
355 tx,
356 "EIP-4844 transactions are not supported",
357 )),
358 EthereumTxEnvelope::Eip1559(tx) => Ok(Self::Eip1559(tx)),
359 EthereumTxEnvelope::Eip7702(tx) => Ok(Self::Eip7702(tx)),
360 }
361 }
362}
363
364impl From<Signed<TxLegacy>> for TempoTxEnvelope {
365 fn from(value: Signed<TxLegacy>) -> Self {
366 Self::Legacy(value)
367 }
368}
369
370impl From<Signed<TxEip2930>> for TempoTxEnvelope {
371 fn from(value: Signed<TxEip2930>) -> Self {
372 Self::Eip2930(value)
373 }
374}
375
376impl From<Signed<TxEip1559>> for TempoTxEnvelope {
377 fn from(value: Signed<TxEip1559>) -> Self {
378 Self::Eip1559(value)
379 }
380}
381
382impl From<Signed<TxEip7702>> for TempoTxEnvelope {
383 fn from(value: Signed<TxEip7702>) -> Self {
384 Self::Eip7702(value)
385 }
386}
387
388impl From<Signed<TxFeeToken>> for TempoTxEnvelope {
389 fn from(value: Signed<TxFeeToken>) -> Self {
390 Self::FeeToken(value)
391 }
392}
393
394impl From<AASigned> for TempoTxEnvelope {
395 fn from(value: AASigned) -> Self {
396 Self::AA(value)
397 }
398}
399
400impl TempoTypedTransaction {
401 pub fn into_envelope(self, sig: Signature) -> TempoTxEnvelope {
403 match self {
404 Self::Legacy(tx) => tx.into_signed(sig).into(),
405 Self::Eip2930(tx) => tx.into_signed(sig).into(),
406 Self::Eip1559(tx) => tx.into_signed(sig).into(),
407 Self::Eip7702(tx) => tx.into_signed(sig).into(),
408 Self::AA(tx) => tx.into_signed(sig.into()).into(),
409 Self::FeeToken(tx) => tx.into_signed(sig).into(),
410 }
411 }
412
413 pub fn as_dyn_signable_mut(&mut self) -> &mut dyn SignableTransaction<Signature> {
415 match self {
416 Self::Legacy(tx) => tx,
417 Self::Eip2930(tx) => tx,
418 Self::Eip1559(tx) => tx,
419 Self::Eip7702(tx) => tx,
420 Self::AA(tx) => tx,
421 Self::FeeToken(tx) => tx,
422 }
423 }
424}
425
426impl TryFrom<TypedTransaction> for TempoTypedTransaction {
427 type Error = UnsupportedTransactionType<TxType>;
428
429 fn try_from(value: TypedTransaction) -> Result<Self, Self::Error> {
430 Ok(match value {
431 TypedTransaction::Legacy(tx) => Self::Legacy(tx),
432 TypedTransaction::Eip2930(tx) => Self::Eip2930(tx),
433 TypedTransaction::Eip1559(tx) => Self::Eip1559(tx),
434 TypedTransaction::Eip4844(..) => {
435 return Err(UnsupportedTransactionType::new(TxType::Eip4844));
436 }
437 TypedTransaction::Eip7702(tx) => Self::Eip7702(tx),
438 })
439 }
440}
441
442impl From<TempoTxEnvelope> for TempoTypedTransaction {
443 fn from(value: TempoTxEnvelope) -> Self {
444 match value {
445 TempoTxEnvelope::Legacy(tx) => Self::Legacy(tx.into_parts().0),
446 TempoTxEnvelope::Eip2930(tx) => Self::Eip2930(tx.into_parts().0),
447 TempoTxEnvelope::Eip1559(tx) => Self::Eip1559(tx.into_parts().0),
448 TempoTxEnvelope::Eip7702(tx) => Self::Eip7702(tx.into_parts().0),
449 TempoTxEnvelope::FeeToken(tx) => Self::FeeToken(tx.into_parts().0),
450 TempoTxEnvelope::AA(tx) => Self::AA(tx.into_parts().0),
451 }
452 }
453}
454
455impl From<TxFeeToken> for TempoTypedTransaction {
456 fn from(value: TxFeeToken) -> Self {
457 Self::FeeToken(value)
458 }
459}
460
461impl From<TempoTransaction> for TempoTypedTransaction {
462 fn from(value: TempoTransaction) -> Self {
463 Self::AA(value)
464 }
465}
466
467#[cfg(feature = "rpc")]
468impl reth_rpc_convert::SignableTxRequest<TempoTxEnvelope>
469 for alloy_rpc_types_eth::TransactionRequest
470{
471 async fn try_build_and_sign(
472 self,
473 signer: impl alloy_network::TxSigner<alloy_primitives::Signature> + Send,
474 ) -> Result<TempoTxEnvelope, reth_rpc_convert::SignTxRequestError> {
475 reth_rpc_convert::SignableTxRequest::<
476 EthereumTxEnvelope<alloy_consensus::TxEip4844>,
477 >::try_build_and_sign(self, signer)
478 .await
479 .and_then(|tx| {
480 tx.try_into()
481 .map_err(|_| reth_rpc_convert::SignTxRequestError::InvalidTransactionRequest)
482 })
483 }
484}
485
486#[cfg(feature = "rpc")]
487impl reth_rpc_convert::TryIntoSimTx<TempoTxEnvelope> for alloy_rpc_types_eth::TransactionRequest {
488 fn try_into_sim_tx(self) -> Result<TempoTxEnvelope, ValueError<Self>> {
489 let tx = self.clone().build_typed_simulate_transaction()?;
490 tx.try_into()
491 .map_err(|_| ValueError::new_static(self, "Invalid transaction request"))
492 }
493}
494
495#[cfg(feature = "serde-bincode-compat")]
496impl reth_primitives_traits::serde_bincode_compat::RlpBincode for TempoTxEnvelope {}
497
498#[cfg(feature = "reth-codec")]
499mod codec {
500 use crate::{TempoSignature, TempoTransaction};
501
502 use super::*;
503 use alloy_eips::eip2718::EIP7702_TX_TYPE_ID;
504 use alloy_primitives::{
505 Bytes, Signature,
506 bytes::{self, BufMut},
507 };
508 use reth_codecs::{
509 Compact,
510 alloy::transaction::{CompactEnvelope, Envelope},
511 txtype::{
512 COMPACT_EXTENDED_IDENTIFIER_FLAG, COMPACT_IDENTIFIER_EIP1559,
513 COMPACT_IDENTIFIER_EIP2930, COMPACT_IDENTIFIER_LEGACY,
514 },
515 };
516
517 impl reth_codecs::alloy::transaction::FromTxCompact for TempoTxEnvelope {
518 type TxType = TempoTxType;
519
520 fn from_tx_compact(
521 buf: &[u8],
522 tx_type: Self::TxType,
523 signature: Signature,
524 ) -> (Self, &[u8]) {
525 use alloy_consensus::Signed;
526 use reth_codecs::Compact;
527
528 match tx_type {
529 TempoTxType::Legacy => {
530 let (tx, buf) = TxLegacy::from_compact(buf, buf.len());
531 let tx = Signed::new_unhashed(tx, signature);
532 (Self::Legacy(tx), buf)
533 }
534 TempoTxType::Eip2930 => {
535 let (tx, buf) = TxEip2930::from_compact(buf, buf.len());
536 let tx = Signed::new_unhashed(tx, signature);
537 (Self::Eip2930(tx), buf)
538 }
539 TempoTxType::Eip1559 => {
540 let (tx, buf) = TxEip1559::from_compact(buf, buf.len());
541 let tx = Signed::new_unhashed(tx, signature);
542 (Self::Eip1559(tx), buf)
543 }
544 TempoTxType::Eip7702 => {
545 let (tx, buf) = TxEip7702::from_compact(buf, buf.len());
546 let tx = Signed::new_unhashed(tx, signature);
547 (Self::Eip7702(tx), buf)
548 }
549 TempoTxType::AA => {
550 let (tx, buf) = TempoTransaction::from_compact(buf, buf.len());
551 let (sig_bytes, buf) = Bytes::from_compact(buf, buf.len());
553 let aa_sig = TempoSignature::from_bytes(&sig_bytes)
554 .map_err(|e| panic!("Failed to decode AA signature: {e}"))
555 .unwrap();
556 let tx = AASigned::new_unhashed(tx, aa_sig);
557 (Self::AA(tx), buf)
558 }
559 TempoTxType::FeeToken => {
560 let (tx, buf) = TxFeeToken::from_compact(buf, buf.len());
561 let tx = Signed::new_unhashed(tx, signature);
562 (Self::FeeToken(tx), buf)
563 }
564 }
565 }
566 }
567
568 impl reth_codecs::alloy::transaction::ToTxCompact for TempoTxEnvelope {
569 fn to_tx_compact(&self, buf: &mut (impl BufMut + AsMut<[u8]>)) {
570 match self {
571 Self::Legacy(tx) => tx.tx().to_compact(buf),
572 Self::Eip2930(tx) => tx.tx().to_compact(buf),
573 Self::Eip1559(tx) => tx.tx().to_compact(buf),
574 Self::Eip7702(tx) => tx.tx().to_compact(buf),
575 Self::AA(tx) => {
576 let mut len = tx.tx().to_compact(buf);
577 len += tx.signature().to_bytes().to_compact(buf);
579 len
580 }
581 Self::FeeToken(tx) => tx.tx().to_compact(buf),
582 };
583 }
584 }
585
586 impl Envelope for TempoTxEnvelope {
587 fn signature(&self) -> &Signature {
588 match self {
589 Self::Legacy(tx) => tx.signature(),
590 Self::Eip2930(tx) => tx.signature(),
591 Self::Eip1559(tx) => tx.signature(),
592 Self::Eip7702(tx) => tx.signature(),
593 Self::AA(_tx) => {
594 &TEMPO_SYSTEM_TX_SIGNATURE
596 }
597 Self::FeeToken(tx) => tx.signature(),
598 }
599 }
600
601 fn tx_type(&self) -> Self::TxType {
602 Self::tx_type(self)
603 }
604 }
605
606 impl Compact for TempoTxType {
607 fn to_compact<B>(&self, buf: &mut B) -> usize
608 where
609 B: BufMut + AsMut<[u8]>,
610 {
611 match self {
612 Self::Legacy => COMPACT_IDENTIFIER_LEGACY,
613 Self::Eip2930 => COMPACT_IDENTIFIER_EIP2930,
614 Self::Eip1559 => COMPACT_IDENTIFIER_EIP1559,
615 Self::Eip7702 => {
616 buf.put_u8(EIP7702_TX_TYPE_ID);
617 COMPACT_EXTENDED_IDENTIFIER_FLAG
618 }
619 Self::AA => {
620 buf.put_u8(crate::transaction::TEMPO_TX_TYPE_ID);
621 COMPACT_EXTENDED_IDENTIFIER_FLAG
622 }
623 Self::FeeToken => {
624 buf.put_u8(crate::transaction::FEE_TOKEN_TX_TYPE_ID);
625 COMPACT_EXTENDED_IDENTIFIER_FLAG
626 }
627 }
628 }
629
630 fn from_compact(mut buf: &[u8], identifier: usize) -> (Self, &[u8]) {
634 use bytes::Buf;
635 (
636 match identifier {
637 COMPACT_IDENTIFIER_LEGACY => Self::Legacy,
638 COMPACT_IDENTIFIER_EIP2930 => Self::Eip2930,
639 COMPACT_IDENTIFIER_EIP1559 => Self::Eip1559,
640 COMPACT_EXTENDED_IDENTIFIER_FLAG => {
641 let extended_identifier = buf.get_u8();
642 match extended_identifier {
643 EIP7702_TX_TYPE_ID => Self::Eip7702,
644 crate::transaction::TEMPO_TX_TYPE_ID => Self::AA,
645 crate::transaction::FEE_TOKEN_TX_TYPE_ID => Self::FeeToken,
646 _ => panic!("Unsupported TxType identifier: {extended_identifier}"),
647 }
648 }
649 _ => panic!("Unknown identifier for TxType: {identifier}"),
650 },
651 buf,
652 )
653 }
654 }
655
656 impl Compact for TempoTxEnvelope {
657 fn to_compact<B>(&self, buf: &mut B) -> usize
658 where
659 B: BufMut + AsMut<[u8]>,
660 {
661 CompactEnvelope::to_compact(self, buf)
662 }
663
664 fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
665 CompactEnvelope::from_compact(buf, len)
666 }
667 }
668
669 impl reth_db_api::table::Compress for TempoTxEnvelope {
670 type Compressed = Vec<u8>;
671
672 fn compress_to_buf<B: alloy_primitives::bytes::BufMut + AsMut<[u8]>>(&self, buf: &mut B) {
673 let _ = Compact::to_compact(self, buf);
674 }
675 }
676
677 impl reth_db_api::table::Decompress for TempoTxEnvelope {
678 fn decompress(value: &[u8]) -> Result<Self, reth_db_api::DatabaseError> {
679 let (obj, _) = Compact::from_compact(value, value.len());
680 Ok(obj)
681 }
682 }
683}
684
685#[cfg(test)]
686mod tests {
687 use super::*;
688 use alloy_primitives::{Signature, TxKind, address};
689
690 #[test]
691 fn test_fee_token_access() {
692 let fee_token_tx = TxFeeToken {
693 fee_token: Some(Address::ZERO),
694 ..Default::default()
695 };
696 let signature = Signature::new(
697 alloy_primitives::U256::ZERO,
698 alloy_primitives::U256::ZERO,
699 false,
700 );
701 let signed = Signed::new_unhashed(fee_token_tx, signature);
702 let envelope = TempoTxEnvelope::FeeToken(signed);
703
704 assert!(envelope.is_fee_token());
705 assert_eq!(envelope.fee_token(), Some(Address::ZERO));
706 }
707
708 #[test]
709 fn test_non_fee_token_access() {
710 let legacy_tx = TxLegacy::default();
711 let signature = Signature::new(
712 alloy_primitives::U256::ZERO,
713 alloy_primitives::U256::ZERO,
714 false,
715 );
716 let signed = Signed::new_unhashed(legacy_tx, signature);
717 let envelope = TempoTxEnvelope::Legacy(signed);
718
719 assert!(!envelope.is_fee_token());
720 assert_eq!(envelope.fee_token(), None);
721 }
722
723 #[test]
724 fn test_payment_classification_with_tip20_prefix() {
725 let payment_addr = address!("20c0000000000000000000000000000000000001");
727 let tx = TxFeeToken {
728 to: TxKind::Call(payment_addr),
729 gas_limit: 21000,
730 ..Default::default()
731 };
732 let signed = Signed::new_unhashed(tx, Signature::test_signature());
733 let envelope = TempoTxEnvelope::FeeToken(signed);
734
735 assert!(envelope.is_payment());
736 }
737
738 #[test]
739 fn test_payment_classification_without_tip20_prefix() {
740 let non_payment_addr = address!("1234567890123456789012345678901234567890");
742 let tx = TxFeeToken {
743 to: TxKind::Call(non_payment_addr),
744 gas_limit: 21000,
745 ..Default::default()
746 };
747 let signed = Signed::new_unhashed(tx, Signature::test_signature());
748 let envelope = TempoTxEnvelope::FeeToken(signed);
749
750 assert!(!envelope.is_payment());
751 }
752
753 #[test]
754 fn test_payment_classification_no_to_address() {
755 let tx = TxFeeToken {
757 to: TxKind::Create,
758 gas_limit: 21000,
759 ..Default::default()
760 };
761 let signed = Signed::new_unhashed(tx, Signature::test_signature());
762 let envelope = TempoTxEnvelope::FeeToken(signed);
763
764 assert!(!envelope.is_payment());
765 }
766
767 #[test]
768 fn test_payment_classification_partial_match() {
769 let partial_match_addr = address!("20c0000000000000000000000000000100000000");
771 let tx = TxFeeToken {
772 to: TxKind::Call(partial_match_addr),
773 gas_limit: 21000,
774 ..Default::default()
775 };
776 let signed = Signed::new_unhashed(tx, Signature::test_signature());
777 let envelope = TempoTxEnvelope::FeeToken(signed);
778
779 assert!(envelope.is_payment());
781 }
782
783 #[test]
784 fn test_payment_classification_different_prefix() {
785 let different_prefix_addr = address!("30c0000000000000000000000000000000000001");
787 let tx = TxFeeToken {
788 to: TxKind::Call(different_prefix_addr),
789 gas_limit: 21000,
790 ..Default::default()
791 };
792 let signed = Signed::new_unhashed(tx, Signature::test_signature());
793 let envelope = TempoTxEnvelope::FeeToken(signed);
794
795 assert!(!envelope.is_payment());
796 }
797
798 #[test]
799 fn test_payment_classification_legacy_tx() {
800 let payment_addr = address!("20c0000000000000000000000000000000000001");
802 let tx = TxLegacy {
803 to: TxKind::Call(payment_addr),
804 gas_limit: 21000,
805 ..Default::default()
806 };
807 let signed = Signed::new_unhashed(tx, Signature::test_signature());
808 let envelope = TempoTxEnvelope::Legacy(signed);
809
810 assert!(envelope.is_payment());
811 }
812}