light_sdk/
account.rs

1//! # Light Account
2//!
3//! LightAccount wraps the compressed account data so that it is easy to use similar to anchor Account.
4//! LightAccount sets the discriminator and creates the compressed account data hash.
5//!
6//! Data structs used with LightAccount must implement the traits:
7//! - LightDiscriminator
8//! - BorshSerialize, BorshDeserialize
9//! - Debug, Default, Clone
10//!
11//! ### Account Data Hashing
12//!
13//! Sha256 data hashing is the recommended for most use cases.
14//! Account data is serialized into a vector with borsh and hashed with Sha256.
15//!
16//! Poseidon data hashing is recommended zk use cases.
17//! The data struct needs to implement the DataHasher implementation.
18//! The LightHasher derives, the DataHasher trait a hashing scheme from the compressed account layout.
19//! Alternatively, DataHasher can be implemented manually.
20//! Poseidon hashing is CU intensive and has limitations with regards to hash inputs see Poseidon module for details.
21//!
22//!
23//! ### Compressed account with LightDiscriminator
24//! ```
25//! use light_sdk::LightDiscriminator;
26//! use solana_pubkey::Pubkey;
27//! use borsh::{BorshSerialize, BorshDeserialize};
28//! #[derive(Clone, Debug, Default, LightDiscriminator, BorshSerialize, BorshDeserialize)]
29//! pub struct CounterAccount {
30//!     pub owner: Pubkey,
31//!     pub counter: u64,
32//! }
33//! ```
34//!
35//!
36//! ### Create compressed account
37//! ```rust
38//! # use light_sdk::{LightAccount, LightDiscriminator};
39//! # use borsh::{BorshSerialize, BorshDeserialize};
40//! # use solana_pubkey::Pubkey;
41//! #
42//! # #[derive(Clone, Debug, Default, LightDiscriminator, BorshSerialize, BorshDeserialize)]
43//! # pub struct CounterAccount {
44//! #     pub owner: Pubkey,
45//! #     pub counter: u64,
46//! # }
47//! #
48//! # let program_id = Pubkey::new_unique();
49//! # let address = [0u8; 32];
50//! # let output_tree_index = 0u8;
51//! # let owner = Pubkey::new_unique();
52//! let mut my_compressed_account = LightAccount::<'_, CounterAccount>::new_init(
53//!     &program_id,
54//!     Some(address),
55//!     output_tree_index,
56//! );
57//! // Set data:
58//! my_compressed_account.owner = owner;
59//! ```
60//! ### Update compressed account
61//! ```rust
62//! # use light_sdk::{LightAccount, LightDiscriminator};
63//! # use light_sdk::instruction::account_meta::CompressedAccountMeta;
64//! # use borsh::{BorshSerialize, BorshDeserialize};
65//! # use solana_pubkey::Pubkey;
66//! # use solana_program_error::ProgramError;
67//! #
68//! # #[derive(Clone, Debug, Default, LightDiscriminator, BorshSerialize, BorshDeserialize)]
69//! # pub struct CounterAccount {
70//! #     pub owner: Pubkey,
71//! #     pub counter: u64,
72//! # }
73//! #
74//! # fn example() -> Result<(), ProgramError> {
75//! # let program_id = Pubkey::new_unique();
76//! # let account_meta = CompressedAccountMeta {
77//! #     output_state_tree_index: 0,
78//! #     ..Default::default()
79//! # };
80//! # let compressed_account_data = CounterAccount::default();
81//! let mut my_compressed_account = LightAccount::<'_, CounterAccount>::new_mut(
82//!     &program_id,
83//!     &account_meta,
84//!     compressed_account_data,
85//! )?;
86//! // Increment counter.
87//! my_compressed_account.counter += 1;
88//! # Ok(())
89//! # }
90//! ```
91//! ### Close compressed account
92//! ```rust
93//! # use light_sdk::{LightAccount, LightDiscriminator};
94//! # use light_sdk::instruction::account_meta::CompressedAccountMeta;
95//! # use borsh::{BorshSerialize, BorshDeserialize};
96//! # use solana_pubkey::Pubkey;
97//! # use solana_program_error::ProgramError;
98//! #
99//! # #[derive(Clone, Debug, Default, LightDiscriminator, BorshSerialize, BorshDeserialize)]
100//! # pub struct CounterAccount {
101//! #     pub owner: Pubkey,
102//! #     pub counter: u64,
103//! # }
104//! #
105//! # fn example() -> Result<(), ProgramError> {
106//! # let program_id = Pubkey::new_unique();
107//! # let account_meta = CompressedAccountMeta {
108//! #     output_state_tree_index: 0,
109//! #     ..Default::default()
110//! # };
111//! # let compressed_account_data = CounterAccount::default();
112//! let my_compressed_account = LightAccount::<'_, CounterAccount>::new_close(
113//!     &program_id,
114//!     &account_meta,
115//!     compressed_account_data,
116//! )?;
117//! # Ok(())
118//! # }
119//! ```
120// TODO: add example for manual hashing
121
122use std::{
123    marker::PhantomData,
124    ops::{Deref, DerefMut},
125};
126
127use light_compressed_account::{
128    compressed_account::PackedMerkleContext,
129    instruction_data::with_account_info::{CompressedAccountInfo, InAccountInfo, OutAccountInfo},
130};
131use light_sdk_types::instruction::account_meta::CompressedAccountMetaTrait;
132use solana_pubkey::Pubkey;
133
134use crate::{
135    error::LightSdkError,
136    light_hasher::{DataHasher, Hasher, Poseidon, Sha256},
137    AnchorDeserialize, AnchorSerialize, LightDiscriminator,
138};
139
140const DEFAULT_DATA_HASH: [u8; 32] = [0u8; 32];
141
142pub trait Size {
143    fn size(&self) -> usize;
144}
145pub use sha::LightAccount;
146/// SHA256 borsh flat hashed Light Account.
147/// This is the recommended account type for most use cases.
148pub mod sha {
149    use super::*;
150    /// Light Account variant that uses SHA256 hashing with flat borsh serialization.
151    /// This is the recommended account type for most use cases.
152    pub type LightAccount<'a, A> = super::LightAccountInner<'a, Sha256, A, true>;
153}
154
155/// Poseidon hashed Light Account.
156/// Poseidon hashing is zk friendly and enables you to do zk proofs over your compressed account data.
157pub mod poseidon {
158    use super::*;
159    /// Light Account type using Poseidon hashing.
160    /// Poseidon hashing is zk friendly and enables you to do zk proofs over your compressed account.
161    /// ### Compressed account with LightHasher and LightDiscriminator
162    /// ```rust
163    /// use light_sdk::{LightHasher, LightDiscriminator};
164    /// use solana_pubkey::Pubkey;
165    /// #[derive(Clone, Debug, Default, LightHasher, LightDiscriminator)]
166    /// pub struct CounterAccount {
167    ///     #[hash]
168    ///     pub owner: Pubkey,
169    ///     pub counter: u64,
170    /// }
171    /// ```
172    /// Constraints:
173    /// - Poseidon hashes can only take up to 12 inputs
174    ///   -> use nested structs for structs with more than 12 fields.
175    /// - Poseidon hashes inputs must be less than bn254 field size (254 bits).
176    ///   hash_to_field_size methods in light hasher can be used to hash data longer than 253 bits.
177    ///   -> use the `#[hash]` attribute for fields with data types greater than 31 bytes eg Pubkeys.
178    pub type LightAccount<'a, A> = super::LightAccountInner<'a, Poseidon, A, false>;
179}
180
181#[doc(hidden)]
182pub use __internal::LightAccountInner;
183
184/// INTERNAL IMPLEMENTATION - DO NOT USE DIRECTLY
185/// **Use the type aliases instead:**
186/// - `LightAccount` for Poseidon hashing
187/// - `sha::LightAccount` for SHA256 hashing
188#[doc(hidden)]
189pub mod __internal {
190    use light_compressed_account::instruction_data::{
191        data::OutputCompressedAccountWithPackedContext, with_readonly::InAccount,
192    };
193    use light_sdk_types::instruction::account_meta::CompressedAccountMetaBurn;
194    use solana_program_error::ProgramError;
195
196    use super::*;
197
198    #[doc(hidden)]
199    #[derive(Debug, PartialEq)]
200    pub struct LightAccountInner<
201        'a,
202        H: Hasher,
203        A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default,
204        const HASH_FLAT: bool,
205    > {
206        owner: &'a Pubkey,
207        pub account: A,
208        account_info: CompressedAccountInfo,
209        should_remove_data: bool,
210        _hasher: PhantomData<H>,
211    }
212
213    impl<
214            'a,
215            H: Hasher,
216            A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default,
217            const HASH_FLAT: bool,
218        > LightAccountInner<'a, H, A, HASH_FLAT>
219    {
220        pub fn new_init(
221            owner: &'a Pubkey,
222            address: Option<[u8; 32]>,
223            output_state_tree_index: u8,
224        ) -> Self {
225            let output_account_info = OutAccountInfo {
226                output_merkle_tree_index: output_state_tree_index,
227                discriminator: A::LIGHT_DISCRIMINATOR,
228                ..Default::default()
229            };
230            Self {
231                owner,
232                account: A::default(),
233                account_info: CompressedAccountInfo {
234                    address,
235                    input: None,
236                    output: Some(output_account_info),
237                },
238                should_remove_data: false,
239                _hasher: PhantomData,
240            }
241        }
242
243        pub fn discriminator(&self) -> &[u8; 8] {
244            &A::LIGHT_DISCRIMINATOR
245        }
246
247        pub fn lamports(&self) -> u64 {
248            if let Some(output) = self.account_info.output.as_ref() {
249                output.lamports
250            } else if let Some(input) = self.account_info.input.as_ref() {
251                input.lamports
252            } else {
253                0
254            }
255        }
256
257        pub fn lamports_mut(&mut self) -> &mut u64 {
258            if let Some(output) = self.account_info.output.as_mut() {
259                &mut output.lamports
260            } else if let Some(input) = self.account_info.input.as_mut() {
261                &mut input.lamports
262            } else {
263                panic!("No lamports field available in account_info")
264            }
265        }
266
267        pub fn address(&self) -> &Option<[u8; 32]> {
268            &self.account_info.address
269        }
270
271        pub fn owner(&self) -> &Pubkey {
272            self.owner
273        }
274
275        pub fn in_account_info(&self) -> &Option<InAccountInfo> {
276            &self.account_info.input
277        }
278
279        pub fn out_account_info(&mut self) -> &Option<OutAccountInfo> {
280            &self.account_info.output
281        }
282    }
283
284    // Specialized implementation for HASH_FLAT = false (structured hashing with DataHasher)
285    impl<
286            'a,
287            H: Hasher,
288            A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + DataHasher + Default,
289        > LightAccountInner<'a, H, A, false>
290    {
291        pub fn new_mut(
292            owner: &'a Pubkey,
293            input_account_meta: &impl CompressedAccountMetaTrait,
294            input_account: A,
295        ) -> Result<Self, LightSdkError> {
296            let input_account_info = {
297                // For HASH_FLAT = false, always use DataHasher
298                let input_data_hash = input_account.hash::<H>()?;
299                let tree_info = input_account_meta.get_tree_info();
300                InAccountInfo {
301                    data_hash: input_data_hash,
302                    lamports: input_account_meta.get_lamports().unwrap_or_default(),
303                    merkle_context: PackedMerkleContext {
304                        merkle_tree_pubkey_index: tree_info.merkle_tree_pubkey_index,
305                        queue_pubkey_index: tree_info.queue_pubkey_index,
306                        leaf_index: tree_info.leaf_index,
307                        prove_by_index: tree_info.prove_by_index,
308                    },
309                    root_index: input_account_meta.get_root_index().unwrap_or_default(),
310                    discriminator: A::LIGHT_DISCRIMINATOR,
311                }
312            };
313            let output_account_info = {
314                let output_merkle_tree_index = input_account_meta
315                    .get_output_state_tree_index()
316                    .ok_or(LightSdkError::OutputStateTreeIndexIsNone)?;
317                OutAccountInfo {
318                    lamports: input_account_meta.get_lamports().unwrap_or_default(),
319                    output_merkle_tree_index,
320                    discriminator: A::LIGHT_DISCRIMINATOR,
321                    ..Default::default()
322                }
323            };
324
325            Ok(Self {
326                owner,
327                account: input_account,
328                account_info: CompressedAccountInfo {
329                    address: input_account_meta.get_address(),
330                    input: Some(input_account_info),
331                    output: Some(output_account_info),
332                },
333                should_remove_data: false,
334                _hasher: PhantomData,
335            })
336        }
337
338        pub fn new_empty(
339            owner: &'a Pubkey,
340            input_account_meta: &impl CompressedAccountMetaTrait,
341        ) -> Result<Self, LightSdkError> {
342            let input_account_info = {
343                let input_data_hash = DEFAULT_DATA_HASH;
344                let tree_info = input_account_meta.get_tree_info();
345                InAccountInfo {
346                    data_hash: input_data_hash,
347                    lamports: input_account_meta.get_lamports().unwrap_or_default(),
348                    merkle_context: PackedMerkleContext {
349                        merkle_tree_pubkey_index: tree_info.merkle_tree_pubkey_index,
350                        queue_pubkey_index: tree_info.queue_pubkey_index,
351                        leaf_index: tree_info.leaf_index,
352                        prove_by_index: tree_info.prove_by_index,
353                    },
354                    root_index: input_account_meta.get_root_index().unwrap_or_default(),
355                    discriminator: [0u8; 8],
356                }
357            };
358            let output_account_info = {
359                let output_merkle_tree_index = input_account_meta
360                    .get_output_state_tree_index()
361                    .ok_or(LightSdkError::OutputStateTreeIndexIsNone)?;
362                OutAccountInfo {
363                    lamports: input_account_meta.get_lamports().unwrap_or_default(),
364                    output_merkle_tree_index,
365                    discriminator: A::LIGHT_DISCRIMINATOR,
366                    ..Default::default()
367                }
368            };
369
370            Ok(Self {
371                owner,
372                account: A::default(),
373                account_info: CompressedAccountInfo {
374                    address: input_account_meta.get_address(),
375                    input: Some(input_account_info),
376                    output: Some(output_account_info),
377                },
378                should_remove_data: false,
379                _hasher: PhantomData,
380            })
381        }
382
383        pub fn new_close(
384            owner: &'a Pubkey,
385            input_account_meta: &impl CompressedAccountMetaTrait,
386            input_account: A,
387        ) -> Result<Self, LightSdkError> {
388            let mut account = Self::new_mut(owner, input_account_meta, input_account)?;
389            account.should_remove_data = true;
390
391            Ok(account)
392        }
393
394        /// Closes the compressed account.
395        /// Define whether to close the account permanently or not.
396        /// The address of an account that is closed permanently cannot be created again.
397        /// For accounts that are not closed permanently the accounts address
398        /// continues to exist in an account with discriminator and without data.
399        pub fn new_burn(
400            owner: &'a Pubkey,
401            input_account_meta: &CompressedAccountMetaBurn,
402            input_account: A,
403        ) -> Result<Self, LightSdkError> {
404            let input_account_info = {
405                // For HASH_FLAT = false, always use DataHasher
406                let input_data_hash = input_account.hash::<H>()?;
407                let tree_info = input_account_meta.get_tree_info();
408                InAccountInfo {
409                    data_hash: input_data_hash,
410                    lamports: input_account_meta.get_lamports().unwrap_or_default(),
411                    merkle_context: PackedMerkleContext {
412                        merkle_tree_pubkey_index: tree_info.merkle_tree_pubkey_index,
413                        queue_pubkey_index: tree_info.queue_pubkey_index,
414                        leaf_index: tree_info.leaf_index,
415                        prove_by_index: tree_info.prove_by_index,
416                    },
417                    root_index: input_account_meta.get_root_index().unwrap_or_default(),
418                    discriminator: A::LIGHT_DISCRIMINATOR,
419                }
420            };
421
422            Ok(Self {
423                owner,
424                account: input_account,
425                account_info: CompressedAccountInfo {
426                    address: input_account_meta.get_address(),
427                    input: Some(input_account_info),
428                    output: None,
429                },
430                should_remove_data: false,
431                _hasher: PhantomData,
432            })
433        }
434
435        pub fn to_account_info(mut self) -> Result<CompressedAccountInfo, ProgramError> {
436            if let Some(output) = self.account_info.output.as_mut() {
437                if self.should_remove_data {
438                    // Data should be empty to close account.
439                    if !output.data.is_empty() {
440                        return Err(LightSdkError::ExpectedNoData.into());
441                    }
442                    output.data_hash = DEFAULT_DATA_HASH;
443                    output.discriminator = [0u8; 8];
444                } else {
445                    output.data = self
446                        .account
447                        .try_to_vec()
448                        .map_err(|_| LightSdkError::Borsh)?;
449                    // For HASH_FLAT = false, always use DataHasher
450                    output.data_hash = self
451                        .account
452                        .hash::<H>()
453                        .map_err(LightSdkError::from)
454                        .map_err(ProgramError::from)?;
455                }
456            }
457            Ok(self.account_info)
458        }
459        pub fn to_in_account(&self) -> Option<InAccount> {
460            self.account_info
461                .input
462                .as_ref()
463                .map(|input| input.into_in_account(self.account_info.address))
464        }
465
466        pub fn to_output_compressed_account_with_packed_context(
467            &self,
468            owner: Option<solana_pubkey::Pubkey>,
469        ) -> Result<Option<OutputCompressedAccountWithPackedContext>, ProgramError> {
470            let owner = if let Some(owner) = owner {
471                owner.to_bytes().into()
472            } else {
473                (*self.owner).to_bytes().into()
474            };
475
476            if let Some(mut output) = self.account_info.output.clone() {
477                if self.should_remove_data {
478                    // Data should be empty to close account.
479                    if !output.data.is_empty() {
480                        return Err(LightSdkError::ExpectedNoData.into());
481                    }
482                    output.data_hash = DEFAULT_DATA_HASH;
483                    output.discriminator = [0u8; 8];
484                } else {
485                    output.data = self
486                        .account
487                        .try_to_vec()
488                        .map_err(|e| ProgramError::BorshIoError(e.to_string()))?;
489                    // For HASH_FLAT = false, always use DataHasher
490                    output.data_hash = self
491                        .account
492                        .hash::<H>()
493                        .map_err(LightSdkError::from)
494                        .map_err(ProgramError::from)?;
495                    output.data = self
496                        .account
497                        .try_to_vec()
498                        .map_err(|e| ProgramError::BorshIoError(e.to_string()))?;
499                }
500                let result = OutputCompressedAccountWithPackedContext::from_with_owner(
501                    &output,
502                    owner,
503                    self.account_info.address,
504                );
505                Ok(Some(result))
506            } else {
507                Ok(None)
508            }
509        }
510    }
511
512    // Specialized implementation for HASH_FLAT = true (flat serialization without DataHasher)
513    impl<'a, H: Hasher, A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default>
514        LightAccountInner<'a, H, A, true>
515    {
516        pub fn new_mut(
517            owner: &'a Pubkey,
518            input_account_meta: &impl CompressedAccountMetaTrait,
519            input_account: A,
520        ) -> Result<Self, ProgramError> {
521            let input_account_info = {
522                // For HASH_FLAT = true, use direct serialization
523                let data = input_account
524                    .try_to_vec()
525                    .map_err(|e| ProgramError::BorshIoError(e.to_string()))?;
526                let mut input_data_hash = H::hash(data.as_slice())
527                    .map_err(LightSdkError::from)
528                    .map_err(ProgramError::from)?;
529                input_data_hash[0] = 0;
530                let tree_info = input_account_meta.get_tree_info();
531                InAccountInfo {
532                    data_hash: input_data_hash,
533                    lamports: input_account_meta.get_lamports().unwrap_or_default(),
534                    merkle_context: PackedMerkleContext {
535                        merkle_tree_pubkey_index: tree_info.merkle_tree_pubkey_index,
536                        queue_pubkey_index: tree_info.queue_pubkey_index,
537                        leaf_index: tree_info.leaf_index,
538                        prove_by_index: tree_info.prove_by_index,
539                    },
540                    root_index: input_account_meta.get_root_index().unwrap_or_default(),
541                    discriminator: A::LIGHT_DISCRIMINATOR,
542                }
543            };
544            let output_account_info = {
545                let output_merkle_tree_index = input_account_meta
546                    .get_output_state_tree_index()
547                    .ok_or(LightSdkError::OutputStateTreeIndexIsNone)
548                    .map_err(ProgramError::from)?;
549                OutAccountInfo {
550                    lamports: input_account_meta.get_lamports().unwrap_or_default(),
551                    output_merkle_tree_index,
552                    discriminator: A::LIGHT_DISCRIMINATOR,
553                    ..Default::default()
554                }
555            };
556
557            Ok(Self {
558                owner,
559                account: input_account,
560                account_info: CompressedAccountInfo {
561                    address: input_account_meta.get_address(),
562                    input: Some(input_account_info),
563                    output: Some(output_account_info),
564                },
565                should_remove_data: false,
566                _hasher: PhantomData,
567            })
568        }
569
570        // TODO: add in a different pr and release
571        // pub fn init_if_needed(
572        //     owner: &'a Pubkey,
573        //     input_account_meta: CompressedAccountMetaInitIfNeeded,
574        //     input_account: A,
575        // ) -> Result<Self, ProgramError> {
576        //     if input_account_meta.init && input_account_meta.with_new_adress {
577        //         Ok(Self::new_init(
578        //             owner,
579        //             Some(input_account_meta.address),
580        //             input_account_meta.output_state_tree_index,
581        //         ))
582        //     } else if input_account_meta.init {
583        //         // For new_empty, we need a CompressedAccountMetaTrait implementor
584        //         let tree_info = input_account_meta
585        //             .tree_info
586        //             .ok_or(LightSdkError::ExpectedTreeInfo)
587        //             .map_err(ProgramError::from)?;
588
589        //         let meta = CompressedAccountMeta {
590        //             tree_info,
591        //             address: input_account_meta.address,
592        //             output_state_tree_index: input_account_meta.output_state_tree_index,
593        //         };
594        //         Self::new_empty(owner, &meta)
595        //     } else {
596        //         // For new_mut, we need a CompressedAccountMetaTrait implementor
597        //         let tree_info = input_account_meta
598        //             .tree_info
599        //             .ok_or(LightSdkError::ExpectedTreeInfo)
600        //             .map_err(ProgramError::from)?;
601        //         let meta = CompressedAccountMeta {
602        //             tree_info,
603        //             address: input_account_meta.address,
604        //             output_state_tree_index: input_account_meta.output_state_tree_index,
605        //         };
606        //         Self::new_mut(owner, &meta, input_account)
607        //     }
608        // }
609
610        pub fn new_empty(
611            owner: &'a Pubkey,
612            input_account_meta: &impl CompressedAccountMetaTrait,
613        ) -> Result<Self, ProgramError> {
614            let input_account_info = {
615                let input_data_hash = DEFAULT_DATA_HASH;
616                let tree_info = input_account_meta.get_tree_info();
617                InAccountInfo {
618                    data_hash: input_data_hash,
619                    lamports: input_account_meta.get_lamports().unwrap_or_default(),
620                    merkle_context: PackedMerkleContext {
621                        merkle_tree_pubkey_index: tree_info.merkle_tree_pubkey_index,
622                        queue_pubkey_index: tree_info.queue_pubkey_index,
623                        leaf_index: tree_info.leaf_index,
624                        prove_by_index: tree_info.prove_by_index,
625                    },
626                    root_index: input_account_meta.get_root_index().unwrap_or_default(),
627                    discriminator: [0u8; 8],
628                }
629            };
630            let output_account_info = {
631                let output_merkle_tree_index = input_account_meta
632                    .get_output_state_tree_index()
633                    .ok_or(LightSdkError::OutputStateTreeIndexIsNone)
634                    .map_err(ProgramError::from)?;
635                OutAccountInfo {
636                    lamports: input_account_meta.get_lamports().unwrap_or_default(),
637                    output_merkle_tree_index,
638                    discriminator: A::LIGHT_DISCRIMINATOR,
639                    ..Default::default()
640                }
641            };
642
643            Ok(Self {
644                owner,
645                account: A::default(),
646                account_info: CompressedAccountInfo {
647                    address: input_account_meta.get_address(),
648                    input: Some(input_account_info),
649                    output: Some(output_account_info),
650                },
651                should_remove_data: false,
652                _hasher: PhantomData,
653            })
654        }
655
656        /// Closes the compressed account.
657        /// Closed accounts can be reopened again (use LightAccount::new_empty to reopen a compressed account.).
658        /// If you want to ensure an account cannot be opened again use burn.
659        /// Closed accounts preserve the accounts address
660        /// in a compressed account without discriminator and data.
661        pub fn new_close(
662            owner: &'a Pubkey,
663            input_account_meta: &impl CompressedAccountMetaTrait,
664            input_account: A,
665        ) -> Result<Self, ProgramError> {
666            let mut account = Self::new_mut(owner, input_account_meta, input_account)?;
667            account.should_remove_data = true;
668            Ok(account)
669        }
670
671        /// Burns the compressed account.
672        /// The address of an account that is burned cannot be created again.
673        pub fn new_burn(
674            owner: &'a Pubkey,
675            input_account_meta: &CompressedAccountMetaBurn,
676            input_account: A,
677        ) -> Result<Self, ProgramError> {
678            let input_account_info = {
679                // For HASH_FLAT = true, use direct serialization
680                let data = input_account
681                    .try_to_vec()
682                    .map_err(|e| ProgramError::BorshIoError(e.to_string()))?;
683                let mut input_data_hash = H::hash(data.as_slice())
684                    .map_err(LightSdkError::from)
685                    .map_err(ProgramError::from)?;
686                input_data_hash[0] = 0;
687                let tree_info = input_account_meta.get_tree_info();
688                InAccountInfo {
689                    data_hash: input_data_hash,
690                    lamports: input_account_meta.get_lamports().unwrap_or_default(),
691                    merkle_context: PackedMerkleContext {
692                        merkle_tree_pubkey_index: tree_info.merkle_tree_pubkey_index,
693                        queue_pubkey_index: tree_info.queue_pubkey_index,
694                        leaf_index: tree_info.leaf_index,
695                        prove_by_index: tree_info.prove_by_index,
696                    },
697                    root_index: input_account_meta.get_root_index().unwrap_or_default(),
698                    discriminator: A::LIGHT_DISCRIMINATOR,
699                }
700            };
701
702            Ok(Self {
703                owner,
704                account: input_account,
705                account_info: CompressedAccountInfo {
706                    address: input_account_meta.get_address(),
707                    input: Some(input_account_info),
708                    output: None,
709                },
710                should_remove_data: false,
711                _hasher: PhantomData,
712            })
713        }
714
715        pub fn to_account_info(mut self) -> Result<CompressedAccountInfo, ProgramError> {
716            if let Some(output) = self.account_info.output.as_mut() {
717                if self.should_remove_data {
718                    // Data should be empty to close account.
719                    if !output.data.is_empty() {
720                        return Err(LightSdkError::ExpectedNoData.into());
721                    }
722                    output.data_hash = DEFAULT_DATA_HASH;
723                    output.discriminator = [0u8; 8];
724                } else {
725                    output.data = self
726                        .account
727                        .try_to_vec()
728                        .map_err(|e| ProgramError::BorshIoError(e.to_string()))?;
729                    // For HASH_FLAT = true, use direct serialization
730                    output.data_hash = H::hash(output.data.as_slice())
731                        .map_err(LightSdkError::from)
732                        .map_err(ProgramError::from)?;
733                    output.data_hash[0] = 0;
734                }
735            }
736            Ok(self.account_info)
737        }
738        pub fn to_in_account(&self) -> Option<InAccount> {
739            self.account_info
740                .input
741                .as_ref()
742                .map(|input| input.into_in_account(self.account_info.address))
743        }
744        pub fn to_output_compressed_account_with_packed_context(
745            &self,
746            owner: Option<solana_pubkey::Pubkey>,
747        ) -> Result<Option<OutputCompressedAccountWithPackedContext>, ProgramError> {
748            let owner = if let Some(owner) = owner {
749                owner.to_bytes().into()
750            } else {
751                (*self.owner).to_bytes().into()
752            };
753
754            if let Some(mut output) = self.account_info.output.clone() {
755                if self.should_remove_data {
756                    // Data should be empty to close account.
757                    if !output.data.is_empty() {
758                        return Err(LightSdkError::ExpectedNoData.into());
759                    }
760                    output.data_hash = DEFAULT_DATA_HASH;
761                    output.discriminator = [0u8; 8];
762                } else {
763                    output.data = self
764                        .account
765                        .try_to_vec()
766                        .map_err(|e| ProgramError::BorshIoError(e.to_string()))?;
767                    // For HASH_FLAT = true, use direct serialization
768                    output.data_hash = H::hash(output.data.as_slice())
769                        .map_err(LightSdkError::from)
770                        .map_err(ProgramError::from)?;
771                    output.data_hash[0] = 0;
772                    output.data = self
773                        .account
774                        .try_to_vec()
775                        .map_err(|e| ProgramError::BorshIoError(e.to_string()))?;
776                }
777
778                let result = OutputCompressedAccountWithPackedContext::from_with_owner(
779                    &output,
780                    owner,
781                    self.account_info.address,
782                );
783                Ok(Some(result))
784            } else {
785                Ok(None)
786            }
787        }
788    }
789
790    impl<
791            H: Hasher,
792            A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default,
793            const HASH_FLAT: bool,
794        > Deref for LightAccountInner<'_, H, A, HASH_FLAT>
795    {
796        type Target = A;
797
798        fn deref(&self) -> &Self::Target {
799            &self.account
800        }
801    }
802
803    impl<
804            H: Hasher,
805            A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default,
806            const HASH_FLAT: bool,
807        > DerefMut for LightAccountInner<'_, H, A, HASH_FLAT>
808    {
809        fn deref_mut(&mut self) -> &mut <Self as Deref>::Target {
810            &mut self.account
811        }
812    }
813}