light_token_interface/state/extensions/
extension_struct.rs

1use light_zero_copy::ZeroCopy;
2use spl_pod::solana_msg::msg;
3
4use crate::{
5    state::extensions::{
6        CompressedOnlyExtension, CompressedOnlyExtensionConfig, CompressibleExtension,
7        CompressibleExtensionConfig, ExtensionType, PausableAccountExtension,
8        PausableAccountExtensionConfig, PermanentDelegateAccountExtension,
9        PermanentDelegateAccountExtensionConfig, TokenMetadata, TokenMetadataConfig,
10        TransferFeeAccountExtension, TransferFeeAccountExtensionConfig,
11        TransferHookAccountExtension, TransferHookAccountExtensionConfig,
12        ZPausableAccountExtensionMut, ZPermanentDelegateAccountExtensionMut, ZTokenMetadataMut,
13        ZTransferFeeAccountExtensionMut, ZTransferHookAccountExtensionMut,
14    },
15    AnchorDeserialize, AnchorSerialize,
16};
17
18#[derive(Debug, Clone, Hash, PartialEq, Eq, AnchorSerialize, AnchorDeserialize, ZeroCopy)]
19#[repr(C)]
20pub enum ExtensionStruct {
21    Placeholder0,
22    Placeholder1,
23    Placeholder2,
24    Placeholder3,
25    Placeholder4,
26    Placeholder5,
27    Placeholder6,
28    Placeholder7,
29    Placeholder8,
30    Placeholder9,
31    Placeholder10,
32    Placeholder11,
33    Placeholder12,
34    Placeholder13,
35    Placeholder14,
36    Placeholder15,
37    Placeholder16,
38    Placeholder17,
39    Placeholder18,
40    TokenMetadata(TokenMetadata),
41    Placeholder20,
42    Placeholder21,
43    Placeholder22,
44    Placeholder23,
45    Placeholder24,
46    Placeholder25,
47    /// Reserved for Token-2022 Pausable compatibility
48    Placeholder26,
49    /// Marker extension indicating the account belongs to a pausable mint
50    PausableAccount(PausableAccountExtension),
51    /// Marker extension indicating the account belongs to a mint with permanent delegate
52    PermanentDelegateAccount(PermanentDelegateAccountExtension),
53    /// Transfer fee extension storing withheld fees from transfers
54    TransferFeeAccount(TransferFeeAccountExtension),
55    /// Marker extension indicating the account belongs to a mint with transfer hook
56    TransferHookAccount(TransferHookAccountExtension),
57    /// CompressedOnly extension for compressed token accounts (stores delegated amount)
58    CompressedOnly(CompressedOnlyExtension),
59    /// Compressible extension for token accounts (compression config and timing data)
60    Compressible(CompressibleExtension),
61}
62
63#[derive(Debug)]
64pub enum ZExtensionStructMut<'a> {
65    Placeholder0,
66    Placeholder1,
67    Placeholder2,
68    Placeholder3,
69    Placeholder4,
70    Placeholder5,
71    Placeholder6,
72    Placeholder7,
73    Placeholder8,
74    Placeholder9,
75    Placeholder10,
76    Placeholder11,
77    Placeholder12,
78    Placeholder13,
79    Placeholder14,
80    Placeholder15,
81    Placeholder16,
82    Placeholder17,
83    Placeholder18,
84    TokenMetadata(ZTokenMetadataMut<'a>),
85    Placeholder20,
86    Placeholder21,
87    Placeholder22,
88    Placeholder23,
89    Placeholder24,
90    Placeholder25,
91    /// Reserved for Token-2022 Pausable compatibility
92    Placeholder26,
93    /// Marker extension indicating the account belongs to a pausable mint
94    PausableAccount(ZPausableAccountExtensionMut<'a>),
95    /// Marker extension indicating the account belongs to a mint with permanent delegate
96    PermanentDelegateAccount(ZPermanentDelegateAccountExtensionMut<'a>),
97    /// Transfer fee extension storing withheld fees from transfers
98    TransferFeeAccount(ZTransferFeeAccountExtensionMut<'a>),
99    /// Marker extension indicating the account belongs to a mint with transfer hook
100    TransferHookAccount(ZTransferHookAccountExtensionMut<'a>),
101    /// CompressedOnly extension for compressed token accounts
102    CompressedOnly(
103        <CompressedOnlyExtension as light_zero_copy::traits::ZeroCopyAtMut<'a>>::ZeroCopyAtMut,
104    ),
105    /// Compressible extension for token accounts
106    Compressible(
107        <CompressibleExtension as light_zero_copy::traits::ZeroCopyAtMut<'a>>::ZeroCopyAtMut,
108    ),
109}
110
111impl<'a> light_zero_copy::traits::ZeroCopyAtMut<'a> for ExtensionStruct {
112    type ZeroCopyAtMut = ZExtensionStructMut<'a>;
113
114    fn zero_copy_at_mut(
115        data: &'a mut [u8],
116    ) -> Result<(Self::ZeroCopyAtMut, &'a mut [u8]), light_zero_copy::errors::ZeroCopyError> {
117        // Read discriminant (first 1 byte for borsh enum)
118        if data.is_empty() {
119            return Err(light_zero_copy::errors::ZeroCopyError::ArraySize(
120                1,
121                data.len(),
122            ));
123        }
124
125        let discriminant = data[0];
126        let remaining_data = &mut data[1..];
127        let extension_type = ExtensionType::try_from(discriminant)
128            .map_err(|_| light_zero_copy::errors::ZeroCopyError::InvalidConversion)?;
129
130        match extension_type {
131            ExtensionType::TokenMetadata => {
132                let (token_metadata, remaining_bytes) =
133                    TokenMetadata::zero_copy_at_mut(remaining_data)?;
134                Ok((
135                    ZExtensionStructMut::TokenMetadata(token_metadata),
136                    remaining_bytes,
137                ))
138            }
139            ExtensionType::PausableAccount => {
140                let (pausable_ext, remaining_bytes) =
141                    PausableAccountExtension::zero_copy_at_mut(remaining_data)?;
142                Ok((
143                    ZExtensionStructMut::PausableAccount(pausable_ext),
144                    remaining_bytes,
145                ))
146            }
147            ExtensionType::PermanentDelegateAccount => {
148                let (permanent_delegate_ext, remaining_bytes) =
149                    PermanentDelegateAccountExtension::zero_copy_at_mut(remaining_data)?;
150                Ok((
151                    ZExtensionStructMut::PermanentDelegateAccount(permanent_delegate_ext),
152                    remaining_bytes,
153                ))
154            }
155            ExtensionType::TransferFeeAccount => {
156                let (transfer_fee_ext, remaining_bytes) =
157                    TransferFeeAccountExtension::zero_copy_at_mut(remaining_data)?;
158                Ok((
159                    ZExtensionStructMut::TransferFeeAccount(transfer_fee_ext),
160                    remaining_bytes,
161                ))
162            }
163            ExtensionType::TransferHookAccount => {
164                let (transfer_hook_ext, remaining_bytes) =
165                    TransferHookAccountExtension::zero_copy_at_mut(remaining_data)?;
166                Ok((
167                    ZExtensionStructMut::TransferHookAccount(transfer_hook_ext),
168                    remaining_bytes,
169                ))
170            }
171            ExtensionType::CompressedOnly => {
172                let (compressed_only_ext, remaining_bytes) =
173                    CompressedOnlyExtension::zero_copy_at_mut(remaining_data)?;
174                Ok((
175                    ZExtensionStructMut::CompressedOnly(compressed_only_ext),
176                    remaining_bytes,
177                ))
178            }
179            ExtensionType::Compressible => {
180                let (compressible_ext, remaining_bytes) =
181                    CompressibleExtension::zero_copy_at_mut(remaining_data)?;
182                Ok((
183                    ZExtensionStructMut::Compressible(compressible_ext),
184                    remaining_bytes,
185                ))
186            }
187            _ => Err(light_zero_copy::errors::ZeroCopyError::InvalidConversion),
188        }
189    }
190}
191
192impl<'a> light_zero_copy::ZeroCopyNew<'a> for ExtensionStruct {
193    type ZeroCopyConfig = ExtensionStructConfig;
194    type Output = ZExtensionStructMut<'a>;
195
196    fn byte_len(
197        config: &Self::ZeroCopyConfig,
198    ) -> Result<usize, light_zero_copy::errors::ZeroCopyError> {
199        Ok(match config {
200            ExtensionStructConfig::TokenMetadata(token_metadata_config) => {
201                // 1 byte for discriminant + TokenMetadata size
202                1 + TokenMetadata::byte_len(token_metadata_config)?
203            }
204            ExtensionStructConfig::PausableAccount(config) => {
205                // 1 byte for discriminant + 0 bytes for marker extension
206                1 + PausableAccountExtension::byte_len(config)?
207            }
208            ExtensionStructConfig::PermanentDelegateAccount(config) => {
209                // 1 byte for discriminant + 0 bytes for marker extension
210                1 + PermanentDelegateAccountExtension::byte_len(config)?
211            }
212            ExtensionStructConfig::TransferFeeAccount(config) => {
213                // 1 byte for discriminant + 8 bytes for withheld_amount
214                1 + TransferFeeAccountExtension::byte_len(config)?
215            }
216            ExtensionStructConfig::TransferHookAccount(config) => {
217                // 1 byte for discriminant + 1 byte for transferring flag
218                1 + TransferHookAccountExtension::byte_len(config)?
219            }
220            ExtensionStructConfig::CompressedOnly(_) => {
221                // 1 byte for discriminant + 17 bytes for CompressedOnlyExtension (2 * u64 + u8)
222                1 + CompressedOnlyExtension::LEN
223            }
224            ExtensionStructConfig::Compressible(_) => {
225                // 1 byte for discriminant + CompressibleExtension size
226                1 + CompressibleExtension::LEN
227            }
228            _ => {
229                msg!("Invalid extension type returning");
230                return Err(light_zero_copy::errors::ZeroCopyError::InvalidConversion);
231            }
232        })
233    }
234
235    fn new_zero_copy(
236        bytes: &'a mut [u8],
237        config: Self::ZeroCopyConfig,
238    ) -> Result<(Self::Output, &'a mut [u8]), light_zero_copy::errors::ZeroCopyError> {
239        match config {
240            ExtensionStructConfig::TokenMetadata(config) => {
241                if bytes.is_empty() {
242                    return Err(light_zero_copy::errors::ZeroCopyError::ArraySize(
243                        1,
244                        bytes.len(),
245                    ));
246                }
247                bytes[0] = ExtensionType::TokenMetadata as u8;
248
249                let (token_metadata, remaining_bytes) =
250                    TokenMetadata::new_zero_copy(&mut bytes[1..], config)?;
251                Ok((
252                    ZExtensionStructMut::TokenMetadata(token_metadata),
253                    remaining_bytes,
254                ))
255            }
256            ExtensionStructConfig::PausableAccount(config) => {
257                if bytes.is_empty() {
258                    return Err(light_zero_copy::errors::ZeroCopyError::ArraySize(
259                        1,
260                        bytes.len(),
261                    ));
262                }
263                bytes[0] = ExtensionType::PausableAccount as u8;
264
265                let (pausable_ext, remaining_bytes) =
266                    PausableAccountExtension::new_zero_copy(&mut bytes[1..], config)?;
267                Ok((
268                    ZExtensionStructMut::PausableAccount(pausable_ext),
269                    remaining_bytes,
270                ))
271            }
272            ExtensionStructConfig::PermanentDelegateAccount(config) => {
273                if bytes.is_empty() {
274                    return Err(light_zero_copy::errors::ZeroCopyError::ArraySize(
275                        1,
276                        bytes.len(),
277                    ));
278                }
279                bytes[0] = ExtensionType::PermanentDelegateAccount as u8;
280
281                let (permanent_delegate_ext, remaining_bytes) =
282                    PermanentDelegateAccountExtension::new_zero_copy(&mut bytes[1..], config)?;
283                Ok((
284                    ZExtensionStructMut::PermanentDelegateAccount(permanent_delegate_ext),
285                    remaining_bytes,
286                ))
287            }
288            ExtensionStructConfig::TransferFeeAccount(config) => {
289                if bytes.is_empty() {
290                    return Err(light_zero_copy::errors::ZeroCopyError::ArraySize(
291                        1,
292                        bytes.len(),
293                    ));
294                }
295                bytes[0] = ExtensionType::TransferFeeAccount as u8;
296
297                let (transfer_fee_ext, remaining_bytes) =
298                    TransferFeeAccountExtension::new_zero_copy(&mut bytes[1..], config)?;
299                Ok((
300                    ZExtensionStructMut::TransferFeeAccount(transfer_fee_ext),
301                    remaining_bytes,
302                ))
303            }
304            ExtensionStructConfig::TransferHookAccount(config) => {
305                if bytes.is_empty() {
306                    return Err(light_zero_copy::errors::ZeroCopyError::ArraySize(
307                        1,
308                        bytes.len(),
309                    ));
310                }
311                bytes[0] = ExtensionType::TransferHookAccount as u8;
312
313                let (transfer_hook_ext, remaining_bytes) =
314                    TransferHookAccountExtension::new_zero_copy(&mut bytes[1..], config)?;
315                Ok((
316                    ZExtensionStructMut::TransferHookAccount(transfer_hook_ext),
317                    remaining_bytes,
318                ))
319            }
320            ExtensionStructConfig::CompressedOnly(config) => {
321                if bytes.len() < 1 + CompressedOnlyExtension::LEN {
322                    return Err(light_zero_copy::errors::ZeroCopyError::ArraySize(
323                        1 + CompressedOnlyExtension::LEN,
324                        bytes.len(),
325                    ));
326                }
327                bytes[0] = ExtensionType::CompressedOnly as u8;
328
329                let (compressed_only_ext, remaining_bytes) =
330                    CompressedOnlyExtension::new_zero_copy(&mut bytes[1..], config)?;
331                Ok((
332                    ZExtensionStructMut::CompressedOnly(compressed_only_ext),
333                    remaining_bytes,
334                ))
335            }
336            ExtensionStructConfig::Compressible(config) => {
337                if bytes.len() < 1 + CompressibleExtension::LEN {
338                    return Err(light_zero_copy::errors::ZeroCopyError::ArraySize(
339                        1 + CompressibleExtension::LEN,
340                        bytes.len(),
341                    ));
342                }
343                bytes[0] = ExtensionType::Compressible as u8;
344
345                let (compressible_ext, remaining_bytes) =
346                    CompressibleExtension::new_zero_copy(&mut bytes[1..], config)?;
347                Ok((
348                    ZExtensionStructMut::Compressible(compressible_ext),
349                    remaining_bytes,
350                ))
351            }
352            _ => Err(light_zero_copy::errors::ZeroCopyError::InvalidConversion),
353        }
354    }
355}
356
357#[derive(Debug, Clone, PartialEq, Default)]
358pub enum ExtensionStructConfig {
359    #[default]
360    Placeholder0,
361    Placeholder1,
362    Placeholder2,
363    Placeholder3,
364    Placeholder4,
365    Placeholder5,
366    Placeholder6,
367    Placeholder7,
368    Placeholder8,
369    Placeholder9,
370    Placeholder10,
371    Placeholder11,
372    Placeholder12,
373    Placeholder13,
374    Placeholder14,
375    Placeholder15,
376    Placeholder16,
377    Placeholder17,
378    Placeholder18, // MetadataPointer(MetadataPointerConfig),
379    TokenMetadata(TokenMetadataConfig),
380    Placeholder20,
381    Placeholder21,
382    Placeholder22,
383    Placeholder23,
384    Placeholder24,
385    Placeholder25,
386    /// Reserved for Token-2022 Pausable compatibility
387    Placeholder26,
388    PausableAccount(PausableAccountExtensionConfig),
389    PermanentDelegateAccount(PermanentDelegateAccountExtensionConfig),
390    TransferFeeAccount(TransferFeeAccountExtensionConfig),
391    TransferHookAccount(TransferHookAccountExtensionConfig),
392    CompressedOnly(CompressedOnlyExtensionConfig),
393    Compressible(CompressibleExtensionConfig),
394}