Skip to main content

tempo_primitives/transaction/
tempo_transaction.rs

1use crate::{
2    subblock::{PartialValidatorKey, has_sub_block_nonce_key_prefix},
3    transaction::{
4        AASigned, TempoSignature, TempoSignedAuthorization,
5        key_authorization::SignedKeyAuthorization,
6    },
7};
8use alloc::vec::Vec;
9use alloy_consensus::{SignableTransaction, Transaction, crypto::RecoveryError};
10use alloy_eips::{Typed2718, eip2930::AccessList, eip7702::SignedAuthorization};
11use alloy_primitives::{Address, B256, Bytes, ChainId, Signature, TxKind, U256, keccak256};
12use alloy_rlp::{Buf, BufMut, Decodable, EMPTY_STRING_CODE, Encodable};
13
14/// Tempo transaction type byte (0x76)
15pub const TEMPO_TX_TYPE_ID: u8 = 0x76;
16
17/// Magic byte for the fee payer signature
18pub const FEE_PAYER_SIGNATURE_MAGIC_BYTE: u8 = 0x78;
19
20/// Signature type constants
21pub const SECP256K1_SIGNATURE_LENGTH: usize = 65;
22pub const P256_SIGNATURE_LENGTH: usize = 129;
23pub const MAX_WEBAUTHN_SIGNATURE_LENGTH: usize = 2048; // 2KB max
24
25/// Nonce key marking an expiring nonce transaction (uses tx hash for replay protection).
26pub const TEMPO_EXPIRING_NONCE_KEY: U256 = U256::MAX;
27
28/// Maximum allowed expiry window for expiring nonce transactions (30 seconds).
29pub const TEMPO_EXPIRING_NONCE_MAX_EXPIRY_SECS: u64 = 30;
30
31/// Signature type enumeration
32#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
35#[cfg_attr(feature = "reth-codec", derive(reth_codecs::Compact))]
36#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
37#[repr(u8)]
38pub enum SignatureType {
39    Secp256k1 = 0,
40    P256 = 1,
41    WebAuthn = 2,
42}
43
44impl From<SignatureType> for u8 {
45    fn from(sig_type: SignatureType) -> Self {
46        match sig_type {
47            SignatureType::Secp256k1 => 0,
48            SignatureType::P256 => 1,
49            SignatureType::WebAuthn => 2,
50        }
51    }
52}
53
54impl alloy_rlp::Encodable for SignatureType {
55    fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
56        (*self as u8).encode(out);
57    }
58
59    fn length(&self) -> usize {
60        1
61    }
62}
63
64impl alloy_rlp::Decodable for SignatureType {
65    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
66        let byte: u8 = alloy_rlp::Decodable::decode(buf)?;
67        match byte {
68            0 => Ok(Self::Secp256k1),
69            1 => Ok(Self::P256),
70            2 => Ok(Self::WebAuthn),
71            _ => Err(alloy_rlp::Error::Custom("Invalid signature type")),
72        }
73    }
74}
75
76/// Helper function to create an RLP header for a list with the given payload length
77#[inline]
78fn rlp_header(payload_length: usize) -> alloy_rlp::Header {
79    alloy_rlp::Header {
80        list: true,
81        payload_length,
82    }
83}
84
85#[derive(Clone, Debug, PartialEq, Eq, Hash)]
86#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
87#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
88#[cfg_attr(feature = "reth-codec", derive(reth_codecs::Compact))]
89#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
90#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact, rlp))]
91pub struct Call {
92    /// Call target.
93    pub to: TxKind,
94
95    /// Call value.
96    pub value: U256,
97
98    /// Call input.
99    #[cfg_attr(feature = "serde", serde(flatten, with = "serde_input"))]
100    pub input: Bytes,
101}
102
103impl Call {
104    /// Returns the RLP header for this call, encapsulating both length calculation and header creation
105    #[inline]
106    fn rlp_header(&self) -> alloy_rlp::Header {
107        let payload_length = self.to.length() + self.value.length() + self.input.length();
108        alloy_rlp::Header {
109            list: true,
110            payload_length,
111        }
112    }
113
114    fn size(&self) -> usize {
115        size_of::<Self>() + self.input.len()
116    }
117}
118
119impl Encodable for Call {
120    fn encode(&self, out: &mut dyn BufMut) {
121        self.rlp_header().encode(out);
122        self.to.encode(out);
123        self.value.encode(out);
124        self.input.encode(out);
125    }
126
127    fn length(&self) -> usize {
128        self.rlp_header().length_with_payload()
129    }
130}
131
132impl Decodable for Call {
133    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
134        let header = alloy_rlp::Header::decode(buf)?;
135        if !header.list {
136            return Err(alloy_rlp::Error::UnexpectedString);
137        }
138        let remaining = buf.len();
139
140        if header.payload_length > remaining {
141            return Err(alloy_rlp::Error::InputTooShort);
142        }
143
144        let this = Self {
145            to: Decodable::decode(buf)?,
146            value: Decodable::decode(buf)?,
147            input: Decodable::decode(buf)?,
148        };
149
150        if buf.len() + header.payload_length != remaining {
151            return Err(alloy_rlp::Error::UnexpectedLength);
152        }
153
154        Ok(this)
155    }
156}
157
158/// Tempo transaction following the Tempo spec.
159///
160/// This transaction type supports:
161/// - Multiple signature types (secp256k1, P256, WebAuthn)
162/// - Parallelizable nonces via 2D nonce system (nonce_key + nonce)
163/// - Gas sponsorship via fee payer
164/// - Scheduled transactions (validBefore/validAfter)
165/// - EIP-7702 authorization lists
166#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
167#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
168#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
169#[cfg_attr(feature = "reth-codec", derive(reth_codecs::Compact))]
170pub struct TempoTransaction {
171    /// EIP-155: Simple replay attack protection
172    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
173    pub chain_id: ChainId,
174
175    /// Optional fee token preference (`None` means no preference)
176    pub fee_token: Option<Address>,
177
178    /// Max Priority fee per gas (EIP-1559)
179    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
180    pub max_priority_fee_per_gas: u128,
181
182    /// Max fee per gas (EIP-1559)
183    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
184    pub max_fee_per_gas: u128,
185
186    /// Gas limit
187    #[cfg_attr(
188        feature = "serde",
189        serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
190    )]
191    pub gas_limit: u64,
192
193    /// Calls to be executed atomically
194    pub calls: Vec<Call>,
195
196    /// Access list (EIP-2930)
197    pub access_list: AccessList,
198
199    /// TT-specific fields
200
201    /// Nonce key for 2D nonce system
202    /// Key 0 is the protocol nonce, keys 1-N are user nonces for parallelization
203    pub nonce_key: U256,
204
205    /// Current nonce value for the nonce key
206    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
207    pub nonce: u64,
208
209    /// Optional features
210
211    /// Optional fee payer signature for sponsored transactions (secp256k1 only)
212    pub fee_payer_signature: Option<Signature>,
213
214    /// Transaction can only be included in a block before this timestamp
215    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity::opt"))]
216    pub valid_before: Option<u64>,
217
218    /// Transaction can only be included in a block after this timestamp
219    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity::opt"))]
220    pub valid_after: Option<u64>,
221
222    /// Optional key authorization for provisioning a new access key
223    ///
224    /// When present, this transaction will add the specified key to the AccountKeychain precompile,
225    /// before verifying the transaction signature.
226    /// The authorization must be signed with the root key, the tx can be signed by the Keychain signature.
227    pub key_authorization: Option<SignedKeyAuthorization>,
228
229    /// Authorization list (EIP-7702 style with Tempo signatures)
230    #[cfg_attr(feature = "serde", serde(rename = "aaAuthorizationList"))]
231    pub tempo_authorization_list: Vec<TempoSignedAuthorization>,
232}
233
234/// Validates the calls list structure for Tempo transactions.
235///
236/// This is a shared validation function used by both `TempoTransaction::validate()`
237/// and the revm handler's `validate_env()` to ensure consistent validation.
238///
239/// Rules:
240/// - Calls list must not be empty
241/// - CREATE calls are not allowed when authorization list is non-empty (EIP-7702 semantics)
242/// - Only the first call can be a CREATE; all subsequent calls must be CALL
243pub fn validate_calls(calls: &[Call], has_authorization_list: bool) -> Result<(), &'static str> {
244    // Calls must not be empty (similar to EIP-7702 rejecting empty auth lists)
245    if calls.is_empty() {
246        return Err("calls list cannot be empty");
247    }
248
249    let mut calls_iter = calls.iter();
250
251    // Only the first call in the batch can be a CREATE call.
252    if let Some(call) = calls_iter.next()
253        // Authorization list validation: Can NOT have CREATE when authorization list is non-empty
254        // This follows EIP-7702 semantics - when using delegation
255        && has_authorization_list
256        && call.to.is_create()
257    {
258        return Err("calls cannot contain CREATE when 'aa_authorization_list' is non-empty");
259    }
260
261    // All subsequent calls must be CALL.
262    for call in calls_iter {
263        if call.to.is_create() {
264            return Err(
265                "only one CREATE call is allowed per transaction, and it must be the first call of the batch",
266            );
267        }
268    }
269
270    Ok(())
271}
272
273impl TempoTransaction {
274    /// Get the transaction type
275    #[doc(alias = "transaction_type")]
276    pub const fn tx_type() -> u8 {
277        TEMPO_TX_TYPE_ID
278    }
279
280    /// Returns true if this is an expiring nonce transaction.
281    ///
282    /// Expiring nonce transactions use the tx hash for replay protection instead of
283    /// sequential nonces. They are identified by `nonce_key == U256::MAX`.
284    #[inline]
285    pub fn is_expiring_nonce_tx(&self) -> bool {
286        self.nonce_key == TEMPO_EXPIRING_NONCE_KEY
287    }
288
289    /// Validates the transaction according to invariant rules.
290    ///
291    /// This performs structural validation that is always required, regardless of hardfork.
292    /// Hardfork-dependent validation (e.g., expiring nonce constraints) is performed by
293    /// the transaction pool validator and execution handler where hardfork context is available.
294    pub fn validate(&self) -> Result<(), &'static str> {
295        // Validate calls list structure using the shared function
296        validate_calls(&self.calls, !self.tempo_authorization_list.is_empty())?;
297
298        // validBefore must be greater than validAfter if both are set
299        if let Some(valid_after) = self.valid_after
300            && let Some(valid_before) = self.valid_before
301            && valid_before <= valid_after
302        {
303            return Err("valid_before must be greater than valid_after");
304        }
305
306        Ok(())
307    }
308
309    /// Calculates a heuristic for the in-memory size of the transaction
310    #[inline]
311    pub fn size(&self) -> usize {
312        size_of::<Self>()
313            + self.calls.iter().map(|call| call.size()).sum::<usize>()
314            + self.access_list.size()
315            + self.key_authorization.as_ref().map_or(0, |k| k.size())
316            + self
317                .tempo_authorization_list
318                .iter()
319                .map(|auth| auth.size())
320                .sum::<usize>()
321    }
322
323    /// Convert the transaction into a signed transaction
324    pub fn into_signed(self, signature: TempoSignature) -> AASigned {
325        AASigned::new_unhashed(self, signature)
326    }
327
328    /// Calculate the signing hash for this transaction
329    /// This is the hash that should be signed by the sender
330    pub fn signature_hash(&self) -> B256 {
331        let mut buf = Vec::new();
332        self.encode_for_signing(&mut buf);
333        keccak256(&buf)
334    }
335
336    /// Calculate the fee payer signature hash.
337    ///
338    /// This hash is signed by the fee payer to sponsor the transaction
339    pub fn fee_payer_signature_hash(&self, sender: Address) -> B256 {
340        // Use helper functions for consistent encoding
341        let payload_length = self.rlp_encoded_fields_length(|_| sender.length(), false);
342
343        let mut buf = Vec::with_capacity(1 + rlp_header(payload_length).length_with_payload());
344
345        // Magic byte for fee payer signature (like TxFeeToken)
346        buf.put_u8(FEE_PAYER_SIGNATURE_MAGIC_BYTE);
347
348        // RLP header
349        rlp_header(payload_length).encode(&mut buf);
350
351        // Encode fields using helper (skip_fee_token = false, so fee_token IS included)
352        self.rlp_encode_fields(
353            &mut buf,
354            |_, out| {
355                // Encode sender address instead of fee_payer_signature
356                sender.encode(out);
357            },
358            false, // skip_fee_token = FALSE - fee payer commits to fee_token!
359        );
360
361        keccak256(&buf)
362    }
363
364    /// Recovers the fee payer for this transaction.
365    ///
366    /// This returns the given sender if the transaction doesn't include a fee payer signature
367    pub fn recover_fee_payer(&self, sender: Address) -> Result<Address, RecoveryError> {
368        if let Some(fee_payer_signature) = &self.fee_payer_signature {
369            alloy_consensus::crypto::secp256k1::recover_signer(
370                fee_payer_signature,
371                self.fee_payer_signature_hash(sender),
372            )
373        } else {
374            Ok(sender)
375        }
376    }
377
378    /// Outputs the length of the transaction's fields, without a RLP header.
379    ///
380    /// This is the internal helper that takes closures for flexible encoding.
381    fn rlp_encoded_fields_length(
382        &self,
383        signature_length: impl FnOnce(&Option<Signature>) -> usize,
384        skip_fee_token: bool,
385    ) -> usize {
386        self.chain_id.length() +
387            self.max_priority_fee_per_gas.length() +
388            self.max_fee_per_gas.length() +
389            self.gas_limit.length() +
390            self.calls.length() +
391            self.access_list.length() +
392            self.nonce_key.length() +
393            self.nonce.length() +
394            if let Some(valid_before) = self.valid_before {
395                valid_before.length()
396            } else {
397                1 // EMPTY_STRING_CODE
398            } +
399            // valid_after (optional u64)
400            if let Some(valid_after) = self.valid_after {
401                valid_after.length()
402            } else {
403                1 // EMPTY_STRING_CODE
404            } +
405            // fee_token (optional Address)
406            if !skip_fee_token && let Some(addr) = self.fee_token {
407                addr.length()
408            } else {
409                1 // EMPTY_STRING_CODE
410            } +
411            signature_length(&self.fee_payer_signature) +
412            // authorization_list
413            self.tempo_authorization_list.length() +
414            // key_authorization (only included if present)
415            if let Some(key_auth) = &self.key_authorization {
416                key_auth.length()
417            } else {
418                0 // No bytes when None
419            }
420    }
421
422    fn rlp_encode_fields(
423        &self,
424        out: &mut dyn BufMut,
425        encode_signature: impl FnOnce(&Option<Signature>, &mut dyn BufMut),
426        skip_fee_token: bool,
427    ) {
428        self.chain_id.encode(out);
429        self.max_priority_fee_per_gas.encode(out);
430        self.max_fee_per_gas.encode(out);
431        self.gas_limit.encode(out);
432        self.calls.encode(out);
433        self.access_list.encode(out);
434        self.nonce_key.encode(out);
435        self.nonce.encode(out);
436
437        if let Some(valid_before) = self.valid_before {
438            valid_before.encode(out);
439        } else {
440            out.put_u8(EMPTY_STRING_CODE);
441        }
442
443        if let Some(valid_after) = self.valid_after {
444            valid_after.encode(out);
445        } else {
446            out.put_u8(EMPTY_STRING_CODE);
447        }
448
449        if !skip_fee_token && let Some(addr) = self.fee_token {
450            addr.encode(out);
451        } else {
452            out.put_u8(EMPTY_STRING_CODE);
453        }
454
455        encode_signature(&self.fee_payer_signature, out);
456
457        // Encode authorization_list
458        self.tempo_authorization_list.encode(out);
459
460        // Encode key_authorization (truly optional - only encoded if present)
461        if let Some(key_auth) = &self.key_authorization {
462            key_auth.encode(out);
463        }
464        // No bytes at all when None - maintains backwards compatibility
465    }
466
467    /// Public version for normal RLP encoding
468    pub(crate) fn rlp_encoded_fields_length_default(&self) -> usize {
469        self.rlp_encoded_fields_length(
470            |signature| {
471                signature.map_or(1, |s| {
472                    rlp_header(s.rlp_rs_len() + s.v().length()).length_with_payload()
473                })
474            },
475            false,
476        )
477    }
478
479    /// Public version for normal RLP encoding
480    pub(crate) fn rlp_encode_fields_default(&self, out: &mut dyn BufMut) {
481        self.rlp_encode_fields(
482            out,
483            |signature, out| {
484                if let Some(signature) = signature {
485                    let payload_length = signature.rlp_rs_len() + signature.v().length();
486                    rlp_header(payload_length).encode(out);
487                    signature.write_rlp_vrs(out, signature.v());
488                } else {
489                    out.put_u8(EMPTY_STRING_CODE);
490                }
491            },
492            false,
493        )
494    }
495
496    /// Decodes the inner TempoTransaction fields from RLP bytes
497    pub(crate) fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
498        let chain_id = Decodable::decode(buf)?;
499        let max_priority_fee_per_gas = Decodable::decode(buf)?;
500        let max_fee_per_gas = Decodable::decode(buf)?;
501        let gas_limit = Decodable::decode(buf)?;
502        let calls = Decodable::decode(buf)?;
503        let access_list = Decodable::decode(buf)?;
504        let nonce_key = Decodable::decode(buf)?;
505        let nonce = Decodable::decode(buf)?;
506
507        let valid_before = if let Some(first) = buf.first() {
508            if *first == EMPTY_STRING_CODE {
509                buf.advance(1);
510                None
511            } else {
512                Some(Decodable::decode(buf)?)
513            }
514        } else {
515            return Err(alloy_rlp::Error::InputTooShort);
516        };
517
518        let valid_after = if let Some(first) = buf.first() {
519            if *first == EMPTY_STRING_CODE {
520                buf.advance(1);
521                None
522            } else {
523                Some(Decodable::decode(buf)?)
524            }
525        } else {
526            return Err(alloy_rlp::Error::InputTooShort);
527        };
528
529        let fee_token = if let Some(first) = buf.first() {
530            if *first == EMPTY_STRING_CODE {
531                buf.advance(1);
532                None
533            } else {
534                TxKind::decode(buf)?.into_to()
535            }
536        } else {
537            return Err(alloy_rlp::Error::InputTooShort);
538        };
539
540        let fee_payer_signature = if let Some(first) = buf.first() {
541            if *first == EMPTY_STRING_CODE {
542                buf.advance(1);
543                None
544            } else {
545                let header = alloy_rlp::Header::decode(buf)?;
546                if buf.len() < header.payload_length {
547                    return Err(alloy_rlp::Error::InputTooShort);
548                }
549                if !header.list {
550                    return Err(alloy_rlp::Error::UnexpectedString);
551                }
552                Some(Signature::decode_rlp_vrs(buf, bool::decode)?)
553            }
554        } else {
555            return Err(alloy_rlp::Error::InputTooShort);
556        };
557
558        let tempo_authorization_list = Decodable::decode(buf)?;
559
560        // Decode optional key_authorization field at the end
561        // Check if the next byte looks like it could be a KeyAuthorization (RLP list)
562        // KeyAuthorization is encoded as a list, so it would start with 0xc0-0xf7 (short list) or 0xf8-0xff (long list)
563        // If it's a bytes string (0x80-0xbf for short, 0xb8-0xbf for long), it's not a
564        // KeyAuthorization and most likely a signature bytes following the AA transaction.
565        let key_authorization = if let Some(&first) = buf.first() {
566            // Check if this looks like an RLP list (KeyAuthorization is always a list)
567            if first >= 0xc0 {
568                // This could be a KeyAuthorization
569                Some(Decodable::decode(buf)?)
570            } else {
571                // This is likely not a KeyAuthorization (probably signature bytes in AASigned context)
572                None
573            }
574        } else {
575            None
576        };
577
578        let tx = Self {
579            chain_id,
580            fee_token,
581            max_priority_fee_per_gas,
582            max_fee_per_gas,
583            gas_limit,
584            calls,
585            access_list,
586            nonce_key,
587            nonce,
588            fee_payer_signature,
589            valid_before,
590            valid_after,
591            key_authorization,
592            tempo_authorization_list,
593        };
594
595        // Validate the transaction
596        tx.validate().map_err(alloy_rlp::Error::Custom)?;
597
598        Ok(tx)
599    }
600
601    /// Returns true if the nonce key of this transaction has the [`TEMPO_SUBBLOCK_NONCE_KEY_PREFIX`](crate::subblock::TEMPO_SUBBLOCK_NONCE_KEY_PREFIX).
602    pub fn has_sub_block_nonce_key_prefix(&self) -> bool {
603        has_sub_block_nonce_key_prefix(&self.nonce_key)
604    }
605
606    /// Returns the proposer of the subblock if this is a subblock transaction.
607    pub fn subblock_proposer(&self) -> Option<PartialValidatorKey> {
608        if self.has_sub_block_nonce_key_prefix() {
609            Some(PartialValidatorKey::from_slice(
610                &self.nonce_key.to_be_bytes::<32>()[1..16],
611            ))
612        } else {
613            None
614        }
615    }
616}
617
618impl Transaction for TempoTransaction {
619    #[inline]
620    fn chain_id(&self) -> Option<ChainId> {
621        Some(self.chain_id)
622    }
623
624    #[inline]
625    fn nonce(&self) -> u64 {
626        self.nonce
627    }
628
629    #[inline]
630    fn gas_limit(&self) -> u64 {
631        self.gas_limit
632    }
633
634    #[inline]
635    fn gas_price(&self) -> Option<u128> {
636        None
637    }
638
639    #[inline]
640    fn max_fee_per_gas(&self) -> u128 {
641        self.max_fee_per_gas
642    }
643
644    #[inline]
645    fn max_priority_fee_per_gas(&self) -> Option<u128> {
646        Some(self.max_priority_fee_per_gas)
647    }
648
649    #[inline]
650    fn max_fee_per_blob_gas(&self) -> Option<u128> {
651        None
652    }
653
654    #[inline]
655    fn priority_fee_or_price(&self) -> u128 {
656        self.max_priority_fee_per_gas
657    }
658
659    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
660        alloy_eips::eip1559::calc_effective_gas_price(
661            self.max_fee_per_gas,
662            self.max_priority_fee_per_gas,
663            base_fee,
664        )
665    }
666
667    #[inline]
668    fn is_dynamic_fee(&self) -> bool {
669        true
670    }
671
672    #[inline]
673    fn kind(&self) -> TxKind {
674        // Return first call's `to` or Create if empty
675        self.calls.first().map(|c| c.to).unwrap_or(TxKind::Create)
676    }
677
678    #[inline]
679    fn is_create(&self) -> bool {
680        self.kind().is_create()
681    }
682
683    #[inline]
684    fn value(&self) -> U256 {
685        // Return sum of all call values, saturating to U256::MAX on overflow
686        self.calls
687            .iter()
688            .fold(U256::ZERO, |acc, call| acc.saturating_add(call.value))
689    }
690
691    #[inline]
692    fn input(&self) -> &Bytes {
693        // Return first call's input or empty
694        static EMPTY_BYTES: Bytes = Bytes::new();
695        self.calls.first().map(|c| &c.input).unwrap_or(&EMPTY_BYTES)
696    }
697
698    #[inline]
699    fn access_list(&self) -> Option<&AccessList> {
700        Some(&self.access_list)
701    }
702
703    #[inline]
704    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
705        None
706    }
707
708    #[inline]
709    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
710        None
711    }
712}
713
714impl Typed2718 for TempoTransaction {
715    fn ty(&self) -> u8 {
716        TEMPO_TX_TYPE_ID
717    }
718}
719
720impl SignableTransaction<Signature> for TempoTransaction {
721    fn set_chain_id(&mut self, chain_id: ChainId) {
722        self.chain_id = chain_id;
723    }
724
725    fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
726        // Skip fee_token if fee_payer_signature is present to ensure user doesn't commit to a specific fee token
727        let skip_fee_token = self.fee_payer_signature.is_some();
728
729        // Type byte
730        out.put_u8(Self::tx_type());
731
732        // Compute payload length using helper
733        let payload_length = self.rlp_encoded_fields_length(|_| 1, skip_fee_token);
734
735        rlp_header(payload_length).encode(out);
736
737        // Encode fields using helper
738        self.rlp_encode_fields(
739            out,
740            |signature, out| {
741                if signature.is_some() {
742                    out.put_u8(0); // placeholder byte
743                } else {
744                    out.put_u8(EMPTY_STRING_CODE);
745                }
746            },
747            skip_fee_token,
748        );
749    }
750
751    fn payload_len_for_signature(&self) -> usize {
752        let skip_fee_token = self.fee_payer_signature.is_some();
753        let payload_length = self.rlp_encoded_fields_length(|_| 1, skip_fee_token);
754
755        1 + rlp_header(payload_length).length_with_payload()
756    }
757}
758
759impl Encodable for TempoTransaction {
760    fn encode(&self, out: &mut dyn BufMut) {
761        // Encode as RLP list of fields
762        let payload_length = self.rlp_encoded_fields_length_default();
763        rlp_header(payload_length).encode(out);
764        self.rlp_encode_fields_default(out);
765    }
766
767    fn length(&self) -> usize {
768        let payload_length = self.rlp_encoded_fields_length_default();
769        rlp_header(payload_length).length_with_payload()
770    }
771}
772
773impl Decodable for TempoTransaction {
774    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
775        let header = alloy_rlp::Header::decode(buf)?;
776        if !header.list {
777            return Err(alloy_rlp::Error::UnexpectedString);
778        }
779        let remaining = buf.len();
780
781        if header.payload_length > remaining {
782            return Err(alloy_rlp::Error::InputTooShort);
783        }
784
785        let mut fields_buf = &buf[..header.payload_length];
786        let this = Self::rlp_decode_fields(&mut fields_buf)?;
787
788        if !fields_buf.is_empty() {
789            return Err(alloy_rlp::Error::UnexpectedLength);
790        }
791        buf.advance(header.payload_length);
792
793        Ok(this)
794    }
795}
796
797#[cfg(feature = "reth")]
798impl reth_primitives_traits::InMemorySize for TempoTransaction {
799    fn size(&self) -> usize {
800        Self::size(self)
801    }
802}
803
804#[cfg(feature = "serde-bincode-compat")]
805impl reth_primitives_traits::serde_bincode_compat::RlpBincode for TempoTransaction {}
806
807// Custom Arbitrary implementation to ensure calls is never empty and CREATE validation passes
808#[cfg(any(test, feature = "arbitrary"))]
809impl<'a> arbitrary::Arbitrary<'a> for TempoTransaction {
810    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
811        // Generate all fields using the default Arbitrary implementation
812        let chain_id = u.arbitrary()?;
813        let fee_token = u.arbitrary()?;
814        let max_priority_fee_per_gas = u.arbitrary()?;
815        let max_fee_per_gas = u.arbitrary()?;
816        let gas_limit = u.arbitrary()?;
817
818        // Generate calls - ensure at least one call is present and CREATE validation passes
819        // CREATE must be first (if present) and only one CREATE allowed
820        let mut calls: Vec<Call> = u.arbitrary()?;
821        if calls.is_empty() {
822            calls.push(Call {
823                to: u.arbitrary()?,
824                value: u.arbitrary()?,
825                input: u.arbitrary()?,
826            });
827        }
828
829        // Filter out CREATEs from non-first positions and ensure only one CREATE (if any)
830        let first_is_create = calls.first().map(|c| c.to.is_create()).unwrap_or(false);
831        if first_is_create {
832            // Keep the first CREATE, remove all other CREATEs
833            for call in calls.iter_mut().skip(1) {
834                if call.to.is_create() {
835                    // Replace with a CALL to a random address
836                    call.to = TxKind::Call(u.arbitrary()?);
837                }
838            }
839        } else {
840            // Remove all CREATEs (they would be at non-first positions)
841            for call in &mut calls {
842                if call.to.is_create() {
843                    call.to = TxKind::Call(u.arbitrary()?);
844                }
845            }
846        }
847
848        let access_list = u.arbitrary()?;
849
850        // For now, always set nonce_key to 0 (protocol nonce) to pass validation
851        let nonce_key = U256::ZERO;
852        let nonce = u.arbitrary()?;
853        let fee_payer_signature = u.arbitrary()?;
854
855        // Ensure valid_before > valid_after if both are set
856        // Note: We avoid generating Some(0) for valid_after because in RLP encoding,
857        // 0 encodes as 0x80 (EMPTY_STRING_CODE), which is indistinguishable from None.
858        // This is a known limitation of RLP for optional integer fields.
859        let valid_after: Option<u64> = u.arbitrary::<Option<u64>>()?.filter(|v| *v != 0);
860        let valid_before: Option<u64> = match valid_after {
861            Some(after) => {
862                // Generate a value greater than valid_after
863                let offset: u64 = u.int_in_range(1..=1000)?;
864                Some(after.saturating_add(offset))
865            }
866            None => {
867                // Similarly avoid Some(0) for valid_before
868                u.arbitrary::<Option<u64>>()?.filter(|v| *v != 0)
869            }
870        };
871
872        Ok(Self {
873            chain_id,
874            fee_token,
875            max_priority_fee_per_gas,
876            max_fee_per_gas,
877            gas_limit,
878            calls,
879            access_list,
880            nonce_key,
881            nonce,
882            fee_payer_signature,
883            valid_before,
884            valid_after,
885            key_authorization: u.arbitrary()?,
886            tempo_authorization_list: vec![],
887        })
888    }
889}
890
891#[cfg(feature = "serde")]
892mod serde_input {
893    //! Helper module for serializing and deserializing the `input` field of a [`Call`] as either `input` or `data` fields.
894
895    use alloc::borrow::Cow;
896
897    use super::*;
898    use serde::{Deserialize, Deserializer, Serialize, Serializer};
899
900    #[derive(Serialize, Deserialize)]
901    struct SerdeHelper<'a> {
902        input: Option<Cow<'a, Bytes>>,
903        data: Option<Cow<'a, Bytes>>,
904    }
905
906    pub(super) fn serialize<S>(input: &Bytes, serializer: S) -> Result<S::Ok, S::Error>
907    where
908        S: Serializer,
909    {
910        SerdeHelper {
911            input: Some(Cow::Borrowed(input)),
912            data: None,
913        }
914        .serialize(serializer)
915    }
916
917    pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<Bytes, D::Error>
918    where
919        D: Deserializer<'de>,
920    {
921        let helper = SerdeHelper::deserialize(deserializer)?;
922        Ok(helper
923            .input
924            .or(helper.data)
925            .ok_or(serde::de::Error::missing_field(
926                "missing `input` or `data` field",
927            ))?
928            .into_owned())
929    }
930}
931
932#[cfg(test)]
933mod tests {
934    use super::*;
935    use crate::{
936        TempoTxEnvelope,
937        transaction::{
938            KeyAuthorization, TempoSignedAuthorization,
939            tt_signature::{
940                PrimitiveSignature, SIGNATURE_TYPE_P256, SIGNATURE_TYPE_WEBAUTHN, TempoSignature,
941                derive_p256_address,
942            },
943        },
944    };
945    use alloy_eips::{Decodable2718, Encodable2718, eip7702::Authorization};
946    use alloy_primitives::{Address, Bytes, Signature, TxKind, U256, address, bytes, hex};
947    use alloy_rlp::{Decodable, Encodable};
948
949    #[test]
950    fn test_tempo_transaction_validation() {
951        // Create a dummy call to satisfy validation
952        let dummy_call = Call {
953            to: TxKind::Create,
954            value: U256::ZERO,
955            input: Bytes::new(),
956        };
957
958        // Valid: valid_before > valid_after
959        let tx1 = TempoTransaction {
960            valid_before: Some(100),
961            valid_after: Some(50),
962            tempo_authorization_list: vec![],
963            calls: vec![dummy_call.clone()],
964            ..Default::default()
965        };
966        assert!(tx1.validate().is_ok());
967
968        // Invalid: valid_before <= valid_after
969        let tx2 = TempoTransaction {
970            valid_before: Some(50),
971            valid_after: Some(100),
972            tempo_authorization_list: vec![],
973            calls: vec![dummy_call.clone()],
974            ..Default::default()
975        };
976        assert!(tx2.validate().is_err());
977
978        // Invalid: valid_before == valid_after
979        let tx3 = TempoTransaction {
980            valid_before: Some(100),
981            valid_after: Some(100),
982            tempo_authorization_list: vec![],
983            calls: vec![dummy_call.clone()],
984            ..Default::default()
985        };
986        assert!(tx3.validate().is_err());
987
988        // Valid: no valid_after
989        let tx4 = TempoTransaction {
990            valid_before: Some(100),
991            valid_after: None,
992            tempo_authorization_list: vec![],
993            calls: vec![dummy_call],
994            ..Default::default()
995        };
996        assert!(tx4.validate().is_ok());
997
998        // Invalid: empty calls
999        let tx5 = TempoTransaction {
1000            ..Default::default()
1001        };
1002        assert!(tx5.validate().is_err());
1003    }
1004
1005    #[test]
1006    fn test_tx_type() {
1007        assert_eq!(TempoTransaction::tx_type(), 0x76);
1008        assert_eq!(TEMPO_TX_TYPE_ID, 0x76);
1009    }
1010
1011    #[test]
1012    fn test_signature_type_detection() {
1013        // Secp256k1 (detected by 65-byte length, no type identifier)
1014        let sig1_bytes = vec![0u8; SECP256K1_SIGNATURE_LENGTH];
1015        let sig1 = TempoSignature::from_bytes(&sig1_bytes).unwrap();
1016        assert_eq!(sig1.signature_type(), SignatureType::Secp256k1);
1017
1018        // P256
1019        let mut sig2_bytes = vec![SIGNATURE_TYPE_P256];
1020        sig2_bytes.extend_from_slice(&[0u8; P256_SIGNATURE_LENGTH]);
1021        let sig2 = TempoSignature::from_bytes(&sig2_bytes).unwrap();
1022        assert_eq!(sig2.signature_type(), SignatureType::P256);
1023
1024        // WebAuthn
1025        let mut sig3_bytes = vec![SIGNATURE_TYPE_WEBAUTHN];
1026        sig3_bytes.extend_from_slice(&[0u8; 200]);
1027        let sig3 = TempoSignature::from_bytes(&sig3_bytes).unwrap();
1028        assert_eq!(sig3.signature_type(), SignatureType::WebAuthn);
1029    }
1030
1031    #[test]
1032    fn test_rlp_roundtrip() {
1033        let call = Call {
1034            to: TxKind::Call(address!("0000000000000000000000000000000000000002")),
1035            value: U256::from(1000),
1036            input: Bytes::from(vec![1, 2, 3, 4]),
1037        };
1038
1039        let tx = TempoTransaction {
1040            chain_id: 1,
1041            fee_token: Some(address!("0000000000000000000000000000000000000001")),
1042            max_priority_fee_per_gas: 1000000000,
1043            max_fee_per_gas: 2000000000,
1044            gas_limit: 21000,
1045            calls: vec![call.clone()],
1046            access_list: Default::default(),
1047            nonce_key: U256::ZERO,
1048            nonce: 1,
1049            fee_payer_signature: Some(Signature::test_signature()),
1050            valid_before: Some(1000000),
1051            valid_after: Some(500000),
1052            key_authorization: None,
1053            tempo_authorization_list: vec![],
1054        };
1055
1056        // Encode
1057        let mut buf = Vec::new();
1058        tx.encode(&mut buf);
1059
1060        // Decode
1061        let decoded = TempoTransaction::decode(&mut buf.as_slice()).unwrap();
1062
1063        // Verify fields
1064        assert_eq!(decoded.chain_id, tx.chain_id);
1065        assert_eq!(decoded.fee_token, tx.fee_token);
1066        assert_eq!(
1067            decoded.max_priority_fee_per_gas,
1068            tx.max_priority_fee_per_gas
1069        );
1070        assert_eq!(decoded.max_fee_per_gas, tx.max_fee_per_gas);
1071        assert_eq!(decoded.gas_limit, tx.gas_limit);
1072        assert_eq!(decoded.calls.len(), 1);
1073        assert_eq!(decoded.calls[0].to, call.to);
1074        assert_eq!(decoded.calls[0].value, call.value);
1075        assert_eq!(decoded.calls[0].input, call.input);
1076        assert_eq!(decoded.nonce_key, tx.nonce_key);
1077        assert_eq!(decoded.nonce, tx.nonce);
1078        assert_eq!(decoded.valid_before, tx.valid_before);
1079        assert_eq!(decoded.valid_after, tx.valid_after);
1080        assert_eq!(decoded.fee_payer_signature, tx.fee_payer_signature);
1081    }
1082
1083    #[test]
1084    fn test_rlp_roundtrip_no_optional_fields() {
1085        let call = Call {
1086            to: TxKind::Call(address!("0000000000000000000000000000000000000002")),
1087            value: U256::from(1000),
1088            input: Bytes::new(),
1089        };
1090
1091        let tx = TempoTransaction {
1092            chain_id: 1,
1093            fee_token: None,
1094            max_priority_fee_per_gas: 1000000000,
1095            max_fee_per_gas: 2000000000,
1096            gas_limit: 21000,
1097            calls: vec![call],
1098            access_list: Default::default(),
1099            nonce_key: U256::ZERO,
1100            nonce: 1,
1101            fee_payer_signature: None,
1102            valid_before: Some(1000),
1103            valid_after: None,
1104            key_authorization: None,
1105            tempo_authorization_list: vec![],
1106        };
1107
1108        // Encode
1109        let mut buf = Vec::new();
1110        tx.encode(&mut buf);
1111
1112        // Decode
1113        let decoded = TempoTransaction::decode(&mut buf.as_slice()).unwrap();
1114
1115        // Verify fields
1116        assert_eq!(decoded.chain_id, tx.chain_id);
1117        assert_eq!(decoded.fee_token, None);
1118        assert_eq!(decoded.fee_payer_signature, None);
1119        assert_eq!(decoded.valid_after, None);
1120        assert_eq!(decoded.calls.len(), 1);
1121    }
1122
1123    #[test]
1124    fn test_p256_address_derivation() {
1125        let pub_key_x =
1126            hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1127        let pub_key_y =
1128            hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1129
1130        let addr1 = derive_p256_address(&pub_key_x, &pub_key_y);
1131        let addr2 = derive_p256_address(&pub_key_x, &pub_key_y);
1132
1133        // Should be deterministic
1134        assert_eq!(addr1, addr2);
1135
1136        // Should not be zero address
1137        assert_ne!(addr1, Address::ZERO);
1138    }
1139
1140    #[test]
1141    fn test_nonce_system() {
1142        // Create a dummy call to satisfy validation
1143        let dummy_call = Call {
1144            to: TxKind::Create,
1145            value: U256::ZERO,
1146            input: Bytes::new(),
1147        };
1148
1149        // Test 1: Protocol nonce (key 0)
1150        let tx1 = TempoTransaction {
1151            nonce_key: U256::ZERO,
1152            nonce: 1,
1153            calls: vec![dummy_call.clone()],
1154            ..Default::default()
1155        };
1156        assert!(tx1.validate().is_ok());
1157        assert_eq!(tx1.nonce(), 1);
1158        assert_eq!(tx1.nonce_key, U256::ZERO);
1159
1160        // Test 2: User nonce (key 1, nonce 0) - first transaction in parallel sequence
1161        let tx2 = TempoTransaction {
1162            nonce_key: U256::from(1),
1163            nonce: 0,
1164            calls: vec![dummy_call.clone()],
1165            ..Default::default()
1166        };
1167        assert!(tx2.validate().is_ok());
1168        assert_eq!(tx2.nonce(), 0);
1169        assert_eq!(tx2.nonce_key, U256::from(1));
1170
1171        // Test 3: Different nonce key (key 42) - independent parallel sequence
1172        let tx3 = TempoTransaction {
1173            nonce_key: U256::from(42),
1174            nonce: 10,
1175            calls: vec![dummy_call.clone()],
1176            ..Default::default()
1177        };
1178        assert!(tx3.validate().is_ok());
1179        assert_eq!(tx3.nonce(), 10);
1180        assert_eq!(tx3.nonce_key, U256::from(42));
1181
1182        // Test 4: Verify nonce independence between different keys
1183        // Transactions with same nonce but different keys are independent
1184        let tx4a = TempoTransaction {
1185            nonce_key: U256::from(1),
1186            nonce: 100,
1187            calls: vec![dummy_call.clone()],
1188            ..Default::default()
1189        };
1190        let tx4b = TempoTransaction {
1191            nonce_key: U256::from(2),
1192            nonce: 100,
1193            calls: vec![dummy_call],
1194            ..Default::default()
1195        };
1196        assert!(tx4a.validate().is_ok());
1197        assert!(tx4b.validate().is_ok());
1198        assert_eq!(tx4a.nonce(), tx4b.nonce()); // Same nonce value
1199        assert_ne!(tx4a.nonce_key, tx4b.nonce_key); // Different keys = independent
1200    }
1201
1202    #[test]
1203    fn test_transaction_trait_impl() {
1204        let call = Call {
1205            to: TxKind::Call(address!("0000000000000000000000000000000000000002")),
1206            value: U256::from(1000),
1207            input: Bytes::new(),
1208        };
1209
1210        let tx = TempoTransaction {
1211            chain_id: 1,
1212            max_priority_fee_per_gas: 1000000000,
1213            max_fee_per_gas: 2000000000,
1214            gas_limit: 21000,
1215            calls: vec![call],
1216            ..Default::default()
1217        };
1218
1219        assert_eq!(tx.chain_id(), Some(1));
1220        assert_eq!(tx.gas_limit(), 21000);
1221        assert_eq!(tx.max_fee_per_gas(), 2000000000);
1222        assert_eq!(tx.max_priority_fee_per_gas(), Some(1000000000));
1223        assert_eq!(tx.value(), U256::from(1000));
1224        assert!(tx.is_dynamic_fee());
1225        assert!(!tx.is_create());
1226    }
1227
1228    #[test]
1229    fn test_effective_gas_price() {
1230        // Create a dummy call to satisfy validation
1231        let dummy_call = Call {
1232            to: TxKind::Create,
1233            value: U256::ZERO,
1234            input: Bytes::new(),
1235        };
1236
1237        let tx = TempoTransaction {
1238            max_priority_fee_per_gas: 1000000000,
1239            max_fee_per_gas: 2000000000,
1240            calls: vec![dummy_call],
1241            ..Default::default()
1242        };
1243
1244        // With base fee
1245        let effective1 = tx.effective_gas_price(Some(500000000));
1246        assert_eq!(effective1, 1500000000); // base_fee + max_priority_fee_per_gas
1247
1248        // Without base fee
1249        let effective2 = tx.effective_gas_price(None);
1250        assert_eq!(effective2, 2000000000); // max_fee_per_gas
1251    }
1252
1253    #[test]
1254    fn test_fee_payer_commits_to_fee_token() {
1255        // This test verifies that the fee payer signature commits to the fee_token value
1256        // i.e., changing fee_token changes the fee_payer_signature_hash
1257
1258        let sender = address!("0000000000000000000000000000000000000001");
1259        let token1 = address!("0000000000000000000000000000000000000002");
1260        let token2 = address!("0000000000000000000000000000000000000003");
1261
1262        let dummy_call = Call {
1263            to: TxKind::Create,
1264            value: U256::ZERO,
1265            input: Bytes::new(),
1266        };
1267
1268        // Transaction with fee_token = None
1269        let tx_no_token = TempoTransaction {
1270            chain_id: 1,
1271            fee_token: None,
1272            max_priority_fee_per_gas: 1000000000,
1273            max_fee_per_gas: 2000000000,
1274            gas_limit: 21000,
1275            calls: vec![dummy_call],
1276            nonce_key: U256::ZERO,
1277            nonce: 1,
1278            fee_payer_signature: Some(Signature::test_signature()),
1279            valid_before: Some(1000),
1280            valid_after: None,
1281            ..Default::default()
1282        };
1283
1284        // Transaction with fee_token = token1
1285        let tx_token1 = TempoTransaction {
1286            fee_token: Some(token1),
1287            ..tx_no_token.clone()
1288        };
1289
1290        // Transaction with fee_token = token2
1291        let tx_token2 = TempoTransaction {
1292            fee_token: Some(token2),
1293            ..tx_no_token.clone()
1294        };
1295
1296        // Calculate fee payer signature hashes
1297        let fee_payer_hash_no_token = tx_no_token.fee_payer_signature_hash(sender);
1298        let fee_payer_hash_token1 = tx_token1.fee_payer_signature_hash(sender);
1299        let fee_payer_hash_token2 = tx_token2.fee_payer_signature_hash(sender);
1300
1301        // All three fee payer hashes should be different (fee payer commits to fee_token)
1302        assert_ne!(
1303            fee_payer_hash_no_token, fee_payer_hash_token1,
1304            "Fee payer hash should change when fee_token changes from None to Some"
1305        );
1306        assert_ne!(
1307            fee_payer_hash_token1, fee_payer_hash_token2,
1308            "Fee payer hash should change when fee_token changes from token1 to token2"
1309        );
1310        assert_ne!(
1311            fee_payer_hash_no_token, fee_payer_hash_token2,
1312            "Fee payer hash should be different for None vs token2"
1313        );
1314
1315        // Calculate user signature hashes (what the sender signs)
1316        let user_hash_no_token = tx_no_token.signature_hash();
1317        let user_hash_token1 = tx_token1.signature_hash();
1318        let user_hash_token2 = tx_token2.signature_hash();
1319
1320        // All three user hashes should be THE SAME (user skips fee_token when fee_payer is present)
1321        assert_eq!(
1322            user_hash_no_token, user_hash_token1,
1323            "User hash should be the same regardless of fee_token (user skips fee_token)"
1324        );
1325        assert_eq!(
1326            user_hash_token1, user_hash_token2,
1327            "User hash should be the same regardless of fee_token (user skips fee_token)"
1328        );
1329        assert_eq!(
1330            user_hash_no_token, user_hash_token2,
1331            "User hash should be the same regardless of fee_token (user skips fee_token)"
1332        );
1333    }
1334
1335    #[test]
1336    fn test_fee_payer_signature_uses_magic_byte() {
1337        // Verify that fee payer signature hash uses the magic byte 0x78
1338
1339        let sender = address!("0000000000000000000000000000000000000001");
1340        let dummy_call = Call {
1341            to: TxKind::Create,
1342            value: U256::ZERO,
1343            input: Bytes::new(),
1344        };
1345
1346        let tx = TempoTransaction {
1347            chain_id: 1,
1348            fee_token: None,
1349            max_priority_fee_per_gas: 1000000000,
1350            max_fee_per_gas: 2000000000,
1351            gas_limit: 21000,
1352            calls: vec![dummy_call],
1353            nonce_key: U256::ZERO,
1354            nonce: 1,
1355            fee_payer_signature: Some(Signature::test_signature()),
1356            valid_before: Some(1000),
1357            ..Default::default()
1358        };
1359
1360        // The fee_payer_signature_hash should start with the magic byte
1361        // We can't directly inspect the hash construction, but we can verify it's different
1362        // from the sender signature hash which uses TEMPO_TX_TYPE_ID (0x76)
1363        let sender_hash = tx.signature_hash();
1364        let fee_payer_hash = tx.fee_payer_signature_hash(sender);
1365
1366        // These should be different because they use different type bytes
1367        assert_ne!(
1368            sender_hash, fee_payer_hash,
1369            "Sender and fee payer hashes should be different (different magic bytes)"
1370        );
1371    }
1372
1373    #[test]
1374    fn test_user_signature_without_fee_payer() {
1375        // Test that user signature hash INCLUDES fee_token when fee_payer is NOT present
1376
1377        let token1 = address!("0000000000000000000000000000000000000002");
1378        let token2 = address!("0000000000000000000000000000000000000003");
1379
1380        let dummy_call = Call {
1381            to: TxKind::Create,
1382            value: U256::ZERO,
1383            input: Bytes::new(),
1384        };
1385
1386        // Transaction WITHOUT fee_payer, fee_token = None
1387        let tx_no_payer_no_token = TempoTransaction {
1388            chain_id: 1,
1389            fee_token: None,
1390            max_priority_fee_per_gas: 1000000000,
1391            max_fee_per_gas: 2000000000,
1392            gas_limit: 21000,
1393            calls: vec![dummy_call],
1394            nonce_key: U256::ZERO,
1395            nonce: 1,
1396            fee_payer_signature: None, // No fee payer
1397            valid_before: Some(1000),
1398            valid_after: None,
1399            tempo_authorization_list: vec![],
1400            access_list: Default::default(),
1401            key_authorization: None,
1402        };
1403
1404        // Transaction WITHOUT fee_payer, fee_token = token1
1405        let tx_no_payer_token1 = TempoTransaction {
1406            fee_token: Some(token1),
1407            ..tx_no_payer_no_token.clone()
1408        };
1409
1410        // Transaction WITHOUT fee_payer, fee_token = token2
1411        let tx_no_payer_token2 = TempoTransaction {
1412            fee_token: Some(token2),
1413            ..tx_no_payer_no_token.clone()
1414        };
1415
1416        // Calculate user signature hashes
1417        let hash_no_token = tx_no_payer_no_token.signature_hash();
1418        let hash_token1 = tx_no_payer_token1.signature_hash();
1419        let hash_token2 = tx_no_payer_token2.signature_hash();
1420
1421        // All three hashes should be DIFFERENT (user includes fee_token when no fee_payer)
1422        assert_ne!(
1423            hash_no_token, hash_token1,
1424            "User hash should change when fee_token changes (no fee_payer)"
1425        );
1426        assert_ne!(
1427            hash_token1, hash_token2,
1428            "User hash should change when fee_token changes (no fee_payer)"
1429        );
1430        assert_ne!(
1431            hash_no_token, hash_token2,
1432            "User hash should change when fee_token changes (no fee_payer)"
1433        );
1434    }
1435
1436    #[test]
1437    fn test_rlp_encoding_includes_fee_token() {
1438        // Test that RLP encoding always includes fee_token in the encoded data
1439
1440        let token = address!("0000000000000000000000000000000000000002");
1441
1442        let dummy_call = Call {
1443            to: TxKind::Create,
1444            value: U256::ZERO,
1445            input: Bytes::new(),
1446        };
1447
1448        // Transaction with fee_token
1449        let tx_with_token = TempoTransaction {
1450            chain_id: 1,
1451            fee_token: Some(token),
1452            max_priority_fee_per_gas: 1000000000,
1453            max_fee_per_gas: 2000000000,
1454            gas_limit: 21000,
1455            calls: vec![dummy_call],
1456            nonce_key: U256::ZERO,
1457            nonce: 1,
1458            fee_payer_signature: Some(Signature::test_signature()),
1459            valid_before: Some(1000),
1460            valid_after: None,
1461            tempo_authorization_list: vec![],
1462            access_list: Default::default(),
1463            key_authorization: None,
1464        };
1465
1466        // Transaction without fee_token
1467        let tx_without_token = TempoTransaction {
1468            fee_token: None,
1469            ..tx_with_token.clone()
1470        };
1471
1472        // Encode both transactions
1473        let mut buf_with = Vec::new();
1474        tx_with_token.encode(&mut buf_with);
1475
1476        let mut buf_without = Vec::new();
1477        tx_without_token.encode(&mut buf_without);
1478
1479        // The encoded bytes should be different lengths
1480        assert_ne!(
1481            buf_with.len(),
1482            buf_without.len(),
1483            "RLP encoding should include fee_token in the encoded data"
1484        );
1485
1486        // The one with token should be longer (20 bytes for address vs 1 byte for empty)
1487        assert!(
1488            buf_with.len() > buf_without.len(),
1489            "Transaction with fee_token should have longer encoding"
1490        );
1491
1492        // Decode and verify
1493        let decoded_with = TempoTransaction::decode(&mut buf_with.as_slice()).unwrap();
1494        let decoded_without = TempoTransaction::decode(&mut buf_without.as_slice()).unwrap();
1495
1496        assert_eq!(decoded_with.fee_token, Some(token));
1497        assert_eq!(decoded_without.fee_token, None);
1498    }
1499
1500    #[test]
1501    fn test_signature_hash_behavior_with_and_without_fee_payer() {
1502        // Comprehensive test showing all signature hash behaviors
1503
1504        let token = address!("0000000000000000000000000000000000000002");
1505
1506        let dummy_call = Call {
1507            to: TxKind::Create,
1508            value: U256::ZERO,
1509            input: Bytes::new(),
1510        };
1511
1512        // Scenario 1: No fee payer, no token
1513        let tx_no_payer_no_token = TempoTransaction {
1514            chain_id: 1,
1515            fee_token: None,
1516            max_priority_fee_per_gas: 1000000000,
1517            max_fee_per_gas: 2000000000,
1518            gas_limit: 21000,
1519            calls: vec![dummy_call],
1520            nonce_key: U256::ZERO,
1521            nonce: 1,
1522            fee_payer_signature: None,
1523            valid_before: Some(1000),
1524            valid_after: None,
1525            tempo_authorization_list: vec![],
1526            access_list: Default::default(),
1527            key_authorization: None,
1528        };
1529
1530        // Scenario 2: No fee payer, with token
1531        let tx_no_payer_with_token = TempoTransaction {
1532            fee_token: Some(token),
1533            ..tx_no_payer_no_token.clone()
1534        };
1535
1536        // Scenario 3: With fee payer, no token
1537        let tx_with_payer_no_token = TempoTransaction {
1538            fee_payer_signature: Some(Signature::test_signature()),
1539            ..tx_no_payer_no_token.clone()
1540        };
1541
1542        // Scenario 4: With fee payer, with token
1543        let tx_with_payer_with_token = TempoTransaction {
1544            fee_token: Some(token),
1545            fee_payer_signature: Some(Signature::test_signature()),
1546            ..tx_no_payer_no_token.clone()
1547        };
1548
1549        // Calculate user signature hashes
1550        let hash1 = tx_no_payer_no_token.signature_hash();
1551        let hash2 = tx_no_payer_with_token.signature_hash();
1552        let hash3 = tx_with_payer_no_token.signature_hash();
1553        let hash4 = tx_with_payer_with_token.signature_hash();
1554
1555        // Without fee_payer: user includes fee_token, so hash1 != hash2
1556        assert_ne!(
1557            hash1, hash2,
1558            "User hash changes with fee_token when no fee_payer"
1559        );
1560
1561        // With fee_payer: user skips fee_token, so hash3 == hash4
1562        assert_eq!(
1563            hash3, hash4,
1564            "User hash ignores fee_token when fee_payer is present"
1565        );
1566
1567        // Hashes without fee_payer should differ from hashes with fee_payer
1568        // (because skip_fee_token logic changes)
1569        assert_ne!(hash1, hash3, "User hash changes when fee_payer is added");
1570    }
1571
1572    #[test]
1573    fn test_backwards_compatibility_key_authorization() {
1574        // Test that transactions without key_authorization are backwards compatible
1575        // and that the RLP encoding doesn't include any extra bytes for None
1576
1577        let call = Call {
1578            to: TxKind::Call(address!("0000000000000000000000000000000000000002")),
1579            value: U256::from(1000),
1580            input: Bytes::from(vec![1, 2, 3, 4]),
1581        };
1582
1583        // Create transaction WITHOUT key_authorization (old format)
1584        let tx_without = TempoTransaction {
1585            chain_id: 1,
1586            fee_token: Some(address!("0000000000000000000000000000000000000001")),
1587            max_priority_fee_per_gas: 1000000000,
1588            max_fee_per_gas: 2000000000,
1589            gas_limit: 21000,
1590            calls: vec![call],
1591            access_list: Default::default(),
1592            nonce_key: U256::ZERO,
1593            nonce: 1,
1594            fee_payer_signature: Some(Signature::test_signature()),
1595            valid_before: Some(1000000),
1596            valid_after: Some(500000),
1597            key_authorization: None, // No key authorization
1598            tempo_authorization_list: vec![],
1599        };
1600
1601        // Encode the transaction
1602        let mut buf_without = Vec::new();
1603        tx_without.encode(&mut buf_without);
1604
1605        // Decode it back
1606        let decoded_without = TempoTransaction::decode(&mut buf_without.as_slice()).unwrap();
1607
1608        // Verify it matches
1609        assert_eq!(decoded_without.key_authorization, None);
1610        assert_eq!(decoded_without.chain_id, tx_without.chain_id);
1611        assert_eq!(decoded_without.calls.len(), tx_without.calls.len());
1612
1613        // Create transaction WITH key_authorization (new format)
1614        let key_auth = KeyAuthorization {
1615            chain_id: 1, // Test chain ID
1616            key_type: SignatureType::Secp256k1,
1617            expiry: Some(1234567890),
1618            limits: Some(vec![crate::transaction::TokenLimit {
1619                token: address!("0000000000000000000000000000000000000003"),
1620                limit: U256::from(10000),
1621            }]),
1622            key_id: address!("0000000000000000000000000000000000000004"),
1623        }
1624        .into_signed(PrimitiveSignature::Secp256k1(Signature::test_signature()));
1625
1626        let tx_with = TempoTransaction {
1627            key_authorization: Some(key_auth.clone()),
1628            ..tx_without.clone()
1629        };
1630
1631        // Encode the transaction
1632        let mut buf_with = Vec::new();
1633        tx_with.encode(&mut buf_with);
1634
1635        // Decode it back
1636        let decoded_with = TempoTransaction::decode(&mut buf_with.as_slice()).unwrap();
1637
1638        // Verify the key_authorization is preserved
1639        assert!(decoded_with.key_authorization.is_some());
1640        let decoded_key_auth = decoded_with.key_authorization.unwrap();
1641        assert_eq!(decoded_key_auth.key_type, key_auth.key_type);
1642        assert_eq!(decoded_key_auth.expiry, key_auth.expiry);
1643        assert_eq!(
1644            decoded_key_auth.limits.as_ref().map(|l| l.len()),
1645            key_auth.limits.as_ref().map(|l| l.len())
1646        );
1647        assert_eq!(decoded_key_auth.key_id, key_auth.key_id);
1648
1649        // Important: The encoded transaction WITHOUT key_authorization should be shorter
1650        // This proves we're not encoding empty bytes for None
1651        assert!(
1652            buf_without.len() < buf_with.len(),
1653            "Transaction without key_authorization should have shorter encoding"
1654        );
1655
1656        // Test that an old decoder (simulated by truncating at the right position)
1657        // can still decode a transaction without key_authorization
1658        // This simulates backwards compatibility with old code that doesn't know about key_authorization
1659        let decoded_old_format = TempoTransaction::decode(&mut buf_without.as_slice()).unwrap();
1660        assert_eq!(decoded_old_format.key_authorization, None);
1661    }
1662
1663    #[test]
1664    fn test_aa_signed_rlp_direct() {
1665        // Simple test for AASigned RLP encoding/decoding without key_authorization
1666        let call = Call {
1667            to: TxKind::Create,
1668            value: U256::ZERO,
1669            input: Bytes::new(),
1670        };
1671
1672        let tx = TempoTransaction {
1673            chain_id: 0,
1674            fee_token: None,
1675            max_priority_fee_per_gas: 0,
1676            max_fee_per_gas: 0,
1677            gas_limit: 0,
1678            calls: vec![call],
1679            access_list: Default::default(),
1680            nonce_key: U256::ZERO,
1681            nonce: 0,
1682            fee_payer_signature: None,
1683            valid_before: None,
1684            valid_after: None,
1685            key_authorization: None, // No key_authorization
1686            tempo_authorization_list: vec![],
1687        };
1688
1689        let signature =
1690            TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature()));
1691        let signed = AASigned::new_unhashed(tx, signature);
1692
1693        // Test direct RLP encoding/decoding
1694        let mut buf = Vec::new();
1695        signed.rlp_encode(&mut buf);
1696
1697        // Decode
1698        let decoded =
1699            AASigned::rlp_decode(&mut buf.as_slice()).expect("Should decode AASigned RLP");
1700        assert_eq!(decoded.tx().key_authorization, None);
1701    }
1702
1703    #[test]
1704    fn test_tempo_transaction_envelope_roundtrip_without_key_auth() {
1705        // Test that TempoTransaction in envelope works without key_authorization
1706        let call = Call {
1707            to: TxKind::Create,
1708            value: U256::ZERO,
1709            input: Bytes::new(),
1710        };
1711
1712        let tx = TempoTransaction {
1713            chain_id: 0,
1714            fee_token: None,
1715            max_priority_fee_per_gas: 0,
1716            max_fee_per_gas: 0,
1717            gas_limit: 0,
1718            calls: vec![call],
1719            access_list: Default::default(),
1720            nonce_key: U256::ZERO,
1721            nonce: 0,
1722            fee_payer_signature: None,
1723            valid_before: None,
1724            valid_after: None,
1725            key_authorization: None, // No key_authorization
1726            tempo_authorization_list: vec![],
1727        };
1728
1729        let signature =
1730            TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature()));
1731        let signed = AASigned::new_unhashed(tx, signature);
1732        let envelope = TempoTxEnvelope::AA(signed);
1733
1734        // Encode and decode the envelope
1735        let mut buf = Vec::new();
1736        envelope.encode_2718(&mut buf);
1737        let decoded = TempoTxEnvelope::decode_2718(&mut buf.as_slice())
1738            .expect("Should decode envelope successfully");
1739
1740        // Verify it's the same
1741        if let TempoTxEnvelope::AA(aa_signed) = decoded {
1742            assert_eq!(aa_signed.tx().key_authorization, None);
1743            assert_eq!(aa_signed.tx().calls.len(), 1);
1744            assert_eq!(aa_signed.tx().chain_id, 0);
1745        } else {
1746            panic!("Expected AA envelope");
1747        }
1748    }
1749
1750    #[test]
1751    fn test_call_decode_rejects_malformed_rlp() {
1752        // Test that Call decoding rejects RLP with mismatched header length
1753        let call = Call {
1754            to: TxKind::Call(Address::random()),
1755            value: U256::random(),
1756            input: Bytes::from(vec![1, 2, 3, 4]),
1757        };
1758
1759        // Encode the call normally
1760        let mut buf = Vec::new();
1761        call.encode(&mut buf);
1762
1763        // Corrupt the header to claim less payload than actually encoded
1764        // This simulates the case where header.payload_length doesn't match actual consumed bytes
1765        let original_len = buf.len();
1766        buf.truncate(original_len - 2); // Remove 2 bytes from the end
1767
1768        let result = Call::decode(&mut buf.as_slice());
1769        assert!(
1770            result.is_err(),
1771            "Decoding should fail when header length doesn't match"
1772        );
1773        // The error could be InputTooShort or UnexpectedLength depending on what field is truncated
1774        assert!(matches!(
1775            result.unwrap_err(),
1776            alloy_rlp::Error::InputTooShort | alloy_rlp::Error::UnexpectedLength
1777        ));
1778    }
1779
1780    #[test]
1781    fn test_tempo_transaction_decode_rejects_malformed_rlp() {
1782        // Test that TempoTransaction decoding rejects RLP with mismatched header length
1783        let call = Call {
1784            to: TxKind::Call(Address::random()),
1785            value: U256::random(),
1786            input: Bytes::from(vec![1, 2, 3, 4]),
1787        };
1788
1789        let tx = TempoTransaction {
1790            chain_id: 1,
1791            fee_token: Some(Address::random()),
1792            max_priority_fee_per_gas: 1000000000,
1793            max_fee_per_gas: 2000000000,
1794            gas_limit: 21000,
1795            calls: vec![call],
1796            access_list: Default::default(),
1797            nonce_key: U256::ZERO,
1798            nonce: 1,
1799            fee_payer_signature: Some(Signature::test_signature()),
1800            valid_before: Some(1000000),
1801            valid_after: Some(500000),
1802            key_authorization: None,
1803            tempo_authorization_list: vec![],
1804        };
1805
1806        // Encode the transaction normally
1807        let mut buf = Vec::new();
1808        tx.encode(&mut buf);
1809
1810        // Corrupt by truncating - simulates header claiming more bytes than available
1811        let original_len = buf.len();
1812        buf.truncate(original_len - 5); // Remove 5 bytes from the end
1813
1814        let result = TempoTransaction::decode(&mut buf.as_slice());
1815        assert!(
1816            result.is_err(),
1817            "Decoding should fail when data is truncated"
1818        );
1819        // The error could be InputTooShort or UnexpectedLength depending on what field is truncated
1820        assert!(matches!(
1821            result.unwrap_err(),
1822            alloy_rlp::Error::InputTooShort | alloy_rlp::Error::UnexpectedLength
1823        ));
1824    }
1825
1826    #[test]
1827    #[cfg(feature = "serde")]
1828    fn call_serde() {
1829        let call: Call = serde_json::from_str(
1830            r#"{"to":"0x0000000000000000000000000000000000000002","value":"0x1","input":"0x1234"}"#,
1831        )
1832        .unwrap();
1833        assert_eq!(
1834            call.to,
1835            TxKind::Call(address!("0000000000000000000000000000000000000002"))
1836        );
1837        assert_eq!(call.value, U256::ONE);
1838        assert_eq!(call.input, bytes!("0x1234"));
1839    }
1840
1841    #[test]
1842    fn test_create_must_be_first_call() {
1843        let create_call = Call {
1844            to: TxKind::Create,
1845            value: U256::ZERO,
1846            input: Bytes::new(),
1847        };
1848        let call_call = Call {
1849            to: TxKind::Call(Address::random()),
1850            value: U256::ZERO,
1851            input: Bytes::new(),
1852        };
1853
1854        // Valid: CREATE as first call
1855        let tx_valid = TempoTransaction {
1856            calls: vec![create_call.clone(), call_call.clone()],
1857            ..Default::default()
1858        };
1859        assert!(tx_valid.validate().is_ok());
1860
1861        // Invalid: CREATE as second call
1862        let tx_invalid = TempoTransaction {
1863            calls: vec![call_call, create_call],
1864            ..Default::default()
1865        };
1866        assert!(tx_invalid.validate().is_err());
1867        assert!(tx_invalid.validate().unwrap_err().contains("first call"));
1868    }
1869
1870    #[test]
1871    fn test_only_one_create_allowed() {
1872        let create_call = Call {
1873            to: TxKind::Create,
1874            value: U256::ZERO,
1875            input: Bytes::new(),
1876        };
1877
1878        // Valid: Single CREATE
1879        let tx_valid = TempoTransaction {
1880            calls: vec![create_call.clone()],
1881            ..Default::default()
1882        };
1883        assert!(tx_valid.validate().is_ok());
1884
1885        // Invalid: Multiple CREATEs (both at first position, second one triggers error)
1886        let tx_invalid = TempoTransaction {
1887            calls: vec![create_call.clone(), create_call],
1888            ..Default::default()
1889        };
1890        assert!(tx_invalid.validate().is_err());
1891        assert!(
1892            tx_invalid
1893                .validate()
1894                .unwrap_err()
1895                .contains("only one CREATE")
1896        );
1897    }
1898
1899    #[test]
1900    fn test_create_forbidden_with_auth_list() {
1901        let create_call = Call {
1902            to: TxKind::Create,
1903            value: U256::ZERO,
1904            input: Bytes::new(),
1905        };
1906
1907        let signed_auth = TempoSignedAuthorization::new_unchecked(
1908            Authorization {
1909                chain_id: U256::ONE,
1910                address: Address::random(),
1911                nonce: 1,
1912            },
1913            TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature())),
1914        );
1915
1916        // Invalid: CREATE call with auth list
1917        let tx = TempoTransaction {
1918            calls: vec![create_call],
1919            tempo_authorization_list: vec![signed_auth],
1920            ..Default::default()
1921        };
1922
1923        let result = tx.validate();
1924        assert!(result.is_err());
1925        assert!(result.unwrap_err().contains("aa_authorization_list"));
1926    }
1927
1928    #[test]
1929    fn test_create_validation_allows_call_only_batch() {
1930        // A batch with only CALL operations should be valid
1931        let call1 = Call {
1932            to: TxKind::Call(Address::random()),
1933            value: U256::ZERO,
1934            input: Bytes::new(),
1935        };
1936        let call2 = Call {
1937            to: TxKind::Call(Address::random()),
1938            value: U256::random(),
1939            input: Bytes::from(vec![1, 2, 3]),
1940        };
1941
1942        let tx = TempoTransaction {
1943            calls: vec![call1, call2],
1944            ..Default::default()
1945        };
1946        assert!(tx.validate().is_ok());
1947    }
1948
1949    #[test]
1950    fn test_value_saturates_on_overflow() {
1951        let call1 = Call {
1952            to: TxKind::Call(Address::ZERO),
1953            value: U256::MAX,
1954            input: Bytes::new(),
1955        };
1956        let call2 = Call {
1957            to: TxKind::Call(Address::ZERO),
1958            value: U256::from(1),
1959            input: Bytes::new(),
1960        };
1961
1962        let tx = TempoTransaction {
1963            calls: vec![call1, call2],
1964            ..Default::default()
1965        };
1966
1967        assert_eq!(tx.value(), U256::MAX);
1968    }
1969
1970    #[test]
1971    fn test_validate_does_not_check_expiring_nonce_constraints() {
1972        let dummy_call = Call {
1973            to: TxKind::Call(Address::ZERO),
1974            value: U256::ZERO,
1975            input: Bytes::new(),
1976        };
1977
1978        // Transaction with expiring nonce key but nonce != 0 should pass validate()
1979        // (expiring nonce constraints are hardfork-dependent and checked elsewhere)
1980        let tx_with_nonzero_nonce = TempoTransaction {
1981            nonce_key: TEMPO_EXPIRING_NONCE_KEY,
1982            nonce: 42,
1983            valid_before: None,
1984            calls: vec![dummy_call.clone()],
1985            ..Default::default()
1986        };
1987        assert!(
1988            tx_with_nonzero_nonce.validate().is_ok(),
1989            "validate() should not enforce expiring nonce constraints (hardfork-dependent)"
1990        );
1991
1992        // Transaction with expiring nonce key but no valid_before should pass validate()
1993        let tx_without_valid_before = TempoTransaction {
1994            nonce_key: TEMPO_EXPIRING_NONCE_KEY,
1995            nonce: 0,
1996            valid_before: None,
1997            calls: vec![dummy_call.clone()],
1998            ..Default::default()
1999        };
2000        assert!(
2001            tx_without_valid_before.validate().is_ok(),
2002            "validate() should not enforce expiring nonce constraints (hardfork-dependent)"
2003        );
2004
2005        // Sanity check: a fully valid expiring nonce tx should also pass
2006        let valid_expiring_tx = TempoTransaction {
2007            nonce_key: TEMPO_EXPIRING_NONCE_KEY,
2008            nonce: 0,
2009            valid_before: Some(1000),
2010            calls: vec![dummy_call],
2011            ..Default::default()
2012        };
2013        assert!(valid_expiring_tx.validate().is_ok());
2014    }
2015}