light_token_interface/
token_2022_extensions.rs

1use light_zero_copy::errors::ZeroCopyError;
2use spl_token_2022::extension::ExtensionType;
3
4use crate::state::ExtensionStructConfig;
5
6/// Restricted extension types that require compression_only mode.
7/// These extensions have special behaviors (pausable, permanent delegate, fees, hooks,
8/// default frozen state) that are incompatible with standard compressed token transfers.
9pub const RESTRICTED_EXTENSION_TYPES: [ExtensionType; 5] = [
10    ExtensionType::Pausable,
11    ExtensionType::PermanentDelegate,
12    ExtensionType::TransferFeeConfig,
13    ExtensionType::TransferHook,
14    ExtensionType::DefaultAccountState,
15];
16
17/// Allowed mint extension types for Token accounts.
18/// Extensions not in this list will cause account creation to fail.
19///
20/// Runtime constraints enforced by check_mint_extensions():
21/// - TransferFeeConfig: fees must be zero
22/// - DefaultAccountState: any state allowed (Initialized or Frozen)
23/// - TransferHook: program_id must be nil (no hook execution)
24pub const ALLOWED_EXTENSION_TYPES: [ExtensionType; 16] = [
25    // Metadata extensions
26    ExtensionType::MetadataPointer,
27    ExtensionType::TokenMetadata,
28    // Group extensions
29    ExtensionType::InterestBearingConfig,
30    ExtensionType::GroupPointer,
31    ExtensionType::GroupMemberPointer,
32    ExtensionType::TokenGroup,
33    ExtensionType::TokenGroupMember,
34    // Token 2022 extensions with runtime constraints
35    ExtensionType::MintCloseAuthority,
36    ExtensionType::TransferFeeConfig,
37    ExtensionType::DefaultAccountState,
38    ExtensionType::PermanentDelegate,
39    ExtensionType::TransferHook,
40    ExtensionType::Pausable,
41    ExtensionType::ConfidentialTransferMint,
42    ExtensionType::ConfidentialTransferFeeConfig,
43    ExtensionType::ConfidentialMintBurn,
44];
45
46/// Check if an extension type is a restricted extension.
47#[inline(always)]
48pub const fn is_restricted_extension(ext: &ExtensionType) -> bool {
49    matches!(
50        ext,
51        ExtensionType::Pausable
52            | ExtensionType::PermanentDelegate
53            | ExtensionType::TransferFeeConfig
54            | ExtensionType::TransferHook
55            | ExtensionType::DefaultAccountState
56    )
57}
58
59/// Flags for mint extensions that affect Token account initialization and transfers
60#[derive(Debug, Default, Clone, Copy)]
61pub struct MintExtensionFlags {
62    /// Whether the mint has the PausableAccount extension
63    pub has_pausable: bool,
64    /// Whether the mint has the PermanentDelegate extension
65    pub has_permanent_delegate: bool,
66    /// Whether the mint has the DefaultAccountState extension (restricted regardless of state)
67    pub has_default_account_state: bool,
68    /// Whether DefaultAccountState is currently set to Frozen (for Token account creation)
69    pub default_state_frozen: bool,
70    /// Whether the mint has the TransferFeeConfig extension
71    pub has_transfer_fee: bool,
72    /// Whether the mint has the TransferHook extension (with nil program_id)
73    pub has_transfer_hook: bool,
74}
75
76impl MintExtensionFlags {
77    pub fn num_token_account_extensions(&self) -> usize {
78        let mut count = 0;
79        if self.has_pausable {
80            count += 1;
81        }
82        if self.has_permanent_delegate {
83            count += 1;
84        }
85        if self.has_transfer_fee {
86            count += 1;
87        }
88        if self.has_transfer_hook {
89            count += 1;
90        }
91        count
92    }
93
94    /// Calculate the token account size based on extension flags.
95    ///
96    /// # Arguments
97    /// * `compressible` - If true, includes the Compressible extension in the size calculation
98    ///
99    /// # Returns
100    /// * `Ok(u64)` - The account size in bytes
101    /// * `Err(ZeroCopyError)` - If extension size calculation fails
102    pub fn calculate_account_size(&self, compressible: bool) -> Result<u64, ZeroCopyError> {
103        // Use stack-allocated array to avoid heap allocation
104        // Maximum 5 extensions: pausable, permanent_delegate, transfer_fee, transfer_hook, compressible
105        let mut extensions: [ExtensionStructConfig; 5] = [
106            ExtensionStructConfig::Placeholder0,
107            ExtensionStructConfig::Placeholder0,
108            ExtensionStructConfig::Placeholder0,
109            ExtensionStructConfig::Placeholder0,
110            ExtensionStructConfig::Placeholder0,
111        ];
112        let mut count = 0;
113
114        if self.has_pausable {
115            extensions[count] = ExtensionStructConfig::PausableAccount(());
116            count += 1;
117        }
118        if self.has_permanent_delegate {
119            extensions[count] = ExtensionStructConfig::PermanentDelegateAccount(());
120            count += 1;
121        }
122        if self.has_transfer_fee {
123            extensions[count] = ExtensionStructConfig::TransferFeeAccount(());
124            count += 1;
125        }
126        if self.has_transfer_hook {
127            extensions[count] = ExtensionStructConfig::TransferHookAccount(());
128            count += 1;
129        }
130        if compressible {
131            extensions[count] =
132                ExtensionStructConfig::Compressible(crate::state::CompressibleExtensionConfig {
133                    info: crate::state::CompressionInfoConfig { rent_config: () },
134                });
135            count += 1;
136        }
137
138        let exts = if count == 0 {
139            None
140        } else {
141            Some(&extensions[..count])
142        };
143        crate::state::calculate_token_account_size(exts).map(|size| size as u64)
144    }
145
146    /// Returns true if mint has any restricted extensions.
147    /// Restricted extensions (Pausable, PermanentDelegate, TransferFee, TransferHook,
148    /// DefaultAccountState) require compression_only mode when compressing tokens.
149    pub const fn has_restricted_extensions(&self) -> bool {
150        self.has_pausable
151            || self.has_permanent_delegate
152            || self.has_transfer_fee
153            || self.has_transfer_hook
154            || self.has_default_account_state
155    }
156}