1use 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;
146pub mod sha {
149 use super::*;
150 pub type LightAccount<'a, A> = super::LightAccountInner<'a, Sha256, A, true>;
153}
154
155pub mod poseidon {
158 use super::*;
159 pub type LightAccount<'a, A> = super::LightAccountInner<'a, Poseidon, A, false>;
179}
180
181#[doc(hidden)]
182pub use __internal::LightAccountInner;
183
184#[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}