light_token_interface/state/mint/
zero_copy.rs

1use core::ops::Deref;
2
3use aligned_sized::aligned_sized;
4use light_compressed_account::Pubkey;
5use light_compressible::compression_info::CompressionInfo;
6use light_program_profiler::profile;
7use light_zero_copy::{
8    traits::{ZeroCopyAt, ZeroCopyAtMut},
9    ZeroCopy, ZeroCopyMut, ZeroCopyNew,
10};
11use spl_pod::solana_msg::msg;
12
13use super::compressed_mint::{MintMetadata, ACCOUNT_TYPE_MINT};
14use crate::{
15    instructions::mint_action::MintInstructionData,
16    state::{
17        ExtensionStruct, ExtensionStructConfig, Mint, TokenDataVersion, ZExtensionStruct,
18        ZExtensionStructMut,
19    },
20    AnchorDeserialize, AnchorSerialize, TokenError,
21};
22
23/// Base size for Mint accounts (without extensions)
24pub const BASE_MINT_ACCOUNT_SIZE: u64 = MintZeroCopyMeta::LEN as u64;
25
26/// Optimized Mint zero copy struct.
27/// Uses derive macros to generate ZMintZeroCopyMeta<'a> and ZMintZeroCopyMetaMut<'a>.
28#[derive(
29    Debug, PartialEq, Eq, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopy, ZeroCopyMut,
30)]
31#[repr(C)]
32#[aligned_sized]
33struct MintZeroCopyMeta {
34    // BaseMint fields with flattened COptions (SPL format: 4 bytes discriminator + 32 bytes pubkey)
35    mint_authority_option_prefix: u32,
36    mint_authority: Pubkey,
37    /// Total supply of tokens.
38    pub supply: u64,
39    /// Number of base 10 digits to the right of the decimal place.
40    pub decimals: u8,
41    /// Is initialized - for SPL compatibility
42    pub is_initialized: u8,
43    freeze_authority_option_prefix: u32,
44    freeze_authority: Pubkey,
45    // MintMetadata
46    pub metadata: MintMetadata,
47    /// Reserved bytes for T22 layout compatibility (padding to reach byte 165)
48    pub reserved: [u8; 16],
49    /// Account type discriminator at byte 165 (1 = Mint, 2 = Account)
50    pub account_type: u8,
51    /// Compression info embedded directly in the mint
52    pub compression: CompressionInfo,
53    /// Extensions flag
54    has_extensions: bool,
55}
56
57/// Zero-copy view of Mint with base and optional extensions
58#[derive(Debug)]
59pub struct ZMint<'a> {
60    pub base: ZMintZeroCopyMeta<'a>,
61    pub extensions: Option<Vec<ZExtensionStruct<'a>>>,
62}
63
64/// Mutable zero-copy view of Mint with base and optional extensions
65#[derive(Debug)]
66pub struct ZMintMut<'a> {
67    pub base: ZMintZeroCopyMetaMut<'a>,
68    pub extensions: Option<Vec<ZExtensionStructMut<'a>>>,
69}
70
71/// Configuration for creating a new Mint via ZeroCopyNew
72#[derive(Debug, Clone, PartialEq)]
73pub struct MintConfig {
74    /// Extension configurations
75    pub extensions: Option<Vec<ExtensionStructConfig>>,
76}
77
78impl<'a> ZeroCopyNew<'a> for Mint {
79    type ZeroCopyConfig = MintConfig;
80    type Output = ZMintMut<'a>;
81
82    fn byte_len(
83        config: &Self::ZeroCopyConfig,
84    ) -> Result<usize, light_zero_copy::errors::ZeroCopyError> {
85        // Use derived byte_len for meta struct
86        let meta_config = MintZeroCopyMetaConfig {
87            metadata: (),
88            compression: light_compressible::compression_info::CompressionInfoConfig {
89                rent_config: (),
90            },
91        };
92        let mut size = MintZeroCopyMeta::byte_len(&meta_config)?;
93
94        // Add extension sizes if present
95        if let Some(ref extensions) = config.extensions {
96            // Vec length prefix (4 bytes) + each extension's size
97            size += 4;
98            for ext_config in extensions {
99                size += ExtensionStruct::byte_len(ext_config)?;
100            }
101        }
102
103        Ok(size)
104    }
105
106    fn new_zero_copy(
107        bytes: &'a mut [u8],
108        config: Self::ZeroCopyConfig,
109    ) -> Result<(Self::Output, &'a mut [u8]), light_zero_copy::errors::ZeroCopyError> {
110        // Check that the account is not already initialized (is_initialized byte at offset 45)
111        const IS_INITIALIZED_OFFSET: usize = 45; // 4 + 32 + 8 + 1 = 45
112        if bytes.len() > IS_INITIALIZED_OFFSET && bytes[IS_INITIALIZED_OFFSET] != 0 {
113            return Err(light_zero_copy::errors::ZeroCopyError::MemoryNotZeroed);
114        }
115        // Use derived new_zero_copy for meta struct
116        let meta_config = MintZeroCopyMetaConfig {
117            metadata: (),
118            compression: light_compressible::compression_info::CompressionInfoConfig {
119                rent_config: (),
120            },
121        };
122        let (mut base, remaining) =
123            <MintZeroCopyMeta as ZeroCopyNew<'a>>::new_zero_copy(bytes, meta_config)?;
124        *base.account_type = ACCOUNT_TYPE_MINT;
125        base.is_initialized = 1;
126
127        // Initialize extensions if present
128        if let Some(extensions_config) = config.extensions {
129            *base.has_extensions = 1u8;
130            let (extensions, remaining) = <Vec<ExtensionStruct> as ZeroCopyNew<'a>>::new_zero_copy(
131                remaining,
132                extensions_config,
133            )?;
134
135            Ok((
136                ZMintMut {
137                    base,
138                    extensions: Some(extensions),
139                },
140                remaining,
141            ))
142        } else {
143            Ok((
144                ZMintMut {
145                    base,
146                    extensions: None,
147                },
148                remaining,
149            ))
150        }
151    }
152}
153
154impl<'a> ZeroCopyAt<'a> for Mint {
155    type ZeroCopyAt = ZMint<'a>;
156
157    fn zero_copy_at(
158        bytes: &'a [u8],
159    ) -> Result<(Self::ZeroCopyAt, &'a [u8]), light_zero_copy::errors::ZeroCopyError> {
160        let (base, bytes) = <MintZeroCopyMeta as ZeroCopyAt<'a>>::zero_copy_at(bytes)?;
161        // has_extensions already consumed the Option discriminator byte
162        if base.has_extensions() {
163            let (extensions, bytes) =
164                <Vec<ExtensionStruct> as ZeroCopyAt<'a>>::zero_copy_at(bytes)?;
165            Ok((
166                ZMint {
167                    base,
168                    extensions: Some(extensions),
169                },
170                bytes,
171            ))
172        } else {
173            Ok((
174                ZMint {
175                    base,
176                    extensions: None,
177                },
178                bytes,
179            ))
180        }
181    }
182}
183
184impl<'a> ZeroCopyAtMut<'a> for Mint {
185    type ZeroCopyAtMut = ZMintMut<'a>;
186
187    fn zero_copy_at_mut(
188        bytes: &'a mut [u8],
189    ) -> Result<(Self::ZeroCopyAtMut, &'a mut [u8]), light_zero_copy::errors::ZeroCopyError> {
190        let (base, bytes) = <MintZeroCopyMeta as ZeroCopyAtMut<'a>>::zero_copy_at_mut(bytes)?;
191        // has_extensions already consumed the Option discriminator byte
192        if base.has_extensions() {
193            let (extensions, bytes) =
194                <Vec<ExtensionStruct> as ZeroCopyAtMut<'a>>::zero_copy_at_mut(bytes)?;
195            Ok((
196                ZMintMut {
197                    base,
198                    extensions: Some(extensions),
199                },
200                bytes,
201            ))
202        } else {
203            Ok((
204                ZMintMut {
205                    base,
206                    extensions: None,
207                },
208                bytes,
209            ))
210        }
211    }
212}
213
214// Deref implementations for field access
215impl<'a> Deref for ZMint<'a> {
216    type Target = ZMintZeroCopyMeta<'a>;
217
218    fn deref(&self) -> &Self::Target {
219        &self.base
220    }
221}
222
223impl<'a> Deref for ZMintMut<'a> {
224    type Target = ZMintZeroCopyMetaMut<'a>;
225
226    fn deref(&self) -> &Self::Target {
227        &self.base
228    }
229}
230
231// Getters on ZMintZeroCopyMeta (immutable)
232impl ZMintZeroCopyMeta<'_> {
233    /// Checks if account_type matches Mint discriminator value
234    #[inline(always)]
235    pub fn is_mint_account(&self) -> bool {
236        self.account_type == ACCOUNT_TYPE_MINT
237    }
238
239    /// Checks if account is initialized
240    #[inline(always)]
241    pub fn is_initialized(&self) -> bool {
242        self.is_initialized != 0
243    }
244
245    /// Get mint_authority if set (COption discriminator == 1)
246    pub fn mint_authority(&self) -> Option<&Pubkey> {
247        if u32::from(self.mint_authority_option_prefix) == 1 {
248            Some(&self.mint_authority)
249        } else {
250            None
251        }
252    }
253
254    /// Get freeze_authority if set (COption discriminator == 1)
255    pub fn freeze_authority(&self) -> Option<&Pubkey> {
256        if u32::from(self.freeze_authority_option_prefix) == 1 {
257            Some(&self.freeze_authority)
258        } else {
259            None
260        }
261    }
262}
263
264// Getters on ZMintZeroCopyMetaMut (mutable)
265impl ZMintZeroCopyMetaMut<'_> {
266    /// Checks if account_type matches Mint discriminator value
267    #[inline(always)]
268    pub fn is_mint_account(&self) -> bool {
269        *self.account_type == ACCOUNT_TYPE_MINT
270    }
271
272    /// Checks if account is initialized
273    #[inline(always)]
274    pub fn is_initialized(&self) -> bool {
275        self.is_initialized == 1
276    }
277
278    /// Get mint_authority if set (COption discriminator == 1)
279    pub fn mint_authority(&self) -> Option<&Pubkey> {
280        if u32::from(self.mint_authority_option_prefix) == 1 {
281            Some(&self.mint_authority)
282        } else {
283            None
284        }
285    }
286
287    /// Set mint_authority using COption format
288    pub fn set_mint_authority(&mut self, pubkey: Option<Pubkey>) {
289        if let Some(pubkey) = pubkey {
290            self.mint_authority_option_prefix = 1u32.into();
291            self.mint_authority = pubkey;
292        } else {
293            self.mint_authority_option_prefix = 0u32.into();
294            self.mint_authority = Pubkey::default();
295        }
296    }
297
298    /// Get freeze_authority if set (COption discriminator == 1)
299    pub fn freeze_authority(&self) -> Option<&Pubkey> {
300        if u32::from(self.freeze_authority_option_prefix) == 1 {
301            Some(&self.freeze_authority)
302        } else {
303            None
304        }
305    }
306
307    /// Set freeze_authority using COption format
308    pub fn set_freeze_authority(&mut self, pubkey: Option<Pubkey>) {
309        if let Some(pubkey) = pubkey {
310            self.freeze_authority_option_prefix = 1u32.into();
311            self.freeze_authority = pubkey;
312        } else {
313            self.freeze_authority_option_prefix = 0u32.into();
314            self.freeze_authority = Pubkey::default();
315        }
316    }
317}
318
319// Checked methods on Mint
320impl Mint {
321    /// Zero-copy deserialization with initialization and account_type check.
322    /// Returns an error if:
323    /// - Account is not initialized (is_initialized == false)
324    /// - Account type is not ACCOUNT_TYPE_MINT (byte 165 != 1)
325    #[profile]
326    pub fn zero_copy_at_checked(bytes: &[u8]) -> Result<(ZMint<'_>, &[u8]), TokenError> {
327        // Check minimum size (use Mint-specific size, not Token size)
328        if bytes.len() < BASE_MINT_ACCOUNT_SIZE as usize {
329            return Err(TokenError::InvalidAccountData);
330        }
331
332        // Proceed with deserialization first
333        let (mint, remaining) =
334            Mint::zero_copy_at(bytes).map_err(|_| TokenError::MintDeserializationFailed)?;
335
336        // Verify account_type using the method
337        if !mint.is_mint_account() {
338            return Err(TokenError::InvalidAccountType);
339        }
340
341        // Check is_initialized
342        if !mint.is_initialized() {
343            return Err(TokenError::MintNotInitialized);
344        }
345
346        Ok((mint, remaining))
347    }
348
349    /// Mutable zero-copy deserialization with initialization and account_type check.
350    /// Returns an error if:
351    /// - Account is not initialized (is_initialized == false)
352    /// - Account type is not ACCOUNT_TYPE_MINT
353    #[profile]
354    pub fn zero_copy_at_mut_checked(
355        bytes: &mut [u8],
356    ) -> Result<(ZMintMut<'_>, &mut [u8]), TokenError> {
357        // Check minimum size (use Mint-specific size, not Token size)
358        if bytes.len() < BASE_MINT_ACCOUNT_SIZE as usize {
359            msg!(
360                "zero_copy_at_mut_checked bytes.len() < BASE_MINT_ACCOUNT_SIZE {}",
361                bytes.len()
362            );
363            return Err(TokenError::InvalidAccountData);
364        }
365
366        let (mint, remaining) =
367            Mint::zero_copy_at_mut(bytes).map_err(|_| TokenError::MintDeserializationFailed)?;
368
369        if !mint.is_initialized() {
370            return Err(TokenError::MintNotInitialized);
371        }
372        if !mint.is_mint_account() {
373            return Err(TokenError::InvalidAccountType);
374        }
375
376        Ok((mint, remaining))
377    }
378}
379
380// Helper methods on ZMint
381impl ZMint<'_> {
382    /// Checks if account_type matches Mint discriminator value
383    #[inline(always)]
384    pub fn is_mint_account(&self) -> bool {
385        self.base.is_mint_account()
386    }
387
388    /// Checks if account is initialized
389    #[inline(always)]
390    pub fn is_initialized(&self) -> bool {
391        self.base.is_initialized()
392    }
393}
394
395// Helper methods on ZMintMut
396impl ZMintMut<'_> {
397    /// Checks if account_type matches Mint discriminator value
398    #[inline(always)]
399    pub fn is_mint_account(&self) -> bool {
400        self.base.is_mint_account()
401    }
402
403    /// Checks if account is initialized
404    #[inline(always)]
405    pub fn is_initialized(&self) -> bool {
406        self.base.is_initialized()
407    }
408
409    /// Set all fields of the Mint struct at once
410    #[inline]
411    #[profile]
412    pub fn set(
413        &mut self,
414        ix_data: &<MintInstructionData as light_zero_copy::traits::ZeroCopyAt<'_>>::ZeroCopyAt,
415        mint_decompressed: bool,
416    ) -> Result<(), TokenError> {
417        if ix_data.metadata.version != TokenDataVersion::ShaFlat as u8 {
418            #[cfg(feature = "solana")]
419            msg!(
420                "Only shaflat version 3 is supported got {}",
421                ix_data.metadata.version
422            );
423            return Err(TokenError::InvalidTokenMetadataVersion);
424        }
425        // Set metadata fields from instruction data
426        self.base.metadata.version = ix_data.metadata.version;
427        self.base.metadata.mint = ix_data.metadata.mint;
428        self.base.metadata.mint_decompressed = if mint_decompressed { 1 } else { 0 };
429        self.base.metadata.mint_signer = ix_data.metadata.mint_signer;
430        self.base.metadata.bump = ix_data.metadata.bump;
431
432        // Set base fields
433        self.base.supply = ix_data.supply;
434        self.base.decimals = ix_data.decimals;
435        self.base.is_initialized = 1; // Always initialized for compressed mints
436
437        if let Some(mint_authority) = ix_data.mint_authority.as_deref() {
438            self.base.set_mint_authority(Some(*mint_authority));
439        }
440        // Set freeze authority using COption format
441        if let Some(freeze_authority) = ix_data.freeze_authority.as_deref() {
442            self.base.set_freeze_authority(Some(*freeze_authority));
443        }
444
445        // account_type is already set in new_zero_copy
446        // extensions are handled separately
447        Ok(())
448    }
449}