1use core::ops::{Deref, DerefMut};
2
3use aligned_sized::aligned_sized;
4use light_compressed_account::Pubkey;
5use light_program_profiler::profile;
6use light_zero_copy::{
7 traits::{ZeroCopyAt, ZeroCopyAtMut},
8 ZeroCopy, ZeroCopyMut, ZeroCopyNew,
9};
10
11use crate::{
12 state::{
13 ExtensionStruct, ExtensionStructConfig, Token, ZExtensionStruct, ZExtensionStructMut,
14 ACCOUNT_TYPE_TOKEN_ACCOUNT,
15 },
16 AnchorDeserialize, AnchorSerialize,
17};
18
19pub const BASE_TOKEN_ACCOUNT_SIZE: u64 = TokenZeroCopyMeta::LEN as u64;
21
22#[derive(
26 Debug, PartialEq, Eq, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopy, ZeroCopyMut,
27)]
28#[repr(C)]
29#[aligned_sized]
30struct TokenZeroCopyMeta {
31 pub mint: Pubkey,
33 pub owner: Pubkey,
35 pub amount: u64,
37 delegate_option_prefix: u32,
38 delegate: Pubkey,
41 pub state: u8,
43 is_native_option_prefix: u32,
48 is_native: u64,
49 pub delegated_amount: u64,
51 close_authority_option_prefix: u32,
53 close_authority: Pubkey,
54 }
56
57#[derive(Debug)]
59pub struct ZToken<'a> {
60 pub base: ZTokenZeroCopyMeta<'a>,
61 account_type: u8,
63 pub extensions: Option<Vec<ZExtensionStruct<'a>>>,
64}
65
66#[derive(Debug)]
68pub struct ZTokenMut<'a> {
69 pub base: ZTokenZeroCopyMetaMut<'a>,
70 account_type: u8,
72 pub extensions: Option<Vec<ZExtensionStructMut<'a>>>,
73}
74
75#[derive(Debug, Clone, PartialEq)]
77pub struct TokenConfig {
78 pub mint: Pubkey,
80 pub owner: Pubkey,
82 pub state: u8,
84 pub extensions: Option<Vec<ExtensionStructConfig>>,
86}
87
88impl<'a> ZeroCopyNew<'a> for Token {
89 type ZeroCopyConfig = TokenConfig;
90 type Output = ZTokenMut<'a>;
91
92 fn byte_len(
93 config: &Self::ZeroCopyConfig,
94 ) -> Result<usize, light_zero_copy::errors::ZeroCopyError> {
95 let mut size = BASE_TOKEN_ACCOUNT_SIZE as usize;
96 if let Some(extensions) = &config.extensions {
97 if !extensions.is_empty() {
98 size += 1; size += 1; size += 4; for ext in extensions {
102 size += ExtensionStruct::byte_len(ext)?;
103 }
104 }
105 }
106 Ok(size)
107 }
108
109 fn new_zero_copy(
110 bytes: &'a mut [u8],
111 config: Self::ZeroCopyConfig,
112 ) -> Result<(Self::Output, &'a mut [u8]), light_zero_copy::errors::ZeroCopyError> {
113 const STATE_OFFSET: usize = 108;
115 if bytes.len() > STATE_OFFSET && bytes[STATE_OFFSET] != 0 {
116 return Err(light_zero_copy::errors::ZeroCopyError::MemoryNotZeroed);
117 }
118 let (mut base, mut remaining) =
120 <TokenZeroCopyMeta as ZeroCopyNew<'a>>::new_zero_copy(bytes, ())?;
121
122 base.mint = config.mint;
124 base.owner = config.owner;
125 base.state = config.state;
126
127 let (account_type, extensions) = if let Some(ref extensions_config) = config.extensions {
129 if extensions_config.is_empty() {
130 return Err(light_zero_copy::errors::ZeroCopyError::InvalidEnumValue);
131 }
132 if remaining.len() < 6 {
134 return Err(
135 light_zero_copy::errors::ZeroCopyError::InsufficientMemoryAllocated(
136 remaining.len(),
137 6,
138 ),
139 );
140 }
141
142 let (header, ext_data) = remaining.split_at_mut(6);
144 header[0] = ACCOUNT_TYPE_TOKEN_ACCOUNT;
146 header[1] = 1;
148 header[2..6].copy_from_slice(&(extensions_config.len() as u32).to_le_bytes());
150
151 let mut parsed_extensions = Vec::with_capacity(extensions_config.len());
153 let mut write_remaining = ext_data;
154
155 for ext_config in extensions_config {
156 let (ext, rest) =
157 ExtensionStruct::new_zero_copy(write_remaining, ext_config.clone())?;
158 parsed_extensions.push(ext);
159 write_remaining = rest;
160 }
161 remaining = write_remaining;
163 (ACCOUNT_TYPE_TOKEN_ACCOUNT, Some(parsed_extensions))
164 } else {
165 (ACCOUNT_TYPE_TOKEN_ACCOUNT, None)
166 };
167 if !remaining.is_empty() {
168 return Err(light_zero_copy::errors::ZeroCopyError::Size);
169 }
170 Ok((
171 ZTokenMut {
172 base,
173 account_type,
174 extensions,
175 },
176 remaining,
177 ))
178 }
179}
180
181impl<'a> ZeroCopyAt<'a> for Token {
182 type ZeroCopyAt = ZToken<'a>;
183
184 #[inline(always)]
185 fn zero_copy_at(
186 bytes: &'a [u8],
187 ) -> Result<(Self::ZeroCopyAt, &'a [u8]), light_zero_copy::errors::ZeroCopyError> {
188 let (base, bytes) = <TokenZeroCopyMeta as ZeroCopyAt<'a>>::zero_copy_at(bytes)?;
189
190 if !bytes.is_empty() {
192 let account_type = bytes[0];
193 let bytes = &bytes[1..];
195
196 let (extensions, bytes) =
198 <Option<Vec<ExtensionStruct>> as ZeroCopyAt<'a>>::zero_copy_at(bytes)?;
199 Ok((
200 ZToken {
201 base,
202 account_type,
203 extensions,
204 },
205 bytes,
206 ))
207 } else {
208 Ok((
210 ZToken {
211 base,
212 account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT,
213 extensions: None,
214 },
215 bytes,
216 ))
217 }
218 }
219}
220
221impl<'a> ZeroCopyAtMut<'a> for Token {
222 type ZeroCopyAtMut = ZTokenMut<'a>;
223
224 #[inline(always)]
225 fn zero_copy_at_mut(
226 bytes: &'a mut [u8],
227 ) -> Result<(Self::ZeroCopyAtMut, &'a mut [u8]), light_zero_copy::errors::ZeroCopyError> {
228 let (base, bytes) = <TokenZeroCopyMeta as ZeroCopyAtMut<'a>>::zero_copy_at_mut(bytes)?;
229
230 if !bytes.is_empty() {
232 let account_type = bytes[0];
233 let bytes = &mut bytes[1..];
235
236 let (extensions, bytes) =
238 <Option<Vec<ExtensionStruct>> as ZeroCopyAtMut<'a>>::zero_copy_at_mut(bytes)?;
239 Ok((
240 ZTokenMut {
241 base,
242 account_type,
243 extensions,
244 },
245 bytes,
246 ))
247 } else {
248 Ok((
250 ZTokenMut {
251 base,
252 account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT,
253 extensions: None,
254 },
255 bytes,
256 ))
257 }
258 }
259}
260
261impl<'a> Deref for ZToken<'a> {
263 type Target = ZTokenZeroCopyMeta<'a>;
264
265 fn deref(&self) -> &Self::Target {
266 &self.base
267 }
268}
269
270impl<'a> Deref for ZTokenMut<'a> {
271 type Target = ZTokenZeroCopyMetaMut<'a>;
272
273 fn deref(&self) -> &Self::Target {
274 &self.base
275 }
276}
277
278impl<'a> DerefMut for ZTokenMut<'a> {
279 fn deref_mut(&mut self) -> &mut Self::Target {
280 &mut self.base
281 }
282}
283
284impl<'a> ZToken<'a> {
286 #[inline(always)]
288 pub fn account_type(&self) -> u8 {
289 self.account_type
290 }
291
292 #[inline(always)]
294 pub fn is_token_account(&self) -> bool {
295 self.account_type == ACCOUNT_TYPE_TOKEN_ACCOUNT
296 }
297
298 #[inline(always)]
300 pub fn get_compressible_extension(
301 &self,
302 ) -> Option<&crate::state::extensions::ZCompressibleExtension<'a>> {
303 self.extensions.as_ref().and_then(|exts| {
304 exts.iter().find_map(|ext| match ext {
305 ZExtensionStruct::Compressible(comp) => Some(comp),
306 _ => None,
307 })
308 })
309 }
310}
311
312impl<'a> ZTokenMut<'a> {
314 #[inline(always)]
316 pub fn account_type(&self) -> u8 {
317 self.account_type
318 }
319
320 #[inline(always)]
322 pub fn is_token_account(&self) -> bool {
323 self.account_type == ACCOUNT_TYPE_TOKEN_ACCOUNT
324 }
325
326 #[inline(always)]
328 pub fn get_compressible_extension_mut(
329 &mut self,
330 ) -> Option<&mut crate::state::extensions::ZCompressibleExtensionMut<'a>> {
331 self.extensions.as_mut().and_then(|exts| {
332 exts.iter_mut().find_map(|ext| match ext {
333 ZExtensionStructMut::Compressible(comp) => Some(comp),
334 _ => None,
335 })
336 })
337 }
338
339 #[inline(always)]
341 pub fn get_compressible_extension(
342 &self,
343 ) -> Option<&crate::state::extensions::ZCompressibleExtensionMut<'a>> {
344 self.extensions.as_ref().and_then(|exts| {
345 exts.iter().find_map(|ext| match ext {
346 ZExtensionStructMut::Compressible(comp) => Some(comp),
347 _ => None,
348 })
349 })
350 }
351}
352
353impl ZTokenZeroCopyMeta<'_> {
355 #[inline(always)]
357 pub fn is_uninitialized(&self) -> bool {
358 self.state == 0
359 }
360
361 #[inline(always)]
363 pub fn is_initialized(&self) -> bool {
364 self.state == 1
365 }
366
367 #[inline(always)]
369 pub fn is_frozen(&self) -> bool {
370 self.state == 2
371 }
372
373 #[inline(always)]
375 pub fn delegate(&self) -> Option<&Pubkey> {
376 if u32::from(self.delegate_option_prefix) == 1 {
377 Some(&self.delegate)
378 } else {
379 None
380 }
381 }
382
383 #[inline(always)]
385 pub fn is_native_value(&self) -> Option<u64> {
386 if u32::from(self.is_native_option_prefix) == 1 {
387 Some(u64::from(self.is_native))
388 } else {
389 None
390 }
391 }
392
393 #[inline(always)]
395 pub fn close_authority(&self) -> Option<&Pubkey> {
396 if u32::from(self.close_authority_option_prefix) == 1 {
397 Some(&self.close_authority)
398 } else {
399 None
400 }
401 }
402}
403
404impl ZTokenZeroCopyMetaMut<'_> {
406 #[inline(always)]
408 pub fn is_uninitialized(&self) -> bool {
409 self.state == 0
410 }
411
412 #[inline(always)]
414 pub fn is_initialized(&self) -> bool {
415 self.state == 1
416 }
417
418 #[inline(always)]
420 pub fn is_frozen(&self) -> bool {
421 self.state == 2
422 }
423
424 #[inline(always)]
426 pub fn delegate(&self) -> Option<&Pubkey> {
427 if u32::from(self.delegate_option_prefix) == 1 {
428 Some(&self.delegate)
429 } else {
430 None
431 }
432 }
433
434 #[inline(always)]
436 pub fn is_native_value(&self) -> Option<u64> {
437 if u32::from(self.is_native_option_prefix) == 1 {
438 Some(u64::from(self.is_native))
439 } else {
440 None
441 }
442 }
443
444 #[inline(always)]
446 pub fn close_authority(&self) -> Option<&Pubkey> {
447 if u32::from(self.close_authority_option_prefix) == 1 {
448 Some(&self.close_authority)
449 } else {
450 None
451 }
452 }
453
454 #[inline(always)]
456 pub fn set_delegate(&mut self, delegate: Option<Pubkey>) -> Result<(), crate::TokenError> {
457 match delegate {
458 Some(pubkey) => {
459 self.delegate_option_prefix.set(1);
460 self.delegate = pubkey;
461 }
462 None => {
463 self.delegate_option_prefix.set(0);
464 self.delegate = Pubkey::default();
466 }
467 }
468 Ok(())
469 }
470
471 #[inline(always)]
473 pub fn set_frozen(&mut self) {
474 self.state = 2;
475 }
476
477 #[inline(always)]
479 pub fn set_initialized(&mut self) {
480 self.state = 1;
481 }
482}
483
484impl Token {
486 #[profile]
492 #[inline(always)]
493 pub fn zero_copy_at_checked(
494 bytes: &[u8],
495 ) -> Result<(ZToken<'_>, &[u8]), crate::error::TokenError> {
496 let (token, remaining) = Token::zero_copy_at(bytes)?;
497
498 if !token.is_initialized() {
499 return Err(crate::error::TokenError::InvalidAccountState);
500 }
501 if !token.is_token_account() {
502 return Err(crate::error::TokenError::InvalidAccountType);
503 }
504
505 Ok((token, remaining))
506 }
507
508 #[profile]
513 #[inline(always)]
514 pub fn zero_copy_at_mut_checked(
515 bytes: &mut [u8],
516 ) -> Result<(ZTokenMut<'_>, &mut [u8]), crate::error::TokenError> {
517 let (token, remaining) = Token::zero_copy_at_mut(bytes)?;
518
519 if !token.is_initialized() {
520 return Err(crate::error::TokenError::InvalidAccountState);
521 }
522 if !token.is_token_account() {
523 return Err(crate::error::TokenError::InvalidAccountType);
524 }
525
526 Ok((token, remaining))
527 }
528
529 #[inline(always)]
541 pub fn from_account_info_checked<'a>(
542 account_info: &pinocchio::account_info::AccountInfo,
543 ) -> Result<ZToken<'a>, crate::error::TokenError> {
544 if !account_info.is_owned_by(&crate::LIGHT_TOKEN_PROGRAM_ID) {
546 return Err(crate::error::TokenError::InvalidTokenOwner);
547 }
548
549 let data = account_info
550 .try_borrow_data()
551 .map_err(|_| crate::error::TokenError::BorrowFailed)?;
552
553 let data_slice: &'a [u8] =
555 unsafe { core::slice::from_raw_parts(data.as_ptr(), data.len()) };
556
557 let (token, remaining) = Token::zero_copy_at_checked(data_slice)?;
558
559 if !remaining.is_empty() {
561 return Err(crate::error::TokenError::InvalidAccountData);
562 }
563
564 Ok(token)
565 }
566
567 #[inline(always)]
576 pub fn from_account_info_mut_checked<'a>(
577 account_info: &pinocchio::account_info::AccountInfo,
578 ) -> Result<ZTokenMut<'a>, crate::error::TokenError> {
579 if !account_info.is_owned_by(&crate::LIGHT_TOKEN_PROGRAM_ID) {
581 return Err(crate::error::TokenError::InvalidTokenOwner);
582 }
583
584 let mut data = account_info
585 .try_borrow_mut_data()
586 .map_err(|_| crate::error::TokenError::BorrowFailed)?;
587
588 let data_slice: &'a mut [u8] =
590 unsafe { core::slice::from_raw_parts_mut(data.as_mut_ptr(), data.len()) };
591
592 let (token, remaining) = Token::zero_copy_at_mut_checked(data_slice)?;
593
594 if !remaining.is_empty() {
596 return Err(crate::error::TokenError::InvalidAccountData);
597 }
598
599 Ok(token)
600 }
601}
602
603#[cfg(feature = "test-only")]
604impl PartialEq<Token> for ZToken<'_> {
605 fn eq(&self, other: &Token) -> bool {
606 if self.mint.to_bytes() != other.mint.to_bytes()
608 || self.owner.to_bytes() != other.owner.to_bytes()
609 || u64::from(self.amount) != other.amount
610 || self.state != other.state as u8
611 || u64::from(self.delegated_amount) != other.delegated_amount
612 || self.account_type != other.account_type
613 {
614 return false;
615 }
616
617 match (self.delegate(), &other.delegate) {
619 (Some(zc_delegate), Some(regular_delegate)) => {
620 if zc_delegate.to_bytes() != regular_delegate.to_bytes() {
621 return false;
622 }
623 }
624 (None, None) => {}
625 _ => return false,
626 }
627
628 match (self.is_native_value(), &other.is_native) {
630 (Some(zc_native), Some(regular_native)) => {
631 if zc_native != *regular_native {
632 return false;
633 }
634 }
635 (None, None) => {}
636 _ => return false,
637 }
638
639 match (self.close_authority(), &other.close_authority) {
641 (Some(zc_close), Some(regular_close)) => {
642 if zc_close.to_bytes() != regular_close.to_bytes() {
643 return false;
644 }
645 }
646 (None, None) => {}
647 _ => return false,
648 }
649
650 match (&self.extensions, &other.extensions) {
652 (Some(zc_extensions), Some(regular_extensions)) => {
653 if zc_extensions.len() != regular_extensions.len() {
654 return false;
655 }
656 for (zc_ext, regular_ext) in zc_extensions.iter().zip(regular_extensions.iter()) {
657 match (zc_ext, regular_ext) {
658 (
659 ZExtensionStruct::TokenMetadata(zc_tm),
660 crate::state::extensions::ExtensionStruct::TokenMetadata(regular_tm),
661 ) => {
662 if zc_tm.mint.to_bytes() != regular_tm.mint.to_bytes()
663 || zc_tm.name != regular_tm.name.as_slice()
664 || zc_tm.symbol != regular_tm.symbol.as_slice()
665 || zc_tm.uri != regular_tm.uri.as_slice()
666 {
667 return false;
668 }
669 if zc_tm.update_authority != regular_tm.update_authority {
670 return false;
671 }
672 if zc_tm.additional_metadata.len()
673 != regular_tm.additional_metadata.len()
674 {
675 return false;
676 }
677 for (zc_meta, regular_meta) in zc_tm
678 .additional_metadata
679 .iter()
680 .zip(regular_tm.additional_metadata.iter())
681 {
682 if zc_meta.key != regular_meta.key.as_slice()
683 || zc_meta.value != regular_meta.value.as_slice()
684 {
685 return false;
686 }
687 }
688 }
689 (
690 ZExtensionStruct::PausableAccount(_),
691 crate::state::extensions::ExtensionStruct::PausableAccount(_),
692 ) => {
693 }
695 (
696 ZExtensionStruct::PermanentDelegateAccount(_),
697 crate::state::extensions::ExtensionStruct::PermanentDelegateAccount(_),
698 ) => {
699 }
701 (
702 ZExtensionStruct::TransferFeeAccount(zc_tfa),
703 crate::state::extensions::ExtensionStruct::TransferFeeAccount(
704 regular_tfa,
705 ),
706 ) => {
707 if u64::from(zc_tfa.withheld_amount) != regular_tfa.withheld_amount {
708 return false;
709 }
710 }
711 (
712 ZExtensionStruct::TransferHookAccount(zc_tha),
713 crate::state::extensions::ExtensionStruct::TransferHookAccount(
714 regular_tha,
715 ),
716 ) => {
717 if zc_tha.transferring != regular_tha.transferring {
718 return false;
719 }
720 }
721 (
722 ZExtensionStruct::CompressedOnly(zc_co),
723 crate::state::extensions::ExtensionStruct::CompressedOnly(regular_co),
724 ) => {
725 if u64::from(zc_co.delegated_amount) != regular_co.delegated_amount
726 || u64::from(zc_co.withheld_transfer_fee)
727 != regular_co.withheld_transfer_fee
728 {
729 return false;
730 }
731 }
732 (
733 ZExtensionStruct::Compressible(zc_comp),
734 crate::state::extensions::ExtensionStruct::Compressible(regular_comp),
735 ) => {
736 let zc_decimals = if zc_comp.decimals_option == 1 {
738 Some(zc_comp.decimals)
739 } else {
740 None
741 };
742 if zc_decimals != regular_comp.decimals() {
743 return false;
744 }
745 if (zc_comp.compression_only != 0) != regular_comp.compression_only {
747 return false;
748 }
749 let zc_info = &zc_comp.info;
751 let regular_info = ®ular_comp.info;
752 if u16::from(zc_info.config_account_version)
753 != regular_info.config_account_version
754 {
755 return false;
756 }
757 if zc_info.compress_to_pubkey != regular_info.compress_to_pubkey {
758 return false;
759 }
760 if zc_info.account_version != regular_info.account_version {
761 return false;
762 }
763 if u64::from(zc_info.last_claimed_slot)
764 != regular_info.last_claimed_slot
765 {
766 return false;
767 }
768 if u32::from(zc_info.lamports_per_write)
769 != regular_info.lamports_per_write
770 {
771 return false;
772 }
773 if zc_info.compression_authority != regular_info.compression_authority {
774 return false;
775 }
776 if zc_info.rent_sponsor != regular_info.rent_sponsor {
777 return false;
778 }
779 if u16::from(zc_info.rent_config.base_rent)
781 != regular_info.rent_config.base_rent
782 {
783 return false;
784 }
785 if u16::from(zc_info.rent_config.compression_cost)
786 != regular_info.rent_config.compression_cost
787 {
788 return false;
789 }
790 if zc_info.rent_config.lamports_per_byte_per_epoch
791 != regular_info.rent_config.lamports_per_byte_per_epoch
792 {
793 return false;
794 }
795 if zc_info.rent_config.max_funded_epochs
796 != regular_info.rent_config.max_funded_epochs
797 {
798 return false;
799 }
800 if u16::from(zc_info.rent_config.max_top_up)
801 != regular_info.rent_config.max_top_up
802 {
803 return false;
804 }
805 }
806 (zc_ext, regular_ext) => {
808 panic!(
809 "Unknown extension type comparison: ZToken extension {:?} vs Token extension {:?}",
810 std::mem::discriminant(zc_ext),
811 std::mem::discriminant(regular_ext)
812 );
813 }
814 }
815 }
816 }
817 (None, None) => {}
818 _ => return false,
819 }
820
821 true
822 }
823}
824
825#[cfg(feature = "test-only")]
826impl PartialEq<ZToken<'_>> for Token {
827 fn eq(&self, other: &ZToken<'_>) -> bool {
828 other.eq(self)
829 }
830}