1#[cfg(feature = "serde")]
2use crate::transaction::key_authorization::serde_nonzero_quantity_opt;
3use crate::{
4 subblock::{PartialValidatorKey, has_sub_block_nonce_key_prefix},
5 transaction::{
6 AASigned, TempoSignature, TempoSignedAuthorization,
7 key_authorization::SignedKeyAuthorization,
8 },
9};
10use alloc::vec::Vec;
11use alloy_consensus::{SignableTransaction, Transaction, crypto::RecoveryError};
12use alloy_eips::{Typed2718, eip2930::AccessList, eip7702::SignedAuthorization};
13use alloy_primitives::{Address, B256, Bytes, ChainId, Signature, TxKind, U256, keccak256};
14use alloy_rlp::{Buf, BufMut, Decodable, EMPTY_STRING_CODE, Encodable};
15use core::num::NonZeroU64;
16
17pub const TEMPO_TX_TYPE_ID: u8 = 0x76;
19
20pub const FEE_PAYER_SIGNATURE_MAGIC_BYTE: u8 = 0x78;
22
23pub const FEE_PAYER_SIGNATURE_MARKER: Signature = Signature::new(U256::ZERO, U256::ZERO, false);
25
26pub const SECP256K1_SIGNATURE_LENGTH: usize = 65;
28pub const P256_SIGNATURE_LENGTH: usize = 129;
29pub const MAX_WEBAUTHN_SIGNATURE_LENGTH: usize = 2048; pub const TEMPO_EXPIRING_NONCE_KEY: U256 = U256::MAX;
33
34pub const TEMPO_EXPIRING_NONCE_MAX_EXPIRY_SECS: u64 = 30;
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
41#[cfg_attr(feature = "reth-codec", derive(reth_codecs::Compact))]
42#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
43#[repr(u8)]
44pub enum SignatureType {
45 Secp256k1 = 0,
46 P256 = 1,
47 WebAuthn = 2,
48}
49
50impl From<SignatureType> for u8 {
51 fn from(sig_type: SignatureType) -> Self {
52 match sig_type {
53 SignatureType::Secp256k1 => 0,
54 SignatureType::P256 => 1,
55 SignatureType::WebAuthn => 2,
56 }
57 }
58}
59
60use tempo_contracts::precompiles::IAccountKeychain::SignatureType as AbiSignatureType;
61
62impl From<SignatureType> for AbiSignatureType {
63 fn from(sig_type: SignatureType) -> Self {
64 match sig_type {
65 SignatureType::Secp256k1 => Self::Secp256k1,
66 SignatureType::P256 => Self::P256,
67 SignatureType::WebAuthn => Self::WebAuthn,
68 }
69 }
70}
71
72impl TryFrom<AbiSignatureType> for SignatureType {
73 type Error = u8;
74
75 fn try_from(sig_type: AbiSignatureType) -> Result<Self, Self::Error> {
76 match sig_type {
77 AbiSignatureType::Secp256k1 => Ok(Self::Secp256k1),
78 AbiSignatureType::P256 => Ok(Self::P256),
79 AbiSignatureType::WebAuthn => Ok(Self::WebAuthn),
80 _ => Err(sig_type as u8),
81 }
82 }
83}
84
85impl alloy_rlp::Encodable for SignatureType {
86 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
87 (*self as u8).encode(out);
88 }
89
90 fn length(&self) -> usize {
91 1
92 }
93}
94
95impl alloy_rlp::Decodable for SignatureType {
96 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
97 let byte: u8 = alloy_rlp::Decodable::decode(buf)?;
98 match byte {
99 0 => Ok(Self::Secp256k1),
100 1 => Ok(Self::P256),
101 2 => Ok(Self::WebAuthn),
102 _ => Err(alloy_rlp::Error::Custom("Invalid signature type")),
103 }
104 }
105}
106
107#[inline]
109fn rlp_header(payload_length: usize) -> alloy_rlp::Header {
110 alloy_rlp::Header {
111 list: true,
112 payload_length,
113 }
114}
115
116#[derive(Clone, Debug, PartialEq, Eq, Hash)]
117#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
118#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
119#[cfg_attr(feature = "reth-codec", derive(reth_codecs::Compact))]
120#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
121#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact, rlp))]
122pub struct Call {
123 pub to: TxKind,
125
126 pub value: U256,
128
129 #[cfg_attr(feature = "serde", serde(flatten, with = "serde_input"))]
131 pub input: Bytes,
132}
133
134impl Call {
135 #[inline]
137 fn rlp_header(&self) -> alloy_rlp::Header {
138 let payload_length = self.to.length() + self.value.length() + self.input.length();
139 alloy_rlp::Header {
140 list: true,
141 payload_length,
142 }
143 }
144
145 fn size(&self) -> usize {
146 size_of::<Self>() + self.input.len()
147 }
148}
149
150impl Encodable for Call {
151 fn encode(&self, out: &mut dyn BufMut) {
152 self.rlp_header().encode(out);
153 self.to.encode(out);
154 self.value.encode(out);
155 self.input.encode(out);
156 }
157
158 fn length(&self) -> usize {
159 self.rlp_header().length_with_payload()
160 }
161}
162
163impl Decodable for Call {
164 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
165 let header = alloy_rlp::Header::decode(buf)?;
166 if !header.list {
167 return Err(alloy_rlp::Error::UnexpectedString);
168 }
169 let remaining = buf.len();
170
171 if header.payload_length > remaining {
172 return Err(alloy_rlp::Error::InputTooShort);
173 }
174
175 let this = Self {
176 to: Decodable::decode(buf)?,
177 value: Decodable::decode(buf)?,
178 input: Decodable::decode(buf)?,
179 };
180
181 if buf.len() + header.payload_length != remaining {
182 return Err(alloy_rlp::Error::UnexpectedLength);
183 }
184
185 Ok(this)
186 }
187}
188
189#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
198#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
199#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
200#[cfg_attr(feature = "reth-codec", derive(reth_codecs::Compact))]
201pub struct TempoTransaction {
202 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
204 pub chain_id: ChainId,
205
206 pub fee_token: Option<Address>,
208
209 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
211 pub max_priority_fee_per_gas: u128,
212
213 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
215 pub max_fee_per_gas: u128,
216
217 #[cfg_attr(
219 feature = "serde",
220 serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
221 )]
222 pub gas_limit: u64,
223
224 pub calls: Vec<Call>,
226
227 pub access_list: AccessList,
229
230 pub nonce_key: U256,
235
236 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
238 pub nonce: u64,
239
240 pub fee_payer_signature: Option<Signature>,
244
245 #[cfg_attr(feature = "serde", serde(with = "serde_nonzero_quantity_opt"))]
253 pub valid_before: Option<NonZeroU64>,
254
255 #[cfg_attr(feature = "serde", serde(with = "serde_nonzero_quantity_opt"))]
263 pub valid_after: Option<NonZeroU64>,
264
265 pub key_authorization: Option<SignedKeyAuthorization>,
271
272 #[cfg_attr(feature = "serde", serde(rename = "aaAuthorizationList"))]
274 pub tempo_authorization_list: Vec<TempoSignedAuthorization>,
275}
276
277pub fn validate_calls(calls: &[Call], has_authorization_list: bool) -> Result<(), &'static str> {
287 if calls.is_empty() {
289 return Err("calls list cannot be empty");
290 }
291
292 let mut calls_iter = calls.iter();
293
294 if let Some(call) = calls_iter.next()
296 && has_authorization_list
299 && call.to.is_create()
300 {
301 return Err("calls cannot contain CREATE when 'aa_authorization_list' is non-empty");
302 }
303
304 for call in calls_iter {
306 if call.to.is_create() {
307 return Err(
308 "only one CREATE call is allowed per transaction, and it must be the first call of the batch",
309 );
310 }
311 }
312
313 Ok(())
314}
315
316impl TempoTransaction {
317 #[doc(alias = "transaction_type")]
319 pub const fn tx_type() -> u8 {
320 TEMPO_TX_TYPE_ID
321 }
322
323 #[inline]
328 pub fn is_expiring_nonce_tx(&self) -> bool {
329 self.nonce_key == TEMPO_EXPIRING_NONCE_KEY
330 }
331
332 pub fn validate(&self) -> Result<(), &'static str> {
338 validate_calls(&self.calls, !self.tempo_authorization_list.is_empty())?;
340
341 if let Some(valid_after) = self.valid_after
343 && let Some(valid_before) = self.valid_before
344 && valid_before <= valid_after
345 {
346 return Err("valid_before must be greater than valid_after");
347 }
348
349 Ok(())
350 }
351
352 #[inline]
354 pub fn size(&self) -> usize {
355 size_of::<Self>()
356 + self.calls.iter().map(|call| call.size()).sum::<usize>()
357 + self.access_list.size()
358 + self.key_authorization.as_ref().map_or(0, |k| k.size())
359 + self
360 .tempo_authorization_list
361 .iter()
362 .map(|auth| auth.size())
363 .sum::<usize>()
364 }
365
366 pub fn into_signed(self, signature: TempoSignature) -> AASigned {
368 AASigned::new_unhashed(self, signature)
369 }
370
371 pub fn signature_hash(&self) -> B256 {
374 let mut buf = Vec::new();
375 self.encode_for_signing(&mut buf);
376 keccak256(&buf)
377 }
378
379 pub fn fee_payer_signature_hash(&self, sender: Address) -> B256 {
383 let payload_length = self.rlp_encoded_fields_length(|_| sender.length(), false);
385
386 let mut buf = Vec::with_capacity(1 + rlp_header(payload_length).length_with_payload());
387
388 buf.put_u8(FEE_PAYER_SIGNATURE_MAGIC_BYTE);
390
391 rlp_header(payload_length).encode(&mut buf);
393
394 self.rlp_encode_fields(
396 &mut buf,
397 |_, out| {
398 sender.encode(out);
400 },
401 false, );
403
404 keccak256(&buf)
405 }
406
407 pub fn recover_fee_payer(&self, sender: Address) -> Result<Address, RecoveryError> {
411 if let Some(fee_payer_signature) = &self.fee_payer_signature {
412 alloy_consensus::crypto::secp256k1::recover_signer(
413 fee_payer_signature,
414 self.fee_payer_signature_hash(sender),
415 )
416 } else {
417 Ok(sender)
418 }
419 }
420
421 pub(crate) fn rlp_encoded_fields_length(
425 &self,
426 signature_length: impl FnOnce(&Option<Signature>) -> usize,
427 skip_fee_token: bool,
428 ) -> usize {
429 self.chain_id.length() +
430 self.max_priority_fee_per_gas.length() +
431 self.max_fee_per_gas.length() +
432 self.gas_limit.length() +
433 self.calls.length() +
434 self.access_list.length() +
435 self.nonce_key.length() +
436 self.nonce.length() +
437 self.valid_before.map_or(1, |valid_before| valid_before.length()) +
438 self.valid_after.map_or(1, |valid_after| valid_after.length()) +
440 if !skip_fee_token && let Some(addr) = self.fee_token {
442 addr.length()
443 } else {
444 1 } +
446 signature_length(&self.fee_payer_signature) +
447 self.tempo_authorization_list.length() +
449 if let Some(key_auth) = &self.key_authorization {
451 key_auth.length()
452 } else {
453 0 }
455 }
456
457 pub(crate) fn rlp_encode_fields(
458 &self,
459 out: &mut dyn BufMut,
460 encode_signature: impl FnOnce(&Option<Signature>, &mut dyn BufMut),
461 skip_fee_token: bool,
462 ) {
463 self.chain_id.encode(out);
464 self.max_priority_fee_per_gas.encode(out);
465 self.max_fee_per_gas.encode(out);
466 self.gas_limit.encode(out);
467 self.calls.encode(out);
468 self.access_list.encode(out);
469 self.nonce_key.encode(out);
470 self.nonce.encode(out);
471
472 if let Some(valid_before) = self.valid_before {
473 valid_before.encode(out);
474 } else {
475 out.put_u8(EMPTY_STRING_CODE);
476 }
477
478 if let Some(valid_after) = self.valid_after {
479 valid_after.encode(out);
480 } else {
481 out.put_u8(EMPTY_STRING_CODE);
482 }
483
484 if !skip_fee_token && let Some(addr) = self.fee_token {
485 addr.encode(out);
486 } else {
487 out.put_u8(EMPTY_STRING_CODE);
488 }
489
490 encode_signature(&self.fee_payer_signature, out);
491
492 self.tempo_authorization_list.encode(out);
494
495 if let Some(key_auth) = &self.key_authorization {
497 key_auth.encode(out);
498 }
499 }
501
502 pub(crate) fn rlp_encoded_fields_length_default(&self) -> usize {
504 self.rlp_encoded_fields_length(
505 |signature| {
506 signature.map_or(1, |s| {
507 rlp_header(s.rlp_rs_len() + s.v().length()).length_with_payload()
508 })
509 },
510 false,
511 )
512 }
513
514 pub(crate) fn rlp_encode_fields_default(&self, out: &mut dyn BufMut) {
516 self.rlp_encode_fields(
517 out,
518 |signature, out| {
519 if let Some(signature) = signature {
520 let payload_length = signature.rlp_rs_len() + signature.v().length();
521 rlp_header(payload_length).encode(out);
522 signature.write_rlp_vrs(out, signature.v());
523 } else {
524 out.put_u8(EMPTY_STRING_CODE);
525 }
526 },
527 false,
528 )
529 }
530
531 pub fn encode_for_fee_payer_service(&self, out: &mut dyn BufMut) {
536 out.put_u8(Self::tx_type());
537
538 let payload_length = self.rlp_encoded_fields_length(|_| 1, true);
539 rlp_header(payload_length).encode(out);
540 self.rlp_encode_fields(out, |_, out| out.put_u8(0x00), true);
541 }
542
543 pub(crate) fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
545 let chain_id = Decodable::decode(buf)?;
546 let max_priority_fee_per_gas = Decodable::decode(buf)?;
547 let max_fee_per_gas = Decodable::decode(buf)?;
548 let gas_limit = Decodable::decode(buf)?;
549 let calls = Decodable::decode(buf)?;
550 let access_list = Decodable::decode(buf)?;
551 let nonce_key = Decodable::decode(buf)?;
552 let nonce = Decodable::decode(buf)?;
553
554 let valid_before = u64::decode(buf).map(NonZeroU64::new)?;
555 let valid_after = u64::decode(buf).map(NonZeroU64::new)?;
556
557 let fee_token = if let Some(first) = buf.first() {
558 if *first == EMPTY_STRING_CODE {
559 buf.advance(1);
560 None
561 } else {
562 TxKind::decode(buf)?.into_to()
563 }
564 } else {
565 return Err(alloy_rlp::Error::InputTooShort);
566 };
567
568 let fee_payer_signature = if let Some(first) = buf.first() {
569 if *first == EMPTY_STRING_CODE {
570 buf.advance(1);
571 None
572 } else {
573 let header = alloy_rlp::Header::decode(buf)?;
574 if buf.len() < header.payload_length {
575 return Err(alloy_rlp::Error::InputTooShort);
576 }
577 if !header.list {
578 return Err(alloy_rlp::Error::UnexpectedString);
579 }
580 Some(Signature::decode_rlp_vrs(buf, bool::decode)?)
581 }
582 } else {
583 return Err(alloy_rlp::Error::InputTooShort);
584 };
585
586 let tempo_authorization_list = Decodable::decode(buf)?;
587
588 let key_authorization = if let Some(&first) = buf.first() {
594 if first >= 0xc0 {
596 Some(Decodable::decode(buf)?)
598 } else {
599 None
601 }
602 } else {
603 None
604 };
605
606 let tx = Self {
607 chain_id,
608 fee_token,
609 max_priority_fee_per_gas,
610 max_fee_per_gas,
611 gas_limit,
612 calls,
613 access_list,
614 nonce_key,
615 nonce,
616 fee_payer_signature,
617 valid_before,
618 valid_after,
619 key_authorization,
620 tempo_authorization_list,
621 };
622
623 tx.validate().map_err(alloy_rlp::Error::Custom)?;
625
626 Ok(tx)
627 }
628
629 pub fn has_sub_block_nonce_key_prefix(&self) -> bool {
631 has_sub_block_nonce_key_prefix(&self.nonce_key)
632 }
633
634 pub fn subblock_proposer(&self) -> Option<PartialValidatorKey> {
636 if self.has_sub_block_nonce_key_prefix() {
637 Some(PartialValidatorKey::from_slice(
638 &self.nonce_key.to_be_bytes::<32>()[1..16],
639 ))
640 } else {
641 None
642 }
643 }
644}
645
646impl Transaction for TempoTransaction {
647 #[inline]
648 fn chain_id(&self) -> Option<ChainId> {
649 Some(self.chain_id)
650 }
651
652 #[inline]
653 fn nonce(&self) -> u64 {
654 self.nonce
655 }
656
657 #[inline]
658 fn gas_limit(&self) -> u64 {
659 self.gas_limit
660 }
661
662 #[inline]
663 fn gas_price(&self) -> Option<u128> {
664 None
665 }
666
667 #[inline]
668 fn max_fee_per_gas(&self) -> u128 {
669 self.max_fee_per_gas
670 }
671
672 #[inline]
673 fn max_priority_fee_per_gas(&self) -> Option<u128> {
674 Some(self.max_priority_fee_per_gas)
675 }
676
677 #[inline]
678 fn max_fee_per_blob_gas(&self) -> Option<u128> {
679 None
680 }
681
682 #[inline]
683 fn priority_fee_or_price(&self) -> u128 {
684 self.max_priority_fee_per_gas
685 }
686
687 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
688 alloy_eips::eip1559::calc_effective_gas_price(
689 self.max_fee_per_gas,
690 self.max_priority_fee_per_gas,
691 base_fee,
692 )
693 }
694
695 #[inline]
696 fn is_dynamic_fee(&self) -> bool {
697 true
698 }
699
700 #[inline]
701 fn kind(&self) -> TxKind {
702 self.calls.first().map(|c| c.to).unwrap_or(TxKind::Create)
704 }
705
706 #[inline]
707 fn is_create(&self) -> bool {
708 self.kind().is_create()
709 }
710
711 #[inline]
712 fn value(&self) -> U256 {
713 self.calls
715 .iter()
716 .fold(U256::ZERO, |acc, call| acc.saturating_add(call.value))
717 }
718
719 #[inline]
720 fn input(&self) -> &Bytes {
721 static EMPTY_BYTES: Bytes = Bytes::new();
723 self.calls.first().map(|c| &c.input).unwrap_or(&EMPTY_BYTES)
724 }
725
726 #[inline]
727 fn access_list(&self) -> Option<&AccessList> {
728 Some(&self.access_list)
729 }
730
731 #[inline]
732 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
733 None
734 }
735
736 #[inline]
737 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
738 None
739 }
740}
741
742impl Typed2718 for TempoTransaction {
743 fn ty(&self) -> u8 {
744 TEMPO_TX_TYPE_ID
745 }
746}
747
748impl SignableTransaction<Signature> for TempoTransaction {
749 fn set_chain_id(&mut self, chain_id: ChainId) {
750 self.chain_id = chain_id;
751 }
752
753 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
754 let skip_fee_token = self.fee_payer_signature.is_some();
756
757 out.put_u8(Self::tx_type());
759
760 let payload_length = self.rlp_encoded_fields_length(|_| 1, skip_fee_token);
762
763 rlp_header(payload_length).encode(out);
764
765 self.rlp_encode_fields(
767 out,
768 |signature, out| {
769 if signature.is_some() {
770 out.put_u8(0); } else {
772 out.put_u8(EMPTY_STRING_CODE);
773 }
774 },
775 skip_fee_token,
776 );
777 }
778
779 fn payload_len_for_signature(&self) -> usize {
780 let skip_fee_token = self.fee_payer_signature.is_some();
781 let payload_length = self.rlp_encoded_fields_length(|_| 1, skip_fee_token);
782
783 1 + rlp_header(payload_length).length_with_payload()
784 }
785}
786
787impl Encodable for TempoTransaction {
788 fn encode(&self, out: &mut dyn BufMut) {
789 let payload_length = self.rlp_encoded_fields_length_default();
791 rlp_header(payload_length).encode(out);
792 self.rlp_encode_fields_default(out);
793 }
794
795 fn length(&self) -> usize {
796 let payload_length = self.rlp_encoded_fields_length_default();
797 rlp_header(payload_length).length_with_payload()
798 }
799}
800
801impl Decodable for TempoTransaction {
802 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
803 let header = alloy_rlp::Header::decode(buf)?;
804 if !header.list {
805 return Err(alloy_rlp::Error::UnexpectedString);
806 }
807 let remaining = buf.len();
808
809 if header.payload_length > remaining {
810 return Err(alloy_rlp::Error::InputTooShort);
811 }
812
813 let mut fields_buf = &buf[..header.payload_length];
814 let this = Self::rlp_decode_fields(&mut fields_buf)?;
815
816 if !fields_buf.is_empty() {
817 return Err(alloy_rlp::Error::UnexpectedLength);
818 }
819 buf.advance(header.payload_length);
820
821 Ok(this)
822 }
823}
824
825#[cfg(any(test, feature = "arbitrary"))]
827impl<'a> arbitrary::Arbitrary<'a> for TempoTransaction {
828 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
829 let chain_id = u.arbitrary()?;
831 let fee_token = u.arbitrary()?;
832 let max_priority_fee_per_gas = u.arbitrary()?;
833 let max_fee_per_gas = u.arbitrary()?;
834 let gas_limit = u.arbitrary()?;
835
836 let mut calls: Vec<Call> = u.arbitrary()?;
839 if calls.is_empty() {
840 calls.push(Call {
841 to: u.arbitrary()?,
842 value: u.arbitrary()?,
843 input: u.arbitrary()?,
844 });
845 }
846
847 let first_is_create = calls.first().map(|c| c.to.is_create()).unwrap_or(false);
849 if first_is_create {
850 for call in calls.iter_mut().skip(1) {
852 if call.to.is_create() {
853 call.to = TxKind::Call(u.arbitrary()?);
855 }
856 }
857 } else {
858 for call in &mut calls {
860 if call.to.is_create() {
861 call.to = TxKind::Call(u.arbitrary()?);
862 }
863 }
864 }
865
866 let access_list = u.arbitrary()?;
867
868 let nonce_key = U256::ZERO;
870 let nonce = u.arbitrary()?;
871 let fee_payer_signature = u.arbitrary()?;
872
873 let valid_after: Option<NonZeroU64> = u.arbitrary()?;
875 let valid_before: Option<NonZeroU64> = match valid_after {
876 Some(after) => {
877 let offset: u64 = u.int_in_range(1..=1000)?;
879 Some(NonZeroU64::new(after.get().saturating_add(offset)).unwrap())
880 }
881 None => u.arbitrary()?,
882 };
883
884 Ok(Self {
885 chain_id,
886 fee_token,
887 max_priority_fee_per_gas,
888 max_fee_per_gas,
889 gas_limit,
890 calls,
891 access_list,
892 nonce_key,
893 nonce,
894 fee_payer_signature,
895 valid_before,
896 valid_after,
897 key_authorization: u.arbitrary()?,
898 tempo_authorization_list: vec![],
899 })
900 }
901}
902
903#[cfg(feature = "serde")]
904mod serde_input {
905 use alloc::borrow::Cow;
908
909 use super::*;
910 use serde::{Deserialize, Deserializer, Serialize, Serializer};
911
912 #[derive(Serialize, Deserialize)]
913 struct SerdeHelper<'a> {
914 input: Option<Cow<'a, Bytes>>,
915 data: Option<Cow<'a, Bytes>>,
916 }
917
918 pub(super) fn serialize<S>(input: &Bytes, serializer: S) -> Result<S::Ok, S::Error>
919 where
920 S: Serializer,
921 {
922 SerdeHelper {
923 input: Some(Cow::Borrowed(input)),
924 data: None,
925 }
926 .serialize(serializer)
927 }
928
929 pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<Bytes, D::Error>
930 where
931 D: Deserializer<'de>,
932 {
933 let helper = SerdeHelper::deserialize(deserializer)?;
934 Ok(helper
935 .input
936 .or(helper.data)
937 .ok_or(serde::de::Error::missing_field(
938 "missing `input` or `data` field",
939 ))?
940 .into_owned())
941 }
942}
943
944#[cfg(test)]
945mod tests {
946 use super::*;
947 use crate::{
948 TempoTxEnvelope,
949 transaction::{
950 KeyAuthorization, TempoSignedAuthorization,
951 tt_signature::{
952 PrimitiveSignature, SIGNATURE_TYPE_P256, SIGNATURE_TYPE_WEBAUTHN, TempoSignature,
953 derive_p256_address,
954 },
955 },
956 };
957 use alloy_eips::{Decodable2718, Encodable2718, eip7702::Authorization};
958 use alloy_primitives::{Address, Bytes, Signature, TxKind, U256, address, bytes, hex};
959 use alloy_rlp::{Decodable, Encodable};
960
961 fn nz(value: u64) -> NonZeroU64 {
962 NonZeroU64::new(value).expect("test timestamp must be non-zero")
963 }
964
965 #[test]
966 fn test_tempo_transaction_validation() {
967 let dummy_call = Call {
969 to: TxKind::Create,
970 value: U256::ZERO,
971 input: Bytes::new(),
972 };
973
974 let tx1 = TempoTransaction {
976 valid_before: Some(nz(100)),
977 valid_after: Some(nz(50)),
978 tempo_authorization_list: vec![],
979 calls: vec![dummy_call.clone()],
980 ..Default::default()
981 };
982 assert!(tx1.validate().is_ok());
983
984 let tx2 = TempoTransaction {
986 valid_before: Some(nz(50)),
987 valid_after: Some(nz(100)),
988 tempo_authorization_list: vec![],
989 calls: vec![dummy_call.clone()],
990 ..Default::default()
991 };
992 assert!(tx2.validate().is_err());
993
994 let tx3 = TempoTransaction {
996 valid_before: Some(nz(100)),
997 valid_after: Some(nz(100)),
998 tempo_authorization_list: vec![],
999 calls: vec![dummy_call.clone()],
1000 ..Default::default()
1001 };
1002 assert!(tx3.validate().is_err());
1003
1004 let tx4 = TempoTransaction {
1006 valid_before: Some(nz(100)),
1007 valid_after: None,
1008 tempo_authorization_list: vec![],
1009 calls: vec![dummy_call],
1010 ..Default::default()
1011 };
1012 assert!(tx4.validate().is_ok());
1013
1014 let tx5 = TempoTransaction {
1016 ..Default::default()
1017 };
1018 assert!(tx5.validate().is_err());
1019 }
1020
1021 #[test]
1022 fn test_tx_type() {
1023 assert_eq!(TempoTransaction::tx_type(), 0x76);
1024 assert_eq!(TEMPO_TX_TYPE_ID, 0x76);
1025 }
1026
1027 #[test]
1028 fn test_signature_type_detection() {
1029 let sig1_bytes = vec![0u8; SECP256K1_SIGNATURE_LENGTH];
1031 let sig1 = TempoSignature::from_bytes(&sig1_bytes).unwrap();
1032 assert_eq!(sig1.signature_type(), SignatureType::Secp256k1);
1033
1034 let mut sig2_bytes = vec![SIGNATURE_TYPE_P256];
1036 sig2_bytes.extend_from_slice(&[0u8; P256_SIGNATURE_LENGTH]);
1037 let sig2 = TempoSignature::from_bytes(&sig2_bytes).unwrap();
1038 assert_eq!(sig2.signature_type(), SignatureType::P256);
1039
1040 let mut sig3_bytes = vec![SIGNATURE_TYPE_WEBAUTHN];
1042 sig3_bytes.extend_from_slice(&[0u8; 200]);
1043 let sig3 = TempoSignature::from_bytes(&sig3_bytes).unwrap();
1044 assert_eq!(sig3.signature_type(), SignatureType::WebAuthn);
1045 }
1046
1047 #[test]
1048 fn test_rlp_roundtrip() {
1049 let call = Call {
1050 to: TxKind::Call(address!("0000000000000000000000000000000000000002")),
1051 value: U256::from(1000),
1052 input: Bytes::from(vec![1, 2, 3, 4]),
1053 };
1054
1055 let tx = TempoTransaction {
1056 chain_id: 1,
1057 fee_token: Some(address!("0000000000000000000000000000000000000001")),
1058 max_priority_fee_per_gas: 1000000000,
1059 max_fee_per_gas: 2000000000,
1060 gas_limit: 21000,
1061 calls: vec![call.clone()],
1062 access_list: Default::default(),
1063 nonce_key: U256::ZERO,
1064 nonce: 1,
1065 fee_payer_signature: Some(Signature::test_signature()),
1066 valid_before: Some(nz(1000000)),
1067 valid_after: Some(nz(500000)),
1068 key_authorization: None,
1069 tempo_authorization_list: vec![],
1070 };
1071
1072 let mut buf = Vec::new();
1074 tx.encode(&mut buf);
1075
1076 let decoded = TempoTransaction::decode(&mut buf.as_slice()).unwrap();
1078
1079 assert_eq!(decoded.chain_id, tx.chain_id);
1081 assert_eq!(decoded.fee_token, tx.fee_token);
1082 assert_eq!(
1083 decoded.max_priority_fee_per_gas,
1084 tx.max_priority_fee_per_gas
1085 );
1086 assert_eq!(decoded.max_fee_per_gas, tx.max_fee_per_gas);
1087 assert_eq!(decoded.gas_limit, tx.gas_limit);
1088 assert_eq!(decoded.calls.len(), 1);
1089 assert_eq!(decoded.calls[0].to, call.to);
1090 assert_eq!(decoded.calls[0].value, call.value);
1091 assert_eq!(decoded.calls[0].input, call.input);
1092 assert_eq!(decoded.nonce_key, tx.nonce_key);
1093 assert_eq!(decoded.nonce, tx.nonce);
1094 assert_eq!(decoded.valid_before, tx.valid_before);
1095 assert_eq!(decoded.valid_after, tx.valid_after);
1096 assert_eq!(decoded.fee_payer_signature, tx.fee_payer_signature);
1097 }
1098
1099 #[test]
1100 fn test_encode_for_fee_payer_service_uses_signature_placeholder_and_skips_fee_token() {
1101 let call = Call {
1102 to: TxKind::Call(Address::random()),
1103 value: U256::ZERO,
1104 input: Bytes::from(vec![1, 2, 3, 4]),
1105 };
1106
1107 let tx = TempoTransaction {
1108 chain_id: 1,
1109 fee_token: None,
1110 max_priority_fee_per_gas: 1000000000,
1111 max_fee_per_gas: 2000000000,
1112 gas_limit: 21000,
1113 calls: vec![call],
1114 access_list: Default::default(),
1115 nonce_key: U256::ZERO,
1116 nonce: 1,
1117 fee_payer_signature: None,
1118 valid_before: Some(nz(1000000)),
1119 valid_after: Some(nz(500000)),
1120 key_authorization: None,
1121 tempo_authorization_list: vec![],
1122 };
1123
1124 let mut service_encoded = Vec::new();
1125 tx.encode_for_fee_payer_service(&mut service_encoded);
1126
1127 assert_eq!(service_encoded[0], TEMPO_TX_TYPE_ID);
1128
1129 let mut signing_tx = tx.clone();
1130 signing_tx.fee_payer_signature = Some(Signature::new(U256::ZERO, U256::ZERO, false));
1131 let mut signing_encoded = Vec::new();
1132 signing_tx.encode_for_signing(&mut signing_encoded);
1133 assert_eq!(service_encoded, signing_encoded);
1134
1135 let mut tx_with_different_fee_token = tx;
1136 tx_with_different_fee_token.fee_token = Some(Address::random());
1137 let mut different_fee_token_encoded = Vec::new();
1138 tx_with_different_fee_token.encode_for_fee_payer_service(&mut different_fee_token_encoded);
1139 assert_eq!(service_encoded, different_fee_token_encoded);
1140 }
1141
1142 #[test]
1143 fn test_rlp_roundtrip_no_optional_fields() {
1144 let call = Call {
1145 to: TxKind::Call(address!("0000000000000000000000000000000000000002")),
1146 value: U256::from(1000),
1147 input: Bytes::new(),
1148 };
1149
1150 let tx = TempoTransaction {
1151 chain_id: 1,
1152 fee_token: None,
1153 max_priority_fee_per_gas: 1000000000,
1154 max_fee_per_gas: 2000000000,
1155 gas_limit: 21000,
1156 calls: vec![call],
1157 access_list: Default::default(),
1158 nonce_key: U256::ZERO,
1159 nonce: 1,
1160 fee_payer_signature: None,
1161 valid_before: Some(nz(1000)),
1162 valid_after: None,
1163 key_authorization: None,
1164 tempo_authorization_list: vec![],
1165 };
1166
1167 let mut buf = Vec::new();
1169 tx.encode(&mut buf);
1170
1171 let decoded = TempoTransaction::decode(&mut buf.as_slice()).unwrap();
1173
1174 assert_eq!(decoded.chain_id, tx.chain_id);
1176 assert_eq!(decoded.fee_token, None);
1177 assert_eq!(decoded.fee_payer_signature, None);
1178 assert_eq!(decoded.valid_after, None);
1179 assert_eq!(decoded.calls.len(), 1);
1180 }
1181
1182 #[test]
1183 fn test_p256_address_derivation() {
1184 let pub_key_x =
1185 hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1186 let pub_key_y =
1187 hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1188
1189 let addr1 = derive_p256_address(&pub_key_x, &pub_key_y);
1190 let addr2 = derive_p256_address(&pub_key_x, &pub_key_y);
1191
1192 assert_eq!(addr1, addr2);
1194
1195 assert_ne!(addr1, Address::ZERO);
1197 }
1198
1199 #[test]
1200 fn test_nonce_system() {
1201 let dummy_call = Call {
1203 to: TxKind::Create,
1204 value: U256::ZERO,
1205 input: Bytes::new(),
1206 };
1207
1208 let tx1 = TempoTransaction {
1210 nonce_key: U256::ZERO,
1211 nonce: 1,
1212 calls: vec![dummy_call.clone()],
1213 ..Default::default()
1214 };
1215 assert!(tx1.validate().is_ok());
1216 assert_eq!(tx1.nonce(), 1);
1217 assert_eq!(tx1.nonce_key, U256::ZERO);
1218
1219 let tx2 = TempoTransaction {
1221 nonce_key: U256::from(1),
1222 nonce: 0,
1223 calls: vec![dummy_call.clone()],
1224 ..Default::default()
1225 };
1226 assert!(tx2.validate().is_ok());
1227 assert_eq!(tx2.nonce(), 0);
1228 assert_eq!(tx2.nonce_key, U256::from(1));
1229
1230 let tx3 = TempoTransaction {
1232 nonce_key: U256::from(42),
1233 nonce: 10,
1234 calls: vec![dummy_call.clone()],
1235 ..Default::default()
1236 };
1237 assert!(tx3.validate().is_ok());
1238 assert_eq!(tx3.nonce(), 10);
1239 assert_eq!(tx3.nonce_key, U256::from(42));
1240
1241 let tx4a = TempoTransaction {
1244 nonce_key: U256::from(1),
1245 nonce: 100,
1246 calls: vec![dummy_call.clone()],
1247 ..Default::default()
1248 };
1249 let tx4b = TempoTransaction {
1250 nonce_key: U256::from(2),
1251 nonce: 100,
1252 calls: vec![dummy_call],
1253 ..Default::default()
1254 };
1255 assert!(tx4a.validate().is_ok());
1256 assert!(tx4b.validate().is_ok());
1257 assert_eq!(tx4a.nonce(), tx4b.nonce()); assert_ne!(tx4a.nonce_key, tx4b.nonce_key); }
1260
1261 #[test]
1262 fn test_transaction_trait_impl() {
1263 let call = Call {
1264 to: TxKind::Call(address!("0000000000000000000000000000000000000002")),
1265 value: U256::from(1000),
1266 input: Bytes::new(),
1267 };
1268
1269 let tx = TempoTransaction {
1270 chain_id: 1,
1271 max_priority_fee_per_gas: 1000000000,
1272 max_fee_per_gas: 2000000000,
1273 gas_limit: 21000,
1274 calls: vec![call],
1275 ..Default::default()
1276 };
1277
1278 assert_eq!(tx.chain_id(), Some(1));
1279 assert_eq!(tx.gas_limit(), 21000);
1280 assert_eq!(tx.max_fee_per_gas(), 2000000000);
1281 assert_eq!(tx.max_priority_fee_per_gas(), Some(1000000000));
1282 assert_eq!(tx.value(), U256::from(1000));
1283 assert!(tx.is_dynamic_fee());
1284 assert!(!tx.is_create());
1285 }
1286
1287 #[test]
1288 fn test_effective_gas_price() {
1289 let dummy_call = Call {
1291 to: TxKind::Create,
1292 value: U256::ZERO,
1293 input: Bytes::new(),
1294 };
1295
1296 let tx = TempoTransaction {
1297 max_priority_fee_per_gas: 1000000000,
1298 max_fee_per_gas: 2000000000,
1299 calls: vec![dummy_call],
1300 ..Default::default()
1301 };
1302
1303 let effective1 = tx.effective_gas_price(Some(500000000));
1305 assert_eq!(effective1, 1500000000); let effective2 = tx.effective_gas_price(None);
1309 assert_eq!(effective2, 2000000000); }
1311
1312 #[test]
1313 fn test_fee_payer_commits_to_fee_token() {
1314 let sender = address!("0000000000000000000000000000000000000001");
1318 let token1 = address!("0000000000000000000000000000000000000002");
1319 let token2 = address!("0000000000000000000000000000000000000003");
1320
1321 let dummy_call = Call {
1322 to: TxKind::Create,
1323 value: U256::ZERO,
1324 input: Bytes::new(),
1325 };
1326
1327 let tx_no_token = TempoTransaction {
1329 chain_id: 1,
1330 fee_token: None,
1331 max_priority_fee_per_gas: 1000000000,
1332 max_fee_per_gas: 2000000000,
1333 gas_limit: 21000,
1334 calls: vec![dummy_call],
1335 nonce_key: U256::ZERO,
1336 nonce: 1,
1337 fee_payer_signature: Some(Signature::test_signature()),
1338 valid_before: Some(nz(1000)),
1339 valid_after: None,
1340 ..Default::default()
1341 };
1342
1343 let tx_token1 = TempoTransaction {
1345 fee_token: Some(token1),
1346 ..tx_no_token.clone()
1347 };
1348
1349 let tx_token2 = TempoTransaction {
1351 fee_token: Some(token2),
1352 ..tx_no_token.clone()
1353 };
1354
1355 let fee_payer_hash_no_token = tx_no_token.fee_payer_signature_hash(sender);
1357 let fee_payer_hash_token1 = tx_token1.fee_payer_signature_hash(sender);
1358 let fee_payer_hash_token2 = tx_token2.fee_payer_signature_hash(sender);
1359
1360 assert_ne!(
1362 fee_payer_hash_no_token, fee_payer_hash_token1,
1363 "Fee payer hash should change when fee_token changes from None to Some"
1364 );
1365 assert_ne!(
1366 fee_payer_hash_token1, fee_payer_hash_token2,
1367 "Fee payer hash should change when fee_token changes from token1 to token2"
1368 );
1369 assert_ne!(
1370 fee_payer_hash_no_token, fee_payer_hash_token2,
1371 "Fee payer hash should be different for None vs token2"
1372 );
1373
1374 let user_hash_no_token = tx_no_token.signature_hash();
1376 let user_hash_token1 = tx_token1.signature_hash();
1377 let user_hash_token2 = tx_token2.signature_hash();
1378
1379 assert_eq!(
1381 user_hash_no_token, user_hash_token1,
1382 "User hash should be the same regardless of fee_token (user skips fee_token)"
1383 );
1384 assert_eq!(
1385 user_hash_token1, user_hash_token2,
1386 "User hash should be the same regardless of fee_token (user skips fee_token)"
1387 );
1388 assert_eq!(
1389 user_hash_no_token, user_hash_token2,
1390 "User hash should be the same regardless of fee_token (user skips fee_token)"
1391 );
1392 }
1393
1394 #[test]
1395 fn test_fee_payer_signature_uses_magic_byte() {
1396 let sender = address!("0000000000000000000000000000000000000001");
1399 let dummy_call = Call {
1400 to: TxKind::Create,
1401 value: U256::ZERO,
1402 input: Bytes::new(),
1403 };
1404
1405 let tx = TempoTransaction {
1406 chain_id: 1,
1407 fee_token: None,
1408 max_priority_fee_per_gas: 1000000000,
1409 max_fee_per_gas: 2000000000,
1410 gas_limit: 21000,
1411 calls: vec![dummy_call],
1412 nonce_key: U256::ZERO,
1413 nonce: 1,
1414 fee_payer_signature: Some(Signature::test_signature()),
1415 valid_before: Some(nz(1000)),
1416 ..Default::default()
1417 };
1418
1419 let sender_hash = tx.signature_hash();
1423 let fee_payer_hash = tx.fee_payer_signature_hash(sender);
1424
1425 assert_ne!(
1427 sender_hash, fee_payer_hash,
1428 "Sender and fee payer hashes should be different (different magic bytes)"
1429 );
1430 }
1431
1432 #[test]
1433 fn test_user_signature_without_fee_payer() {
1434 let token1 = address!("0000000000000000000000000000000000000002");
1437 let token2 = address!("0000000000000000000000000000000000000003");
1438
1439 let dummy_call = Call {
1440 to: TxKind::Create,
1441 value: U256::ZERO,
1442 input: Bytes::new(),
1443 };
1444
1445 let tx_no_payer_no_token = TempoTransaction {
1447 chain_id: 1,
1448 fee_token: None,
1449 max_priority_fee_per_gas: 1000000000,
1450 max_fee_per_gas: 2000000000,
1451 gas_limit: 21000,
1452 calls: vec![dummy_call],
1453 nonce_key: U256::ZERO,
1454 nonce: 1,
1455 fee_payer_signature: None, valid_before: Some(nz(1000)),
1457 valid_after: None,
1458 tempo_authorization_list: vec![],
1459 access_list: Default::default(),
1460 key_authorization: None,
1461 };
1462
1463 let tx_no_payer_token1 = TempoTransaction {
1465 fee_token: Some(token1),
1466 ..tx_no_payer_no_token.clone()
1467 };
1468
1469 let tx_no_payer_token2 = TempoTransaction {
1471 fee_token: Some(token2),
1472 ..tx_no_payer_no_token.clone()
1473 };
1474
1475 let hash_no_token = tx_no_payer_no_token.signature_hash();
1477 let hash_token1 = tx_no_payer_token1.signature_hash();
1478 let hash_token2 = tx_no_payer_token2.signature_hash();
1479
1480 assert_ne!(
1482 hash_no_token, hash_token1,
1483 "User hash should change when fee_token changes (no fee_payer)"
1484 );
1485 assert_ne!(
1486 hash_token1, hash_token2,
1487 "User hash should change when fee_token changes (no fee_payer)"
1488 );
1489 assert_ne!(
1490 hash_no_token, hash_token2,
1491 "User hash should change when fee_token changes (no fee_payer)"
1492 );
1493 }
1494
1495 #[test]
1496 fn test_rlp_encoding_includes_fee_token() {
1497 let token = address!("0000000000000000000000000000000000000002");
1500
1501 let dummy_call = Call {
1502 to: TxKind::Create,
1503 value: U256::ZERO,
1504 input: Bytes::new(),
1505 };
1506
1507 let tx_with_token = TempoTransaction {
1509 chain_id: 1,
1510 fee_token: Some(token),
1511 max_priority_fee_per_gas: 1000000000,
1512 max_fee_per_gas: 2000000000,
1513 gas_limit: 21000,
1514 calls: vec![dummy_call],
1515 nonce_key: U256::ZERO,
1516 nonce: 1,
1517 fee_payer_signature: Some(Signature::test_signature()),
1518 valid_before: Some(nz(1000)),
1519 valid_after: None,
1520 tempo_authorization_list: vec![],
1521 access_list: Default::default(),
1522 key_authorization: None,
1523 };
1524
1525 let tx_without_token = TempoTransaction {
1527 fee_token: None,
1528 ..tx_with_token.clone()
1529 };
1530
1531 let mut buf_with = Vec::new();
1533 tx_with_token.encode(&mut buf_with);
1534
1535 let mut buf_without = Vec::new();
1536 tx_without_token.encode(&mut buf_without);
1537
1538 assert_ne!(
1540 buf_with.len(),
1541 buf_without.len(),
1542 "RLP encoding should include fee_token in the encoded data"
1543 );
1544
1545 assert!(
1547 buf_with.len() > buf_without.len(),
1548 "Transaction with fee_token should have longer encoding"
1549 );
1550
1551 let decoded_with = TempoTransaction::decode(&mut buf_with.as_slice()).unwrap();
1553 let decoded_without = TempoTransaction::decode(&mut buf_without.as_slice()).unwrap();
1554
1555 assert_eq!(decoded_with.fee_token, Some(token));
1556 assert_eq!(decoded_without.fee_token, None);
1557 }
1558
1559 #[test]
1560 fn test_signature_hash_behavior_with_and_without_fee_payer() {
1561 let token = address!("0000000000000000000000000000000000000002");
1564
1565 let dummy_call = Call {
1566 to: TxKind::Create,
1567 value: U256::ZERO,
1568 input: Bytes::new(),
1569 };
1570
1571 let tx_no_payer_no_token = TempoTransaction {
1573 chain_id: 1,
1574 fee_token: None,
1575 max_priority_fee_per_gas: 1000000000,
1576 max_fee_per_gas: 2000000000,
1577 gas_limit: 21000,
1578 calls: vec![dummy_call],
1579 nonce_key: U256::ZERO,
1580 nonce: 1,
1581 fee_payer_signature: None,
1582 valid_before: Some(nz(1000)),
1583 valid_after: None,
1584 tempo_authorization_list: vec![],
1585 access_list: Default::default(),
1586 key_authorization: None,
1587 };
1588
1589 let tx_no_payer_with_token = TempoTransaction {
1591 fee_token: Some(token),
1592 ..tx_no_payer_no_token.clone()
1593 };
1594
1595 let tx_with_payer_no_token = TempoTransaction {
1597 fee_payer_signature: Some(Signature::test_signature()),
1598 ..tx_no_payer_no_token.clone()
1599 };
1600
1601 let tx_with_payer_with_token = TempoTransaction {
1603 fee_token: Some(token),
1604 fee_payer_signature: Some(Signature::test_signature()),
1605 ..tx_no_payer_no_token.clone()
1606 };
1607
1608 let hash1 = tx_no_payer_no_token.signature_hash();
1610 let hash2 = tx_no_payer_with_token.signature_hash();
1611 let hash3 = tx_with_payer_no_token.signature_hash();
1612 let hash4 = tx_with_payer_with_token.signature_hash();
1613
1614 assert_ne!(
1616 hash1, hash2,
1617 "User hash changes with fee_token when no fee_payer"
1618 );
1619
1620 assert_eq!(
1622 hash3, hash4,
1623 "User hash ignores fee_token when fee_payer is present"
1624 );
1625
1626 assert_ne!(hash1, hash3, "User hash changes when fee_payer is added");
1629 }
1630
1631 #[test]
1632 fn test_backwards_compatibility_key_authorization() {
1633 let call = Call {
1637 to: TxKind::Call(address!("0000000000000000000000000000000000000002")),
1638 value: U256::from(1000),
1639 input: Bytes::from(vec![1, 2, 3, 4]),
1640 };
1641
1642 let tx_without = TempoTransaction {
1644 chain_id: 1,
1645 fee_token: Some(address!("0000000000000000000000000000000000000001")),
1646 max_priority_fee_per_gas: 1000000000,
1647 max_fee_per_gas: 2000000000,
1648 gas_limit: 21000,
1649 calls: vec![call],
1650 access_list: Default::default(),
1651 nonce_key: U256::ZERO,
1652 nonce: 1,
1653 fee_payer_signature: Some(Signature::test_signature()),
1654 valid_before: Some(nz(1000000)),
1655 valid_after: Some(nz(500000)),
1656 key_authorization: None, tempo_authorization_list: vec![],
1658 };
1659
1660 let mut buf_without = Vec::new();
1662 tx_without.encode(&mut buf_without);
1663
1664 let decoded_without = TempoTransaction::decode(&mut buf_without.as_slice()).unwrap();
1666
1667 assert_eq!(decoded_without.key_authorization, None);
1669 assert_eq!(decoded_without.chain_id, tx_without.chain_id);
1670 assert_eq!(decoded_without.calls.len(), tx_without.calls.len());
1671
1672 let key_auth = KeyAuthorization::unrestricted(
1674 1,
1675 SignatureType::Secp256k1,
1676 address!("0000000000000000000000000000000000000004"),
1677 )
1678 .with_expiry(1234567890)
1679 .with_limits(vec![crate::transaction::TokenLimit {
1680 token: address!("0000000000000000000000000000000000000003"),
1681 limit: U256::from(10000),
1682 period: 0,
1683 }])
1684 .into_signed(PrimitiveSignature::Secp256k1(Signature::test_signature()));
1685
1686 let tx_with = TempoTransaction {
1687 key_authorization: Some(key_auth.clone()),
1688 ..tx_without.clone()
1689 };
1690
1691 let mut buf_with = Vec::new();
1693 tx_with.encode(&mut buf_with);
1694
1695 let decoded_with = TempoTransaction::decode(&mut buf_with.as_slice()).unwrap();
1697
1698 assert!(decoded_with.key_authorization.is_some());
1700 let decoded_key_auth = decoded_with.key_authorization.unwrap();
1701 assert_eq!(decoded_key_auth.key_type, key_auth.key_type);
1702 assert_eq!(decoded_key_auth.expiry, key_auth.expiry);
1703 assert_eq!(
1704 decoded_key_auth.limits.as_ref().map(|l| l.len()),
1705 key_auth.limits.as_ref().map(|l| l.len())
1706 );
1707 assert_eq!(decoded_key_auth.key_id, key_auth.key_id);
1708
1709 assert!(
1712 buf_without.len() < buf_with.len(),
1713 "Transaction without key_authorization should have shorter encoding"
1714 );
1715
1716 let decoded_old_format = TempoTransaction::decode(&mut buf_without.as_slice()).unwrap();
1720 assert_eq!(decoded_old_format.key_authorization, None);
1721 }
1722
1723 #[test]
1724 fn test_aa_signed_rlp_direct() {
1725 let call = Call {
1727 to: TxKind::Create,
1728 value: U256::ZERO,
1729 input: Bytes::new(),
1730 };
1731
1732 let tx = TempoTransaction {
1733 chain_id: 0,
1734 fee_token: None,
1735 max_priority_fee_per_gas: 0,
1736 max_fee_per_gas: 0,
1737 gas_limit: 0,
1738 calls: vec![call],
1739 access_list: Default::default(),
1740 nonce_key: U256::ZERO,
1741 nonce: 0,
1742 fee_payer_signature: None,
1743 valid_before: None,
1744 valid_after: None,
1745 key_authorization: None, tempo_authorization_list: vec![],
1747 };
1748
1749 let signature =
1750 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature()));
1751 let signed = AASigned::new_unhashed(tx, signature);
1752
1753 let mut buf = Vec::new();
1755 signed.rlp_encode(&mut buf);
1756
1757 let decoded =
1759 AASigned::rlp_decode(&mut buf.as_slice()).expect("Should decode AASigned RLP");
1760 assert_eq!(decoded.tx().key_authorization, None);
1761 }
1762
1763 #[test]
1764 fn test_tempo_transaction_envelope_roundtrip_without_key_auth() {
1765 let call = Call {
1767 to: TxKind::Create,
1768 value: U256::ZERO,
1769 input: Bytes::new(),
1770 };
1771
1772 let tx = TempoTransaction {
1773 chain_id: 0,
1774 fee_token: None,
1775 max_priority_fee_per_gas: 0,
1776 max_fee_per_gas: 0,
1777 gas_limit: 0,
1778 calls: vec![call],
1779 access_list: Default::default(),
1780 nonce_key: U256::ZERO,
1781 nonce: 0,
1782 fee_payer_signature: None,
1783 valid_before: None,
1784 valid_after: None,
1785 key_authorization: None, tempo_authorization_list: vec![],
1787 };
1788
1789 let signature =
1790 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature()));
1791 let signed = AASigned::new_unhashed(tx, signature);
1792 let envelope = TempoTxEnvelope::AA(signed);
1793
1794 let mut buf = Vec::new();
1796 envelope.encode_2718(&mut buf);
1797 let decoded = TempoTxEnvelope::decode_2718(&mut buf.as_slice())
1798 .expect("Should decode envelope successfully");
1799
1800 if let TempoTxEnvelope::AA(aa_signed) = decoded {
1802 assert_eq!(aa_signed.tx().key_authorization, None);
1803 assert_eq!(aa_signed.tx().calls.len(), 1);
1804 assert_eq!(aa_signed.tx().chain_id, 0);
1805 } else {
1806 panic!("Expected AA envelope");
1807 }
1808 }
1809
1810 #[test]
1811 fn test_call_decode_rejects_malformed_rlp() {
1812 let call = Call {
1814 to: TxKind::Call(Address::random()),
1815 value: U256::random(),
1816 input: Bytes::from(vec![1, 2, 3, 4]),
1817 };
1818
1819 let mut buf = Vec::new();
1821 call.encode(&mut buf);
1822
1823 let original_len = buf.len();
1826 buf.truncate(original_len - 2); let result = Call::decode(&mut buf.as_slice());
1829 assert!(
1830 result.is_err(),
1831 "Decoding should fail when header length doesn't match"
1832 );
1833 assert!(matches!(
1835 result.unwrap_err(),
1836 alloy_rlp::Error::InputTooShort | alloy_rlp::Error::UnexpectedLength
1837 ));
1838 }
1839
1840 #[test]
1841 fn test_tempo_transaction_decode_rejects_malformed_rlp() {
1842 let call = Call {
1844 to: TxKind::Call(Address::random()),
1845 value: U256::random(),
1846 input: Bytes::from(vec![1, 2, 3, 4]),
1847 };
1848
1849 let tx = TempoTransaction {
1850 chain_id: 1,
1851 fee_token: Some(Address::random()),
1852 max_priority_fee_per_gas: 1000000000,
1853 max_fee_per_gas: 2000000000,
1854 gas_limit: 21000,
1855 calls: vec![call],
1856 access_list: Default::default(),
1857 nonce_key: U256::ZERO,
1858 nonce: 1,
1859 fee_payer_signature: Some(Signature::test_signature()),
1860 valid_before: Some(nz(1000000)),
1861 valid_after: Some(nz(500000)),
1862 key_authorization: None,
1863 tempo_authorization_list: vec![],
1864 };
1865
1866 let mut buf = Vec::new();
1868 tx.encode(&mut buf);
1869
1870 let original_len = buf.len();
1872 buf.truncate(original_len - 5); let result = TempoTransaction::decode(&mut buf.as_slice());
1875 assert!(
1876 result.is_err(),
1877 "Decoding should fail when data is truncated"
1878 );
1879 assert!(matches!(
1881 result.unwrap_err(),
1882 alloy_rlp::Error::InputTooShort | alloy_rlp::Error::UnexpectedLength
1883 ));
1884 }
1885
1886 #[test]
1887 #[cfg(feature = "serde")]
1888 fn call_serde() {
1889 let call: Call = serde_json::from_str(
1890 r#"{"to":"0x0000000000000000000000000000000000000002","value":"0x1","input":"0x1234"}"#,
1891 )
1892 .unwrap();
1893 assert_eq!(
1894 call.to,
1895 TxKind::Call(address!("0000000000000000000000000000000000000002"))
1896 );
1897 assert_eq!(call.value, U256::ONE);
1898 assert_eq!(call.input, bytes!("0x1234"));
1899 }
1900
1901 #[test]
1902 fn test_create_must_be_first_call() {
1903 let create_call = Call {
1904 to: TxKind::Create,
1905 value: U256::ZERO,
1906 input: Bytes::new(),
1907 };
1908 let call_call = Call {
1909 to: TxKind::Call(Address::random()),
1910 value: U256::ZERO,
1911 input: Bytes::new(),
1912 };
1913
1914 let tx_valid = TempoTransaction {
1916 calls: vec![create_call.clone(), call_call.clone()],
1917 ..Default::default()
1918 };
1919 assert!(tx_valid.validate().is_ok());
1920
1921 let tx_invalid = TempoTransaction {
1923 calls: vec![call_call, create_call],
1924 ..Default::default()
1925 };
1926 assert!(tx_invalid.validate().is_err());
1927 assert!(tx_invalid.validate().unwrap_err().contains("first call"));
1928 }
1929
1930 #[test]
1931 fn test_only_one_create_allowed() {
1932 let create_call = Call {
1933 to: TxKind::Create,
1934 value: U256::ZERO,
1935 input: Bytes::new(),
1936 };
1937
1938 let tx_valid = TempoTransaction {
1940 calls: vec![create_call.clone()],
1941 ..Default::default()
1942 };
1943 assert!(tx_valid.validate().is_ok());
1944
1945 let tx_invalid = TempoTransaction {
1947 calls: vec![create_call.clone(), create_call],
1948 ..Default::default()
1949 };
1950 assert!(tx_invalid.validate().is_err());
1951 assert!(
1952 tx_invalid
1953 .validate()
1954 .unwrap_err()
1955 .contains("only one CREATE")
1956 );
1957 }
1958
1959 #[test]
1960 fn test_create_forbidden_with_auth_list() {
1961 let create_call = Call {
1962 to: TxKind::Create,
1963 value: U256::ZERO,
1964 input: Bytes::new(),
1965 };
1966
1967 let signed_auth = TempoSignedAuthorization::new_unchecked(
1968 Authorization {
1969 chain_id: U256::ONE,
1970 address: Address::random(),
1971 nonce: 1,
1972 },
1973 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature())),
1974 );
1975
1976 let tx = TempoTransaction {
1978 calls: vec![create_call],
1979 tempo_authorization_list: vec![signed_auth],
1980 ..Default::default()
1981 };
1982
1983 let result = tx.validate();
1984 assert!(result.is_err());
1985 assert!(result.unwrap_err().contains("aa_authorization_list"));
1986 }
1987
1988 #[test]
1989 fn test_create_validation_allows_call_only_batch() {
1990 let call1 = Call {
1992 to: TxKind::Call(Address::random()),
1993 value: U256::ZERO,
1994 input: Bytes::new(),
1995 };
1996 let call2 = Call {
1997 to: TxKind::Call(Address::random()),
1998 value: U256::random(),
1999 input: Bytes::from(vec![1, 2, 3]),
2000 };
2001
2002 let tx = TempoTransaction {
2003 calls: vec![call1, call2],
2004 ..Default::default()
2005 };
2006 assert!(tx.validate().is_ok());
2007 }
2008
2009 #[test]
2010 fn test_value_saturates_on_overflow() {
2011 let call1 = Call {
2012 to: TxKind::Call(Address::ZERO),
2013 value: U256::MAX,
2014 input: Bytes::new(),
2015 };
2016 let call2 = Call {
2017 to: TxKind::Call(Address::ZERO),
2018 value: U256::from(1),
2019 input: Bytes::new(),
2020 };
2021
2022 let tx = TempoTransaction {
2023 calls: vec![call1, call2],
2024 ..Default::default()
2025 };
2026
2027 assert_eq!(tx.value(), U256::MAX);
2028 }
2029
2030 #[test]
2031 fn test_validate_does_not_check_expiring_nonce_constraints() {
2032 let dummy_call = Call {
2033 to: TxKind::Call(Address::ZERO),
2034 value: U256::ZERO,
2035 input: Bytes::new(),
2036 };
2037
2038 let tx_with_nonzero_nonce = TempoTransaction {
2041 nonce_key: TEMPO_EXPIRING_NONCE_KEY,
2042 nonce: 42,
2043 valid_before: None,
2044 calls: vec![dummy_call.clone()],
2045 ..Default::default()
2046 };
2047 assert!(
2048 tx_with_nonzero_nonce.validate().is_ok(),
2049 "validate() should not enforce expiring nonce constraints (hardfork-dependent)"
2050 );
2051
2052 let tx_without_valid_before = TempoTransaction {
2054 nonce_key: TEMPO_EXPIRING_NONCE_KEY,
2055 nonce: 0,
2056 valid_before: None,
2057 calls: vec![dummy_call.clone()],
2058 ..Default::default()
2059 };
2060 assert!(
2061 tx_without_valid_before.validate().is_ok(),
2062 "validate() should not enforce expiring nonce constraints (hardfork-dependent)"
2063 );
2064
2065 let valid_expiring_tx = TempoTransaction {
2067 nonce_key: TEMPO_EXPIRING_NONCE_KEY,
2068 nonce: 0,
2069 valid_before: Some(nz(1000)),
2070 calls: vec![dummy_call],
2071 ..Default::default()
2072 };
2073 assert!(valid_expiring_tx.validate().is_ok());
2074 }
2075}
2076
2077#[cfg(all(test, feature = "reth-codec"))]
2078mod compact_tests {
2079 use super::*;
2080 use crate::transaction::{
2081 KeyAuthorization, TempoSignedAuthorization, TokenLimit,
2082 tt_signature::{P256SignatureWithPreHash, PrimitiveSignature, TempoSignature},
2083 };
2084 use alloy_eips::{eip2930::AccessListItem, eip7702::Authorization};
2085 use alloy_primitives::{Signature, U256, address, b256, bytes, hex};
2086 use reth_codecs::Compact;
2087
2088 #[test]
2093 fn compact_types_have_unused_bits() {
2094 assert_ne!(
2095 TempoTransaction::bitflag_unused_bits(),
2096 0,
2097 "TempoTransaction"
2098 );
2099 assert_ne!(Call::bitflag_unused_bits(), 0, "Call");
2100 }
2101
2102 #[test]
2103 fn call_compact_roundtrip() {
2104 let call = Call {
2105 to: TxKind::Call(address!("0x0000000000000000000000000000000000000001")),
2106 value: U256::from(1000u64),
2107 input: bytes!("deadbeef"),
2108 };
2109
2110 let expected = hex!("05000000000000000000000000000000000000000103e8deadbeef");
2111
2112 let mut buf = vec![];
2113 let len = call.to_compact(&mut buf);
2114 assert_eq!(buf, expected, "Call compact encoding changed");
2115 assert_eq!(len, expected.len());
2116
2117 let (decoded, _) = Call::from_compact(&expected, expected.len());
2118 assert_eq!(decoded, call);
2119 }
2120
2121 #[test]
2122 fn tempo_transaction_compact_roundtrip() {
2123 let tx = TempoTransaction {
2124 chain_id: 42170,
2125 fee_token: Some(address!("0x0000000000000000000000000000000000000abc")),
2126 max_priority_fee_per_gas: 1_000_000_000,
2127 max_fee_per_gas: 50_000_000_000,
2128 gas_limit: 21000,
2129 calls: vec![
2130 Call {
2131 to: TxKind::Call(address!("0x0000000000000000000000000000000000000001")),
2132 value: U256::from(1000u64),
2133 input: bytes!("cafe"),
2134 },
2135 Call {
2136 to: TxKind::Create,
2137 value: U256::ZERO,
2138 input: bytes!("6080604052"),
2139 },
2140 ],
2141 access_list: AccessList(vec![AccessListItem {
2142 address: address!("0x0000000000000000000000000000000000000001"),
2143 storage_keys: vec![B256::ZERO],
2144 }]),
2145 nonce_key: U256::from(7u64),
2146 nonce: 42,
2147 fee_payer_signature: Some(Signature::new(U256::from(1u64), U256::from(2u64), false)),
2148 valid_before: Some(NonZeroU64::new(1_700_001_000).unwrap()),
2149 valid_after: Some(NonZeroU64::new(1_700_000_000).unwrap()),
2150 key_authorization: Some(
2151 KeyAuthorization {
2152 chain_id: 42170,
2153 key_type: SignatureType::P256,
2154 key_id: address!("0x000000000000000000000000000000000000dead"),
2155 expiry: Some(core::num::NonZeroU64::new(1_700_100_000).unwrap()),
2156 limits: Some(vec![TokenLimit {
2157 token: address!("0x0000000000000000000000000000000000000042"),
2158 limit: U256::from(1_000_000u64),
2159 period: 86400,
2160 }]),
2161 allowed_calls: None,
2162 witness: None,
2163 is_admin: false,
2164 account: None,
2165 }
2166 .into_signed(PrimitiveSignature::P256(P256SignatureWithPreHash {
2167 r: b256!("0x1111111111111111111111111111111111111111111111111111111111111111"),
2168 s: b256!("0x2222222222222222222222222222222222222222222222222222222222222222"),
2169 pub_key_x: b256!(
2170 "0x3333333333333333333333333333333333333333333333333333333333333333"
2171 ),
2172 pub_key_y: b256!(
2173 "0x4444444444444444444444444444444444444444444444444444444444444444"
2174 ),
2175 pre_hash: false,
2176 })),
2177 ),
2178 tempo_authorization_list: vec![TempoSignedAuthorization::new_unchecked(
2179 Authorization {
2180 chain_id: U256::from(42170u64),
2181 address: address!("0x0000000000000000000000000000000000000099"),
2182 nonce: 1,
2183 },
2184 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::new(
2185 U256::from(3u64),
2186 U256::from(4u64),
2187 true,
2188 ))),
2189 )],
2190 };
2191
2192 let expected = hex!(
2193 "921409e201a4ba0000000000000000000000000000000000000abc3b9aca000ba43b74005208021905000000000000000000000000000000000000000103e8cafe0600608060405201350000000000000000000000000000000000000001010000000000000000000000000000000000000000000000000000000000000000072a0001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000046553f4e8046553f100c501f8c3f83d82a4ba0194000000000000000000000000000000000000dead84655577a0dedd940000000000000000000000000000000000000042830f424083015180b88201111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222223333333333333333333333333333333333333333333333333333333333333333444444444444444444444444444444444444444444444444444444444444444400015ef85c82a4ba94000000000000000000000000000000000000009901b841000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000041c"
2194 );
2195
2196 let mut buf = vec![];
2197 let len = tx.to_compact(&mut buf);
2198 assert_eq!(buf, expected, "TempoTransaction compact encoding changed");
2199 assert_eq!(len, expected.len());
2200
2201 let (decoded, _) = TempoTransaction::from_compact(&expected, expected.len());
2202 assert_eq!(decoded, tx);
2203 }
2204
2205 #[test]
2206 fn signature_type_compact_roundtrip() {
2207 for (variant, expected_byte) in [
2208 (SignatureType::Secp256k1, 0x00u8),
2209 (SignatureType::P256, 0x01),
2210 (SignatureType::WebAuthn, 0x02),
2211 ] {
2212 let mut buf = vec![];
2213 let len = variant.to_compact(&mut buf);
2214 assert_eq!(
2215 buf,
2216 [expected_byte],
2217 "SignatureType::{variant:?} compact encoding changed"
2218 );
2219 assert_eq!(len, 1);
2220
2221 let (decoded, _) = SignatureType::from_compact(&[expected_byte], 1);
2222 assert_eq!(decoded, variant);
2223 }
2224 }
2225}