1use alloy_consensus::{SignableTransaction, Transaction};
2use alloy_eips::{Typed2718, eip2930::AccessList, eip7702::SignedAuthorization};
3use alloy_primitives::{Address, B256, Bytes, ChainId, Signature, TxKind, U256, keccak256};
4use alloy_rlp::{Buf, BufMut, Decodable, EMPTY_STRING_CODE, Encodable};
5use core::mem;
6use reth_primitives_traits::InMemorySize;
7
8use crate::{
9 subblock::{PartialValidatorKey, has_sub_block_nonce_key_prefix},
10 transaction::{
11 AASigned, KeyAuthorization, TempoSignature, TempoSignedAuthorization,
12 key_authorization::SignedKeyAuthorization,
13 },
14};
15
16pub const TEMPO_TX_TYPE_ID: u8 = 0x76;
18
19pub const FEE_PAYER_SIGNATURE_MAGIC_BYTE: u8 = 0x78;
21
22pub const SECP256K1_SIGNATURE_LENGTH: usize = 65;
24pub const P256_SIGNATURE_LENGTH: usize = 129;
25pub const MAX_WEBAUTHN_SIGNATURE_LENGTH: usize = 2048; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
31#[cfg_attr(feature = "reth-codec", derive(reth_codecs::Compact))]
32#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
33#[repr(u8)]
34pub enum SignatureType {
35 Secp256k1 = 0,
36 P256 = 1,
37 WebAuthn = 2,
38}
39
40impl From<SignatureType> for u8 {
41 fn from(sig_type: SignatureType) -> Self {
42 match sig_type {
43 SignatureType::Secp256k1 => 0,
44 SignatureType::P256 => 1,
45 SignatureType::WebAuthn => 2,
46 }
47 }
48}
49
50impl alloy_rlp::Encodable for SignatureType {
51 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
52 (*self as u8).encode(out);
53 }
54
55 fn length(&self) -> usize {
56 1
57 }
58}
59
60impl alloy_rlp::Decodable for SignatureType {
61 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
62 let byte: u8 = alloy_rlp::Decodable::decode(buf)?;
63 match byte {
64 0 => Ok(Self::Secp256k1),
65 1 => Ok(Self::P256),
66 2 => Ok(Self::WebAuthn),
67 _ => Err(alloy_rlp::Error::Custom("Invalid signature type")),
68 }
69 }
70}
71
72#[inline]
74fn rlp_header(payload_length: usize) -> alloy_rlp::Header {
75 alloy_rlp::Header {
76 list: true,
77 payload_length,
78 }
79}
80
81#[derive(Clone, Debug, PartialEq, Eq, Hash)]
82#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
83#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
84#[cfg_attr(feature = "reth-codec", derive(reth_codecs::Compact))]
85#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
86#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact, rlp))]
87pub struct Call {
88 pub to: TxKind,
90
91 pub value: U256,
93
94 #[cfg_attr(feature = "serde", serde(flatten, with = "serde_input"))]
96 pub input: Bytes,
97}
98
99impl Call {
100 #[inline]
102 fn rlp_header(&self) -> alloy_rlp::Header {
103 let payload_length = self.to.length() + self.value.length() + self.input.length();
104 alloy_rlp::Header {
105 list: true,
106 payload_length,
107 }
108 }
109}
110
111impl Encodable for Call {
112 fn encode(&self, out: &mut dyn BufMut) {
113 self.rlp_header().encode(out);
114 self.to.encode(out);
115 self.value.encode(out);
116 self.input.encode(out);
117 }
118
119 fn length(&self) -> usize {
120 self.rlp_header().length_with_payload()
121 }
122}
123
124impl Decodable for Call {
125 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
126 let header = alloy_rlp::Header::decode(buf)?;
127 if !header.list {
128 return Err(alloy_rlp::Error::UnexpectedString);
129 }
130 let remaining = buf.len();
131
132 if header.payload_length > remaining {
133 return Err(alloy_rlp::Error::InputTooShort);
134 }
135
136 let this = Self {
137 to: Decodable::decode(buf)?,
138 value: Decodable::decode(buf)?,
139 input: Decodable::decode(buf)?,
140 };
141
142 if buf.len() + header.payload_length != remaining {
143 return Err(alloy_rlp::Error::UnexpectedLength);
144 }
145
146 Ok(this)
147 }
148}
149
150#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
159#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
160#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
161pub struct TempoTransaction {
162 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
164 pub chain_id: ChainId,
165
166 pub fee_token: Option<Address>,
168
169 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
171 pub max_priority_fee_per_gas: u128,
172
173 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
175 pub max_fee_per_gas: u128,
176
177 #[cfg_attr(
179 feature = "serde",
180 serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
181 )]
182 pub gas_limit: u64,
183
184 pub calls: Vec<Call>,
186
187 pub access_list: AccessList,
189
190 pub nonce_key: U256,
195
196 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
198 pub nonce: u64,
199
200 pub fee_payer_signature: Option<Signature>,
204
205 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity::opt"))]
207 pub valid_before: Option<u64>,
208
209 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity::opt"))]
211 pub valid_after: Option<u64>,
212
213 pub key_authorization: Option<SignedKeyAuthorization>,
219
220 #[cfg_attr(feature = "serde", serde(rename = "aaAuthorizationList"))]
222 pub tempo_authorization_list: Vec<TempoSignedAuthorization>,
223}
224
225impl TempoTransaction {
226 #[doc(alias = "transaction_type")]
228 pub const fn tx_type() -> u8 {
229 TEMPO_TX_TYPE_ID
230 }
231
232 pub fn validate(&self) -> Result<(), &'static str> {
234 if self.calls.is_empty() {
236 return Err("calls list cannot be empty");
237 }
238
239 if let Some(valid_after) = self.valid_after
241 && let Some(valid_before) = self.valid_before
242 && valid_before <= valid_after
243 {
244 return Err("valid_before must be greater than valid_after");
245 }
246
247 if !self.tempo_authorization_list.is_empty() {
250 for call in &self.calls {
251 if call.to.is_create() {
252 return Err(
253 "calls cannot contain Create when aa_authorization_list is non-empty",
254 );
255 }
256 }
257 }
258
259 Ok(())
260 }
261
262 #[inline]
264 pub fn size(&self) -> usize {
265 mem::size_of::<ChainId>() + mem::size_of::<Option<Address>>() + mem::size_of::<u128>() + mem::size_of::<u128>() + mem::size_of::<u64>() + self.calls.iter().map(|call| {
271 mem::size_of::<TxKind>() + mem::size_of::<U256>() + call.input.len()
272 }).sum::<usize>() + self.access_list.size() + mem::size_of::<U256>() + mem::size_of::<u64>() + mem::size_of::<Option<Signature>>() + mem::size_of::<Option<u64>>() + mem::size_of::<Option<u64>>() + self.key_authorization.as_ref().map(|k| k.size()).unwrap_or(mem::size_of::<Option<KeyAuthorization>>()) +
281 self.tempo_authorization_list.iter().map(|auth| auth.size()).sum::<usize>() }
283
284 pub fn into_signed(self, signature: TempoSignature) -> AASigned {
286 AASigned::new_unhashed(self, signature)
287 }
288
289 pub fn signature_hash(&self) -> B256 {
292 let mut buf = Vec::new();
293 self.encode_for_signing(&mut buf);
294 keccak256(&buf)
295 }
296
297 pub fn fee_payer_signature_hash(&self, sender: Address) -> B256 {
300 let payload_length = self.rlp_encoded_fields_length(|_| sender.length(), false);
302
303 let mut buf = Vec::with_capacity(1 + rlp_header(payload_length).length_with_payload());
304
305 buf.put_u8(FEE_PAYER_SIGNATURE_MAGIC_BYTE);
307
308 rlp_header(payload_length).encode(&mut buf);
310
311 self.rlp_encode_fields(
313 &mut buf,
314 |_, out| {
315 sender.encode(out);
317 },
318 false, );
320
321 keccak256(&buf)
322 }
323
324 fn rlp_encoded_fields_length(
328 &self,
329 signature_length: impl FnOnce(&Option<Signature>) -> usize,
330 skip_fee_token: bool,
331 ) -> usize {
332 self.chain_id.length() +
333 self.max_priority_fee_per_gas.length() +
334 self.max_fee_per_gas.length() +
335 self.gas_limit.length() +
336 self.calls.length() +
337 self.access_list.length() +
338 self.nonce_key.length() +
339 self.nonce.length() +
340 if let Some(valid_before) = self.valid_before {
341 valid_before.length()
342 } else {
343 1 } +
345 if let Some(valid_after) = self.valid_after {
347 valid_after.length()
348 } else {
349 1 } +
351 if !skip_fee_token && let Some(addr) = self.fee_token {
353 addr.length()
354 } else {
355 1 } +
357 signature_length(&self.fee_payer_signature) +
358 self.tempo_authorization_list.length() +
360 if let Some(key_auth) = &self.key_authorization {
362 key_auth.length()
363 } else {
364 0 }
366 }
367
368 fn rlp_encode_fields(
369 &self,
370 out: &mut dyn BufMut,
371 encode_signature: impl FnOnce(&Option<Signature>, &mut dyn BufMut),
372 skip_fee_token: bool,
373 ) {
374 self.chain_id.encode(out);
375 self.max_priority_fee_per_gas.encode(out);
376 self.max_fee_per_gas.encode(out);
377 self.gas_limit.encode(out);
378 self.calls.encode(out);
379 self.access_list.encode(out);
380 self.nonce_key.encode(out);
381 self.nonce.encode(out);
382
383 if let Some(valid_before) = self.valid_before {
384 valid_before.encode(out);
385 } else {
386 out.put_u8(EMPTY_STRING_CODE);
387 }
388
389 if let Some(valid_after) = self.valid_after {
390 valid_after.encode(out);
391 } else {
392 out.put_u8(EMPTY_STRING_CODE);
393 }
394
395 if !skip_fee_token && let Some(addr) = self.fee_token {
396 addr.encode(out);
397 } else {
398 out.put_u8(EMPTY_STRING_CODE);
399 }
400
401 encode_signature(&self.fee_payer_signature, out);
402
403 self.tempo_authorization_list.encode(out);
405
406 if let Some(key_auth) = &self.key_authorization {
408 key_auth.encode(out);
409 }
410 }
412
413 pub(crate) fn rlp_encoded_fields_length_default(&self) -> usize {
415 self.rlp_encoded_fields_length(
416 |signature| {
417 signature.map_or(1, |s| {
418 rlp_header(s.rlp_rs_len() + s.v().length()).length_with_payload()
419 })
420 },
421 false,
422 )
423 }
424
425 pub(crate) fn rlp_encode_fields_default(&self, out: &mut dyn BufMut) {
427 self.rlp_encode_fields(
428 out,
429 |signature, out| {
430 if let Some(signature) = signature {
431 let payload_length = signature.rlp_rs_len() + signature.v().length();
432 rlp_header(payload_length).encode(out);
433 signature.write_rlp_vrs(out, signature.v());
434 } else {
435 out.put_u8(EMPTY_STRING_CODE);
436 }
437 },
438 false,
439 )
440 }
441
442 pub(crate) fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
444 let chain_id = Decodable::decode(buf)?;
445 let max_priority_fee_per_gas = Decodable::decode(buf)?;
446 let max_fee_per_gas = Decodable::decode(buf)?;
447 let gas_limit = Decodable::decode(buf)?;
448 let calls = Decodable::decode(buf)?;
449 let access_list = Decodable::decode(buf)?;
450 let nonce_key = Decodable::decode(buf)?;
451 let nonce = Decodable::decode(buf)?;
452
453 let valid_before = if let Some(first) = buf.first() {
454 if *first == EMPTY_STRING_CODE {
455 buf.advance(1);
456 None
457 } else {
458 Some(Decodable::decode(buf)?)
459 }
460 } else {
461 return Err(alloy_rlp::Error::InputTooShort);
462 };
463
464 let valid_after = if let Some(first) = buf.first() {
465 if *first == EMPTY_STRING_CODE {
466 buf.advance(1);
467 None
468 } else {
469 Some(Decodable::decode(buf)?)
470 }
471 } else {
472 return Err(alloy_rlp::Error::InputTooShort);
473 };
474
475 let fee_token = if let Some(first) = buf.first() {
476 if *first == EMPTY_STRING_CODE {
477 buf.advance(1);
478 None
479 } else {
480 TxKind::decode(buf)?.into_to()
481 }
482 } else {
483 return Err(alloy_rlp::Error::InputTooShort);
484 };
485
486 let fee_payer_signature = if let Some(first) = buf.first() {
487 if *first == EMPTY_STRING_CODE {
488 buf.advance(1);
489 None
490 } else {
491 let header = alloy_rlp::Header::decode(buf)?;
492 if buf.len() < header.payload_length {
493 return Err(alloy_rlp::Error::InputTooShort);
494 }
495 if !header.list {
496 return Err(alloy_rlp::Error::UnexpectedString);
497 }
498 Some(Signature::decode_rlp_vrs(buf, bool::decode)?)
499 }
500 } else {
501 return Err(alloy_rlp::Error::InputTooShort);
502 };
503
504 let tempo_authorization_list = Decodable::decode(buf)?;
505
506 let key_authorization = if let Some(&first) = buf.first() {
512 if first >= 0xc0 {
514 Some(Decodable::decode(buf)?)
516 } else {
517 None
519 }
520 } else {
521 None
522 };
523
524 let tx = Self {
525 chain_id,
526 fee_token,
527 max_priority_fee_per_gas,
528 max_fee_per_gas,
529 gas_limit,
530 calls,
531 access_list,
532 nonce_key,
533 nonce,
534 fee_payer_signature,
535 valid_before,
536 valid_after,
537 key_authorization,
538 tempo_authorization_list,
539 };
540
541 tx.validate().map_err(alloy_rlp::Error::Custom)?;
543
544 Ok(tx)
545 }
546
547 pub fn has_sub_block_nonce_key_prefix(&self) -> bool {
549 has_sub_block_nonce_key_prefix(&self.nonce_key)
550 }
551
552 pub fn subblock_proposer(&self) -> Option<PartialValidatorKey> {
554 if self.has_sub_block_nonce_key_prefix() {
555 Some(PartialValidatorKey::from_slice(
556 &self.nonce_key.to_be_bytes::<32>()[1..16],
557 ))
558 } else {
559 None
560 }
561 }
562}
563
564impl Transaction for TempoTransaction {
565 #[inline]
566 fn chain_id(&self) -> Option<ChainId> {
567 Some(self.chain_id)
568 }
569
570 #[inline]
571 fn nonce(&self) -> u64 {
572 self.nonce
573 }
574
575 #[inline]
576 fn gas_limit(&self) -> u64 {
577 self.gas_limit
578 }
579
580 #[inline]
581 fn gas_price(&self) -> Option<u128> {
582 None
583 }
584
585 #[inline]
586 fn max_fee_per_gas(&self) -> u128 {
587 self.max_fee_per_gas
588 }
589
590 #[inline]
591 fn max_priority_fee_per_gas(&self) -> Option<u128> {
592 Some(self.max_priority_fee_per_gas)
593 }
594
595 #[inline]
596 fn max_fee_per_blob_gas(&self) -> Option<u128> {
597 None
598 }
599
600 #[inline]
601 fn priority_fee_or_price(&self) -> u128 {
602 self.max_priority_fee_per_gas
603 }
604
605 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
606 alloy_eips::eip1559::calc_effective_gas_price(
607 self.max_fee_per_gas,
608 self.max_priority_fee_per_gas,
609 base_fee,
610 )
611 }
612
613 #[inline]
614 fn is_dynamic_fee(&self) -> bool {
615 true
616 }
617
618 #[inline]
619 fn kind(&self) -> TxKind {
620 self.calls.first().map(|c| c.to).unwrap_or(TxKind::Create)
622 }
623
624 #[inline]
625 fn is_create(&self) -> bool {
626 self.kind().is_create()
627 }
628
629 #[inline]
630 fn value(&self) -> U256 {
631 self.calls
633 .iter()
634 .fold(U256::ZERO, |acc, call| acc + call.value)
635 }
636
637 #[inline]
638 fn input(&self) -> &Bytes {
639 static EMPTY_BYTES: Bytes = Bytes::new();
641 self.calls.first().map(|c| &c.input).unwrap_or(&EMPTY_BYTES)
642 }
643
644 #[inline]
645 fn access_list(&self) -> Option<&AccessList> {
646 Some(&self.access_list)
647 }
648
649 #[inline]
650 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
651 None
652 }
653
654 #[inline]
655 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
656 None
657 }
658}
659
660impl Typed2718 for TempoTransaction {
661 fn ty(&self) -> u8 {
662 TEMPO_TX_TYPE_ID
663 }
664}
665
666impl SignableTransaction<Signature> for TempoTransaction {
667 fn set_chain_id(&mut self, chain_id: ChainId) {
668 self.chain_id = chain_id;
669 }
670
671 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
672 let skip_fee_token = self.fee_payer_signature.is_some();
674
675 out.put_u8(Self::tx_type());
677
678 let payload_length = self.rlp_encoded_fields_length(|_| 1, skip_fee_token);
680
681 rlp_header(payload_length).encode(out);
682
683 self.rlp_encode_fields(
685 out,
686 |signature, out| {
687 if signature.is_some() {
688 out.put_u8(0); } else {
690 out.put_u8(EMPTY_STRING_CODE);
691 }
692 },
693 skip_fee_token,
694 );
695 }
696
697 fn payload_len_for_signature(&self) -> usize {
698 let skip_fee_token = self.fee_payer_signature.is_some();
699 let payload_length = self.rlp_encoded_fields_length(|_| 1, skip_fee_token);
700
701 1 + rlp_header(payload_length).length_with_payload()
702 }
703}
704
705impl Encodable for TempoTransaction {
706 fn encode(&self, out: &mut dyn BufMut) {
707 let payload_length = self.rlp_encoded_fields_length_default();
709 rlp_header(payload_length).encode(out);
710 self.rlp_encode_fields_default(out);
711 }
712
713 fn length(&self) -> usize {
714 let payload_length = self.rlp_encoded_fields_length_default();
715 rlp_header(payload_length).length_with_payload()
716 }
717}
718
719impl Decodable for TempoTransaction {
720 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
721 let header = alloy_rlp::Header::decode(buf)?;
722 if !header.list {
723 return Err(alloy_rlp::Error::UnexpectedString);
724 }
725 let remaining = buf.len();
726
727 if header.payload_length > remaining {
728 return Err(alloy_rlp::Error::InputTooShort);
729 }
730
731 let mut fields_buf = &buf[..header.payload_length];
732 let this = Self::rlp_decode_fields(&mut fields_buf)?;
733
734 if !fields_buf.is_empty() {
735 return Err(alloy_rlp::Error::UnexpectedLength);
736 }
737 buf.advance(header.payload_length);
738
739 Ok(this)
740 }
741}
742
743impl reth_primitives_traits::InMemorySize for TempoTransaction {
744 fn size(&self) -> usize {
745 Self::size(self)
746 }
747}
748
749#[cfg(feature = "serde-bincode-compat")]
750impl reth_primitives_traits::serde_bincode_compat::RlpBincode for TempoTransaction {}
751
752#[cfg(any(test, feature = "arbitrary"))]
754impl<'a> arbitrary::Arbitrary<'a> for TempoTransaction {
755 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
756 let chain_id = u.arbitrary()?;
758 let fee_token = u.arbitrary()?;
759 let max_priority_fee_per_gas = u.arbitrary()?;
760 let max_fee_per_gas = u.arbitrary()?;
761 let gas_limit = u.arbitrary()?;
762
763 let mut calls: Vec<Call> = u.arbitrary()?;
765 if calls.is_empty() {
766 calls.push(Call {
767 to: u.arbitrary()?,
768 value: u.arbitrary()?,
769 input: u.arbitrary()?,
770 });
771 }
772
773 let access_list = u.arbitrary()?;
774
775 let nonce_key = U256::ZERO;
777 let nonce = u.arbitrary()?;
778 let fee_payer_signature = u.arbitrary()?;
779
780 let valid_after: Option<u64> = u.arbitrary::<Option<u64>>()?.filter(|v| *v != 0);
785 let valid_before: Option<u64> = match valid_after {
786 Some(after) => {
787 let offset: u64 = u.int_in_range(1..=1000)?;
789 Some(after.saturating_add(offset))
790 }
791 None => {
792 u.arbitrary::<Option<u64>>()?.filter(|v| *v != 0)
794 }
795 };
796
797 Ok(Self {
798 chain_id,
799 fee_token,
800 max_priority_fee_per_gas,
801 max_fee_per_gas,
802 gas_limit,
803 calls,
804 access_list,
805 nonce_key,
806 nonce,
807 fee_payer_signature,
808 valid_before,
809 valid_after,
810 key_authorization: u.arbitrary()?,
811 tempo_authorization_list: vec![],
812 })
813 }
814}
815
816#[cfg(feature = "serde")]
817mod serde_input {
818 use std::borrow::Cow;
821
822 use super::*;
823 use serde::{Deserialize, Deserializer, Serialize, Serializer};
824
825 #[derive(Serialize, Deserialize)]
826 struct SerdeHelper<'a> {
827 input: Option<Cow<'a, Bytes>>,
828 data: Option<Cow<'a, Bytes>>,
829 }
830
831 pub(super) fn serialize<S>(input: &Bytes, serializer: S) -> Result<S::Ok, S::Error>
832 where
833 S: Serializer,
834 {
835 SerdeHelper {
836 input: Some(Cow::Borrowed(input)),
837 data: None,
838 }
839 .serialize(serializer)
840 }
841
842 pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<Bytes, D::Error>
843 where
844 D: Deserializer<'de>,
845 {
846 let helper = SerdeHelper::deserialize(deserializer)?;
847 Ok(helper
848 .input
849 .or(helper.data)
850 .ok_or(serde::de::Error::missing_field(
851 "missing `input` or `data` field",
852 ))?
853 .into_owned())
854 }
855}
856
857#[cfg(feature = "reth-codec")]
858mod compact {
859 use super::*;
860 use reth_codecs::Compact;
861
862 #[derive(Compact)]
863
864 struct OldTempoTransaction {
865 chain_id: ChainId,
866 fee_token: Option<Address>,
867 max_priority_fee_per_gas: u128,
868 max_fee_per_gas: u128,
869 gas_limit: u64,
870 calls: Vec<Call>,
871 access_list: AccessList,
872 nonce_key: U256,
873 nonce: u64,
874 fee_payer_signature: Option<Signature>,
875 valid_before: Option<u64>,
876 valid_after: Option<u64>,
877 tempo_authorization_list: Vec<TempoSignedAuthorization>,
878 }
879
880 #[derive(Compact)]
881
882 struct NewTempoTransaction {
883 chain_id: ChainId,
884 fee_token: Option<Address>,
885 max_priority_fee_per_gas: u128,
886 max_fee_per_gas: u128,
887 gas_limit: u64,
888 calls: Vec<Call>,
889 access_list: AccessList,
890 nonce_key: U256,
891 nonce: u64,
892 fee_payer_signature: Option<Signature>,
893 valid_before: Option<u64>,
894 valid_after: Option<u64>,
895 key_authorization: Option<SignedKeyAuthorization>,
896 tempo_authorization_list: Vec<TempoSignedAuthorization>,
897 }
898
899 impl Compact for TempoTransaction {
900 fn to_compact<B>(&self, buf: &mut B) -> usize
901 where
902 B: alloy_rlp::bytes::BufMut + AsMut<[u8]>,
903 {
904 let mut flags = NewTempoTransactionFlags::default();
906 let mut total_length = 0;
907 let mut buffer = reth_codecs::__private::bytes::BytesMut::new();
908 let chain_id_len = self.chain_id.to_compact(&mut buffer);
909 flags.set_chain_id_len(chain_id_len as u8);
910 let fee_token_len = self.fee_token.specialized_to_compact(&mut buffer);
911 flags.set_fee_token_len(fee_token_len as u8);
912 let max_priority_fee_per_gas_len =
913 self.max_priority_fee_per_gas.to_compact(&mut buffer);
914 flags.set_max_priority_fee_per_gas_len(max_priority_fee_per_gas_len as u8);
915 let max_fee_per_gas_len = self.max_fee_per_gas.to_compact(&mut buffer);
916 flags.set_max_fee_per_gas_len(max_fee_per_gas_len as u8);
917 let gas_limit_len = self.gas_limit.to_compact(&mut buffer);
918 flags.set_gas_limit_len(gas_limit_len as u8);
919 self.calls.to_compact(&mut buffer);
920 self.access_list.to_compact(&mut buffer);
921 let nonce_key_len = self.nonce_key.to_compact(&mut buffer);
922 flags.set_nonce_key_len(nonce_key_len as u8);
923 let nonce_len = self.nonce.to_compact(&mut buffer);
924 flags.set_nonce_len(nonce_len as u8);
925 let fee_payer_signature_len = self.fee_payer_signature.to_compact(&mut buffer);
926 flags.set_fee_payer_signature_len(fee_payer_signature_len as u8);
927 let valid_before_len = self.valid_before.to_compact(&mut buffer);
928 flags.set_valid_before_len(valid_before_len as u8);
929 let valid_after_len = self.valid_after.to_compact(&mut buffer);
930 flags.set_valid_after_len(valid_after_len as u8);
931 let key_authorization_len = self.key_authorization.to_compact(&mut buffer);
932 flags.set_key_authorization_len(key_authorization_len as u8);
933 self.tempo_authorization_list.to_compact(&mut buffer);
934 let flags = flags.into_bytes();
935 total_length += flags.len() + buffer.len();
936 buf.put_slice(&flags);
937 buf.put(buffer);
938 total_length
939 }
940
941 fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
942 if buf[4] <= 1 {
950 let (
951 NewTempoTransaction {
952 chain_id,
953 fee_token,
954 max_priority_fee_per_gas,
955 max_fee_per_gas,
956 gas_limit,
957 calls,
958 access_list,
959 nonce_key,
960 nonce,
961 fee_payer_signature,
962 valid_before,
963 valid_after,
964 key_authorization,
965 tempo_authorization_list,
966 },
967 buf,
968 ) = NewTempoTransaction::from_compact(buf, len);
969 (
970 Self {
971 chain_id,
972 fee_token,
973 max_priority_fee_per_gas,
974 max_fee_per_gas,
975 gas_limit,
976 calls,
977 access_list,
978 nonce_key,
979 nonce,
980 fee_payer_signature,
981 valid_before,
982 valid_after,
983 key_authorization,
984 tempo_authorization_list,
985 },
986 buf,
987 )
988 } else {
989 let (
990 OldTempoTransaction {
991 chain_id,
992 fee_token,
993 max_priority_fee_per_gas,
994 max_fee_per_gas,
995 gas_limit,
996 calls,
997 access_list,
998 nonce_key,
999 nonce,
1000 fee_payer_signature,
1001 valid_before,
1002 valid_after,
1003 tempo_authorization_list,
1004 },
1005 buf,
1006 ) = OldTempoTransaction::from_compact(buf, len);
1007
1008 (
1009 Self {
1010 chain_id,
1011 fee_token,
1012 max_priority_fee_per_gas,
1013 max_fee_per_gas,
1014 gas_limit,
1015 calls,
1016 access_list,
1017 nonce_key,
1018 nonce,
1019 fee_payer_signature,
1020 valid_before,
1021 valid_after,
1022 key_authorization: None,
1023 tempo_authorization_list,
1024 },
1025 buf,
1026 )
1027 }
1028 }
1029 }
1030}
1031
1032#[cfg(test)]
1033mod tests {
1034 use super::*;
1035 use crate::transaction::tt_signature::{
1036 PrimitiveSignature, TempoSignature, derive_p256_address,
1037 };
1038 use alloy_primitives::{Address, Bytes, Signature, TxKind, U256, address, bytes, hex};
1039 use alloy_rlp::{Decodable, Encodable};
1040
1041 #[test]
1042 fn test_tempo_transaction_validation() {
1043 let dummy_call = Call {
1045 to: TxKind::Create,
1046 value: U256::ZERO,
1047 input: Bytes::new(),
1048 };
1049
1050 let tx1 = TempoTransaction {
1052 valid_before: Some(100),
1053 valid_after: Some(50),
1054 tempo_authorization_list: vec![],
1055 calls: vec![dummy_call.clone()],
1056 ..Default::default()
1057 };
1058 assert!(tx1.validate().is_ok());
1059
1060 let tx2 = TempoTransaction {
1062 valid_before: Some(50),
1063 valid_after: Some(100),
1064 tempo_authorization_list: vec![],
1065 calls: vec![dummy_call.clone()],
1066 ..Default::default()
1067 };
1068 assert!(tx2.validate().is_err());
1069
1070 let tx3 = TempoTransaction {
1072 valid_before: Some(100),
1073 valid_after: Some(100),
1074 tempo_authorization_list: vec![],
1075 calls: vec![dummy_call.clone()],
1076 ..Default::default()
1077 };
1078 assert!(tx3.validate().is_err());
1079
1080 let tx4 = TempoTransaction {
1082 valid_before: Some(100),
1083 valid_after: None,
1084 tempo_authorization_list: vec![],
1085 calls: vec![dummy_call],
1086 ..Default::default()
1087 };
1088 assert!(tx4.validate().is_ok());
1089
1090 let tx5 = TempoTransaction {
1092 ..Default::default()
1093 };
1094 assert!(tx5.validate().is_err());
1095 }
1096
1097 #[test]
1098 fn test_tx_type() {
1099 assert_eq!(TempoTransaction::tx_type(), 0x76);
1100 assert_eq!(TEMPO_TX_TYPE_ID, 0x76);
1101 }
1102
1103 #[test]
1104 fn test_signature_type_detection() {
1105 use crate::transaction::tt_signature::{SIGNATURE_TYPE_P256, SIGNATURE_TYPE_WEBAUTHN};
1106
1107 let sig1_bytes = vec![0u8; SECP256K1_SIGNATURE_LENGTH];
1109 let sig1 = TempoSignature::from_bytes(&sig1_bytes).unwrap();
1110 assert_eq!(sig1.signature_type(), SignatureType::Secp256k1);
1111
1112 let mut sig2_bytes = vec![SIGNATURE_TYPE_P256];
1114 sig2_bytes.extend_from_slice(&[0u8; P256_SIGNATURE_LENGTH]);
1115 let sig2 = TempoSignature::from_bytes(&sig2_bytes).unwrap();
1116 assert_eq!(sig2.signature_type(), SignatureType::P256);
1117
1118 let mut sig3_bytes = vec![SIGNATURE_TYPE_WEBAUTHN];
1120 sig3_bytes.extend_from_slice(&[0u8; 200]);
1121 let sig3 = TempoSignature::from_bytes(&sig3_bytes).unwrap();
1122 assert_eq!(sig3.signature_type(), SignatureType::WebAuthn);
1123 }
1124
1125 #[test]
1126 fn test_rlp_roundtrip() {
1127 let call = Call {
1128 to: TxKind::Call(address!("0000000000000000000000000000000000000002")),
1129 value: U256::from(1000),
1130 input: Bytes::from(vec![1, 2, 3, 4]),
1131 };
1132
1133 let tx = TempoTransaction {
1134 chain_id: 1,
1135 fee_token: Some(address!("0000000000000000000000000000000000000001")),
1136 max_priority_fee_per_gas: 1000000000,
1137 max_fee_per_gas: 2000000000,
1138 gas_limit: 21000,
1139 calls: vec![call.clone()],
1140 access_list: Default::default(),
1141 nonce_key: U256::ZERO,
1142 nonce: 1,
1143 fee_payer_signature: Some(Signature::test_signature()),
1144 valid_before: Some(1000000),
1145 valid_after: Some(500000),
1146 key_authorization: None,
1147 tempo_authorization_list: vec![],
1148 };
1149
1150 let mut buf = Vec::new();
1152 tx.encode(&mut buf);
1153
1154 let decoded = TempoTransaction::decode(&mut buf.as_slice()).unwrap();
1156
1157 assert_eq!(decoded.chain_id, tx.chain_id);
1159 assert_eq!(decoded.fee_token, tx.fee_token);
1160 assert_eq!(
1161 decoded.max_priority_fee_per_gas,
1162 tx.max_priority_fee_per_gas
1163 );
1164 assert_eq!(decoded.max_fee_per_gas, tx.max_fee_per_gas);
1165 assert_eq!(decoded.gas_limit, tx.gas_limit);
1166 assert_eq!(decoded.calls.len(), 1);
1167 assert_eq!(decoded.calls[0].to, call.to);
1168 assert_eq!(decoded.calls[0].value, call.value);
1169 assert_eq!(decoded.calls[0].input, call.input);
1170 assert_eq!(decoded.nonce_key, tx.nonce_key);
1171 assert_eq!(decoded.nonce, tx.nonce);
1172 assert_eq!(decoded.valid_before, tx.valid_before);
1173 assert_eq!(decoded.valid_after, tx.valid_after);
1174 assert_eq!(decoded.fee_payer_signature, tx.fee_payer_signature);
1175 }
1176
1177 #[test]
1178 fn test_rlp_roundtrip_no_optional_fields() {
1179 let call = Call {
1180 to: TxKind::Call(address!("0000000000000000000000000000000000000002")),
1181 value: U256::from(1000),
1182 input: Bytes::new(),
1183 };
1184
1185 let tx = TempoTransaction {
1186 chain_id: 1,
1187 fee_token: None,
1188 max_priority_fee_per_gas: 1000000000,
1189 max_fee_per_gas: 2000000000,
1190 gas_limit: 21000,
1191 calls: vec![call],
1192 access_list: Default::default(),
1193 nonce_key: U256::ZERO,
1194 nonce: 1,
1195 fee_payer_signature: None,
1196 valid_before: Some(1000),
1197 valid_after: None,
1198 key_authorization: None,
1199 tempo_authorization_list: vec![],
1200 };
1201
1202 let mut buf = Vec::new();
1204 tx.encode(&mut buf);
1205
1206 let decoded = TempoTransaction::decode(&mut buf.as_slice()).unwrap();
1208
1209 assert_eq!(decoded.chain_id, tx.chain_id);
1211 assert_eq!(decoded.fee_token, None);
1212 assert_eq!(decoded.fee_payer_signature, None);
1213 assert_eq!(decoded.valid_after, None);
1214 assert_eq!(decoded.calls.len(), 1);
1215 }
1216
1217 #[test]
1218 fn test_p256_address_derivation() {
1219 let pub_key_x =
1220 hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1221 let pub_key_y =
1222 hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1223
1224 let addr1 = derive_p256_address(&pub_key_x, &pub_key_y);
1225 let addr2 = derive_p256_address(&pub_key_x, &pub_key_y);
1226
1227 assert_eq!(addr1, addr2);
1229
1230 assert_ne!(addr1, Address::ZERO);
1232 }
1233
1234 #[test]
1235 fn test_nonce_system() {
1236 use alloy_consensus::Transaction;
1237
1238 let dummy_call = Call {
1240 to: TxKind::Create,
1241 value: U256::ZERO,
1242 input: Bytes::new(),
1243 };
1244
1245 let tx1 = TempoTransaction {
1247 nonce_key: U256::ZERO,
1248 nonce: 1,
1249 calls: vec![dummy_call.clone()],
1250 ..Default::default()
1251 };
1252 assert!(tx1.validate().is_ok());
1253 assert_eq!(tx1.nonce(), 1);
1254 assert_eq!(tx1.nonce_key, U256::ZERO);
1255
1256 let tx2 = TempoTransaction {
1258 nonce_key: U256::from(1),
1259 nonce: 0,
1260 calls: vec![dummy_call.clone()],
1261 ..Default::default()
1262 };
1263 assert!(tx2.validate().is_ok());
1264 assert_eq!(tx2.nonce(), 0);
1265 assert_eq!(tx2.nonce_key, U256::from(1));
1266
1267 let tx3 = TempoTransaction {
1269 nonce_key: U256::from(42),
1270 nonce: 10,
1271 calls: vec![dummy_call.clone()],
1272 ..Default::default()
1273 };
1274 assert!(tx3.validate().is_ok());
1275 assert_eq!(tx3.nonce(), 10);
1276 assert_eq!(tx3.nonce_key, U256::from(42));
1277
1278 let tx4a = TempoTransaction {
1281 nonce_key: U256::from(1),
1282 nonce: 100,
1283 calls: vec![dummy_call.clone()],
1284 ..Default::default()
1285 };
1286 let tx4b = TempoTransaction {
1287 nonce_key: U256::from(2),
1288 nonce: 100,
1289 calls: vec![dummy_call],
1290 ..Default::default()
1291 };
1292 assert!(tx4a.validate().is_ok());
1293 assert!(tx4b.validate().is_ok());
1294 assert_eq!(tx4a.nonce(), tx4b.nonce()); assert_ne!(tx4a.nonce_key, tx4b.nonce_key); }
1297
1298 #[test]
1299 fn test_transaction_trait_impl() {
1300 use alloy_consensus::Transaction;
1301
1302 let call = Call {
1303 to: TxKind::Call(address!("0000000000000000000000000000000000000002")),
1304 value: U256::from(1000),
1305 input: Bytes::new(),
1306 };
1307
1308 let tx = TempoTransaction {
1309 chain_id: 1,
1310 max_priority_fee_per_gas: 1000000000,
1311 max_fee_per_gas: 2000000000,
1312 gas_limit: 21000,
1313 calls: vec![call],
1314 ..Default::default()
1315 };
1316
1317 assert_eq!(tx.chain_id(), Some(1));
1318 assert_eq!(tx.gas_limit(), 21000);
1319 assert_eq!(tx.max_fee_per_gas(), 2000000000);
1320 assert_eq!(tx.max_priority_fee_per_gas(), Some(1000000000));
1321 assert_eq!(tx.value(), U256::from(1000));
1322 assert!(tx.is_dynamic_fee());
1323 assert!(!tx.is_create());
1324 }
1325
1326 #[test]
1327 fn test_effective_gas_price() {
1328 use alloy_consensus::Transaction;
1329
1330 let dummy_call = Call {
1332 to: TxKind::Create,
1333 value: U256::ZERO,
1334 input: Bytes::new(),
1335 };
1336
1337 let tx = TempoTransaction {
1338 max_priority_fee_per_gas: 1000000000,
1339 max_fee_per_gas: 2000000000,
1340 calls: vec![dummy_call],
1341 ..Default::default()
1342 };
1343
1344 let effective1 = tx.effective_gas_price(Some(500000000));
1346 assert_eq!(effective1, 1500000000); let effective2 = tx.effective_gas_price(None);
1350 assert_eq!(effective2, 2000000000); }
1352
1353 #[test]
1354 fn test_fee_payer_commits_to_fee_token() {
1355 let sender = address!("0000000000000000000000000000000000000001");
1359 let token1 = address!("0000000000000000000000000000000000000002");
1360 let token2 = address!("0000000000000000000000000000000000000003");
1361
1362 let dummy_call = Call {
1363 to: TxKind::Create,
1364 value: U256::ZERO,
1365 input: Bytes::new(),
1366 };
1367
1368 let tx_no_token = TempoTransaction {
1370 chain_id: 1,
1371 fee_token: None,
1372 max_priority_fee_per_gas: 1000000000,
1373 max_fee_per_gas: 2000000000,
1374 gas_limit: 21000,
1375 calls: vec![dummy_call],
1376 nonce_key: U256::ZERO,
1377 nonce: 1,
1378 fee_payer_signature: Some(Signature::test_signature()),
1379 valid_before: Some(1000),
1380 valid_after: None,
1381 ..Default::default()
1382 };
1383
1384 let tx_token1 = TempoTransaction {
1386 fee_token: Some(token1),
1387 ..tx_no_token.clone()
1388 };
1389
1390 let tx_token2 = TempoTransaction {
1392 fee_token: Some(token2),
1393 ..tx_no_token.clone()
1394 };
1395
1396 let fee_payer_hash_no_token = tx_no_token.fee_payer_signature_hash(sender);
1398 let fee_payer_hash_token1 = tx_token1.fee_payer_signature_hash(sender);
1399 let fee_payer_hash_token2 = tx_token2.fee_payer_signature_hash(sender);
1400
1401 assert_ne!(
1403 fee_payer_hash_no_token, fee_payer_hash_token1,
1404 "Fee payer hash should change when fee_token changes from None to Some"
1405 );
1406 assert_ne!(
1407 fee_payer_hash_token1, fee_payer_hash_token2,
1408 "Fee payer hash should change when fee_token changes from token1 to token2"
1409 );
1410 assert_ne!(
1411 fee_payer_hash_no_token, fee_payer_hash_token2,
1412 "Fee payer hash should be different for None vs token2"
1413 );
1414
1415 let user_hash_no_token = tx_no_token.signature_hash();
1417 let user_hash_token1 = tx_token1.signature_hash();
1418 let user_hash_token2 = tx_token2.signature_hash();
1419
1420 assert_eq!(
1422 user_hash_no_token, user_hash_token1,
1423 "User hash should be the same regardless of fee_token (user skips fee_token)"
1424 );
1425 assert_eq!(
1426 user_hash_token1, user_hash_token2,
1427 "User hash should be the same regardless of fee_token (user skips fee_token)"
1428 );
1429 assert_eq!(
1430 user_hash_no_token, user_hash_token2,
1431 "User hash should be the same regardless of fee_token (user skips fee_token)"
1432 );
1433 }
1434
1435 #[test]
1436 fn test_fee_payer_signature_uses_magic_byte() {
1437 let sender = address!("0000000000000000000000000000000000000001");
1440 let dummy_call = Call {
1441 to: TxKind::Create,
1442 value: U256::ZERO,
1443 input: Bytes::new(),
1444 };
1445
1446 let tx = 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: Some(Signature::test_signature()),
1456 valid_before: Some(1000),
1457 ..Default::default()
1458 };
1459
1460 let sender_hash = tx.signature_hash();
1464 let fee_payer_hash = tx.fee_payer_signature_hash(sender);
1465
1466 assert_ne!(
1468 sender_hash, fee_payer_hash,
1469 "Sender and fee payer hashes should be different (different magic bytes)"
1470 );
1471 }
1472
1473 #[test]
1474 fn test_user_signature_without_fee_payer() {
1475 let token1 = address!("0000000000000000000000000000000000000002");
1478 let token2 = address!("0000000000000000000000000000000000000003");
1479
1480 let dummy_call = Call {
1481 to: TxKind::Create,
1482 value: U256::ZERO,
1483 input: Bytes::new(),
1484 };
1485
1486 let tx_no_payer_no_token = TempoTransaction {
1488 chain_id: 1,
1489 fee_token: None,
1490 max_priority_fee_per_gas: 1000000000,
1491 max_fee_per_gas: 2000000000,
1492 gas_limit: 21000,
1493 calls: vec![dummy_call],
1494 nonce_key: U256::ZERO,
1495 nonce: 1,
1496 fee_payer_signature: None, valid_before: Some(1000),
1498 valid_after: None,
1499 tempo_authorization_list: vec![],
1500 access_list: Default::default(),
1501 key_authorization: None,
1502 };
1503
1504 let tx_no_payer_token1 = TempoTransaction {
1506 fee_token: Some(token1),
1507 ..tx_no_payer_no_token.clone()
1508 };
1509
1510 let tx_no_payer_token2 = TempoTransaction {
1512 fee_token: Some(token2),
1513 ..tx_no_payer_no_token.clone()
1514 };
1515
1516 let hash_no_token = tx_no_payer_no_token.signature_hash();
1518 let hash_token1 = tx_no_payer_token1.signature_hash();
1519 let hash_token2 = tx_no_payer_token2.signature_hash();
1520
1521 assert_ne!(
1523 hash_no_token, hash_token1,
1524 "User hash should change when fee_token changes (no fee_payer)"
1525 );
1526 assert_ne!(
1527 hash_token1, hash_token2,
1528 "User hash should change when fee_token changes (no fee_payer)"
1529 );
1530 assert_ne!(
1531 hash_no_token, hash_token2,
1532 "User hash should change when fee_token changes (no fee_payer)"
1533 );
1534 }
1535
1536 #[test]
1537 fn test_rlp_encoding_includes_fee_token() {
1538 let token = address!("0000000000000000000000000000000000000002");
1541
1542 let dummy_call = Call {
1543 to: TxKind::Create,
1544 value: U256::ZERO,
1545 input: Bytes::new(),
1546 };
1547
1548 let tx_with_token = TempoTransaction {
1550 chain_id: 1,
1551 fee_token: Some(token),
1552 max_priority_fee_per_gas: 1000000000,
1553 max_fee_per_gas: 2000000000,
1554 gas_limit: 21000,
1555 calls: vec![dummy_call],
1556 nonce_key: U256::ZERO,
1557 nonce: 1,
1558 fee_payer_signature: Some(Signature::test_signature()),
1559 valid_before: Some(1000),
1560 valid_after: None,
1561 tempo_authorization_list: vec![],
1562 access_list: Default::default(),
1563 key_authorization: None,
1564 };
1565
1566 let tx_without_token = TempoTransaction {
1568 fee_token: None,
1569 ..tx_with_token.clone()
1570 };
1571
1572 let mut buf_with = Vec::new();
1574 tx_with_token.encode(&mut buf_with);
1575
1576 let mut buf_without = Vec::new();
1577 tx_without_token.encode(&mut buf_without);
1578
1579 assert_ne!(
1581 buf_with.len(),
1582 buf_without.len(),
1583 "RLP encoding should include fee_token in the encoded data"
1584 );
1585
1586 assert!(
1588 buf_with.len() > buf_without.len(),
1589 "Transaction with fee_token should have longer encoding"
1590 );
1591
1592 let decoded_with = TempoTransaction::decode(&mut buf_with.as_slice()).unwrap();
1594 let decoded_without = TempoTransaction::decode(&mut buf_without.as_slice()).unwrap();
1595
1596 assert_eq!(decoded_with.fee_token, Some(token));
1597 assert_eq!(decoded_without.fee_token, None);
1598 }
1599
1600 #[test]
1601 fn test_signature_hash_behavior_with_and_without_fee_payer() {
1602 let token = address!("0000000000000000000000000000000000000002");
1605
1606 let dummy_call = Call {
1607 to: TxKind::Create,
1608 value: U256::ZERO,
1609 input: Bytes::new(),
1610 };
1611
1612 let tx_no_payer_no_token = TempoTransaction {
1614 chain_id: 1,
1615 fee_token: None,
1616 max_priority_fee_per_gas: 1000000000,
1617 max_fee_per_gas: 2000000000,
1618 gas_limit: 21000,
1619 calls: vec![dummy_call],
1620 nonce_key: U256::ZERO,
1621 nonce: 1,
1622 fee_payer_signature: None,
1623 valid_before: Some(1000),
1624 valid_after: None,
1625 tempo_authorization_list: vec![],
1626 access_list: Default::default(),
1627 key_authorization: None,
1628 };
1629
1630 let tx_no_payer_with_token = TempoTransaction {
1632 fee_token: Some(token),
1633 ..tx_no_payer_no_token.clone()
1634 };
1635
1636 let tx_with_payer_no_token = TempoTransaction {
1638 fee_payer_signature: Some(Signature::test_signature()),
1639 ..tx_no_payer_no_token.clone()
1640 };
1641
1642 let tx_with_payer_with_token = TempoTransaction {
1644 fee_token: Some(token),
1645 fee_payer_signature: Some(Signature::test_signature()),
1646 ..tx_no_payer_no_token.clone()
1647 };
1648
1649 let hash1 = tx_no_payer_no_token.signature_hash();
1651 let hash2 = tx_no_payer_with_token.signature_hash();
1652 let hash3 = tx_with_payer_no_token.signature_hash();
1653 let hash4 = tx_with_payer_with_token.signature_hash();
1654
1655 assert_ne!(
1657 hash1, hash2,
1658 "User hash changes with fee_token when no fee_payer"
1659 );
1660
1661 assert_eq!(
1663 hash3, hash4,
1664 "User hash ignores fee_token when fee_payer is present"
1665 );
1666
1667 assert_ne!(hash1, hash3, "User hash changes when fee_payer is added");
1670 }
1671
1672 #[test]
1673 fn test_backwards_compatibility_key_authorization() {
1674 let call = Call {
1678 to: TxKind::Call(address!("0000000000000000000000000000000000000002")),
1679 value: U256::from(1000),
1680 input: Bytes::from(vec![1, 2, 3, 4]),
1681 };
1682
1683 let tx_without = TempoTransaction {
1685 chain_id: 1,
1686 fee_token: Some(address!("0000000000000000000000000000000000000001")),
1687 max_priority_fee_per_gas: 1000000000,
1688 max_fee_per_gas: 2000000000,
1689 gas_limit: 21000,
1690 calls: vec![call],
1691 access_list: Default::default(),
1692 nonce_key: U256::ZERO,
1693 nonce: 1,
1694 fee_payer_signature: Some(Signature::test_signature()),
1695 valid_before: Some(1000000),
1696 valid_after: Some(500000),
1697 key_authorization: None, tempo_authorization_list: vec![],
1699 };
1700
1701 let mut buf_without = Vec::new();
1703 tx_without.encode(&mut buf_without);
1704
1705 let decoded_without = TempoTransaction::decode(&mut buf_without.as_slice()).unwrap();
1707
1708 assert_eq!(decoded_without.key_authorization, None);
1710 assert_eq!(decoded_without.chain_id, tx_without.chain_id);
1711 assert_eq!(decoded_without.calls.len(), tx_without.calls.len());
1712
1713 let key_auth = KeyAuthorization {
1715 chain_id: 1, key_type: SignatureType::Secp256k1,
1717 expiry: Some(1234567890),
1718 limits: Some(vec![crate::transaction::TokenLimit {
1719 token: address!("0000000000000000000000000000000000000003"),
1720 limit: U256::from(10000),
1721 }]),
1722 key_id: address!("0000000000000000000000000000000000000004"),
1723 }
1724 .into_signed(PrimitiveSignature::Secp256k1(Signature::test_signature()));
1725
1726 let tx_with = TempoTransaction {
1727 key_authorization: Some(key_auth.clone()),
1728 ..tx_without.clone()
1729 };
1730
1731 let mut buf_with = Vec::new();
1733 tx_with.encode(&mut buf_with);
1734
1735 let decoded_with = TempoTransaction::decode(&mut buf_with.as_slice()).unwrap();
1737
1738 assert!(decoded_with.key_authorization.is_some());
1740 let decoded_key_auth = decoded_with.key_authorization.unwrap();
1741 assert_eq!(decoded_key_auth.key_type, key_auth.key_type);
1742 assert_eq!(decoded_key_auth.expiry, key_auth.expiry);
1743 assert_eq!(
1744 decoded_key_auth.limits.as_ref().map(|l| l.len()),
1745 key_auth.limits.as_ref().map(|l| l.len())
1746 );
1747 assert_eq!(decoded_key_auth.key_id, key_auth.key_id);
1748
1749 assert!(
1752 buf_without.len() < buf_with.len(),
1753 "Transaction without key_authorization should have shorter encoding"
1754 );
1755
1756 let decoded_old_format = TempoTransaction::decode(&mut buf_without.as_slice()).unwrap();
1760 assert_eq!(decoded_old_format.key_authorization, None);
1761 }
1762
1763 #[test]
1764 fn test_aa_signed_rlp_direct() {
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
1793 let mut buf = Vec::new();
1795 signed.rlp_encode(&mut buf);
1796
1797 let decoded =
1799 AASigned::rlp_decode(&mut buf.as_slice()).expect("Should decode AASigned RLP");
1800 assert_eq!(decoded.tx().key_authorization, None);
1801 }
1802
1803 #[test]
1804 fn test_tempo_transaction_envelope_roundtrip_without_key_auth() {
1805 use crate::TempoTxEnvelope;
1807 use alloy_eips::eip2718::{Decodable2718, Encodable2718};
1808
1809 let call = Call {
1810 to: TxKind::Create,
1811 value: U256::ZERO,
1812 input: Bytes::new(),
1813 };
1814
1815 let tx = TempoTransaction {
1816 chain_id: 0,
1817 fee_token: None,
1818 max_priority_fee_per_gas: 0,
1819 max_fee_per_gas: 0,
1820 gas_limit: 0,
1821 calls: vec![call],
1822 access_list: Default::default(),
1823 nonce_key: U256::ZERO,
1824 nonce: 0,
1825 fee_payer_signature: None,
1826 valid_before: None,
1827 valid_after: None,
1828 key_authorization: None, tempo_authorization_list: vec![],
1830 };
1831
1832 let signature =
1833 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature()));
1834 let signed = AASigned::new_unhashed(tx, signature);
1835 let envelope = TempoTxEnvelope::AA(signed);
1836
1837 let mut buf = Vec::new();
1839 envelope.encode_2718(&mut buf);
1840 let decoded = TempoTxEnvelope::decode_2718(&mut buf.as_slice())
1841 .expect("Should decode envelope successfully");
1842
1843 if let TempoTxEnvelope::AA(aa_signed) = decoded {
1845 assert_eq!(aa_signed.tx().key_authorization, None);
1846 assert_eq!(aa_signed.tx().calls.len(), 1);
1847 assert_eq!(aa_signed.tx().chain_id, 0);
1848 } else {
1849 panic!("Expected AA envelope");
1850 }
1851 }
1852
1853 #[test]
1854 fn test_call_decode_rejects_malformed_rlp() {
1855 let call = Call {
1857 to: TxKind::Call(address!("0000000000000000000000000000000000000002")),
1858 value: U256::from(1000),
1859 input: Bytes::from(vec![1, 2, 3, 4]),
1860 };
1861
1862 let mut buf = Vec::new();
1864 call.encode(&mut buf);
1865
1866 let original_len = buf.len();
1869 buf.truncate(original_len - 2); let result = Call::decode(&mut buf.as_slice());
1872 assert!(
1873 result.is_err(),
1874 "Decoding should fail when header length doesn't match"
1875 );
1876 assert!(matches!(
1878 result.unwrap_err(),
1879 alloy_rlp::Error::InputTooShort | alloy_rlp::Error::UnexpectedLength
1880 ));
1881 }
1882
1883 #[test]
1884 fn test_tempo_transaction_decode_rejects_malformed_rlp() {
1885 let call = Call {
1887 to: TxKind::Call(address!("0000000000000000000000000000000000000002")),
1888 value: U256::from(1000),
1889 input: Bytes::from(vec![1, 2, 3, 4]),
1890 };
1891
1892 let tx = TempoTransaction {
1893 chain_id: 1,
1894 fee_token: Some(address!("0000000000000000000000000000000000000001")),
1895 max_priority_fee_per_gas: 1000000000,
1896 max_fee_per_gas: 2000000000,
1897 gas_limit: 21000,
1898 calls: vec![call],
1899 access_list: Default::default(),
1900 nonce_key: U256::ZERO,
1901 nonce: 1,
1902 fee_payer_signature: Some(Signature::test_signature()),
1903 valid_before: Some(1000000),
1904 valid_after: Some(500000),
1905 key_authorization: None,
1906 tempo_authorization_list: vec![],
1907 };
1908
1909 let mut buf = Vec::new();
1911 tx.encode(&mut buf);
1912
1913 let original_len = buf.len();
1915 buf.truncate(original_len - 5); let result = TempoTransaction::decode(&mut buf.as_slice());
1918 assert!(
1919 result.is_err(),
1920 "Decoding should fail when data is truncated"
1921 );
1922 assert!(matches!(
1924 result.unwrap_err(),
1925 alloy_rlp::Error::InputTooShort | alloy_rlp::Error::UnexpectedLength
1926 ));
1927 }
1928
1929 #[test]
1930 #[cfg(feature = "serde")]
1931 fn call_serde() {
1932 let call: Call = serde_json::from_str(
1933 r#"{"to":"0x0000000000000000000000000000000000000002","value":"0x1","input":"0x1234"}"#,
1934 )
1935 .unwrap();
1936 assert_eq!(
1937 call.to,
1938 TxKind::Call(address!("0000000000000000000000000000000000000002"))
1939 );
1940 assert_eq!(call.value, U256::ONE);
1941 assert_eq!(call.input, bytes!("0x1234"));
1942 }
1943
1944 #[test]
1945 fn test_size_accounts_for_all_fields() {
1946 let dummy_call = Call {
1954 to: TxKind::Create,
1955 value: U256::ZERO,
1956 input: Bytes::new(),
1957 };
1958
1959 let tx = TempoTransaction {
1960 chain_id: 1,
1961 fee_token: None,
1962 max_priority_fee_per_gas: 0,
1963 max_fee_per_gas: 0,
1964 gas_limit: 0,
1965 calls: vec![dummy_call],
1966 access_list: Default::default(),
1967 nonce_key: U256::ZERO,
1968 nonce: 0,
1969 fee_payer_signature: None,
1970 valid_before: None,
1971 valid_after: None,
1972 key_authorization: None,
1973 tempo_authorization_list: vec![],
1974 };
1975
1976 let expected_size = mem::size_of::<ChainId>() + mem::size_of::<Option<Address>>() + mem::size_of::<u128>() + mem::size_of::<u128>() + mem::size_of::<u64>() + (mem::size_of::<TxKind>() + mem::size_of::<U256>()) + tx.access_list.size() + mem::size_of::<U256>() + mem::size_of::<u64>() + mem::size_of::<Option<Signature>>() + mem::size_of::<Option<u64>>() + mem::size_of::<Option<u64>>() + mem::size_of::<Option<KeyAuthorization>>(); assert_eq!(
1993 tx.size(),
1994 expected_size,
1995 "size() calculation doesn't match expected field sizes. \
1996 If you added/changed a field in TempoTransaction, update both size() and this test."
1997 );
1998 }
1999}