light_sdk/
account.rs

1//! # Light Account
2//!
3//! LightAccount is a wrapper around a compressed account similar to anchor Account.
4//! LightAccount abstracts hashing of compressed account data,
5//! and wraps the compressed account data so that it is easy to use.
6//!
7//! Data structs used with LightAccount must implement the traits:
8//! - DataHasher
9//! - LightDiscriminator
10//! - BorshSerialize, BorshDeserialize
11//! - Debug, Default, Clone
12//!
13//! ### Account Data Hashing
14//! The LightHasher derives a hashing scheme from the compressed account layout.
15//! Alternatively, DataHasher can be implemented manually.
16//!
17//! Constraints:
18//! - Poseidon hashes can only take up to 12 inputs
19//!     -> use nested structs for structs with more than 12 fields.
20//! - Poseidon hashes inputs must be less than bn254 field size (254 bits).
21//! hash_to_field_size methods in light hasher can be used to hash data longer than 253 bits.
22//!     -> use the `#[hash]` attribute for fields with data types greater than 31 bytes eg Pubkeys.
23//!
24//! ### Compressed account with LightHasher and LightDiscriminator
25//! ```
26//! use light_sdk::{LightHasher, LightDiscriminator};
27//! use solana_pubkey::Pubkey;
28//! #[derive(Clone, Debug, Default, LightHasher, LightDiscriminator)]
29//! pub struct CounterAccount {
30//!     #[hash]
31//!     pub owner: Pubkey,
32//!     pub counter: u64,
33//! }
34//! ```
35//!
36//!
37//! ### Create compressed account
38//! ```ignore
39//! let mut my_compressed_account = LightAccount::<'_, CounterAccount>::new_init(
40//!     &crate::ID,
41//!     // Address
42//!     Some(address),
43//!     output_tree_index,
44//! );
45//! // Set data:
46//! my_compressed_account.owner = ctx.accounts.signer.key();
47//! ```
48//! ### Update compressed account
49//! ```ignore
50//! let mut my_compressed_account = LightAccount::<'_, CounterAccount>::new_mut(
51//!     &crate::ID,
52//!     &account_meta,
53//!     my_compressed_account,
54//! );
55//! // Increment counter.
56//! my_compressed_account.counter += 1;
57//! ```
58//! ### Close compressed account
59//! ```ignore
60//! let mut my_compressed_account = LightAccount::<'_, CounterAccount>::new_close(
61//!     &crate::ID,
62//!     &account_meta_close,
63//!     my_compressed_account,
64//! );
65//! ```
66// TODO: add example for manual hashing
67
68use std::ops::{Deref, DerefMut};
69
70use light_compressed_account::{
71    compressed_account::PackedMerkleContext,
72    instruction_data::with_account_info::{CompressedAccountInfo, InAccountInfo, OutAccountInfo},
73};
74use light_sdk_types::instruction::account_meta::CompressedAccountMetaTrait;
75use solana_pubkey::Pubkey;
76
77use crate::{
78    error::LightSdkError,
79    light_hasher::{DataHasher, Poseidon},
80    AnchorDeserialize, AnchorSerialize, LightDiscriminator,
81};
82
83#[derive(Debug, PartialEq)]
84pub struct LightAccount<
85    'a,
86    A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + DataHasher + Default,
87> {
88    owner: &'a Pubkey,
89    pub account: A,
90    account_info: CompressedAccountInfo,
91}
92
93impl<'a, A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + DataHasher + Default>
94    LightAccount<'a, A>
95{
96    pub fn new_init(
97        owner: &'a Pubkey,
98        address: Option<[u8; 32]>,
99        output_state_tree_index: u8,
100    ) -> Self {
101        let output_account_info = OutAccountInfo {
102            output_merkle_tree_index: output_state_tree_index,
103            discriminator: A::LIGHT_DISCRIMINATOR,
104            ..Default::default()
105        };
106        Self {
107            owner,
108            account: A::default(),
109            account_info: CompressedAccountInfo {
110                address,
111                input: None,
112                output: Some(output_account_info),
113            },
114        }
115    }
116
117    pub fn new_mut(
118        owner: &'a Pubkey,
119        input_account_meta: &impl CompressedAccountMetaTrait,
120        input_account: A,
121    ) -> Result<Self, LightSdkError> {
122        let input_account_info = {
123            let input_data_hash = input_account.hash::<Poseidon>()?;
124            let tree_info = input_account_meta.get_tree_info();
125            InAccountInfo {
126                data_hash: input_data_hash,
127                lamports: input_account_meta.get_lamports().unwrap_or_default(),
128                merkle_context: PackedMerkleContext {
129                    merkle_tree_pubkey_index: tree_info.merkle_tree_pubkey_index,
130                    queue_pubkey_index: tree_info.queue_pubkey_index,
131                    leaf_index: tree_info.leaf_index,
132                    prove_by_index: tree_info.prove_by_index,
133                },
134                root_index: input_account_meta.get_root_index().unwrap_or_default(),
135                discriminator: A::LIGHT_DISCRIMINATOR,
136            }
137        };
138        let output_account_info = {
139            let output_merkle_tree_index = input_account_meta
140                .get_output_state_tree_index()
141                .ok_or(LightSdkError::OutputStateTreeIndexIsNone)?;
142            OutAccountInfo {
143                lamports: input_account_meta.get_lamports().unwrap_or_default(),
144                output_merkle_tree_index,
145                discriminator: A::LIGHT_DISCRIMINATOR,
146                ..Default::default()
147            }
148        };
149
150        Ok(Self {
151            owner,
152            account: input_account,
153            account_info: CompressedAccountInfo {
154                address: input_account_meta.get_address(),
155                input: Some(input_account_info),
156                output: Some(output_account_info),
157            },
158        })
159    }
160
161    pub fn new_close(
162        owner: &'a Pubkey,
163        input_account_meta: &impl CompressedAccountMetaTrait,
164        input_account: A,
165    ) -> Result<Self, LightSdkError> {
166        let input_account_info = {
167            let input_data_hash = input_account.hash::<Poseidon>()?;
168            let tree_info = input_account_meta.get_tree_info();
169            InAccountInfo {
170                data_hash: input_data_hash,
171                lamports: input_account_meta.get_lamports().unwrap_or_default(),
172                merkle_context: PackedMerkleContext {
173                    merkle_tree_pubkey_index: tree_info.merkle_tree_pubkey_index,
174                    queue_pubkey_index: tree_info.queue_pubkey_index,
175                    leaf_index: tree_info.leaf_index,
176                    prove_by_index: tree_info.prove_by_index,
177                },
178                root_index: input_account_meta.get_root_index().unwrap_or_default(),
179                discriminator: A::LIGHT_DISCRIMINATOR,
180            }
181        };
182        Ok(Self {
183            owner,
184            account: input_account,
185            account_info: CompressedAccountInfo {
186                address: input_account_meta.get_address(),
187                input: Some(input_account_info),
188                output: None,
189            },
190        })
191    }
192
193    pub fn discriminator(&self) -> &[u8; 8] {
194        &A::LIGHT_DISCRIMINATOR
195    }
196
197    pub fn lamports(&self) -> u64 {
198        if let Some(output) = self.account_info.output.as_ref() {
199            output.lamports
200        } else if let Some(input) = self.account_info.input.as_ref() {
201            input.lamports
202        } else {
203            0
204        }
205    }
206
207    pub fn lamports_mut(&mut self) -> &mut u64 {
208        if let Some(output) = self.account_info.output.as_mut() {
209            &mut output.lamports
210        } else if let Some(input) = self.account_info.input.as_mut() {
211            &mut input.lamports
212        } else {
213            panic!("No lamports field available in account_info")
214        }
215    }
216
217    pub fn address(&self) -> &Option<[u8; 32]> {
218        &self.account_info.address
219    }
220
221    pub fn owner(&self) -> &Pubkey {
222        self.owner
223    }
224
225    pub fn in_account_info(&self) -> &Option<InAccountInfo> {
226        &self.account_info.input
227    }
228
229    pub fn out_account_info(&mut self) -> &Option<OutAccountInfo> {
230        &self.account_info.output
231    }
232
233    /// 1. Serializes the account data and sets the output data hash.
234    /// 2. Returns CompressedAccountInfo.
235    ///
236    /// Note this is an expensive operation
237    /// that should only be called once per instruction.
238    pub fn to_account_info(mut self) -> Result<CompressedAccountInfo, LightSdkError> {
239        if let Some(output) = self.account_info.output.as_mut() {
240            output.data_hash = self.account.hash::<Poseidon>()?;
241            output.data = self
242                .account
243                .try_to_vec()
244                .map_err(|_| LightSdkError::Borsh)?;
245        }
246        Ok(self.account_info)
247    }
248}
249
250impl<A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + DataHasher + Default> Deref
251    for LightAccount<'_, A>
252{
253    type Target = A;
254
255    fn deref(&self) -> &Self::Target {
256        &self.account
257    }
258}
259
260impl<A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + DataHasher + Default> DerefMut
261    for LightAccount<'_, A>
262{
263    fn deref_mut(&mut self) -> &mut <Self as Deref>::Target {
264        &mut self.account
265    }
266}