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