Skip to main content

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