Skip to main content

tempo_primitives/transaction/
key_authorization.rs

1use super::SignatureType;
2use crate::transaction::PrimitiveSignature;
3use alloc::vec::Vec;
4use alloy_consensus::crypto::RecoveryError;
5use alloy_primitives::{Address, B256, U256, keccak256};
6use alloy_rlp::Encodable;
7use core::{
8    hash::{Hash, Hasher},
9    num::NonZeroU64,
10};
11
12#[cfg(not(feature = "std"))]
13use once_cell::race::OnceBox as OnceLock;
14#[cfg(feature = "std")]
15use std::sync::OnceLock;
16
17/// Token spending limit for access keys
18///
19/// Defines a per-token spending limit for an access key provisioned via key_authorization.
20/// This limit is enforced by the AccountKeychain precompile when the key is used.
21#[derive(Clone, Debug, PartialEq, Eq, Hash)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
24#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
25#[cfg_attr(feature = "reth-codec", derive(reth_codecs::Compact))]
26#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact, rlp))]
27pub struct TokenLimit {
28    /// TIP20 token address
29    pub token: Address,
30
31    /// Maximum spending amount for this token (enforced over the key's lifetime)
32    pub limit: U256,
33
34    /// Period duration in seconds.
35    ///
36    /// `0` means one-time limit. `>0` means the limit resets periodically.
37    #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity"))]
38    pub period: u64,
39}
40
41/// Per-target call scope for an access key.
42///
43/// `selector_rules` semantics:
44/// - `[]` => allow any selector for this target
45/// - `[rule1, ...]` => allow exactly the listed selector rules
46#[derive(Clone, Debug, PartialEq, Eq, Hash, alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
49#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
50#[cfg_attr(test, reth_codecs::add_arbitrary_tests(rlp))]
51pub struct CallScope {
52    /// Target contract address.
53    pub target: Address,
54    /// Selector rules for this target. Empty means any selector is allowed.
55    #[cfg_attr(
56        feature = "serde",
57        serde(default, skip_serializing_if = "Vec::is_empty")
58    )]
59    pub selector_rules: Vec<SelectorRule>,
60}
61
62impl CallScope {
63    /// Returns the target contract address.
64    pub fn target(&self) -> Address {
65        self.target
66    }
67
68    /// Returns `true` when any call to this target is allowed (no selector restrictions).
69    pub fn allows_all_selectors(&self) -> bool {
70        self.selector_rules.is_empty()
71    }
72
73    /// Returns the selector rules for this target.
74    pub fn selector_rules(&self) -> &[SelectorRule] {
75        &self.selector_rules
76    }
77
78    fn heap_size(&self) -> usize {
79        self.selector_rules.capacity() * size_of::<SelectorRule>()
80            + self
81                .selector_rules
82                .iter()
83                .map(SelectorRule::heap_size)
84                .sum::<usize>()
85    }
86}
87
88/// Selector-level rule within a [`CallScope`].
89///
90/// `recipients` semantics:
91/// - `[]` => no recipient constraint
92/// - `[a1, ...]` => first ABI address argument must be in this list
93#[derive(Clone, Debug, PartialEq, Eq, Hash, alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable)]
94#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
95#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
96#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
97#[cfg_attr(test, reth_codecs::add_arbitrary_tests(rlp))]
98pub struct SelectorRule {
99    /// 4-byte function selector.
100    #[cfg_attr(feature = "serde", serde(with = "selector_hex_serde"))]
101    pub selector: [u8; 4],
102    /// Recipient allowlist. Empty means no recipient restriction.
103    #[cfg_attr(
104        feature = "serde",
105        serde(default, skip_serializing_if = "Vec::is_empty")
106    )]
107    pub recipients: Vec<Address>,
108}
109
110impl SelectorRule {
111    /// Returns the 4-byte function selector.
112    pub fn selector(&self) -> [u8; 4] {
113        self.selector
114    }
115
116    /// Returns the allowed recipients for this selector.
117    pub fn recipients(&self) -> &[Address] {
118        &self.recipients
119    }
120
121    /// Returns `true` when any recipient is allowed (no recipient restriction).
122    pub fn allows_all_recipients(&self) -> bool {
123        self.recipients.is_empty()
124    }
125
126    fn heap_size(&self) -> usize {
127        self.recipients.capacity() * size_of::<Address>()
128    }
129}
130
131use tempo_contracts::precompiles::IAccountKeychain::{
132    CallScope as AbiCallScope, SelectorRule as AbiSelectorRule,
133};
134
135impl From<AbiCallScope> for CallScope {
136    fn from(scope: AbiCallScope) -> Self {
137        Self {
138            target: scope.target,
139            selector_rules: scope.selectorRules.into_iter().map(Into::into).collect(),
140        }
141    }
142}
143
144impl From<CallScope> for AbiCallScope {
145    fn from(scope: CallScope) -> Self {
146        Self {
147            target: scope.target,
148            selectorRules: scope.selector_rules.into_iter().map(Into::into).collect(),
149        }
150    }
151}
152
153impl From<AbiSelectorRule> for SelectorRule {
154    fn from(rule: AbiSelectorRule) -> Self {
155        Self {
156            selector: rule.selector.into(),
157            recipients: rule.recipients,
158        }
159    }
160}
161
162impl From<SelectorRule> for AbiSelectorRule {
163    fn from(rule: SelectorRule) -> Self {
164        Self {
165            selector: rule.selector.into(),
166            recipients: rule.recipients,
167        }
168    }
169}
170
171/// Key authorization for provisioning access keys
172///
173/// Used in TempoTransaction to add a new key to the AccountKeychain precompile.
174/// The transaction must be signed by the root key, or by an active admin key when authorizing for
175/// the admin key's account.
176///
177/// RLP encoding: `[chain_id, key_type, key_id, expiry?, limits?, allowed_calls?, witness?, is_admin?, account?]`
178/// - Non-optional fields come first, followed by optional (trailing) fields
179/// - `expiry`: `None` (omitted or 0x80) = key never expires, `Some(timestamp)` = expires at timestamp
180/// - `limits`: `None` (omitted or 0x80) = unlimited spending, `Some([])` = no spending, `Some([...])` = specific limits
181/// - `allowed_calls`: `None` (canonically omitted, explicit 0x80 accepted) = unrestricted,
182///   `Some([])` = scoped with no allowed calls, `Some([...])` = scoped calls
183/// - `witness`: `None` (canonically omitted) = no TIP-1053 witness,
184///   `Some(bytes32)` = arbitrary signed witness checked against the account's burned set.
185#[derive(Clone, Debug, PartialEq, Eq, Hash)]
186#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
187#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
188#[cfg_attr(test, reth_codecs::add_arbitrary_tests(rlp))]
189pub struct KeyAuthorization {
190    /// Chain ID for replay protection.
191    /// Pre-T1C: 0 = valid on any chain (wildcard). T1C+: must match current chain.
192    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
193    pub chain_id: u64,
194
195    /// Type of key being authorized (Secp256k1, P256, or WebAuthn)
196    pub key_type: SignatureType,
197
198    /// Key identifier, is the address derived from the public key of the key type.
199    pub key_id: Address,
200
201    /// Unix timestamp when key expires.
202    /// - `None` (RLP 0x80) = key never expires (stored as u64::MAX in precompile)
203    /// - `Some(timestamp)` = key expires at this timestamp
204    ///
205    /// This uses `Option<NonZeroU64>` so `Some(0)` is unrepresentable and cannot silently
206    /// roundtrip into `None`.
207    #[cfg_attr(feature = "serde", serde(with = "serde_nonzero_quantity_opt"))]
208    pub expiry: Option<NonZeroU64>,
209
210    /// TIP20 spending limits for this key.
211    /// - `None` (RLP 0x80) = unlimited spending (no limits enforced)
212    /// - `Some([])` = no spending allowed (enforce_limits=true but no tokens allowed)
213    /// - `Some([TokenLimit{...}])` = specific limits enforced
214    pub limits: Option<Vec<TokenLimit>>,
215
216    /// Optional call scopes for this key.
217    /// - `None` (canonically omitted, explicit 0x80 accepted) = unrestricted calls
218    /// - `Some([])` = scoped mode with no allowed calls
219    /// - `Some([CallScope{...}])` = explicit target/selector scope list
220    pub allowed_calls: Option<Vec<CallScope>>,
221
222    /// Optional TIP-1053 witness for offchain context binding and manual revocation.
223    ///
224    /// `None` means no witness. `Some(witness)` means the witness field is present, including when
225    /// `witness == B256::ZERO`.
226    pub witness: Option<B256>,
227
228    /// Whether this authorization creates an admin access key.
229    #[cfg_attr(feature = "serde", serde(default))]
230    pub is_admin: bool,
231
232    /// Account this authorization targets.
233    ///
234    /// Required for admin-signed authorizations so signatures cannot be replayed across accounts
235    /// that share the same admin key. Root-signed authorizations may omit it.
236    pub account: Option<Address>,
237}
238
239impl KeyAuthorization {
240    /// Create a fully unrestricted key authorization: no expiry, no spending limits, no call
241    /// scopes.
242    pub fn unrestricted(chain_id: u64, key_type: SignatureType, key_id: Address) -> Self {
243        Self {
244            chain_id,
245            key_type,
246            key_id,
247            expiry: None,
248            limits: None,
249            allowed_calls: None,
250            witness: None,
251            is_admin: false,
252            account: None,
253        }
254    }
255
256    /// Set an expiry timestamp on this key authorization.
257    pub fn with_expiry(mut self, expiry: u64) -> Self {
258        self.expiry = NonZeroU64::new(expiry);
259        self
260    }
261
262    /// Set token spending limits on this key authorization.
263    pub fn with_limits(mut self, limits: Vec<TokenLimit>) -> Self {
264        self.limits = Some(limits);
265        self
266    }
267
268    /// Set call-scope restrictions on this key authorization.
269    pub fn with_allowed_calls(mut self, allowed_calls: Vec<CallScope>) -> Self {
270        self.allowed_calls = Some(allowed_calls);
271        self
272    }
273
274    /// Deny all spending (enforce limits with an empty allowlist).
275    pub fn with_no_spending(mut self) -> Self {
276        self.limits = Some(Vec::new());
277        self
278    }
279
280    /// Deny all calls (scoped mode with an empty allowlist).
281    pub fn with_no_calls(mut self) -> Self {
282        self.allowed_calls = Some(Vec::new());
283        self
284    }
285
286    /// Attach a TIP-1053 witness to this authorization.
287    pub fn with_witness(mut self, witness: B256) -> Self {
288        self.witness = Some(witness);
289        self
290    }
291
292    /// Returns this authorization's TIP-1053 witness, if present.
293    pub fn witness(&self) -> Option<B256> {
294        self.witness
295    }
296
297    /// Convert this authorization into an account-bound admin-key authorization.
298    pub fn into_admin(mut self, account: Address) -> Self {
299        self.is_admin = true;
300        self.account = Some(account);
301        self
302    }
303
304    /// Bind this authorization to a target account without making the authorized key admin.
305    pub fn with_account(mut self, account: Address) -> Self {
306        self.account = Some(account);
307        self
308    }
309
310    /// Returns whether this authorization creates an admin key.
311    pub fn is_admin(&self) -> bool {
312        self.is_admin
313    }
314
315    /// Computes the authorization message hash for this key authorization.
316    pub fn signature_hash(&self) -> B256 {
317        let mut buf = Vec::new();
318        self.encode(&mut buf);
319        keccak256(&buf)
320    }
321
322    /// Returns whether any token limit uses periodic reset semantics.
323    pub fn has_periodic_limits(&self) -> bool {
324        self.limits
325            .as_ref()
326            .is_some_and(|limits| limits.iter().any(|limit| limit.period != 0))
327    }
328
329    /// Returns whether this authorization carries explicit call-scope restrictions.
330    pub fn has_call_scopes(&self) -> bool {
331        self.allowed_calls.is_some()
332    }
333
334    /// Returns whether this authorization carries a TIP-1053 witness field.
335    pub fn has_witness(&self) -> bool {
336        self.witness.is_some()
337    }
338
339    /// Returns whether this key has unlimited spending (limits is None)
340    pub fn has_unlimited_spending(&self) -> bool {
341        self.limits.is_none()
342    }
343
344    /// Returns whether this key never expires (expiry is None)
345    pub fn never_expires(&self) -> bool {
346        self.expiry.is_none()
347    }
348
349    /// Returns whether this authorization can be encoded with the legacy pre-T3 ABI.
350    pub fn is_legacy_compatible(&self) -> bool {
351        !(self.has_periodic_limits()
352            || self.has_call_scopes()
353            || self.has_witness()
354            || self.is_admin
355            || self.account.is_some())
356    }
357
358    /// Convert the key authorization into a [`SignedKeyAuthorization`] with a signature.
359    pub fn into_signed(self, signature: PrimitiveSignature) -> SignedKeyAuthorization {
360        SignedKeyAuthorization::new(self, signature)
361    }
362
363    /// Validates that this key authorization's `chain_id` is compatible with `expected_chain_id`.
364    ///
365    /// - Post-T1C: `chain_id` must exactly match (wildcard `0` is no longer allowed).
366    /// - Pre-T1C: `chain_id == 0` is a wildcard (valid on any chain), otherwise must match.
367    pub fn validate_chain_id(
368        &self,
369        expected_chain_id: u64,
370        is_t1c: bool,
371    ) -> Result<(), KeyAuthorizationChainIdError> {
372        if is_t1c {
373            if self.chain_id != expected_chain_id {
374                return Err(KeyAuthorizationChainIdError {
375                    expected: expected_chain_id,
376                    got: self.chain_id,
377                });
378            }
379        } else if self.chain_id != 0 && self.chain_id != expected_chain_id {
380            return Err(KeyAuthorizationChainIdError {
381                expected: expected_chain_id,
382                got: self.chain_id,
383            });
384        }
385        Ok(())
386    }
387
388    /// Calculates a heuristic for the in-memory size of the key authorization
389    pub fn size(&self) -> usize {
390        size_of::<Self>()
391            + self
392                .limits
393                .as_ref()
394                .map_or(0, |limits| limits.capacity() * size_of::<TokenLimit>())
395            + self.allowed_calls.as_ref().map_or(0, |scopes| {
396                scopes.capacity() * size_of::<CallScope>()
397                    + scopes.iter().map(CallScope::heap_size).sum::<usize>()
398            })
399    }
400}
401
402/// Error returned when a [`KeyAuthorization`]'s `chain_id` does not match the expected value.
403#[derive(Debug, Clone, Copy, PartialEq, Eq)]
404pub struct KeyAuthorizationChainIdError {
405    /// The expected chain ID (current chain).
406    pub expected: u64,
407    /// The chain ID from the KeyAuthorization.
408    pub got: u64,
409}
410
411/// Signed key authorization that can be attached to a transaction.
412#[derive(Clone, Debug, alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable, derive_more::Deref)]
413#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
414#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
415#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
416#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact, rlp))]
417pub struct SignedKeyAuthorization {
418    /// Key authorization for provisioning access keys
419    #[cfg_attr(feature = "serde", serde(flatten))]
420    #[deref]
421    pub authorization: KeyAuthorization,
422
423    /// Signature authorizing this key (signed by root key)
424    pub signature: PrimitiveSignature,
425
426    /// Cached signer recovered from `signature`.
427    ///
428    /// Excluded from encoding, equality, hashing, and arbitrary generation.
429    #[cfg_attr(feature = "serde", serde(skip))]
430    #[cfg_attr(any(test, feature = "arbitrary"), arbitrary(default))]
431    #[rlp(skip, default)]
432    signer: OnceLock<Address>,
433}
434
435impl SignedKeyAuthorization {
436    /// Create a signed key authorization with an empty signer cache.
437    pub fn new(authorization: KeyAuthorization, signature: PrimitiveSignature) -> Self {
438        Self {
439            authorization,
440            signature,
441            signer: OnceLock::new(),
442        }
443    }
444
445    /// Recover the signer of the [`KeyAuthorization`].
446    pub fn recover_signer(&self) -> Result<Address, RecoveryError> {
447        if let Some(signer) = self.signer.get() {
448            return Ok(*signer);
449        }
450
451        let signer = self
452            .signature
453            .recover_signer(&self.authorization.signature_hash())?;
454        self.cache_signer(signer);
455
456        Ok(signer)
457    }
458
459    #[cfg(feature = "std")]
460    fn cache_signer(&self, signer: Address) {
461        let _ = self.signer.set(signer);
462    }
463
464    #[cfg(not(feature = "std"))]
465    fn cache_signer(&self, signer: Address) {
466        let _ = self.signer.set(alloc::boxed::Box::new(signer));
467    }
468
469    /// Calculates a heuristic for the in-memory size of the signed key authorization
470    pub fn size(&self) -> usize {
471        self.authorization.size() + self.signature.size()
472    }
473}
474
475impl PartialEq for SignedKeyAuthorization {
476    fn eq(&self, other: &Self) -> bool {
477        self.authorization == other.authorization && self.signature == other.signature
478    }
479}
480
481impl Eq for SignedKeyAuthorization {}
482
483impl Hash for SignedKeyAuthorization {
484    fn hash<H: Hasher>(&self, state: &mut H) {
485        self.authorization.hash(state);
486        self.signature.hash(state);
487    }
488}
489
490#[cfg(any(test, feature = "arbitrary"))]
491impl<'a> arbitrary::Arbitrary<'a> for KeyAuthorization {
492    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
493        Ok(Self {
494            chain_id: u.arbitrary()?,
495            key_type: u.arbitrary()?,
496            key_id: u.arbitrary()?,
497            expiry: u.arbitrary()?,
498            limits: u.arbitrary()?,
499            allowed_calls: u.arbitrary()?,
500            witness: u.arbitrary::<Option<[u8; 32]>>()?.map(B256::from),
501            is_admin: u.arbitrary()?,
502            account: u.arbitrary()?,
503        })
504    }
505}
506
507#[cfg(feature = "serde")]
508#[doc(hidden)]
509pub mod serde_nonzero_quantity_opt {
510    use core::num::NonZeroU64;
511
512    use serde::{Deserializer, Serializer, de::Error as _};
513
514    pub fn serialize<S>(value: &Option<NonZeroU64>, serializer: S) -> Result<S::Ok, S::Error>
515    where
516        S: Serializer,
517    {
518        alloy_serde::quantity::opt::serialize(&value.map(NonZeroU64::get), serializer)
519    }
520
521    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<NonZeroU64>, D::Error>
522    where
523        D: Deserializer<'de>,
524    {
525        alloy_serde::quantity::opt::deserialize(deserializer).and_then(|value: Option<u64>| {
526            value
527                .map(|value| {
528                    NonZeroU64::new(value)
529                        .ok_or_else(|| D::Error::custom("expected non-zero quantity"))
530                })
531                .transpose()
532        })
533    }
534}
535
536mod rlp {
537    use super::*;
538    use alloy_rlp::{Decodable, Encodable};
539
540    #[derive(
541        Clone, Debug, PartialEq, Eq, Hash, alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable,
542    )]
543    #[rlp(trailing(canonical))]
544    struct KeyAuthorizationWire {
545        chain_id: u64,
546        key_type: SignatureType,
547        key_id: Address,
548        expiry: Option<NonZeroU64>,
549        limits: Option<Vec<TokenLimit>>,
550        allowed_calls: Option<Vec<CallScope>>,
551        witness: Option<B256>,
552        is_admin: Option<NonZeroU64>,
553        account: Option<Address>,
554    }
555
556    impl From<&KeyAuthorization> for KeyAuthorizationWire {
557        fn from(value: &KeyAuthorization) -> Self {
558            let KeyAuthorization {
559                chain_id,
560                key_type,
561                key_id,
562                expiry,
563                limits,
564                allowed_calls,
565                witness,
566                is_admin,
567                account,
568            } = value;
569
570            Self {
571                chain_id: *chain_id,
572                key_type: *key_type,
573                key_id: *key_id,
574                expiry: *expiry,
575                limits: limits.clone(),
576                allowed_calls: allowed_calls.clone(),
577                witness: *witness,
578                is_admin: is_admin.then_some(NonZeroU64::MIN),
579                account: *account,
580            }
581        }
582    }
583
584    impl TryFrom<KeyAuthorizationWire> for KeyAuthorization {
585        type Error = alloy_rlp::Error;
586
587        fn try_from(value: KeyAuthorizationWire) -> alloy_rlp::Result<Self> {
588            if value.is_admin.is_some_and(|marker| marker.get() != 1) {
589                return Err(alloy_rlp::Error::Custom(
590                    "invalid admin key authorization marker",
591                ));
592            }
593
594            Ok(Self {
595                chain_id: value.chain_id,
596                key_type: value.key_type,
597                key_id: value.key_id,
598                expiry: value.expiry,
599                limits: value.limits,
600                allowed_calls: value.allowed_calls,
601                witness: value.witness,
602                is_admin: value.is_admin.is_some(),
603                account: value.account,
604            })
605        }
606    }
607
608    impl Decodable for KeyAuthorization {
609        fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
610            KeyAuthorizationWire::decode(buf).and_then(TryInto::try_into)
611        }
612    }
613
614    impl Encodable for KeyAuthorization {
615        fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
616            KeyAuthorizationWire::from(self).encode(out);
617        }
618
619        fn length(&self) -> usize {
620            KeyAuthorizationWire::from(self).length()
621        }
622    }
623
624    #[derive(
625        Clone, Debug, PartialEq, Eq, Hash, alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable,
626    )]
627    #[rlp(trailing(canonical))]
628    struct TokenLimitWire {
629        token: Address,
630        limit: U256,
631        period: Option<NonZeroU64>,
632    }
633
634    impl From<&TokenLimit> for TokenLimitWire {
635        fn from(value: &TokenLimit) -> Self {
636            let TokenLimit {
637                token,
638                limit,
639                period,
640            } = value;
641            Self {
642                token: *token,
643                limit: *limit,
644                period: NonZeroU64::new(*period),
645            }
646        }
647    }
648
649    impl From<TokenLimitWire> for TokenLimit {
650        fn from(value: TokenLimitWire) -> Self {
651            Self {
652                token: value.token,
653                limit: value.limit,
654                period: value.period.map(|period| period.get()).unwrap_or(0),
655            }
656        }
657    }
658
659    impl Decodable for TokenLimit {
660        fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
661            Ok(TokenLimitWire::decode(buf)?.into())
662        }
663    }
664
665    impl Encodable for TokenLimit {
666        fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
667            TokenLimitWire::from(self).encode(out)
668        }
669
670        fn length(&self) -> usize {
671            TokenLimitWire::from(self).length()
672        }
673    }
674}
675
676#[cfg(feature = "serde")]
677mod selector_hex_serde {
678    use alloy_primitives::FixedBytes;
679    use serde::{Deserialize, Deserializer, Serialize, Serializer};
680
681    #[derive(Deserialize)]
682    #[serde(untagged)]
683    enum SelectorValue {
684        Hex(FixedBytes<4>),
685        Array([u8; 4]),
686    }
687
688    pub(super) fn serialize<S>(selector: &[u8; 4], serializer: S) -> Result<S::Ok, S::Error>
689    where
690        S: Serializer,
691    {
692        FixedBytes::<4>::from(*selector).serialize(serializer)
693    }
694
695    pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 4], D::Error>
696    where
697        D: Deserializer<'de>,
698    {
699        Ok(match SelectorValue::deserialize(deserializer)? {
700            SelectorValue::Hex(selector) => selector.into(),
701            SelectorValue::Array(selector) => selector,
702        })
703    }
704}
705
706#[cfg(test)]
707mod tests {
708    use super::*;
709    use crate::transaction::{
710        TempoSignature,
711        tt_authorization::tests::{generate_secp256k1_keypair, sign_hash},
712    };
713    use alloy_rlp::{Decodable, Encodable};
714
715    fn nonzero(value: u64) -> NonZeroU64 {
716        NonZeroU64::new(value).expect("test expiry must be non-zero")
717    }
718
719    fn make_auth(expiry: Option<u64>, limits: Option<Vec<TokenLimit>>) -> KeyAuthorization {
720        KeyAuthorization {
721            chain_id: 1,
722            key_type: SignatureType::Secp256k1,
723            key_id: Address::random(),
724            expiry: expiry.and_then(NonZeroU64::new),
725            limits,
726            allowed_calls: None,
727            witness: None,
728            is_admin: false,
729            account: None,
730        }
731    }
732
733    #[test]
734    fn test_zero_witness_roundtrip_and_changes_signature_hash() {
735        let auth = make_auth(None, None);
736        let zero_witness_auth = auth.clone().with_witness(B256::ZERO);
737
738        let mut encoded = Vec::new();
739        zero_witness_auth.encode(&mut encoded);
740
741        assert_eq!(zero_witness_auth.witness(), Some(B256::ZERO));
742        assert!(zero_witness_auth.has_witness());
743        assert_ne!(zero_witness_auth.signature_hash(), auth.signature_hash());
744
745        let decoded =
746            <KeyAuthorization as Decodable>::decode(&mut encoded.as_slice()).expect("decode auth");
747        assert_eq!(decoded, zero_witness_auth);
748
749        let mut reencoded = Vec::new();
750        decoded.encode(&mut reencoded);
751        assert_eq!(reencoded, encoded);
752    }
753
754    #[test]
755    fn test_witness_roundtrip_and_changes_signature_hash() {
756        let auth = make_auth(None, None);
757        let witness = B256::repeat_byte(0x53);
758        let witness_auth = auth.clone().with_witness(witness);
759
760        assert_eq!(witness_auth.witness(), Some(witness));
761        assert!(witness_auth.has_witness());
762        assert_ne!(witness_auth.signature_hash(), auth.signature_hash());
763
764        let mut encoded = Vec::new();
765        witness_auth.encode(&mut encoded);
766
767        let decoded =
768            <KeyAuthorization as Decodable>::decode(&mut encoded.as_slice()).expect("decode auth");
769        assert_eq!(decoded, witness_auth);
770
771        let mut reencoded = Vec::new();
772        decoded.encode(&mut reencoded);
773        assert_eq!(reencoded, encoded);
774    }
775
776    #[test]
777    fn test_account_roundtrip_and_signature_binding() {
778        let account = Address::repeat_byte(0x11);
779        let other_account = Address::repeat_byte(0x22);
780        let key_id = Address::repeat_byte(0x33);
781        let witness = B256::repeat_byte(0x44);
782
783        let normal = KeyAuthorization::unrestricted(1, SignatureType::Secp256k1, key_id)
784            .with_witness(witness);
785        let admin = normal.clone().into_admin(account);
786        let other_admin = normal.clone().into_admin(other_account);
787        let account_bound = normal.clone().with_account(account);
788        let other_account_bound = normal.clone().with_account(other_account);
789
790        assert!(!normal.is_admin());
791        assert!(admin.is_admin());
792        assert!(admin.is_admin);
793        assert_eq!(admin.account, Some(account));
794        assert!(!admin.is_legacy_compatible());
795        assert!(!account_bound.is_admin());
796        assert!(!account_bound.is_admin);
797        assert_eq!(account_bound.account, Some(account));
798        assert!(!account_bound.is_legacy_compatible());
799
800        let mut encoded = Vec::new();
801        admin.encode(&mut encoded);
802        let decoded =
803            <KeyAuthorization as Decodable>::decode(&mut encoded.as_slice()).expect("decode auth");
804        assert_eq!(decoded, admin);
805        assert_eq!(decoded.witness(), Some(witness));
806        assert!(decoded.is_admin);
807        assert_eq!(decoded.account, Some(account));
808
809        assert_ne!(admin.signature_hash(), normal.signature_hash());
810        assert_ne!(admin.signature_hash(), other_admin.signature_hash());
811        assert_ne!(account_bound.signature_hash(), normal.signature_hash());
812        assert_ne!(
813            account_bound.signature_hash(),
814            other_account_bound.signature_hash()
815        );
816    }
817
818    #[test]
819    fn test_witness_encoding_preserves_prior_absent_trailing_fields() {
820        let witness = B256::repeat_byte(0x53);
821        let auth = make_auth(None, None).with_witness(witness);
822
823        let mut encoded = Vec::new();
824        auth.encode(&mut encoded);
825
826        let mut payload = &encoded[..];
827        let header = alloy_rlp::Header::decode(&mut payload).expect("decode list header");
828        assert!(header.list);
829        assert_eq!(header.payload_length, payload.len());
830
831        let fixed_fields_len =
832            auth.chain_id.length() + auth.key_type.length() + auth.key_id.length();
833        assert_eq!(
834            &payload[fixed_fields_len..fixed_fields_len + 3],
835            &[alloy_rlp::EMPTY_STRING_CODE; 3],
836            "expiry, limits, and allowed_calls must be explicit empty placeholders before witness"
837        );
838
839        let mut witness_payload = &payload[fixed_fields_len + 3..];
840        let decoded_witness = B256::decode(&mut witness_payload).expect("decode witness field");
841        assert_eq!(decoded_witness, witness);
842        assert!(witness_payload.is_empty());
843    }
844
845    #[test]
846    fn test_decode_accepts_explicit_zero_witness() {
847        let auth = make_auth(None, None);
848        let mut encoded = Vec::new();
849        let payload_length = auth.chain_id.length()
850            + auth.key_type.length()
851            + auth.key_id.length()
852            + 3
853            + B256::ZERO.length();
854        alloy_rlp::Header {
855            list: true,
856            payload_length,
857        }
858        .encode(&mut encoded);
859        auth.chain_id.encode(&mut encoded);
860        auth.key_type.encode(&mut encoded);
861        auth.key_id.encode(&mut encoded);
862        encoded.extend_from_slice(&[alloy_rlp::EMPTY_STRING_CODE; 3]);
863        B256::ZERO.encode(&mut encoded);
864
865        let decoded =
866            <KeyAuthorization as Decodable>::decode(&mut encoded.as_slice()).expect("decode auth");
867        assert_eq!(decoded.witness(), Some(B256::ZERO));
868    }
869
870    #[test]
871    fn test_decode_rejects_explicit_absent_witness_field() {
872        let auth = make_auth(None, None);
873        let mut encoded = Vec::new();
874        let payload_length =
875            auth.chain_id.length() + auth.key_type.length() + auth.key_id.length() + 4;
876        alloy_rlp::Header {
877            list: true,
878            payload_length,
879        }
880        .encode(&mut encoded);
881        auth.chain_id.encode(&mut encoded);
882        auth.key_type.encode(&mut encoded);
883        auth.key_id.encode(&mut encoded);
884        encoded.extend_from_slice(&[alloy_rlp::EMPTY_STRING_CODE; 4]);
885
886        <KeyAuthorization as Decodable>::decode(&mut encoded.as_slice())
887            .expect_err("absent witness field must be omitted, not encoded as 0x80");
888    }
889
890    #[test]
891    fn test_signature_hash_and_recover_signer() {
892        let (signing_key, expected_address) = generate_secp256k1_keypair();
893
894        let auth = make_auth(Some(1000), None);
895
896        // Hash determinism
897        let hash1 = auth.signature_hash();
898        let hash2 = auth.signature_hash();
899        assert_eq!(hash1, hash2, "signature_hash should be deterministic");
900        assert_ne!(hash1, B256::ZERO);
901
902        // Different auth produces different hash
903        let auth2 = make_auth(Some(2000), None);
904        assert_ne!(auth.signature_hash(), auth2.signature_hash());
905
906        // Sign and recover
907        let signature = sign_hash(&signing_key, &auth.signature_hash());
908        let inner_sig = match signature {
909            TempoSignature::Primitive(p) => p,
910            _ => panic!("Expected primitive signature"),
911        };
912        let signed = auth.clone().into_signed(inner_sig);
913
914        // Recovery should succeed with correct address
915        let recovered = signed.recover_signer();
916        assert!(recovered.is_ok());
917        assert_eq!(recovered.unwrap(), expected_address);
918
919        // Wrong signature hash yields wrong address
920        let wrong_sig = sign_hash(&signing_key, &B256::random());
921        let wrong_inner = match wrong_sig {
922            TempoSignature::Primitive(p) => p,
923            _ => panic!("Expected primitive signature"),
924        };
925        let bad_signed = auth.into_signed(wrong_inner);
926        let bad_recovered = bad_signed.recover_signer();
927        assert!(bad_recovered.is_ok());
928        assert_ne!(bad_recovered.unwrap(), expected_address);
929    }
930
931    #[test]
932    fn test_spending_expiry_and_size() {
933        // has_unlimited_spending: None = true, Some = false
934        assert!(make_auth(None, None).has_unlimited_spending());
935        assert!(!make_auth(None, Some(vec![])).has_unlimited_spending());
936        assert!(
937            !make_auth(
938                None,
939                Some(vec![TokenLimit {
940                    token: Address::ZERO,
941                    limit: U256::from(100),
942                    period: 0,
943                }])
944            )
945            .has_unlimited_spending()
946        );
947
948        // never_expires: None = true, Some = false
949        assert!(make_auth(None, None).never_expires());
950        assert!(!make_auth(Some(1000), None).never_expires());
951        assert_eq!(NonZeroU64::new(0), None);
952    }
953
954    #[test]
955    fn test_size_does_not_double_count_call_scope_structs() {
956        let recipients = vec![Address::repeat_byte(0x11), Address::repeat_byte(0x22)];
957        let mut rules = Vec::with_capacity(3);
958        rules.push(SelectorRule {
959            selector: [1, 2, 3, 4],
960            recipients,
961        });
962
963        let mut scopes = Vec::with_capacity(2);
964        scopes.push(CallScope {
965            target: Address::repeat_byte(0x33),
966            selector_rules: rules,
967        });
968
969        let auth =
970            KeyAuthorization::unrestricted(1, SignatureType::Secp256k1, Address::repeat_byte(0x44))
971                .with_allowed_calls(scopes);
972
973        let scope_rules = auth.allowed_calls.as_ref().unwrap();
974        let selector_rules = &scope_rules[0].selector_rules;
975        let recipients = &selector_rules[0].recipients;
976
977        let expected = size_of::<KeyAuthorization>()
978            + scope_rules.capacity() * size_of::<CallScope>()
979            + selector_rules.capacity() * size_of::<SelectorRule>()
980            + recipients.capacity() * size_of::<Address>();
981
982        assert_eq!(auth.size(), expected);
983    }
984
985    #[test]
986    fn test_zero_expiry_is_unrepresentable() {
987        assert_eq!(NonZeroU64::new(0), None);
988        assert_eq!(Some(NonZeroU64::get(nonzero(1))), Some(1));
989    }
990
991    fn make_auth_with_chain_id(chain_id: u64) -> KeyAuthorization {
992        KeyAuthorization {
993            chain_id,
994            key_type: SignatureType::Secp256k1,
995            key_id: Address::random(),
996            expiry: None,
997            limits: None,
998            allowed_calls: None,
999            witness: None,
1000            is_admin: false,
1001            account: None,
1002        }
1003    }
1004
1005    #[test]
1006    fn test_token_limit_legacy_decode_defaults_period_to_zero() {
1007        let token = Address::random();
1008        let limit = U256::from(42);
1009
1010        // Legacy pre-T3 payloads encode TokenLimit as [token, limit].
1011        let mut encoded = Vec::new();
1012        alloy_rlp::Header {
1013            list: true,
1014            payload_length: token.length() + limit.length(),
1015        }
1016        .encode(&mut encoded);
1017        token.encode(&mut encoded);
1018        limit.encode(&mut encoded);
1019
1020        let decoded: TokenLimit =
1021            Decodable::decode(&mut encoded.as_slice()).expect("decode legacy token limit");
1022        assert_eq!(decoded.token, token);
1023        assert_eq!(decoded.limit, limit);
1024        assert_eq!(decoded.period, 0);
1025    }
1026
1027    #[test]
1028    fn test_token_limit_encoding_omits_zero_period() {
1029        let token_limit = TokenLimit {
1030            token: Address::random(),
1031            limit: U256::from(1234),
1032            period: 0,
1033        };
1034
1035        let mut encoded = Vec::new();
1036        token_limit.encode(&mut encoded);
1037
1038        let mut payload = &encoded[..];
1039        let header = alloy_rlp::Header::decode(&mut payload).expect("decode list header");
1040        assert!(header.list);
1041        assert_eq!(
1042            header.payload_length,
1043            token_limit.token.length() + token_limit.limit.length()
1044        );
1045    }
1046
1047    #[test]
1048    fn test_token_limit_decode_accepts_explicit_zero_period_field() {
1049        let token = Address::random();
1050        let limit = U256::from(42);
1051
1052        let mut encoded = Vec::new();
1053        alloy_rlp::Header {
1054            list: true,
1055            payload_length: token.length() + limit.length(),
1056        }
1057        .encode(&mut encoded);
1058        token.encode(&mut encoded);
1059        limit.encode(&mut encoded);
1060
1061        let decoded: TokenLimit =
1062            <TokenLimit as Decodable>::decode(&mut encoded.as_slice()).expect("decode token limit");
1063        assert_eq!(decoded.token, token);
1064        assert_eq!(decoded.limit, limit);
1065        assert_eq!(decoded.period, 0);
1066    }
1067
1068    #[test]
1069    fn test_key_authorization_roundtrip_preserves_explicit_nested_allow_all_lists() {
1070        let auth =
1071            KeyAuthorization::unrestricted(1, SignatureType::Secp256k1, Address::repeat_byte(0x11))
1072                .with_allowed_calls(vec![
1073                    CallScope {
1074                        target: Address::repeat_byte(0x22),
1075                        selector_rules: vec![],
1076                    },
1077                    CallScope {
1078                        target: Address::repeat_byte(0x33),
1079                        selector_rules: vec![SelectorRule {
1080                            selector: [0xaa, 0xbb, 0xcc, 0xdd],
1081                            recipients: vec![],
1082                        }],
1083                    },
1084                ]);
1085
1086        let mut encoded = Vec::new();
1087        auth.encode(&mut encoded);
1088
1089        let decoded =
1090            <KeyAuthorization as Decodable>::decode(&mut encoded.as_slice()).expect("decode auth");
1091
1092        let mut reencoded = Vec::new();
1093        decoded.encode(&mut reencoded);
1094
1095        assert_eq!(reencoded, encoded);
1096    }
1097
1098    #[test]
1099    fn test_call_scope_decode_rejects_omitted_selector_rules() {
1100        let target = Address::repeat_byte(0x11);
1101
1102        let mut encoded = Vec::new();
1103        alloy_rlp::Header {
1104            list: true,
1105            payload_length: target.length(),
1106        }
1107        .encode(&mut encoded);
1108        target.encode(&mut encoded);
1109
1110        <CallScope as Decodable>::decode(&mut encoded.as_slice())
1111            .expect_err("omitted selector_rules should be rejected");
1112    }
1113
1114    #[test]
1115    fn test_call_scope_explicit_empty_selector_rules_roundtrip() {
1116        let scope = CallScope {
1117            target: Address::repeat_byte(0x11),
1118            selector_rules: Vec::new(),
1119        };
1120
1121        let mut encoded = Vec::new();
1122        scope.encode(&mut encoded);
1123
1124        let mut payload = &encoded[..];
1125        let header = alloy_rlp::Header::decode(&mut payload).expect("decode list header");
1126        assert!(header.list);
1127        assert_eq!(
1128            header.payload_length,
1129            scope.target.length() + Vec::<SelectorRule>::new().length()
1130        );
1131
1132        let decoded =
1133            <CallScope as Decodable>::decode(&mut encoded.as_slice()).expect("decode scope");
1134        assert_eq!(decoded, scope);
1135    }
1136
1137    #[test]
1138    fn test_call_scope_decode_accepts_explicit_empty_selector_rules_list() {
1139        let target = Address::repeat_byte(0x11);
1140
1141        let mut encoded = Vec::new();
1142        alloy_rlp::Header {
1143            list: true,
1144            payload_length: target.length() + Vec::<SelectorRule>::new().length(),
1145        }
1146        .encode(&mut encoded);
1147        target.encode(&mut encoded);
1148        Vec::<SelectorRule>::new().encode(&mut encoded);
1149
1150        let decoded =
1151            <CallScope as Decodable>::decode(&mut encoded.as_slice()).expect("decode scope");
1152        assert_eq!(decoded.target, target);
1153        assert!(decoded.selector_rules.is_empty());
1154
1155        let mut reencoded = Vec::new();
1156        decoded.encode(&mut reencoded);
1157        assert_eq!(reencoded, encoded);
1158    }
1159
1160    #[test]
1161    fn test_selector_rule_decode_rejects_omitted_recipients() {
1162        let selector = [0xaa, 0xbb, 0xcc, 0xdd];
1163
1164        let mut encoded = Vec::new();
1165        alloy_rlp::Header {
1166            list: true,
1167            payload_length: selector.length(),
1168        }
1169        .encode(&mut encoded);
1170        selector.encode(&mut encoded);
1171
1172        <SelectorRule as Decodable>::decode(&mut encoded.as_slice())
1173            .expect_err("omitted recipients should be rejected");
1174    }
1175
1176    #[test]
1177    fn test_selector_rule_empty_recipients_roundtrip() {
1178        let rule = SelectorRule {
1179            selector: [0xaa, 0xbb, 0xcc, 0xdd],
1180            recipients: Vec::new(),
1181        };
1182
1183        let mut encoded = Vec::new();
1184        rule.encode(&mut encoded);
1185
1186        let mut payload = &encoded[..];
1187        let header = alloy_rlp::Header::decode(&mut payload).expect("decode list header");
1188        assert!(header.list);
1189        assert_eq!(
1190            header.payload_length,
1191            rule.selector.length() + Vec::<Address>::new().length()
1192        );
1193
1194        let decoded =
1195            <SelectorRule as Decodable>::decode(&mut encoded.as_slice()).expect("decode rule");
1196        assert_eq!(decoded, rule);
1197    }
1198
1199    #[test]
1200    fn test_selector_rule_decode_accepts_explicit_empty_recipient_list() {
1201        let selector = [0xaa, 0xbb, 0xcc, 0xdd];
1202
1203        let mut encoded = Vec::new();
1204        alloy_rlp::Header {
1205            list: true,
1206            payload_length: selector.length() + Vec::<Address>::new().length(),
1207        }
1208        .encode(&mut encoded);
1209        selector.encode(&mut encoded);
1210        Vec::<Address>::new().encode(&mut encoded);
1211
1212        let decoded =
1213            <SelectorRule as Decodable>::decode(&mut encoded.as_slice()).expect("decode rule");
1214        assert_eq!(decoded.selector, selector);
1215        assert!(decoded.recipients.is_empty());
1216
1217        let mut reencoded = Vec::new();
1218        decoded.encode(&mut reencoded);
1219        assert_eq!(reencoded, encoded);
1220    }
1221
1222    #[test]
1223    fn test_selector_rule_roundtrip_preserves_non_empty_recipient_list() {
1224        let rule = SelectorRule {
1225            selector: [0xaa, 0xbb, 0xcc, 0xdd],
1226            recipients: vec![Address::repeat_byte(0x11), Address::repeat_byte(0x22)],
1227        };
1228
1229        let mut encoded = Vec::new();
1230        rule.encode(&mut encoded);
1231
1232        let decoded =
1233            <SelectorRule as Decodable>::decode(&mut encoded.as_slice()).expect("decode rule");
1234        assert_eq!(decoded, rule);
1235    }
1236
1237    #[cfg(feature = "serde")]
1238    #[test]
1239    fn test_token_limit_json_defaults_period_to_zero() {
1240        let token = Address::repeat_byte(0x11);
1241
1242        let decoded: TokenLimit = serde_json::from_value(serde_json::json!({
1243            "token": token,
1244            "limit": "0x2a",
1245        }))
1246        .expect("deserialize legacy JSON token limit");
1247
1248        assert_eq!(decoded.token, token);
1249        assert_eq!(decoded.limit, U256::from(42));
1250        assert_eq!(decoded.period, 0);
1251    }
1252
1253    #[cfg(feature = "serde")]
1254    #[test]
1255    fn test_token_limit_json_serializes_period_as_quantity() {
1256        let value = serde_json::to_value(TokenLimit {
1257            token: Address::repeat_byte(0x11),
1258            limit: U256::from(42),
1259            period: 7,
1260        })
1261        .expect("serialize token limit");
1262
1263        assert_eq!(value["period"], serde_json::json!("0x7"));
1264    }
1265
1266    #[cfg(feature = "serde")]
1267    #[test]
1268    fn test_selector_rule_json_accepts_hex_selector() {
1269        let recipient = Address::repeat_byte(0x11);
1270
1271        let decoded: SelectorRule = serde_json::from_value(serde_json::json!({
1272            "selector": "0xaabbccdd",
1273            "recipients": [recipient],
1274        }))
1275        .expect("deserialize selector rule with hex selector");
1276
1277        assert_eq!(decoded.selector, [0xaa, 0xbb, 0xcc, 0xdd]);
1278        assert_eq!(decoded.recipients, vec![recipient]);
1279    }
1280
1281    #[cfg(feature = "serde")]
1282    #[test]
1283    fn test_selector_rule_json_accepts_legacy_selector_array() {
1284        let decoded: SelectorRule = serde_json::from_value(serde_json::json!({
1285            "selector": [170, 187, 204, 221],
1286            "recipients": [],
1287        }))
1288        .expect("deserialize selector rule with legacy selector array");
1289
1290        assert_eq!(decoded.selector, [0xaa, 0xbb, 0xcc, 0xdd]);
1291        assert!(decoded.recipients.is_empty());
1292    }
1293
1294    #[cfg(feature = "serde")]
1295    #[test]
1296    fn test_selector_rule_json_serializes_selector_as_hex() {
1297        let value = serde_json::to_value(SelectorRule {
1298            selector: [0xaa, 0xbb, 0xcc, 0xdd],
1299            recipients: Vec::new(),
1300        })
1301        .expect("serialize selector rule");
1302
1303        assert_eq!(value["selector"], serde_json::json!("0xaabbccdd"));
1304    }
1305
1306    #[cfg(feature = "serde")]
1307    #[test]
1308    fn test_key_authorization_json_rejects_zero_expiry() {
1309        let err = serde_json::from_value::<KeyAuthorization>(serde_json::json!({
1310            "chainId": "0x1",
1311            "keyType": "secp256k1",
1312            "keyId": Address::repeat_byte(0x11),
1313            "expiry": "0x0",
1314        }))
1315        .expect_err("zero expiry must be rejected");
1316
1317        assert!(err.to_string().contains("expected non-zero quantity"));
1318    }
1319
1320    #[test]
1321    fn test_key_authorization_decode_accepts_explicit_unrestricted_allowed_calls_field() {
1322        let chain_id = 1u64;
1323        let key_type = SignatureType::Secp256k1;
1324        let key_id = Address::random();
1325
1326        let mut payload = Vec::new();
1327        chain_id.encode(&mut payload);
1328        key_type.encode(&mut payload);
1329        key_id.encode(&mut payload);
1330
1331        let mut encoded = Vec::new();
1332        alloy_rlp::Header {
1333            list: true,
1334            payload_length: payload.len(),
1335        }
1336        .encode(&mut encoded);
1337        encoded.extend_from_slice(&payload);
1338
1339        let decoded =
1340            <KeyAuthorization as Decodable>::decode(&mut encoded.as_slice()).expect("decode auth");
1341        assert_eq!(decoded.chain_id, chain_id);
1342        assert_eq!(decoded.key_type, key_type);
1343        assert_eq!(decoded.key_id, key_id);
1344        assert_eq!(decoded.expiry, None);
1345        assert_eq!(decoded.limits, None);
1346        assert_eq!(decoded.allowed_calls, None);
1347
1348        let mut reencoded = Vec::new();
1349        decoded.encode(&mut reencoded);
1350        assert_eq!(reencoded.len(), encoded.len());
1351    }
1352
1353    #[test]
1354    fn test_key_authorization_decode_accepts_explicit_deny_all_allowed_calls_field() {
1355        let chain_id = 1u64;
1356        let key_type = SignatureType::Secp256k1;
1357        let key_id = Address::random();
1358
1359        let mut payload = Vec::new();
1360        chain_id.encode(&mut payload);
1361        key_type.encode(&mut payload);
1362        key_id.encode(&mut payload);
1363        payload.extend_from_slice(&[
1364            alloy_rlp::EMPTY_STRING_CODE,
1365            alloy_rlp::EMPTY_STRING_CODE,
1366            0xc0,
1367        ]);
1368
1369        let mut encoded = Vec::new();
1370        alloy_rlp::Header {
1371            list: true,
1372            payload_length: payload.len(),
1373        }
1374        .encode(&mut encoded);
1375        encoded.extend_from_slice(&payload);
1376
1377        let decoded =
1378            <KeyAuthorization as Decodable>::decode(&mut encoded.as_slice()).expect("decode auth");
1379        assert_eq!(decoded.chain_id, chain_id);
1380        assert_eq!(decoded.key_type, key_type);
1381        assert_eq!(decoded.key_id, key_id);
1382        assert_eq!(decoded.expiry, None);
1383        assert_eq!(decoded.limits, None);
1384        assert_eq!(decoded.allowed_calls, Some(vec![]));
1385
1386        let mut reencoded = Vec::new();
1387        decoded.encode(&mut reencoded);
1388        assert_eq!(reencoded, encoded);
1389    }
1390
1391    #[test]
1392    fn test_validate_chain_id_pre_t1c() {
1393        let expected = 42431;
1394
1395        // Matching chain_id → ok
1396        assert!(
1397            make_auth_with_chain_id(expected)
1398                .validate_chain_id(expected, false)
1399                .is_ok()
1400        );
1401
1402        // Wildcard chain_id=0 → ok pre-T1C
1403        assert!(
1404            make_auth_with_chain_id(0)
1405                .validate_chain_id(expected, false)
1406                .is_ok()
1407        );
1408
1409        // Wrong chain_id → err
1410        let err = make_auth_with_chain_id(999)
1411            .validate_chain_id(expected, false)
1412            .unwrap_err();
1413        assert_eq!(err.expected, expected);
1414        assert_eq!(err.got, 999);
1415    }
1416
1417    #[test]
1418    fn test_validate_chain_id_post_t1c() {
1419        let expected = 42431;
1420
1421        // Matching chain_id → ok
1422        assert!(
1423            make_auth_with_chain_id(expected)
1424                .validate_chain_id(expected, true)
1425                .is_ok()
1426        );
1427
1428        // Wildcard chain_id=0 → rejected post-T1C
1429        let err = make_auth_with_chain_id(0)
1430            .validate_chain_id(expected, true)
1431            .unwrap_err();
1432        assert_eq!(err.expected, expected);
1433        assert_eq!(err.got, 0);
1434
1435        // Wrong chain_id → rejected
1436        let err = make_auth_with_chain_id(999)
1437            .validate_chain_id(expected, true)
1438            .unwrap_err();
1439        assert_eq!(err.expected, expected);
1440        assert_eq!(err.got, 999);
1441    }
1442
1443    #[test]
1444    fn test_call_scope_accessors() {
1445        let target = Address::repeat_byte(0x11);
1446        let rule = SelectorRule {
1447            selector: [0xaa, 0xbb, 0xcc, 0xdd],
1448            recipients: vec![Address::repeat_byte(0x22)],
1449        };
1450        let scope = CallScope {
1451            target,
1452            selector_rules: vec![rule],
1453        };
1454
1455        assert_eq!(scope.target(), target);
1456        assert!(!scope.allows_all_selectors());
1457        assert_eq!(scope.selector_rules().len(), 1);
1458    }
1459
1460    #[test]
1461    fn test_call_scope_allows_all_selectors_when_empty() {
1462        let scope = CallScope {
1463            target: Address::repeat_byte(0x11),
1464            selector_rules: vec![],
1465        };
1466        assert!(scope.allows_all_selectors());
1467    }
1468
1469    #[test]
1470    fn test_selector_rule_accessors() {
1471        let selector = [0x12, 0x34, 0x56, 0x78];
1472        let recipients = vec![Address::repeat_byte(0x33), Address::repeat_byte(0x44)];
1473        let rule = SelectorRule {
1474            selector,
1475            recipients: recipients.clone(),
1476        };
1477
1478        assert_eq!(rule.selector(), selector);
1479        assert_eq!(rule.recipients(), &recipients);
1480        assert!(!rule.allows_all_recipients());
1481    }
1482
1483    #[test]
1484    fn test_selector_rule_allows_all_recipients_when_empty() {
1485        let rule = SelectorRule {
1486            selector: [0xaa, 0xbb, 0xcc, 0xdd],
1487            recipients: vec![],
1488        };
1489        assert!(rule.allows_all_recipients());
1490    }
1491}
1492
1493#[cfg(all(test, feature = "reth-codec"))]
1494mod compact_tests {
1495    use super::*;
1496    use alloy_primitives::{address, hex};
1497    use reth_codecs::Compact;
1498
1499    /// Ensures backwards compatibility of compact bitflags.
1500    ///
1501    /// See reth's `HeaderExt` pattern:
1502    /// <https://github.com/paradigmxyz/reth-core/blob/0476d1bc4b71f3c3b080622be297edd91ee4e70c/crates/codecs/src/alloy/header.rs>
1503    #[test]
1504    fn compact_types_have_unused_bits() {
1505        assert_ne!(TokenLimit::bitflag_unused_bits(), 0, "TokenLimit");
1506    }
1507
1508    #[test]
1509    fn token_limit_compact_roundtrip() {
1510        let token_limit = TokenLimit {
1511            token: address!("0x0000000000000000000000000000000000000042"),
1512            limit: U256::from(1_000_000u64),
1513            period: 86400,
1514        };
1515
1516        let expected = hex!("c30000000000000000000000000000000000000000420f4240015180");
1517
1518        let mut buf = vec![];
1519        let len = token_limit.to_compact(&mut buf);
1520        assert_eq!(buf, expected, "TokenLimit compact encoding changed");
1521        assert_eq!(len, expected.len());
1522
1523        let (decoded, _) = TokenLimit::from_compact(&expected, expected.len());
1524        assert_eq!(decoded, token_limit);
1525    }
1526}