light_token/
pack.rs

1//! Pack implementation for TokenData types for c-tokens.
2use light_compressed_account::compressed_account::CompressedAccountWithMerkleContext;
3use light_sdk::{
4    instruction::PackedAccounts,
5    light_hasher::{sha256::Sha256BE, HasherError},
6};
7pub use light_token_interface::state::TokenData;
8use light_token_interface::state::TokenDataVersion;
9use solana_account_info::AccountInfo;
10use solana_program_error::ProgramError;
11
12use crate::{AnchorDeserialize, AnchorSerialize};
13
14// Note: We define Pack/Unpack traits locally to circumvent the orphan rule.
15// This allows implementing them for external types like TokenData from ctoken-interface.
16// The sdk has identical trait definitions in light_sdk::interface.
17pub trait Pack {
18    type Packed;
19    fn pack(&self, remaining_accounts: &mut PackedAccounts) -> Result<Self::Packed, ProgramError>;
20}
21pub trait Unpack {
22    type Unpacked;
23    fn unpack(
24        &self,
25        remaining_accounts: &[AccountInfo],
26    ) -> std::result::Result<Self::Unpacked, ProgramError>;
27}
28
29/// Solana-compatible token types using `solana_pubkey::Pubkey`
30pub mod compat {
31    use solana_pubkey::Pubkey;
32
33    use super::*;
34
35    #[derive(Clone, Copy, Debug, PartialEq, Eq, AnchorDeserialize, AnchorSerialize, Default)]
36    #[repr(u8)]
37    pub enum AccountState {
38        #[default]
39        Initialized = 0,
40        Frozen = 1,
41    }
42
43    impl From<AccountState> for light_token_interface::state::CompressedTokenAccountState {
44        fn from(state: AccountState) -> Self {
45            match state {
46                AccountState::Initialized => {
47                    light_token_interface::state::CompressedTokenAccountState::Initialized
48                }
49                AccountState::Frozen => {
50                    light_token_interface::state::CompressedTokenAccountState::Frozen
51                }
52            }
53        }
54    }
55
56    impl TryFrom<u8> for AccountState {
57        type Error = ProgramError;
58
59        fn try_from(value: u8) -> Result<Self, Self::Error> {
60            match value {
61                0 => Ok(AccountState::Initialized),
62                1 => Ok(AccountState::Frozen),
63                _ => Err(ProgramError::InvalidAccountData),
64            }
65        }
66    }
67
68    /// TokenData using standard Solana pubkeys.
69    ///
70    /// For zero-copy operations, use `TokenData` from `light_token_interface`.
71    #[derive(Debug, PartialEq, Eq, AnchorDeserialize, AnchorSerialize, Clone, Default)]
72    pub struct TokenData {
73        /// The mint associated with this account
74        pub mint: Pubkey,
75        /// The owner of this account
76        pub owner: Pubkey,
77        /// The amount of tokens this account holds
78        pub amount: u64,
79        /// Optional delegate authorized to transfer tokens
80        pub delegate: Option<Pubkey>,
81        /// The account's state
82        pub state: AccountState,
83        /// TLV extensions for compressed token accounts
84        pub tlv: Option<Vec<light_token_interface::state::ExtensionStruct>>,
85    }
86
87    impl TokenData {
88        /// TokenDataVersion 3
89        /// CompressedAccount Discriminator `[0,0,0,0,0,0,0,4]`
90        #[inline(always)]
91        pub fn hash_sha_flat(&self) -> Result<[u8; 32], HasherError> {
92            use light_sdk::light_hasher::Hasher;
93            let bytes = self.try_to_vec().map_err(|_| HasherError::BorshError)?;
94            Sha256BE::hash(bytes.as_slice())
95        }
96    }
97
98    /// TokenData with merkle context for verification
99    #[derive(Debug, Clone, PartialEq)]
100    pub struct TokenDataWithMerkleContext {
101        pub token_data: TokenData,
102        pub compressed_account: CompressedAccountWithMerkleContext,
103    }
104
105    impl TokenDataWithMerkleContext {
106        /// Only works for sha flat hash
107        pub fn hash(&self) -> Result<[u8; 32], HasherError> {
108            if let Some(data) = self.compressed_account.compressed_account.data.as_ref() {
109                match data.discriminator {
110                    [0, 0, 0, 0, 0, 0, 0, 4] => self.token_data.hash_sha_flat(),
111                    _ => Err(HasherError::EmptyInput),
112                }
113            } else {
114                Err(HasherError::EmptyInput)
115            }
116        }
117    }
118
119    impl From<TokenData> for light_token_interface::state::TokenData {
120        fn from(data: TokenData) -> Self {
121            use light_token_interface::state::CompressedTokenAccountState;
122
123            Self {
124                mint: data.mint.to_bytes().into(),
125                owner: data.owner.to_bytes().into(),
126                amount: data.amount,
127                delegate: data.delegate.map(|d| d.to_bytes().into()),
128                state: match data.state {
129                    AccountState::Initialized => CompressedTokenAccountState::Initialized as u8,
130                    AccountState::Frozen => CompressedTokenAccountState::Frozen as u8,
131                },
132                tlv: data.tlv,
133            }
134        }
135    }
136
137    impl From<light_token_interface::state::TokenData> for TokenData {
138        fn from(data: light_token_interface::state::TokenData) -> Self {
139            Self {
140                mint: Pubkey::new_from_array(data.mint.to_bytes()),
141                owner: Pubkey::new_from_array(data.owner.to_bytes()),
142                amount: data.amount,
143                delegate: data.delegate.map(|d| Pubkey::new_from_array(d.to_bytes())),
144                state: AccountState::try_from(data.state).unwrap_or(AccountState::Initialized),
145                tlv: data.tlv,
146            }
147        }
148    }
149
150    impl Pack for TokenData {
151        type Packed = InputTokenDataCompressible;
152
153        fn pack(
154            &self,
155            remaining_accounts: &mut PackedAccounts,
156        ) -> Result<Self::Packed, ProgramError> {
157            Ok(InputTokenDataCompressible {
158                owner: remaining_accounts.insert_or_get(self.owner),
159                mint: remaining_accounts.insert_or_get_read_only(self.mint),
160                amount: self.amount,
161                has_delegate: self.delegate.is_some(),
162                delegate: if let Some(delegate) = self.delegate {
163                    remaining_accounts.insert_or_get(delegate)
164                } else {
165                    0
166                },
167                version: TokenDataVersion::ShaFlat as u8,
168            })
169        }
170    }
171
172    impl Unpack for TokenData {
173        type Unpacked = Self;
174
175        fn unpack(
176            &self,
177            _remaining_accounts: &[AccountInfo],
178        ) -> std::result::Result<Self::Unpacked, ProgramError> {
179            Ok(self.clone())
180        }
181    }
182
183    impl Unpack for InputTokenDataCompressible {
184        type Unpacked = TokenData;
185
186        fn unpack(
187            &self,
188            remaining_accounts: &[AccountInfo],
189        ) -> std::result::Result<Self::Unpacked, ProgramError> {
190            Ok(TokenData {
191                owner: *remaining_accounts
192                    .get(self.owner as usize)
193                    .ok_or(ProgramError::InvalidAccountData)?
194                    .key,
195                amount: self.amount,
196                delegate: if self.has_delegate {
197                    Some(
198                        *remaining_accounts
199                            .get(self.delegate as usize)
200                            .ok_or(ProgramError::InvalidAccountData)?
201                            .key,
202                    )
203                } else {
204                    None
205                },
206                mint: *remaining_accounts
207                    .get(self.mint as usize)
208                    .ok_or(ProgramError::InvalidAccountData)?
209                    .key,
210                state: AccountState::Initialized,
211                tlv: None,
212            })
213        }
214    }
215
216    /// Wrapper for token data with variant information
217    #[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone)]
218    pub struct TokenDataWithVariant<V> {
219        pub variant: V,
220        pub token_data: TokenData,
221    }
222
223    #[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone)]
224    pub struct PackedTokenDataWithVariant<V> {
225        pub variant: V,
226        pub token_data: InputTokenDataCompressible,
227    }
228
229    #[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone)]
230    pub struct CTokenDataWithVariant<V> {
231        pub variant: V,
232        pub token_data: TokenData,
233    }
234
235    impl<V> Pack for CTokenDataWithVariant<V>
236    where
237        V: Pack,
238        V::Packed: AnchorSerialize + Clone + std::fmt::Debug,
239    {
240        type Packed = PackedTokenDataWithVariant<V::Packed>;
241
242        fn pack(
243            &self,
244            remaining_accounts: &mut PackedAccounts,
245        ) -> Result<Self::Packed, ProgramError> {
246            Ok(PackedTokenDataWithVariant {
247                variant: self.variant.pack(remaining_accounts)?,
248                token_data: self.token_data.pack(remaining_accounts)?,
249            })
250        }
251    }
252
253    impl<V> Unpack for CTokenDataWithVariant<V>
254    where
255        V: Clone,
256    {
257        type Unpacked = TokenDataWithVariant<V>;
258
259        fn unpack(
260            &self,
261            remaining_accounts: &[AccountInfo],
262        ) -> std::result::Result<Self::Unpacked, ProgramError> {
263            // Note: This impl assumes V is already unpacked (has Pubkeys).
264            // For packed variants, use PackedTokenDataWithVariant::unpack instead.
265            Ok(TokenDataWithVariant {
266                variant: self.variant.clone(),
267                token_data: self.token_data.unpack(remaining_accounts)?,
268            })
269        }
270    }
271
272    impl<V> Pack for TokenDataWithVariant<V>
273    where
274        V: Pack,
275        V::Packed: AnchorSerialize + Clone + std::fmt::Debug,
276    {
277        type Packed = PackedTokenDataWithVariant<V::Packed>;
278
279        fn pack(
280            &self,
281            remaining_accounts: &mut PackedAccounts,
282        ) -> Result<Self::Packed, ProgramError> {
283            Ok(PackedTokenDataWithVariant {
284                variant: self.variant.pack(remaining_accounts)?,
285                token_data: self.token_data.pack(remaining_accounts)?,
286            })
287        }
288    }
289
290    impl<V> Unpack for PackedTokenDataWithVariant<V>
291    where
292        V: Unpack,
293    {
294        type Unpacked = TokenDataWithVariant<V::Unpacked>;
295
296        fn unpack(
297            &self,
298            remaining_accounts: &[AccountInfo],
299        ) -> std::result::Result<Self::Unpacked, ProgramError> {
300            Ok(TokenDataWithVariant {
301                variant: self.variant.unpack(remaining_accounts)?,
302                token_data: self.token_data.unpack(remaining_accounts)?,
303            })
304        }
305    }
306
307    // TODO: remove aliases in separate PR
308    pub type InputTokenDataCompressible =
309        light_token_interface::instructions::transfer2::MultiTokenTransferOutputData;
310    pub type CompressibleTokenDataWithVariant<V> = CTokenDataWithVariant<V>;
311    pub type PackedCompressibleTokenDataWithVariant<V> = PackedTokenDataWithVariant<V>;
312    pub type CTokenData<V> = CTokenDataWithVariant<V>;
313    pub type PackedCTokenData<V> = PackedTokenDataWithVariant<V>;
314}