From 9d2b79cc956d8e4d799bc4d122b9488225747959 Mon Sep 17 00:00:00 2001 From: ananas Date: Sat, 31 Jan 2026 21:54:38 +0000 Subject: [PATCH 01/15] refactor: sdk/interface -> standalone crate --- Cargo.lock | 30 ++ Cargo.toml | 2 + .../src/account_info/account_info_trait.rs | 4 + .../src/account_info/pinocchio.rs | 21 + .../account-checks/src/account_info/solana.rs | 7 + program-libs/account-checks/src/error.rs | 3 + sdk-libs/client/src/interface/instructions.rs | 1 + sdk-libs/client/src/interface/pack.rs | 1 + .../compressed_token/v2/compress_and_close.rs | 13 +- .../compressed_token/v2/decompress_full.rs | 15 +- .../macros/src/light_pdas/accounts/builder.rs | 8 +- .../macros/src/light_pdas/accounts/pda.rs | 8 +- .../macros/src/light_pdas/program/parsing.rs | 6 +- sdk-libs/sdk-interface/Cargo.toml | 99 ++++ .../src}/account/compression_info.rs | 56 +- .../src}/account/light_account.rs | 7 +- .../src}/account/mod.rs | 0 .../src}/account/pack.rs | 0 .../src}/account/pda_seeds.rs | 4 - .../src}/account/token_seeds.rs | 7 +- .../src}/accounts/create_pda.rs | 14 +- .../src}/accounts/finalize.rs | 27 +- .../src}/accounts/init_compressed_account.rs | 16 +- .../src}/accounts/mod.rs | 1 - .../{sdk => sdk-interface}/src/cpi/account.rs | 15 +- sdk-libs/sdk-interface/src/cpi/instruction.rs | 63 +++ .../{sdk => sdk-interface}/src/cpi/invoke.rs | 32 +- sdk-libs/sdk-interface/src/cpi/mod.rs | 18 + .../src/cpi/v1/accounts.rs | 10 +- sdk-libs/sdk-interface/src/cpi/v1/invoke.rs | 186 +++++++ sdk-libs/sdk-interface/src/cpi/v1/mod.rs | 32 ++ .../src/cpi/v2/accounts.rs | 6 +- .../src/cpi/v2/accounts_cpi_context.rs | 6 - sdk-libs/sdk-interface/src/cpi/v2/invoke.rs | 100 ++++ sdk-libs/sdk-interface/src/cpi/v2/mod.rs | 70 +++ sdk-libs/sdk-interface/src/error.rs | 87 +++ sdk-libs/sdk-interface/src/instruction/mod.rs | 34 ++ .../src/instruction/pack_accounts.rs | 179 ++++++ .../mod.rs => sdk-interface/src/lib.rs} | 51 +- .../src}/program/compression/close.rs | 12 +- .../src}/program/compression/mod.rs | 1 - .../src}/program/compression/pda.rs | 9 +- .../src}/program/compression/processor.rs | 19 +- .../src}/program/config/create.rs | 46 +- .../src}/program/config/mod.rs | 10 +- .../src}/program/config/state.rs | 25 +- .../src}/program/config/update.rs | 15 +- .../decompression/create_token_account.rs | 0 .../src}/program/decompression/mod.rs | 0 .../src}/program/decompression/pda.rs | 10 +- .../src}/program/decompression/processor.rs | 19 +- .../src}/program/decompression/token.rs | 5 +- .../src}/program/mod.rs | 0 .../src}/program/validation.rs | 15 +- .../src}/program/variant.rs | 4 +- sdk-libs/sdk/Cargo.toml | 11 +- sdk-libs/sdk/src/cpi/instruction.rs | 80 +-- sdk-libs/sdk/src/cpi/mod.rs | 17 +- sdk-libs/sdk/src/cpi/v1/invoke.rs | 308 +---------- sdk-libs/sdk/src/cpi/v1/mod.rs | 22 +- sdk-libs/sdk/src/cpi/v2/invoke.rs | 127 +---- sdk-libs/sdk/src/cpi/v2/mod.rs | 163 +----- sdk-libs/sdk/src/error.rs | 56 ++ sdk-libs/sdk/src/instruction/mod.rs | 74 +-- sdk-libs/sdk/src/instruction/pack_accounts.rs | 509 ------------------ .../src/instruction/packed_accounts_ext.rs | 66 +++ sdk-libs/sdk/src/lib.rs | 20 +- .../tests/failing_tests.rs | 11 +- 68 files changed, 1394 insertions(+), 1499 deletions(-) create mode 100644 sdk-libs/sdk-interface/Cargo.toml rename sdk-libs/{sdk/src/interface => sdk-interface/src}/account/compression_info.rs (91%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/account/light_account.rs (89%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/account/mod.rs (100%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/account/pack.rs (100%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/account/pda_seeds.rs (83%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/account/token_seeds.rs (97%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/accounts/create_pda.rs (93%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/accounts/finalize.rs (61%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/accounts/init_compressed_account.rs (95%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/accounts/mod.rs (92%) rename sdk-libs/{sdk => sdk-interface}/src/cpi/account.rs (88%) create mode 100644 sdk-libs/sdk-interface/src/cpi/instruction.rs rename sdk-libs/{sdk => sdk-interface}/src/cpi/invoke.rs (91%) create mode 100644 sdk-libs/sdk-interface/src/cpi/mod.rs rename sdk-libs/{sdk => sdk-interface}/src/cpi/v1/accounts.rs (96%) create mode 100644 sdk-libs/sdk-interface/src/cpi/v1/invoke.rs create mode 100644 sdk-libs/sdk-interface/src/cpi/v1/mod.rs rename sdk-libs/{sdk => sdk-interface}/src/cpi/v2/accounts.rs (94%) rename sdk-libs/{sdk => sdk-interface}/src/cpi/v2/accounts_cpi_context.rs (65%) create mode 100644 sdk-libs/sdk-interface/src/cpi/v2/invoke.rs create mode 100644 sdk-libs/sdk-interface/src/cpi/v2/mod.rs create mode 100644 sdk-libs/sdk-interface/src/error.rs create mode 100644 sdk-libs/sdk-interface/src/instruction/mod.rs create mode 100644 sdk-libs/sdk-interface/src/instruction/pack_accounts.rs rename sdk-libs/{sdk/src/interface/mod.rs => sdk-interface/src/lib.rs} (77%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/program/compression/close.rs (73%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/program/compression/mod.rs (86%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/program/compression/pda.rs (94%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/program/compression/processor.rs (89%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/program/config/create.rs (88%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/program/config/mod.rs (88%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/program/config/state.rs (89%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/program/config/update.rs (89%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/program/decompression/create_token_account.rs (100%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/program/decompression/mod.rs (100%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/program/decompression/pda.rs (95%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/program/decompression/processor.rs (96%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/program/decompression/token.rs (97%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/program/mod.rs (100%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/program/validation.rs (94%) rename sdk-libs/{sdk/src/interface => sdk-interface/src}/program/variant.rs (97%) delete mode 100644 sdk-libs/sdk/src/instruction/pack_accounts.rs create mode 100644 sdk-libs/sdk/src/instruction/packed_accounts_ext.rs diff --git a/Cargo.lock b/Cargo.lock index 5d3283b325..bb2e70f56e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4059,6 +4059,7 @@ dependencies = [ "light-heap", "light-macros", "light-program-profiler", + "light-sdk-interface", "light-sdk-macros", "light-sdk-types", "light-token-interface", @@ -4078,6 +4079,35 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "light-sdk-interface" +version = "0.19.0" +dependencies = [ + "anchor-lang", + "bincode", + "borsh 0.10.4", + "bytemuck", + "light-account-checks", + "light-compressed-account", + "light-compressible", + "light-hasher", + "light-sdk-types", + "light-token-interface", + "pinocchio", + "pinocchio-system", + "solana-account-info", + "solana-clock", + "solana-cpi", + "solana-instruction", + "solana-loader-v3-interface", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "solana-system-interface 1.0.0", + "solana-sysvar", + "thiserror 2.0.17", +] + [[package]] name = "light-sdk-macros" version = "0.19.0" diff --git a/Cargo.toml b/Cargo.toml index b708d9c74d..b1d78ac96e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "sdk-libs/token-client", "sdk-libs/macros", "sdk-libs/sdk", + "sdk-libs/sdk-interface", "sdk-libs/sdk-pinocchio", "sdk-libs/sdk-types", "sdk-libs/photon-api", @@ -199,6 +200,7 @@ light-merkle-tree-reference = { path = "program-tests/merkle-tree", version = "4 light-heap = { path = "program-libs/heap", version = "2.0.0" } light-prover-client = { path = "prover/client", version = "6.0.0" } light-sdk = { path = "sdk-libs/sdk", version = "0.19.0" } +light-sdk-interface = { path = "sdk-libs/sdk-interface", version = "0.19.0", default-features = false } light-sdk-pinocchio = { path = "sdk-libs/sdk-pinocchio", version = "0.19.0" } light-sdk-macros = { path = "sdk-libs/macros", version = "0.19.0" } light-sdk-types = { path = "sdk-libs/sdk-types", version = "0.19.0", default-features = false } diff --git a/program-libs/account-checks/src/account_info/account_info_trait.rs b/program-libs/account-checks/src/account_info/account_info_trait.rs index 14eddd2b26..d2efbdaee0 100644 --- a/program-libs/account-checks/src/account_info/account_info_trait.rs +++ b/program-libs/account-checks/src/account_info/account_info_trait.rs @@ -45,6 +45,10 @@ pub trait AccountInfoTrait { /// Get minimum rent balance for a given size fn get_min_rent_balance(size: usize) -> Result; + /// Get the current clock slot from sysvar. + /// Only meaningful on-chain; implementations may error off-chain. + fn get_current_slot() -> Result; + fn data_is_empty(&self) -> bool { self.data_len() == 0 } diff --git a/program-libs/account-checks/src/account_info/pinocchio.rs b/program-libs/account-checks/src/account_info/pinocchio.rs index b6b7c83134..03c5f07704 100644 --- a/program-libs/account-checks/src/account_info/pinocchio.rs +++ b/program-libs/account-checks/src/account_info/pinocchio.rs @@ -120,4 +120,25 @@ impl AccountInfoTrait for pinocchio::account_info::AccountInfo { Err(AccountError::FailedBorrowRentSysvar) } } + + fn get_current_slot() -> Result { + #[cfg(target_os = "solana")] + { + use pinocchio::sysvars::Sysvar; + pinocchio::sysvars::clock::Clock::get() + .map(|c| c.slot) + .map_err(|_| AccountError::FailedSysvarAccess) + } + #[cfg(all(not(target_os = "solana"), feature = "solana"))] + { + use solana_sysvar::Sysvar; + solana_sysvar::clock::Clock::get() + .map(|c| c.slot) + .map_err(|_| AccountError::FailedSysvarAccess) + } + #[cfg(all(not(target_os = "solana"), not(feature = "solana")))] + { + Err(AccountError::FailedSysvarAccess) + } + } } diff --git a/program-libs/account-checks/src/account_info/solana.rs b/program-libs/account-checks/src/account_info/solana.rs index ebeca75dc5..dfc8254b2b 100644 --- a/program-libs/account-checks/src/account_info/solana.rs +++ b/program-libs/account-checks/src/account_info/solana.rs @@ -85,4 +85,11 @@ impl AccountInfoTrait for solana_account_info::AccountInfo<'_> { .map(|rent| rent.minimum_balance(size)) .map_err(|_| AccountError::FailedBorrowRentSysvar) } + + fn get_current_slot() -> Result { + use solana_sysvar::Sysvar; + solana_sysvar::clock::Clock::get() + .map(|c| c.slot) + .map_err(|_| AccountError::FailedSysvarAccess) + } } diff --git a/program-libs/account-checks/src/error.rs b/program-libs/account-checks/src/error.rs index 1a0d3f0cc3..381eaac6f4 100644 --- a/program-libs/account-checks/src/error.rs +++ b/program-libs/account-checks/src/error.rs @@ -34,6 +34,8 @@ pub enum AccountError { NotEnoughAccountKeys, #[error("Invalid Account.")] InvalidAccount, + #[error("Failed to access sysvar.")] + FailedSysvarAccess, #[error("Pinocchio program error with code: {0}")] PinocchioProgramError(u32), } @@ -57,6 +59,7 @@ impl From for u32 { AccountError::AccountNotZeroed => 20013, AccountError::NotEnoughAccountKeys => 20014, AccountError::InvalidAccount => 20015, + AccountError::FailedSysvarAccess => 20016, AccountError::PinocchioProgramError(code) => code, } } diff --git a/sdk-libs/client/src/interface/instructions.rs b/sdk-libs/client/src/interface/instructions.rs index 702e231379..6a88000b89 100644 --- a/sdk-libs/client/src/interface/instructions.rs +++ b/sdk-libs/client/src/interface/instructions.rs @@ -10,6 +10,7 @@ use light_sdk::{ account_meta::CompressedAccountMetaNoLamportsNoAddress, PackedAccounts, SystemAccountMetaConfig, ValidityProof, }, + PackedAccountsExt, }; use light_token::constants::{ LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_CPI_AUTHORITY, LIGHT_TOKEN_PROGRAM_ID, diff --git a/sdk-libs/client/src/interface/pack.rs b/sdk-libs/client/src/interface/pack.rs index 1247586928..90216a8921 100644 --- a/sdk-libs/client/src/interface/pack.rs +++ b/sdk-libs/client/src/interface/pack.rs @@ -1,6 +1,7 @@ //! Helper for packing validity proofs into remaining accounts. use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; +use light_sdk::PackedAccountsExt; pub use light_sdk::instruction::{PackedAddressTreeInfo, PackedStateTreeInfo}; use solana_instruction::AccountMeta; use solana_pubkey::Pubkey; diff --git a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs index 7e5d7a636d..efd7c0b965 100644 --- a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs +++ b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs @@ -2,9 +2,11 @@ use light_program_profiler::profile; // PackedAccounts and AccountMetasVec are only available off-chain (client-side) #[cfg(not(target_os = "solana"))] use light_sdk::{ - error::LightSdkError, instruction::{AccountMetasVec, PackedAccounts, SystemAccountMetaConfig}, + PackedAccountsExt, }; +#[cfg(not(target_os = "solana"))] +use solana_program_error::ProgramError as PackedAccountsError; use light_token_interface::instructions::transfer2::CompressedCpiContext; #[cfg(not(target_os = "solana"))] use light_token_interface::state::Token; @@ -402,7 +404,10 @@ impl AccountMetasVec for CompressAndCloseAccounts { /// Adds: /// 1. system accounts if not set /// 2. compressed token program and ctoken cpi authority pda to pre accounts - fn get_account_metas_vec(&self, accounts: &mut PackedAccounts) -> Result<(), LightSdkError> { + fn get_account_metas_vec( + &self, + accounts: &mut PackedAccounts, + ) -> Result<(), PackedAccountsError> { if !accounts.system_accounts_set() { let mut config = SystemAccountMetaConfig::default(); config.self_program = self.self_program; @@ -414,10 +419,10 @@ impl AccountMetasVec for CompressAndCloseAccounts { { if self.cpi_context.is_some() { msg!("Error: cpi_context is set but 'cpi-context' feature is not enabled"); - return Err(LightSdkError::ExpectedCpiContext); + return Err(PackedAccountsError::InvalidArgument); } } - accounts.add_system_accounts_v2(config)?; + accounts.add_system_accounts_v2(config).map_err(PackedAccountsError::from)?; } // Add both accounts in one operation for better performance accounts.pre_accounts.extend_from_slice(&[ diff --git a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs index f6346cabad..3e2bb25739 100644 --- a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs +++ b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs @@ -2,15 +2,15 @@ use light_compressed_account::compressed_account::PackedMerkleContext; use light_compressed_account::instruction_data::compressed_proof::ValidityProof; use light_program_profiler::profile; -#[cfg(not(target_os = "solana"))] -use light_sdk::error::LightSdkError; use light_sdk::{instruction::PackedStateTreeInfo, Unpack}; // Pack and PackedAccounts only available off-chain (client-side) #[cfg(not(target_os = "solana"))] use light_sdk::{ instruction::{AccountMetasVec, PackedAccounts, SystemAccountMetaConfig}, - Pack, + Pack, PackedAccountsExt, }; +#[cfg(not(target_os = "solana"))] +use solana_program_error::ProgramError as PackedAccountsError; use light_token_interface::instructions::{ extensions::ExtensionInstructionData, transfer2::{CompressedCpiContext, MultiInputTokenDataWithContext}, @@ -354,7 +354,10 @@ impl AccountMetasVec for DecompressFullAccounts { /// Adds: /// 1. system accounts if not set /// 2. compressed token program and ctoken cpi authority pda to pre accounts - fn get_account_metas_vec(&self, accounts: &mut PackedAccounts) -> Result<(), LightSdkError> { + fn get_account_metas_vec( + &self, + accounts: &mut PackedAccounts, + ) -> Result<(), PackedAccountsError> { if !accounts.system_accounts_set() { #[cfg(feature = "cpi-context")] let config = { @@ -370,7 +373,9 @@ impl AccountMetasVec for DecompressFullAccounts { config }; - accounts.add_system_accounts_v2(config)?; + accounts + .add_system_accounts_v2(config) + .map_err(PackedAccountsError::from)?; } // Add both accounts in one operation for better performance accounts.pre_accounts.extend_from_slice(&[ diff --git a/sdk-libs/macros/src/light_pdas/accounts/builder.rs b/sdk-libs/macros/src/light_pdas/accounts/builder.rs index c272442707..b65eaad3a8 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/builder.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/builder.rs @@ -149,7 +149,7 @@ impl LightAccountsBuilder { &mut self, _remaining: &[solana_account_info::AccountInfo<'info>], _params: &(), - ) -> std::result::Result { + ) -> std::result::Result { Ok(false) } } @@ -161,7 +161,7 @@ impl LightAccountsBuilder { _remaining: &[solana_account_info::AccountInfo<'info>], _params: &(), _has_pre_init: bool, - ) -> std::result::Result<(), light_sdk::error::LightSdkError> { + ) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { Ok(()) } } @@ -410,7 +410,7 @@ impl LightAccountsBuilder { &mut self, _remaining: &[solana_account_info::AccountInfo<'info>], #params_ident: &#params_type, - ) -> std::result::Result { + ) -> std::result::Result { use anchor_lang::ToAccountInfo; #body } @@ -436,7 +436,7 @@ impl LightAccountsBuilder { _remaining: &[solana_account_info::AccountInfo<'info>], #params_ident: &#params_type, _has_pre_init: bool, - ) -> std::result::Result<(), light_sdk::error::LightSdkError> { + ) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { use anchor_lang::ToAccountInfo; #body } diff --git a/sdk-libs/macros/src/light_pdas/accounts/pda.rs b/sdk-libs/macros/src/light_pdas/accounts/pda.rs index aefb103106..813cbc845e 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/pda.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/pda.rs @@ -119,9 +119,9 @@ impl<'a> PdaBlockBuilder<'a> { // Now serialize - the mutable borrow above is released let mut data = account_info .try_borrow_mut_data() - .map_err(|_| light_sdk::error::LightSdkError::ConstraintViolation)?; + .map_err(|_| light_sdk::interface::error::LightPdaError::ConstraintViolation)?; self.#ident.serialize(&mut &mut data[8..]) - .map_err(|_| light_sdk::error::LightSdkError::ConstraintViolation)?; + .map_err(|_| light_sdk::interface::error::LightPdaError::ConstraintViolation)?; } } } else { @@ -141,9 +141,9 @@ impl<'a> PdaBlockBuilder<'a> { // Now serialize - the mutable borrow above is released let mut data = account_info .try_borrow_mut_data() - .map_err(|_| light_sdk::error::LightSdkError::ConstraintViolation)?; + .map_err(|_| light_sdk::interface::error::LightPdaError::ConstraintViolation)?; self.#ident.serialize(&mut &mut data[8..]) - .map_err(|_| light_sdk::error::LightSdkError::ConstraintViolation)?; + .map_err(|_| light_sdk::interface::error::LightPdaError::ConstraintViolation)?; } } } diff --git a/sdk-libs/macros/src/light_pdas/program/parsing.rs b/sdk-libs/macros/src/light_pdas/program/parsing.rs index 0c8be23ee8..2865605b7c 100644 --- a/sdk-libs/macros/src/light_pdas/program/parsing.rs +++ b/sdk-libs/macros/src/light_pdas/program/parsing.rs @@ -684,7 +684,7 @@ pub fn wrap_function_with_light( // Phase 1: Pre-init (creates mints via CPI context write, registers compressed addresses) use light_sdk::interface::{LightPreInit, LightFinalize}; let _ = #ctx_name.accounts.light_pre_init(#ctx_name.remaining_accounts, &#params_ident) - .map_err(|e: light_sdk::error::LightSdkError| -> solana_program_error::ProgramError { + .map_err(|e: light_sdk::interface::error::LightPdaError| -> solana_program_error::ProgramError { e.into() })?; @@ -700,7 +700,7 @@ pub fn wrap_function_with_light( // Phase 1: Pre-init (creates mints via CPI context write, registers compressed addresses) use light_sdk::interface::{LightPreInit, LightFinalize}; let __has_pre_init = #ctx_name.accounts.light_pre_init(#ctx_name.remaining_accounts, &#params_ident) - .map_err(|e: light_sdk::error::LightSdkError| -> solana_program_error::ProgramError { + .map_err(|e: light_sdk::interface::error::LightPdaError| -> solana_program_error::ProgramError { e.into() })?; @@ -711,7 +711,7 @@ pub fn wrap_function_with_light( // Phase 2: Finalize (creates token accounts/ATAs via CPI) #ctx_name.accounts.light_finalize(#ctx_name.remaining_accounts, &#params_ident, __has_pre_init) - .map_err(|e: light_sdk::error::LightSdkError| -> solana_program_error::ProgramError { + .map_err(|e: light_sdk::interface::error::LightPdaError| -> solana_program_error::ProgramError { e.into() })?; diff --git a/sdk-libs/sdk-interface/Cargo.toml b/sdk-libs/sdk-interface/Cargo.toml new file mode 100644 index 0000000000..d138a0e770 --- /dev/null +++ b/sdk-libs/sdk-interface/Cargo.toml @@ -0,0 +1,99 @@ +[package] +name = "light-sdk-interface" +version = "0.19.0" +description = "Framework-agnostic interface for Light Protocol compressible accounts." +repository = "https://github.com/Lightprotocol/light-protocol" +license = "Apache-2.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] + +[features] +default = ["solana", "std"] +solana = [ + "light-account-checks/solana", + "light-compressible/solana", + "dep:solana-account-info", + "dep:solana-pubkey", + "dep:solana-cpi", + "dep:solana-instruction", + "dep:solana-system-interface", + "dep:solana-program-error", + "dep:solana-clock", + "dep:solana-sysvar", + "dep:solana-loader-v3-interface", + "dep:solana-msg", + "dep:bincode", +] +pinocchio = [ + "light-account-checks/pinocchio", + "light-compressible/pinocchio", + "dep:pinocchio", + "dep:pinocchio-system", +] +std = [ + "alloc", + "light-compressed-account/std", + "light-sdk-types/std", +] +alloc = ["light-compressed-account/alloc"] +anchor = [ + "anchor-lang", + "light-compressed-account/anchor", + "light-sdk-types/anchor", + "light-compressible/anchor", + "light-token-interface/anchor", +] +poseidon = ["light-hasher/poseidon", "light-compressed-account/poseidon"] +keccak = ["light-hasher/keccak", "light-compressed-account/keccak"] +sha256 = ["light-hasher/sha256", "light-compressed-account/sha256"] +idl-build = [ + "anchor", + "anchor-lang/idl-build", + "light-compressed-account/idl-build", + "light-sdk-types/idl-build", + "light-compressible/idl-build", + "light-token-interface/idl-build", +] + +[dependencies] +# Light Protocol internal +light-account-checks = { workspace = true } +light-sdk-types = { workspace = true, default-features = false, features = ["v2", "cpi-context"] } +light-compressed-account = { workspace = true, default-features = false } +light-compressible = { workspace = true, default-features = false } +light-hasher = { workspace = true, default-features = false } +light-token-interface = { workspace = true, default-features = false } + +# Solana-specific (behind solana feature) +solana-account-info = { workspace = true, optional = true } +solana-pubkey = { workspace = true, optional = true } +solana-cpi = { workspace = true, optional = true } +solana-instruction = { workspace = true, optional = true } +solana-system-interface = { workspace = true, optional = true } +solana-program-error = { workspace = true, optional = true } +solana-clock = { workspace = true, optional = true } +solana-sysvar = { workspace = true, optional = true, features = ["bincode"] } +solana-loader-v3-interface = { workspace = true, optional = true } +solana-msg = { workspace = true, optional = true } +bincode = { version = "1", optional = true } + +# Pinocchio-specific (behind pinocchio feature) +pinocchio = { workspace = true, optional = true } +pinocchio-system = { workspace = true, optional = true } + +# Anchor (optional) +anchor-lang = { workspace = true, optional = true } + +# Third-party +borsh = { workspace = true } +bytemuck = { workspace = true } +thiserror = { workspace = true } + +[lints.rust.unexpected_cfgs] +level = "allow" +check-cfg = [ + 'cfg(target_os, values("solana"))', + 'cfg(feature, values("frozen-abi", "no-entrypoint"))', +] diff --git a/sdk-libs/sdk/src/interface/account/compression_info.rs b/sdk-libs/sdk-interface/src/account/compression_info.rs similarity index 91% rename from sdk-libs/sdk/src/interface/account/compression_info.rs rename to sdk-libs/sdk-interface/src/account/compression_info.rs index c32aa6022d..724d00e917 100644 --- a/sdk-libs/sdk/src/interface/account/compression_info.rs +++ b/sdk-libs/sdk-interface/src/account/compression_info.rs @@ -1,17 +1,14 @@ -use std::borrow::Cow; +extern crate alloc; +use alloc::borrow::Cow; use bytemuck::{Pod, Zeroable}; use light_compressible::rent::RentConfig; use light_sdk_types::instruction::PackedStateTreeInfo; use solana_account_info::AccountInfo; -use solana_clock::Clock; -use solana_cpi::invoke; -use solana_instruction::{AccountMeta, Instruction}; -use solana_pubkey::Pubkey; -use solana_sysvar::Sysvar; +use solana_program_error::ProgramError; use super::pack::Unpack; -use crate::{AnchorDeserialize, AnchorSerialize, ProgramError}; +use crate::{AnchorDeserialize, AnchorSerialize}; #[derive(Clone, Copy, Debug, PartialEq, Eq, AnchorSerialize, AnchorDeserialize)] #[repr(u8)] @@ -92,13 +89,13 @@ impl HasCompressionInfo for T { fn compression_info(&self) -> Result<&CompressionInfo, ProgramError> { self.compression_info_field() .as_ref() - .ok_or(crate::error::LightSdkError::MissingCompressionInfo.into()) + .ok_or(crate::error::LightPdaError::MissingCompressionInfo.into()) } fn compression_info_mut(&mut self) -> Result<&mut CompressionInfo, ProgramError> { self.compression_info_field_mut() .as_mut() - .ok_or(crate::error::LightSdkError::MissingCompressionInfo.into()) + .ok_or(crate::error::LightPdaError::MissingCompressionInfo.into()) } fn compression_info_mut_opt(&mut self) -> &mut Option { @@ -121,7 +118,6 @@ pub trait CompressAs { type Output: crate::AnchorSerialize + crate::AnchorDeserialize + crate::LightDiscriminator - + crate::account::Size + HasCompressionInfo + Default + Clone; @@ -203,7 +199,7 @@ impl CompressionInfo { /// Rent sponsor is always the config's rent_sponsor (not stored per-account). /// This means rent always flows to the protocol's rent pool upon compression, /// regardless of who paid for account creation. - pub fn new_from_config(cfg: &crate::interface::LightConfig, current_slot: u64) -> Self { + pub fn new_from_config(cfg: &crate::program::config::LightConfig, current_slot: u64) -> Self { Self { last_claimed_slot: current_slot, lamports_per_write: cfg.write_top_up, @@ -214,23 +210,22 @@ impl CompressionInfo { } } - /// Backward-compat constructor used by older call sites; initializes minimal fields. + /// Backward-compat constructor; initializes minimal fields. /// Rent will flow to config's rent_sponsor upon compression. - pub fn new_decompressed() -> Result { - Ok(Self { - last_claimed_slot: Clock::get()?.slot, + pub fn new_decompressed(current_slot: u64) -> Self { + Self { + last_claimed_slot: current_slot, lamports_per_write: 0, config_version: 0, state: CompressionState::Decompressed, _padding: 0, rent_config: RentConfig::default(), - }) + } } - /// Update last_claimed_slot to the current slot. - pub fn bump_last_claimed_slot(&mut self) -> Result<(), crate::ProgramError> { - self.last_claimed_slot = Clock::get()?.slot; - Ok(()) + /// Update last_claimed_slot to the given slot. + pub fn bump_last_claimed_slot(&mut self, current_slot: u64) { + self.last_claimed_slot = current_slot; } /// Explicitly set last_claimed_slot. @@ -310,13 +305,12 @@ impl CompressionInfo { account_info: &AccountInfo<'a>, payer_info: &AccountInfo<'a>, system_program_info: &AccountInfo<'a>, - ) -> Result<(), crate::ProgramError> { - use solana_clock::Clock; + ) -> Result<(), ProgramError> { use solana_sysvar::{rent::Rent, Sysvar}; let bytes = account_info.data_len() as u64; let current_lamports = account_info.lamports(); - let current_slot = Clock::get()?.slot; + let current_slot = solana_sysvar::clock::Clock::get()?.slot; let rent_exemption_lamports = Rent::get()?.minimum_balance(bytes as usize); let top_up = self.calculate_top_up_lamports( @@ -328,8 +322,6 @@ impl CompressionInfo { if top_up > 0 { // Use System Program CPI to transfer lamports - // This is required because the payer account is owned by the System Program, - // not by the calling program transfer_lamports_cpi(payer_info, account_info, system_program_info, top_up)?; } @@ -385,9 +377,9 @@ where A: HasCompressionInfo, { use light_compressible::rent::{AccountRentState, SLOTS_PER_EPOCH}; - use solana_sysvar::rent::Rent; + use solana_sysvar::{rent::Rent, Sysvar}; - let current_slot = Clock::get()?.slot; + let current_slot = solana_sysvar::clock::Clock::get()?.slot; let bytes = account_info.data_len() as u64; let current_lamports = account_info.lamports(); let rent_exemption_lamports = Rent::get() @@ -444,18 +436,16 @@ where /// Transfer lamports from one account to another using System Program CPI. /// This is required when transferring from accounts owned by the System Program. -/// -/// # Arguments -/// * `from` - Source account (owned by System Program) -/// * `to` - Destination account -/// * `system_program` - System Program account -/// * `lamports` - Amount of lamports to transfer fn transfer_lamports_cpi<'a>( from: &AccountInfo<'a>, to: &AccountInfo<'a>, system_program: &AccountInfo<'a>, lamports: u64, ) -> Result<(), ProgramError> { + use solana_cpi::invoke; + use solana_instruction::{AccountMeta, Instruction}; + use solana_pubkey::Pubkey; + // System Program Transfer instruction discriminator: 2 (u32 little-endian) let mut instruction_data = vec![2, 0, 0, 0]; instruction_data.extend_from_slice(&lamports.to_le_bytes()); diff --git a/sdk-libs/sdk/src/interface/account/light_account.rs b/sdk-libs/sdk-interface/src/account/light_account.rs similarity index 89% rename from sdk-libs/sdk/src/interface/account/light_account.rs rename to sdk-libs/sdk-interface/src/account/light_account.rs index de61d89dbd..923c11878f 100644 --- a/sdk-libs/sdk/src/interface/account/light_account.rs +++ b/sdk-libs/sdk-interface/src/account/light_account.rs @@ -1,16 +1,13 @@ //! LightAccount trait definition for compressible account data structs. -//! -//! This trait does NOT yet exist in the SDK - it is defined locally for this test -//! to demonstrate manual implementation without macros. use anchor_lang::prelude::*; use light_hasher::DataHasher; use solana_program_error::ProgramError; use crate::{ - compressible::CompressionInfo, + account::compression_info::CompressionInfo, instruction::PackedAccounts, - interface::LightConfig, + program::config::LightConfig, light_account_checks::{packed_accounts::ProgramPackedAccounts, AccountInfoTrait}, }; diff --git a/sdk-libs/sdk/src/interface/account/mod.rs b/sdk-libs/sdk-interface/src/account/mod.rs similarity index 100% rename from sdk-libs/sdk/src/interface/account/mod.rs rename to sdk-libs/sdk-interface/src/account/mod.rs diff --git a/sdk-libs/sdk/src/interface/account/pack.rs b/sdk-libs/sdk-interface/src/account/pack.rs similarity index 100% rename from sdk-libs/sdk/src/interface/account/pack.rs rename to sdk-libs/sdk-interface/src/account/pack.rs diff --git a/sdk-libs/sdk/src/interface/account/pda_seeds.rs b/sdk-libs/sdk-interface/src/account/pda_seeds.rs similarity index 83% rename from sdk-libs/sdk/src/interface/account/pda_seeds.rs rename to sdk-libs/sdk-interface/src/account/pda_seeds.rs index d553071e77..fc088eb498 100644 --- a/sdk-libs/sdk/src/interface/account/pda_seeds.rs +++ b/sdk-libs/sdk-interface/src/account/pda_seeds.rs @@ -1,19 +1,15 @@ // --- cpi-context-gated traits (from decompress_runtime.rs) --- -#[cfg(feature = "cpi-context")] use solana_program_error::ProgramError; -#[cfg(feature = "cpi-context")] use solana_pubkey::Pubkey; /// Trait for account variants that can be checked for token or PDA type. -#[cfg(feature = "cpi-context")] pub trait HasTokenVariant { /// Returns true if this variant represents a token account (PackedTokenData). fn is_packed_token(&self) -> bool; } /// Trait for PDA types that can derive seeds with full account context access. -#[cfg(feature = "cpi-context")] pub trait PdaSeedDerivation { fn derive_pda_seeds_with_accounts( &self, diff --git a/sdk-libs/sdk/src/interface/account/token_seeds.rs b/sdk-libs/sdk-interface/src/account/token_seeds.rs similarity index 97% rename from sdk-libs/sdk/src/interface/account/token_seeds.rs rename to sdk-libs/sdk-interface/src/account/token_seeds.rs index 18d0c0a178..c6cb257279 100644 --- a/sdk-libs/sdk/src/interface/account/token_seeds.rs +++ b/sdk-libs/sdk-interface/src/account/token_seeds.rs @@ -17,12 +17,13 @@ use solana_pubkey::Pubkey; use super::pack::Unpack; // Pack trait and PackedAccounts only available off-chain (client-side packing) #[cfg(not(target_os = "solana"))] -use crate::{instruction::PackedAccounts, interface::Pack}; +use crate::{account::pack::Pack, instruction::PackedAccounts}; use crate::{ - interface::{ - AccountType, LightAccountVariantTrait, PackedLightAccountVariantTrait, PackedTokenSeeds, + program::variant::{ + LightAccountVariantTrait, PackedLightAccountVariantTrait, PackedTokenSeeds, UnpackedTokenSeeds, }, + account::light_account::AccountType, AnchorDeserialize, AnchorSerialize, }; diff --git a/sdk-libs/sdk/src/interface/accounts/create_pda.rs b/sdk-libs/sdk-interface/src/accounts/create_pda.rs similarity index 93% rename from sdk-libs/sdk/src/interface/accounts/create_pda.rs rename to sdk-libs/sdk-interface/src/accounts/create_pda.rs index 92ab4e505f..0440e6c094 100644 --- a/sdk-libs/sdk/src/interface/accounts/create_pda.rs +++ b/sdk-libs/sdk-interface/src/accounts/create_pda.rs @@ -3,7 +3,7 @@ use solana_cpi::invoke_signed; use solana_pubkey::Pubkey; use solana_system_interface::instruction as system_instruction; -use crate::error::LightSdkError; +use crate::error::LightPdaError; /// Cold path: Account already has lamports (e.g., attacker donation). /// Uses Assign + Allocate + Transfer instead of CreateAccount which would fail. @@ -18,7 +18,7 @@ fn create_pda_account_with_lamports<'info>( owner: &Pubkey, seeds: &[&[u8]], system_program: &AccountInfo<'info>, -) -> Result<(), LightSdkError> { +) -> Result<(), LightPdaError> { let current_lamports = solana_account.lamports(); // Assign owner @@ -28,7 +28,7 @@ fn create_pda_account_with_lamports<'info>( &[solana_account.clone(), system_program.clone()], &[seeds], ) - .map_err(LightSdkError::ProgramError)?; + .map_err(LightPdaError::ProgramError)?; // Allocate space let allocate_ix = system_instruction::allocate(solana_account.key, space); @@ -37,7 +37,7 @@ fn create_pda_account_with_lamports<'info>( &[solana_account.clone(), system_program.clone()], &[seeds], ) - .map_err(LightSdkError::ProgramError)?; + .map_err(LightPdaError::ProgramError)?; // Transfer remaining lamports for rent-exemption if needed if lamports > current_lamports { @@ -56,7 +56,7 @@ fn create_pda_account_with_lamports<'info>( ], &[rent_sponsor_seeds], ) - .map_err(LightSdkError::ProgramError)?; + .map_err(LightPdaError::ProgramError)?; } Ok(()) @@ -88,7 +88,7 @@ pub fn create_pda_account<'info>( owner: &Pubkey, seeds: &[&[u8]], system_program: &AccountInfo<'info>, -) -> Result<(), LightSdkError> { +) -> Result<(), LightPdaError> { // Cold path: account already has lamports (e.g., attacker donation) if solana_account.lamports() > 0 { return create_pda_account_with_lamports( @@ -122,5 +122,5 @@ pub fn create_pda_account<'info>( ], &[rent_sponsor_seeds, seeds], ) - .map_err(LightSdkError::ProgramError) + .map_err(LightPdaError::ProgramError) } diff --git a/sdk-libs/sdk/src/interface/accounts/finalize.rs b/sdk-libs/sdk-interface/src/accounts/finalize.rs similarity index 61% rename from sdk-libs/sdk/src/interface/accounts/finalize.rs rename to sdk-libs/sdk-interface/src/accounts/finalize.rs index b16767f7cc..685d6ba8ae 100644 --- a/sdk-libs/sdk/src/interface/accounts/finalize.rs +++ b/sdk-libs/sdk-interface/src/accounts/finalize.rs @@ -25,22 +25,11 @@ use solana_account_info::AccountInfo; /// * `P` - The instruction params type (from `#[instruction(params: P)]`) pub trait LightPreInit<'info, P> { /// Execute pre-initialization operations (mint creation). - /// - /// This writes mint creation operations to CPI context. The actual execution - /// with proof happens in `light_finalize()`. - /// - /// # Arguments - /// * `remaining_accounts` - The remaining accounts from the context, used for CPI - /// * `params` - The instruction parameters containing compression data - /// - /// # Returns - /// `true` if mints were written to CPI context and `light_finalize` should execute - /// with CPI context. `false` if no mints exist and normal flow should proceed. fn light_pre_init( &mut self, remaining_accounts: &[AccountInfo<'info>], params: &P, - ) -> Result; + ) -> Result; } /// Trait for finalizing compression operations on accounts. @@ -48,24 +37,12 @@ pub trait LightPreInit<'info, P> { /// # Type Parameters /// * `'info` - The account info lifetime /// * `P` - The instruction params type (from `#[instruction(params: P)]`) -/// pub trait LightFinalize<'info, P> { /// Execute compression finalization. - /// - /// This method is called at the end of an instruction to batch and execute - /// all compression CPIs for accounts marked with `#[compressible(...)]`. - /// - /// # Arguments - /// * `remaining_accounts` - The remaining accounts from the context, used for CPI - /// * `params` - The instruction parameters containing compression data - /// * `has_pre_init` - Whether `light_pre_init` was called and wrote to CPI context - /// - /// # Errors - /// Returns an error if the compression CPI fails. fn light_finalize( &mut self, remaining_accounts: &[AccountInfo<'info>], params: &P, has_pre_init: bool, - ) -> Result<(), crate::error::LightSdkError>; + ) -> Result<(), crate::error::LightPdaError>; } diff --git a/sdk-libs/sdk/src/interface/accounts/init_compressed_account.rs b/sdk-libs/sdk-interface/src/accounts/init_compressed_account.rs similarity index 95% rename from sdk-libs/sdk/src/interface/accounts/init_compressed_account.rs rename to sdk-libs/sdk-interface/src/accounts/init_compressed_account.rs index 11811cba63..c1c4908b8a 100644 --- a/sdk-libs/sdk/src/interface/accounts/init_compressed_account.rs +++ b/sdk-libs/sdk-interface/src/accounts/init_compressed_account.rs @@ -2,20 +2,22 @@ use light_compressed_account::{ address::derive_address, - instruction_data::{data::NewAddressParamsAssignedPacked, with_account_info::OutAccountInfo}, + instruction_data::{ + data::NewAddressParamsAssignedPacked, + with_account_info::{CompressedAccountInfo, OutAccountInfo}, + }, }; use light_compressible::DECOMPRESSED_PDA_DISCRIMINATOR; use light_hasher::{errors::HasherError, sha256::Sha256BE, Hasher}; use light_sdk_types::constants::RENT_SPONSOR_SEED; +use light_sdk_types::instruction::PackedAddressTreeInfo; use solana_account_info::AccountInfo; use solana_program_error::ProgramError; use solana_pubkey::Pubkey; use solana_sysvar::{rent::Rent, Sysvar}; -use crate::{ - compressed_account::CompressedAccountInfo, error::LightSdkError, - instruction::PackedAddressTreeInfo, light_account_checks::checks::check_mut, -}; +use crate::error::LightPdaError; +use light_account_checks::checks::check_mut; /// Prepare a compressed account for a PDA during initialization. /// @@ -148,7 +150,7 @@ pub fn prepare_compressed_account_on_init_checked( new_address_params, account_infos, ) - .map_err(|e| LightSdkError::from(e).into()) + .map_err(|e| LightPdaError::from(e).into()) } /// Reimburse the fee payer for rent paid during PDA initialization. @@ -197,7 +199,7 @@ pub fn reimburse_rent<'info>( expected_rent_sponsor, rent_sponsor.key ); - return Err(LightSdkError::InvalidRentSponsor.into()); + return Err(LightPdaError::InvalidRentSponsor.into()); } // Validate accounts are writable for transfer diff --git a/sdk-libs/sdk/src/interface/accounts/mod.rs b/sdk-libs/sdk-interface/src/accounts/mod.rs similarity index 92% rename from sdk-libs/sdk/src/interface/accounts/mod.rs rename to sdk-libs/sdk-interface/src/accounts/mod.rs index cf105f5726..c9a07a4127 100644 --- a/sdk-libs/sdk/src/interface/accounts/mod.rs +++ b/sdk-libs/sdk-interface/src/accounts/mod.rs @@ -3,7 +3,6 @@ //! This module contains traits and functions for context struct handling, //! validation, and initialization at the accounts struct level. -#[cfg(feature = "v2")] pub mod create_pda; pub mod finalize; pub mod init_compressed_account; diff --git a/sdk-libs/sdk/src/cpi/account.rs b/sdk-libs/sdk-interface/src/cpi/account.rs similarity index 88% rename from sdk-libs/sdk/src/cpi/account.rs rename to sdk-libs/sdk-interface/src/cpi/account.rs index b535cc2b2f..00c425fba8 100644 --- a/sdk-libs/sdk/src/cpi/account.rs +++ b/sdk-libs/sdk-interface/src/cpi/account.rs @@ -1,15 +1,13 @@ -#[cfg(all(feature = "v2", feature = "cpi-context"))] use light_sdk_types::cpi_context_write::CpiContextWriteAccounts; -#[cfg(all(feature = "v2", feature = "cpi-context"))] use crate::cpi::v2::get_account_metas_from_config_cpi_context; -use crate::{ - cpi::v1::{ - lowlevel::{get_account_metas_from_config, CpiInstructionConfig}, - CpiAccounts, - }, - AccountInfo, AccountMeta, ProgramError, +use crate::cpi::v1::{ + lowlevel::{get_account_metas_from_config, CpiInstructionConfig}, + CpiAccounts, }; +use solana_account_info::AccountInfo; +use solana_instruction::AccountMeta; +use solana_program_error::ProgramError; /// Trait for types that can provide account information for CPI calls pub trait CpiAccountsTrait<'info> { @@ -64,7 +62,6 @@ impl<'info> CpiAccountsTrait<'info> for &[AccountInfo<'info>] { } // Implementation for CpiContextWriteAccounts -#[cfg(all(feature = "v2", feature = "cpi-context"))] impl<'a, 'info> CpiAccountsTrait<'info> for CpiContextWriteAccounts<'a, AccountInfo<'info>> { fn to_account_infos(&self) -> Vec> { vec![ diff --git a/sdk-libs/sdk-interface/src/cpi/instruction.rs b/sdk-libs/sdk-interface/src/cpi/instruction.rs new file mode 100644 index 0000000000..6965da52ff --- /dev/null +++ b/sdk-libs/sdk-interface/src/cpi/instruction.rs @@ -0,0 +1,63 @@ +use light_compressed_account::instruction_data::compressed_proof::ValidityProof; + +/// Trait for Light CPI instruction types. +/// +/// This is the framework-agnostic version that provides CPI builder methods +/// without referencing light-sdk-specific types like `LightAccount`. +/// The `with_light_account` and `with_light_account_poseidon` methods are +/// provided by light-sdk, which depends on this crate. +pub trait LightCpiInstruction: Sized { + /// Creates a new CPI instruction builder with a validity proof. + /// + /// # Arguments + /// * `cpi_signer` - The CPI signer containing program ID and bump seed + /// * `proof` - Validity proof for compressed account operations + fn new_cpi(cpi_signer: crate::cpi::CpiSigner, proof: ValidityProof) -> Self; + + /// Returns the instruction mode (0 for v1, 1 for v2). + fn get_mode(&self) -> u8; + + /// Returns the CPI signer bump seed. + fn get_bump(&self) -> u8; + + /// Writes instruction to CPI context as the first operation in a batch. + /// + /// # Availability + /// Only available with the `cpi-context` feature enabled. + #[must_use = "write_to_cpi_context_first returns a new value"] + fn write_to_cpi_context_first(self) -> Self; + + /// Writes instruction to CPI context as a subsequent operation in a batch. + /// + /// # Availability + /// Only available with the `cpi-context` feature enabled. + #[must_use = "write_to_cpi_context_set returns a new value"] + fn write_to_cpi_context_set(self) -> Self; + + /// Executes all operations accumulated in CPI context. + /// + /// # Availability + /// Only available with the `cpi-context` feature enabled. + #[must_use = "execute_with_cpi_context returns a new value"] + fn execute_with_cpi_context(self) -> Self; + + /// Returns whether this instruction uses CPI context. + /// + /// # Availability + /// Only available with the `cpi-context` feature enabled. + fn get_with_cpi_context(&self) -> bool; + + /// Returns the CPI context configuration. + /// + /// # Availability + /// Only available with the `cpi-context` feature enabled. + fn get_cpi_context( + &self, + ) -> &light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; + + /// Returns whether this instruction has any read-only accounts. + /// + /// # Availability + /// Only available with the `cpi-context` feature enabled. + fn has_read_only_accounts(&self) -> bool; +} diff --git a/sdk-libs/sdk/src/cpi/invoke.rs b/sdk-libs/sdk-interface/src/cpi/invoke.rs similarity index 91% rename from sdk-libs/sdk/src/cpi/invoke.rs rename to sdk-libs/sdk-interface/src/cpi/invoke.rs index 4562d0ad18..768d6ccfed 100644 --- a/sdk-libs/sdk/src/cpi/invoke.rs +++ b/sdk-libs/sdk-interface/src/cpi/invoke.rs @@ -1,30 +1,26 @@ pub use light_compressed_account::LightInstructionData; use light_sdk_types::constants::{CPI_AUTHORITY_PDA_SEED, LIGHT_SYSTEM_PROGRAM_ID}; +use solana_instruction::AccountMeta; +use solana_account_info::AccountInfo; +use solana_instruction::Instruction; +use solana_program_error::ProgramError; -#[cfg(feature = "cpi-context")] -use crate::AccountMeta; use crate::{ cpi::{account::CpiAccountsTrait, instruction::LightCpiInstruction}, - error::LightSdkError, - invoke_signed, AccountInfo, Instruction, ProgramError, + error::LightPdaError, }; +use solana_cpi::invoke_signed; pub trait InvokeLightSystemProgram { fn invoke<'info>(self, accounts: impl CpiAccountsTrait<'info>) -> Result<(), ProgramError>; - - #[cfg(feature = "cpi-context")] fn invoke_write_to_cpi_context_first<'info>( self, accounts: impl CpiAccountsTrait<'info>, ) -> Result<(), ProgramError>; - - #[cfg(feature = "cpi-context")] fn invoke_write_to_cpi_context_set<'info>( self, accounts: impl CpiAccountsTrait<'info>, ) -> Result<(), ProgramError>; - - #[cfg(feature = "cpi-context")] fn invoke_execute_cpi_context<'info>( self, accounts: impl CpiAccountsTrait<'info>, @@ -37,7 +33,6 @@ where T: LightInstructionData + LightCpiInstruction, { fn invoke<'info>(self, accounts: impl CpiAccountsTrait<'info>) -> Result<(), ProgramError> { - #[cfg(feature = "cpi-context")] { // Check if CPI context operations are being attempted use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; @@ -67,7 +62,7 @@ where // Serialize instruction data with discriminator let data = self .data() - .map_err(LightSdkError::from) + .map_err(LightPdaError::from) .map_err(ProgramError::from)?; // Get account infos and metas @@ -81,8 +76,6 @@ where }; invoke_light_system_program(&account_infos, instruction, self.get_bump()) } - - #[cfg(feature = "cpi-context")] fn invoke_write_to_cpi_context_first<'info>( self, accounts: impl CpiAccountsTrait<'info>, @@ -90,8 +83,6 @@ where let instruction_data = self.write_to_cpi_context_first(); inner_invoke_write_to_cpi_context_typed(instruction_data, accounts) } - - #[cfg(feature = "cpi-context")] fn invoke_write_to_cpi_context_set<'info>( self, accounts: impl CpiAccountsTrait<'info>, @@ -99,8 +90,6 @@ where let instruction_data = self.write_to_cpi_context_set(); inner_invoke_write_to_cpi_context_typed(instruction_data, accounts) } - - #[cfg(feature = "cpi-context")] fn invoke_execute_cpi_context<'info>( self, accounts: impl CpiAccountsTrait<'info>, @@ -109,7 +98,7 @@ where // Serialize instruction data with discriminator let data = instruction_data .data() - .map_err(LightSdkError::from) + .map_err(LightPdaError::from) .map_err(ProgramError::from)?; // Get account infos and metas @@ -126,7 +115,6 @@ where } // Generic inner helper for write_to_cpi_context operations -#[cfg(feature = "cpi-context")] #[inline(always)] fn inner_invoke_write_to_cpi_context_typed<'info, T>( instruction_data: T, @@ -140,13 +128,13 @@ where solana_msg::msg!( "Read-only accounts are not supported in write_to_cpi_context operations. Use invoke_execute_cpi_context() instead." ); - return Err(LightSdkError::ReadOnlyAccountsNotSupportedInCpiContext.into()); + return Err(LightPdaError::ReadOnlyAccountsNotSupportedInCpiContext.into()); } // Serialize instruction data with discriminator let data = instruction_data .data() - .map_err(LightSdkError::from) + .map_err(LightPdaError::from) .map_err(ProgramError::from)?; // Get account infos and metas diff --git a/sdk-libs/sdk-interface/src/cpi/mod.rs b/sdk-libs/sdk-interface/src/cpi/mod.rs new file mode 100644 index 0000000000..ff7301083e --- /dev/null +++ b/sdk-libs/sdk-interface/src/cpi/mod.rs @@ -0,0 +1,18 @@ +//! +//! +//! To create, update, or close compressed accounts, +//! programs need to invoke the light system program via cross program invocation (cpi). + +mod account; +mod instruction; +pub mod invoke; + +pub mod v1; +pub mod v2; + +pub use account::*; +pub use instruction::*; +pub use invoke::InvokeLightSystemProgram; +pub use light_compressed_account::instruction_data::traits::LightInstructionData; +/// Contains program id, derived cpi signer, and bump, +pub use light_sdk_types::{cpi_accounts::CpiAccountsConfig, CpiSigner}; diff --git a/sdk-libs/sdk/src/cpi/v1/accounts.rs b/sdk-libs/sdk-interface/src/cpi/v1/accounts.rs similarity index 96% rename from sdk-libs/sdk/src/cpi/v1/accounts.rs rename to sdk-libs/sdk-interface/src/cpi/v1/accounts.rs index e0d9dd8503..a845fafb76 100644 --- a/sdk-libs/sdk/src/cpi/v1/accounts.rs +++ b/sdk-libs/sdk-interface/src/cpi/v1/accounts.rs @@ -5,10 +5,10 @@ use light_sdk_types::{ REGISTERED_PROGRAM_PDA, }; -use crate::{ - error::{LightSdkError, Result}, - AccountInfo, AccountMeta, Pubkey, -}; +use crate::error::{LightPdaError, Result}; +use solana_account_info::AccountInfo; +use solana_instruction::AccountMeta; +use solana_pubkey::Pubkey; #[derive(Debug)] pub struct CpiInstructionConfig<'a, 'info> { @@ -137,7 +137,7 @@ pub fn get_account_metas_from_config(config: CpiInstructionConfig<'_, '_>) -> Ve } impl<'a, 'info> TryFrom<&'a CpiAccounts<'a, 'info>> for CpiInstructionConfig<'a, 'info> { - type Error = LightSdkError; + type Error = LightPdaError; fn try_from(cpi_accounts: &'a CpiAccounts<'a, 'info>) -> Result { Ok(CpiInstructionConfig { diff --git a/sdk-libs/sdk-interface/src/cpi/v1/invoke.rs b/sdk-libs/sdk-interface/src/cpi/v1/invoke.rs new file mode 100644 index 0000000000..b90c1c8db3 --- /dev/null +++ b/sdk-libs/sdk-interface/src/cpi/v1/invoke.rs @@ -0,0 +1,186 @@ +use light_compressed_account::instruction_data::{ + compressed_proof::ValidityProof, invoke_cpi::InstructionDataInvokeCpi, +}; + +use crate::{ + cpi::{instruction::LightCpiInstruction, invoke::LightInstructionData, CpiSigner}, + AnchorSerialize, +}; + +/// Light system program CPI instruction data builder. +/// +/// Use this builder to construct instructions for compressed account operations: +/// creating, updating, closing accounts, and compressing/decompressing SOL. +/// +/// # Builder Methods +/// +/// ## Common Methods +/// +/// - [`with_new_addresses()`](Self::with_new_addresses) - Create new compressed account addresses +/// - [`compress_lamports()`](Self::compress_lamports) - Compress SOL into compressed accounts +/// - [`decompress_lamports()`](Self::decompress_lamports) - Decompress SOL from compressed accounts +/// +/// **Note**: An instruction can either compress **or** decompress lamports, not both. +/// +/// ## Advanced Methods +/// +/// For fine-grained control: +/// +/// - [`with_input_compressed_accounts_with_merkle_context()`](Self::with_input_compressed_accounts_with_merkle_context) - Manually specify input accounts +/// - [`with_output_compressed_accounts()`](Self::with_output_compressed_accounts) - Manually specify output accounts +#[derive(Clone)] +pub struct LightSystemProgramCpi { + cpi_signer: CpiSigner, + instruction_data: InstructionDataInvokeCpi, +} + +impl LightSystemProgramCpi { + #[must_use = "with_new_addresses returns a new value"] + pub fn with_new_addresses( + mut self, + new_address_params: &[light_compressed_account::instruction_data::data::NewAddressParamsPacked], + ) -> Self { + self.instruction_data = self.instruction_data.with_new_addresses(new_address_params); + self + } + + #[must_use = "with_input_compressed_accounts_with_merkle_context returns a new value"] + pub fn with_input_compressed_accounts_with_merkle_context( + mut self, + input_compressed_accounts_with_merkle_context: &[light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext], + ) -> Self { + self.instruction_data = self + .instruction_data + .with_input_compressed_accounts_with_merkle_context( + input_compressed_accounts_with_merkle_context, + ); + self + } + + #[must_use = "with_output_compressed_accounts returns a new value"] + pub fn with_output_compressed_accounts( + mut self, + output_compressed_accounts: &[light_compressed_account::instruction_data::data::OutputCompressedAccountWithPackedContext], + ) -> Self { + self.instruction_data = self + .instruction_data + .with_output_compressed_accounts(output_compressed_accounts); + self + } + + #[must_use = "compress_lamports returns a new value"] + pub fn compress_lamports(mut self, lamports: u64) -> Self { + self.instruction_data = self.instruction_data.compress_lamports(lamports); + self + } + + #[must_use = "decompress_lamports returns a new value"] + pub fn decompress_lamports(mut self, lamports: u64) -> Self { + self.instruction_data = self.instruction_data.decompress_lamports(lamports); + self + } + #[must_use = "write_to_cpi_context_set returns a new value"] + pub fn write_to_cpi_context_set(mut self) -> Self { + self.instruction_data = self.instruction_data.write_to_cpi_context_set(); + self + } + #[must_use = "write_to_cpi_context_first returns a new value"] + pub fn write_to_cpi_context_first(mut self) -> Self { + self.instruction_data = self.instruction_data.write_to_cpi_context_first(); + self + } + #[must_use = "with_cpi_context returns a new value"] + pub fn with_cpi_context( + mut self, + cpi_context: light_compressed_account::instruction_data::cpi_context::CompressedCpiContext, + ) -> Self { + self.instruction_data = self.instruction_data.with_cpi_context(cpi_context); + self + } + + /// Returns a reference to the inner instruction data. + pub fn instruction_data(&self) -> &InstructionDataInvokeCpi { + &self.instruction_data + } + + /// Returns a mutable reference to the inner instruction data. + pub fn instruction_data_mut(&mut self) -> &mut InstructionDataInvokeCpi { + &mut self.instruction_data + } + + /// Returns the CPI signer. + pub fn cpi_signer(&self) -> &CpiSigner { + &self.cpi_signer + } +} + +impl LightCpiInstruction for LightSystemProgramCpi { + fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self { + Self { + cpi_signer, + instruction_data: InstructionDataInvokeCpi::new(proof.into()), + } + } + + fn get_mode(&self) -> u8 { + 0 // V1 uses regular mode by default + } + + fn get_bump(&self) -> u8 { + self.cpi_signer.bump + } + fn write_to_cpi_context_first(mut self) -> Self { + self.instruction_data = self.instruction_data.write_to_cpi_context_first(); + self + } + fn write_to_cpi_context_set(mut self) -> Self { + self.instruction_data = self.instruction_data.write_to_cpi_context_set(); + self + } + fn execute_with_cpi_context(self) -> Self { + // V1 doesn't have a direct execute context, just return self + // The execute happens through the invoke call + self + } + fn get_with_cpi_context(&self) -> bool { + self.instruction_data.cpi_context.is_some() + } + fn get_cpi_context( + &self, + ) -> &light_compressed_account::instruction_data::cpi_context::CompressedCpiContext { + use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; + // Use a static default with all fields set to false/0 + static DEFAULT: CompressedCpiContext = CompressedCpiContext { + set_context: false, + first_set_context: false, + cpi_context_account_index: 0, + }; + self.instruction_data + .cpi_context + .as_ref() + .unwrap_or(&DEFAULT) + } + fn has_read_only_accounts(&self) -> bool { + // V1 doesn't support read-only accounts + false + } +} + +// Manual BorshSerialize implementation that only serializes instruction_data +impl AnchorSerialize for LightSystemProgramCpi { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + self.instruction_data.serialize(writer) + } +} + +impl light_compressed_account::InstructionDiscriminator for LightSystemProgramCpi { + fn discriminator(&self) -> &'static [u8] { + self.instruction_data.discriminator() + } +} + +impl LightInstructionData for LightSystemProgramCpi { + fn data(&self) -> Result, light_compressed_account::CompressedAccountError> { + self.instruction_data.data() + } +} diff --git a/sdk-libs/sdk-interface/src/cpi/v1/mod.rs b/sdk-libs/sdk-interface/src/cpi/v1/mod.rs new file mode 100644 index 0000000000..b9c66a8cd4 --- /dev/null +++ b/sdk-libs/sdk-interface/src/cpi/v1/mod.rs @@ -0,0 +1,32 @@ +//! V1 CPI for Light system program. +//! +//! # Main Types +//! +//! - [`LightSystemProgramCpi`] - CPI instruction data builder +//! - [`CpiAccounts`] - CPI accounts struct +//! +//! +//! # Advanced Usage +//! +//! For maximum flexible light system program CPIs, see the [`lowlevel`] module or use `light-compressed-account` directly. + +mod accounts; +mod invoke; + +pub use accounts::CpiAccounts; +pub use invoke::LightSystemProgramCpi; + +/// Low-level types and functions for flexible Light system program CPIs. +/// +/// # Main Types +/// +/// For most use cases, you only need: +/// - [`LightSystemProgramCpi`] - Main CPI interface +/// - [`CpiAccounts`] - Account management +/// +/// The remaining types in this module are exported for low-level operations and internal use. +pub mod lowlevel { + pub use super::accounts::{ + get_account_metas_from_config, CpiInstructionConfig, SYSTEM_ACCOUNTS_LEN, + }; +} diff --git a/sdk-libs/sdk/src/cpi/v2/accounts.rs b/sdk-libs/sdk-interface/src/cpi/v2/accounts.rs similarity index 94% rename from sdk-libs/sdk/src/cpi/v2/accounts.rs rename to sdk-libs/sdk-interface/src/cpi/v2/accounts.rs index bce47fe5ea..592878b59b 100644 --- a/sdk-libs/sdk/src/cpi/v2/accounts.rs +++ b/sdk-libs/sdk-interface/src/cpi/v2/accounts.rs @@ -2,7 +2,9 @@ use light_sdk_types::cpi_accounts::v2::{ CompressionCpiAccountIndex, CpiAccounts as GenericCpiAccounts, PROGRAM_ACCOUNTS_LEN, }; -use crate::{error::Result, AccountInfo, AccountMeta}; +use crate::error::Result; +use solana_account_info::AccountInfo; +use solana_instruction::AccountMeta; /// Light system program CPI accounts struct. /// @@ -82,7 +84,7 @@ pub fn to_account_metas(cpi_accounts: &CpiAccounts<'_, '_>) -> Result, ) -> [AccountMeta; 3] { diff --git a/sdk-libs/sdk-interface/src/cpi/v2/invoke.rs b/sdk-libs/sdk-interface/src/cpi/v2/invoke.rs new file mode 100644 index 0000000000..ac73d3dc89 --- /dev/null +++ b/sdk-libs/sdk-interface/src/cpi/v2/invoke.rs @@ -0,0 +1,100 @@ +use light_compressed_account::instruction_data::{ + compressed_proof::ValidityProof, with_account_info::InstructionDataInvokeCpiWithAccountInfo, +}; +use light_sdk_types::CpiSigner; +use super::lowlevel::CompressedCpiContext; +use super::lowlevel::{to_account_metas, InstructionDataInvokeCpiWithReadOnly}; +use crate::cpi::{account::CpiAccountsTrait, instruction::LightCpiInstruction, v2::CpiAccounts}; +use solana_account_info::AccountInfo; +use solana_instruction::AccountMeta; +use solana_program_error::ProgramError; + +impl<'info> CpiAccountsTrait<'info> for CpiAccounts<'_, 'info> { + fn to_account_infos(&self) -> Vec> { + self.to_account_infos() + } + + fn to_account_metas(&self) -> Result, ProgramError> { + to_account_metas(self).map_err(ProgramError::from) + } + + fn get_mode(&self) -> Option { + Some(1) // v2 mode + } +} + +impl LightCpiInstruction for InstructionDataInvokeCpiWithReadOnly { + fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self { + Self { + bump: cpi_signer.bump, + invoking_program_id: cpi_signer.program_id.into(), + proof: proof.into(), + mode: 1, + ..Default::default() + } + } + fn write_to_cpi_context_first(self) -> Self { + self.write_to_cpi_context_first() + } + fn write_to_cpi_context_set(self) -> Self { + self.write_to_cpi_context_set() + } + fn execute_with_cpi_context(self) -> Self { + self.execute_with_cpi_context() + } + + fn get_mode(&self) -> u8 { + self.mode + } + fn get_with_cpi_context(&self) -> bool { + self.with_cpi_context + } + fn get_cpi_context(&self) -> &CompressedCpiContext { + &self.cpi_context + } + + fn get_bump(&self) -> u8 { + self.bump + } + fn has_read_only_accounts(&self) -> bool { + !self.read_only_accounts.is_empty() + } +} + +impl LightCpiInstruction for InstructionDataInvokeCpiWithAccountInfo { + fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self { + Self { + bump: cpi_signer.bump, + invoking_program_id: cpi_signer.program_id.into(), + proof: proof.into(), + mode: 1, + ..Default::default() + } + } + fn write_to_cpi_context_first(self) -> Self { + self.write_to_cpi_context_first() + } + fn write_to_cpi_context_set(self) -> Self { + self.write_to_cpi_context_set() + } + fn execute_with_cpi_context(self) -> Self { + self.execute_with_cpi_context() + } + + fn get_mode(&self) -> u8 { + self.mode + } + fn get_with_cpi_context(&self) -> bool { + self.with_cpi_context + } + fn get_cpi_context(&self) -> &CompressedCpiContext { + &self.cpi_context + } + + fn get_bump(&self) -> u8 { + self.bump + } + fn has_read_only_accounts(&self) -> bool { + !self.read_only_accounts.is_empty() + } +} diff --git a/sdk-libs/sdk-interface/src/cpi/v2/mod.rs b/sdk-libs/sdk-interface/src/cpi/v2/mod.rs new file mode 100644 index 0000000000..3bc79d43bf --- /dev/null +++ b/sdk-libs/sdk-interface/src/cpi/v2/mod.rs @@ -0,0 +1,70 @@ +//! V2 CPI for Light system program - optimized for compressed PDAs. +//! +//! # Main Types +//! +//! - [`LightSystemProgramCpi`] - CPI instruction data builder +//! - [`CpiAccounts`] - CPI accounts struct +//! +//! +//! # Advanced Usage +//! +//! For maximum flexible light system program CPIs, see the [`lowlevel`] module or use `light-compressed-account` directly. + +mod accounts; +mod accounts_cpi_context; +mod invoke; + +pub use accounts::CpiAccounts; +pub use accounts_cpi_context::*; +/// Light system program CPI instruction data builder. +/// +/// Use this builder to construct instructions for compressed account operations: +/// creating, updating, closing accounts, and compressing/decompressing SOL. +/// +/// # Builder Methods +/// +/// ## Common Methods +/// +/// - [`with_new_addresses()`](crate::cpi::v2::LightSystemProgramCpi::with_new_addresses) - Create new compressed account addresses. +/// - [`with_read_only_addresses()`](crate::cpi::v2::LightSystemProgramCpi::with_read_only_addresses) - Validate that addresses don't exist without creating them. +/// - [`with_read_only_accounts()`](crate::cpi::v2::LightSystemProgramCpi::with_read_only_accounts) - Validate that compressed account state exists without updating it. +/// - [`compress_lamports()`](crate::cpi::v2::LightSystemProgramCpi::compress_lamports) - Compress SOL into compressed accounts. +/// - [`decompress_lamports()`](crate::cpi::v2::LightSystemProgramCpi::decompress_lamports) - Decompress SOL from compressed accounts. +/// +/// **Note**: An instruction can either compress **or** decompress lamports, not both. +/// ## Advanced Methods +/// +/// For fine-grained control: +/// +/// - [`with_account_infos()`](crate::cpi::v2::LightSystemProgramCpi::with_account_infos) - Manually specify CompressedAccountInfos. +pub use light_compressed_account::instruction_data::with_account_info::InstructionDataInvokeCpiWithAccountInfo as LightSystemProgramCpi; + +/// Low-level types and functions for flexible Light system program CPIs. +/// +/// # Main Types +/// +/// For most use cases, you only need: +/// - [`LightSystemProgramCpi`] - Main CPI interface +/// - [`CpiAccounts`] - Account management +/// +/// The remaining types in this module are exported for low-level operations and internal use. +pub mod lowlevel { + + /// CPI context for batched compressed account operations. + pub use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; + /// Account information for compressed accounts in CPI operations. + pub use light_compressed_account::instruction_data::with_account_info::CompressedAccountInfo; + /// Input account information for compressed accounts. + pub use light_compressed_account::instruction_data::with_account_info::InAccountInfo; + /// Output account information for compressed accounts. + pub use light_compressed_account::instruction_data::with_account_info::OutAccountInfo; + /// Input compressed account for read-only operations. + pub use light_compressed_account::instruction_data::with_readonly::InAccount; + /// V2 CPI instruction data for read-only compressed account operations. + /// + /// Provides more flexibility for complex operations such as changing the compressed account owner. + /// Most users should use [`crate::cpi::v2::LightSystemProgramCpi`] instead. + pub use light_compressed_account::instruction_data::with_readonly::InstructionDataInvokeCpiWithReadOnly; + + pub use crate::cpi::v2::accounts::to_account_metas; +} diff --git a/sdk-libs/sdk-interface/src/error.rs b/sdk-libs/sdk-interface/src/error.rs new file mode 100644 index 0000000000..4e694789bc --- /dev/null +++ b/sdk-libs/sdk-interface/src/error.rs @@ -0,0 +1,87 @@ +use light_account_checks::error::AccountError; +use light_compressed_account::CompressedAccountError; +use light_hasher::HasherError; +use light_sdk_types::error::LightSdkTypesError; +use thiserror::Error; + +#[derive(Debug, Error, PartialEq)] +pub enum LightPdaError { + #[error("Constraint violation")] + ConstraintViolation, + #[error("Borsh error.")] + Borsh, + #[error("Account check error: {0}")] + AccountCheck(#[from] AccountError), + #[error("Hasher error: {0}")] + Hasher(#[from] HasherError), + #[error("Missing compression_info field")] + MissingCompressionInfo, + #[error("Rent sponsor account does not match the expected PDA from config")] + InvalidRentSponsor, + #[cfg(feature = "solana")] + #[error("Program error: {0}")] + ProgramError(#[from] solana_program_error::ProgramError), + #[error("Borsh IO error: {0}")] + BorshIo(String), + #[error("CPI accounts index out of bounds: {0}")] + CpiAccountsIndexOutOfBounds(usize), + #[error("Read-only accounts are not supported in write_to_cpi_context operations")] + ReadOnlyAccountsNotSupportedInCpiContext, + #[error("Compressed account error: {0}")] + CompressedAccountError(#[from] CompressedAccountError), +} + +pub type Result = core::result::Result; + +#[cfg(feature = "solana")] +impl From for solana_program_error::ProgramError { + fn from(e: LightPdaError) -> Self { + solana_program_error::ProgramError::Custom(u32::from(e)) + } +} + +impl From for LightPdaError { + fn from(e: LightSdkTypesError) -> Self { + match e { + LightSdkTypesError::CpiAccountsIndexOutOfBounds(index) => { + LightPdaError::CpiAccountsIndexOutOfBounds(index) + } + LightSdkTypesError::AccountError(e) => LightPdaError::AccountCheck(e), + LightSdkTypesError::Hasher(e) => LightPdaError::Hasher(e), + _ => LightPdaError::ConstraintViolation, + } + } +} + +impl From for u32 { + fn from(e: LightPdaError) -> Self { + match e { + LightPdaError::ConstraintViolation => 17001, + LightPdaError::Borsh => 17002, + LightPdaError::AccountCheck(e) => e.into(), + LightPdaError::Hasher(e) => e.into(), + LightPdaError::MissingCompressionInfo => 17003, + LightPdaError::InvalidRentSponsor => 17004, + #[cfg(feature = "solana")] + LightPdaError::ProgramError(e) => u64::from(e) as u32, + LightPdaError::BorshIo(_) => 17005, + LightPdaError::CpiAccountsIndexOutOfBounds(_) => 17006, + LightPdaError::ReadOnlyAccountsNotSupportedInCpiContext => 17007, + LightPdaError::CompressedAccountError(e) => e.into(), + } + } +} + +#[cfg(feature = "solana")] +impl From for LightPdaError { + fn from(_e: core::cell::BorrowError) -> Self { + LightPdaError::AccountCheck(AccountError::BorrowAccountDataFailed) + } +} + +#[cfg(feature = "solana")] +impl From for LightPdaError { + fn from(_e: core::cell::BorrowMutError) -> Self { + LightPdaError::AccountCheck(AccountError::BorrowAccountDataFailed) + } +} diff --git a/sdk-libs/sdk-interface/src/instruction/mod.rs b/sdk-libs/sdk-interface/src/instruction/mod.rs new file mode 100644 index 0000000000..f23027164e --- /dev/null +++ b/sdk-libs/sdk-interface/src/instruction/mod.rs @@ -0,0 +1,34 @@ +// Only available off-chain (client-side) - contains sorting code that exceeds BPF stack limits +#[cfg(not(target_os = "solana"))] +mod pack_accounts; + +// Stub type for on-chain compilation - allows trait signatures to compile +// The actual pack methods are never called on-chain +#[cfg(target_os = "solana")] +mod pack_accounts_stub { + use solana_pubkey::Pubkey; + + /// Stub type for on-chain compilation. The actual implementation with sorting + /// is only available off-chain. This allows trait signatures that reference + /// PackedAccounts to compile on Solana. + pub struct PackedAccounts { + _phantom: core::marker::PhantomData<()>, + } + + impl PackedAccounts { + pub fn insert_or_get(&mut self, _pubkey: Pubkey) -> u8 { + panic!("PackedAccounts::insert_or_get is not available on-chain") + } + + pub fn insert_or_get_read_only(&mut self, _pubkey: Pubkey) -> u8 { + panic!("PackedAccounts::insert_or_get_read_only is not available on-chain") + } + } +} + +/// Re-exports from light-sdk-types instruction types. +pub use light_sdk_types::instruction::*; +#[cfg(not(target_os = "solana"))] +pub use pack_accounts::*; +#[cfg(target_os = "solana")] +pub use pack_accounts_stub::PackedAccounts; diff --git a/sdk-libs/sdk-interface/src/instruction/pack_accounts.rs b/sdk-libs/sdk-interface/src/instruction/pack_accounts.rs new file mode 100644 index 0000000000..3564fc4f71 --- /dev/null +++ b/sdk-libs/sdk-interface/src/instruction/pack_accounts.rs @@ -0,0 +1,179 @@ +//! Utilities for packing accounts into instruction data. +//! +//! [`PackedAccounts`] is a builder for efficiently organizing accounts into the three categories +//! required for compressed account instructions: +//! 1. **Pre-accounts** - Custom accounts needed before system accounts +//! 2. **System accounts** - Static light system program accounts +//! 3. **Packed accounts** - Dynamically packed accounts (Merkle trees, address trees, queues) with automatic deduplication + +use std::collections::HashMap; + +use solana_instruction::AccountMeta; +use solana_program_error::ProgramError; +use solana_pubkey::Pubkey; + +/// Builder to collect accounts for compressed account instructions. +/// +/// Manages three categories of accounts: +/// - **Pre-accounts**: Signers and other custom accounts that come before system accounts. +/// - **System accounts**: Light system program accounts (authority, trees, queues). +/// - **Packed accounts**: Dynamically tracked deduplicated accounts. +#[derive(Default, Debug)] +pub struct PackedAccounts { + /// Accounts that must come before system accounts (e.g., signers, fee payer). + pub pre_accounts: Vec, + /// Light system program accounts (authority, programs, trees, queues). + system_accounts: Vec, + /// Next available index for packed accounts. + next_index: u8, + /// Map of pubkey to (index, AccountMeta) for deduplication and index tracking. + map: HashMap, + /// Field to sanity check + system_accounts_set: bool, +} + +impl PackedAccounts { + pub fn system_accounts_set(&self) -> bool { + self.system_accounts_set + } + + pub fn add_pre_accounts_signer(&mut self, pubkey: Pubkey) { + self.pre_accounts.push(AccountMeta { + pubkey, + is_signer: true, + is_writable: false, + }); + } + + pub fn add_pre_accounts_signer_mut(&mut self, pubkey: Pubkey) { + self.pre_accounts.push(AccountMeta { + pubkey, + is_signer: true, + is_writable: true, + }); + } + + pub fn add_pre_accounts_meta(&mut self, account_meta: AccountMeta) { + self.pre_accounts.push(account_meta); + } + + pub fn add_pre_accounts_metas(&mut self, account_metas: &[AccountMeta]) { + self.pre_accounts.extend_from_slice(account_metas); + } + + pub fn add_system_accounts_raw(&mut self, system_accounts: Vec) { + self.system_accounts.extend(system_accounts); + self.system_accounts_set = true; + } + + /// Returns the index of the provided `pubkey` in the collection. + /// + /// If the provided `pubkey` is not a part of the collection, it gets + /// inserted with a `next_index`. + /// + /// If the provided `pubkey` already exists in the collection, its already + /// existing index is returned. + pub fn insert_or_get(&mut self, pubkey: Pubkey) -> u8 { + self.insert_or_get_config(pubkey, false, true) + } + + pub fn insert_or_get_read_only(&mut self, pubkey: Pubkey) -> u8 { + self.insert_or_get_config(pubkey, false, false) + } + + pub fn insert_or_get_config( + &mut self, + pubkey: Pubkey, + is_signer: bool, + is_writable: bool, + ) -> u8 { + match self.map.get_mut(&pubkey) { + Some((index, entry)) => { + if !entry.is_writable { + entry.is_writable = is_writable; + } + if !entry.is_signer { + entry.is_signer = is_signer; + } + *index + } + None => { + let index = self.next_index; + self.next_index += 1; + self.map.insert( + pubkey, + ( + index, + AccountMeta { + pubkey, + is_signer, + is_writable, + }, + ), + ); + index + } + } + } + + fn hash_set_accounts_to_metas(&self) -> Vec { + let mut packed_accounts = self.map.iter().collect::>(); + // hash maps are not sorted so we need to sort manually and collect into a vector again + packed_accounts.sort_by(|a, b| a.1 .0.cmp(&b.1 .0)); + let packed_accounts = packed_accounts + .iter() + .map(|(_, (_, k))| k.clone()) + .collect::>(); + packed_accounts + } + + fn get_offsets(&self) -> (usize, usize) { + let system_accounts_start_offset = self.pre_accounts.len(); + let packed_accounts_start_offset = + system_accounts_start_offset + self.system_accounts.len(); + (system_accounts_start_offset, packed_accounts_start_offset) + } + + /// Converts the collection of accounts to a vector of + /// [`AccountMeta`](solana_instruction::AccountMeta), which can be used + /// as remaining accounts in instructions or CPI calls. + /// + /// # Returns + /// + /// A tuple of `(account_metas, system_accounts_offset, packed_accounts_offset)`: + /// - `account_metas`: All accounts concatenated in order: `[pre_accounts][system_accounts][packed_accounts]` + /// - `system_accounts_offset`: Index where system accounts start (= pre_accounts.len()) + /// - `packed_accounts_offset`: Index where packed accounts start (= pre_accounts.len() + system_accounts.len()) + pub fn to_account_metas(&self) -> (Vec, usize, usize) { + let packed_accounts = self.hash_set_accounts_to_metas(); + let (system_accounts_start_offset, packed_accounts_start_offset) = self.get_offsets(); + ( + [ + self.pre_accounts.clone(), + self.system_accounts.clone(), + packed_accounts, + ] + .concat(), + system_accounts_start_offset, + packed_accounts_start_offset, + ) + } + + pub fn packed_pubkeys(&self) -> Vec { + self.hash_set_accounts_to_metas() + .iter() + .map(|meta| meta.pubkey) + .collect() + } + + pub fn add_custom_system_accounts( + &mut self, + accounts: T, + ) -> Result<(), ProgramError> { + accounts.get_account_metas_vec(self) + } +} + +pub trait AccountMetasVec { + fn get_account_metas_vec(&self, accounts: &mut PackedAccounts) -> Result<(), ProgramError>; +} diff --git a/sdk-libs/sdk/src/interface/mod.rs b/sdk-libs/sdk-interface/src/lib.rs similarity index 77% rename from sdk-libs/sdk/src/interface/mod.rs rename to sdk-libs/sdk-interface/src/lib.rs index 25d379d018..3c65584510 100644 --- a/sdk-libs/sdk/src/interface/mod.rs +++ b/sdk-libs/sdk-interface/src/lib.rs @@ -1,22 +1,46 @@ -//! Light Protocol interface module. +//! Framework-agnostic interface for Light Protocol compressible accounts. //! -//! This module provides the interface for compressible accounts, organized by +//! This crate provides the interface for compressible accounts, organized by //! macro hierarchy: //! //! - `program/` - #[light_program] level (instruction processors) //! - `accounts/` - #[derive(LightAccounts)] level (context structs, validation) //! - `account/` - #[derive(LightAccount)] level (single account operations) +//! +//! # Features +//! - `solana` (default) - Enables Solana runtime support +//! - `pinocchio` - Enables Pinocchio runtime support +//! - `anchor` - Enables Anchor framework support +//! - `v2` - Enables v2 Light system program instructions +//! - `cpi-context` - Enables CPI context operations + +// --- Conditional serialization trait aliases --- +#[cfg(feature = "anchor")] +pub use anchor_lang::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; + +pub mod error; // --- Subdirectory modules --- pub mod account; pub mod accounts; pub mod program; +// --- CPI module (solana-only for now) --- +#[cfg(feature = "solana")] +pub mod cpi; + +// --- Instruction module --- +#[cfg(feature = "solana")] +pub mod instruction; + +// --- Re-exports from light-account-checks --- +pub use light_account_checks::{self, discriminator::Discriminator as LightDiscriminator}; + // ============================================================================= // BACKWARD COMPATIBILITY: Submodule path preservation // ============================================================================= -// External code uses paths like `light_sdk::interface::config::LightConfig` -// and `light_sdk::interface::token::*`. Preserve with re-export aliases. /// Re-export config module for backward compatibility. pub mod config { @@ -43,7 +67,6 @@ pub mod compression_info { } /// Re-export close module for backward compatibility. -#[cfg(feature = "v2")] pub mod close { pub use super::program::compression::close::*; } @@ -66,21 +89,14 @@ pub mod traits { } // ============================================================================= -// BACKWARD COMPATIBILITY: Flat re-exports at interface level +// FLAT RE-EXPORTS // ============================================================================= -// The root interface/mod.rs re-exports everything at the flat level for -// backward compatibility with existing code. -// --- Re-exports from program/ --- // --- Re-exports from account/ --- -// Pack trait is only available off-chain (client-side) - uses PackedAccounts #[cfg(feature = "anchor")] pub use account::light_account::{AccountType, LightAccount}; -#[cfg(not(target_os = "solana"))] +#[cfg(all(not(target_os = "solana"), feature = "solana"))] pub use account::pack::Pack; -// --- Re-exports from program/variant --- -#[cfg(all(feature = "v2", feature = "cpi-context"))] -pub use account::pda_seeds::{HasTokenVariant, PdaSeedDerivation}; pub use account::{ compression_info::{ claim_completed_epoch_rent, CompressAs, CompressedAccountData, CompressedInitSpace, @@ -89,8 +105,8 @@ pub use account::{ }, pack::Unpack, }; +pub use account::pda_seeds::{HasTokenVariant, PdaSeedDerivation}; // --- Re-exports from accounts/ --- -#[cfg(feature = "v2")] pub use accounts::create_pda::create_pda_account; pub use accounts::{ finalize::{LightFinalize, LightPreInit}, @@ -101,16 +117,13 @@ pub use accounts::{ }; // --- Re-exports from external crates --- pub use light_compressible::{rent, CreateAccountsProof}; -#[cfg(feature = "v2")] pub use program::compression::close::close; #[cfg(feature = "anchor")] pub use program::compression::pda::prepare_account_for_compression; #[cfg(feature = "anchor")] pub use program::compression::processor::process_compress_pda_accounts_idempotent; #[cfg(feature = "anchor")] -pub use program::compression::processor::{ - CompressAndCloseParams, CompressCtx, CompressDispatchFn, -}; +pub use program::compression::processor::{CompressAndCloseParams, CompressCtx, CompressDispatchFn}; #[cfg(feature = "anchor")] pub use program::decompression::pda::prepare_account_for_decompression; #[cfg(feature = "anchor")] diff --git a/sdk-libs/sdk/src/interface/program/compression/close.rs b/sdk-libs/sdk-interface/src/program/compression/close.rs similarity index 73% rename from sdk-libs/sdk/src/interface/program/compression/close.rs rename to sdk-libs/sdk-interface/src/program/compression/close.rs index d19d8390b8..b2b1f68956 100644 --- a/sdk-libs/sdk/src/interface/program/compression/close.rs +++ b/sdk-libs/sdk-interface/src/program/compression/close.rs @@ -1,6 +1,6 @@ use solana_account_info::AccountInfo; -use crate::error::{LightSdkError, Result}; +use crate::error::{LightPdaError, Result}; // close native solana account pub fn close<'info>( @@ -12,7 +12,7 @@ pub fn close<'info>( if info.key == sol_destination.key { info.assign(&system_program_id); info.resize(0) - .map_err(|_| LightSdkError::ConstraintViolation)?; + .map_err(|_| LightPdaError::ConstraintViolation)?; return Ok(()); } @@ -21,25 +21,25 @@ pub fn close<'info>( let new_destination_lamports = sol_destination .lamports() .checked_add(lamports_to_transfer) - .ok_or(LightSdkError::ConstraintViolation)?; + .ok_or(LightPdaError::ConstraintViolation)?; { let mut destination_lamports = sol_destination .try_borrow_mut_lamports() - .map_err(|_| LightSdkError::ConstraintViolation)?; + .map_err(|_| LightPdaError::ConstraintViolation)?; **destination_lamports = new_destination_lamports; } { let mut source_lamports = info .try_borrow_mut_lamports() - .map_err(|_| LightSdkError::ConstraintViolation)?; + .map_err(|_| LightPdaError::ConstraintViolation)?; **source_lamports = 0; } info.assign(&system_program_id); info.resize(0) - .map_err(|_| LightSdkError::ConstraintViolation)?; + .map_err(|_| LightPdaError::ConstraintViolation)?; Ok(()) } diff --git a/sdk-libs/sdk/src/interface/program/compression/mod.rs b/sdk-libs/sdk-interface/src/program/compression/mod.rs similarity index 86% rename from sdk-libs/sdk/src/interface/program/compression/mod.rs rename to sdk-libs/sdk-interface/src/program/compression/mod.rs index d308bd5984..46572ac8b4 100644 --- a/sdk-libs/sdk/src/interface/program/compression/mod.rs +++ b/sdk-libs/sdk-interface/src/program/compression/mod.rs @@ -1,6 +1,5 @@ //! Compression functions for PDA accounts. -#[cfg(feature = "v2")] pub mod close; #[cfg(feature = "anchor")] diff --git a/sdk-libs/sdk/src/interface/program/compression/pda.rs b/sdk-libs/sdk-interface/src/program/compression/pda.rs similarity index 94% rename from sdk-libs/sdk/src/interface/program/compression/pda.rs rename to sdk-libs/sdk-interface/src/program/compression/pda.rs index 9ec9f40327..2066d056a5 100644 --- a/sdk-libs/sdk/src/interface/program/compression/pda.rs +++ b/sdk-libs/sdk-interface/src/program/compression/pda.rs @@ -17,9 +17,11 @@ use light_hasher::{sha256::Sha256BE, Hasher, Sha256}; use light_sdk_types::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress; use solana_program_error::ProgramError; +use light_sdk_types::instruction::account_meta::{CompressedAccountMeta, CompressedAccountMetaTrait}; + use crate::{ - instruction::account_meta::{CompressedAccountMeta, CompressedAccountMetaTrait}, - interface::{program::compression::processor::CompressCtx, LightAccount}, + program::compression::processor::CompressCtx, + account::light_account::LightAccount, LightDiscriminator, }; @@ -108,7 +110,8 @@ where // Create compressed account with canonical compressed CompressionInfo for hashing let mut compressed_data = account_data.clone(); - *compressed_data.compression_info_mut() = crate::compressible::CompressionInfo::compressed(); + *compressed_data.compression_info_mut() = + crate::account::compression_info::CompressionInfo::compressed(); // Hash the data (discriminator NOT included per protocol convention) let data_bytes = compressed_data diff --git a/sdk-libs/sdk/src/interface/program/compression/processor.rs b/sdk-libs/sdk-interface/src/program/compression/processor.rs similarity index 89% rename from sdk-libs/sdk/src/interface/program/compression/processor.rs rename to sdk-libs/sdk-interface/src/program/compression/processor.rs index 7cfb98dd14..e7500a2456 100644 --- a/sdk-libs/sdk/src/interface/program/compression/processor.rs +++ b/sdk-libs/sdk-interface/src/program/compression/processor.rs @@ -1,6 +1,9 @@ //! Compression instruction processor. -use light_compressed_account::instruction_data::with_account_info::CompressedAccountInfo; +use light_compressed_account::instruction_data::{ + compressed_proof::ValidityProof, + with_account_info::CompressedAccountInfo, +}; use light_sdk_types::{ instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, CpiSigner, }; @@ -13,8 +16,7 @@ use crate::{ v2::{CpiAccounts, LightSystemProgramCpi}, InvokeLightSystemProgram, LightCpiInstruction, }, - instruction::ValidityProof, - interface::LightConfig, + program::config::LightConfig, AnchorDeserialize, AnchorSerialize, }; @@ -89,12 +91,12 @@ pub fn process_compress_pda_accounts_idempotent<'info>( // Extract and validate accounts using shared validation let validated_ctx = - crate::interface::validation::validate_compress_accounts(remaining_accounts, program_id)?; + crate::program::validation::validate_compress_accounts(remaining_accounts, program_id)?; let fee_payer = &validated_ctx.fee_payer; let rent_sponsor = &validated_ctx.rent_sponsor; let light_config = validated_ctx.light_config; - let (_, system_accounts) = crate::interface::validation::split_at_system_accounts_offset( + let (_, system_accounts) = crate::program::validation::split_at_system_accounts_offset( remaining_accounts, params.system_accounts_offset, )?; @@ -114,7 +116,7 @@ pub fn process_compress_pda_accounts_idempotent<'info>( }; // PDA accounts at end of remaining_accounts - let pda_accounts = crate::interface::validation::extract_tail_accounts( + let pda_accounts = crate::program::validation::extract_tail_accounts( remaining_accounts, params.compressed_accounts.len(), )?; @@ -123,7 +125,7 @@ pub fn process_compress_pda_accounts_idempotent<'info>( let pda_account = &pda_accounts[i]; // Skip empty accounts or accounts not owned by this program - if crate::interface::validation::should_skip_compression(pda_account, program_id) { + if crate::program::validation::should_skip_compression(pda_account, program_id) { continue; } @@ -150,7 +152,8 @@ pub fn process_compress_pda_accounts_idempotent<'info>( // Close the PDA accounts for idx in compress_ctx.pda_indices_to_close { let mut info = pda_accounts[idx].clone(); - crate::interface::close::close(&mut info, rent_sponsor).map_err(ProgramError::from)?; + crate::program::compression::close::close(&mut info, rent_sponsor) + .map_err(ProgramError::from)?; } } diff --git a/sdk-libs/sdk/src/interface/program/config/create.rs b/sdk-libs/sdk-interface/src/program/config/create.rs similarity index 88% rename from sdk-libs/sdk/src/interface/program/config/create.rs rename to sdk-libs/sdk-interface/src/program/config/create.rs index e6f87d1128..c86c3a9997 100644 --- a/sdk-libs/sdk/src/interface/program/config/create.rs +++ b/sdk-libs/sdk-interface/src/program/config/create.rs @@ -1,17 +1,21 @@ //! Config initialization instructions. -use light_account_checks::discriminator::{Discriminator, DISCRIMINATOR_LEN}; +use light_account_checks::{ + checks::check_signer, + discriminator::{Discriminator, DISCRIMINATOR_LEN}, +}; use light_compressible::rent::RentConfig; use solana_account_info::AccountInfo; use solana_cpi::invoke_signed; use solana_loader_v3_interface::state::UpgradeableLoaderState; use solana_msg::msg; +use solana_program_error::ProgramError; use solana_pubkey::Pubkey; use solana_system_interface::instruction as system_instruction; use solana_sysvar::{rent::Rent, Sysvar}; use super::{state::LightConfig, validate_address_space_no_duplicates, COMPRESSIBLE_CONFIG_SEED}; -use crate::{error::LightSdkError, light_account_checks::checks::check_signer, AnchorSerialize}; +use crate::{error::LightPdaError, AnchorSerialize}; const BPF_LOADER_UPGRADEABLE_ID: Pubkey = Pubkey::from_str_const("BPFLoaderUpgradeab1e11111111111111111111111"); @@ -57,17 +61,17 @@ pub fn process_initialize_light_config<'info>( payer: &AccountInfo<'info>, system_program: &AccountInfo<'info>, program_id: &Pubkey, -) -> Result<(), crate::ProgramError> { +) -> Result<(), ProgramError> { // CHECK: only 1 address_space if config_bump != 0 { msg!("Config bump must be 0 for now, found: {}", config_bump); - return Err(LightSdkError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation.into()); } // CHECK: not already initialized if config_account.data_len() > 0 { msg!("Config account already initialized"); - return Err(LightSdkError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation.into()); } // CHECK: only 1 address_space @@ -76,7 +80,7 @@ pub fn process_initialize_light_config<'info>( "Address space must contain exactly 1 pubkey, found: {}", address_space.len() ); - return Err(LightSdkError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation.into()); } // CHECK: unique pubkeys in address_space @@ -91,7 +95,7 @@ pub fn process_initialize_light_config<'info>( let (derived_pda, bump) = LightConfig::derive_pda(program_id, config_bump); if derived_pda != *config_account.key { msg!("Invalid config PDA"); - return Err(LightSdkError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation.into()); } // Derive rent_sponsor_bump for storage @@ -103,10 +107,10 @@ pub fn process_initialize_light_config<'info>( derived_rent_sponsor, rent_sponsor ); - return Err(LightSdkError::InvalidRentSponsor.into()); + return Err(LightPdaError::InvalidRentSponsor.into()); } - let rent = Rent::get().map_err(LightSdkError::from)?; + let rent = Rent::get().map_err(LightPdaError::from)?; let account_size = LightConfig::size_for_address_space(address_space.len()); let rent_lamports = rent.minimum_balance(account_size); @@ -134,7 +138,7 @@ pub fn process_initialize_light_config<'info>( ], &[seeds], ) - .map_err(LightSdkError::from)?; + .map_err(LightPdaError::from)?; let config = LightConfig { version: 1, @@ -151,7 +155,7 @@ pub fn process_initialize_light_config<'info>( let mut data = config_account .try_borrow_mut_data() - .map_err(LightSdkError::from)?; + .map_err(LightPdaError::from)?; // Write discriminator first (using trait constant) data[..DISCRIMINATOR_LEN].copy_from_slice(&LightConfig::LIGHT_DISCRIMINATOR); @@ -159,7 +163,7 @@ pub fn process_initialize_light_config<'info>( // Serialize config data after discriminator config .serialize(&mut &mut data[DISCRIMINATOR_LEN..]) - .map_err(|_| LightSdkError::Borsh)?; + .map_err(|_| LightPdaError::Borsh)?; Ok(()) } @@ -173,24 +177,24 @@ pub fn process_initialize_light_config<'info>( /// /// # Returns /// * `Ok(())` if authority is valid -/// * `Err(LightSdkError)` if authority is invalid or verification fails +/// * `Err(LightPdaError)` if authority is invalid or verification fails pub fn check_program_upgrade_authority( program_id: &Pubkey, program_data_account: &AccountInfo, authority: &AccountInfo, -) -> Result<(), crate::ProgramError> { +) -> Result<(), ProgramError> { // CHECK: program data PDA let (expected_program_data, _) = Pubkey::find_program_address(&[program_id.as_ref()], &BPF_LOADER_UPGRADEABLE_ID); if program_data_account.key != &expected_program_data { msg!("Invalid program data account"); - return Err(LightSdkError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation.into()); } let data = program_data_account.try_borrow_data()?; let program_state: UpgradeableLoaderState = bincode::deserialize(&data).map_err(|_| { msg!("Failed to deserialize program data account"); - LightSdkError::ConstraintViolation + LightPdaError::ConstraintViolation })?; // Extract upgrade authority @@ -204,19 +208,19 @@ pub fn check_program_upgrade_authority( // Check for invalid zero authority when authority exists if auth == Pubkey::default() { msg!("Invalid state: authority is zero pubkey"); - return Err(LightSdkError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation.into()); } auth } None => { msg!("Program has no upgrade authority"); - return Err(LightSdkError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation.into()); } } } _ => { msg!("Account is not ProgramData, found: {:?}", program_state); - return Err(LightSdkError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation.into()); } }; @@ -232,7 +236,7 @@ pub fn check_program_upgrade_authority( authority.key, upgrade_authority ); - return Err(LightSdkError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation.into()); } Ok(()) @@ -272,7 +276,7 @@ pub fn process_initialize_light_config_checked<'info>( payer: &AccountInfo<'info>, system_program: &AccountInfo<'info>, program_id: &Pubkey, -) -> Result<(), crate::ProgramError> { +) -> Result<(), ProgramError> { msg!( "create_compression_config_checked program_data_account: {:?}", program_data_account.key diff --git a/sdk-libs/sdk/src/interface/program/config/mod.rs b/sdk-libs/sdk-interface/src/program/config/mod.rs similarity index 88% rename from sdk-libs/sdk/src/interface/program/config/mod.rs rename to sdk-libs/sdk-interface/src/program/config/mod.rs index 30a7975f5c..bbe17261d0 100644 --- a/sdk-libs/sdk/src/interface/program/config/mod.rs +++ b/sdk-libs/sdk-interface/src/program/config/mod.rs @@ -5,7 +5,7 @@ use std::collections::HashSet; use solana_msg::msg; use solana_pubkey::Pubkey; -use crate::error::LightSdkError; +use crate::error::LightPdaError; mod create; mod state; @@ -33,12 +33,12 @@ pub use update::process_update_light_config; /// Validates that address_space contains no duplicate pubkeys pub(super) fn validate_address_space_no_duplicates( address_space: &[Pubkey], -) -> Result<(), LightSdkError> { +) -> Result<(), LightPdaError> { let mut seen = HashSet::new(); for pubkey in address_space { if !seen.insert(pubkey) { msg!("Duplicate pubkey found in address_space: {}", pubkey); - return Err(LightSdkError::ConstraintViolation); + return Err(LightPdaError::ConstraintViolation); } } Ok(()) @@ -48,14 +48,14 @@ pub(super) fn validate_address_space_no_duplicates( pub(super) fn validate_address_space_only_adds( existing_address_space: &[Pubkey], new_address_space: &[Pubkey], -) -> Result<(), LightSdkError> { +) -> Result<(), LightPdaError> { for existing_pubkey in existing_address_space { if !new_address_space.contains(existing_pubkey) { msg!( "Cannot remove existing pubkey from address_space: {}", existing_pubkey ); - return Err(LightSdkError::ConstraintViolation); + return Err(LightPdaError::ConstraintViolation); } } Ok(()) diff --git a/sdk-libs/sdk/src/interface/program/config/state.rs b/sdk-libs/sdk-interface/src/program/config/state.rs similarity index 89% rename from sdk-libs/sdk/src/interface/program/config/state.rs rename to sdk-libs/sdk-interface/src/program/config/state.rs index 8f1074892f..bfb274dadd 100644 --- a/sdk-libs/sdk/src/interface/program/config/state.rs +++ b/sdk-libs/sdk-interface/src/program/config/state.rs @@ -7,10 +7,11 @@ use light_account_checks::{ use light_compressible::rent::RentConfig; use solana_account_info::AccountInfo; use solana_msg::msg; +use solana_program_error::ProgramError; use solana_pubkey::Pubkey; use super::{COMPRESSIBLE_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE, RENT_SPONSOR_SEED}; -use crate::{error::LightSdkError, AnchorDeserialize, AnchorSerialize}; +use crate::{error::LightPdaError, AnchorDeserialize, AnchorSerialize}; /// Global configuration for compressible accounts #[derive(Clone, AnchorDeserialize, AnchorSerialize, Debug)] @@ -100,33 +101,33 @@ impl LightConfig { pub fn validate_rent_sponsor( &self, rent_sponsor: &AccountInfo, - ) -> Result { + ) -> Result { if *rent_sponsor.key != self.rent_sponsor { msg!( "rent_sponsor mismatch: expected {:?}, got {:?}", self.rent_sponsor, rent_sponsor.key ); - return Err(LightSdkError::InvalidRentSponsor.into()); + return Err(LightPdaError::InvalidRentSponsor.into()); } Ok(self.rent_sponsor_bump) } /// Checks the config account - pub fn validate(&self) -> Result<(), crate::ProgramError> { + pub fn validate(&self) -> Result<(), ProgramError> { if self.version != 1 { msg!( "LightConfig validation failed: Unsupported config version: {}", self.version ); - return Err(LightSdkError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation.into()); } if self.address_space.len() != 1 { msg!( "LightConfig validation failed: Address space must contain exactly 1 pubkey, found: {}", self.address_space.len() ); - return Err(LightSdkError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation.into()); } // For now, only allow config_bump = 0 to keep it simple if self.config_bump != 0 { @@ -134,7 +135,7 @@ impl LightConfig { "LightConfig validation failed: Config bump must be 0 for now, found: {}", self.config_bump ); - return Err(LightSdkError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation.into()); } Ok(()) } @@ -144,7 +145,7 @@ impl LightConfig { pub fn load_checked( account: &AccountInfo, program_id: &Pubkey, - ) -> Result { + ) -> Result { // CHECK: Owner if account.owner != program_id { msg!( @@ -152,7 +153,7 @@ impl LightConfig { program_id, account.owner ); - return Err(LightSdkError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation.into()); } let data = account.try_borrow_data()?; @@ -160,7 +161,7 @@ impl LightConfig { // CHECK: Discriminator using light-account-checks check_discriminator::(&data).map_err(|e| { msg!("LightConfig::load_checked failed: {:?}", e); - LightSdkError::ConstraintViolation + LightPdaError::ConstraintViolation })?; // Deserialize from offset after discriminator @@ -169,7 +170,7 @@ impl LightConfig { "LightConfig::load_checked failed: Failed to deserialize config data: {:?}", err ); - LightSdkError::Borsh + LightPdaError::Borsh })?; config.validate()?; @@ -181,7 +182,7 @@ impl LightConfig { expected_pda, account.key ); - return Err(LightSdkError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation.into()); } Ok(config) diff --git a/sdk-libs/sdk/src/interface/program/config/update.rs b/sdk-libs/sdk-interface/src/program/config/update.rs similarity index 89% rename from sdk-libs/sdk/src/interface/program/config/update.rs rename to sdk-libs/sdk-interface/src/program/config/update.rs index 9d70336f8e..a5571b2577 100644 --- a/sdk-libs/sdk/src/interface/program/config/update.rs +++ b/sdk-libs/sdk-interface/src/program/config/update.rs @@ -1,16 +1,17 @@ //! Config update instruction. -use light_account_checks::discriminator::DISCRIMINATOR_LEN; +use light_account_checks::{checks::check_signer, discriminator::DISCRIMINATOR_LEN}; use light_compressible::rent::RentConfig; use solana_account_info::AccountInfo; use solana_msg::msg; +use solana_program_error::ProgramError; use solana_pubkey::Pubkey; use super::{ state::LightConfig, validate_address_space_no_duplicates, validate_address_space_only_adds, MAX_ADDRESS_TREES_PER_SPACE, }; -use crate::{error::LightSdkError, light_account_checks::checks::check_signer, AnchorSerialize}; +use crate::{error::LightPdaError, AnchorSerialize}; /// Updates an existing compressible config /// @@ -39,7 +40,7 @@ pub fn process_update_light_config<'info>( new_write_top_up: Option, new_address_space: Option>, owner_program_id: &Pubkey, -) -> Result<(), crate::ProgramError> { +) -> Result<(), ProgramError> { // CHECK: PDA derivation let mut config = LightConfig::load_checked(config_account, owner_program_id)?; @@ -50,7 +51,7 @@ pub fn process_update_light_config<'info>( // CHECK: authority if *authority.key != config.update_authority { msg!("Invalid update authority"); - return Err(LightSdkError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation.into()); } if let Some(new_authority) = new_update_authority { @@ -75,7 +76,7 @@ pub fn process_update_light_config<'info>( "New address space must contain exactly 1 pubkey, found: {}", new_address_space.len() ); - return Err(LightSdkError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation.into()); } validate_address_space_no_duplicates(&new_address_space)?; @@ -87,14 +88,14 @@ pub fn process_update_light_config<'info>( let mut data = config_account.try_borrow_mut_data().map_err(|e| { msg!("Failed to borrow mut data for config_account: {:?}", e); - LightSdkError::from(e) + LightPdaError::from(e) })?; // Serialize after discriminator (discriminator is preserved from init) config .serialize(&mut &mut data[DISCRIMINATOR_LEN..]) .map_err(|e| { msg!("Failed to serialize updated config: {:?}", e); - LightSdkError::Borsh + LightPdaError::Borsh })?; Ok(()) diff --git a/sdk-libs/sdk/src/interface/program/decompression/create_token_account.rs b/sdk-libs/sdk-interface/src/program/decompression/create_token_account.rs similarity index 100% rename from sdk-libs/sdk/src/interface/program/decompression/create_token_account.rs rename to sdk-libs/sdk-interface/src/program/decompression/create_token_account.rs diff --git a/sdk-libs/sdk/src/interface/program/decompression/mod.rs b/sdk-libs/sdk-interface/src/program/decompression/mod.rs similarity index 100% rename from sdk-libs/sdk/src/interface/program/decompression/mod.rs rename to sdk-libs/sdk-interface/src/program/decompression/mod.rs diff --git a/sdk-libs/sdk/src/interface/program/decompression/pda.rs b/sdk-libs/sdk-interface/src/program/decompression/pda.rs similarity index 95% rename from sdk-libs/sdk/src/interface/program/decompression/pda.rs rename to sdk-libs/sdk-interface/src/program/decompression/pda.rs index 60f9f7c4c3..2fd0cca988 100644 --- a/sdk-libs/sdk/src/interface/program/decompression/pda.rs +++ b/sdk-libs/sdk-interface/src/program/decompression/pda.rs @@ -12,10 +12,10 @@ use solana_program_error::ProgramError; use solana_pubkey::Pubkey; use crate::{ - interface::{ - create_pda_account, DecompressCtx, LightAccount, LightAccountVariantTrait, - PackedLightAccountVariantTrait, - }, + accounts::create_pda::create_pda_account, + program::decompression::processor::DecompressCtx, + account::light_account::LightAccount, + program::variant::{LightAccountVariantTrait, PackedLightAccountVariantTrait}, LightDiscriminator, }; @@ -83,7 +83,7 @@ where // 4. Idempotency check - if PDA already has data (non-zero discriminator), skip // IMPORTANT: This runs AFTER PDA validation so wrong PDAs cannot bypass validation - if crate::interface::validation::is_pda_initialized(pda_account)? { + if crate::program::validation::is_pda_initialized(pda_account)? { return Ok(()); } diff --git a/sdk-libs/sdk/src/interface/program/decompression/processor.rs b/sdk-libs/sdk-interface/src/program/decompression/processor.rs similarity index 96% rename from sdk-libs/sdk/src/interface/program/decompression/processor.rs rename to sdk-libs/sdk-interface/src/program/decompression/processor.rs index c59c17ed9c..85ff61b499 100644 --- a/sdk-libs/sdk/src/interface/program/decompression/processor.rs +++ b/sdk-libs/sdk-interface/src/program/decompression/processor.rs @@ -8,9 +8,10 @@ use anchor_lang::{ solana_program::{clock::Clock, program::invoke_signed, rent::Rent, sysvar::Sysvar}, }; use light_compressed_account::instruction_data::{ - cpi_context::CompressedCpiContext, with_account_info::CompressedAccountInfo, + cpi_context::CompressedCpiContext, + compressed_proof::ValidityProof, + with_account_info::CompressedAccountInfo, }; -#[cfg(feature = "cpi-context")] use light_sdk_types::cpi_context_write::CpiContextWriteAccounts; use light_sdk_types::{ cpi_accounts::CpiAccountsConfig, instruction::PackedStateTreeInfo, CpiSigner, @@ -30,9 +31,9 @@ use solana_instruction::Instruction; use solana_program_error::ProgramError; use crate::{ + account::compression_info::CompressedAccountData, cpi::{v2::CpiAccounts, InvokeLightSystemProgram}, - instruction::ValidityProof, - interface::{compression_info::CompressedAccountData, LightConfig}, + program::config::LightConfig, }; // ============================================================================ @@ -141,7 +142,7 @@ where // Extract and validate accounts using shared validation let validated_ctx = - crate::interface::validation::validate_decompress_accounts(remaining_accounts, program_id)?; + crate::program::validation::validate_decompress_accounts(remaining_accounts, program_id)?; let fee_payer = &validated_ctx.fee_payer; let rent_sponsor = &validated_ctx.rent_sponsor; let rent_sponsor_bump = validated_ctx.rent_sponsor_bump; @@ -233,10 +234,7 @@ where if has_pda_accounts { // CPI to Light System Program with proof - #[cfg(feature = "cpi-context")] let pda_only = !cpi_context; - #[cfg(not(feature = "cpi-context"))] - let pda_only = true; if pda_only { // Manual construction to avoid extra allocations @@ -257,7 +255,6 @@ where }; instruction_data.invoke(cpi_accounts.clone())?; } else { - #[cfg(feature = "cpi-context")] { // PDAs + tokens - write to CPI context first, tokens will execute let authority = cpi_accounts @@ -291,10 +288,6 @@ where }; instruction_data.invoke_write_to_cpi_context_first(system_cpi_accounts)?; } - #[cfg(not(feature = "cpi-context"))] - { - return Err(ProgramError::InvalidInstructionData); - } } } diff --git a/sdk-libs/sdk/src/interface/program/decompression/token.rs b/sdk-libs/sdk-interface/src/program/decompression/token.rs similarity index 97% rename from sdk-libs/sdk/src/interface/program/decompression/token.rs rename to sdk-libs/sdk-interface/src/program/decompression/token.rs index f1cef2f13c..014046af96 100644 --- a/sdk-libs/sdk/src/interface/program/decompression/token.rs +++ b/sdk-libs/sdk-interface/src/program/decompression/token.rs @@ -8,7 +8,10 @@ use solana_program_error::ProgramError; use super::create_token_account::{ build_create_ata_instruction, build_create_token_account_instruction, }; -use crate::interface::{DecompressCtx, PackedLightAccountVariantTrait}; +use crate::program::{ + decompression::processor::DecompressCtx, + variant::PackedLightAccountVariantTrait, +}; pub fn prepare_token_account_for_decompression<'info, const SEED_COUNT: usize, P>( packed: &P, diff --git a/sdk-libs/sdk/src/interface/program/mod.rs b/sdk-libs/sdk-interface/src/program/mod.rs similarity index 100% rename from sdk-libs/sdk/src/interface/program/mod.rs rename to sdk-libs/sdk-interface/src/program/mod.rs diff --git a/sdk-libs/sdk/src/interface/program/validation.rs b/sdk-libs/sdk-interface/src/program/validation.rs similarity index 94% rename from sdk-libs/sdk/src/interface/program/validation.rs rename to sdk-libs/sdk-interface/src/program/validation.rs index 9efe406514..de504eaf73 100644 --- a/sdk-libs/sdk/src/interface/program/validation.rs +++ b/sdk-libs/sdk-interface/src/program/validation.rs @@ -5,9 +5,12 @@ use solana_program_error::ProgramError; use solana_pubkey::Pubkey; use crate::{ - error::LightSdkError, - interface::LightConfig, - light_account_checks::{account_iterator::AccountIterator, checks::check_data_is_zeroed}, + error::LightPdaError, + program::config::LightConfig, +}; +use light_account_checks::{ + account_iterator::AccountIterator, + checks::check_data_is_zeroed, }; /// Validated PDA context after account extraction and config validation. @@ -91,7 +94,7 @@ fn validate_pda_common_accounts_inner<'info, const EXTRACT_COMPRESSION_AUTHORITY let rent_sponsor_bump = light_config .validate_rent_sponsor(rent_sponsor) - .map_err(|_| LightSdkError::InvalidRentSponsor)?; + .map_err(|_| LightPdaError::InvalidRentSponsor)?; // TODO: validate compression_authority matches config when client-side code is updated // if EXTRACT_COMPRESSION_AUTHORITY { @@ -102,7 +105,7 @@ fn validate_pda_common_accounts_inner<'info, const EXTRACT_COMPRESSION_AUTHORITY // light_config.compression_authority, // auth.key // ); - // return Err(LightSdkError::ConstraintViolation.into()); + // return Err(LightPdaError::ConstraintViolation.into()); // } // } // } @@ -159,7 +162,7 @@ pub fn extract_tail_accounts<'a, 'info>( /// - `Ok(true)` if account has data and non-zero discriminator (initialized) /// - `Ok(false)` if account is empty or has zeroed discriminator (not initialized) pub fn is_pda_initialized(account: &AccountInfo) -> Result { - use crate::light_account_checks::discriminator::DISCRIMINATOR_LEN; + use light_account_checks::discriminator::DISCRIMINATOR_LEN; if account.data_is_empty() { return Ok(false); diff --git a/sdk-libs/sdk/src/interface/program/variant.rs b/sdk-libs/sdk-interface/src/program/variant.rs similarity index 97% rename from sdk-libs/sdk/src/interface/program/variant.rs rename to sdk-libs/sdk-interface/src/program/variant.rs index c199e24cb6..9b3ab25e37 100644 --- a/sdk-libs/sdk/src/interface/program/variant.rs +++ b/sdk-libs/sdk-interface/src/program/variant.rs @@ -49,7 +49,7 @@ mod anchor_traits { }; use solana_program_error::ProgramError; - use super::super::super::account::light_account::AccountType; + use crate::account::light_account::AccountType; /// Trait for unpacked compressed account variants with seeds. /// @@ -167,7 +167,7 @@ mod anchor_traits { /// (e.g., `PackedTokenVaultSeeds`). Provides seed-specific behavior for the blanket /// `PackedLightAccountVariantTrait` impl on `TokenDataWithPackedSeeds`. pub trait PackedTokenSeeds: - crate::Unpack + Clone + std::fmt::Debug + AnchorSerialize + AnchorDeserialize + crate::account::pack::Unpack + Clone + std::fmt::Debug + AnchorSerialize + AnchorDeserialize { fn bump(&self) -> u8; fn seed_refs_with_bump<'a>( diff --git a/sdk-libs/sdk/Cargo.toml b/sdk-libs/sdk/Cargo.toml index 6670989c0f..0b17313464 100644 --- a/sdk-libs/sdk/Cargo.toml +++ b/sdk-libs/sdk/Cargo.toml @@ -18,6 +18,7 @@ idl-build = [ "light-sdk-types/idl-build", "light-compressible/idl-build", "light-token-interface/idl-build", + "light-sdk-interface/idl-build", "anchor", "dep:solana-program" ] @@ -26,14 +27,15 @@ anchor = [ "light-compressed-account/anchor", "light-sdk-types/anchor", "light-compressible/anchor", - "light-token-interface/anchor" + "light-token-interface/anchor", + "light-sdk-interface/anchor" ] v2 = ["light-sdk-types/v2"] cpi-context = ["light-sdk-types/cpi-context"] devnet = [] -poseidon = ["light-hasher/poseidon", "light-compressed-account/poseidon"] -keccak = ["light-hasher/keccak", "light-compressed-account/keccak"] -sha256 = ["light-hasher/sha256", "light-compressed-account/sha256"] +poseidon = ["light-hasher/poseidon", "light-compressed-account/poseidon", "light-sdk-interface/poseidon"] +keccak = ["light-hasher/keccak", "light-compressed-account/keccak", "light-sdk-interface/keccak"] +sha256 = ["light-hasher/sha256", "light-compressed-account/sha256", "light-sdk-interface/sha256"] merkle-tree = ["light-concurrent-merkle-tree/solana"] anchor-discriminator = ["light-sdk-macros/anchor-discriminator"] custom-heap = ["light-heap"] @@ -75,6 +77,7 @@ light-concurrent-merkle-tree = { workspace = true, optional = true } light-compressible = { workspace = true } light-heap = { workspace = true, optional = true } light-token-interface = { workspace = true } # TODO: make optional +light-sdk-interface = { workspace = true, features = ["solana", "std"] } [dev-dependencies] num-bigint = { workspace = true } diff --git a/sdk-libs/sdk/src/cpi/instruction.rs b/sdk-libs/sdk/src/cpi/instruction.rs index d284d747dd..87e5a7ac29 100644 --- a/sdk-libs/sdk/src/cpi/instruction.rs +++ b/sdk-libs/sdk/src/cpi/instruction.rs @@ -1,20 +1,21 @@ -use light_compressed_account::instruction_data::compressed_proof::ValidityProof; +use crate::{account::LightAccount, AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError}; #[cfg(feature = "poseidon")] use crate::DataHasher; -use crate::{ - account::LightAccount, AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError, -}; - -/// Trait for Light CPI instruction types -pub trait LightCpiInstruction: Sized { - /// Creates a new CPI instruction builder with a validity proof. - /// - /// # Arguments - /// * `cpi_signer` - The CPI signer containing program ID and bump seed - /// * `proof` - Validity proof for compressed account operations - fn new_cpi(cpi_signer: crate::cpi::CpiSigner, proof: ValidityProof) -> Self; +/// Extension trait adding `with_light_account` to CPI instruction builders. +/// +/// This is SDK-specific because it depends on [`LightAccount`](crate::account::LightAccount), +/// which requires SHA256/Poseidon hashing. The base [`LightCpiInstruction`](light_sdk_interface::cpi::LightCpiInstruction) +/// trait from `light-sdk-interface` is framework-agnostic. +/// +/// # Usage +/// +/// Import this trait alongside [`LightCpiInstruction`](light_sdk_interface::cpi::LightCpiInstruction): +/// ```rust,ignore +/// use light_sdk::cpi::{LightCpiInstruction, WithLightAccount}; +/// ``` +pub trait WithLightAccount: Sized { /// Adds a compressed account to the instruction (using SHA256 hashing). /// /// The account can be an input (for updating/closing), output (for creating/updating), @@ -48,57 +49,4 @@ pub trait LightCpiInstruction: Sized { ) -> Result where A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + DataHasher + Default; - - /// Returns the instruction mode (0 for v1, 1 for v2). - fn get_mode(&self) -> u8; - - /// Returns the CPI signer bump seed. - fn get_bump(&self) -> u8; - - /// Writes instruction to CPI context as the first operation in a batch. - /// - /// # Availability - /// Only available with the `cpi-context` feature enabled. - #[cfg(feature = "cpi-context")] - #[must_use = "write_to_cpi_context_first returns a new value"] - fn write_to_cpi_context_first(self) -> Self; - - /// Writes instruction to CPI context as a subsequent operation in a batch. - /// - /// # Availability - /// Only available with the `cpi-context` feature enabled. - #[cfg(feature = "cpi-context")] - #[must_use = "write_to_cpi_context_set returns a new value"] - fn write_to_cpi_context_set(self) -> Self; - - /// Executes all operations accumulated in CPI context. - /// - /// # Availability - /// Only available with the `cpi-context` feature enabled. - #[cfg(feature = "cpi-context")] - #[must_use = "execute_with_cpi_context returns a new value"] - fn execute_with_cpi_context(self) -> Self; - - /// Returns whether this instruction uses CPI context. - /// - /// # Availability - /// Only available with the `cpi-context` feature enabled. - #[cfg(feature = "cpi-context")] - fn get_with_cpi_context(&self) -> bool; - - /// Returns the CPI context configuration. - /// - /// # Availability - /// Only available with the `cpi-context` feature enabled. - #[cfg(feature = "cpi-context")] - fn get_cpi_context( - &self, - ) -> &light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; - - /// Returns whether this instruction has any read-only accounts. - /// - /// # Availability - /// Only available with the `cpi-context` feature enabled. - #[cfg(feature = "cpi-context")] - fn has_read_only_accounts(&self) -> bool; } diff --git a/sdk-libs/sdk/src/cpi/mod.rs b/sdk-libs/sdk/src/cpi/mod.rs index c5392e5946..489bbaf655 100644 --- a/sdk-libs/sdk/src/cpi/mod.rs +++ b/sdk-libs/sdk/src/cpi/mod.rs @@ -35,19 +35,14 @@ //! .invoke(light_cpi_accounts)?; //! ``` -mod account; +// Re-export everything from interface's CPI module +pub use light_sdk_interface::cpi::*; + +// SDK-specific extension trait (adds with_light_account to CPI builders) mod instruction; -pub mod invoke; +pub use instruction::WithLightAccount; +// V1/V2 modules that provide WithLightAccount impls pub mod v1; #[cfg(feature = "v2")] pub mod v2; - -pub use account::*; -pub use instruction::*; -pub use invoke::InvokeLightSystemProgram; -pub use light_compressed_account::instruction_data::traits::LightInstructionData; -/// Derives cpi signer and bump to invoke the light system program at compile time. -pub use light_macros::derive_light_cpi_signer; -/// Contains program id, derived cpi signer, and bump, -pub use light_sdk_types::{cpi_accounts::CpiAccountsConfig, CpiSigner}; diff --git a/sdk-libs/sdk/src/cpi/v1/invoke.rs b/sdk-libs/sdk/src/cpi/v1/invoke.rs index 15a492b6a6..8f9c346410 100644 --- a/sdk-libs/sdk/src/cpi/v1/invoke.rs +++ b/sdk-libs/sdk/src/cpi/v1/invoke.rs @@ -1,235 +1,29 @@ -use light_compressed_account::instruction_data::{ - compressed_proof::ValidityProof, invoke_cpi::InstructionDataInvokeCpi, -}; +use light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext; #[cfg(feature = "poseidon")] use crate::{account::poseidon::LightAccount as LightAccountPoseidon, DataHasher}; use crate::{ account::LightAccount, - cpi::{instruction::LightCpiInstruction, invoke::LightInstructionData, CpiSigner}, + cpi::instruction::WithLightAccount, error::LightSdkError, instruction::account_info::CompressedAccountInfoTrait, AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError, }; -/// Light system program CPI instruction data builder. -/// -/// Use this builder to construct instructions for compressed account operations: -/// creating, updating, closing accounts, and compressing/decompressing SOL. -/// -/// # Builder Methods -/// -/// ## Common Methods -/// -/// - [`with_light_account()`](Self::with_light_account) - Add a compressed account (handles output hashing, and type conversion to instruction data) -/// - [`with_new_addresses()`](Self::with_new_addresses) - Create new compressed account addresses -/// - [`compress_lamports()`](Self::compress_lamports) - Compress SOL into compressed accounts -/// - [`decompress_lamports()`](Self::decompress_lamports) - Decompress SOL from compressed accounts -/// -/// **Note**: An instruction can either compress **or** decompress lamports, not both. -/// -/// ## Advanced Methods -/// -/// For fine-grained control, use these low-level methods instead of [`with_light_account()`](Self::with_light_account): -/// -/// - [`with_input_compressed_accounts_with_merkle_context()`](Self::with_input_compressed_accounts_with_merkle_context) - Manually specify input accounts -/// - [`with_output_compressed_accounts()`](Self::with_output_compressed_accounts) - Manually specify output accounts -/// -/// # Examples -/// -/// ## Create a compressed account with an address -/// ```rust,no_run -/// # use light_sdk::cpi::{v1::LightSystemProgramCpi, InvokeLightSystemProgram, LightCpiInstruction, CpiSigner}; -/// # use light_sdk::instruction::ValidityProof; -/// # use light_compressed_account::instruction_data::data::NewAddressParamsPacked; -/// # use light_sdk::{LightAccount, LightDiscriminator}; -/// # use borsh::{BorshSerialize, BorshDeserialize}; -/// # use solana_pubkey::Pubkey; -/// # use solana_program_error::ProgramError; -/// # -/// # const LIGHT_CPI_SIGNER: CpiSigner = CpiSigner { -/// # program_id: [0; 32], -/// # cpi_signer: [0; 32], -/// # bump: 255, -/// # }; -/// # -/// # #[derive(Clone, Debug, Default, LightDiscriminator, BorshSerialize, BorshDeserialize)] -/// # pub struct MyAccount { -/// # pub value: u64, -/// # } -/// # -/// # fn example() -> Result<(), ProgramError> { -/// # let proof = ValidityProof::default(); -/// # let new_address_params = NewAddressParamsPacked::default(); -/// # let program_id = Pubkey::new_unique(); -/// # let account = LightAccount::::new_init(&program_id, None, 0); -/// # let key = Pubkey::new_unique(); -/// # let owner = Pubkey::default(); -/// # let mut lamports = 0u64; -/// # let mut data = []; -/// # let fee_payer = &solana_account_info::AccountInfo::new( -/// # &key, -/// # true, -/// # true, -/// # &mut lamports, -/// # &mut data, -/// # &owner, -/// # false, -/// # 0, -/// # ); -/// # let cpi_accounts = light_sdk::cpi::v1::CpiAccounts::new(fee_payer, &[], LIGHT_CPI_SIGNER); -/// LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof) -/// .with_new_addresses(&[new_address_params]) -/// .with_light_account(account)? -/// .invoke(cpi_accounts)?; -/// # Ok(()) -/// # } -/// ``` -/// ## Update a compressed account -/// ```rust,no_run -/// # use light_sdk::cpi::{v1::LightSystemProgramCpi, InvokeLightSystemProgram, LightCpiInstruction, CpiSigner}; -/// # use light_sdk::instruction::ValidityProof; -/// # use light_sdk::{LightAccount, LightDiscriminator}; -/// # use light_sdk::instruction::account_meta::CompressedAccountMeta; -/// # use borsh::{BorshSerialize, BorshDeserialize}; -/// # use solana_pubkey::Pubkey; -/// # use solana_program_error::ProgramError; -/// # -/// # const LIGHT_CPI_SIGNER: CpiSigner = CpiSigner { -/// # program_id: [0; 32], -/// # cpi_signer: [0; 32], -/// # bump: 255, -/// # }; -/// # -/// # #[derive(Clone, Debug, Default, LightDiscriminator, BorshSerialize, BorshDeserialize)] -/// # pub struct MyAccount { -/// # pub value: u64, -/// # } -/// # -/// # fn example() -> Result<(), ProgramError> { -/// # let proof = ValidityProof::default(); -/// # let program_id = Pubkey::new_unique(); -/// # let account_meta = CompressedAccountMeta::default(); -/// # let account_data = MyAccount::default(); -/// # let account = LightAccount::::new_mut(&program_id, &account_meta, account_data)?; -/// # let key = Pubkey::new_unique(); -/// # let owner = Pubkey::default(); -/// # let mut lamports = 0u64; -/// # let mut data = []; -/// # let fee_payer = &solana_account_info::AccountInfo::new( -/// # &key, -/// # true, -/// # true, -/// # &mut lamports, -/// # &mut data, -/// # &owner, -/// # false, -/// # 0, -/// # ); -/// # let cpi_accounts = light_sdk::cpi::v1::CpiAccounts::new(fee_payer, &[], LIGHT_CPI_SIGNER); -/// LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof) -/// .with_light_account(account)? -/// .invoke(cpi_accounts)?; -/// # Ok(()) -/// # } -/// ``` -#[derive(Clone)] -pub struct LightSystemProgramCpi { - cpi_signer: CpiSigner, - instruction_data: InstructionDataInvokeCpi, -} - -impl LightSystemProgramCpi { - #[must_use = "with_new_addresses returns a new value"] - pub fn with_new_addresses( - mut self, - new_address_params: &[light_compressed_account::instruction_data::data::NewAddressParamsPacked], - ) -> Self { - self.instruction_data = self.instruction_data.with_new_addresses(new_address_params); - self - } - - #[must_use = "with_input_compressed_accounts_with_merkle_context returns a new value"] - pub fn with_input_compressed_accounts_with_merkle_context( - mut self, - input_compressed_accounts_with_merkle_context: &[light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext], - ) -> Self { - self.instruction_data = self - .instruction_data - .with_input_compressed_accounts_with_merkle_context( - input_compressed_accounts_with_merkle_context, - ); - self - } - - #[must_use = "with_output_compressed_accounts returns a new value"] - pub fn with_output_compressed_accounts( - mut self, - output_compressed_accounts: &[light_compressed_account::instruction_data::data::OutputCompressedAccountWithPackedContext], - ) -> Self { - self.instruction_data = self - .instruction_data - .with_output_compressed_accounts(output_compressed_accounts); - self - } - - #[must_use = "compress_lamports returns a new value"] - pub fn compress_lamports(mut self, lamports: u64) -> Self { - self.instruction_data = self.instruction_data.compress_lamports(lamports); - self - } - - #[must_use = "decompress_lamports returns a new value"] - pub fn decompress_lamports(mut self, lamports: u64) -> Self { - self.instruction_data = self.instruction_data.decompress_lamports(lamports); - self - } - - #[cfg(feature = "cpi-context")] - #[must_use = "write_to_cpi_context_set returns a new value"] - pub fn write_to_cpi_context_set(mut self) -> Self { - self.instruction_data = self.instruction_data.write_to_cpi_context_set(); - self - } - - #[cfg(feature = "cpi-context")] - #[must_use = "write_to_cpi_context_first returns a new value"] - pub fn write_to_cpi_context_first(mut self) -> Self { - self.instruction_data = self.instruction_data.write_to_cpi_context_first(); - self - } - - #[cfg(feature = "cpi-context")] - #[must_use = "with_cpi_context returns a new value"] - pub fn with_cpi_context( - mut self, - cpi_context: light_compressed_account::instruction_data::cpi_context::CompressedCpiContext, - ) -> Self { - self.instruction_data = self.instruction_data.with_cpi_context(cpi_context); - self - } -} - -impl LightCpiInstruction for LightSystemProgramCpi { - fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self { - Self { - cpi_signer, - instruction_data: InstructionDataInvokeCpi::new(proof.into()), - } - } +// Re-export LightSystemProgramCpi from interface +pub use light_sdk_interface::cpi::v1::LightSystemProgramCpi; +impl WithLightAccount for LightSystemProgramCpi { fn with_light_account(mut self, account: LightAccount) -> Result where A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default, { - use light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext; - // Convert LightAccount to account info let account_info = account.to_account_info()?; // Handle input accounts - convert to PackedCompressedAccountWithMerkleContext if let Some(input_account) = account_info - .input_compressed_account(self.cpi_signer.program_id.into()) + .input_compressed_account(self.cpi_signer().program_id.into()) .map_err(LightSdkError::from) .map_err(ProgramError::from)? { @@ -239,18 +33,18 @@ impl LightCpiInstruction for LightSystemProgramCpi { root_index: input_account.root_index, read_only: false, // Default to false for v1 }; - self.instruction_data + self.instruction_data_mut() .input_compressed_accounts_with_merkle_context .push(packed_input); } // Handle output accounts if let Some(output_account) = account_info - .output_compressed_account(self.cpi_signer.program_id.into()) + .output_compressed_account(self.cpi_signer().program_id.into()) .map_err(LightSdkError::from) .map_err(ProgramError::from)? { - self.instruction_data + self.instruction_data_mut() .output_compressed_accounts .push(output_account); } @@ -266,14 +60,12 @@ impl LightCpiInstruction for LightSystemProgramCpi { where A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + DataHasher + Default, { - use light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext; - // Convert LightAccount to account info let account_info = account.to_account_info()?; // Handle input accounts - convert to PackedCompressedAccountWithMerkleContext if let Some(input_account) = account_info - .input_compressed_account(self.cpi_signer.program_id.into()) + .input_compressed_account(self.cpi_signer().program_id.into()) .map_err(LightSdkError::from) .map_err(ProgramError::from)? { @@ -283,96 +75,22 @@ impl LightCpiInstruction for LightSystemProgramCpi { root_index: input_account.root_index, read_only: false, // Default to false for v1 }; - self.instruction_data + self.instruction_data_mut() .input_compressed_accounts_with_merkle_context .push(packed_input); } // Handle output accounts if let Some(output_account) = account_info - .output_compressed_account(self.cpi_signer.program_id.into()) + .output_compressed_account(self.cpi_signer().program_id.into()) .map_err(LightSdkError::from) .map_err(ProgramError::from)? { - self.instruction_data + self.instruction_data_mut() .output_compressed_accounts .push(output_account); } Ok(self) } - - fn get_mode(&self) -> u8 { - 0 // V1 uses regular mode by default - } - - fn get_bump(&self) -> u8 { - self.cpi_signer.bump - } - - #[cfg(feature = "cpi-context")] - fn write_to_cpi_context_first(mut self) -> Self { - self.instruction_data = self.instruction_data.write_to_cpi_context_first(); - self - } - - #[cfg(feature = "cpi-context")] - fn write_to_cpi_context_set(mut self) -> Self { - self.instruction_data = self.instruction_data.write_to_cpi_context_set(); - self - } - - #[cfg(feature = "cpi-context")] - fn execute_with_cpi_context(self) -> Self { - // V1 doesn't have a direct execute context, just return self - // The execute happens through the invoke call - self - } - - #[cfg(feature = "cpi-context")] - fn get_with_cpi_context(&self) -> bool { - self.instruction_data.cpi_context.is_some() - } - - #[cfg(feature = "cpi-context")] - fn get_cpi_context( - &self, - ) -> &light_compressed_account::instruction_data::cpi_context::CompressedCpiContext { - use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; - // Use a static default with all fields set to false/0 - static DEFAULT: CompressedCpiContext = CompressedCpiContext { - set_context: false, - first_set_context: false, - cpi_context_account_index: 0, - }; - self.instruction_data - .cpi_context - .as_ref() - .unwrap_or(&DEFAULT) - } - - #[cfg(feature = "cpi-context")] - fn has_read_only_accounts(&self) -> bool { - // V1 doesn't support read-only accounts - false - } -} - -// Manual BorshSerialize implementation that only serializes instruction_data -impl AnchorSerialize for LightSystemProgramCpi { - fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - self.instruction_data.serialize(writer) - } -} - -impl light_compressed_account::InstructionDiscriminator for LightSystemProgramCpi { - fn discriminator(&self) -> &'static [u8] { - self.instruction_data.discriminator() - } -} - -impl LightInstructionData for LightSystemProgramCpi { - fn data(&self) -> Result, light_compressed_account::CompressedAccountError> { - self.instruction_data.data() - } } diff --git a/sdk-libs/sdk/src/cpi/v1/mod.rs b/sdk-libs/sdk/src/cpi/v1/mod.rs index b9c66a8cd4..cf3f428360 100644 --- a/sdk-libs/sdk/src/cpi/v1/mod.rs +++ b/sdk-libs/sdk/src/cpi/v1/mod.rs @@ -10,23 +10,9 @@ //! //! For maximum flexible light system program CPIs, see the [`lowlevel`] module or use `light-compressed-account` directly. -mod accounts; -mod invoke; +// Re-export everything from interface's v1 module +pub use light_sdk_interface::cpi::v1::*; -pub use accounts::CpiAccounts; +// SDK extension: WithLightAccount impl for LightSystemProgramCpi +mod invoke; pub use invoke::LightSystemProgramCpi; - -/// Low-level types and functions for flexible Light system program CPIs. -/// -/// # Main Types -/// -/// For most use cases, you only need: -/// - [`LightSystemProgramCpi`] - Main CPI interface -/// - [`CpiAccounts`] - Account management -/// -/// The remaining types in this module are exported for low-level operations and internal use. -pub mod lowlevel { - pub use super::accounts::{ - get_account_metas_from_config, CpiInstructionConfig, SYSTEM_ACCOUNTS_LEN, - }; -} diff --git a/sdk-libs/sdk/src/cpi/v2/invoke.rs b/sdk-libs/sdk/src/cpi/v2/invoke.rs index e3841fa694..50f1662ad6 100644 --- a/sdk-libs/sdk/src/cpi/v2/invoke.rs +++ b/sdk-libs/sdk/src/cpi/v2/invoke.rs @@ -1,46 +1,19 @@ -use light_compressed_account::instruction_data::{ - compressed_proof::ValidityProof, with_account_info::InstructionDataInvokeCpiWithAccountInfo, +use light_compressed_account::instruction_data::with_account_info::InstructionDataInvokeCpiWithAccountInfo; +use light_compressed_account::instruction_data::with_readonly::{ + InAccount, InstructionDataInvokeCpiWithReadOnly, }; -use light_sdk_types::CpiSigner; -#[cfg(feature = "cpi-context")] -use super::lowlevel::CompressedCpiContext; -use super::lowlevel::{to_account_metas, InAccount, InstructionDataInvokeCpiWithReadOnly}; #[cfg(feature = "poseidon")] use crate::{account::poseidon::LightAccount as LightAccountPoseidon, DataHasher}; use crate::{ account::LightAccount, - cpi::{account::CpiAccountsTrait, instruction::LightCpiInstruction, v2::CpiAccounts}, + cpi::instruction::WithLightAccount, error::LightSdkError, instruction::account_info::CompressedAccountInfoTrait, - AccountInfo, AccountMeta, AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError, + AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError, }; -impl<'info> CpiAccountsTrait<'info> for CpiAccounts<'_, 'info> { - fn to_account_infos(&self) -> Vec> { - self.to_account_infos() - } - - fn to_account_metas(&self) -> Result, ProgramError> { - to_account_metas(self).map_err(ProgramError::from) - } - - fn get_mode(&self) -> Option { - Some(1) // v2 mode - } -} - -impl LightCpiInstruction for InstructionDataInvokeCpiWithReadOnly { - fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self { - Self { - bump: cpi_signer.bump, - invoking_program_id: cpi_signer.program_id.into(), - proof: proof.into(), - mode: 1, - ..Default::default() - } - } - +impl WithLightAccount for InstructionDataInvokeCpiWithReadOnly { fn with_light_account(mut self, account: LightAccount) -> Result where A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default, @@ -152,57 +125,9 @@ impl LightCpiInstruction for InstructionDataInvokeCpiWithReadOnly { Ok(self) } - - #[cfg(feature = "cpi-context")] - fn write_to_cpi_context_first(self) -> Self { - self.write_to_cpi_context_first() - } - - #[cfg(feature = "cpi-context")] - fn write_to_cpi_context_set(self) -> Self { - self.write_to_cpi_context_set() - } - - #[cfg(feature = "cpi-context")] - fn execute_with_cpi_context(self) -> Self { - self.execute_with_cpi_context() - } - - fn get_mode(&self) -> u8 { - self.mode - } - - #[cfg(feature = "cpi-context")] - fn get_with_cpi_context(&self) -> bool { - self.with_cpi_context - } - - #[cfg(feature = "cpi-context")] - fn get_cpi_context(&self) -> &CompressedCpiContext { - &self.cpi_context - } - - fn get_bump(&self) -> u8 { - self.bump - } - - #[cfg(feature = "cpi-context")] - fn has_read_only_accounts(&self) -> bool { - !self.read_only_accounts.is_empty() - } } -impl LightCpiInstruction for InstructionDataInvokeCpiWithAccountInfo { - fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self { - Self { - bump: cpi_signer.bump, - invoking_program_id: cpi_signer.program_id.into(), - proof: proof.into(), - mode: 1, - ..Default::default() - } - } - +impl WithLightAccount for InstructionDataInvokeCpiWithAccountInfo { fn with_light_account(mut self, account: LightAccount) -> Result where A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default, @@ -240,42 +165,4 @@ impl LightCpiInstruction for InstructionDataInvokeCpiWithAccountInfo { self.account_infos.push(account_info); Ok(self) } - - #[cfg(feature = "cpi-context")] - fn write_to_cpi_context_first(self) -> Self { - self.write_to_cpi_context_first() - } - - #[cfg(feature = "cpi-context")] - fn write_to_cpi_context_set(self) -> Self { - self.write_to_cpi_context_set() - } - - #[cfg(feature = "cpi-context")] - fn execute_with_cpi_context(self) -> Self { - self.execute_with_cpi_context() - } - - fn get_mode(&self) -> u8 { - self.mode - } - - #[cfg(feature = "cpi-context")] - fn get_with_cpi_context(&self) -> bool { - self.with_cpi_context - } - - #[cfg(feature = "cpi-context")] - fn get_cpi_context(&self) -> &CompressedCpiContext { - &self.cpi_context - } - - fn get_bump(&self) -> u8 { - self.bump - } - - #[cfg(feature = "cpi-context")] - fn has_read_only_accounts(&self) -> bool { - !self.read_only_accounts.is_empty() - } } diff --git a/sdk-libs/sdk/src/cpi/v2/mod.rs b/sdk-libs/sdk/src/cpi/v2/mod.rs index 06916706f8..8170bb952d 100644 --- a/sdk-libs/sdk/src/cpi/v2/mod.rs +++ b/sdk-libs/sdk/src/cpi/v2/mod.rs @@ -10,163 +10,8 @@ //! //! For maximum flexible light system program CPIs, see the [`lowlevel`] module or use `light-compressed-account` directly. -mod accounts; -#[cfg(feature = "cpi-context")] -mod accounts_cpi_context; -mod invoke; - -pub use accounts::CpiAccounts; -#[cfg(feature = "cpi-context")] -pub use accounts_cpi_context::*; -/// Light system program CPI instruction data builder. -/// -/// Use this builder to construct instructions for compressed account operations: -/// creating, updating, closing accounts, and compressing/decompressing SOL. -/// -/// # Builder Methods -/// -/// ## Common Methods -/// -/// - [`with_light_account()`](crate::cpi::LightCpiInstruction::with_light_account) - Add a compressed account (handles output hashing, and type conversion to instruction data). -/// - [`with_new_addresses()`](crate::cpi::v2::LightSystemProgramCpi::with_new_addresses) - Create new compressed account addresses. -/// - [`with_read_only_addresses()`](crate::cpi::v2::LightSystemProgramCpi::with_read_only_addresses) - Validate that addresses don't exist without creating them. -/// - [`with_read_only_accounts()`](crate::cpi::v2::LightSystemProgramCpi::with_read_only_accounts) - Validate that compressed account state exists without updating it. -/// - [`compress_lamports()`](crate::cpi::v2::LightSystemProgramCpi::compress_lamports) - Compress SOL into compressed accounts. -/// - [`decompress_lamports()`](crate::cpi::v2::LightSystemProgramCpi::decompress_lamports) - Decompress SOL from compressed accounts. -/// -/// **Note**: An instruction can either compress **or** decompress lamports, not both. -/// ## Advanced Methods -/// -/// For fine-grained control, use these low-level methods instead of [`with_light_account()`](crate::cpi::LightCpiInstruction::with_light_account): -/// -/// - [`with_account_infos()`](crate::cpi::v2::LightSystemProgramCpi::with_account_infos) - Manually specify CompressedAccountInfos. -/// -/// # Examples -/// -/// ## Create a compressed account with an address -/// ```rust,no_run -/// # use light_sdk::cpi::{v2::LightSystemProgramCpi, InvokeLightSystemProgram, LightCpiInstruction, CpiSigner}; -/// # use light_sdk::instruction::ValidityProof; -/// # use light_compressed_account::instruction_data::data::NewAddressParamsAssignedPacked; -/// # use light_sdk::{LightAccount, LightDiscriminator}; -/// # use borsh::{BorshSerialize, BorshDeserialize}; -/// # use solana_pubkey::Pubkey; -/// # use solana_program_error::ProgramError; -/// # -/// # const LIGHT_CPI_SIGNER: CpiSigner = CpiSigner { -/// # program_id: [0; 32], -/// # cpi_signer: [0; 32], -/// # bump: 255, -/// # }; -/// # -/// # #[derive(Clone, Debug, Default, LightDiscriminator, BorshSerialize, BorshDeserialize)] -/// # pub struct MyAccount { -/// # pub value: u64, -/// # } -/// # -/// # fn example() -> Result<(), ProgramError> { -/// # let proof = ValidityProof::default(); -/// # let new_address_params = NewAddressParamsAssignedPacked::default(); -/// # let program_id = Pubkey::new_unique(); -/// # let account = LightAccount::::new_init(&program_id, None, 0); -/// # let key = Pubkey::new_unique(); -/// # let owner = Pubkey::default(); -/// # let mut lamports = 0u64; -/// # let mut data = []; -/// # let fee_payer = &solana_account_info::AccountInfo::new( -/// # &key, -/// # true, -/// # true, -/// # &mut lamports, -/// # &mut data, -/// # &owner, -/// # false, -/// # 0, -/// # ); -/// # let cpi_accounts = light_sdk::cpi::v2::CpiAccounts::new(fee_payer, &[], LIGHT_CPI_SIGNER); -/// LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof) -/// .with_new_addresses(&[new_address_params]) -/// .with_light_account(account)? -/// .invoke(cpi_accounts)?; -/// # Ok(()) -/// # } -/// ``` -/// ## Update a compressed account -/// ```rust,no_run -/// # use light_sdk::cpi::{v2::LightSystemProgramCpi, InvokeLightSystemProgram, LightCpiInstruction, CpiSigner}; -/// # use light_sdk::instruction::ValidityProof; -/// # use light_sdk::{LightAccount, LightDiscriminator}; -/// # use light_sdk::instruction::account_meta::CompressedAccountMeta; -/// # use borsh::{BorshSerialize, BorshDeserialize}; -/// # use solana_pubkey::Pubkey; -/// # use solana_program_error::ProgramError; -/// # -/// # const LIGHT_CPI_SIGNER: CpiSigner = CpiSigner { -/// # program_id: [0; 32], -/// # cpi_signer: [0; 32], -/// # bump: 255, -/// # }; -/// # -/// # #[derive(Clone, Debug, Default, LightDiscriminator, BorshSerialize, BorshDeserialize)] -/// # pub struct MyAccount { -/// # pub value: u64, -/// # } -/// # -/// # fn example() -> Result<(), ProgramError> { -/// # let proof = ValidityProof::default(); -/// # let program_id = Pubkey::new_unique(); -/// # let account_meta = CompressedAccountMeta::default(); -/// # let account_data = MyAccount::default(); -/// # let account = LightAccount::::new_mut(&program_id, &account_meta, account_data)?; -/// # let key = Pubkey::new_unique(); -/// # let owner = Pubkey::default(); -/// # let mut lamports = 0u64; -/// # let mut data = []; -/// # let fee_payer = &solana_account_info::AccountInfo::new( -/// # &key, -/// # true, -/// # true, -/// # &mut lamports, -/// # &mut data, -/// # &owner, -/// # false, -/// # 0, -/// # ); -/// # let cpi_accounts = light_sdk::cpi::v2::CpiAccounts::new(fee_payer, &[], LIGHT_CPI_SIGNER); -/// LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof) -/// .with_light_account(account)? -/// .invoke(cpi_accounts)?; -/// # Ok(()) -/// # } -/// ``` -pub use light_compressed_account::instruction_data::with_account_info::InstructionDataInvokeCpiWithAccountInfo as LightSystemProgramCpi; - -/// Low-level types and functions for flexible Light system program CPIs. -/// -/// # Main Types -/// -/// For most use cases, you only need: -/// - [`LightSystemProgramCpi`] - Main CPI interface -/// - [`CpiAccounts`] - Account management -/// -/// The remaining types in this module are exported for low-level operations and internal use. -pub mod lowlevel { +// Re-export everything from interface's v2 module +pub use light_sdk_interface::cpi::v2::*; - /// CPI context for batched compressed account operations. - pub use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; - /// Account information for compressed accounts in CPI operations. - pub use light_compressed_account::instruction_data::with_account_info::CompressedAccountInfo; - /// Input account information for compressed accounts. - pub use light_compressed_account::instruction_data::with_account_info::InAccountInfo; - /// Output account information for compressed accounts. - pub use light_compressed_account::instruction_data::with_account_info::OutAccountInfo; - /// Input compressed account for read-only operations. - pub use light_compressed_account::instruction_data::with_readonly::InAccount; - /// V2 CPI instruction data for read-only compressed account operations. - /// - /// Provides more flexibility for complex operations such as changing the compressed account owner. - /// Most users should use [`crate::cpi::v2::LightSystemProgramCpi`] instead. - pub use light_compressed_account::instruction_data::with_readonly::InstructionDataInvokeCpiWithReadOnly; - - pub use crate::cpi::v2::accounts::to_account_metas; -} +// SDK extension: WithLightAccount impls for v2 instruction types +mod invoke; diff --git a/sdk-libs/sdk/src/error.rs b/sdk-libs/sdk/src/error.rs index bd542cc112..48ee48712e 100644 --- a/sdk-libs/sdk/src/error.rs +++ b/sdk-libs/sdk/src/error.rs @@ -129,6 +129,62 @@ impl From for ProgramError { } } +/// Convert from SDK's LightSdkError to interface's LightPdaError. +/// This allows SDK error types to be used where interface error types are expected +/// (e.g., in trait impls for LightPreInit, LightFinalize, AccountMetasVec). +impl From for light_sdk_interface::error::LightPdaError { + fn from(e: LightSdkError) -> Self { + use light_sdk_interface::error::LightPdaError as InterfaceError; + match e { + LightSdkError::ConstraintViolation => InterfaceError::ConstraintViolation, + LightSdkError::Borsh => InterfaceError::Borsh, + LightSdkError::AccountError(e) => InterfaceError::AccountCheck(e), + LightSdkError::Hasher(e) => InterfaceError::Hasher(e), + LightSdkError::MissingCompressionInfo => InterfaceError::MissingCompressionInfo, + LightSdkError::InvalidRentSponsor => InterfaceError::InvalidRentSponsor, + LightSdkError::ProgramError(e) => InterfaceError::ProgramError(e), + LightSdkError::CpiAccountsIndexOutOfBounds(i) => { + InterfaceError::CpiAccountsIndexOutOfBounds(i) + } + LightSdkError::ReadOnlyAccountsNotSupportedInCpiContext => { + InterfaceError::ReadOnlyAccountsNotSupportedInCpiContext + } + LightSdkError::CompressedAccountError(e) => { + InterfaceError::CompressedAccountError(e) + } + // SDK-specific variants that don't have exact interface equivalents + // are converted to ConstraintViolation as a fallback + _ => InterfaceError::ConstraintViolation, + } + } +} + +/// Convert from interface's LightPdaError to SDK's LightSdkError. +impl From for LightSdkError { + fn from(e: light_sdk_interface::error::LightPdaError) -> Self { + use light_sdk_interface::error::LightPdaError as InterfaceError; + match e { + InterfaceError::ConstraintViolation => LightSdkError::ConstraintViolation, + InterfaceError::Borsh => LightSdkError::Borsh, + InterfaceError::AccountCheck(e) => LightSdkError::AccountError(e), + InterfaceError::Hasher(e) => LightSdkError::Hasher(e), + InterfaceError::MissingCompressionInfo => LightSdkError::MissingCompressionInfo, + InterfaceError::InvalidRentSponsor => LightSdkError::InvalidRentSponsor, + InterfaceError::ProgramError(e) => LightSdkError::ProgramError(e), + InterfaceError::BorshIo(_) => LightSdkError::Borsh, + InterfaceError::CpiAccountsIndexOutOfBounds(i) => { + LightSdkError::CpiAccountsIndexOutOfBounds(i) + } + InterfaceError::ReadOnlyAccountsNotSupportedInCpiContext => { + LightSdkError::ReadOnlyAccountsNotSupportedInCpiContext + } + InterfaceError::CompressedAccountError(e) => { + LightSdkError::CompressedAccountError(e) + } + } + } +} + impl From for LightSdkError { fn from(e: LightSdkTypesError) -> Self { match e { diff --git a/sdk-libs/sdk/src/instruction/mod.rs b/sdk-libs/sdk/src/instruction/mod.rs index 9574cccb22..95cb4ff6e2 100644 --- a/sdk-libs/sdk/src/instruction/mod.rs +++ b/sdk-libs/sdk/src/instruction/mod.rs @@ -3,18 +3,18 @@ //! This module provides types and utilities for building Solana instructions that work with //! compressed accounts. The main workflow involves: //! ```text -//! ├─ 𝐂𝐥𝐢𝐞𝐧𝐭 -//! │ ├─ Get ValidityProof from RPC. -//! │ ├─ pack accounts with PackedAccounts into PackedAddressTreeInfo and PackedStateTreeInfo. -//! │ ├─ pack CompressedAccountMeta. -//! │ ├─ Build Instruction from packed accounts and CompressedAccountMetas. -//! │ └─ Send transaction -//! │ -//! └─ 𝐂𝐮𝐬𝐭𝐨𝐦 𝐏𝐫𝐨𝐠𝐫𝐚𝐦 -//! ├─ use PackedAddressTreeInfo to create a new address. -//! ├─ use CompressedAccountMeta to instantiate a LightAccount struct. -//! │ -//! └─ 𝐋𝐢𝐠𝐡𝐭 𝐒𝐲𝐬𝐭𝐞𝐦 𝐏𝐫𝐨𝐠𝐫𝐚𝐦 𝐂𝐏𝐈 +//! |- Client +//! | |- Get ValidityProof from RPC. +//! | |- pack accounts with PackedAccounts into PackedAddressTreeInfo and PackedStateTreeInfo. +//! | |- pack CompressedAccountMeta. +//! | |- Build Instruction from packed accounts and CompressedAccountMetas. +//! | |_ Send transaction +//! | +//! |_ Custom Program +//! |- use PackedAddressTreeInfo to create a new address. +//! |- use CompressedAccountMeta to instantiate a LightAccount struct. +//! | +//! |_ Light System Program CPI //! ``` //! ## Main Types //! @@ -40,44 +40,24 @@ // TODO: link to examples -// Only available off-chain (client-side) - contains sorting code that exceeds BPF stack limits -#[cfg(not(target_os = "solana"))] -mod pack_accounts; -mod system_accounts; -mod tree_info; - -// Stub type for on-chain compilation - allows trait signatures to compile -// The actual pack methods are never called on-chain -#[cfg(target_os = "solana")] -mod pack_accounts_stub { - use solana_pubkey::Pubkey; +// Re-export PackedAccounts and base instruction types from interface +pub use light_sdk_interface::instruction::*; - /// Stub type for on-chain compilation. The actual implementation with sorting - /// is only available off-chain. This allows trait signatures that reference - /// PackedAccounts to compile on Solana. - pub struct PackedAccounts { - _phantom: core::marker::PhantomData<()>, - } - - impl PackedAccounts { - pub fn insert_or_get(&mut self, _pubkey: Pubkey) -> u8 { - panic!("PackedAccounts::insert_or_get is not available on-chain") - } - - pub fn insert_or_get_read_only(&mut self, _pubkey: Pubkey) -> u8 { - panic!("PackedAccounts::insert_or_get_read_only is not available on-chain") - } - } -} - -/// Zero-knowledge proof to prove the validity of existing compressed accounts and new addresses. +// SDK-specific: ValidityProof and CompressedProof pub use light_compressed_account::instruction_data::compressed_proof::{ CompressedProof, ValidityProof, }; -pub use light_sdk_types::instruction::*; -#[cfg(not(target_os = "solana"))] -pub use pack_accounts::*; -#[cfg(target_os = "solana")] -pub use pack_accounts_stub::PackedAccounts; + +// SDK-specific: system account helpers (depend on find_cpi_signer_macro!) +mod system_accounts; pub use system_accounts::*; + +// SDK-specific: tree info packing/unpacking +mod tree_info; pub use tree_info::*; + +// SDK-specific: PackedAccountsExt extension trait +#[cfg(not(target_os = "solana"))] +mod packed_accounts_ext; +#[cfg(not(target_os = "solana"))] +pub use packed_accounts_ext::*; diff --git a/sdk-libs/sdk/src/instruction/pack_accounts.rs b/sdk-libs/sdk/src/instruction/pack_accounts.rs deleted file mode 100644 index c6c5d4663c..0000000000 --- a/sdk-libs/sdk/src/instruction/pack_accounts.rs +++ /dev/null @@ -1,509 +0,0 @@ -//! Utilities for packing accounts into instruction data. -//! -//! [`PackedAccounts`] is a builder for efficiently organizing accounts into the three categories -//! required for compressed account instructions: -//! 1. **Pre-accounts** - Custom accounts needed before system accounts -//! 2. **System accounts** - Static light system program accounts -//! 3. **Packed accounts** - Dynamically packed accounts (Merkle trees, address trees, queues) with automatic deduplication -//! -//! -//! ## System Account Versioning -//! -//! **`add_system_accounts()` is complementary to [`cpi::v1::CpiAccounts`](crate::cpi::v1::CpiAccounts)** -//! **`add_system_accounts_v2()` is complementary to [`cpi::v2::CpiAccounts`](crate::cpi::v2::CpiAccounts)** -//! -//! Always use the matching version - v1 client-side account packing with v1 program-side CPI, -//! and v2 with v2. Mixing versions will cause account layout mismatches. -//! -//! # Example: Creating a compressed PDA -//! -//! ```rust -//! # use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; -//! # use solana_pubkey::Pubkey; -//! # fn example() -> Result<(), Box> { -//! # let program_id = Pubkey::new_unique(); -//! # let payer_pubkey = Pubkey::new_unique(); -//! # let merkle_tree_pubkey = Pubkey::new_unique(); -//! // Initialize with system accounts -//! let system_account_meta_config = SystemAccountMetaConfig::new(program_id); -//! let mut accounts = PackedAccounts::default(); -//! -//! // Add pre-accounts (signers) -//! accounts.add_pre_accounts_signer(payer_pubkey); -//! -//! // Add Light system program accounts (v2) -//! #[cfg(feature = "v2")] -//! accounts.add_system_accounts_v2(system_account_meta_config)?; -//! #[cfg(not(feature = "v2"))] -//! accounts.add_system_accounts(system_account_meta_config)?; -//! -//! // Add Merkle tree accounts (automatically tracked and deduplicated) -//! let output_merkle_tree_index = accounts.insert_or_get(merkle_tree_pubkey); -//! -//! // Convert to final account metas with offsets -//! let (account_metas, system_accounts_offset, tree_accounts_offset) = accounts.to_account_metas(); -//! # assert_eq!(output_merkle_tree_index, 0); -//! # Ok(()) -//! # } -//! ``` -//! -//! # Account Organization -//! -//! The final account layout is: -//! ```text -//! [pre_accounts] [system_accounts] [packed_accounts] -//! ↑ ↑ ↑ -//! Signers, Light system Merkle trees, -//! fee payer program accts address trees -//! ``` -//! -//! # Automatic Deduplication -//! -//! ```rust -//! # use light_sdk::instruction::PackedAccounts; -//! # use solana_pubkey::Pubkey; -//! let mut accounts = PackedAccounts::default(); -//! let tree_pubkey = Pubkey::new_unique(); -//! let other_tree = Pubkey::new_unique(); -//! -//! // First insertion gets index 0 -//! let index1 = accounts.insert_or_get(tree_pubkey); -//! assert_eq!(index1, 0); -//! -//! // Same tree inserted again returns same index (deduplicated) -//! let index2 = accounts.insert_or_get(tree_pubkey); -//! assert_eq!(index2, 0); -//! -//! // Different tree gets next index -//! let index3 = accounts.insert_or_get(other_tree); -//! assert_eq!(index3, 1); -//! ``` -//! -//! # Building Instructions with Anchor Programs -//! -//! When building instructions for Anchor programs, concatenate your custom accounts with the packed accounts: -//! -//! ```rust,ignore -//! # use anchor_lang::InstructionData; -//! # use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; -//! # use solana_instruction::{AccountMeta, Instruction}; -//! -//! // 1. Set up packed accounts -//! let config = SystemAccountMetaConfig::new(program_id); -//! let mut remaining_accounts = PackedAccounts::default(); -//! remaining_accounts.add_system_accounts(config)?; -//! -//! // 2. Pack tree accounts from proof result -//! let packed_tree_info = proof_result.pack_tree_infos(&mut remaining_accounts); -//! let output_tree_index = state_tree_info.pack_output_tree_index(&mut remaining_accounts)?; -//! -//! // 3. Convert to account metas -//! let (remaining_accounts, _, _) = remaining_accounts.to_account_metas(); -//! -//! // 4. Build instruction: custom accounts first, then remaining_accounts -//! let instruction = Instruction { -//! program_id: your_program::ID, -//! accounts: [ -//! vec![AccountMeta::new(payer.pubkey(), true)], // Your program's accounts -//! // Add other custom accounts here if needed -//! remaining_accounts, // Light system accounts + trees -//! ] -//! .concat(), -//! data: your_program::instruction::YourInstruction { -//! proof: proof_result.proof, -//! address_tree_info: packed_tree_info.address_trees[0], -//! output_tree_index, -//! // ... your other fields -//! } -//! .data(), -//! }; -//! ``` - -use std::collections::HashMap; - -use crate::{ - error::LightSdkError, - instruction::system_accounts::{get_light_system_account_metas, SystemAccountMetaConfig}, - AccountMeta, Pubkey, -}; - -/// Builder to collect accounts for compressed account instructions. -/// -/// Manages three categories of accounts: -/// - **Pre-accounts**: Signers and other custom accounts that come before system accounts. -/// - **System accounts**: Light system program accounts (authority, trees, queues). -/// - **Packed accounts**: Dynamically tracked deduplicted accounts. -/// -/// # Example -/// -/// ```rust -/// # use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; -/// # use solana_pubkey::Pubkey; -/// # fn example() -> Result<(), Box> { -/// # let payer_pubkey = Pubkey::new_unique(); -/// # let program_id = Pubkey::new_unique(); -/// # let merkle_tree_pubkey = Pubkey::new_unique(); -/// let mut accounts = PackedAccounts::default(); -/// -/// // Add signer -/// accounts.add_pre_accounts_signer(payer_pubkey); -/// -/// // Add system accounts (use v2 if feature is enabled) -/// let config = SystemAccountMetaConfig::new(program_id); -/// #[cfg(feature = "v2")] -/// accounts.add_system_accounts_v2(config)?; -/// #[cfg(not(feature = "v2"))] -/// accounts.add_system_accounts(config)?; -/// -/// // Add and track tree accounts -/// let tree_index = accounts.insert_or_get(merkle_tree_pubkey); -/// -/// // Get final account metas -/// let (metas, system_offset, tree_offset) = accounts.to_account_metas(); -/// # assert_eq!(tree_index, 0); -/// # Ok(()) -/// # } -/// ``` -#[derive(Default, Debug)] -pub struct PackedAccounts { - /// Accounts that must come before system accounts (e.g., signers, fee payer). - pub pre_accounts: Vec, - /// Light system program accounts (authority, programs, trees, queues). - system_accounts: Vec, - /// Next available index for packed accounts. - next_index: u8, - /// Map of pubkey to (index, AccountMeta) for deduplication and index tracking. - map: HashMap, - /// Field to sanity check - system_accounts_set: bool, -} - -impl PackedAccounts { - pub fn new_with_system_accounts(config: SystemAccountMetaConfig) -> crate::error::Result { - let mut remaining_accounts = PackedAccounts::default(); - remaining_accounts.add_system_accounts(config)?; - Ok(remaining_accounts) - } - - pub fn system_accounts_set(&self) -> bool { - self.system_accounts_set - } - - pub fn add_pre_accounts_signer(&mut self, pubkey: Pubkey) { - self.pre_accounts.push(AccountMeta { - pubkey, - is_signer: true, - is_writable: false, - }); - } - - pub fn add_pre_accounts_signer_mut(&mut self, pubkey: Pubkey) { - self.pre_accounts.push(AccountMeta { - pubkey, - is_signer: true, - is_writable: true, - }); - } - - pub fn add_pre_accounts_meta(&mut self, account_meta: AccountMeta) { - self.pre_accounts.push(account_meta); - } - - pub fn add_pre_accounts_metas(&mut self, account_metas: &[AccountMeta]) { - self.pre_accounts.extend_from_slice(account_metas); - } - - /// Adds v1 Light system program accounts to the account list. - /// - /// **Use with [`cpi::v1::CpiAccounts`](crate::cpi::v1::CpiAccounts) on the program side.** - /// - /// This adds all the accounts required by the Light system program for v1 operations, - /// including the CPI authority, registered programs, account compression program, and Noop program. - /// - /// # Example - /// - /// ```rust - /// # use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; - /// # use solana_pubkey::Pubkey; - /// # fn example() -> Result<(), Box> { - /// # let program_id = Pubkey::new_unique(); - /// let mut accounts = PackedAccounts::default(); - /// let config = SystemAccountMetaConfig::new(program_id); - /// accounts.add_system_accounts(config)?; - /// # Ok(()) - /// # } - /// ``` - pub fn add_system_accounts( - &mut self, - config: SystemAccountMetaConfig, - ) -> crate::error::Result<()> { - self.system_accounts - .extend(get_light_system_account_metas(config)); - // note cpi context account is part of the system accounts - /* if let Some(pubkey) = config.cpi_context { - if self.next_index != 0 { - return Err(crate::error::LightSdkError::CpiContextOrderingViolation); - } - self.insert_or_get(pubkey); - }*/ - Ok(()) - } - - /// Adds v2 Light system program accounts to the account list. - /// - /// **Use with [`cpi::v2::CpiAccounts`](crate::cpi::v2::CpiAccounts) on the program side.** - /// - /// This adds all the accounts required by the Light system program for v2 operations. - /// V2 uses a different account layout optimized for batched state trees. - /// - /// # Example - /// - /// ```rust - /// # #[cfg(feature = "v2")] - /// # { - /// # use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; - /// # use solana_pubkey::Pubkey; - /// # fn example() -> Result<(), Box> { - /// # let program_id = Pubkey::new_unique(); - /// let mut accounts = PackedAccounts::default(); - /// let config = SystemAccountMetaConfig::new(program_id); - /// accounts.add_system_accounts_v2(config)?; - /// # Ok(()) - /// # } - /// # } - /// ``` - #[cfg(feature = "v2")] - pub fn add_system_accounts_v2( - &mut self, - config: SystemAccountMetaConfig, - ) -> crate::error::Result<()> { - self.system_accounts - .extend(crate::instruction::get_light_system_account_metas_v2( - config, - )); - // note cpi context account is part of the system accounts - /* if let Some(pubkey) = config.cpi_context { - if self.next_index != 0 { - return Err(crate::error::LightSdkError::CpiContextOrderingViolation); - } - self.insert_or_get(pubkey); - }*/ - Ok(()) - } - - /// Returns the index of the provided `pubkey` in the collection. - /// - /// If the provided `pubkey` is not a part of the collection, it gets - /// inserted with a `next_index`. - /// - /// If the privided `pubkey` already exists in the collection, its already - /// existing index is returned. - pub fn insert_or_get(&mut self, pubkey: Pubkey) -> u8 { - self.insert_or_get_config(pubkey, false, true) - } - - pub fn insert_or_get_read_only(&mut self, pubkey: Pubkey) -> u8 { - self.insert_or_get_config(pubkey, false, false) - } - - pub fn insert_or_get_config( - &mut self, - pubkey: Pubkey, - is_signer: bool, - is_writable: bool, - ) -> u8 { - match self.map.get_mut(&pubkey) { - Some((index, entry)) => { - if !entry.is_writable { - entry.is_writable = is_writable; - } - if !entry.is_signer { - entry.is_signer = is_signer; - } - *index - } - None => { - let index = self.next_index; - self.next_index += 1; - self.map.insert( - pubkey, - ( - index, - AccountMeta { - pubkey, - is_signer, - is_writable, - }, - ), - ); - index - } - } - } - - fn hash_set_accounts_to_metas(&self) -> Vec { - let mut packed_accounts = self.map.iter().collect::>(); - // hash maps are not sorted so we need to sort manually and collect into a vector again - packed_accounts.sort_by(|a, b| a.1 .0.cmp(&b.1 .0)); - let packed_accounts = packed_accounts - .iter() - .map(|(_, (_, k))| k.clone()) - .collect::>(); - packed_accounts - } - - fn get_offsets(&self) -> (usize, usize) { - let system_accounts_start_offset = self.pre_accounts.len(); - let packed_accounts_start_offset = - system_accounts_start_offset + self.system_accounts.len(); - (system_accounts_start_offset, packed_accounts_start_offset) - } - - /// Converts the collection of accounts to a vector of - /// [`AccountMeta`](solana_instruction::AccountMeta), which can be used - /// as remaining accounts in instructions or CPI calls. - /// - /// # Returns - /// - /// A tuple of `(account_metas, system_accounts_offset, packed_accounts_offset)`: - /// - `account_metas`: All accounts concatenated in order: `[pre_accounts][system_accounts][packed_accounts]` - /// - `system_accounts_offset`: Index where system accounts start (= pre_accounts.len()) - /// - `packed_accounts_offset`: Index where packed accounts start (= pre_accounts.len() + system_accounts.len()) - /// - /// The `system_accounts_offset` can be used to slice the accounts when creating [`CpiAccounts`](crate::cpi::v1::CpiAccounts): - /// ```ignore - /// let accounts_for_cpi = &ctx.remaining_accounts[system_accounts_offset..]; - /// let cpi_accounts = CpiAccounts::new(fee_payer, accounts_for_cpi, cpi_signer)?; - /// ``` - /// - /// The offset can be hardcoded if your program always has the same pre-accounts layout, or passed - /// as a field in your instruction data. - pub fn to_account_metas(&self) -> (Vec, usize, usize) { - let packed_accounts = self.hash_set_accounts_to_metas(); - let (system_accounts_start_offset, packed_accounts_start_offset) = self.get_offsets(); - ( - [ - self.pre_accounts.clone(), - self.system_accounts.clone(), - packed_accounts, - ] - .concat(), - system_accounts_start_offset, - packed_accounts_start_offset, - ) - } - - pub fn packed_pubkeys(&self) -> Vec { - self.hash_set_accounts_to_metas() - .iter() - .map(|meta| meta.pubkey) - .collect() - } - - pub fn add_custom_system_accounts( - &mut self, - accounts: T, - ) -> crate::error::Result<()> { - accounts.get_account_metas_vec(self) - } -} - -pub trait AccountMetasVec { - fn get_account_metas_vec(&self, accounts: &mut PackedAccounts) -> Result<(), LightSdkError>; -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_remaining_accounts() { - let mut remaining_accounts = PackedAccounts::default(); - - let pubkey_1 = Pubkey::new_unique(); - let pubkey_2 = Pubkey::new_unique(); - let pubkey_3 = Pubkey::new_unique(); - let pubkey_4 = Pubkey::new_unique(); - - // Initial insertion. - assert_eq!(remaining_accounts.insert_or_get(pubkey_1), 0); - assert_eq!(remaining_accounts.insert_or_get(pubkey_2), 1); - assert_eq!(remaining_accounts.insert_or_get(pubkey_3), 2); - - assert_eq!( - remaining_accounts.to_account_metas().0.as_slice(), - &[ - AccountMeta { - pubkey: pubkey_1, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_2, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_3, - is_signer: false, - is_writable: true, - } - ] - ); - - // Insertion of already existing pubkeys. - assert_eq!(remaining_accounts.insert_or_get(pubkey_1), 0); - assert_eq!(remaining_accounts.insert_or_get(pubkey_2), 1); - assert_eq!(remaining_accounts.insert_or_get(pubkey_3), 2); - - assert_eq!( - remaining_accounts.to_account_metas().0.as_slice(), - &[ - AccountMeta { - pubkey: pubkey_1, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_2, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_3, - is_signer: false, - is_writable: true, - } - ] - ); - - // Again, initial insertion. - assert_eq!(remaining_accounts.insert_or_get(pubkey_4), 3); - - assert_eq!( - remaining_accounts.to_account_metas().0.as_slice(), - &[ - AccountMeta { - pubkey: pubkey_1, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_2, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_3, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_4, - is_signer: false, - is_writable: true, - } - ] - ); - } -} diff --git a/sdk-libs/sdk/src/instruction/packed_accounts_ext.rs b/sdk-libs/sdk/src/instruction/packed_accounts_ext.rs new file mode 100644 index 0000000000..a02eb7186c --- /dev/null +++ b/sdk-libs/sdk/src/instruction/packed_accounts_ext.rs @@ -0,0 +1,66 @@ +use light_sdk_interface::instruction::PackedAccounts; + +use super::system_accounts::{get_light_system_account_metas, SystemAccountMetaConfig}; + +/// Extension trait adding Light system account helpers to [`PackedAccounts`]. +/// +/// These methods depend on [`SystemAccountMetaConfig`] and `find_cpi_signer_macro!` +/// which are SDK-specific (use CPI signer derivation). +pub trait PackedAccountsExt { + /// Creates a new [`PackedAccounts`] with v1 system accounts pre-configured. + /// + /// **Use with [`cpi::v1::CpiAccounts`](crate::cpi::v1::CpiAccounts) on the program side.** + fn new_with_system_accounts(config: SystemAccountMetaConfig) -> crate::error::Result + where + Self: Sized; + + /// Adds v1 Light system program accounts to the account list. + /// + /// **Use with [`cpi::v1::CpiAccounts`](crate::cpi::v1::CpiAccounts) on the program side.** + /// + /// This adds all the accounts required by the Light system program for v1 operations, + /// including the CPI authority, registered programs, account compression program, and Noop program. + fn add_system_accounts( + &mut self, + config: SystemAccountMetaConfig, + ) -> crate::error::Result<()>; + + /// Adds v2 Light system program accounts to the account list. + /// + /// **Use with [`cpi::v2::CpiAccounts`](crate::cpi::v2::CpiAccounts) on the program side.** + /// + /// This adds all the accounts required by the Light system program for v2 operations. + /// V2 uses a different account layout optimized for batched state trees. + #[cfg(feature = "v2")] + fn add_system_accounts_v2( + &mut self, + config: SystemAccountMetaConfig, + ) -> crate::error::Result<()>; +} + +impl PackedAccountsExt for PackedAccounts { + fn new_with_system_accounts(config: SystemAccountMetaConfig) -> crate::error::Result { + let mut accounts = PackedAccounts::default(); + accounts.add_system_accounts(config)?; + Ok(accounts) + } + + fn add_system_accounts( + &mut self, + config: SystemAccountMetaConfig, + ) -> crate::error::Result<()> { + self.add_system_accounts_raw(get_light_system_account_metas(config)); + Ok(()) + } + + #[cfg(feature = "v2")] + fn add_system_accounts_v2( + &mut self, + config: SystemAccountMetaConfig, + ) -> crate::error::Result<()> { + self.add_system_accounts_raw( + super::system_accounts::get_light_system_account_metas_v2(config), + ); + Ok(()) + } +} diff --git a/sdk-libs/sdk/src/lib.rs b/sdk-libs/sdk/src/lib.rs index c4bad6d8a3..8f44d4c00b 100644 --- a/sdk-libs/sdk/src/lib.rs +++ b/sdk-libs/sdk/src/lib.rs @@ -164,9 +164,10 @@ pub mod transfer; pub mod utils; pub use proof::borsh_compat; -pub mod interface; -/// Backward-compat alias -pub use interface as compressible; +/// Backward-compat alias for the interface module +pub use light_sdk_interface as compressible; +/// Re-export the interface crate +pub use light_sdk_interface as interface; #[cfg(feature = "merkle-tree")] pub mod merkle_tree; @@ -203,10 +204,11 @@ pub mod sdk_types { use anchor_lang::{AnchorDeserialize, AnchorSerialize}; #[cfg(not(feature = "anchor"))] use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; +// Re-export interface types from light-sdk-interface // Pack trait is only available off-chain (client-side) - uses PackedAccounts #[cfg(not(target_os = "solana"))] -pub use interface::Pack; -pub use interface::{ +pub use light_sdk_interface::Pack; +pub use light_sdk_interface::{ process_initialize_light_config, process_initialize_light_config_checked, process_update_light_config, CompressAs, CompressedInitSpace, CompressionInfo, HasCompressionInfo, LightConfig, Space, Unpack, COMPRESSIBLE_CONFIG_SEED, @@ -224,11 +226,15 @@ pub use light_sdk_macros::{ }; pub use light_sdk_types::{constants, instruction::PackedAddressTreeInfoExt, CpiSigner}; use solana_account_info::AccountInfo; -use solana_cpi::invoke_signed; -use solana_instruction::{AccountMeta, Instruction}; +use solana_instruction::AccountMeta; use solana_program_error::ProgramError; use solana_pubkey::Pubkey; +// Re-export SDK extension traits +pub use crate::cpi::WithLightAccount; +#[cfg(not(target_os = "solana"))] +pub use crate::instruction::PackedAccountsExt; + pub trait PubkeyTrait { fn to_solana_pubkey(&self) -> Pubkey; fn to_array(&self) -> [u8; 32]; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/failing_tests.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/failing_tests.rs index 05e8ab04b7..59cb380ccf 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/failing_tests.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/failing_tests.rs @@ -7,7 +7,8 @@ //! - 8: MissingRequiredSignature //! - 11: NotEnoughAccountKeys //! - 14: InvalidSeeds -//! - 16001: LightSdkError::ConstraintViolation +//! - 17001: LightPdaError::ConstraintViolation +//! - 17004: LightPdaError::InvalidRentSponsor mod shared; @@ -208,8 +209,8 @@ async fn test_pda_wrong_rent_sponsor() { .create_and_send_transaction(&decompress_instructions, &ctx.payer.pubkey(), &[&ctx.payer]) .await; - // Should fail with InvalidRentSponsor (16050) - assert_rpc_error(result, 0, 16050).unwrap(); + // Should fail with InvalidRentSponsor (17004) + assert_rpc_error(result, 0, 17004).unwrap(); } /// Test: Double decompression should be a noop (idempotent). @@ -295,7 +296,7 @@ async fn test_pda_double_decompress_is_noop() { ); } -/// Test: Wrong config PDA should fail with ConstraintViolation (16001). +/// Test: Wrong config PDA should fail with InvalidAccountData (3). /// Validates config check in config.rs:144-153. #[tokio::test] async fn test_pda_wrong_config() { @@ -438,7 +439,7 @@ async fn test_token_accounts_offset_invalid() { // ============================================================================= /// Test: Removing required accounts should fail. -/// Error code 16031 is LightSdkError::CpiAccountsMissing. +/// Error code 11 is NotEnoughAccountKeys. #[tokio::test] async fn test_missing_system_accounts() { let mut ctx = FailingTestContext::new().await; From 235bb90f69855c1ba088775f9a4d09be1e02d491 Mon Sep 17 00:00:00 2001 From: ananas Date: Sun, 1 Feb 2026 21:34:30 +0000 Subject: [PATCH 02/15] sdk interface compiles --- Cargo.lock | 53 +- Cargo.toml | 1 + program-libs/account-checks/Cargo.toml | 9 +- .../src/account_info/account_info_trait.rs | 89 + .../src/account_info/account_meta_trait.rs | 14 + .../account-checks/src/account_info/mod.rs | 1 + .../src/account_info/pinocchio.rs | 220 +++ .../account-checks/src/account_info/solana.rs | 205 ++ .../account-checks/src/close_account.rs | 31 + program-libs/account-checks/src/error.rs | 3 + program-libs/account-checks/src/lib.rs | 5 +- sdk-libs/client/src/interface/instructions.rs | 36 +- sdk-libs/macros/src/lib.rs | 36 + .../src/light_pdas/light_account_keywords.rs | 21 +- .../src/light_pdas/parsing/crate_context.rs | 8 + .../macros/src/light_pdas/program/compress.rs | 58 + .../src/light_pdas/program/decompress.rs | 31 + .../program/derive_light_program.rs | 1702 +++++++++++++++++ .../src/light_pdas/program/instructions.rs | 234 +-- sdk-libs/macros/src/light_pdas/program/mod.rs | 4 +- sdk-libs/sdk-interface/Cargo.toml | 68 +- .../src/.backup/account/token_seeds.rs | 261 +++ .../src/{ => .backup}/accounts/create_pda.rs | 0 .../sdk-interface/src/.backup/cpi/account.rs | 85 + .../sdk-interface/src/.backup/cpi/invoke.rs | 187 ++ .../src/{ => .backup}/cpi/v1/accounts.rs | 0 .../src/{ => .backup}/cpi/v1/invoke.rs | 0 .../src/{ => .backup}/cpi/v1/mod.rs | 0 .../src/{ => .backup}/cpi/v2/accounts.rs | 0 .../cpi/v2/accounts_cpi_context.rs | 0 .../src/{ => .backup}/cpi/v2/invoke.rs | 0 .../src/{ => .backup}/cpi/v2/mod.rs | 0 .../src/.backup/program/config/create.rs | 305 +++ .../src/.backup/program/config/update.rs | 102 + .../decompression/create_token_account.rs | 160 ++ .../src/.backup/program/decompression/mod.rs | 13 + .../src/.backup/program/decompression/pda.rs | 181 ++ .../program/decompression/processor.rs | 400 ++++ .../.backup/program/decompression/token.rs | 149 ++ .../src/.backup/program/variant.rs | 187 ++ .../src/account/compression_info.rs | 145 +- .../src/account/light_account.rs | 30 +- sdk-libs/sdk-interface/src/account/mod.rs | 7 +- sdk-libs/sdk-interface/src/account/pack.rs | 22 +- .../sdk-interface/src/account/pda_seeds.rs | 9 +- .../sdk-interface/src/account/token_seeds.rs | 199 +- .../sdk-interface/src/accounts/finalize.rs | 14 +- .../src/accounts/init_compressed_account.rs | 184 +- sdk-libs/sdk-interface/src/accounts/mod.rs | 1 - sdk-libs/sdk-interface/src/cpi/account.rs | 179 +- sdk-libs/sdk-interface/src/cpi/impls.rs | 88 + sdk-libs/sdk-interface/src/cpi/instruction.rs | 11 +- sdk-libs/sdk-interface/src/cpi/invoke.rs | 217 +-- sdk-libs/sdk-interface/src/cpi/mod.rs | 20 +- sdk-libs/sdk-interface/src/error.rs | 44 +- sdk-libs/sdk-interface/src/instruction/mod.rs | 34 +- .../src/instruction/pack_accounts.rs | 109 +- sdk-libs/sdk-interface/src/lib.rs | 165 +- .../src/program/compression/close.rs | 47 +- .../src/program/compression/mod.rs | 6 +- .../src/program/compression/pda.rs | 56 +- .../src/program/compression/processor.rs | 204 +- .../src/program/config/create.rs | 356 ++-- .../sdk-interface/src/program/config/mod.rs | 130 +- .../sdk-interface/src/program/config/state.rs | 130 +- .../src/program/config/update.rs | 74 +- .../decompression/create_token_account.rs | 213 ++- .../src/program/decompression/mod.rs | 13 +- .../src/program/decompression/pda.rs | 113 +- .../src/program/decompression/processor.rs | 613 +++--- .../src/program/decompression/token.rs | 121 +- sdk-libs/sdk-interface/src/program/mod.rs | 4 +- .../sdk-interface/src/program/validation.rs | 141 +- sdk-libs/sdk-interface/src/program/variant.rs | 275 +-- sdk-libs/sdk-types/src/cpi_accounts/v2.rs | 2 + sdk-libs/sdk/Cargo.toml | 4 +- sdk-libs/sdk/src/cpi/instruction.rs | 52 - sdk-libs/sdk/src/cpi/mod.rs | 110 +- sdk-libs/sdk/src/cpi/v1/invoke.rs | 10 +- sdk-libs/sdk/src/cpi/v1/mod.rs | 2 +- sdk-libs/sdk/src/cpi/v2/invoke.rs | 14 +- sdk-libs/sdk/src/cpi/v2/mod.rs | 2 +- sdk-libs/sdk/src/lib.rs | 12 +- .../manual-test/src/derived_light_config.rs | 32 +- sdk-tests/manual-test/src/lib.rs | 4 +- sdk-tests/single-pda-derive-test/Cargo.toml | 61 + .../src/instruction_accounts.rs | 445 +++++ sdk-tests/single-pda-derive-test/src/lib.rs | 205 ++ sdk-tests/single-pda-derive-test/src/state.rs | 25 + .../single-pda-derive-test/tests/test.rs | 943 +++++++++ 90 files changed, 8462 insertions(+), 2294 deletions(-) create mode 100644 program-libs/account-checks/src/account_info/account_meta_trait.rs create mode 100644 program-libs/account-checks/src/close_account.rs create mode 100644 sdk-libs/macros/src/light_pdas/program/derive_light_program.rs create mode 100644 sdk-libs/sdk-interface/src/.backup/account/token_seeds.rs rename sdk-libs/sdk-interface/src/{ => .backup}/accounts/create_pda.rs (100%) create mode 100644 sdk-libs/sdk-interface/src/.backup/cpi/account.rs create mode 100644 sdk-libs/sdk-interface/src/.backup/cpi/invoke.rs rename sdk-libs/sdk-interface/src/{ => .backup}/cpi/v1/accounts.rs (100%) rename sdk-libs/sdk-interface/src/{ => .backup}/cpi/v1/invoke.rs (100%) rename sdk-libs/sdk-interface/src/{ => .backup}/cpi/v1/mod.rs (100%) rename sdk-libs/sdk-interface/src/{ => .backup}/cpi/v2/accounts.rs (100%) rename sdk-libs/sdk-interface/src/{ => .backup}/cpi/v2/accounts_cpi_context.rs (100%) rename sdk-libs/sdk-interface/src/{ => .backup}/cpi/v2/invoke.rs (100%) rename sdk-libs/sdk-interface/src/{ => .backup}/cpi/v2/mod.rs (100%) create mode 100644 sdk-libs/sdk-interface/src/.backup/program/config/create.rs create mode 100644 sdk-libs/sdk-interface/src/.backup/program/config/update.rs create mode 100644 sdk-libs/sdk-interface/src/.backup/program/decompression/create_token_account.rs create mode 100644 sdk-libs/sdk-interface/src/.backup/program/decompression/mod.rs create mode 100644 sdk-libs/sdk-interface/src/.backup/program/decompression/pda.rs create mode 100644 sdk-libs/sdk-interface/src/.backup/program/decompression/processor.rs create mode 100644 sdk-libs/sdk-interface/src/.backup/program/decompression/token.rs create mode 100644 sdk-libs/sdk-interface/src/.backup/program/variant.rs create mode 100644 sdk-libs/sdk-interface/src/cpi/impls.rs delete mode 100644 sdk-libs/sdk/src/cpi/instruction.rs create mode 100644 sdk-tests/single-pda-derive-test/Cargo.toml create mode 100644 sdk-tests/single-pda-derive-test/src/instruction_accounts.rs create mode 100644 sdk-tests/single-pda-derive-test/src/lib.rs create mode 100644 sdk-tests/single-pda-derive-test/src/state.rs create mode 100644 sdk-tests/single-pda-derive-test/tests/test.rs diff --git a/Cargo.lock b/Cargo.lock index bb2e70f56e..562ac31e5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3466,11 +3466,15 @@ version = "0.7.0" dependencies = [ "borsh 0.10.4", "pinocchio", + "pinocchio-system", "rand 0.8.5", "solana-account-info", + "solana-cpi", + "solana-instruction", "solana-msg 2.2.1", "solana-program-error 2.2.2", "solana-pubkey 2.4.0", + "solana-system-interface 1.0.0", "solana-sysvar", "thiserror 2.0.17", ] @@ -4083,8 +4087,6 @@ dependencies = [ name = "light-sdk-interface" version = "0.19.0" dependencies = [ - "anchor-lang", - "bincode", "borsh 0.10.4", "bytemuck", "light-account-checks", @@ -4093,18 +4095,6 @@ dependencies = [ "light-hasher", "light-sdk-types", "light-token-interface", - "pinocchio", - "pinocchio-system", - "solana-account-info", - "solana-clock", - "solana-cpi", - "solana-instruction", - "solana-loader-v3-interface", - "solana-msg 2.2.1", - "solana-program-error 2.2.2", - "solana-pubkey 2.4.0", - "solana-system-interface 1.0.0", - "solana-sysvar", "thiserror 2.0.17", ] @@ -6653,6 +6643,41 @@ dependencies = [ "tokio", ] +[[package]] +name = "single-pda-derive-test" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "borsh 0.10.4", + "bytemuck", + "light-anchor-spl", + "light-client", + "light-compressed-account", + "light-compressible", + "light-hasher", + "light-heap", + "light-macros", + "light-program-test", + "light-sdk", + "light-sdk-interface", + "light-sdk-macros", + "light-sdk-types", + "light-test-utils", + "light-token", + "light-token-interface", + "light-token-types", + "solana-account-info", + "solana-instruction", + "solana-keypair", + "solana-msg 2.2.1", + "solana-program", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "solana-sdk", + "solana-signer", + "tokio", +] + [[package]] name = "single-pda-test" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index b1d78ac96e..92f83dd1df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ members = [ "sdk-tests/csdk-anchor-full-derived-test-sdk", "sdk-tests/single-mint-test", "sdk-tests/single-pda-test", + "sdk-tests/single-pda-derive-test", "sdk-tests/single-account-loader-test", "sdk-tests/single-ata-test", "sdk-tests/single-token-test", diff --git a/program-libs/account-checks/Cargo.toml b/program-libs/account-checks/Cargo.toml index 89290c8671..9dd8fd7653 100644 --- a/program-libs/account-checks/Cargo.toml +++ b/program-libs/account-checks/Cargo.toml @@ -15,11 +15,14 @@ solana = [ "solana-sysvar", "solana-account-info", "solana-pubkey", + "solana-cpi", + "solana-system-interface", + "solana-instruction", "msg", "std" ] msg = ["dep:solana-msg"] -pinocchio = ["dep:pinocchio"] +pinocchio = ["dep:pinocchio", "dep:pinocchio-system"] test-only = ["dep:rand", "std"] [dependencies] @@ -31,7 +34,11 @@ solana-pubkey = { workspace = true, optional = true, features = [ "sha2", ] } solana-msg = { workspace = true, optional = true } +solana-cpi = { workspace = true, optional = true } +solana-instruction = { workspace = true, optional = true } +solana-system-interface = { workspace = true, optional = true, features = ["bincode"] } pinocchio = { workspace = true, optional = true } +pinocchio-system = { workspace = true, optional = true } thiserror = { workspace = true } rand = { workspace = true, optional = true } diff --git a/program-libs/account-checks/src/account_info/account_info_trait.rs b/program-libs/account-checks/src/account_info/account_info_trait.rs index d2efbdaee0..632e9667f1 100644 --- a/program-libs/account-checks/src/account_info/account_info_trait.rs +++ b/program-libs/account-checks/src/account_info/account_info_trait.rs @@ -5,6 +5,17 @@ use core::{ use crate::error::AccountError; +/// Lightweight owned account metadata for CPI instruction building. +/// +/// Replaces solana_instruction::AccountMeta / pinocchio::instruction::AccountMeta +/// as a framework-agnostic type that can be stored in collections without lifetime issues. +#[derive(Clone, Debug)] +pub struct CpiMeta { + pub pubkey: [u8; 32], + pub is_signer: bool, + pub is_writable: bool, +} + /// Trait to abstract over different AccountInfo implementations (pinocchio vs solana) pub trait AccountInfoTrait { type Pubkey: Copy + Clone + Debug + PartialEq; @@ -49,7 +60,85 @@ pub trait AccountInfoTrait { /// Only meaningful on-chain; implementations may error off-chain. fn get_current_slot() -> Result; + /// Assign the account to a new owner program. + fn assign(&self, new_owner: &[u8; 32]) -> Result<(), AccountError>; + + /// Resize the account data (truncating or zero-extending). + fn realloc(&self, new_len: usize, zero_init: bool) -> Result<(), AccountError>; + + /// Subtract lamports from the account (checked). + fn sub_lamports(&self, amount: u64) -> Result<(), AccountError>; + + /// Add lamports to the account (checked). + fn add_lamports(&self, amount: u64) -> Result<(), AccountError>; + fn data_is_empty(&self) -> bool { self.data_len() == 0 } + + /// Close this account: zero data, transfer all lamports to destination, + /// assign to system program. + fn close(&self, destination: &Self) -> Result<(), AccountError>; + + /// Create a PDA account via system program CPI (invoke_signed). + /// + /// `self` is the uninitialized PDA account to be created. + /// Handles the edge case where the account already has lamports + /// (e.g. attacker donation) by falling back to Assign + Allocate + Transfer. + /// + /// # Arguments + /// * `lamports` - Amount of lamports for rent-exemption + /// * `space` - Size of the account data in bytes + /// * `owner` - Program that will own the created account + /// * `pda_seeds` - Seeds for this PDA (including bump) for signing + /// * `rent_payer` - Account paying for rent + /// * `rent_payer_seeds` - Seeds for the rent payer PDA for signing + /// * `system_program` - The system program account + #[allow(clippy::too_many_arguments)] + fn create_pda_account( + &self, + lamports: u64, + space: u64, + owner: &[u8; 32], + pda_seeds: &[&[u8]], + rent_payer: &Self, + rent_payer_seeds: &[&[u8]], + system_program: &Self, + ) -> Result<(), AccountError>; + + /// Transfer lamports by direct lamport manipulation (no CPI). + fn transfer_lamports(&self, destination: &Self, lamports: u64) -> Result<(), AccountError> { + self.sub_lamports(lamports)?; + destination.add_lamports(lamports) + } + + /// Transfer lamports via system program CPI with invoke_signed. + /// Pass `&[]` for `signer_seeds` if the sender is already a signer. + fn transfer_lamports_cpi( + &self, + destination: &Self, + lamports: u64, + signer_seeds: &[&[u8]], + ) -> Result<(), AccountError>; + + /// Invoke an arbitrary program via CPI with optional PDA signing. + /// + /// This is the generic CPI entry point. It builds a native instruction + /// from the decomposed components and calls the runtime's invoke_signed. + /// + /// # Arguments + /// * `program_id` - Target program to invoke + /// * `instruction_data` - Serialized instruction data + /// * `account_metas` - Account metadata describing each account's role + /// * `account_infos` - The actual account info objects (must match metas) + /// * `signer_seeds` - PDA signer seeds; pass `&[]` for no PDA signing + fn invoke_cpi( + program_id: &[u8; 32], + instruction_data: &[u8], + account_metas: &[CpiMeta], + account_infos: &[Self], + signer_seeds: &[&[&[u8]]], + ) -> Result<(), AccountError> + where + Self: Sized; } diff --git a/program-libs/account-checks/src/account_info/account_meta_trait.rs b/program-libs/account-checks/src/account_info/account_meta_trait.rs new file mode 100644 index 0000000000..51fdaf18cd --- /dev/null +++ b/program-libs/account-checks/src/account_info/account_meta_trait.rs @@ -0,0 +1,14 @@ +use core::fmt::Debug; + +/// Trait abstracting over AccountMeta implementations (solana vs pinocchio). +/// +/// Uses `[u8; 32]` for pubkeys (same pattern as AccountInfoTrait::key()). +/// Implementations convert to/from native types internally. +pub trait AccountMetaTrait: Clone + Debug { + fn new(pubkey: [u8; 32], is_signer: bool, is_writable: bool) -> Self; + fn pubkey_bytes(&self) -> [u8; 32]; + fn is_signer(&self) -> bool; + fn is_writable(&self) -> bool; + fn set_is_signer(&mut self, val: bool); + fn set_is_writable(&mut self, val: bool); +} diff --git a/program-libs/account-checks/src/account_info/mod.rs b/program-libs/account-checks/src/account_info/mod.rs index d86788da66..c14ee41695 100644 --- a/program-libs/account-checks/src/account_info/mod.rs +++ b/program-libs/account-checks/src/account_info/mod.rs @@ -1,4 +1,5 @@ pub mod account_info_trait; +pub mod account_meta_trait; #[cfg(feature = "pinocchio")] pub mod pinocchio; #[cfg(feature = "solana")] diff --git a/program-libs/account-checks/src/account_info/pinocchio.rs b/program-libs/account-checks/src/account_info/pinocchio.rs index 03c5f07704..3b89dccaca 100644 --- a/program-libs/account-checks/src/account_info/pinocchio.rs +++ b/program-libs/account-checks/src/account_info/pinocchio.rs @@ -1,6 +1,48 @@ use super::account_info_trait::AccountInfoTrait; +use super::account_meta_trait::AccountMetaTrait; use crate::error::AccountError; +/// Owned account meta for pinocchio. +/// +/// Pinocchio's native `AccountMeta<'a>` borrows the pubkey, so we need an +/// owned wrapper that can be stored in collections. +#[derive(Clone, Debug)] +pub struct OwnedAccountMeta { + pub pubkey: [u8; 32], + pub is_signer: bool, + pub is_writable: bool, +} + +impl AccountMetaTrait for OwnedAccountMeta { + fn new(pubkey: [u8; 32], is_signer: bool, is_writable: bool) -> Self { + Self { + pubkey, + is_signer, + is_writable, + } + } + + fn pubkey_bytes(&self) -> [u8; 32] { + self.pubkey + } + + fn is_signer(&self) -> bool { + self.is_signer + } + + fn is_writable(&self) -> bool { + self.is_writable + } + + fn set_is_signer(&mut self, val: bool) { + self.is_signer = val; + } + + fn set_is_writable(&mut self, val: bool) { + self.is_writable = val; + } +} + /// Implement trait for pinocchio AccountInfo impl AccountInfoTrait for pinocchio::account_info::AccountInfo { type Pubkey = [u8; 32]; @@ -141,4 +183,182 @@ impl AccountInfoTrait for pinocchio::account_info::AccountInfo { Err(AccountError::FailedSysvarAccess) } } + + fn assign(&self, new_owner: &[u8; 32]) -> Result<(), AccountError> { + // SAFETY: We trust the caller to provide a valid owner. + // This is safe in the Solana runtime context where the runtime + // validates ownership changes. + unsafe { + self.assign(&pinocchio::pubkey::Pubkey::from(*new_owner)); + } + Ok(()) + } + + fn realloc(&self, new_len: usize, _zero_init: bool) -> Result<(), AccountError> { + self.resize(new_len).map_err(|e| AccountError::from(e)) + } + + fn sub_lamports(&self, amount: u64) -> Result<(), AccountError> { + let mut lamports = self + .try_borrow_mut_lamports() + .map_err(AccountError::from)?; + *lamports = lamports + .checked_sub(amount) + .ok_or(AccountError::ArithmeticOverflow)?; + Ok(()) + } + + fn add_lamports(&self, amount: u64) -> Result<(), AccountError> { + let mut lamports = self + .try_borrow_mut_lamports() + .map_err(AccountError::from)?; + *lamports = lamports + .checked_add(amount) + .ok_or(AccountError::ArithmeticOverflow)?; + Ok(()) + } + + fn close(&self, destination: &Self) -> Result<(), AccountError> { + crate::close_account::close_account(self, destination) + } + + #[inline(never)] + fn create_pda_account( + &self, + lamports: u64, + space: u64, + owner: &[u8; 32], + pda_seeds: &[&[u8]], + rent_payer: &Self, + rent_payer_seeds: &[&[u8]], + _system_program: &Self, + ) -> Result<(), AccountError> { + extern crate alloc; + use alloc::vec::Vec; + use pinocchio::instruction::{Seed, Signer}; + + let pda_seeds_vec: Vec = pda_seeds.iter().map(|s| Seed::from(*s)).collect(); + let pda_signer = Signer::from(&pda_seeds_vec[..]); + + let payer_seeds_vec: Vec = + rent_payer_seeds.iter().map(|s| Seed::from(*s)).collect(); + let payer_signer = Signer::from(&payer_seeds_vec[..]); + + // Cold path: account already has lamports (e.g., attacker donation). + // CreateAccount would fail, so use Assign + Allocate + Transfer. + if self.lamports() > 0 { + pinocchio_system::instructions::Assign { + account: self, + owner, + } + .invoke_signed(&[pda_signer.clone()]) + .map_err(AccountError::from)?; + + pinocchio_system::instructions::Allocate { + account: self, + space, + } + .invoke_signed(&[pda_signer]) + .map_err(AccountError::from)?; + + let current_lamports = self.lamports(); + if lamports > current_lamports { + pinocchio_system::instructions::Transfer { + from: rent_payer, + to: self, + lamports: lamports - current_lamports, + } + .invoke_signed(&[payer_signer]) + .map_err(AccountError::from)?; + } + + return Ok(()); + } + + // Normal path: CreateAccount + pinocchio_system::instructions::CreateAccount { + from: rent_payer, + to: self, + lamports, + space, + owner, + } + .invoke_signed(&[payer_signer, pda_signer]) + .map_err(AccountError::from) + } + + fn transfer_lamports_cpi( + &self, + destination: &Self, + lamports: u64, + signer_seeds: &[&[u8]], + ) -> Result<(), AccountError> { + extern crate alloc; + use alloc::vec::Vec; + use pinocchio::instruction::{Seed, Signer}; + + let seeds_vec: Vec = signer_seeds.iter().map(|s| Seed::from(*s)).collect(); + let signer = Signer::from(&seeds_vec[..]); + + pinocchio_system::instructions::Transfer { + from: self, + to: destination, + lamports, + } + .invoke_signed(&[signer]) + .map_err(AccountError::from) + } + + fn invoke_cpi( + program_id: &[u8; 32], + instruction_data: &[u8], + account_metas: &[super::account_info_trait::CpiMeta], + account_infos: &[Self], + signer_seeds: &[&[&[u8]]], + ) -> Result<(), AccountError> { + extern crate alloc; + use alloc::vec::Vec; + use pinocchio::instruction::{AccountMeta, Seed, Signer}; + + // Build owned pubkeys so AccountMeta can borrow them + let pubkeys: Vec = account_metas + .iter() + .map(|m| pinocchio::pubkey::Pubkey::from(m.pubkey)) + .collect(); + + // Build pinocchio AccountMetas referencing the owned pubkeys + let metas: Vec> = account_metas + .iter() + .zip(pubkeys.iter()) + .map(|(m, pk)| AccountMeta::new(pk, m.is_writable, m.is_signer)) + .collect(); + + let program_pubkey = pinocchio::pubkey::Pubkey::from(*program_id); + let instruction = pinocchio::instruction::Instruction { + program_id: &program_pubkey, + accounts: &metas, + data: instruction_data, + }; + + // Build account info refs + let info_refs: Vec<&pinocchio::account_info::AccountInfo> = + account_infos.iter().collect(); + + // Build signers from seeds + let signer_seed_vecs: Vec> = signer_seeds + .iter() + .map(|seeds| seeds.iter().map(|s| Seed::from(*s)).collect()) + .collect(); + let signers: Vec = signer_seed_vecs + .iter() + .map(|seeds| Signer::from(&seeds[..])) + .collect(); + + pinocchio::cpi::invoke_signed_with_bounds::<64>( + &instruction, + &info_refs, + &signers, + ) + .map_err(AccountError::from) + } } diff --git a/program-libs/account-checks/src/account_info/solana.rs b/program-libs/account-checks/src/account_info/solana.rs index dfc8254b2b..6a931dbe4e 100644 --- a/program-libs/account-checks/src/account_info/solana.rs +++ b/program-libs/account-checks/src/account_info/solana.rs @@ -1,4 +1,5 @@ use super::account_info_trait::AccountInfoTrait; +use super::account_meta_trait::AccountMetaTrait; use crate::error::AccountError; /// Implement trait for solana AccountInfo @@ -92,4 +93,208 @@ impl AccountInfoTrait for solana_account_info::AccountInfo<'_> { .map(|c| c.slot) .map_err(|_| AccountError::FailedSysvarAccess) } + + fn assign(&self, new_owner: &[u8; 32]) -> Result<(), AccountError> { + self.assign(&solana_pubkey::Pubkey::from(*new_owner)); + Ok(()) + } + + fn realloc(&self, new_len: usize, zero_init: bool) -> Result<(), AccountError> { + #[allow(deprecated)] + self.realloc(new_len, zero_init) + .map_err(|_| AccountError::InvalidAccountSize) + } + + fn sub_lamports(&self, amount: u64) -> Result<(), AccountError> { + let mut lamports = self + .try_borrow_mut_lamports() + .map_err(|_| AccountError::BorrowAccountDataFailed)?; + **lamports = lamports + .checked_sub(amount) + .ok_or(AccountError::ArithmeticOverflow)?; + Ok(()) + } + + fn add_lamports(&self, amount: u64) -> Result<(), AccountError> { + let mut lamports = self + .try_borrow_mut_lamports() + .map_err(|_| AccountError::BorrowAccountDataFailed)?; + **lamports = lamports + .checked_add(amount) + .ok_or(AccountError::ArithmeticOverflow)?; + Ok(()) + } + + fn close(&self, destination: &Self) -> Result<(), AccountError> { + crate::close_account::close_account(self, destination) + } + + #[inline(never)] + fn create_pda_account( + &self, + lamports: u64, + space: u64, + owner: &[u8; 32], + pda_seeds: &[&[u8]], + rent_payer: &Self, + rent_payer_seeds: &[&[u8]], + system_program: &Self, + ) -> Result<(), AccountError> { + use solana_cpi::invoke_signed; + use solana_system_interface::instruction as system_instruction; + + let owner_pubkey = solana_pubkey::Pubkey::from(*owner); + + // Cold path: account already has lamports (e.g., attacker donation). + // CreateAccount would fail, so use Assign + Allocate + Transfer. + if self.lamports() > 0 { + return create_pda_with_lamports_solana( + self, + lamports, + space, + &owner_pubkey, + pda_seeds, + rent_payer, + rent_payer_seeds, + system_program, + ); + } + + // Normal path: CreateAccount + let create_ix = system_instruction::create_account( + rent_payer.key, + self.key, + lamports, + space, + &owner_pubkey, + ); + invoke_signed( + &create_ix, + &[rent_payer.clone(), self.clone()], + &[rent_payer_seeds, pda_seeds], + ) + .map_err(|_| AccountError::InvalidAccount) + } + + fn transfer_lamports_cpi( + &self, + destination: &Self, + lamports: u64, + signer_seeds: &[&[u8]], + ) -> Result<(), AccountError> { + use solana_cpi::invoke_signed; + use solana_system_interface::instruction as system_instruction; + + let ix = system_instruction::transfer(self.key, destination.key, lamports); + invoke_signed(&ix, &[self.clone(), destination.clone()], &[signer_seeds]) + .map_err(|_| AccountError::InvalidAccount) + } + + fn invoke_cpi( + program_id: &[u8; 32], + instruction_data: &[u8], + account_metas: &[super::account_info_trait::CpiMeta], + account_infos: &[Self], + signer_seeds: &[&[&[u8]]], + ) -> Result<(), AccountError> { + use solana_cpi::invoke_signed; + + let metas: Vec = account_metas + .iter() + .map(|m| solana_instruction::AccountMeta { + pubkey: solana_pubkey::Pubkey::from(m.pubkey), + is_signer: m.is_signer, + is_writable: m.is_writable, + }) + .collect(); + + let ix = solana_instruction::Instruction { + program_id: solana_pubkey::Pubkey::from(*program_id), + accounts: metas, + data: instruction_data.to_vec(), + }; + + invoke_signed(&ix, account_infos, signer_seeds).map_err(|_| AccountError::InvalidAccount) + } +} + +impl AccountMetaTrait for solana_instruction::AccountMeta { + fn new(pubkey: [u8; 32], is_signer: bool, is_writable: bool) -> Self { + Self { + pubkey: solana_pubkey::Pubkey::from(pubkey), + is_signer, + is_writable, + } + } + + fn pubkey_bytes(&self) -> [u8; 32] { + self.pubkey.to_bytes() + } + + fn is_signer(&self) -> bool { + self.is_signer + } + + fn is_writable(&self) -> bool { + self.is_writable + } + + fn set_is_signer(&mut self, val: bool) { + self.is_signer = val; + } + + fn set_is_writable(&mut self, val: bool) { + self.is_writable = val; + } +} + +/// Cold path for create_pda_account when account already has lamports. +#[cold] +#[inline(never)] +#[allow(clippy::too_many_arguments)] +fn create_pda_with_lamports_solana<'a>( + account: &solana_account_info::AccountInfo<'a>, + lamports: u64, + space: u64, + owner: &solana_pubkey::Pubkey, + pda_seeds: &[&[u8]], + rent_payer: &solana_account_info::AccountInfo<'a>, + rent_payer_seeds: &[&[u8]], + system_program: &solana_account_info::AccountInfo<'a>, +) -> Result<(), AccountError> { + use solana_cpi::invoke_signed; + use solana_system_interface::instruction as system_instruction; + + let current_lamports = account.lamports(); + + // Assign owner + let assign_ix = system_instruction::assign(account.key, owner); + invoke_signed(&assign_ix, &[account.clone()], &[pda_seeds]) + .map_err(|_| AccountError::InvalidAccount)?; + + // Allocate space + let allocate_ix = system_instruction::allocate(account.key, space); + invoke_signed(&allocate_ix, &[account.clone()], &[pda_seeds]) + .map_err(|_| AccountError::InvalidAccount)?; + + // Transfer remaining lamports for rent-exemption if needed + if lamports > current_lamports { + let transfer_ix = system_instruction::transfer( + rent_payer.key, + account.key, + lamports - current_lamports, + ); + invoke_signed( + &transfer_ix, + &[ + rent_payer.clone(), + account.clone(), + system_program.clone(), + ], + &[rent_payer_seeds], + ) + .map_err(|_| AccountError::InvalidAccount)?; + } + + Ok(()) } diff --git a/program-libs/account-checks/src/close_account.rs b/program-libs/account-checks/src/close_account.rs new file mode 100644 index 0000000000..d3f9ab85f9 --- /dev/null +++ b/program-libs/account-checks/src/close_account.rs @@ -0,0 +1,31 @@ +use crate::{account_info::account_info_trait::AccountInfoTrait, error::AccountError}; + +/// Close a native Solana account by transferring lamports and clearing data. +/// +/// Transfers all lamports to `sol_destination`, assigns the account to the +/// system program (all-zero owner), and resizes data to 0. +/// +/// If `info` and `sol_destination` are the same account, the lamports stay +/// in the account but the owner and data are cleared. +pub fn close_account( + info: &AI, + sol_destination: &AI, +) -> Result<(), AccountError> { + let system_program_id = [0u8; 32]; + + if info.key() == sol_destination.key() { + info.assign(&system_program_id)?; + info.realloc(0, false)?; + return Ok(()); + } + + let lamports_to_transfer = info.lamports(); + + sol_destination.add_lamports(lamports_to_transfer)?; + info.sub_lamports(lamports_to_transfer)?; + + info.assign(&system_program_id)?; + info.realloc(0, false)?; + + Ok(()) +} diff --git a/program-libs/account-checks/src/error.rs b/program-libs/account-checks/src/error.rs index 381eaac6f4..42bbee32ac 100644 --- a/program-libs/account-checks/src/error.rs +++ b/program-libs/account-checks/src/error.rs @@ -38,6 +38,8 @@ pub enum AccountError { FailedSysvarAccess, #[error("Pinocchio program error with code: {0}")] PinocchioProgramError(u32), + #[error("Arithmetic overflow.")] + ArithmeticOverflow, } impl From for u32 { @@ -61,6 +63,7 @@ impl From for u32 { AccountError::InvalidAccount => 20015, AccountError::FailedSysvarAccess => 20016, AccountError::PinocchioProgramError(code) => code, + AccountError::ArithmeticOverflow => 20017, } } } diff --git a/program-libs/account-checks/src/lib.rs b/program-libs/account-checks/src/lib.rs index 5d973032b3..56d4decff1 100644 --- a/program-libs/account-checks/src/lib.rs +++ b/program-libs/account-checks/src/lib.rs @@ -16,10 +16,13 @@ pub mod account_info; pub mod account_iterator; pub mod checks; +pub mod close_account; pub mod discriminator; pub mod error; pub mod packed_accounts; -pub use account_info::account_info_trait::AccountInfoTrait; +pub use account_info::account_info_trait::{AccountInfoTrait, CpiMeta}; +pub use account_info::account_meta_trait::AccountMetaTrait; pub use account_iterator::AccountIterator; +pub use close_account::close_account; pub use error::AccountError; diff --git a/sdk-libs/client/src/interface/instructions.rs b/sdk-libs/client/src/interface/instructions.rs index 6a88000b89..c6d2369b01 100644 --- a/sdk-libs/client/src/interface/instructions.rs +++ b/sdk-libs/client/src/interface/instructions.rs @@ -30,19 +30,7 @@ fn get_output_queue(tree_info: &TreeInfo) -> Pubkey { .unwrap_or(tree_info.queue) } -#[derive(AnchorSerialize, AnchorDeserialize)] -pub struct InitializeConfigData { - pub rent_sponsor: Pubkey, - pub address_space: Vec, - pub config_bump: u8, -} - -#[derive(AnchorSerialize, AnchorDeserialize)] -pub struct UpdateConfigData { - pub new_rent_sponsor: Option, - pub new_address_space: Option>, - pub new_update_authority: Option, -} +use light_sdk::{InitializeLightConfigParams, UpdateLightConfigParams}; #[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] pub struct LoadAccountsData { @@ -129,13 +117,17 @@ pub fn initialize_config( AccountMeta::new_readonly(system_program, false), ]; - let ix_data = InitializeConfigData { + let params = InitializeLightConfigParams { rent_sponsor, + compression_authority: *authority, + rent_config: Default::default(), + write_top_up: 0, address_space, config_bump, }; - - let serialized = ix_data.try_to_vec().expect("serialize"); + // Serialize params, then wrap as Vec for Anchor's borsh deserialization + let params_bytes: Vec = params.try_to_vec().expect("serialize params"); + let serialized = params_bytes.try_to_vec().expect("serialize vec"); let mut data = Vec::with_capacity(discriminator.len() + serialized.len()); data.extend_from_slice(discriminator); data.extend_from_slice(&serialized); @@ -162,13 +154,17 @@ pub fn update_config( AccountMeta::new_readonly(*authority, true), ]; - let ix_data = UpdateConfigData { + let params = UpdateLightConfigParams { + new_update_authority, new_rent_sponsor, + new_compression_authority: None, + new_rent_config: None, + new_write_top_up: None, new_address_space, - new_update_authority, }; - - let serialized = ix_data.try_to_vec().expect("serialize"); + // Serialize params, then wrap as Vec for Anchor's borsh deserialization + let params_bytes: Vec = params.try_to_vec().expect("serialize params"); + let serialized = params_bytes.try_to_vec().expect("serialize vec"); let mut data = Vec::with_capacity(discriminator.len() + serialized.len()); data.extend_from_slice(discriminator); data.extend_from_slice(&serialized); diff --git a/sdk-libs/macros/src/lib.rs b/sdk-libs/macros/src/lib.rs index 8a465adce4..9a04d70371 100644 --- a/sdk-libs/macros/src/lib.rs +++ b/sdk-libs/macros/src/lib.rs @@ -210,6 +210,42 @@ pub fn light_program(args: TokenStream, input: TokenStream) -> TokenStream { into_token_stream(light_pdas::program::light_program_impl(args.into(), module)) } +/// Derive macro for manually specifying compressed account variants on an enum. +/// +/// Generates equivalent code to `#[light_program]` auto-discovery, but allows +/// specifying account types and seeds explicitly. Useful for external programs +/// where you don't own the module. +/// +/// ## Example +/// +/// ```ignore +/// #[derive(LightProgram)] +/// pub enum ProgramAccounts { +/// #[light_account(pda::seeds = [b"record", ctx.owner])] +/// Record(MinimalRecord), +/// +/// #[light_account(pda::seeds = [RECORD_SEED, ctx.owner], pda::zero_copy)] +/// ZeroCopyRecord(ZeroCopyRecord), +/// +/// #[light_account(token::seeds = [VAULT_SEED, ctx.mint], token::owner_seeds = [AUTH_SEED])] +/// Vault, +/// +/// #[light_account(associated_token)] +/// Ata, +/// } +/// ``` +/// +/// Seed expressions use explicit prefixes: +/// - `ctx.field` - context account reference +/// - `data.field` - instruction data parameter +/// - `b"literal"` or `"literal"` - byte/string literal +/// - `CONSTANT` or `path::CONSTANT` - constant in SCREAMING_SNAKE_CASE +#[proc_macro_derive(LightProgram, attributes(light_account))] +pub fn light_program_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + into_token_stream(light_pdas::program::derive_light_program_impl(input)) +} + #[proc_macro_attribute] pub fn account(_: TokenStream, input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as ItemStruct); diff --git a/sdk-libs/macros/src/light_pdas/light_account_keywords.rs b/sdk-libs/macros/src/light_pdas/light_account_keywords.rs index bbee9d4853..846a7c609e 100644 --- a/sdk-libs/macros/src/light_pdas/light_account_keywords.rs +++ b/sdk-libs/macros/src/light_pdas/light_account_keywords.rs @@ -22,6 +22,12 @@ //! pub vault: UncheckedAccount<'info>, //! ``` +/// Valid keys for `pda::` namespace in `#[light_account(pda::...)]` attributes. +/// Used by `#[derive(LightProgram)]` enum variants. +/// - `seeds`: PDA seeds for account derivation +/// - `zero_copy`: Flag indicating zero-copy deserialization +pub const PDA_NAMESPACE_KEYS: &[&str] = &["seeds", "zero_copy"]; + /// Valid keys for `token::` namespace in `#[light_account(init, token::...)]` attributes. /// These map to the TokenAccountField struct. /// - `seeds`: Token account PDA seeds (for signing as the token account) - can be dynamic @@ -93,6 +99,7 @@ pub fn is_shorthand_key(namespace: &str, key: &str) -> bool { /// A slice of valid key strings for the namespace. pub fn valid_keys_for_namespace(namespace: &str) -> &'static [&'static str] { match namespace { + "pda" => PDA_NAMESPACE_KEYS, "token" => TOKEN_NAMESPACE_KEYS, "associated_token" => ASSOCIATED_TOKEN_NAMESPACE_KEYS, "mint" => MINT_NAMESPACE_KEYS, @@ -113,7 +120,7 @@ pub fn validate_namespaced_key(namespace: &str, key: &str) -> Result<(), String> if valid_keys.is_empty() { return Err(format!( - "Unknown namespace `{}`. Expected: token, associated_token, or mint", + "Unknown namespace `{}`. Expected: pda, token, associated_token, or mint", namespace )); } @@ -142,7 +149,7 @@ pub fn unknown_key_error(namespace: &str, key: &str) -> String { let valid = valid_keys_for_namespace(namespace); if valid.is_empty() { format!( - "Unknown namespace `{}`. Expected: token, associated_token, or mint", + "Unknown namespace `{}`. Expected: pda, token, associated_token, or mint", namespace ) } else { @@ -175,6 +182,13 @@ pub fn missing_namespace_error(key: &str, account_type: &str) -> String { mod tests { use super::*; + #[test] + fn test_pda_namespace_keys() { + assert!(PDA_NAMESPACE_KEYS.contains(&"seeds")); + assert!(PDA_NAMESPACE_KEYS.contains(&"zero_copy")); + assert!(!PDA_NAMESPACE_KEYS.contains(&"unknown")); + } + #[test] fn test_token_namespace_keys() { assert!(TOKEN_NAMESPACE_KEYS.contains(&"seeds")); @@ -244,6 +258,9 @@ mod tests { #[test] fn test_valid_keys_for_namespace() { + let pda_kw = valid_keys_for_namespace("pda"); + assert_eq!(pda_kw, PDA_NAMESPACE_KEYS); + let token_kw = valid_keys_for_namespace("token"); assert_eq!(token_kw, TOKEN_NAMESPACE_KEYS); diff --git a/sdk-libs/macros/src/light_pdas/parsing/crate_context.rs b/sdk-libs/macros/src/light_pdas/parsing/crate_context.rs index 7ede4a0858..3b34a76287 100644 --- a/sdk-libs/macros/src/light_pdas/parsing/crate_context.rs +++ b/sdk-libs/macros/src/light_pdas/parsing/crate_context.rs @@ -25,6 +25,14 @@ pub struct CrateContext { } impl CrateContext { + /// Create an empty CrateContext (for testing or when no struct discovery is needed). + #[allow(dead_code)] + pub fn empty() -> Self { + CrateContext { + modules: BTreeMap::new(), + } + } + /// Parse all modules starting from the crate root (lib.rs or main.rs). /// /// Uses `CARGO_MANIFEST_DIR` environment variable to locate the crate root. diff --git a/sdk-libs/macros/src/light_pdas/program/compress.rs b/sdk-libs/macros/src/light_pdas/program/compress.rs index 02c14b2933..ceb2064b10 100644 --- a/sdk-libs/macros/src/light_pdas/program/compress.rs +++ b/sdk-libs/macros/src/light_pdas/program/compress.rs @@ -311,6 +311,64 @@ impl CompressBuilder { }) } + /// Generate compress dispatch as an associated function on the enum. + /// + /// When `#[derive(LightProgram)]` is used, the dispatch function is generated + /// as `impl EnumName { pub fn compress_dispatch(...) }` so it can be referenced + /// as `EnumName::compress_dispatch` and passed to SDK functions. + pub fn generate_enum_dispatch_method(&self, enum_name: &syn::Ident) -> Result { + let compress_arms: Vec<_> = self.accounts.iter().map(|info| { + let name = qualify_type_with_crate(&info.account_type); + + if info.is_zero_copy { + quote! { + d if d == #name::LIGHT_DISCRIMINATOR => { + let pod_bytes = &data[8..8 + core::mem::size_of::<#name>()]; + let mut account_data: #name = *bytemuck::from_bytes(pod_bytes); + drop(data); + light_sdk::interface::prepare_account_for_compression( + account_info, &mut account_data, meta, index, ctx, + ) + } + } + } else { + quote! { + d if d == #name::LIGHT_DISCRIMINATOR => { + let mut reader = &data[8..]; + let mut account_data = #name::deserialize(&mut reader) + .map_err(|_| solana_program_error::ProgramError::InvalidAccountData)?; + drop(data); + light_sdk::interface::prepare_account_for_compression( + account_info, &mut account_data, meta, index, ctx, + ) + } + } + } + }).collect(); + + Ok(quote! { + impl #enum_name { + pub fn compress_dispatch<'info>( + account_info: &anchor_lang::prelude::AccountInfo<'info>, + meta: &light_sdk::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, + index: usize, + ctx: &mut light_sdk::interface::CompressCtx<'_, 'info>, + ) -> std::result::Result<(), solana_program_error::ProgramError> { + use light_sdk::LightDiscriminator; + use borsh::BorshDeserialize; + let data = account_info.try_borrow_data()?; + let discriminator: [u8; 8] = data[..8] + .try_into() + .map_err(|_| solana_program_error::ProgramError::InvalidAccountData)?; + match discriminator { + #(#compress_arms)* + _ => Ok(()), + } + } + } + }) + } + /// Generate compile-time size validation for compressed accounts. pub fn generate_size_validation(&self) -> Result { let size_checks: Vec<_> = self.accounts.iter().map(|info| { diff --git a/sdk-libs/macros/src/light_pdas/program/decompress.rs b/sdk-libs/macros/src/light_pdas/program/decompress.rs index 1cbab4a6b5..ea4d2cc1ac 100644 --- a/sdk-libs/macros/src/light_pdas/program/decompress.rs +++ b/sdk-libs/macros/src/light_pdas/program/decompress.rs @@ -328,6 +328,37 @@ impl DecompressBuilder { Ok(results) } + + /// Generate decompress dispatch as an associated function on the enum. + /// + /// When `#[derive(LightProgram)]` is used, the dispatch function is generated + /// as `impl EnumName { pub fn decompress_dispatch(...) }` so it can be referenced + /// as `EnumName::decompress_dispatch`. + /// + /// This wraps the type-parameter-based SDK call, binding `PackedLightAccountVariant` + /// as the concrete type. + pub fn generate_enum_decompress_dispatch( + &self, + enum_name: &syn::Ident, + ) -> Result { + Ok(quote! { + impl #enum_name { + pub fn decompress_dispatch<'info>( + remaining_accounts: &[solana_account_info::AccountInfo<'info>], + instruction_data: &[u8], + cpi_signer: light_sdk_types::CpiSigner, + program_id: &solana_pubkey::Pubkey, + ) -> std::result::Result<(), solana_program_error::ProgramError> { + light_sdk::interface::process_decompress_pda_accounts_idempotent::( + remaining_accounts, + instruction_data, + cpi_signer, + program_id, + ) + } + } + }) + } } // ============================================================================= diff --git a/sdk-libs/macros/src/light_pdas/program/derive_light_program.rs b/sdk-libs/macros/src/light_pdas/program/derive_light_program.rs new file mode 100644 index 0000000000..37c797a6ab --- /dev/null +++ b/sdk-libs/macros/src/light_pdas/program/derive_light_program.rs @@ -0,0 +1,1702 @@ +//! Manual `#[derive(LightProgram)]` macro implementation. +//! +//! Allows specifying compressed account variants on an enum, generating equivalent +//! code to `#[light_program]` auto-discovery. Useful for external programs where +//! you can't add `#[light_program]` to the module. +//! +//! ## Syntax +//! +//! ```ignore +//! #[derive(LightProgram)] +//! pub enum ProgramAccounts { +//! #[light_account(pda::seeds = [b"record", ctx.owner])] +//! Record(MinimalRecord), +//! +//! #[light_account(pda::seeds = [RECORD_SEED, ctx.owner], pda::zero_copy)] +//! ZeroCopyRecord(ZeroCopyRecord), +//! +//! #[light_account(token::seeds = [VAULT_SEED, ctx.mint], token::owner_seeds = [VAULT_AUTH_SEED])] +//! Vault, +//! +//! #[light_account(associated_token)] +//! Ata, +//! } +//! ``` + +use std::collections::HashSet; + +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{parse::ParseStream, DeriveInput, Ident, Result, Token, Type}; + +use super::instructions::{ + generate_light_program_items, CompressibleAccountInfo, InstructionDataSpec, SeedElement, + TokenSeedSpec, +}; +use crate::light_pdas::{ + accounts::variant::VariantBuilder, + light_account_keywords::validate_namespaced_key, + parsing::CrateContext, + seeds::{ClassifiedSeed, ExtractedSeedSpec}, + shared_utils::is_constant_identifier, +}; + +// ============================================================================= +// PARSING TYPES +// ============================================================================= + +/// Kind of a manual variant in the enum. +#[derive(Clone, Debug)] +enum ManualVariantKind { + Pda, + Token, + Ata, +} + +/// A single seed element parsed from the attribute. +#[derive(Clone, Debug)] +enum ManualSeed { + /// b"literal" - byte string literal + ByteLiteral(syn::LitByteStr), + /// "literal" - string literal (converted to bytes) + StrLiteral(syn::LitStr), + /// CONSTANT or path::CONSTANT + Constant(syn::Path), + /// ctx.field - context account reference + CtxField(Ident), + /// data.field - instruction data reference + DataField(Ident), +} + +/// Parsed variant from the manual enum. +#[derive(Clone, Debug)] +struct ParsedManualVariant { + ident: Ident, + kind: ManualVariantKind, + inner_type: Option, + is_zero_copy: bool, + seeds: Vec, + owner_seeds: Option>, +} + +// ============================================================================= +// PARSING +// ============================================================================= + +/// Parse all variants from the derive input enum. +fn parse_enum_variants(input: &DeriveInput) -> Result> { + let data = match &input.data { + syn::Data::Enum(data) => data, + _ => { + return Err(syn::Error::new_spanned( + input, + "#[derive(LightProgram)] can only be applied to enums", + )) + } + }; + + let mut variants = Vec::new(); + for variant in &data.variants { + // Find the #[light_account(...)] attribute + let attr = variant + .attrs + .iter() + .find(|a| a.path().is_ident("light_account")) + .ok_or_else(|| { + syn::Error::new_spanned( + &variant.ident, + format!( + "Variant '{}' is missing #[light_account(...)] attribute", + variant.ident + ), + ) + })?; + + let parsed = parse_variant_attr(attr, &variant.ident, &variant.fields)?; + variants.push(parsed); + } + + Ok(variants) +} + +/// Parse a single variant's `#[light_account(...)]` attribute. +fn parse_variant_attr( + attr: &syn::Attribute, + variant_ident: &Ident, + fields: &syn::Fields, +) -> Result { + let tokens: TokenStream = attr.parse_args()?; + let parsed: VariantAttrContent = syn::parse2(tokens)?; + + // Extract inner type from tuple field for PDA variants + let inner_type = match &parsed.kind { + ManualVariantKind::Pda => { + let ty = extract_inner_type(variant_ident, fields)?; + Some(ty) + } + ManualVariantKind::Token | ManualVariantKind::Ata => { + // Ensure unit variant + if !matches!(fields, syn::Fields::Unit) { + return Err(syn::Error::new_spanned( + variant_ident, + format!( + "Token/ATA variant '{}' must be a unit variant (no fields)", + variant_ident + ), + )); + } + None + } + }; + + Ok(ParsedManualVariant { + ident: variant_ident.clone(), + kind: parsed.kind, + inner_type, + is_zero_copy: parsed.is_zero_copy, + seeds: parsed.seeds, + owner_seeds: parsed.owner_seeds, + }) +} + +/// Extract inner type from a tuple variant's first field. +fn extract_inner_type(variant_ident: &Ident, fields: &syn::Fields) -> Result { + match fields { + syn::Fields::Unnamed(unnamed) => { + if unnamed.unnamed.len() != 1 { + return Err(syn::Error::new_spanned( + variant_ident, + format!( + "PDA variant '{}' must have exactly one field (the data type)", + variant_ident + ), + )); + } + Ok(unnamed.unnamed[0].ty.clone()) + } + _ => Err(syn::Error::new_spanned( + variant_ident, + format!( + "PDA variant '{}' must be a tuple variant with the data type, e.g., {}(MyRecord)", + variant_ident, variant_ident + ), + )), + } +} + +// ============================================================================= +// ATTRIBUTE CONTENT PARSING +// ============================================================================= + +/// Parsed content of `#[light_account(...)]`. +/// +/// Kind is inferred from namespace prefix or standalone keyword: +/// - Any `pda::*` present -> PDA +/// - Any `token::*` present -> Token +/// - `associated_token` -> ATA +struct VariantAttrContent { + kind: ManualVariantKind, + is_zero_copy: bool, + seeds: Vec, + owner_seeds: Option>, +} + +/// Tracks seen keywords/namespaces to detect duplicates and conflicts. +#[derive(Default)] +struct SeenDeriveKeywords { + namespace: Option, + seen_keys: HashSet, +} + +impl SeenDeriveKeywords { + /// Record a namespaced key. Returns error on mixed namespaces or duplicate keys. + fn add_namespaced_key(&mut self, ns: &Ident, key: &Ident) -> Result<()> { + let ns_str = ns.to_string(); + let key_str = key.to_string(); + + if let Err(err_msg) = validate_namespaced_key(&ns_str, &key_str) { + return Err(syn::Error::new_spanned(key, err_msg)); + } + + if let Some(ref prev_ns) = self.namespace { + if prev_ns != &ns_str { + return Err(syn::Error::new_spanned( + ns, + format!( + "Mixed namespaces: `{}::` conflicts with previous `{}::`. \ + Each variant must use a single namespace.", + ns_str, prev_ns + ), + )); + } + } else { + self.namespace = Some(ns_str.clone()); + } + + if !self.seen_keys.insert(key_str.clone()) { + return Err(syn::Error::new_spanned( + key, + format!( + "Duplicate key `{}::{}`. Each key can only appear once.", + ns_str, key_str + ), + )); + } + + Ok(()) + } +} + +/// Map namespace ident to ManualVariantKind. +fn infer_kind_from_namespace(ns: &Ident) -> Result { + match ns.to_string().as_str() { + "pda" => Ok(ManualVariantKind::Pda), + "token" => Ok(ManualVariantKind::Token), + _ => Err(syn::Error::new_spanned( + ns, + format!( + "Unknown namespace `{}` for #[derive(LightProgram)]. \ + Expected: `pda` or `token`. For ATA use `associated_token`. \ + Mints are decompressed directly with the Light Token Program \ + and don't need to be declared here.", + ns + ), + )), + } +} + +/// Parse the value part of a namespaced key. +fn parse_namespaced_value( + ns: &Ident, + key: &Ident, + input: ParseStream, + seeds: &mut Vec, + owner_seeds: &mut Option>, + is_zero_copy: &mut bool, +) -> Result<()> { + let ns_str = ns.to_string(); + let key_str = key.to_string(); + + match (ns_str.as_str(), key_str.as_str()) { + ("pda", "seeds") => { + input.parse::()?; + *seeds = parse_seed_array(input)?; + } + ("pda", "zero_copy") => { + *is_zero_copy = true; + } + ("token", "seeds") => { + input.parse::()?; + *seeds = parse_seed_array(input)?; + } + ("token", "owner_seeds") => { + input.parse::()?; + *owner_seeds = Some(parse_seed_array(input)?); + } + _ => { + return Err(syn::Error::new_spanned( + key, + format!( + "Unsupported key `{}::{}` in #[derive(LightProgram)]", + ns_str, key_str + ), + )); + } + } + Ok(()) +} + +impl syn::parse::Parse for VariantAttrContent { + fn parse(input: ParseStream) -> Result { + let mut seen = SeenDeriveKeywords::default(); + let mut is_zero_copy = false; + let mut seeds = Vec::new(); + let mut owner_seeds = None; + + // Parse first token to determine kind + let first: Ident = input.parse()?; + + let kind = if first == "associated_token" { + ManualVariantKind::Ata + } else if input.peek(Token![::]) { + // Namespaced key: pda::seeds, token::seeds, etc. + input.parse::()?; + let key: Ident = input.parse()?; + + seen.add_namespaced_key(&first, &key)?; + let k = infer_kind_from_namespace(&first)?; + + parse_namespaced_value( + &first, + &key, + input, + &mut seeds, + &mut owner_seeds, + &mut is_zero_copy, + )?; + k + } else { + return Err(syn::Error::new_spanned( + &first, + format!( + "Unknown keyword `{}`. Expected: `associated_token` \ + or namespaced key like `pda::seeds`, `token::seeds`. \ + Mints are decompressed directly with the Light Token Program \ + and don't need to be declared here.", + first + ), + )); + }; + + // Parse remaining comma-separated items + while input.peek(Token![,]) { + input.parse::()?; + if input.is_empty() { + break; + } + + let ident: Ident = input.parse()?; + + if !input.peek(Token![::]) { + return Err(syn::Error::new_spanned( + &ident, + format!( + "Unexpected keyword `{}`. Use namespaced syntax: `pda::{}` or `token::{}`", + ident, ident, ident + ), + )); + } + + input.parse::()?; + let key: Ident = input.parse()?; + + seen.add_namespaced_key(&ident, &key)?; + + parse_namespaced_value( + &ident, + &key, + input, + &mut seeds, + &mut owner_seeds, + &mut is_zero_copy, + )?; + } + + // Post-parse validation + + match kind { + ManualVariantKind::Pda => { + if seeds.is_empty() { + return Err(syn::Error::new( + Span::call_site(), + "PDA variant requires `pda::seeds = [...]`", + )); + } + } + ManualVariantKind::Token => { + if seeds.is_empty() { + return Err(syn::Error::new( + Span::call_site(), + "Token variant requires `token::seeds = [...]`", + )); + } + if owner_seeds.is_none() { + return Err(syn::Error::new( + Span::call_site(), + "Token variant requires `token::owner_seeds = [...]`", + )); + } + } + ManualVariantKind::Ata => {} + } + + Ok(VariantAttrContent { + kind, + is_zero_copy, + seeds, + owner_seeds, + }) + } +} + +/// Parse a seed array `[seed1, seed2, ...]`. +fn parse_seed_array(input: syn::parse::ParseStream) -> Result> { + let content; + syn::bracketed!(content in input); + + let mut seeds = Vec::new(); + while !content.is_empty() { + seeds.push(parse_single_seed(&content)?); + if content.peek(syn::Token![,]) { + let _: syn::Token![,] = content.parse()?; + } else { + break; + } + } + Ok(seeds) +} + +/// Parse a single seed expression with explicit prefix disambiguation. +fn parse_single_seed(input: syn::parse::ParseStream) -> Result { + // Check for byte string literal: b"..." + if input.peek(syn::LitByteStr) { + let lit: syn::LitByteStr = input.parse()?; + return Ok(ManualSeed::ByteLiteral(lit)); + } + + // Check for string literal: "..." + if input.peek(syn::LitStr) { + let lit: syn::LitStr = input.parse()?; + return Ok(ManualSeed::StrLiteral(lit)); + } + + // Parse as path/expression + // Could be: ctx.field, data.field, CONSTANT, path::CONSTANT + let expr: syn::Expr = input.parse()?; + classify_seed_expr(&expr) +} + +/// Classify a parsed expression into a ManualSeed. +fn classify_seed_expr(expr: &syn::Expr) -> Result { + match expr { + // ctx.field or data.field + syn::Expr::Field(field_expr) => { + if let syn::Expr::Path(base_path) = field_expr.base.as_ref() { + if let Some(base_ident) = base_path.path.get_ident() { + let base_str = base_ident.to_string(); + if let syn::Member::Named(field_name) = &field_expr.member { + if base_str == "ctx" { + return Ok(ManualSeed::CtxField(field_name.clone())); + } else if base_str == "data" { + return Ok(ManualSeed::DataField(field_name.clone())); + } + } + } + } + Err(syn::Error::new_spanned( + expr, + "Field access seeds must use ctx.field or data.field prefix", + )) + } + // CONSTANT or path::CONSTANT + syn::Expr::Path(path_expr) => { + let path = &path_expr.path; + // Check if last segment is a constant (SCREAMING_SNAKE_CASE) + if let Some(last_seg) = path.segments.last() { + if is_constant_identifier(&last_seg.ident.to_string()) { + return Ok(ManualSeed::Constant(path.clone())); + } + } + // Could be a single lowercase ident like `ctx` or `data` without field access + Err(syn::Error::new_spanned( + expr, + "Seed path must be a SCREAMING_SNAKE_CASE constant, or use ctx.field / data.field prefix", + )) + } + _ => Err(syn::Error::new_spanned( + expr, + "Unsupported seed expression. Use: b\"literal\", \"literal\", ctx.field, data.field, or CONSTANT", + )), + } +} + +// ============================================================================= +// CONVERSION: ManualSeed -> ClassifiedSeed +// ============================================================================= + +fn manual_seed_to_classified(seed: &ManualSeed) -> ClassifiedSeed { + match seed { + ManualSeed::ByteLiteral(lit) => ClassifiedSeed::Literal(lit.value()), + ManualSeed::StrLiteral(lit) => ClassifiedSeed::Literal(lit.value().into_bytes()), + ManualSeed::Constant(path) => { + let expr: syn::Expr = syn::parse_quote!(#path); + ClassifiedSeed::Constant { + path: path.clone(), + expr: Box::new(expr), + } + } + ManualSeed::CtxField(ident) => ClassifiedSeed::CtxRooted { + account: ident.clone(), + }, + ManualSeed::DataField(ident) => { + let expr: syn::Expr = syn::parse_quote!(data.#ident); + ClassifiedSeed::DataRooted { + root: ident.clone(), + expr: Box::new(expr), + } + } + } +} + +// ============================================================================= +// CONVERSION: ManualSeed -> SeedElement +// ============================================================================= + +fn manual_seed_to_seed_element(seed: &ManualSeed) -> SeedElement { + match seed { + ManualSeed::ByteLiteral(lit) => { + let expr: syn::Expr = syn::parse_quote!(#lit); + SeedElement::Expression(Box::new(expr)) + } + ManualSeed::StrLiteral(lit) => SeedElement::Literal(lit.clone()), + ManualSeed::Constant(path) => { + let expr: syn::Expr = syn::parse_quote!(#path); + SeedElement::Expression(Box::new(expr)) + } + ManualSeed::CtxField(ident) => { + let expr: syn::Expr = syn::parse_quote!(ctx.#ident); + SeedElement::Expression(Box::new(expr)) + } + ManualSeed::DataField(ident) => { + let expr: syn::Expr = syn::parse_quote!(data.#ident); + SeedElement::Expression(Box::new(expr)) + } + } +} + +fn manual_seeds_to_punctuated( + seeds: &[ManualSeed], +) -> syn::punctuated::Punctuated { + let mut result = syn::punctuated::Punctuated::new(); + for seed in seeds { + result.push(manual_seed_to_seed_element(seed)); + } + result +} + +fn manual_seeds_to_seed_elements_vec(seeds: &[ManualSeed]) -> Vec { + seeds.iter().map(manual_seed_to_seed_element).collect() +} + +// ============================================================================= +// BUILDER: Convert parsed variants to intermediate types +// ============================================================================= + +#[allow(clippy::type_complexity)] +fn build_intermediate_types( + variants: &[ParsedManualVariant], + _crate_ctx: &CrateContext, +) -> Result<( + Vec, + Option>, + Option>, + Vec, + bool, + bool, + TokenStream, +)> { + let mut compressible_accounts = Vec::new(); + let mut pda_seed_specs = Vec::new(); + let mut token_seed_specs = Vec::new(); + let mut instruction_data_specs = Vec::new(); + let has_mint_fields = false; + let mut has_ata_fields = false; + let mut pda_variant_code = TokenStream::new(); + + // Track data field names we've already added to instruction_data + let mut seen_data_fields = std::collections::HashSet::new(); + + for variant in variants { + match &variant.kind { + ManualVariantKind::Pda => { + let inner_type = variant.inner_type.as_ref().unwrap(); + + // Build CompressibleAccountInfo + compressible_accounts.push(CompressibleAccountInfo { + account_type: inner_type.clone(), + is_zero_copy: variant.is_zero_copy, + }); + + // Build ClassifiedSeeds for VariantBuilder + let classified_seeds: Vec = + variant.seeds.iter().map(manual_seed_to_classified).collect(); + + // Build ExtractedSeedSpec for VariantBuilder + let extracted_spec = ExtractedSeedSpec { + variant_name: variant.ident.clone(), + inner_type: inner_type.clone(), + seeds: classified_seeds, + is_zero_copy: variant.is_zero_copy, + struct_name: variant.ident.to_string(), + module_path: "crate".to_string(), + }; + + // Generate variant code + let builder = VariantBuilder::from_extracted_spec(&extracted_spec); + pda_variant_code.extend(builder.build()); + + // Build TokenSeedSpec for PDA seeds + let seed_elements = manual_seeds_to_punctuated(&variant.seeds); + let dummy_eq: syn::Token![=] = + syn::parse_quote!(=); + pda_seed_specs.push(TokenSeedSpec { + variant: variant.ident.clone(), + _eq: dummy_eq, + is_token: Some(false), + seeds: seed_elements, + owner_seeds: None, + inner_type: Some(inner_type.clone()), + is_zero_copy: variant.is_zero_copy, + }); + + // Extract data fields for InstructionDataSpec + for seed in &variant.seeds { + if let ManualSeed::DataField(ident) = seed { + let name = ident.to_string(); + if seen_data_fields.insert(name) { + // Default to Pubkey type for data seeds without conversion + instruction_data_specs.push(InstructionDataSpec { + field_name: ident.clone(), + field_type: syn::parse_quote!(Pubkey), + }); + } + } + } + } + + ManualVariantKind::Token => { + let seed_elements = manual_seeds_to_punctuated(&variant.seeds); + let owner_seeds_elements = variant + .owner_seeds + .as_ref() + .map(|os| manual_seeds_to_seed_elements_vec(os)); + + let dummy_eq: syn::Token![=] = + syn::parse_quote!(=); + token_seed_specs.push(TokenSeedSpec { + variant: variant.ident.clone(), + _eq: dummy_eq, + is_token: Some(true), + seeds: seed_elements, + owner_seeds: owner_seeds_elements, + inner_type: None, + is_zero_copy: false, + }); + } + + ManualVariantKind::Ata => { + has_ata_fields = true; + } + } + } + + let pda_seeds = if pda_seed_specs.is_empty() { + None + } else { + Some(pda_seed_specs) + }; + + let token_seeds = if token_seed_specs.is_empty() { + None + } else { + Some(token_seed_specs) + }; + + Ok(( + compressible_accounts, + pda_seeds, + token_seeds, + instruction_data_specs, + has_mint_fields, + has_ata_fields, + pda_variant_code, + )) +} + +// ============================================================================= +// ENTRY POINT +// ============================================================================= + +/// Main entry point for `#[derive(LightProgram)]`. +pub fn derive_light_program_impl(input: DeriveInput) -> Result { + // 1. Parse the enum variants + let variants = parse_enum_variants(&input)?; + + if variants.is_empty() { + return Err(syn::Error::new_spanned( + &input, + "#[derive(LightProgram)] enum must have at least one variant", + )); + } + + // 2. Parse crate context for struct field lookup + let crate_ctx = CrateContext::parse_from_manifest()?; + + // 3. Build intermediate types + let ( + compressible_accounts, + pda_seeds, + token_seeds, + instruction_data, + has_mint_fields, + has_ata_fields, + pda_variant_code, + ) = build_intermediate_types(&variants, &crate_ctx)?; + + // 4. Generate all items using the shared function + let enum_name = &input.ident; + let items = generate_light_program_items( + compressible_accounts, + pda_seeds, + token_seeds, + instruction_data, + &crate_ctx, + has_mint_fields, + has_ata_fields, + pda_variant_code, + Some(enum_name), + )?; + + // 5. Combine into single TokenStream + // The derive output appears at the call site, so add the anchor import + let anchor_import = quote! { + use anchor_lang::prelude::*; + }; + + let mut output = TokenStream::new(); + output.extend(anchor_import); + for item in items { + output.extend(item); + } + + Ok(output) +} + +// ============================================================================= +// TESTS +// ============================================================================= + +#[cfg(test)] +mod tests { + use super::*; + use quote::format_ident; + + fn parse_derive_input(input: &str) -> DeriveInput { + syn::parse_str(input).expect("Failed to parse derive input") + } + + // ========================================================================= + // PARSING TESTS: new #[light_account(...)] namespace syntax + // ========================================================================= + + #[test] + fn test_parse_pda_variant() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::seeds = [b"record", ctx.owner])] + Record(MinimalRecord), + } + "#, + ); + + let variants = parse_enum_variants(&input).expect("should parse"); + assert_eq!(variants.len(), 1); + assert_eq!(variants[0].ident.to_string(), "Record"); + assert!(matches!(variants[0].kind, ManualVariantKind::Pda)); + assert!(!variants[0].is_zero_copy); + assert_eq!(variants[0].seeds.len(), 2); + assert!(variants[0].inner_type.is_some()); + } + + #[test] + fn test_parse_zero_copy_variant() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::seeds = [b"zc_record", ctx.owner], pda::zero_copy)] + ZcRecord(ZeroCopyRecord), + } + "#, + ); + + let variants = parse_enum_variants(&input).expect("should parse"); + assert_eq!(variants.len(), 1); + assert!(variants[0].is_zero_copy); + } + + #[test] + fn test_parse_token_variant() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(token::seeds = [VAULT_SEED, ctx.mint], token::owner_seeds = [AUTH_SEED])] + Vault, + } + "#, + ); + + let variants = parse_enum_variants(&input).expect("should parse"); + assert_eq!(variants.len(), 1); + assert!(matches!(variants[0].kind, ManualVariantKind::Token)); + assert!(variants[0].inner_type.is_none()); + assert_eq!(variants[0].seeds.len(), 2); + assert!(variants[0].owner_seeds.is_some()); + } + + #[test] + fn test_parse_ata_variant() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(associated_token)] + Ata, + } + "#, + ); + + let variants = parse_enum_variants(&input).expect("should parse"); + assert_eq!(variants.len(), 1); + assert!(matches!(variants[0].kind, ManualVariantKind::Ata)); + } + + #[test] + fn test_parse_mixed_enum() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::seeds = [b"record", ctx.owner])] + Record(MinimalRecord), + + #[light_account(token::seeds = [VAULT_SEED, ctx.mint], token::owner_seeds = [AUTH_SEED])] + Vault, + + #[light_account(associated_token)] + Ata, + } + "#, + ); + + let variants = parse_enum_variants(&input).expect("should parse"); + assert_eq!(variants.len(), 3); + } + + #[test] + fn test_error_missing_inner_type_for_pda() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::seeds = [b"record", ctx.owner])] + Record, + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + assert!(err_msg.contains("tuple variant"), "Error: {}", err_msg); + } + + #[test] + fn test_error_missing_seeds_for_pda() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::zero_copy)] + Record(MinimalRecord), + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + assert!( + err_msg.contains("pda::seeds"), + "Error: {}", + err_msg + ); + } + + #[test] + fn test_error_missing_attribute() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + Record(MinimalRecord), + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + } + + #[test] + fn test_seed_classification() { + // Test byte literal + let byte_lit: syn::LitByteStr = syn::parse_quote!(b"seed"); + let seed = ManualSeed::ByteLiteral(byte_lit); + let classified = manual_seed_to_classified(&seed); + assert!(matches!(classified, ClassifiedSeed::Literal(_))); + + // Test string literal + let str_lit: syn::LitStr = syn::parse_quote!("seed"); + let seed = ManualSeed::StrLiteral(str_lit); + let classified = manual_seed_to_classified(&seed); + assert!(matches!(classified, ClassifiedSeed::Literal(_))); + + // Test constant + let path: syn::Path = syn::parse_quote!(MY_SEED); + let seed = ManualSeed::Constant(path); + let classified = manual_seed_to_classified(&seed); + assert!(matches!(classified, ClassifiedSeed::Constant { .. })); + + // Test ctx field + let ident = format_ident!("owner"); + let seed = ManualSeed::CtxField(ident); + let classified = manual_seed_to_classified(&seed); + assert!(matches!(classified, ClassifiedSeed::CtxRooted { .. })); + + // Test data field + let ident = format_ident!("owner"); + let seed = ManualSeed::DataField(ident); + let classified = manual_seed_to_classified(&seed); + assert!(matches!(classified, ClassifiedSeed::DataRooted { .. })); + } + + #[test] + fn test_string_literal_seeds() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::seeds = ["record_seed", ctx.owner])] + Record(MinimalRecord), + } + "#, + ); + + let variants = parse_enum_variants(&input).expect("should parse"); + assert_eq!(variants[0].seeds.len(), 2); + assert!(matches!(variants[0].seeds[0], ManualSeed::StrLiteral(_))); + } + + #[test] + fn test_data_field_seeds() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::seeds = [b"record", data.some_key])] + Record(MinimalRecord), + } + "#, + ); + + let variants = parse_enum_variants(&input).expect("should parse"); + assert_eq!(variants[0].seeds.len(), 2); + assert!(matches!(variants[0].seeds[1], ManualSeed::DataField(_))); + } + + #[test] + fn test_constant_path_seeds() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::seeds = [RECORD_SEED, ctx.owner])] + Record(MinimalRecord), + } + "#, + ); + + let variants = parse_enum_variants(&input).expect("should parse"); + assert!(matches!(variants[0].seeds[0], ManualSeed::Constant(_))); + } + + // ========================================================================= + // BUILDER TESTS: verify build_intermediate_types for each configuration + // ========================================================================= + + fn parse_and_build( + input_str: &str, + ) -> ( + Vec, + Option>, + Option>, + Vec, + bool, + bool, + TokenStream, + ) { + let input = parse_derive_input(input_str); + let variants = parse_enum_variants(&input).expect("should parse"); + let crate_ctx = CrateContext::empty(); + build_intermediate_types(&variants, &crate_ctx).expect("should build") + } + + /// 1 PDA: verify compressible_accounts, pda_seeds, no token/mint/ata + #[test] + fn test_build_single_pda() { + let (accounts, pda_seeds, token_seeds, _instr_data, has_mint, has_ata, _variant_code) = + parse_and_build( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::seeds = [b"minimal_record", ctx.owner])] + MinimalRecord(MinimalRecord), + } + "#, + ); + + assert_eq!(accounts.len(), 1, "should have 1 compressible account"); + assert!(!accounts[0].is_zero_copy, "should not be zero_copy"); + assert!(pda_seeds.is_some(), "should have pda_seeds"); + assert_eq!(pda_seeds.as_ref().unwrap().len(), 1); + assert_eq!( + pda_seeds.as_ref().unwrap()[0].variant.to_string(), + "MinimalRecord" + ); + assert!(token_seeds.is_none(), "should have no token_seeds"); + assert!(!has_mint, "should not have mint"); + assert!(!has_ata, "should not have ata"); + } + + /// 1 ATA: verify has_ata_fields=true, nothing else + #[test] + fn test_build_single_ata() { + let (accounts, pda_seeds, token_seeds, _instr_data, has_mint, has_ata, _variant_code) = + parse_and_build( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(associated_token)] + Ata, + } + "#, + ); + + assert!(accounts.is_empty(), "should have no compressible accounts"); + assert!(pda_seeds.is_none(), "should have no pda_seeds"); + assert!(token_seeds.is_none(), "should have no token_seeds"); + assert!(!has_mint, "should not have mint"); + assert!(has_ata, "should have ata"); + } + + /// 1 token PDA: verify token_seeds, no pda_seeds/mint/ata + #[test] + fn test_build_single_token_pda() { + let (accounts, pda_seeds, token_seeds, _instr_data, has_mint, has_ata, _variant_code) = + parse_and_build( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(token::seeds = [VAULT_SEED, ctx.mint], token::owner_seeds = [VAULT_AUTH_SEED])] + Vault, + } + "#, + ); + + assert!(accounts.is_empty(), "should have no compressible accounts"); + assert!(pda_seeds.is_none(), "should have no pda_seeds"); + assert!(token_seeds.is_some(), "should have token_seeds"); + assert_eq!(token_seeds.as_ref().unwrap().len(), 1); + let ts = &token_seeds.as_ref().unwrap()[0]; + assert_eq!(ts.variant.to_string(), "Vault"); + assert_eq!(ts.is_token, Some(true)); + assert!(ts.owner_seeds.is_some(), "should have owner_seeds"); + assert!(!has_mint, "should not have mint"); + assert!(!has_ata, "should not have ata"); + } + + /// 1 account loader (zero_copy PDA): verify is_zero_copy flag + #[test] + fn test_build_single_account_loader() { + let (accounts, pda_seeds, token_seeds, _instr_data, has_mint, has_ata, _variant_code) = + parse_and_build( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::seeds = [RECORD_SEED, ctx.owner], pda::zero_copy)] + ZeroCopyRecord(ZeroCopyRecord), + } + "#, + ); + + assert_eq!(accounts.len(), 1, "should have 1 compressible account"); + assert!(accounts[0].is_zero_copy, "should be zero_copy"); + assert!(pda_seeds.is_some(), "should have pda_seeds"); + assert_eq!(pda_seeds.as_ref().unwrap().len(), 1); + assert!( + pda_seeds.as_ref().unwrap()[0].is_zero_copy, + "seed spec should be zero_copy" + ); + assert!(token_seeds.is_none(), "should have no token_seeds"); + assert!(!has_mint, "should not have mint"); + assert!(!has_ata, "should not have ata"); + } + + /// Combined: 1 pda + 1 ata + 1 token pda + 1 account loader + #[test] + fn test_parse_full_combined() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::seeds = [b"minimal_record", ctx.owner])] + MinimalRecord(MinimalRecord), + + #[light_account(associated_token)] + Ata, + + #[light_account(token::seeds = [VAULT_SEED, ctx.mint], token::owner_seeds = [VAULT_AUTH_SEED])] + Vault, + + #[light_account(pda::seeds = [RECORD_SEED, ctx.owner], pda::zero_copy)] + ZeroCopyRecord(ZeroCopyRecord), + } + "#, + ); + + let variants = parse_enum_variants(&input).expect("should parse"); + assert_eq!(variants.len(), 4); + + assert!(matches!(variants[0].kind, ManualVariantKind::Pda)); + assert!(!variants[0].is_zero_copy); + + assert!(matches!(variants[1].kind, ManualVariantKind::Ata)); + + assert!(matches!(variants[2].kind, ManualVariantKind::Token)); + assert!(variants[2].owner_seeds.is_some()); + + assert!(matches!(variants[3].kind, ManualVariantKind::Pda)); + assert!(variants[3].is_zero_copy); + } + + #[test] + fn test_build_full_combined() { + let (accounts, pda_seeds, token_seeds, _instr_data, has_mint, has_ata, _variant_code) = + parse_and_build( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::seeds = [b"minimal_record", ctx.owner])] + MinimalRecord(MinimalRecord), + + #[light_account(associated_token)] + Ata, + + #[light_account(token::seeds = [VAULT_SEED, ctx.mint], token::owner_seeds = [VAULT_AUTH_SEED])] + Vault, + + #[light_account(pda::seeds = [RECORD_SEED, ctx.owner], pda::zero_copy)] + ZeroCopyRecord(ZeroCopyRecord), + } + "#, + ); + + // 2 PDA variants (one regular, one zero_copy) + assert_eq!(accounts.len(), 2, "should have 2 compressible accounts"); + assert!(!accounts[0].is_zero_copy, "first account is regular PDA"); + assert!(accounts[1].is_zero_copy, "second account is zero_copy"); + + // PDA seeds + assert!(pda_seeds.is_some(), "should have pda_seeds"); + let pda = pda_seeds.as_ref().unwrap(); + assert_eq!(pda.len(), 2, "should have 2 pda seed specs"); + assert_eq!(pda[0].variant.to_string(), "MinimalRecord"); + assert!(!pda[0].is_zero_copy); + assert_eq!(pda[1].variant.to_string(), "ZeroCopyRecord"); + assert!(pda[1].is_zero_copy); + + // Token seeds + assert!(token_seeds.is_some(), "should have token_seeds"); + let tok = token_seeds.as_ref().unwrap(); + assert_eq!(tok.len(), 1, "should have 1 token seed spec"); + assert_eq!(tok[0].variant.to_string(), "Vault"); + assert_eq!(tok[0].is_token, Some(true)); + assert!(tok[0].owner_seeds.is_some()); + + // Flags + assert!(!has_mint, "should not have mint"); + assert!(has_ata, "should have ata"); + } + + // ========================================================================= + // SEED ELEMENT CONVERSION TESTS + // ========================================================================= + + #[test] + fn test_seed_element_conversions() { + // ByteLiteral -> SeedElement::Expression + let byte_seed = ManualSeed::ByteLiteral(syn::parse_quote!(b"test")); + let elem = manual_seed_to_seed_element(&byte_seed); + assert!( + matches!(elem, SeedElement::Expression(_)), + "byte literal -> Expression" + ); + + // StrLiteral -> SeedElement::Literal + let str_seed = ManualSeed::StrLiteral(syn::parse_quote!("test")); + let elem = manual_seed_to_seed_element(&str_seed); + assert!( + matches!(elem, SeedElement::Literal(_)), + "str literal -> Literal" + ); + + // Constant -> SeedElement::Expression + let const_seed = ManualSeed::Constant(syn::parse_quote!(MY_CONSTANT)); + let elem = manual_seed_to_seed_element(&const_seed); + assert!( + matches!(elem, SeedElement::Expression(_)), + "constant -> Expression" + ); + + // CtxField -> SeedElement::Expression + let ctx_seed = ManualSeed::CtxField(format_ident!("owner")); + let elem = manual_seed_to_seed_element(&ctx_seed); + assert!( + matches!(elem, SeedElement::Expression(_)), + "ctx field -> Expression" + ); + + // DataField -> SeedElement::Expression + let data_seed = ManualSeed::DataField(format_ident!("key")); + let elem = manual_seed_to_seed_element(&data_seed); + assert!( + matches!(elem, SeedElement::Expression(_)), + "data field -> Expression" + ); + } + + #[test] + fn test_manual_seeds_to_punctuated() { + let seeds = vec![ + ManualSeed::ByteLiteral(syn::parse_quote!(b"prefix")), + ManualSeed::CtxField(format_ident!("owner")), + ManualSeed::Constant(syn::parse_quote!(EXTRA_SEED)), + ]; + + let punctuated = manual_seeds_to_punctuated(&seeds); + assert_eq!(punctuated.len(), 3); + } + + // ========================================================================= + // ERROR CASE TESTS + // ========================================================================= + + /// Token variant without seeds should fail + #[test] + fn test_error_token_missing_seeds() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(token::owner_seeds = [AUTH_SEED])] + Vault, + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + assert!( + err_msg.contains("token::seeds"), + "Error: {}", + err_msg + ); + } + + /// Token variant without owner_seeds should fail + #[test] + fn test_error_token_missing_owner_seeds() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(token::seeds = [VAULT_SEED])] + Vault, + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + assert!( + err_msg.contains("token::owner_seeds"), + "Error: {}", + err_msg + ); + } + + /// PDA variant with unit type (no tuple field) should fail + #[test] + fn test_error_pda_unit_variant() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::seeds = [b"test"])] + Record, + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + } + + /// Token variant with fields should fail + #[test] + fn test_error_token_with_fields() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(token::seeds = [SEED], token::owner_seeds = [AUTH])] + Vault(SomeType), + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + assert!( + err_msg.contains("unit variant"), + "Error: {}", + err_msg + ); + } + + /// ATA variant with fields should fail + #[test] + fn test_error_ata_with_fields() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(associated_token)] + Ata(SomeType), + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + } + + /// Standalone mint keyword should fail (mints handled by Light Token Program) + #[test] + fn test_error_mint_keyword_rejected() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(mint)] + MintAccount, + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + assert!( + err_msg.contains("Unknown keyword") || err_msg.contains("Light Token Program"), + "Error: {}", + err_msg + ); + } + + /// Unknown keyword should fail + #[test] + fn test_error_unknown_keyword() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(unknown)] + Something, + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + assert!( + err_msg.contains("Unknown keyword"), + "Error: {}", + err_msg + ); + } + + /// Derive on struct (not enum) should fail + #[test] + fn test_error_struct_not_enum() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub struct NotAnEnum { + pub field: u64, + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + assert!( + err_msg.contains("can only be applied to enums"), + "Error: {}", + err_msg + ); + } + + /// Empty enum should parse but derive_light_program_impl should fail + #[test] + fn test_error_empty_enum() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts {} + "#, + ); + + let variants = parse_enum_variants(&input).expect("empty enum parses"); + assert!(variants.is_empty()); + } + + // ========================================================================= + // NAMESPACE VALIDATION TESTS + // ========================================================================= + + /// Mixed namespaces on same variant should fail + #[test] + fn test_error_mixed_namespaces() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::seeds = [b"test"], token::seeds = [SEED])] + Mixed(SomeType), + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + assert!( + err_msg.contains("Mixed namespaces"), + "Error: {}", + err_msg + ); + } + + /// Unknown namespace should fail + #[test] + fn test_error_unknown_namespace() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(foo::seeds = [b"test"])] + Something(SomeType), + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + assert!( + err_msg.contains("Unknown namespace") || err_msg.contains("foo"), + "Error: {}", + err_msg + ); + } + + /// Unknown key within valid namespace should fail + #[test] + fn test_error_unknown_key_in_namespace() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::invalid = [b"test"])] + Something(SomeType), + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + assert!( + err_msg.contains("invalid") && err_msg.contains("pda"), + "Error: {}", + err_msg + ); + } + + /// Duplicate keys should fail + #[test] + fn test_error_duplicate_key() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::seeds = [b"a"], pda::seeds = [b"b"])] + Something(SomeType), + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + assert!( + err_msg.contains("Duplicate"), + "Error: {}", + err_msg + ); + } + + /// Bare seeds keyword (without namespace) should fail + #[test] + fn test_error_bare_seeds_keyword() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(seeds = [b"test"])] + Something(SomeType), + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + assert!( + err_msg.contains("Unknown keyword") || err_msg.contains("seeds"), + "Error: {}", + err_msg + ); + } + + /// Bare pda keyword (old syntax) should fail + #[test] + fn test_error_bare_pda_keyword() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda, seeds = [b"test"])] + Something(SomeType), + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + assert!( + err_msg.contains("pda") && (err_msg.contains("Unknown keyword") || err_msg.contains("namespaced")), + "Error: {}", + err_msg + ); + } + + /// associated_token standalone keyword works + #[test] + fn test_associated_token_keyword() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(associated_token)] + Ata, + } + "#, + ); + + let variants = parse_enum_variants(&input).expect("should parse"); + assert_eq!(variants.len(), 1); + assert!(matches!(variants[0].kind, ManualVariantKind::Ata)); + } + + /// Bare keyword in middle position should fail + #[test] + fn test_error_bare_keyword_in_middle() { + let input = parse_derive_input( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::seeds = [b"test"], zero_copy)] + Something(SomeType), + } + "#, + ); + + let result = parse_enum_variants(&input); + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + assert!( + err_msg.contains("Unexpected keyword") || err_msg.contains("namespaced"), + "Error: {}", + err_msg + ); + } + + // ========================================================================= + // DATA FIELD EXTRACTION TESTS + // ========================================================================= + + /// PDA with data.field seeds should generate InstructionDataSpecs + #[test] + fn test_build_data_field_extraction() { + let (_, _, _, instr_data, _, _, _) = parse_and_build( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::seeds = [b"record", data.some_key, data.another_key])] + Record(MinimalRecord), + } + "#, + ); + + assert_eq!(instr_data.len(), 2, "should extract 2 data fields"); + let names: Vec = instr_data.iter().map(|s| s.field_name.to_string()).collect(); + assert!(names.contains(&"some_key".to_string())); + assert!(names.contains(&"another_key".to_string())); + } + + /// Duplicate data fields across variants should be deduplicated + #[test] + fn test_build_dedup_data_fields() { + let (_, _, _, instr_data, _, _, _) = parse_and_build( + r#" + #[derive(LightProgram)] + pub enum ProgramAccounts { + #[light_account(pda::seeds = [b"record_a", data.owner])] + RecordA(RecordA), + + #[light_account(pda::seeds = [b"record_b", data.owner])] + RecordB(RecordB), + } + "#, + ); + + assert_eq!( + instr_data.len(), + 1, + "duplicate data.owner should be deduplicated" + ); + assert_eq!(instr_data[0].field_name.to_string(), "owner"); + } +} diff --git a/sdk-libs/macros/src/light_pdas/program/instructions.rs b/sdk-libs/macros/src/light_pdas/program/instructions.rs index 428a26b341..c3551caa63 100644 --- a/sdk-libs/macros/src/light_pdas/program/instructions.rs +++ b/sdk-libs/macros/src/light_pdas/program/instructions.rs @@ -4,19 +4,21 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::{Item, ItemMod, Result}; -// Re-export types from parsing for external use +// Re-export types from parsing, compress, and variant_enum for external use +pub use super::compress::CompressibleAccountInfo; pub use super::parsing::{ extract_ctx_seed_fields, extract_data_seed_fields, InstructionDataSpec, InstructionVariant, SeedElement, TokenSeedSpec, }; +pub use super::variant_enum::PdaCtxSeedInfo; use super::{ - compress::{CompressBuilder, CompressibleAccountInfo}, + compress::CompressBuilder, decompress::DecompressBuilder, parsing::{ convert_classified_to_seed_elements, convert_classified_to_seed_elements_vec, extract_context_and_params, macro_error, wrap_function_with_light, }, - variant_enum::{LightVariantBuilder, PdaCtxSeedInfo}, + variant_enum::LightVariantBuilder, }; use crate::{ light_pdas::shared_utils::{ident_to_type, qualify_type_with_crate}, @@ -27,11 +29,13 @@ use crate::{ // MAIN CODEGEN // ============================================================================= -/// Orchestrates all code generation for the rentfree module. +/// Shared code generation used by both `#[light_program]` and `#[derive(LightProgram)]`. +/// +/// Returns a `Vec` of all generated items (enums, structs, trait impls, +/// instruction handlers, etc.) that can be injected into a module or returned directly. #[inline(never)] #[allow(clippy::too_many_arguments)] -fn codegen( - module: &mut ItemMod, +pub(crate) fn generate_light_program_items( compressible_accounts: Vec, pda_seeds: Option>, token_seeds: Option>, @@ -40,20 +44,8 @@ fn codegen( has_mint_fields: bool, has_ata_fields: bool, pda_variant_code: TokenStream, -) -> Result { - let content = match module.content.as_mut() { - Some(content) => content, - None => return Err(macro_error!(module, "Module must have a body")), - }; - - // Insert anchor_lang::prelude::* import at the beginning of the module - // This ensures Accounts, Signer, AccountInfo, Result, error_code etc. are in scope - // for the generated code (structs, enums, functions). - let anchor_import: syn::Item = syn::parse_quote! { - use anchor_lang::prelude::*; - }; - content.1.insert(0, anchor_import); - + enum_name: Option<&syn::Ident>, +) -> Result> { // TODO: Unify seed extraction - currently #[light_program] extracts seeds from Anchor's // #[account(seeds = [...])] automatically, while #[derive(LightAccounts)] requires // explicit token::seeds = [...] in #[light_account]. Consider removing the duplicate @@ -358,8 +350,8 @@ fn codegen( (false, false, true, _) => InstructionVariant::MintOnly, (false, false, false, true) => InstructionVariant::AtaOnly, (false, false, false, false) => { - return Err(macro_error!( - module, + return Err(syn::Error::new( + proc_macro2::Span::call_site(), "No #[light_account(init)], #[light_account(init, mint::...)], #[light_account(init, associated_token::...)], or #[light_account(token::...)] fields found.\n\ At least one light account field must be provided." )) @@ -513,27 +505,20 @@ fn codegen( let init_config_instruction: syn::ItemFn = syn::parse_quote! { #[inline(never)] - #[allow(clippy::too_many_arguments)] pub fn initialize_compression_config<'info>( ctx: Context<'_, '_, '_, 'info, InitializeCompressionConfig<'info>>, - write_top_up: u32, - rent_sponsor: Pubkey, - compression_authority: Pubkey, - rent_config: ::light_sdk::interface::rent::RentConfig, - address_space: Vec, + instruction_data: Vec, ) -> Result<()> { + let remaining = [ + ctx.accounts.payer.to_account_info(), + ctx.accounts.config.to_account_info(), + ctx.accounts.program_data.to_account_info(), + ctx.accounts.authority.to_account_info(), + ctx.accounts.system_program.to_account_info(), + ]; light_sdk::interface::process_initialize_light_config_checked( - &ctx.accounts.config.to_account_info(), - &ctx.accounts.authority.to_account_info(), - &ctx.accounts.program_data.to_account_info(), - &rent_sponsor, - &compression_authority, - rent_config, - write_top_up, - address_space, - 0, - &ctx.accounts.payer.to_account_info(), - &ctx.accounts.system_program.to_account_info(), + &remaining, + &instruction_data, &crate::ID, )?; Ok(()) @@ -542,25 +527,17 @@ fn codegen( let update_config_instruction: syn::ItemFn = syn::parse_quote! { #[inline(never)] - #[allow(clippy::too_many_arguments)] pub fn update_compression_config<'info>( ctx: Context<'_, '_, '_, 'info, UpdateCompressionConfig<'info>>, - new_rent_sponsor: Option, - new_compression_authority: Option, - new_rent_config: Option<::light_sdk::interface::rent::RentConfig>, - new_write_top_up: Option, - new_address_space: Option>, - new_update_authority: Option, + instruction_data: Vec, ) -> Result<()> { + let remaining = [ + ctx.accounts.config.to_account_info(), + ctx.accounts.update_authority.to_account_info(), + ]; light_sdk::interface::process_update_light_config( - ctx.accounts.config.as_ref(), - ctx.accounts.update_authority.as_ref(), - new_update_authority.as_ref(), - new_rent_sponsor.as_ref(), - new_compression_authority.as_ref(), - new_rent_config, - new_write_top_up, - new_address_space, + &remaining, + &instruction_data, &crate::ID, )?; Ok(()) @@ -573,81 +550,124 @@ fn codegen( &instruction_data, )?; - // Insert SeedParams struct and impl - let seed_params_file: syn::File = syn::parse2(seed_params_struct)?; - for item in seed_params_file.items { - content.1.push(item); - } + // Collect all generated items into a Vec + let mut items: Vec = Vec::new(); - // Insert XxxSeeds structs and LightAccountVariant constructors + // SeedParams struct and impl + items.push(seed_params_struct); + + // XxxSeeds structs and LightAccountVariant constructors for seeds_tokens in seeds_structs_and_constructors.into_iter() { - let wrapped: syn::File = syn::parse2(seeds_tokens)?; - for item in wrapped.items { - content.1.push(item); - } + items.push(seeds_tokens); } - // Insert PDA variant structs directly into the module. - // The variant code uses fully qualified paths (crate::CONSTANT) for all - // constant references, so no additional imports are needed. + // PDA variant structs (variant code uses fully qualified paths) if !pda_variant_code.is_empty() { - let wrapped: syn::File = syn::parse2(pda_variant_code)?; - for item in wrapped.items { - content.1.push(item); - } + items.push(pda_variant_code); } - content.1.push(Item::Verbatim(size_validation_checks)); - content.1.push(Item::Verbatim(enum_and_traits)); - content.1.push(Item::Struct(decompress_accounts)); - content.1.push(Item::Verbatim( - decompress_builder.generate_accounts_trait_impls()?, - )); + items.push(size_validation_checks); + items.push(enum_and_traits); + items.push(quote! { #decompress_accounts }); + items.push(decompress_builder.generate_accounts_trait_impls()?); if let Some(trait_impls) = trait_impls { - content.1.push(Item::Mod(trait_impls)); + items.push(quote! { #trait_impls }); } - content.1.push(Item::Mod(processor_module)); + items.push(quote! { #processor_module }); if let Some(decompress_instruction) = decompress_instruction { - content.1.push(Item::Fn(decompress_instruction)); + items.push(quote! { #decompress_instruction }); } - content.1.push(Item::Struct(compress_accounts)); - content.1.push(Item::Verbatim( - compress_builder.generate_accounts_trait_impls()?, - )); - content.1.push(Item::Fn(compress_instruction)); - content.1.push(Item::Struct(init_config_accounts)); - content.1.push(Item::Struct(update_config_accounts)); - content.1.push(Item::Fn(init_config_instruction)); - content.1.push(Item::Fn(update_config_instruction)); - - // Add pda seed provider impls + items.push(quote! { #compress_accounts }); + items.push(compress_builder.generate_accounts_trait_impls()?); + items.push(quote! { #compress_instruction }); + items.push(quote! { #init_config_accounts }); + items.push(quote! { #update_config_accounts }); + items.push(quote! { #init_config_instruction }); + items.push(quote! { #update_config_instruction }); + + // PDA seed provider impls for pda_impl in pda_seed_provider_impls.into_iter() { - let wrapped: syn::File = syn::parse2(pda_impl)?; - for item in wrapped.items { - content.1.push(item); - } + items.push(pda_impl); } - // Add ctoken seed provider impls (one per token variant) + // CToken seed provider impls (one per token variant) if let Some(ref seeds) = token_seeds { if !seeds.is_empty() { let impl_code = super::seed_codegen::generate_ctoken_seed_provider_implementation(seeds)?; - let impl_file: syn::File = syn::parse2(impl_code)?; - for item in impl_file.items { - content.1.push(item); - } + items.push(impl_code); } } - // Add error codes - let error_item: syn::ItemEnum = syn::parse2(error_codes)?; - content.1.push(Item::Enum(error_item)); + // Error codes + items.push(error_codes); + + // Client functions (module + pub use statement) + items.push(client_functions); + + // Generate enum dispatch methods for #[derive(LightProgram)] + if let Some(enum_name) = enum_name { + // Compress dispatch: impl EnumName { pub fn compress_dispatch(...) } + if compress_builder.has_pdas() { + items.push(compress_builder.generate_enum_dispatch_method(enum_name)?); + } + + // Decompress dispatch: impl EnumName { pub fn decompress_dispatch(...) } + if !pda_ctx_seeds.is_empty() { + items.push(decompress_builder.generate_enum_decompress_dispatch(enum_name)?); + } + } + + Ok(items) +} + +/// Thin wrapper around `generate_light_program_items` that injects items into a module. +/// +/// Used by `#[light_program]` attribute macro. +#[inline(never)] +#[allow(clippy::too_many_arguments)] +fn codegen( + module: &mut ItemMod, + compressible_accounts: Vec, + pda_seeds: Option>, + token_seeds: Option>, + instruction_data: Vec, + crate_ctx: &crate::light_pdas::parsing::CrateContext, + has_mint_fields: bool, + has_ata_fields: bool, + pda_variant_code: TokenStream, +) -> Result { + let content = match module.content.as_mut() { + Some(content) => content, + None => return Err(macro_error!(module, "Module must have a body")), + }; + + // Insert anchor_lang::prelude::* import at the beginning of the module + let anchor_import: syn::Item = syn::parse_quote! { + use anchor_lang::prelude::*; + }; + content.1.insert(0, anchor_import); - // Add client functions (module + pub use statement) - let client_file: syn::File = syn::parse2(client_functions)?; - for item in client_file.items { - content.1.push(item); + // Generate all items using the shared function + // #[light_program] attribute macro doesn't have an enum name - pass None + let generated_items = generate_light_program_items( + compressible_accounts, + pda_seeds, + token_seeds, + instruction_data, + crate_ctx, + has_mint_fields, + has_ata_fields, + pda_variant_code, + None, + )?; + + // Inject all generated items into the module + for item_tokens in generated_items { + let file: syn::File = syn::parse2(item_tokens)?; + for item in file.items { + content.1.push(item); + } } Ok(quote! { #module }) diff --git a/sdk-libs/macros/src/light_pdas/program/mod.rs b/sdk-libs/macros/src/light_pdas/program/mod.rs index 24d5e1a708..67cbe616b9 100644 --- a/sdk-libs/macros/src/light_pdas/program/mod.rs +++ b/sdk-libs/macros/src/light_pdas/program/mod.rs @@ -5,8 +5,9 @@ //! - Auto-wraps instruction handlers with light_pre_init/light_finalize logic //! - Generates all necessary types, enums, and instruction handlers -mod compress; +pub(crate) mod compress; mod decompress; +pub mod derive_light_program; pub mod expr_traversal; pub mod instructions; pub mod seed_codegen; @@ -17,4 +18,5 @@ pub mod variant_enum; pub(crate) mod parsing; pub(crate) mod visitors; +pub use derive_light_program::derive_light_program_impl; pub use instructions::light_program_impl; diff --git a/sdk-libs/sdk-interface/Cargo.toml b/sdk-libs/sdk-interface/Cargo.toml index d138a0e770..052131ebb7 100644 --- a/sdk-libs/sdk-interface/Cargo.toml +++ b/sdk-libs/sdk-interface/Cargo.toml @@ -10,83 +10,25 @@ edition = "2021" crate-type = ["cdylib", "lib"] [features] -default = ["solana", "std"] -solana = [ - "light-account-checks/solana", - "light-compressible/solana", - "dep:solana-account-info", - "dep:solana-pubkey", - "dep:solana-cpi", - "dep:solana-instruction", - "dep:solana-system-interface", - "dep:solana-program-error", - "dep:solana-clock", - "dep:solana-sysvar", - "dep:solana-loader-v3-interface", - "dep:solana-msg", - "dep:bincode", -] -pinocchio = [ - "light-account-checks/pinocchio", - "light-compressible/pinocchio", - "dep:pinocchio", - "dep:pinocchio-system", -] +default = ["std"] std = [ "alloc", "light-compressed-account/std", "light-sdk-types/std", ] alloc = ["light-compressed-account/alloc"] -anchor = [ - "anchor-lang", - "light-compressed-account/anchor", - "light-sdk-types/anchor", - "light-compressible/anchor", - "light-token-interface/anchor", -] poseidon = ["light-hasher/poseidon", "light-compressed-account/poseidon"] keccak = ["light-hasher/keccak", "light-compressed-account/keccak"] sha256 = ["light-hasher/sha256", "light-compressed-account/sha256"] -idl-build = [ - "anchor", - "anchor-lang/idl-build", - "light-compressed-account/idl-build", - "light-sdk-types/idl-build", - "light-compressible/idl-build", - "light-token-interface/idl-build", -] +token = ["dep:light-token-interface"] [dependencies] -# Light Protocol internal -light-account-checks = { workspace = true } +light-account-checks = { workspace = true, default-features = false } light-sdk-types = { workspace = true, default-features = false, features = ["v2", "cpi-context"] } light-compressed-account = { workspace = true, default-features = false } light-compressible = { workspace = true, default-features = false } light-hasher = { workspace = true, default-features = false } -light-token-interface = { workspace = true, default-features = false } - -# Solana-specific (behind solana feature) -solana-account-info = { workspace = true, optional = true } -solana-pubkey = { workspace = true, optional = true } -solana-cpi = { workspace = true, optional = true } -solana-instruction = { workspace = true, optional = true } -solana-system-interface = { workspace = true, optional = true } -solana-program-error = { workspace = true, optional = true } -solana-clock = { workspace = true, optional = true } -solana-sysvar = { workspace = true, optional = true, features = ["bincode"] } -solana-loader-v3-interface = { workspace = true, optional = true } -solana-msg = { workspace = true, optional = true } -bincode = { version = "1", optional = true } - -# Pinocchio-specific (behind pinocchio feature) -pinocchio = { workspace = true, optional = true } -pinocchio-system = { workspace = true, optional = true } - -# Anchor (optional) -anchor-lang = { workspace = true, optional = true } - -# Third-party +light-token-interface = { workspace = true, optional = true } borsh = { workspace = true } bytemuck = { workspace = true } thiserror = { workspace = true } @@ -95,5 +37,5 @@ thiserror = { workspace = true } level = "allow" check-cfg = [ 'cfg(target_os, values("solana"))', - 'cfg(feature, values("frozen-abi", "no-entrypoint"))', + 'cfg(feature, values("frozen-abi", "no-entrypoint", "token"))', ] diff --git a/sdk-libs/sdk-interface/src/.backup/account/token_seeds.rs b/sdk-libs/sdk-interface/src/.backup/account/token_seeds.rs new file mode 100644 index 0000000000..c6cb257279 --- /dev/null +++ b/sdk-libs/sdk-interface/src/.backup/account/token_seeds.rs @@ -0,0 +1,261 @@ +use light_compressed_account::compressed_account::PackedMerkleContext; +use light_sdk_types::instruction::PackedStateTreeInfo; +pub use light_token_interface::{ + instructions::{ + extensions::{CompressedOnlyExtensionInstructionData, ExtensionInstructionData}, + transfer2::MultiInputTokenDataWithContext, + }, + state::{ + extensions::{CompressedOnlyExtension, ExtensionStruct}, + AccountState, Token, TokenDataVersion, + }, +}; +use solana_account_info::AccountInfo; +use solana_program_error::ProgramError; +use solana_pubkey::Pubkey; + +use super::pack::Unpack; +// Pack trait and PackedAccounts only available off-chain (client-side packing) +#[cfg(not(target_os = "solana"))] +use crate::{account::pack::Pack, instruction::PackedAccounts}; +use crate::{ + program::variant::{ + LightAccountVariantTrait, PackedLightAccountVariantTrait, PackedTokenSeeds, + UnpackedTokenSeeds, + }, + account::light_account::AccountType, + AnchorDeserialize, AnchorSerialize, +}; + +#[derive(Clone, Debug, AnchorSerialize, AnchorDeserialize)] +pub struct TokenDataWithSeeds { + pub seeds: S, + pub token_data: Token, +} +#[repr(C)] +#[derive(Debug, Copy, Clone, Default, PartialEq, AnchorSerialize, AnchorDeserialize)] +pub struct PackedTokenData { + pub owner: u8, + pub amount: u64, + pub has_delegate: bool, // Optional delegate is set + pub delegate: u8, + pub mint: u8, + pub version: u8, +} + +#[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone)] +pub struct TokenDataWithPackedSeeds< + S: Unpack + AnchorSerialize + AnchorDeserialize + Clone + std::fmt::Debug, +> { + pub seeds: S, + pub token_data: PackedTokenData, + pub extension: Option, +} + +#[cfg(not(target_os = "solana"))] +impl Pack for TokenDataWithSeeds +where + S: Pack, + S::Packed: Unpack + AnchorDeserialize + AnchorSerialize + Clone + std::fmt::Debug, +{ + type Packed = TokenDataWithPackedSeeds; + + fn pack(&self, remaining_accounts: &mut PackedAccounts) -> Result { + let seeds = self.seeds.pack(remaining_accounts)?; + + let owner_index = remaining_accounts + .insert_or_get(Pubkey::new_from_array(self.token_data.owner.to_bytes())); + + let token_data = PackedTokenData { + owner: owner_index, + amount: self.token_data.amount, + has_delegate: self.token_data.delegate.is_some(), + delegate: self + .token_data + .delegate + .map(|d| remaining_accounts.insert_or_get(Pubkey::new_from_array(d.to_bytes()))) + .unwrap_or(0), + mint: remaining_accounts + .insert_or_get(Pubkey::new_from_array(self.token_data.mint.to_bytes())), + version: TokenDataVersion::ShaFlat as u8, + }; + + // Extract CompressedOnly extension from Token state if present. + let extension = self.token_data.extensions.as_ref().and_then(|exts| { + exts.iter().find_map(|ext| { + if let ExtensionStruct::CompressedOnly(co) = ext { + Some(CompressedOnlyExtensionInstructionData { + delegated_amount: co.delegated_amount, + withheld_transfer_fee: co.withheld_transfer_fee, + is_frozen: self.token_data.state == AccountState::Frozen, + compression_index: 0, + is_ata: co.is_ata != 0, + bump: 0, + owner_index, + }) + } else { + None + } + }) + }); + + Ok(TokenDataWithPackedSeeds { + seeds, + token_data, + extension, + }) + } +} + +impl Unpack for TokenDataWithPackedSeeds +where + S: Unpack + AnchorSerialize + AnchorDeserialize + Clone + std::fmt::Debug, +{ + type Unpacked = TokenDataWithSeeds; + + fn unpack(&self, remaining_accounts: &[AccountInfo]) -> Result { + let seeds = self.seeds.unpack(remaining_accounts)?; + + let owner_key = remaining_accounts + .get(self.token_data.owner as usize) + .ok_or(ProgramError::InvalidAccountData)? + .key; + let mint_key = remaining_accounts + .get(self.token_data.mint as usize) + .ok_or(ProgramError::InvalidAccountData)? + .key; + let delegate = if self.token_data.has_delegate { + let delegate_key = remaining_accounts + .get(self.token_data.delegate as usize) + .ok_or(ProgramError::InvalidAccountData)? + .key; + Some(light_compressed_account::Pubkey::from( + delegate_key.to_bytes(), + )) + } else { + None + }; + + // Reconstruct extensions from instruction extension data. + let extensions = self.extension.map(|ext| { + vec![ExtensionStruct::CompressedOnly(CompressedOnlyExtension { + delegated_amount: ext.delegated_amount, + withheld_transfer_fee: ext.withheld_transfer_fee, + is_ata: ext.is_ata as u8, + })] + }); + + let state = self.extension.map_or(AccountState::Initialized, |ext| { + if ext.is_frozen { + AccountState::Frozen + } else { + AccountState::Initialized + } + }); + + let delegated_amount = self.extension.map_or(0, |ext| ext.delegated_amount); + + let token_data = Token { + mint: light_compressed_account::Pubkey::from(mint_key.to_bytes()), + owner: light_compressed_account::Pubkey::from(owner_key.to_bytes()), + amount: self.token_data.amount, + delegate, + state, + is_native: None, + delegated_amount, + close_authority: None, + account_type: TokenDataVersion::ShaFlat as u8, + extensions, + }; + + Ok(TokenDataWithSeeds { seeds, token_data }) + } +} + +// ============================================================================= +// Blanket impls: LightAccountVariantTrait / PackedLightAccountVariantTrait +// for TokenDataWithSeeds / TokenDataWithPackedSeeds +// where S implements the seed-specific helper traits. +// ============================================================================= + +impl LightAccountVariantTrait for TokenDataWithSeeds +where + S: UnpackedTokenSeeds, + S::Packed: PackedTokenSeeds + Unpack, +{ + const PROGRAM_ID: Pubkey = S::PROGRAM_ID; + type Seeds = S; + type Data = Token; + type Packed = TokenDataWithPackedSeeds; + + fn data(&self) -> &Self::Data { + &self.token_data + } + + fn seed_vec(&self) -> Vec> { + self.seeds.seed_vec() + } + + fn seed_refs_with_bump<'a>(&'a self, bump_storage: &'a [u8; 1]) -> [&'a [u8]; N] { + self.seeds.seed_refs_with_bump(bump_storage) + } +} + +impl PackedLightAccountVariantTrait for TokenDataWithPackedSeeds +where + S: PackedTokenSeeds, + S::Unpacked: UnpackedTokenSeeds, +{ + type Unpacked = TokenDataWithSeeds; + + const ACCOUNT_TYPE: AccountType = AccountType::Token; + + fn bump(&self) -> u8 { + self.seeds.bump() + } + + fn unpack(&self, accounts: &[AccountInfo]) -> anchor_lang::Result { + ::unpack(self, accounts).map_err(anchor_lang::error::Error::from) + } + + fn seed_refs_with_bump<'a>( + &'a self, + accounts: &'a [AccountInfo], + bump_storage: &'a [u8; 1], + ) -> std::result::Result<[&'a [u8]; N], ProgramError> { + self.seeds.seed_refs_with_bump(accounts, bump_storage) + } + + fn into_in_token_data( + &self, + tree_info: &PackedStateTreeInfo, + output_queue_index: u8, + ) -> anchor_lang::Result { + Ok(MultiInputTokenDataWithContext { + amount: self.token_data.amount, + mint: self.token_data.mint, + owner: self.token_data.owner, + version: self.token_data.version, + has_delegate: self.token_data.has_delegate, + delegate: self.token_data.delegate, + root_index: tree_info.root_index, + merkle_context: PackedMerkleContext { + merkle_tree_pubkey_index: tree_info.merkle_tree_pubkey_index, + queue_pubkey_index: output_queue_index, + leaf_index: tree_info.leaf_index, + prove_by_index: tree_info.prove_by_index, + }, + }) + } + + fn into_in_tlv(&self) -> anchor_lang::Result>> { + Ok(self + .extension + .as_ref() + .map(|ext| vec![ExtensionInstructionData::CompressedOnly(*ext)])) + } + + fn derive_owner(&self) -> Pubkey { + self.seeds.derive_owner() + } +} diff --git a/sdk-libs/sdk-interface/src/accounts/create_pda.rs b/sdk-libs/sdk-interface/src/.backup/accounts/create_pda.rs similarity index 100% rename from sdk-libs/sdk-interface/src/accounts/create_pda.rs rename to sdk-libs/sdk-interface/src/.backup/accounts/create_pda.rs diff --git a/sdk-libs/sdk-interface/src/.backup/cpi/account.rs b/sdk-libs/sdk-interface/src/.backup/cpi/account.rs new file mode 100644 index 0000000000..00c425fba8 --- /dev/null +++ b/sdk-libs/sdk-interface/src/.backup/cpi/account.rs @@ -0,0 +1,85 @@ +use light_sdk_types::cpi_context_write::CpiContextWriteAccounts; + +use crate::cpi::v2::get_account_metas_from_config_cpi_context; +use crate::cpi::v1::{ + lowlevel::{get_account_metas_from_config, CpiInstructionConfig}, + CpiAccounts, +}; +use solana_account_info::AccountInfo; +use solana_instruction::AccountMeta; +use solana_program_error::ProgramError; + +/// Trait for types that can provide account information for CPI calls +pub trait CpiAccountsTrait<'info> { + /// Convert to a vector of AccountInfo references + fn to_account_infos(&self) -> Vec>; + + /// Generate account metas + fn to_account_metas(&self) -> Result, ProgramError>; + + /// Get the mode for the instruction (0 for v1, 1 for v2, None if unknown) + fn get_mode(&self) -> Option; +} + +// Implementation for CpiAccounts +impl<'info> CpiAccountsTrait<'info> for CpiAccounts<'_, 'info> { + fn to_account_infos(&self) -> Vec> { + self.to_account_infos() + } + + fn to_account_metas(&self) -> Result, ProgramError> { + let config = CpiInstructionConfig::try_from(self).map_err(ProgramError::from)?; + Ok(get_account_metas_from_config(config)) + } + + fn get_mode(&self) -> Option { + Some(0) // v1 mode + } +} + +// Implementation for &[AccountInfo] +impl<'info> CpiAccountsTrait<'info> for &[AccountInfo<'info>] { + fn to_account_infos(&self) -> Vec> { + self.to_vec() + } + + fn to_account_metas(&self) -> Result, ProgramError> { + // For raw account info slices, create simple account metas + // preserving the original signer and writable flags + Ok(self + .iter() + .map(|account| AccountMeta { + pubkey: *account.key, + is_signer: account.is_signer, + is_writable: account.is_writable, + }) + .collect()) + } + + fn get_mode(&self) -> Option { + None // Unknown mode for raw slices + } +} + +// Implementation for CpiContextWriteAccounts +impl<'a, 'info> CpiAccountsTrait<'info> for CpiContextWriteAccounts<'a, AccountInfo<'info>> { + fn to_account_infos(&self) -> Vec> { + vec![ + self.fee_payer.clone(), + self.authority.clone(), + self.cpi_context.clone(), + ] + } + + fn to_account_metas(&self) -> Result, ProgramError> { + // Use the helper function to generate the account metas + let metas = get_account_metas_from_config_cpi_context(self.clone()); + Ok(metas.to_vec()) + } + + fn get_mode(&self) -> Option { + // CPI context write accounts always use v2 mode (1) + // This type requires both the `v2` and `cpi-context` features + Some(1) + } +} diff --git a/sdk-libs/sdk-interface/src/.backup/cpi/invoke.rs b/sdk-libs/sdk-interface/src/.backup/cpi/invoke.rs new file mode 100644 index 0000000000..768d6ccfed --- /dev/null +++ b/sdk-libs/sdk-interface/src/.backup/cpi/invoke.rs @@ -0,0 +1,187 @@ +pub use light_compressed_account::LightInstructionData; +use light_sdk_types::constants::{CPI_AUTHORITY_PDA_SEED, LIGHT_SYSTEM_PROGRAM_ID}; +use solana_instruction::AccountMeta; +use solana_account_info::AccountInfo; +use solana_instruction::Instruction; +use solana_program_error::ProgramError; + +use crate::{ + cpi::{account::CpiAccountsTrait, instruction::LightCpiInstruction}, + error::LightPdaError, +}; +use solana_cpi::invoke_signed; + +pub trait InvokeLightSystemProgram { + fn invoke<'info>(self, accounts: impl CpiAccountsTrait<'info>) -> Result<(), ProgramError>; + fn invoke_write_to_cpi_context_first<'info>( + self, + accounts: impl CpiAccountsTrait<'info>, + ) -> Result<(), ProgramError>; + fn invoke_write_to_cpi_context_set<'info>( + self, + accounts: impl CpiAccountsTrait<'info>, + ) -> Result<(), ProgramError>; + fn invoke_execute_cpi_context<'info>( + self, + accounts: impl CpiAccountsTrait<'info>, + ) -> Result<(), ProgramError>; +} + +// Blanket implementation for types that implement both LightInstructionData and LightCpiInstruction +impl InvokeLightSystemProgram for T +where + T: LightInstructionData + LightCpiInstruction, +{ + fn invoke<'info>(self, accounts: impl CpiAccountsTrait<'info>) -> Result<(), ProgramError> { + { + // Check if CPI context operations are being attempted + use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; + if self.get_with_cpi_context() + || *self.get_cpi_context() == CompressedCpiContext::set() + || *self.get_cpi_context() == CompressedCpiContext::first() + { + solana_msg::msg!( + "CPI context operations not supported in invoke(). Use invoke_write_to_cpi_context_first(), invoke_write_to_cpi_context_set(), or invoke_execute_cpi_context() instead" + ); + return Err(ProgramError::InvalidInstructionData); + } + } + + // Validate mode consistency + if let Some(account_mode) = accounts.get_mode() { + if account_mode != self.get_mode() { + solana_msg::msg!( + "Mode mismatch: accounts have mode {} but instruction data has mode {}", + account_mode, + self.get_mode() + ); + return Err(ProgramError::InvalidInstructionData); + } + } + + // Serialize instruction data with discriminator + let data = self + .data() + .map_err(LightPdaError::from) + .map_err(ProgramError::from)?; + + // Get account infos and metas + let account_infos = accounts.to_account_infos(); + let account_metas = accounts.to_account_metas()?; + + let instruction = Instruction { + program_id: LIGHT_SYSTEM_PROGRAM_ID.into(), + accounts: account_metas, + data, + }; + invoke_light_system_program(&account_infos, instruction, self.get_bump()) + } + fn invoke_write_to_cpi_context_first<'info>( + self, + accounts: impl CpiAccountsTrait<'info>, + ) -> Result<(), ProgramError> { + let instruction_data = self.write_to_cpi_context_first(); + inner_invoke_write_to_cpi_context_typed(instruction_data, accounts) + } + fn invoke_write_to_cpi_context_set<'info>( + self, + accounts: impl CpiAccountsTrait<'info>, + ) -> Result<(), ProgramError> { + let instruction_data = self.write_to_cpi_context_set(); + inner_invoke_write_to_cpi_context_typed(instruction_data, accounts) + } + fn invoke_execute_cpi_context<'info>( + self, + accounts: impl CpiAccountsTrait<'info>, + ) -> Result<(), ProgramError> { + let instruction_data = self.execute_with_cpi_context(); + // Serialize instruction data with discriminator + let data = instruction_data + .data() + .map_err(LightPdaError::from) + .map_err(ProgramError::from)?; + + // Get account infos and metas + let account_infos = accounts.to_account_infos(); + let account_metas = accounts.to_account_metas()?; + + let instruction = Instruction { + program_id: LIGHT_SYSTEM_PROGRAM_ID.into(), + accounts: account_metas, + data, + }; + invoke_light_system_program(&account_infos, instruction, instruction_data.get_bump()) + } +} + +// Generic inner helper for write_to_cpi_context operations +#[inline(always)] +fn inner_invoke_write_to_cpi_context_typed<'info, T>( + instruction_data: T, + accounts: impl CpiAccountsTrait<'info>, +) -> Result<(), ProgramError> +where + T: LightInstructionData + LightCpiInstruction, +{ + // Check if read-only accounts are present + if instruction_data.has_read_only_accounts() { + solana_msg::msg!( + "Read-only accounts are not supported in write_to_cpi_context operations. Use invoke_execute_cpi_context() instead." + ); + return Err(LightPdaError::ReadOnlyAccountsNotSupportedInCpiContext.into()); + } + + // Serialize instruction data with discriminator + let data = instruction_data + .data() + .map_err(LightPdaError::from) + .map_err(ProgramError::from)?; + + // Get account infos and metas + let account_infos = accounts.to_account_infos(); + + // Extract account pubkeys from account_infos + // Assuming order: [fee_payer, authority, cpi_context, ...] + if account_infos.len() < 3 { + return Err(ProgramError::NotEnoughAccountKeys); + } + + let instruction = Instruction { + program_id: LIGHT_SYSTEM_PROGRAM_ID.into(), + accounts: vec![ + AccountMeta { + pubkey: *account_infos[0].key, // fee_payer + is_writable: true, + is_signer: true, + }, + AccountMeta { + pubkey: *account_infos[1].key, // authority + is_writable: false, + is_signer: true, + }, + AccountMeta { + pubkey: *account_infos[2].key, // cpi_context + is_writable: true, + is_signer: false, + }, + ], + data, + }; + + invoke_light_system_program(&account_infos, instruction, instruction_data.get_bump()) +} + +/// Low-level function to invoke the Light system program with a PDA signer. +/// +/// **Note**: This is a low-level function. In most cases, you should use the +/// [`InvokeLightSystemProgram`] trait methods instead, which provide a higher-level +/// interface with better type safety and ergonomics. +#[inline(always)] +pub fn invoke_light_system_program( + account_infos: &[AccountInfo], + instruction: Instruction, + bump: u8, +) -> Result<(), ProgramError> { + let signer_seeds = [CPI_AUTHORITY_PDA_SEED, &[bump]]; + invoke_signed(&instruction, account_infos, &[signer_seeds.as_slice()]) +} diff --git a/sdk-libs/sdk-interface/src/cpi/v1/accounts.rs b/sdk-libs/sdk-interface/src/.backup/cpi/v1/accounts.rs similarity index 100% rename from sdk-libs/sdk-interface/src/cpi/v1/accounts.rs rename to sdk-libs/sdk-interface/src/.backup/cpi/v1/accounts.rs diff --git a/sdk-libs/sdk-interface/src/cpi/v1/invoke.rs b/sdk-libs/sdk-interface/src/.backup/cpi/v1/invoke.rs similarity index 100% rename from sdk-libs/sdk-interface/src/cpi/v1/invoke.rs rename to sdk-libs/sdk-interface/src/.backup/cpi/v1/invoke.rs diff --git a/sdk-libs/sdk-interface/src/cpi/v1/mod.rs b/sdk-libs/sdk-interface/src/.backup/cpi/v1/mod.rs similarity index 100% rename from sdk-libs/sdk-interface/src/cpi/v1/mod.rs rename to sdk-libs/sdk-interface/src/.backup/cpi/v1/mod.rs diff --git a/sdk-libs/sdk-interface/src/cpi/v2/accounts.rs b/sdk-libs/sdk-interface/src/.backup/cpi/v2/accounts.rs similarity index 100% rename from sdk-libs/sdk-interface/src/cpi/v2/accounts.rs rename to sdk-libs/sdk-interface/src/.backup/cpi/v2/accounts.rs diff --git a/sdk-libs/sdk-interface/src/cpi/v2/accounts_cpi_context.rs b/sdk-libs/sdk-interface/src/.backup/cpi/v2/accounts_cpi_context.rs similarity index 100% rename from sdk-libs/sdk-interface/src/cpi/v2/accounts_cpi_context.rs rename to sdk-libs/sdk-interface/src/.backup/cpi/v2/accounts_cpi_context.rs diff --git a/sdk-libs/sdk-interface/src/cpi/v2/invoke.rs b/sdk-libs/sdk-interface/src/.backup/cpi/v2/invoke.rs similarity index 100% rename from sdk-libs/sdk-interface/src/cpi/v2/invoke.rs rename to sdk-libs/sdk-interface/src/.backup/cpi/v2/invoke.rs diff --git a/sdk-libs/sdk-interface/src/cpi/v2/mod.rs b/sdk-libs/sdk-interface/src/.backup/cpi/v2/mod.rs similarity index 100% rename from sdk-libs/sdk-interface/src/cpi/v2/mod.rs rename to sdk-libs/sdk-interface/src/.backup/cpi/v2/mod.rs diff --git a/sdk-libs/sdk-interface/src/.backup/program/config/create.rs b/sdk-libs/sdk-interface/src/.backup/program/config/create.rs new file mode 100644 index 0000000000..c86c3a9997 --- /dev/null +++ b/sdk-libs/sdk-interface/src/.backup/program/config/create.rs @@ -0,0 +1,305 @@ +//! Config initialization instructions. + +use light_account_checks::{ + checks::check_signer, + discriminator::{Discriminator, DISCRIMINATOR_LEN}, +}; +use light_compressible::rent::RentConfig; +use solana_account_info::AccountInfo; +use solana_cpi::invoke_signed; +use solana_loader_v3_interface::state::UpgradeableLoaderState; +use solana_msg::msg; +use solana_program_error::ProgramError; +use solana_pubkey::Pubkey; +use solana_system_interface::instruction as system_instruction; +use solana_sysvar::{rent::Rent, Sysvar}; + +use super::{state::LightConfig, validate_address_space_no_duplicates, COMPRESSIBLE_CONFIG_SEED}; +use crate::{error::LightPdaError, AnchorSerialize}; + +const BPF_LOADER_UPGRADEABLE_ID: Pubkey = + Pubkey::from_str_const("BPFLoaderUpgradeab1e11111111111111111111111"); + +/// Creates a new compressible config PDA +/// +/// # Security - Solana Best Practice +/// This function follows the standard Solana pattern where only the program's +/// upgrade authority can create the initial config. This prevents unauthorized +/// parties from hijacking the config system. +/// +/// # Arguments +/// * `config_account` - The config PDA account to initialize +/// * `update_authority` - Authority that can update the config after creation +/// * `rent_sponsor` - Account that receives rent from compressed PDAs +/// * `compression_authority` - Authority that can compress/close PDAs +/// * `rent_config` - Rent function parameters +/// * `write_top_up` - Lamports to top up on each write +/// * `address_space` - Address space for compressed accounts (currently 1 address_tree allowed) +/// * `config_bump` - Config bump seed (must be 0 for now) +/// * `payer` - Account paying for the PDA creation +/// * `system_program` - System program +/// * `program_id` - The program that owns the config +/// +/// # Required Validation (must be done by caller) +/// The caller MUST validate that the signer is the program's upgrade authority +/// by checking against the program data account. This cannot be done in the SDK +/// due to dependency constraints. +/// +/// # Returns +/// * `Ok(())` if config was created successfully +/// * `Err(ProgramError)` if there was an error +#[allow(clippy::too_many_arguments)] +pub fn process_initialize_light_config<'info>( + config_account: &AccountInfo<'info>, + update_authority: &AccountInfo<'info>, + rent_sponsor: &Pubkey, + compression_authority: &Pubkey, + rent_config: RentConfig, + write_top_up: u32, + address_space: Vec, + config_bump: u8, + payer: &AccountInfo<'info>, + system_program: &AccountInfo<'info>, + program_id: &Pubkey, +) -> Result<(), ProgramError> { + // CHECK: only 1 address_space + if config_bump != 0 { + msg!("Config bump must be 0 for now, found: {}", config_bump); + return Err(LightPdaError::ConstraintViolation.into()); + } + + // CHECK: not already initialized + if config_account.data_len() > 0 { + msg!("Config account already initialized"); + return Err(LightPdaError::ConstraintViolation.into()); + } + + // CHECK: only 1 address_space + if address_space.len() != 1 { + msg!( + "Address space must contain exactly 1 pubkey, found: {}", + address_space.len() + ); + return Err(LightPdaError::ConstraintViolation.into()); + } + + // CHECK: unique pubkeys in address_space + validate_address_space_no_duplicates(&address_space)?; + + // CHECK: signer + check_signer(update_authority).inspect_err(|_| { + msg!("Update authority must be signer for initial config creation"); + })?; + + // CHECK: pda derivation + let (derived_pda, bump) = LightConfig::derive_pda(program_id, config_bump); + if derived_pda != *config_account.key { + msg!("Invalid config PDA"); + return Err(LightPdaError::ConstraintViolation.into()); + } + + // Derive rent_sponsor_bump for storage + let (derived_rent_sponsor, rent_sponsor_bump) = + LightConfig::derive_rent_sponsor_pda(program_id); + if *rent_sponsor != derived_rent_sponsor { + msg!( + "rent_sponsor must be derived PDA: expected {:?}, got {:?}", + derived_rent_sponsor, + rent_sponsor + ); + return Err(LightPdaError::InvalidRentSponsor.into()); + } + + let rent = Rent::get().map_err(LightPdaError::from)?; + let account_size = LightConfig::size_for_address_space(address_space.len()); + let rent_lamports = rent.minimum_balance(account_size); + + // Use u16 to_le_bytes to match derive_pda (2 bytes instead of 1) + let config_bump_bytes = (config_bump as u16).to_le_bytes(); + let seeds = &[ + COMPRESSIBLE_CONFIG_SEED, + config_bump_bytes.as_ref(), + &[bump], + ]; + let create_account_ix = system_instruction::create_account( + payer.key, + config_account.key, + rent_lamports, + account_size as u64, + program_id, + ); + + invoke_signed( + &create_account_ix, + &[ + payer.clone(), + config_account.clone(), + system_program.clone(), + ], + &[seeds], + ) + .map_err(LightPdaError::from)?; + + let config = LightConfig { + version: 1, + write_top_up, + update_authority: *update_authority.key, + rent_sponsor: *rent_sponsor, + compression_authority: *compression_authority, + rent_config, + config_bump, + bump, + rent_sponsor_bump, + address_space, + }; + + let mut data = config_account + .try_borrow_mut_data() + .map_err(LightPdaError::from)?; + + // Write discriminator first (using trait constant) + data[..DISCRIMINATOR_LEN].copy_from_slice(&LightConfig::LIGHT_DISCRIMINATOR); + + // Serialize config data after discriminator + config + .serialize(&mut &mut data[DISCRIMINATOR_LEN..]) + .map_err(|_| LightPdaError::Borsh)?; + + Ok(()) +} + +/// Checks that the signer is the program's upgrade authority +/// +/// # Arguments +/// * `program_id` - The program to check +/// * `program_data_account` - The program's data account (ProgramData) +/// * `authority` - The authority to verify +/// +/// # Returns +/// * `Ok(())` if authority is valid +/// * `Err(LightPdaError)` if authority is invalid or verification fails +pub fn check_program_upgrade_authority( + program_id: &Pubkey, + program_data_account: &AccountInfo, + authority: &AccountInfo, +) -> Result<(), ProgramError> { + // CHECK: program data PDA + let (expected_program_data, _) = + Pubkey::find_program_address(&[program_id.as_ref()], &BPF_LOADER_UPGRADEABLE_ID); + if program_data_account.key != &expected_program_data { + msg!("Invalid program data account"); + return Err(LightPdaError::ConstraintViolation.into()); + } + + let data = program_data_account.try_borrow_data()?; + let program_state: UpgradeableLoaderState = bincode::deserialize(&data).map_err(|_| { + msg!("Failed to deserialize program data account"); + LightPdaError::ConstraintViolation + })?; + + // Extract upgrade authority + let upgrade_authority = match program_state { + UpgradeableLoaderState::ProgramData { + slot: _, + upgrade_authority_address, + } => { + match upgrade_authority_address { + Some(auth) => { + // Check for invalid zero authority when authority exists + if auth == Pubkey::default() { + msg!("Invalid state: authority is zero pubkey"); + return Err(LightPdaError::ConstraintViolation.into()); + } + auth + } + None => { + msg!("Program has no upgrade authority"); + return Err(LightPdaError::ConstraintViolation.into()); + } + } + } + _ => { + msg!("Account is not ProgramData, found: {:?}", program_state); + return Err(LightPdaError::ConstraintViolation.into()); + } + }; + + // CHECK: upgrade authority is signer + check_signer(authority).inspect_err(|_| { + msg!("Authority must be signer"); + })?; + + // CHECK: upgrade authority is program's upgrade authority + if *authority.key != upgrade_authority { + msg!( + "Signer is not the program's upgrade authority. Signer: {:?}, Expected Authority: {:?}", + authority.key, + upgrade_authority + ); + return Err(LightPdaError::ConstraintViolation.into()); + } + + Ok(()) +} + +/// Creates a new compressible config PDA. +/// +/// # Arguments +/// * `config_account` - The config PDA account to initialize +/// * `update_authority` - Must be the program's upgrade authority +/// * `program_data_account` - The program's data account for validation +/// * `rent_sponsor` - Account that receives rent from compressed PDAs +/// * `compression_authority` - Authority that can compress/close PDAs +/// * `rent_config` - Rent function parameters +/// * `write_top_up` - Lamports to top up on each write +/// * `address_space` - Address spaces for compressed accounts (exactly 1 +/// allowed) +/// * `config_bump` - Config bump seed (must be 0 for now) +/// * `payer` - Account paying for the PDA creation +/// * `system_program` - System program +/// * `program_id` - The program that owns the config +/// +/// # Returns +/// * `Ok(())` if config was created successfully +/// * `Err(ProgramError)` if there was an error or authority validation fails +#[allow(clippy::too_many_arguments)] +pub fn process_initialize_light_config_checked<'info>( + config_account: &AccountInfo<'info>, + update_authority: &AccountInfo<'info>, + program_data_account: &AccountInfo<'info>, + rent_sponsor: &Pubkey, + compression_authority: &Pubkey, + rent_config: RentConfig, + write_top_up: u32, + address_space: Vec, + config_bump: u8, + payer: &AccountInfo<'info>, + system_program: &AccountInfo<'info>, + program_id: &Pubkey, +) -> Result<(), ProgramError> { + msg!( + "create_compression_config_checked program_data_account: {:?}", + program_data_account.key + ); + msg!( + "create_compression_config_checked program_id: {:?}", + program_id + ); + // Verify the signer is the program's upgrade authority + check_program_upgrade_authority(program_id, program_data_account, update_authority)?; + + // Create the config with validated authority + process_initialize_light_config( + config_account, + update_authority, + rent_sponsor, + compression_authority, + rent_config, + write_top_up, + address_space, + config_bump, + payer, + system_program, + program_id, + ) +} diff --git a/sdk-libs/sdk-interface/src/.backup/program/config/update.rs b/sdk-libs/sdk-interface/src/.backup/program/config/update.rs new file mode 100644 index 0000000000..a5571b2577 --- /dev/null +++ b/sdk-libs/sdk-interface/src/.backup/program/config/update.rs @@ -0,0 +1,102 @@ +//! Config update instruction. + +use light_account_checks::{checks::check_signer, discriminator::DISCRIMINATOR_LEN}; +use light_compressible::rent::RentConfig; +use solana_account_info::AccountInfo; +use solana_msg::msg; +use solana_program_error::ProgramError; +use solana_pubkey::Pubkey; + +use super::{ + state::LightConfig, validate_address_space_no_duplicates, validate_address_space_only_adds, + MAX_ADDRESS_TREES_PER_SPACE, +}; +use crate::{error::LightPdaError, AnchorSerialize}; + +/// Updates an existing compressible config +/// +/// # Arguments +/// * `config_account` - The config PDA account to update +/// * `authority` - Current update authority (must match config) +/// * `new_update_authority` - Optional new update authority +/// * `new_rent_sponsor` - Optional new rent recipient +/// * `new_compression_authority` - Optional new compression authority +/// * `new_rent_config` - Optional new rent function parameters +/// * `new_write_top_up` - Optional new write top-up amount +/// * `new_address_space` - Optional new address space (currently 1 address_tree allowed) +/// * `owner_program_id` - The program that owns the config +/// +/// # Returns +/// * `Ok(())` if config was updated successfully +/// * `Err(ProgramError)` if there was an error +#[allow(clippy::too_many_arguments)] +pub fn process_update_light_config<'info>( + config_account: &AccountInfo<'info>, + authority: &AccountInfo<'info>, + new_update_authority: Option<&Pubkey>, + new_rent_sponsor: Option<&Pubkey>, + new_compression_authority: Option<&Pubkey>, + new_rent_config: Option, + new_write_top_up: Option, + new_address_space: Option>, + owner_program_id: &Pubkey, +) -> Result<(), ProgramError> { + // CHECK: PDA derivation + let mut config = LightConfig::load_checked(config_account, owner_program_id)?; + + // CHECK: signer + check_signer(authority).inspect_err(|_| { + msg!("Update authority must be signer"); + })?; + // CHECK: authority + if *authority.key != config.update_authority { + msg!("Invalid update authority"); + return Err(LightPdaError::ConstraintViolation.into()); + } + + if let Some(new_authority) = new_update_authority { + config.update_authority = *new_authority; + } + if let Some(new_recipient) = new_rent_sponsor { + config.rent_sponsor = *new_recipient; + } + if let Some(new_auth) = new_compression_authority { + config.compression_authority = *new_auth; + } + if let Some(new_rcfg) = new_rent_config { + config.rent_config = new_rcfg; + } + if let Some(new_top_up) = new_write_top_up { + config.write_top_up = new_top_up; + } + if let Some(new_address_space) = new_address_space { + // CHECK: address space length + if new_address_space.len() != MAX_ADDRESS_TREES_PER_SPACE { + msg!( + "New address space must contain exactly 1 pubkey, found: {}", + new_address_space.len() + ); + return Err(LightPdaError::ConstraintViolation.into()); + } + + validate_address_space_no_duplicates(&new_address_space)?; + + validate_address_space_only_adds(&config.address_space, &new_address_space)?; + + config.address_space = new_address_space; + } + + let mut data = config_account.try_borrow_mut_data().map_err(|e| { + msg!("Failed to borrow mut data for config_account: {:?}", e); + LightPdaError::from(e) + })?; + // Serialize after discriminator (discriminator is preserved from init) + config + .serialize(&mut &mut data[DISCRIMINATOR_LEN..]) + .map_err(|e| { + msg!("Failed to serialize updated config: {:?}", e); + LightPdaError::Borsh + })?; + + Ok(()) +} diff --git a/sdk-libs/sdk-interface/src/.backup/program/decompression/create_token_account.rs b/sdk-libs/sdk-interface/src/.backup/program/decompression/create_token_account.rs new file mode 100644 index 0000000000..ef03341a25 --- /dev/null +++ b/sdk-libs/sdk-interface/src/.backup/program/decompression/create_token_account.rs @@ -0,0 +1,160 @@ +//! ATA and token account creation helpers for decompression. + +use light_token_interface::instructions::{ + create_token_account::CreateTokenAccountInstructionData, + extensions::{CompressToPubkey, CompressibleExtensionInstructionData}, +}; +use solana_instruction::{AccountMeta, Instruction}; +use solana_program_error::ProgramError; +use solana_pubkey::Pubkey; + +use crate::AnchorSerialize; + +/// Build a CreateAssociatedTokenAccountIdempotent instruction for ATA decompression. +/// +/// Creates a compressible ATA with compression_only mode (required for ATA decompression). +/// +/// # Account order (per on-chain handler): +/// 0. owner (non-mut, non-signer) - The wallet owner +/// 1. mint (non-mut, non-signer) - The token mint +/// 2. fee_payer (signer, writable) - Pays for account creation +/// 3. associated_token_account (writable, NOT signer) - The ATA to create +/// 4. system_program (readonly) - System program +/// 5. compressible_config (readonly) - Compressible config PDA +/// 6. rent_payer (writable) - Rent sponsor account +/// +/// # Arguments +/// * `wallet_owner` - The wallet owner (ATA derivation seed) +/// * `mint` - The token mint +/// * `fee_payer` - Pays for account creation +/// * `ata` - The ATA pubkey (derived from wallet_owner, program_id, mint) +/// * `bump` - The ATA derivation bump +/// * `compressible_config` - Compressible config PDA +/// * `rent_sponsor` - Rent sponsor account +/// * `write_top_up` - Lamports per write for top-up +#[allow(clippy::too_many_arguments)] +pub fn build_create_ata_instruction( + wallet_owner: &Pubkey, + mint: &Pubkey, + fee_payer: &Pubkey, + ata: &Pubkey, + bump: u8, + compressible_config: &Pubkey, + rent_sponsor: &Pubkey, + write_top_up: u32, +) -> Result { + use light_token_interface::instructions::{ + create_associated_token_account::CreateAssociatedTokenAccountInstructionData, + extensions::CompressibleExtensionInstructionData, + }; + + let instruction_data = CreateAssociatedTokenAccountInstructionData { + bump, + compressible_config: Some(CompressibleExtensionInstructionData { + token_account_version: 3, // ShaFlat version (required) + rent_payment: 16, // 24h, TODO: make configurable + compression_only: 1, // Required for ATA + write_top_up, + compress_to_account_pubkey: None, // Required to be None for ATA + }), + }; + + let mut data = Vec::new(); + data.push(102u8); // CreateAssociatedTokenAccountIdempotent discriminator + instruction_data + .serialize(&mut data) + .map_err(|e| ProgramError::BorshIoError(e.to_string()))?; + + let accounts = vec![ + AccountMeta::new_readonly(*wallet_owner, false), + AccountMeta::new_readonly(*mint, false), + AccountMeta::new(*fee_payer, true), + AccountMeta::new(*ata, false), // NOT a signer - ATA is derived + AccountMeta::new_readonly(Pubkey::default(), false), // system_program + AccountMeta::new_readonly(*compressible_config, false), + AccountMeta::new(*rent_sponsor, false), + ]; + + Ok(Instruction { + program_id: light_token_interface::LIGHT_TOKEN_PROGRAM_ID.into(), + accounts, + data, + }) +} + +/// Build a CreateTokenAccount instruction for decompression. +/// +/// Creates a compressible token account with ShaFlat version (required by light token program). +/// +/// # Account order: +/// 0. token_account (signer, writable) - The token account PDA to create +/// 1. mint (readonly) - The token mint +/// 2. fee_payer (signer, writable) - Pays for account creation +/// 3. compressible_config (readonly) - Compressible config PDA +/// 4. system_program (readonly) - System program +/// 5. rent_sponsor (writable) - Rent sponsor account +/// +/// # Arguments +/// * `signer_seeds` - Seeds including bump for the token account PDA +/// * `program_id` - Program ID that owns the token account PDA +#[allow(clippy::too_many_arguments)] +pub fn build_create_token_account_instruction( + token_account: &Pubkey, + mint: &Pubkey, + owner: &Pubkey, + fee_payer: &Pubkey, + compressible_config: &Pubkey, + rent_sponsor: &Pubkey, + write_top_up: u32, + signer_seeds: &[&[u8]], + program_id: &Pubkey, +) -> Result { + // Build CompressToPubkey from signer_seeds (last seed is bump) + let bump = signer_seeds + .last() + .and_then(|s| s.first().copied()) + .ok_or(ProgramError::InvalidSeeds)?; + let seeds_without_bump: Vec> = signer_seeds + .iter() + .take(signer_seeds.len().saturating_sub(1)) + .map(|s| s.to_vec()) + .collect(); + + let compress_to_account_pubkey = CompressToPubkey { + bump, + program_id: program_id.to_bytes(), + seeds: seeds_without_bump, + }; + + let instruction_data = CreateTokenAccountInstructionData { + owner: light_compressed_account::Pubkey::from(owner.to_bytes()), + compressible_config: Some(CompressibleExtensionInstructionData { + token_account_version: 3, // ShaFlat version (required) + rent_payment: 16, // 24h, TODO: make configurable + compression_only: 0, // Regular tokens can be transferred, not compression-only + write_top_up, + compress_to_account_pubkey: Some(compress_to_account_pubkey), + }), + }; + + let mut data = Vec::new(); + data.push(18u8); // InitializeAccount3 opcode + instruction_data + .serialize(&mut data) + .map_err(|e| ProgramError::BorshIoError(e.to_string()))?; + + let accounts = vec![ + AccountMeta::new(*token_account, true), + AccountMeta::new_readonly(*mint, false), + AccountMeta::new(*fee_payer, true), + AccountMeta::new_readonly(*compressible_config, false), + AccountMeta::new_readonly(Pubkey::default(), false), // system_program + AccountMeta::new(*rent_sponsor, false), + ]; + + Ok(Instruction { + program_id: light_token_interface::LIGHT_TOKEN_PROGRAM_ID.into(), + accounts, + data, + }) +} diff --git a/sdk-libs/sdk-interface/src/.backup/program/decompression/mod.rs b/sdk-libs/sdk-interface/src/.backup/program/decompression/mod.rs new file mode 100644 index 0000000000..d6d99bfac8 --- /dev/null +++ b/sdk-libs/sdk-interface/src/.backup/program/decompression/mod.rs @@ -0,0 +1,13 @@ +//! Decompression functions for PDA and token accounts. + +#[cfg(feature = "anchor")] +pub mod create_token_account; + +#[cfg(feature = "anchor")] +pub mod processor; + +#[cfg(feature = "anchor")] +pub mod pda; + +#[cfg(feature = "anchor")] +pub mod token; diff --git a/sdk-libs/sdk-interface/src/.backup/program/decompression/pda.rs b/sdk-libs/sdk-interface/src/.backup/program/decompression/pda.rs new file mode 100644 index 0000000000..2fd0cca988 --- /dev/null +++ b/sdk-libs/sdk-interface/src/.backup/program/decompression/pda.rs @@ -0,0 +1,181 @@ +use anchor_lang::prelude::*; +use light_compressed_account::{ + address::derive_address, + compressed_account::PackedMerkleContext, + instruction_data::with_account_info::{CompressedAccountInfo, InAccountInfo, OutAccountInfo}, +}; +use light_compressible::DECOMPRESSED_PDA_DISCRIMINATOR; +use light_hasher::{sha256::Sha256BE, Hasher, Sha256}; +use light_sdk_types::{constants::RENT_SPONSOR_SEED, instruction::PackedStateTreeInfo}; +use solana_account_info::AccountInfo; +use solana_program_error::ProgramError; +use solana_pubkey::Pubkey; + +use crate::{ + accounts::create_pda::create_pda_account, + program::decompression::processor::DecompressCtx, + account::light_account::LightAccount, + program::variant::{LightAccountVariantTrait, PackedLightAccountVariantTrait}, + LightDiscriminator, +}; + +/// Generic prepare_account_for_decompression. +/// +/// Takes a packed variant and metadata, handles: +/// 1. Validating PDA derivation (security check - MUST be first) +/// 2. Checking idempotency (skip if already initialized) +/// 3. Getting seeds from packed variant +/// 4. Unpacking data +/// 5. Creating PDA and writing data +/// 6. Deriving compressed address from PDA key +/// 7. Building CompressedAccountInfo for CPI +/// +/// # Security +/// PDA validation MUST run before idempotency check to prevent accepting +/// wrong PDAs that happen to be already initialized. +/// +/// # Type Parameters +/// * `SEED_COUNT` - Number of seeds including bump +/// * `P` - Packed variant type implementing PackedLightAccountVariantTrait +pub fn prepare_account_for_decompression<'info, const SEED_COUNT: usize, P>( + packed: &P, + tree_info: &PackedStateTreeInfo, + output_queue_index: u8, + pda_account: &AccountInfo<'info>, + ctx: &mut DecompressCtx<'_, 'info>, +) -> std::result::Result<(), ProgramError> +where + P: PackedLightAccountVariantTrait, + >::Data: + LightAccount + LightDiscriminator + Clone + AnchorSerialize + AnchorDeserialize, +{ + // 1. Unpack to get seeds (must happen first for PDA validation) + let packed_accounts = ctx + .cpi_accounts + .packed_accounts() + .map_err(|_| ProgramError::NotEnoughAccountKeys)?; + + let unpacked = packed + .unpack(packed_accounts) + .map_err(|_| ProgramError::InvalidAccountData)?; + let account_data = unpacked.data().clone(); + + // 2. Get seeds from unpacked variant using seed_vec() (owned data, no lifetime issues) + let bump = packed.bump(); + let bump_bytes = [bump]; + let mut seed_vecs = unpacked.seed_vec(); + seed_vecs.push(bump_bytes.to_vec()); + let seed_slices: Vec<&[u8]> = seed_vecs.iter().map(|v| v.as_slice()).collect(); + + // 3. SECURITY: Validate PDA derivation FIRST (defense-in-depth) + // This MUST run before idempotency check to prevent accepting wrong PDAs + let expected_pda = Pubkey::create_program_address(&seed_slices, ctx.program_id) + .map_err(|_| ProgramError::InvalidSeeds)?; + + if pda_account.key != &expected_pda { + solana_msg::msg!( + "PDA key mismatch: expected {:?}, got {:?}", + expected_pda, + pda_account.key + ); + return Err(ProgramError::InvalidSeeds); + } + + // 4. Idempotency check - if PDA already has data (non-zero discriminator), skip + // IMPORTANT: This runs AFTER PDA validation so wrong PDAs cannot bypass validation + if crate::program::validation::is_pda_initialized(pda_account)? { + return Ok(()); + } + + // 5. Hash with canonical CompressionInfo::compressed() for input verification + let data_bytes = account_data + .try_to_vec() + .map_err(|_| ProgramError::InvalidAccountData)?; + let data_len = data_bytes.len(); + let mut input_data_hash = Sha256::hash(&data_bytes).map_err(|_| ProgramError::Custom(100))?; + input_data_hash[0] = 0; // Zero first byte per protocol convention + + // 6. Calculate space and create PDA + type Data = + <

>::Unpacked as LightAccountVariantTrait>::Data; + // 1. Unpack to get seeds (must happen first for PDA validation) let packed_accounts = ctx .cpi_accounts .packed_accounts() - .map_err(|_| ProgramError::NotEnoughAccountKeys)?; + .map_err(LightPdaError::from)?; let unpacked = packed .unpack(packed_accounts) - .map_err(|_| ProgramError::InvalidAccountData)?; + .map_err(|_| LightPdaError::InvalidInstructionData)?; let account_data = unpacked.data().clone(); // 2. Get seeds from unpacked variant using seed_vec() (owned data, no lifetime issues) @@ -69,16 +77,13 @@ where // 3. SECURITY: Validate PDA derivation FIRST (defense-in-depth) // This MUST run before idempotency check to prevent accepting wrong PDAs - let expected_pda = Pubkey::create_program_address(&seed_slices, ctx.program_id) - .map_err(|_| ProgramError::InvalidSeeds)?; - - if pda_account.key != &expected_pda { - solana_msg::msg!( - "PDA key mismatch: expected {:?}, got {:?}", - expected_pda, - pda_account.key - ); - return Err(ProgramError::InvalidSeeds); + let expected_pda = + AI::create_program_address(&seed_slices, ctx.program_id).map_err(|_| { + LightPdaError::InvalidSeeds + })?; + + if pda_account.key() != expected_pda { + return Err(LightPdaError::InvalidSeeds); } // 4. Idempotency check - if PDA already has data (non-zero discriminator), skip @@ -90,40 +95,42 @@ where // 5. Hash with canonical CompressionInfo::compressed() for input verification let data_bytes = account_data .try_to_vec() - .map_err(|_| ProgramError::InvalidAccountData)?; + .map_err(|_| LightPdaError::Borsh)?; let data_len = data_bytes.len(); - let mut input_data_hash = Sha256::hash(&data_bytes).map_err(|_| ProgramError::Custom(100))?; + let mut input_data_hash = Sha256BE::hash(&data_bytes)?; input_data_hash[0] = 0; // Zero first byte per protocol convention // 6. Calculate space and create PDA - type Data = - <

>::Unpacked as LightAccountVariantTrait>::Data; let discriminator_len = 8; - let space = discriminator_len + data_len.max( as LightAccount>::INIT_SPACE); - let rent_minimum = ctx.rent.minimum_balance(space); + let space = + discriminator_len + data_len.max( as LightAccount>::INIT_SPACE); + let rent_minimum = AI::get_min_rent_balance(space)?; let system_program = ctx .cpi_accounts .system_program() - .map_err(|_| ProgramError::InvalidAccountData)?; + .map_err(LightPdaError::from)?; // Construct rent sponsor seeds for PDA signing let rent_sponsor_bump_bytes = [ctx.rent_sponsor_bump]; let rent_sponsor_seeds: &[&[u8]] = &[RENT_SPONSOR_SEED, &rent_sponsor_bump_bytes]; - create_pda_account( - ctx.rent_sponsor, - rent_sponsor_seeds, - pda_account, - rent_minimum, - space as u64, - ctx.program_id, - &seed_slices, - system_program, - )?; + pda_account + .create_pda_account( + rent_minimum, + space as u64, + ctx.program_id, + &seed_slices, + ctx.rent_sponsor, + rent_sponsor_seeds, + system_program, + ) + .map_err(|_| LightPdaError::CpiFailed)?; // 7. Write discriminator + data to PDA - let mut pda_data = pda_account.try_borrow_mut_data()?; + let mut pda_data = pda_account + .try_borrow_mut_data() + .map_err(|_| LightPdaError::ConstraintViolation)?; pda_data[..8] .copy_from_slice(& as LightDiscriminator>::LIGHT_DISCRIMINATOR); @@ -133,13 +140,14 @@ where let writer = &mut &mut pda_data[8..]; decompressed .serialize(writer) - .map_err(|_| ProgramError::InvalidAccountData)?; + .map_err(|_| LightPdaError::Borsh)?; - // 9. Derive compressed address from PDA key (saves instruction data size) + // 9. Derive compressed address from PDA key + let pda_key = pda_account.key(); let address = derive_address( - &pda_account.key.to_bytes(), - &ctx.light_config.address_space[0].to_bytes(), - &ctx.program_id.to_bytes(), + &pda_key, + &ctx.light_config.address_space[0], + ctx.program_id, ); // 10. Build CompressedAccountInfo for CPI @@ -159,9 +167,8 @@ where // Output is a DECOMPRESSED_PDA placeholder (same as init creates). // This allows CompressAccountsIdempotent to re-compress the account // in a future cycle by finding and nullifying this placeholder. - let pda_pubkey_bytes = pda_account.key.to_bytes(); - let output_data_hash = - Sha256BE::hash(&pda_pubkey_bytes).map_err(|_| ProgramError::Custom(101))?; + let pda_pubkey_bytes = pda_account.key(); + let output_data_hash = Sha256BE::hash(&pda_pubkey_bytes)?; let output = OutAccountInfo { lamports: 0, output_merkle_tree_index: output_queue_index, diff --git a/sdk-libs/sdk-interface/src/program/decompression/processor.rs b/sdk-libs/sdk-interface/src/program/decompression/processor.rs index 85ff61b499..6b767cb7f8 100644 --- a/sdk-libs/sdk-interface/src/program/decompression/processor.rs +++ b/sdk-libs/sdk-interface/src/program/decompression/processor.rs @@ -1,53 +1,65 @@ -//! SDK generic decompression functions. -//! -//! These functions are generic over account types and can be reused by the macro. -//! The decompress flow creates PDAs from compressed state (needs validity proof, packed data, seeds). - -use anchor_lang::{ - prelude::*, - solana_program::{clock::Clock, program::invoke_signed, rent::Rent, sysvar::Sysvar}, -}; +//! Decompression instruction processor. + +use light_account_checks::AccountInfoTrait; use light_compressed_account::instruction_data::{ - cpi_context::CompressedCpiContext, compressed_proof::ValidityProof, - with_account_info::CompressedAccountInfo, + with_account_info::{CompressedAccountInfo, InstructionDataInvokeCpiWithAccountInfo}, +}; +use light_sdk_types::{ + cpi_accounts::v2::CpiAccounts, instruction::PackedStateTreeInfo, CpiSigner, +}; + +use crate::{ + account::compression_info::CompressedAccountData, + cpi::InvokeLightSystemProgram, + error::LightPdaError, + program::config::LightConfig, + AnchorDeserialize, AnchorSerialize, }; -use light_sdk_types::cpi_context_write::CpiContextWriteAccounts; + +#[cfg(feature = "token")] +use light_account_checks::CpiMeta; +#[cfg(feature = "token")] +use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; +#[cfg(feature = "token")] use light_sdk_types::{ - cpi_accounts::CpiAccountsConfig, instruction::PackedStateTreeInfo, CpiSigner, - ACCOUNT_COMPRESSION_AUTHORITY_PDA, ACCOUNT_COMPRESSION_PROGRAM_ID, LIGHT_SYSTEM_PROGRAM_ID, - REGISTERED_PROGRAM_PDA, + cpi_accounts::CpiAccountsConfig, + cpi_context_write::CpiContextWriteAccounts, + constants::{ + ACCOUNT_COMPRESSION_AUTHORITY_PDA, ACCOUNT_COMPRESSION_PROGRAM_ID, + LIGHT_SYSTEM_PROGRAM_ID, REGISTERED_PROGRAM_PDA, + }, }; +#[cfg(feature = "token")] use light_token_interface::{ instructions::{ extensions::ExtensionInstructionData, transfer2::{ - CompressedTokenInstructionDataTransfer2, Compression, MultiInputTokenDataWithContext, + Compression, CompressedTokenInstructionDataTransfer2, MultiInputTokenDataWithContext, }, }, CPI_AUTHORITY, LIGHT_TOKEN_PROGRAM_ID, TRANSFER2, }; -use solana_instruction::Instruction; -use solana_program_error::ProgramError; -use crate::{ - account::compression_info::CompressedAccountData, - cpi::{v2::CpiAccounts, InvokeLightSystemProgram}, - program::config::LightConfig, -}; +/// Account indices within remaining_accounts for decompress instructions. +const FEE_PAYER_INDEX: usize = 0; +const CONFIG_INDEX: usize = 1; +const RENT_SPONSOR_INDEX: usize = 2; // ============================================================================ -// DecompressVariant Trait (implemented by program's PackedProgramAccountVariant) +// DecompressVariant Trait // ============================================================================ /// Trait for packed program account variants that support decompression. /// -/// This trait is implemented by the program's `PackedProgramAccountVariant` enum +/// Implemented by the program's `PackedProgramAccountVariant` enum /// to handle type-specific dispatch during decompression. /// /// MACRO-GENERATED: The implementation contains a match statement routing each /// enum variant to the appropriate `prepare_account_for_decompression` call. -pub trait DecompressVariant<'info>: AnchorSerialize + AnchorDeserialize + Clone { +pub trait DecompressVariant: + AnchorSerialize + AnchorDeserialize + Clone +{ /// Decompress this variant into a PDA account. /// /// The implementation should match on the enum variant and call @@ -55,9 +67,9 @@ pub trait DecompressVariant<'info>: AnchorSerialize + AnchorDeserialize + Clone fn decompress( &self, meta: &PackedStateTreeInfo, - pda_account: &AccountInfo<'info>, - ctx: &mut DecompressCtx<'_, 'info>, - ) -> std::result::Result<(), ProgramError>; + pda_account: &AI, + ctx: &mut DecompressCtx<'_, AI>, + ) -> Result<(), LightPdaError>; } // ============================================================================ @@ -75,8 +87,8 @@ where { /// Offset into remaining_accounts where Light system accounts begin pub system_accounts_offset: u8, - /// All account variants less than offset are pda acccounts. - /// 255 if no token accounts + /// Accounts before this offset are PDA accounts, at and after are token accounts. + /// Set to accounts.len() if no token accounts. pub token_accounts_offset: u8, /// Packed index of the output queue in remaining_accounts. pub output_queue_index: u8, @@ -87,221 +99,352 @@ where } /// Context struct holding all data needed for decompression. -/// Contains internal vec for collecting CompressedAccountInfo results. -pub struct DecompressCtx<'a, 'info> { - pub program_id: &'a Pubkey, - pub cpi_accounts: &'a CpiAccounts<'a, 'info>, - pub remaining_accounts: &'a [AccountInfo<'info>], - pub rent_sponsor: &'a AccountInfo<'info>, +/// Generic over AccountInfoTrait to work with both solana and pinocchio. +pub struct DecompressCtx<'a, AI: AccountInfoTrait + Clone> { + pub program_id: &'a [u8; 32], + pub cpi_accounts: &'a CpiAccounts<'a, AI>, + pub remaining_accounts: &'a [AI], + pub rent_sponsor: &'a AI, /// Rent sponsor PDA bump for signing pub rent_sponsor_bump: u8, pub light_config: &'a LightConfig, - /// Token (ctoken) rent sponsor for creating token accounts - pub ctoken_rent_sponsor: &'a AccountInfo<'info>, - /// Token (ctoken) compressible config for creating token accounts - pub ctoken_compressible_config: &'a AccountInfo<'info>, - pub rent: &'a Rent, pub current_slot: u64, /// Packed index of the output queue in remaining_accounts. pub output_queue_index: u8, /// Internal vec - dispatch functions push results here pub compressed_account_infos: Vec, + // Token-specific fields (only present when token feature is enabled) + #[cfg(feature = "token")] + pub ctoken_rent_sponsor: Option<&'a AI>, + #[cfg(feature = "token")] + pub ctoken_compressible_config: Option<&'a AI>, + #[cfg(feature = "token")] pub in_token_data: Vec, + #[cfg(feature = "token")] pub in_tlv: Option>>, + #[cfg(feature = "token")] pub token_seeds: Vec>, } // ============================================================================ -// Processor Function +// PDA-only Processor // ============================================================================ -/// Remaining accounts layout: -/// [0]: fee_payer (Signer, mut) -/// [1]: config (LightConfig PDA) -/// [2]: rent_sponsor (mut) -/// [system_accounts_offset..]: Light system accounts for CPI -/// [remaining_accounts.len() - num_pda_accounts..]: PDA accounts to decompress +/// Process decompression for PDA accounts (idempotent, PDA-only). +/// +/// Iterates over PDA accounts, dispatches each for decompression via `DecompressVariant`, +/// then invokes the Light system program CPI to commit compressed state. /// -/// Runtime processor - handles all the plumbing, dispatches via DecompressVariant trait. +/// Idempotent: if a PDA is already initialized, it is silently skipped. /// -/// **Takes raw instruction data** and deserializes internally - minimizes macro code. -/// **Uses only remaining_accounts** - no Context struct needed. -/// **Generic over V** - the program's `PackedProgramAccountVariant` enum. -pub fn process_decompress_pda_accounts_idempotent<'info, V>( - remaining_accounts: &[AccountInfo<'info>], +/// # Account layout in remaining_accounts: +/// - [0]: fee_payer (Signer, mut) +/// - [1]: config (LightConfig PDA) +/// - [2]: rent_sponsor (mut) +/// - [system_accounts_offset..hot_accounts_start]: Light system + tree accounts +/// - [hot_accounts_start..]: PDA accounts to decompress into +#[inline(never)] +pub fn process_decompress_pda_accounts_idempotent( + remaining_accounts: &[AI], instruction_data: &[u8], cpi_signer: CpiSigner, - program_id: &Pubkey, -) -> std::result::Result<(), ProgramError> + program_id: &[u8; 32], + current_slot: u64, +) -> Result<(), LightPdaError> where - V: DecompressVariant<'info>, + AI: AccountInfoTrait + Clone, + V: DecompressVariant, { - // Deserialize params internally + // 1. Deserialize params let params = DecompressIdempotentParams::::try_from_slice(instruction_data) - .map_err(|_| ProgramError::InvalidInstructionData)?; - - // Extract and validate accounts using shared validation - let validated_ctx = - crate::program::validation::validate_decompress_accounts(remaining_accounts, program_id)?; - let fee_payer = &validated_ctx.fee_payer; - let rent_sponsor = &validated_ctx.rent_sponsor; - let rent_sponsor_bump = validated_ctx.rent_sponsor_bump; - let light_config = validated_ctx.light_config; - - let rent = Rent::get()?; - let current_slot = Clock::get()?.slot; - - let system_accounts_offset_usize = params.system_accounts_offset as usize; - if system_accounts_offset_usize > remaining_accounts.len() { - return Err(ProgramError::InvalidInstructionData); + .map_err(|_| LightPdaError::Borsh)?; + + let system_accounts_offset = params.system_accounts_offset as usize; + if system_accounts_offset > remaining_accounts.len() { + return Err(LightPdaError::InvalidInstructionData); + } + + // PDA accounts: all accounts up to token_accounts_offset + let num_pda_accounts = params.token_accounts_offset as usize; + let pda_accounts = params + .accounts + .get(..num_pda_accounts) + .ok_or(LightPdaError::InvalidInstructionData)?; + + if pda_accounts.is_empty() { + return Err(LightPdaError::InvalidInstructionData); + } + + // 2. Load and validate config + let config = LightConfig::load_checked(&remaining_accounts[CONFIG_INDEX], program_id)?; + let rent_sponsor = &remaining_accounts[RENT_SPONSOR_INDEX]; + let rent_sponsor_bump = config.validate_rent_sponsor_account::(rent_sponsor)?; + + // 3. Hot accounts (PDAs) at the tail of remaining_accounts + let num_hot_accounts = params.accounts.len(); + let hot_accounts_start = remaining_accounts + .len() + .checked_sub(num_hot_accounts) + .ok_or(LightPdaError::NotEnoughAccountKeys)?; + let hot_account_infos = &remaining_accounts[hot_accounts_start..]; + let pda_account_infos = hot_account_infos + .get(..num_pda_accounts) + .ok_or(LightPdaError::NotEnoughAccountKeys)?; + + // 4. Build CpiAccounts (system + tree accounts, excluding hot accounts) + let cpi_accounts = CpiAccounts::new( + &remaining_accounts[FEE_PAYER_INDEX], + &remaining_accounts[system_accounts_offset..hot_accounts_start], + cpi_signer, + ); + + // 5. Build context and dispatch (scoped to release borrows before CPI) + let compressed_account_infos = { + let mut decompress_ctx = DecompressCtx { + program_id, + cpi_accounts: &cpi_accounts, + remaining_accounts, + rent_sponsor, + rent_sponsor_bump, + light_config: &config, + current_slot, + output_queue_index: params.output_queue_index, + compressed_account_infos: Vec::with_capacity(num_pda_accounts), + #[cfg(feature = "token")] + ctoken_rent_sponsor: None, + #[cfg(feature = "token")] + ctoken_compressible_config: None, + #[cfg(feature = "token")] + in_token_data: Vec::new(), + #[cfg(feature = "token")] + in_tlv: None, + #[cfg(feature = "token")] + token_seeds: Vec::new(), + }; + + for (pda_account_data, pda_account_info) in pda_accounts.iter().zip(pda_account_infos) { + pda_account_data.data.decompress( + &pda_account_data.tree_info, + pda_account_info, + &mut decompress_ctx, + )?; + } + + decompress_ctx.compressed_account_infos + }; + + // 6. If no compressed accounts were produced (all already initialized), skip CPI + if compressed_account_infos.is_empty() { + return Ok(()); } + + // 7. Build and invoke Light system program CPI + let mut cpi_ix_data = InstructionDataInvokeCpiWithAccountInfo::new( + program_id.into(), + cpi_signer.bump, + params.proof.into(), + ); + cpi_ix_data.account_infos = compressed_account_infos; + cpi_ix_data.invoke::(cpi_accounts)?; + + Ok(()) +} + +// ============================================================================ +// Full Processor (PDA + Token) +// ============================================================================ + +/// Process decompression for both PDA and token accounts (idempotent). +/// +/// Handles the combined PDA + token decompression flow: +/// - PDA accounts are decompressed first +/// - If both PDAs and tokens exist, PDA data is written to CPI context first +/// - Token accounts are decompressed via Transfer2 CPI to the light token program +/// +/// # Account layout in remaining_accounts: +/// - [0]: fee_payer (Signer, mut) +/// - [1]: config (LightConfig PDA) +/// - [2]: rent_sponsor (mut) +/// - [3]: ctoken_rent_sponsor (mut) +/// - [4]: light_token_program +/// - [5]: cpi_authority +/// - [6]: ctoken_compressible_config +/// - [system_accounts_offset..hot_accounts_start]: Light system + tree accounts +/// - [hot_accounts_start..]: Hot accounts (PDAs then tokens) +#[cfg(feature = "token")] +#[inline(never)] +pub fn process_decompress_accounts_idempotent( + remaining_accounts: &[AI], + instruction_data: &[u8], + cpi_signer: CpiSigner, + program_id: &[u8; 32], + current_slot: u64, +) -> Result<(), LightPdaError> +where + AI: AccountInfoTrait + Clone, + V: DecompressVariant, +{ + // 1. Deserialize params + let params = DecompressIdempotentParams::::try_from_slice(instruction_data) + .map_err(|_| LightPdaError::Borsh)?; + + let system_accounts_offset = params.system_accounts_offset as usize; + if system_accounts_offset > remaining_accounts.len() { + return Err(LightPdaError::InvalidInstructionData); + } + + // 2. Split accounts into PDA and token let (pda_accounts, token_accounts) = params .accounts .split_at_checked(params.token_accounts_offset as usize) - .ok_or(ProgramError::NotEnoughAccountKeys)?; + .ok_or(LightPdaError::InvalidInstructionData)?; - // PDA and token account infos are at the tail of remaining_accounts. + // 3. Load and validate config + let config = LightConfig::load_checked(&remaining_accounts[CONFIG_INDEX], program_id)?; + let rent_sponsor = &remaining_accounts[RENT_SPONSOR_INDEX]; + let rent_sponsor_bump = config.validate_rent_sponsor_account::(rent_sponsor)?; + + // 4. Hot accounts at the tail of remaining_accounts let num_hot_accounts = params.accounts.len(); let hot_accounts_start = remaining_accounts .len() .checked_sub(num_hot_accounts) - .ok_or(ProgramError::NotEnoughAccountKeys)?; + .ok_or(LightPdaError::NotEnoughAccountKeys)?; let hot_account_infos = &remaining_accounts[hot_accounts_start..]; let (pda_account_infos, token_account_infos) = hot_account_infos .split_at_checked(params.token_accounts_offset as usize) - .ok_or(ProgramError::NotEnoughAccountKeys)?; + .ok_or(LightPdaError::NotEnoughAccountKeys)?; let has_pda_accounts = !pda_accounts.is_empty(); let has_token_accounts = !token_accounts.is_empty(); let cpi_context = has_pda_accounts && has_token_accounts; - let config = CpiAccountsConfig { + + // 5. Build CpiAccounts + let cpi_config = CpiAccountsConfig { sol_compression_recipient: false, sol_pool_pda: false, cpi_context, cpi_signer, }; let cpi_accounts = CpiAccounts::new_with_config( - fee_payer, - &remaining_accounts[system_accounts_offset_usize..], - config, + &remaining_accounts[FEE_PAYER_INDEX], + &remaining_accounts[system_accounts_offset..hot_accounts_start], + cpi_config, ); - // Token (ctoken) accounts layout in remaining_accounts: - // [0]fee_payer, [1]pda_config, [2]pda_rent_sponsor, [3]ctoken_rent_sponsor, - // [4]light_token_program, [5]cpi_authority, [6]ctoken_compressible_config + // Token (ctoken) accounts layout: + // [3] ctoken_rent_sponsor, [6] ctoken_compressible_config let ctoken_rent_sponsor = remaining_accounts .get(3) - .ok_or(ProgramError::NotEnoughAccountKeys)?; + .ok_or(LightPdaError::NotEnoughAccountKeys)?; let ctoken_compressible_config = remaining_accounts .get(6) - .ok_or(ProgramError::NotEnoughAccountKeys)?; - - // Build context struct with all needed data (includes internal vec) - let mut decompress_ctx = DecompressCtx { - program_id, - cpi_accounts: &cpi_accounts, - remaining_accounts, - rent_sponsor, - rent_sponsor_bump, - light_config: &light_config, - ctoken_rent_sponsor, - ctoken_compressible_config, - rent: &rent, - current_slot, - output_queue_index: params.output_queue_index, - compressed_account_infos: Vec::new(), - in_token_data: Vec::new(), - in_tlv: None, - token_seeds: Vec::new(), - }; + .ok_or(LightPdaError::NotEnoughAccountKeys)?; - // Process each account using trait dispatch on inner variant - for (pda_account, pda_account_info) in pda_accounts.iter().zip(pda_account_infos) { - pda_account.data.decompress( - &pda_account.tree_info, - pda_account_info, - &mut decompress_ctx, - )?; - } - // Process token accounts - for (token_account, token_account_info) in token_accounts.iter().zip(token_account_infos) { - token_account.data.decompress( - &token_account.tree_info, - token_account_info, - &mut decompress_ctx, - )?; - } + // 6. Build context and dispatch (scoped to release borrows before CPI) + let (compressed_account_infos, in_token_data, in_tlv, token_seeds) = { + let mut decompress_ctx = DecompressCtx { + program_id, + cpi_accounts: &cpi_accounts, + remaining_accounts, + rent_sponsor, + rent_sponsor_bump, + light_config: &config, + current_slot, + output_queue_index: params.output_queue_index, + compressed_account_infos: Vec::new(), + ctoken_rent_sponsor: Some(ctoken_rent_sponsor), + ctoken_compressible_config: Some(ctoken_compressible_config), + in_token_data: Vec::new(), + in_tlv: None, + token_seeds: Vec::new(), + }; + + // Process PDA accounts + for (pda_account_data, pda_account_info) in pda_accounts.iter().zip(pda_account_infos) { + pda_account_data.data.decompress( + &pda_account_data.tree_info, + pda_account_info, + &mut decompress_ctx, + )?; + } + + // Process token accounts + for (token_account_data, token_account_info) in + token_accounts.iter().zip(token_account_infos) + { + token_account_data.data.decompress( + &token_account_data.tree_info, + token_account_info, + &mut decompress_ctx, + )?; + } + ( + decompress_ctx.compressed_account_infos, + decompress_ctx.in_token_data, + decompress_ctx.in_tlv, + decompress_ctx.token_seeds, + ) + }; + + // 7. PDA CPI (Light system program) if has_pda_accounts { - // CPI to Light System Program with proof let pda_only = !cpi_context; if pda_only { - // Manual construction to avoid extra allocations - let instruction_data = light_compressed_account::instruction_data::with_account_info::InstructionDataInvokeCpiWithAccountInfo { + let mut cpi_ix_data = InstructionDataInvokeCpiWithAccountInfo::new( + program_id.into(), + cpi_signer.bump, + params.proof.into(), + ); + cpi_ix_data.account_infos = compressed_account_infos; + cpi_ix_data.invoke::(cpi_accounts.clone())?; + } else { + // PDAs + tokens: write PDA data to CPI context first, tokens will execute + let authority = cpi_accounts + .authority() + .map_err(LightPdaError::from)?; + let cpi_context_account = cpi_accounts + .cpi_context() + .map_err(LightPdaError::from)?; + let system_cpi_accounts = CpiContextWriteAccounts { + fee_payer: &remaining_accounts[FEE_PAYER_INDEX], + authority, + cpi_context: cpi_context_account, + cpi_signer, + }; + + let cpi_ix_data = InstructionDataInvokeCpiWithAccountInfo { mode: 1, bump: cpi_signer.bump, invoking_program_id: cpi_signer.program_id.into(), compress_or_decompress_lamports: 0, is_compress: false, - with_cpi_context: false, + with_cpi_context: true, with_transaction_hash: false, - cpi_context: CompressedCpiContext::default(), - proof: params.proof.0, + cpi_context: CompressedCpiContext::first(), + proof: None, new_address_params: Vec::new(), - account_infos: decompress_ctx.compressed_account_infos, + account_infos: compressed_account_infos, read_only_addresses: Vec::new(), read_only_accounts: Vec::new(), }; - instruction_data.invoke(cpi_accounts.clone())?; - } else { - { - // PDAs + tokens - write to CPI context first, tokens will execute - let authority = cpi_accounts - .authority() - .map_err(|_| ProgramError::MissingRequiredSignature)?; - let cpi_context_account = cpi_accounts - .cpi_context() - .map_err(|_| ProgramError::MissingRequiredSignature)?; - let system_cpi_accounts = CpiContextWriteAccounts { - fee_payer, - authority, - cpi_context: cpi_context_account, - cpi_signer, - }; - - // Manual construction to avoid extra allocations - let instruction_data = light_compressed_account::instruction_data::with_account_info::InstructionDataInvokeCpiWithAccountInfo { - mode: 1, - bump: cpi_signer.bump, - invoking_program_id: cpi_signer.program_id.into(), - compress_or_decompress_lamports: 0, - is_compress: false, - with_cpi_context: true, - with_transaction_hash: false, - cpi_context: CompressedCpiContext::first(), - proof: None, - new_address_params: Vec::new(), - account_infos: decompress_ctx.compressed_account_infos, - read_only_addresses: Vec::new(), - read_only_accounts: Vec::new(), - }; - instruction_data.invoke_write_to_cpi_context_first(system_cpi_accounts)?; - } + cpi_ix_data.invoke_write_to_cpi_context_first(system_cpi_accounts)?; } } + // 8. Token CPI (Transfer2 to light token program) if has_token_accounts { let mut compressions = Vec::new(); - // Assumes is compressed to pubkey. - decompress_ctx - .in_token_data - .iter() - .for_each(|a| compressions.push(Compression::decompress(a.amount, a.mint, a.owner))); + for a in &in_token_data { + compressions.push(Compression::decompress(a.amount, a.mint, a.owner)); + } + let mut cpi = CompressedTokenInstructionDataTransfer2 { with_transaction_hash: false, - in_token_data: decompress_ctx.in_token_data.clone(), - in_tlv: decompress_ctx.in_tlv.clone(), + in_token_data: in_token_data.clone(), + in_tlv: in_tlv.clone(), with_lamports_change_account_merkle_tree_index: false, lamports_change_account_merkle_tree_index: 0, lamports_change_account_owner_index: 0, @@ -315,16 +458,17 @@ where out_lamports: None, out_tlv: None, }; + if has_pda_accounts { cpi.cpi_context = Some( light_token_interface::instructions::transfer2::CompressedCpiContext { set_context: false, first_set_context: false, }, - ) + ); } - // Build Transfer2 account_metas in the order the handler expects: + // Build Transfer2 account metas in the order the handler expects: // [0] light_system_program (readonly) // [1] fee_payer (signer, writable) // [2] cpi_authority_pda (readonly) @@ -334,65 +478,100 @@ where // [6] system_program (readonly) // [7] cpi_context (optional, writable) // [N+] packed_accounts + let fee_payer_key = remaining_accounts[FEE_PAYER_INDEX].key(); let mut account_metas = vec![ - AccountMeta::new_readonly(Pubkey::new_from_array(LIGHT_SYSTEM_PROGRAM_ID), false), - AccountMeta::new(*fee_payer.key, true), - AccountMeta::new_readonly(Pubkey::new_from_array(CPI_AUTHORITY), false), - AccountMeta::new_readonly(Pubkey::new_from_array(REGISTERED_PROGRAM_PDA), false), - AccountMeta::new_readonly( - Pubkey::new_from_array(ACCOUNT_COMPRESSION_AUTHORITY_PDA), - false, - ), - AccountMeta::new_readonly( - Pubkey::new_from_array(ACCOUNT_COMPRESSION_PROGRAM_ID), - false, - ), - AccountMeta::new_readonly(Pubkey::default(), false), + CpiMeta { + pubkey: LIGHT_SYSTEM_PROGRAM_ID, + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: fee_payer_key, + is_signer: true, + is_writable: true, + }, + CpiMeta { + pubkey: CPI_AUTHORITY, + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: REGISTERED_PROGRAM_PDA, + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: ACCOUNT_COMPRESSION_AUTHORITY_PDA, + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: ACCOUNT_COMPRESSION_PROGRAM_ID, + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: [0u8; 32], + is_signer: false, + is_writable: false, + }, // system_program ]; + if cpi_context { let cpi_ctx = cpi_accounts .cpi_context() - .map_err(|_| ProgramError::NotEnoughAccountKeys)?; - account_metas.push(AccountMeta::new(*cpi_ctx.key, false)); + .map_err(LightPdaError::from)?; + account_metas.push(CpiMeta { + pubkey: cpi_ctx.key(), + is_signer: false, + is_writable: true, + }); } + let transfer2_packed_start = account_metas.len(); let packed_accounts_offset = - system_accounts_offset_usize + cpi_accounts.system_accounts_end_offset(); + system_accounts_offset + cpi_accounts.system_accounts_end_offset(); for account in &remaining_accounts[packed_accounts_offset..] { - account_metas.push(AccountMeta { - pubkey: *account.key, - is_signer: account.is_signer, - is_writable: account.is_writable, + account_metas.push(CpiMeta { + pubkey: account.key(), + is_signer: account.is_signer(), + is_writable: account.is_writable(), }); } - cpi.in_token_data.iter().for_each(|data| { + + // Mark owner accounts as signers for the Transfer2 CPI + for data in &in_token_data { account_metas[data.owner as usize + transfer2_packed_start].is_signer = true; - }); - let mut instruction_data = vec![TRANSFER2]; - cpi.serialize(&mut instruction_data).unwrap(); - let instruction = Instruction { - program_id: LIGHT_TOKEN_PROGRAM_ID.into(), - accounts: account_metas, - data: instruction_data, - }; - // For ATAs, no PDA signing is needed (wallet owner signed at transaction level). - // For regular token accounts, use invoke_signed with PDA seeds. - if decompress_ctx.token_seeds.is_empty() { - // All tokens are ATAs - use regular invoke (no PDA signing needed) - anchor_lang::solana_program::program::invoke(&instruction, remaining_accounts)?; + } + + // Serialize instruction data + let mut transfer2_data = vec![TRANSFER2]; + cpi.serialize(&mut transfer2_data) + .map_err(|_| LightPdaError::Borsh)?; + + // Invoke the light token program + if token_seeds.is_empty() { + // All ATAs - no PDA signing needed + AI::invoke_cpi( + &LIGHT_TOKEN_PROGRAM_ID, + &transfer2_data, + &account_metas, + remaining_accounts, + &[], + ) + .map_err(|_| LightPdaError::CpiFailed)?; } else { // At least one regular token account - use invoke_signed with PDA seeds - let signer_seed_refs: Vec<&[u8]> = decompress_ctx - .token_seeds - .iter() - .map(|s| s.as_slice()) - .collect(); - - invoke_signed( - &instruction, + let signer_seed_refs: Vec<&[u8]> = + token_seeds.iter().map(|s| s.as_slice()).collect(); + AI::invoke_cpi( + &LIGHT_TOKEN_PROGRAM_ID, + &transfer2_data, + &account_metas, remaining_accounts, &[signer_seed_refs.as_slice()], - )?; + ) + .map_err(|_| LightPdaError::CpiFailed)?; } } diff --git a/sdk-libs/sdk-interface/src/program/decompression/token.rs b/sdk-libs/sdk-interface/src/program/decompression/token.rs index 014046af96..256d9dc6df 100644 --- a/sdk-libs/sdk-interface/src/program/decompression/token.rs +++ b/sdk-libs/sdk-interface/src/program/decompression/token.rs @@ -1,32 +1,35 @@ //! Token account decompression. +use light_account_checks::AccountInfoTrait; use light_sdk_types::instruction::PackedStateTreeInfo; -use light_token_interface::instructions::extensions::ExtensionInstructionData; -use solana_account_info::AccountInfo; -use solana_program_error::ProgramError; +use light_token_interface::{instructions::extensions::ExtensionInstructionData, LIGHT_TOKEN_PROGRAM_ID}; use super::create_token_account::{ build_create_ata_instruction, build_create_token_account_instruction, }; -use crate::program::{ - decompression::processor::DecompressCtx, - variant::PackedLightAccountVariantTrait, +use crate::{ + error::LightPdaError, + program::{ + decompression::processor::DecompressCtx, + variant::PackedLightAccountVariantTrait, + }, }; -pub fn prepare_token_account_for_decompression<'info, const SEED_COUNT: usize, P>( +pub fn prepare_token_account_for_decompression( packed: &P, tree_info: &PackedStateTreeInfo, output_queue_index: u8, - token_account_info: &AccountInfo<'info>, - ctx: &mut DecompressCtx<'_, 'info>, -) -> std::result::Result<(), ProgramError> + token_account_info: &AI, + ctx: &mut DecompressCtx<'_, AI>, +) -> Result<(), LightPdaError> where + AI: AccountInfoTrait + Clone, P: PackedLightAccountVariantTrait, { let packed_accounts = ctx .cpi_accounts .packed_accounts() - .map_err(|_| ProgramError::NotEnoughAccountKeys)?; + .map_err(LightPdaError::from)?; let token_data = packed.into_in_token_data(tree_info, output_queue_index)?; // Get TLV extension early to detect ATA @@ -48,83 +51,103 @@ where }); // Resolve mint pubkey from packed index - let mint_pubkey = packed_accounts + let mint_key = packed_accounts .get(token_data.mint as usize) - .ok_or(ProgramError::InvalidAccountData)? - .key; + .ok_or(LightPdaError::InvalidInstructionData)? + .key(); - let fee_payer = ctx.cpi_accounts.fee_payer(); + let fee_payer_key = ctx.cpi_accounts.fee_payer().key(); - // Helper to check if token account is already initialized + // Idempotency: check if token account is already initialized // State byte at offset 108: 0=Uninitialized, 1=Initialized, 2=Frozen const STATE_OFFSET: usize = 108; - let is_already_initialized = !token_account_info.data_is_empty() - && token_account_info.data_len() > STATE_OFFSET - && token_account_info.try_borrow_data()?[STATE_OFFSET] != 0; + let is_already_initialized = token_account_info.data_len() > STATE_OFFSET && { + let data = token_account_info + .try_borrow_data() + .map_err(|_| LightPdaError::ConstraintViolation)?; + data[STATE_OFFSET] != 0 + }; + + // Get token-specific references from context + let ctoken_compressible_config_key = ctx + .ctoken_compressible_config + .as_ref() + .ok_or(LightPdaError::NotEnoughAccountKeys)? + .key(); + let ctoken_rent_sponsor_key = ctx + .ctoken_rent_sponsor + .as_ref() + .ok_or(LightPdaError::NotEnoughAccountKeys)? + .key(); if let Some((ata_bump, wallet_owner_index)) = ata_info { // ATA path: use invoke() without signer seeds - // Resolve wallet owner pubkey from packed index - let wallet_owner_pubkey = packed_accounts + let wallet_owner_key = packed_accounts .get(wallet_owner_index as usize) - .ok_or(ProgramError::InvalidAccountData)? - .key; - - // Idempotency check: only create ATA if it doesn't exist - // For ATAs, we still continue with decompression even if account exists - if token_account_info.data_is_empty() { - let instruction = build_create_ata_instruction( - wallet_owner_pubkey, - mint_pubkey, - fee_payer.key, - token_account_info.key, + .ok_or(LightPdaError::InvalidInstructionData)? + .key(); + + // Idempotency: only create ATA if it doesn't exist + if token_account_info.data_len() == 0 { + let (data, account_metas) = build_create_ata_instruction( + &wallet_owner_key, + &mint_key, + &fee_payer_key, + &token_account_info.key(), ata_bump, - ctx.ctoken_compressible_config.key, - ctx.ctoken_rent_sponsor.key, + &ctoken_compressible_config_key, + &ctoken_rent_sponsor_key, ctx.light_config.write_top_up, )?; - // Invoke WITHOUT signer seeds - ATA is derived from light token program, not our program - anchor_lang::solana_program::program::invoke(&instruction, ctx.remaining_accounts)?; + // Invoke WITHOUT signer seeds - ATA is derived from light token program + AI::invoke_cpi( + &LIGHT_TOKEN_PROGRAM_ID, + &data, + &account_metas, + ctx.remaining_accounts, + &[], + ) + .map_err(|_| LightPdaError::CpiFailed)?; } - // Don't extend token_seeds for ATAs (invoke, not invoke_signed) } else { // Regular token vault path: use invoke_signed with PDA seeds - // For regular vaults, if already initialized, skip BOTH creation AND decompression (full idempotency) if is_already_initialized { - solana_msg::msg!("Token vault is already decompressed, skipping"); return Ok(()); } let bump = &[packed.bump()]; let seeds = packed .seed_refs_with_bump(packed_accounts, bump) - .map_err(|_| ProgramError::InvalidSeeds)?; + .map_err(|_| LightPdaError::InvalidSeeds)?; // Derive owner pubkey from constant owner_seeds let owner = packed.derive_owner(); let signer_seeds: Vec<&[u8]> = seeds.iter().copied().collect(); - let instruction = build_create_token_account_instruction( - token_account_info.key, - mint_pubkey, + let (data, account_metas) = build_create_token_account_instruction( + &token_account_info.key(), + &mint_key, &owner, - fee_payer.key, - ctx.ctoken_compressible_config.key, - ctx.ctoken_rent_sponsor.key, + &fee_payer_key, + &ctoken_compressible_config_key, + &ctoken_rent_sponsor_key, ctx.light_config.write_top_up, &signer_seeds, ctx.program_id, )?; // Invoke with PDA seeds - anchor_lang::solana_program::program::invoke_signed( - &instruction, + AI::invoke_cpi( + &LIGHT_TOKEN_PROGRAM_ID, + &data, + &account_metas, ctx.remaining_accounts, &[signer_seeds.as_slice()], - )?; + ) + .map_err(|_| LightPdaError::CpiFailed)?; // Push seeds for the Transfer2 CPI (needed for invoke_signed) ctx.token_seeds.extend(seeds.iter().map(|s| s.to_vec())); diff --git a/sdk-libs/sdk-interface/src/program/mod.rs b/sdk-libs/sdk-interface/src/program/mod.rs index 3e0139e7d1..123df1ee9c 100644 --- a/sdk-libs/sdk-interface/src/program/mod.rs +++ b/sdk-libs/sdk-interface/src/program/mod.rs @@ -5,8 +5,6 @@ pub mod compression; pub mod config; +pub mod decompression; pub mod validation; pub mod variant; - -#[cfg(feature = "anchor")] -pub mod decompression; diff --git a/sdk-libs/sdk-interface/src/program/validation.rs b/sdk-libs/sdk-interface/src/program/validation.rs index de504eaf73..3e374a7efb 100644 --- a/sdk-libs/sdk-interface/src/program/validation.rs +++ b/sdk-libs/sdk-interface/src/program/validation.rs @@ -1,26 +1,19 @@ //! Shared validation utilities for compress/decompress operations. -use solana_account_info::AccountInfo; -use solana_program_error::ProgramError; -use solana_pubkey::Pubkey; - -use crate::{ - error::LightPdaError, - program::config::LightConfig, -}; use light_account_checks::{ - account_iterator::AccountIterator, - checks::check_data_is_zeroed, + account_iterator::AccountIterator, checks::check_data_is_zeroed, AccountInfoTrait, }; +use crate::{error::LightPdaError, program::config::LightConfig}; + /// Validated PDA context after account extraction and config validation. -pub struct ValidatedPdaContext<'info> { - pub fee_payer: AccountInfo<'info>, +pub struct ValidatedPdaContext { + pub fee_payer: AI, pub light_config: LightConfig, - pub rent_sponsor: AccountInfo<'info>, + pub rent_sponsor: AI, pub rent_sponsor_bump: u8, /// Only present when EXTRACT_COMPRESSION_AUTHORITY=true - pub compression_authority: Option>, + pub compression_authority: Option, } /// Extract and validate accounts for compress operations (4 accounts including compression_authority). @@ -29,12 +22,12 @@ pub struct ValidatedPdaContext<'info> { /// - `0` - fee_payer (Signer, mut) /// - `1` - config (LightConfig PDA) /// - `2` - rent_sponsor (mut) -/// - `3` - compression_authority (TODO: Signer when client-side code is updated) -pub fn validate_compress_accounts<'info>( - remaining_accounts: &[AccountInfo<'info>], - program_id: &Pubkey, -) -> Result, ProgramError> { - validate_pda_common_accounts_inner::(remaining_accounts, program_id) +/// - `3` - compression_authority +pub fn validate_compress_accounts( + remaining_accounts: &[AI], + program_id: &[u8; 32], +) -> Result, LightPdaError> { + validate_pda_common_accounts_inner::(remaining_accounts, program_id) } /// Extract and validate accounts for decompress operations (3 accounts, no compression_authority). @@ -43,72 +36,47 @@ pub fn validate_compress_accounts<'info>( /// - `0` - fee_payer (Signer, mut) /// - `1` - config (LightConfig PDA) /// - `2` - rent_sponsor (mut) -pub fn validate_decompress_accounts<'info>( - remaining_accounts: &[AccountInfo<'info>], - program_id: &Pubkey, -) -> Result, ProgramError> { - validate_pda_common_accounts_inner::(remaining_accounts, program_id) +pub fn validate_decompress_accounts( + remaining_accounts: &[AI], + program_id: &[u8; 32], +) -> Result, LightPdaError> { + validate_pda_common_accounts_inner::(remaining_accounts, program_id) } /// Internal function with const generic for optional compression_authority extraction. -/// -/// # Security checks: -/// - fee_payer is signer and mutable -/// - config exists and is not mutable -/// - rent_sponsor is mutable -/// - compression_authority is extracted (if EXTRACT_COMPRESSION_AUTHORITY=true) -/// - LightConfig ownership matches program_id -/// - LightConfig PDA derivation is correct -/// - rent_sponsor matches config.rent_sponsor -/// - TODO: compression_authority matches config.compression_authority (when enabled) -fn validate_pda_common_accounts_inner<'info, const EXTRACT_COMPRESSION_AUTHORITY: bool>( - remaining_accounts: &[AccountInfo<'info>], - program_id: &Pubkey, -) -> Result, ProgramError> { +fn validate_pda_common_accounts_inner( + remaining_accounts: &[AI], + program_id: &[u8; 32], +) -> Result, LightPdaError> +where + AI: AccountInfoTrait + Clone, +{ let mut account_iter = AccountIterator::new(remaining_accounts); let fee_payer = account_iter .next_signer_mut("fee_payer") - .map_err(ProgramError::from)?; + .map_err(LightPdaError::AccountCheck)?; let config = account_iter .next_non_mut("config") - .map_err(ProgramError::from)?; + .map_err(LightPdaError::AccountCheck)?; let rent_sponsor = account_iter .next_mut("rent_sponsor") - .map_err(ProgramError::from)?; + .map_err(LightPdaError::AccountCheck)?; let compression_authority = if EXTRACT_COMPRESSION_AUTHORITY { - // TODO: make compression_authority a signer when client-side code is updated Some( account_iter .next_account("compression_authority") - .map_err(ProgramError::from)? + .map_err(LightPdaError::AccountCheck)? .clone(), ) } else { None }; - let light_config = LightConfig::load_checked(config, program_id) - .map_err(|_| ProgramError::InvalidAccountData)?; - - let rent_sponsor_bump = light_config - .validate_rent_sponsor(rent_sponsor) - .map_err(|_| LightPdaError::InvalidRentSponsor)?; - - // TODO: validate compression_authority matches config when client-side code is updated - // if EXTRACT_COMPRESSION_AUTHORITY { - // if let Some(ref auth) = compression_authority { - // if *auth.key != light_config.compression_authority { - // solana_msg::msg!( - // "compression_authority mismatch: expected {:?}, got {:?}", - // light_config.compression_authority, - // auth.key - // ); - // return Err(LightPdaError::ConstraintViolation.into()); - // } - // } - // } + let light_config = LightConfig::load_checked(config, program_id)?; + + let rent_sponsor_bump = light_config.validate_rent_sponsor_account(rent_sponsor)?; Ok(ValidatedPdaContext { fee_payer: fee_payer.clone(), @@ -122,37 +90,25 @@ fn validate_pda_common_accounts_inner<'info, const EXTRACT_COMPRESSION_AUTHORITY /// Validate and split remaining_accounts at system_accounts_offset. /// /// Returns (accounts_before_offset, accounts_from_offset). -pub fn split_at_system_accounts_offset<'a, 'info>( - remaining_accounts: &'a [AccountInfo<'info>], +pub fn split_at_system_accounts_offset( + remaining_accounts: &[AI], system_accounts_offset: u8, -) -> Result<(&'a [AccountInfo<'info>], &'a [AccountInfo<'info>]), ProgramError> { +) -> Result<(&[AI], &[AI]), LightPdaError> { let offset = system_accounts_offset as usize; - remaining_accounts.split_at_checked(offset).ok_or_else(|| { - solana_msg::msg!( - "system_accounts_offset {} > len {}", - offset, - remaining_accounts.len() - ); - ProgramError::InvalidInstructionData - }) + remaining_accounts + .split_at_checked(offset) + .ok_or(LightPdaError::ConstraintViolation) } /// Extract PDA accounts from the tail of remaining_accounts. -pub fn extract_tail_accounts<'a, 'info>( - remaining_accounts: &'a [AccountInfo<'info>], +pub fn extract_tail_accounts( + remaining_accounts: &[AI], num_pda_accounts: usize, -) -> Result<&'a [AccountInfo<'info>], ProgramError> { +) -> Result<&[AI], LightPdaError> { let start = remaining_accounts .len() .checked_sub(num_pda_accounts) - .ok_or_else(|| { - solana_msg::msg!( - "num_pda_accounts {} > len {}", - num_pda_accounts, - remaining_accounts.len() - ); - ProgramError::NotEnoughAccountKeys - })?; + .ok_or(LightPdaError::ConstraintViolation)?; Ok(&remaining_accounts[start..]) } @@ -161,13 +117,15 @@ pub fn extract_tail_accounts<'a, 'info>( /// Returns: /// - `Ok(true)` if account has data and non-zero discriminator (initialized) /// - `Ok(false)` if account is empty or has zeroed discriminator (not initialized) -pub fn is_pda_initialized(account: &AccountInfo) -> Result { +pub fn is_pda_initialized(account: &AI) -> Result { use light_account_checks::discriminator::DISCRIMINATOR_LEN; if account.data_is_empty() { return Ok(false); } - let data = account.try_borrow_data()?; + let data = account + .try_borrow_data() + .map_err(|_| LightPdaError::ConstraintViolation)?; if data.len() < DISCRIMINATOR_LEN { return Ok(false); } @@ -180,6 +138,9 @@ pub fn is_pda_initialized(account: &AccountInfo) -> Result { /// Returns true if: /// - Account has no data (empty) /// - Account is not owned by the expected program -pub fn should_skip_compression(account: &AccountInfo, expected_owner: &Pubkey) -> bool { - account.data_is_empty() || account.owner != expected_owner +pub fn should_skip_compression( + account: &AI, + expected_owner: &[u8; 32], +) -> bool { + account.data_is_empty() || !account.is_owned_by(expected_owner) } diff --git a/sdk-libs/sdk-interface/src/program/variant.rs b/sdk-libs/sdk-interface/src/program/variant.rs index 9b3ab25e37..4af921a388 100644 --- a/sdk-libs/sdk-interface/src/program/variant.rs +++ b/sdk-libs/sdk-interface/src/program/variant.rs @@ -1,149 +1,139 @@ -//! Traits for decompression variant construction and manual Light Protocol implementation. +//! Traits for decompression variant construction and Light Protocol implementation. //! //! This module contains traits for typed compressed account handling: //! - Base traits (`IntoVariant`) - always available -//! - Variant traits (`LightAccountVariantTrait`, `PackedLightAccountVariantTrait`) - anchor-gated -//! - Token seed traits (`UnpackedTokenSeeds`, `PackedTokenSeeds`) - anchor-gated +//! - Variant traits (`LightAccountVariantTrait`, `PackedLightAccountVariantTrait`) - always available +//! - Token seed traits (`UnpackedTokenSeeds`, `PackedTokenSeeds`) - behind `token` feature -// --- Base traits (always available) --- +use light_account_checks::AccountInfoTrait; -#[cfg(feature = "anchor")] -use anchor_lang::error::Error; -#[cfg(not(feature = "anchor"))] -use solana_program_error::ProgramError as Error; +use crate::{ + account::light_account::AccountType, + error::LightPdaError, + AnchorDeserialize, AnchorSerialize, +}; + +// --- Base trait (always available) --- /// Trait for seeds that can construct a compressed account variant. /// /// Implemented by generated `XxxSeeds` structs (e.g., `UserRecordSeeds`). /// The macro generates impls that deserialize account data and verify seeds match. -/// -/// # Example (generated code) -/// ```ignore -/// impl IntoVariant for UserRecordSeeds { -/// fn into_variant(self, data: &[u8]) -> Result { -/// RentFreeAccountVariant::user_record(data, self) -/// } -/// } -/// ``` pub trait IntoVariant { /// Construct variant from compressed account data bytes and these seeds. - /// - /// # Arguments - /// * `data` - Raw compressed account data bytes - /// - /// # Returns - /// The constructed variant on success, or an error if: - /// - Deserialization fails - /// - Seed verification fails (data.* seeds don't match account data) - fn into_variant(self, data: &[u8]) -> Result; + fn into_variant(self, data: &[u8]) -> Result; } -// --- Anchor-gated variant traits --- - -#[cfg(feature = "anchor")] -mod anchor_traits { - use anchor_lang::prelude::*; - use light_sdk_types::instruction::PackedStateTreeInfo; - use light_token_interface::instructions::{ - extensions::ExtensionInstructionData, transfer2::MultiInputTokenDataWithContext, - }; - use solana_program_error::ProgramError; - - use crate::account::light_account::AccountType; - - /// Trait for unpacked compressed account variants with seeds. - /// - /// Implementations are generated by the `#[light_program]` macro for each - /// account type marked with `#[light_account(init)]`. - /// - /// # Type Parameters - /// * `SEED_COUNT` - Number of seeds including bump for CPI signing - /// * `Seeds` - The seeds struct type (e.g., `UserRecordSeeds`) - /// * `Data` - The account data type (e.g., `UserRecord`) - /// * `Packed` - The packed variant type for serialization - pub trait LightAccountVariantTrait: - Sized + Clone + AnchorSerialize + AnchorDeserialize - { - /// The program ID that owns accounts of this variant type. - const PROGRAM_ID: Pubkey; - - /// The seeds struct type containing seed values. - type Seeds; - - /// The account data type. - type Data; - - /// The packed variant type for efficient serialization. - type Packed: PackedLightAccountVariantTrait; - - /// Get a reference to the account data. - fn data(&self) -> &Self::Data; - - /// Get seed values as owned byte vectors for PDA derivation. - fn seed_vec(&self) -> Vec>; +// --- Variant traits --- - /// Get seed references with bump for CPI signing. - /// Returns a fixed-size array that can be passed to invoke_signed. - fn seed_refs_with_bump<'a>(&'a self, bump_storage: &'a [u8; 1]) -> [&'a [u8]; SEED_COUNT]; - - /// Derive the PDA address and bump seed using PROGRAM_ID. - fn derive_pda(&self) -> (Pubkey, u8) { - let seeds = self.seed_vec(); - let seed_slices: Vec<&[u8]> = seeds.iter().map(|s| s.as_slice()).collect(); - Pubkey::find_program_address(&seed_slices, &Self::PROGRAM_ID) - } +/// Trait for unpacked compressed account variants with seeds. +/// +/// Implementations are generated by the `#[light_program]` macro for each +/// account type marked with `#[light_account(init)]`. +/// +/// # Type Parameters +/// * `SEED_COUNT` - Number of seeds including bump for CPI signing +pub trait LightAccountVariantTrait: + Sized + Clone + AnchorSerialize + AnchorDeserialize +{ + /// The program ID that owns accounts of this variant type. + const PROGRAM_ID: [u8; 32]; + + /// The seeds struct type containing seed values. + type Seeds; + + /// The account data type. + type Data; + + /// The packed variant type for efficient serialization. + type Packed: PackedLightAccountVariantTrait; + + /// Get a reference to the account data. + fn data(&self) -> &Self::Data; + + /// Get seed values as owned byte vectors for PDA derivation. + fn seed_vec(&self) -> Vec>; + + /// Get seed references with bump for CPI signing. + /// Returns a fixed-size array that can be passed to invoke_signed. + fn seed_refs_with_bump<'a>(&'a self, bump_storage: &'a [u8; 1]) -> [&'a [u8]; SEED_COUNT]; + + /// Derive the PDA address and bump seed using PROGRAM_ID. + fn derive_pda(&self) -> ([u8; 32], u8) { + let seeds = self.seed_vec(); + let seed_slices: Vec<&[u8]> = seeds.iter().map(|s| s.as_slice()).collect(); + AI::find_program_address(&seed_slices, &Self::PROGRAM_ID) } +} - /// Trait for packed compressed account variants. - /// - /// Packed variants use u8 indices instead of 32-byte Pubkeys for efficient - /// serialization. They can be unpacked back to full variants using account info. - #[allow(clippy::wrong_self_convention)] - pub trait PackedLightAccountVariantTrait: - Sized + Clone + AnchorSerialize + AnchorDeserialize - { - /// The unpacked variant type with full Pubkey values. - type Unpacked: LightAccountVariantTrait; - - /// The account type (Pda, Token, Ata, etc.) for dispatch. - const ACCOUNT_TYPE: AccountType; - - /// Get the PDA bump seed. - fn bump(&self) -> u8; - - /// Unpack this variant by resolving u8 indices to Pubkeys. - fn unpack(&self, accounts: &[AccountInfo]) -> Result; - - /// Get seed references with bump for CPI signing. - /// Resolves u8 indices to pubkey refs from accounts slice. - fn seed_refs_with_bump<'a>( - &'a self, - accounts: &'a [AccountInfo], - bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; SEED_COUNT], ProgramError>; +/// Trait for packed compressed account variants. +/// +/// Packed variants use u8 indices instead of 32-byte Pubkeys for efficient +/// serialization. They can be unpacked back to full variants using account info. +#[allow(clippy::wrong_self_convention)] +pub trait PackedLightAccountVariantTrait: + Sized + Clone + AnchorSerialize + AnchorDeserialize +{ + /// The unpacked variant type with full Pubkey values. + type Unpacked: LightAccountVariantTrait; + + /// The account type (Pda, Token, Ata, etc.) for dispatch. + const ACCOUNT_TYPE: AccountType; + + /// Get the PDA bump seed. + fn bump(&self) -> u8; + + /// Unpack this variant by resolving u8 indices to Pubkeys. + fn unpack( + &self, + accounts: &[AI], + ) -> Result; + + /// Get seed references with bump for CPI signing. + /// Resolves u8 indices to pubkey refs from accounts slice. + fn seed_refs_with_bump<'a, AI: AccountInfoTrait>( + &'a self, + accounts: &'a [AI], + bump_storage: &'a [u8; 1], + ) -> Result<[&'a [u8]; SEED_COUNT], LightPdaError>; + + /// Extract token data for compressed token CPI. + /// Only meaningful for token account variants; PDA variants should return an error. + #[cfg(feature = "token")] + fn into_in_token_data( + &self, + tree_info: &light_sdk_types::instruction::PackedStateTreeInfo, + output_queue_index: u8, + ) -> Result< + light_token_interface::instructions::transfer2::MultiInputTokenDataWithContext, + LightPdaError, + >; + + /// Extract TLV extension data for compressed token CPI. + /// Only meaningful for token account variants; PDA variants return `None`. + #[cfg(feature = "token")] + fn into_in_tlv( + &self, + ) -> Result< + Option>, + LightPdaError, + >; + + /// Derive the owner pubkey from constant owner_seeds and program ID. + /// Only meaningful for token account variants; PDA variants return default. + #[cfg(feature = "token")] + fn derive_owner(&self) -> [u8; 32] { + [0u8; 32] + } +} - /// Extract token data for compressed token CPI. - /// - /// Returns the packed token data needed for the token transfer instruction. - /// Only meaningful for token account variants; PDA variants should not override. - fn into_in_token_data( - &self, - tree_info: &PackedStateTreeInfo, - output_queue_index: u8, - ) -> Result; +// --- Token seed traits (behind `token` feature) --- - /// Extract TLV extension data for compressed token CPI. - /// - /// Returns extension instruction data if the token account has extensions. - /// Only meaningful for token account variants; PDA variants return `None`. - fn into_in_tlv(&self) -> Result>>; +#[cfg(feature = "token")] +mod token_traits { + use light_account_checks::AccountInfoTrait; - /// Derive the owner pubkey from constant owner_seeds and program ID. - /// Only meaningful for token account variants; PDA variants return default. - fn derive_owner(&self) -> Pubkey { - Pubkey::default() - } - } + use crate::{AnchorDeserialize, AnchorSerialize, error::LightPdaError}; /// Trait for unpacked token seed structs. /// @@ -151,12 +141,12 @@ mod anchor_traits { /// (e.g., `TokenVaultSeeds`). Provides seed-specific behavior for the blanket /// `LightAccountVariantTrait` impl on `TokenDataWithSeeds`. pub trait UnpackedTokenSeeds: - Clone + std::fmt::Debug + AnchorSerialize + AnchorDeserialize + Clone + core::fmt::Debug + AnchorSerialize + AnchorDeserialize { /// The packed seeds type. type Packed: PackedTokenSeeds; - const PROGRAM_ID: Pubkey; + const PROGRAM_ID: [u8; 32]; fn seed_vec(&self) -> Vec>; fn seed_refs_with_bump<'a>(&'a self, bump_storage: &'a [u8; 1]) -> [&'a [u8]; N]; } @@ -166,22 +156,33 @@ mod anchor_traits { /// Generated by the `#[light_program]` macro on per-variant packed seed structs /// (e.g., `PackedTokenVaultSeeds`). Provides seed-specific behavior for the blanket /// `PackedLightAccountVariantTrait` impl on `TokenDataWithPackedSeeds`. + /// + /// Note: `Unpack` cannot be a supertrait because `AI` is not available in + /// the trait definition. Instead, generic unpack/seed methods are defined here directly. pub trait PackedTokenSeeds: - crate::account::pack::Unpack + Clone + std::fmt::Debug + AnchorSerialize + AnchorDeserialize + Clone + core::fmt::Debug + AnchorSerialize + AnchorDeserialize { + type Unpacked: UnpackedTokenSeeds; + fn bump(&self) -> u8; - fn seed_refs_with_bump<'a>( + + /// Unpack seeds by resolving u8 indices to pubkeys from accounts slice. + fn unpack_seeds( + &self, + accounts: &[AI], + ) -> Result; + + /// Get seed references with bump for CPI signing. + fn seed_refs_with_bump<'a, AI: AccountInfoTrait>( &'a self, - accounts: &'a [AccountInfo], + accounts: &'a [AI], bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; N], ProgramError>; + ) -> Result<[&'a [u8]; N], LightPdaError>; /// Derive the owner pubkey from constant owner_seeds and program ID. - fn derive_owner(&self) -> Pubkey; + fn derive_owner(&self) -> [u8; 32]; } } -#[cfg(feature = "anchor")] -pub use anchor_traits::{ - LightAccountVariantTrait, PackedLightAccountVariantTrait, PackedTokenSeeds, UnpackedTokenSeeds, -}; +#[cfg(feature = "token")] +pub use token_traits::{PackedTokenSeeds, UnpackedTokenSeeds}; diff --git a/sdk-libs/sdk-types/src/cpi_accounts/v2.rs b/sdk-libs/sdk-types/src/cpi_accounts/v2.rs index 9afb32facf..98de6e19cd 100644 --- a/sdk-libs/sdk-types/src/cpi_accounts/v2.rs +++ b/sdk-libs/sdk-types/src/cpi_accounts/v2.rs @@ -208,6 +208,7 @@ impl<'a, T: AccountInfoTrait + Clone> CpiAccounts<'a, T> { } /// Create a vector of account info references + #[cfg(any(feature = "std", feature = "alloc"))] pub fn to_account_infos(&self) -> Vec { let mut account_infos = Vec::with_capacity(1 + self.accounts.len()); account_infos.push(self.fee_payer().clone()); @@ -240,6 +241,7 @@ impl<'a, T: AccountInfoTrait + Clone> CpiAccounts<'a, T> { Ok(&self.accounts[system_offset..]) } + #[cfg(any(feature = "std", feature = "alloc"))] pub fn tree_pubkeys(&self) -> Result> { Ok(self .tree_accounts()? diff --git a/sdk-libs/sdk/Cargo.toml b/sdk-libs/sdk/Cargo.toml index 0b17313464..5840967686 100644 --- a/sdk-libs/sdk/Cargo.toml +++ b/sdk-libs/sdk/Cargo.toml @@ -18,7 +18,6 @@ idl-build = [ "light-sdk-types/idl-build", "light-compressible/idl-build", "light-token-interface/idl-build", - "light-sdk-interface/idl-build", "anchor", "dep:solana-program" ] @@ -28,7 +27,6 @@ anchor = [ "light-sdk-types/anchor", "light-compressible/anchor", "light-token-interface/anchor", - "light-sdk-interface/anchor" ] v2 = ["light-sdk-types/v2"] cpi-context = ["light-sdk-types/cpi-context"] @@ -77,7 +75,7 @@ light-concurrent-merkle-tree = { workspace = true, optional = true } light-compressible = { workspace = true } light-heap = { workspace = true, optional = true } light-token-interface = { workspace = true } # TODO: make optional -light-sdk-interface = { workspace = true, features = ["solana", "std"] } +light-sdk-interface = { workspace = true, features = ["std"] } [dev-dependencies] num-bigint = { workspace = true } diff --git a/sdk-libs/sdk/src/cpi/instruction.rs b/sdk-libs/sdk/src/cpi/instruction.rs deleted file mode 100644 index 87e5a7ac29..0000000000 --- a/sdk-libs/sdk/src/cpi/instruction.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::{account::LightAccount, AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError}; - -#[cfg(feature = "poseidon")] -use crate::DataHasher; - -/// Extension trait adding `with_light_account` to CPI instruction builders. -/// -/// This is SDK-specific because it depends on [`LightAccount`](crate::account::LightAccount), -/// which requires SHA256/Poseidon hashing. The base [`LightCpiInstruction`](light_sdk_interface::cpi::LightCpiInstruction) -/// trait from `light-sdk-interface` is framework-agnostic. -/// -/// # Usage -/// -/// Import this trait alongside [`LightCpiInstruction`](light_sdk_interface::cpi::LightCpiInstruction): -/// ```rust,ignore -/// use light_sdk::cpi::{LightCpiInstruction, WithLightAccount}; -/// ``` -pub trait WithLightAccount: Sized { - /// Adds a compressed account to the instruction (using SHA256 hashing). - /// - /// The account can be an input (for updating/closing), output (for creating/updating), - /// or both. The method automatically handles the conversion based on the account state. - /// - /// # Arguments - /// * `account` - The light account to add to the instruction - /// - /// # Type Parameters - /// * `A` - The compressed account data type - #[must_use = "with_light_account returns a new value"] - fn with_light_account(self, account: LightAccount) -> Result - where - A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default; - - /// Adds a compressed account to the instruction (using Poseidon hashing). - /// - /// Similar to [`with_light_account`](Self::with_light_account), but uses Poseidon hashing - /// instead of SHA256. Use this when your compressed account data implements [`DataHasher`]. - /// - /// # Arguments - /// * `account` - The light account to add to the instruction - /// - /// # Type Parameters - /// * `A` - The compressed account data type that implements DataHasher - #[cfg(feature = "poseidon")] - #[must_use = "with_light_account_poseidon returns a new value"] - fn with_light_account_poseidon( - self, - account: crate::account::poseidon::LightAccount, - ) -> Result - where - A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + DataHasher + Default; -} diff --git a/sdk-libs/sdk/src/cpi/mod.rs b/sdk-libs/sdk/src/cpi/mod.rs index 489bbaf655..ad154cd423 100644 --- a/sdk-libs/sdk/src/cpi/mod.rs +++ b/sdk-libs/sdk/src/cpi/mod.rs @@ -35,14 +35,114 @@ //! .invoke(light_cpi_accounts)?; //! ``` -// Re-export everything from interface's CPI module +// Re-export everything from interface's CPI module (LightCpi, InvokeLightSystemProgram, etc.) pub use light_sdk_interface::cpi::*; -// SDK-specific extension trait (adds with_light_account to CPI builders) -mod instruction; -pub use instruction::WithLightAccount; +use light_compressed_account::instruction_data::{ + compressed_proof::ValidityProof, + cpi_context::CompressedCpiContext, +}; + +use crate::{ + account::LightAccount, AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError, +}; + +#[cfg(feature = "poseidon")] +use crate::DataHasher; + +/// Trait for Light CPI instruction types including `with_light_account`. +/// +/// This is the SDK-level trait that provides the full builder API: +/// - CPI builder methods (`new_cpi`, `get_mode`, `get_bump`, etc.) +/// - `with_light_account` for adding compressed accounts +/// +/// Internally delegates base methods to [`LightCpi`]. +pub trait LightCpiInstruction: Sized { + /// Creates a new CPI instruction builder with a validity proof. + fn new_cpi(cpi_signer: crate::cpi::CpiSigner, proof: ValidityProof) -> Self; + + /// Returns the instruction mode (0 for v1, 1 for v2). + fn get_mode(&self) -> u8; + + /// Returns the CPI signer bump seed. + fn get_bump(&self) -> u8; + + /// Writes instruction to CPI context as the first operation in a batch. + #[must_use = "write_to_cpi_context_first returns a new value"] + fn write_to_cpi_context_first(self) -> Self; + + /// Writes instruction to CPI context as a subsequent operation in a batch. + #[must_use = "write_to_cpi_context_set returns a new value"] + fn write_to_cpi_context_set(self) -> Self; + + /// Executes all operations accumulated in CPI context. + #[must_use = "execute_with_cpi_context returns a new value"] + fn execute_with_cpi_context(self) -> Self; + + /// Returns whether this instruction uses CPI context. + fn get_with_cpi_context(&self) -> bool; + + /// Returns the CPI context configuration. + fn get_cpi_context(&self) -> &CompressedCpiContext; + + /// Returns whether this instruction has any read-only accounts. + fn has_read_only_accounts(&self) -> bool; + + /// Adds a compressed account to the instruction (SHA256 hashing). + #[must_use = "with_light_account returns a new value"] + fn with_light_account(self, account: LightAccount) -> Result + where + A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default; + + /// Adds a compressed account to the instruction (Poseidon hashing). + #[cfg(feature = "poseidon")] + #[must_use = "with_light_account_poseidon returns a new value"] + fn with_light_account_poseidon( + self, + account: crate::account::poseidon::LightAccount, + ) -> Result + where + A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + DataHasher + Default; +} + +/// Macro to delegate base LightCpi methods to the interface trait impl. +macro_rules! delegate_light_cpi { + ($ty:ty) => { + fn new_cpi( + cpi_signer: crate::cpi::CpiSigner, + proof: ValidityProof, + ) -> Self { + <$ty as light_sdk_interface::cpi::LightCpi>::new_cpi(cpi_signer, proof) + } + fn get_mode(&self) -> u8 { + <$ty as light_sdk_interface::cpi::LightCpi>::get_mode(self) + } + fn get_bump(&self) -> u8 { + <$ty as light_sdk_interface::cpi::LightCpi>::get_bump(self) + } + fn write_to_cpi_context_first(self) -> Self { + <$ty as light_sdk_interface::cpi::LightCpi>::write_to_cpi_context_first(self) + } + fn write_to_cpi_context_set(self) -> Self { + <$ty as light_sdk_interface::cpi::LightCpi>::write_to_cpi_context_set(self) + } + fn execute_with_cpi_context(self) -> Self { + <$ty as light_sdk_interface::cpi::LightCpi>::execute_with_cpi_context(self) + } + fn get_with_cpi_context(&self) -> bool { + <$ty as light_sdk_interface::cpi::LightCpi>::get_with_cpi_context(self) + } + fn get_cpi_context(&self) -> &CompressedCpiContext { + <$ty as light_sdk_interface::cpi::LightCpi>::get_cpi_context(self) + } + fn has_read_only_accounts(&self) -> bool { + <$ty as light_sdk_interface::cpi::LightCpi>::has_read_only_accounts(self) + } + }; +} + +pub(crate) use delegate_light_cpi; -// V1/V2 modules that provide WithLightAccount impls pub mod v1; #[cfg(feature = "v2")] pub mod v2; diff --git a/sdk-libs/sdk/src/cpi/v1/invoke.rs b/sdk-libs/sdk/src/cpi/v1/invoke.rs index 8f9c346410..cd620aa001 100644 --- a/sdk-libs/sdk/src/cpi/v1/invoke.rs +++ b/sdk-libs/sdk/src/cpi/v1/invoke.rs @@ -1,10 +1,14 @@ use light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext; +use light_compressed_account::instruction_data::{ + compressed_proof::ValidityProof, + cpi_context::CompressedCpiContext, +}; #[cfg(feature = "poseidon")] use crate::{account::poseidon::LightAccount as LightAccountPoseidon, DataHasher}; use crate::{ account::LightAccount, - cpi::instruction::WithLightAccount, + cpi::{delegate_light_cpi, LightCpiInstruction}, error::LightSdkError, instruction::account_info::CompressedAccountInfoTrait, AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError, @@ -13,7 +17,9 @@ use crate::{ // Re-export LightSystemProgramCpi from interface pub use light_sdk_interface::cpi::v1::LightSystemProgramCpi; -impl WithLightAccount for LightSystemProgramCpi { +impl LightCpiInstruction for LightSystemProgramCpi { + delegate_light_cpi!(LightSystemProgramCpi); + fn with_light_account(mut self, account: LightAccount) -> Result where A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default, diff --git a/sdk-libs/sdk/src/cpi/v1/mod.rs b/sdk-libs/sdk/src/cpi/v1/mod.rs index cf3f428360..cb17ae926a 100644 --- a/sdk-libs/sdk/src/cpi/v1/mod.rs +++ b/sdk-libs/sdk/src/cpi/v1/mod.rs @@ -13,6 +13,6 @@ // Re-export everything from interface's v1 module pub use light_sdk_interface::cpi::v1::*; -// SDK extension: WithLightAccount impl for LightSystemProgramCpi +// LightCpiInstruction impl for LightSystemProgramCpi mod invoke; pub use invoke::LightSystemProgramCpi; diff --git a/sdk-libs/sdk/src/cpi/v2/invoke.rs b/sdk-libs/sdk/src/cpi/v2/invoke.rs index 50f1662ad6..c6cb852e2a 100644 --- a/sdk-libs/sdk/src/cpi/v2/invoke.rs +++ b/sdk-libs/sdk/src/cpi/v2/invoke.rs @@ -2,18 +2,24 @@ use light_compressed_account::instruction_data::with_account_info::InstructionDa use light_compressed_account::instruction_data::with_readonly::{ InAccount, InstructionDataInvokeCpiWithReadOnly, }; +use light_compressed_account::instruction_data::{ + compressed_proof::ValidityProof, + cpi_context::CompressedCpiContext, +}; #[cfg(feature = "poseidon")] use crate::{account::poseidon::LightAccount as LightAccountPoseidon, DataHasher}; use crate::{ account::LightAccount, - cpi::instruction::WithLightAccount, + cpi::{delegate_light_cpi, LightCpiInstruction}, error::LightSdkError, instruction::account_info::CompressedAccountInfoTrait, AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError, }; -impl WithLightAccount for InstructionDataInvokeCpiWithReadOnly { +impl LightCpiInstruction for InstructionDataInvokeCpiWithReadOnly { + delegate_light_cpi!(InstructionDataInvokeCpiWithReadOnly); + fn with_light_account(mut self, account: LightAccount) -> Result where A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default, @@ -127,7 +133,9 @@ impl WithLightAccount for InstructionDataInvokeCpiWithReadOnly { } } -impl WithLightAccount for InstructionDataInvokeCpiWithAccountInfo { +impl LightCpiInstruction for InstructionDataInvokeCpiWithAccountInfo { + delegate_light_cpi!(InstructionDataInvokeCpiWithAccountInfo); + fn with_light_account(mut self, account: LightAccount) -> Result where A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default, diff --git a/sdk-libs/sdk/src/cpi/v2/mod.rs b/sdk-libs/sdk/src/cpi/v2/mod.rs index 8170bb952d..21bcc7394b 100644 --- a/sdk-libs/sdk/src/cpi/v2/mod.rs +++ b/sdk-libs/sdk/src/cpi/v2/mod.rs @@ -13,5 +13,5 @@ // Re-export everything from interface's v2 module pub use light_sdk_interface::cpi::v2::*; -// SDK extension: WithLightAccount impls for v2 instruction types +// LightCpiInstruction impls for v2 instruction types mod invoke; diff --git a/sdk-libs/sdk/src/lib.rs b/sdk-libs/sdk/src/lib.rs index 8f44d4c00b..ac3962d6b3 100644 --- a/sdk-libs/sdk/src/lib.rs +++ b/sdk-libs/sdk/src/lib.rs @@ -209,9 +209,11 @@ use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSeria #[cfg(not(target_os = "solana"))] pub use light_sdk_interface::Pack; pub use light_sdk_interface::{ - process_initialize_light_config, process_initialize_light_config_checked, - process_update_light_config, CompressAs, CompressedInitSpace, CompressionInfo, - HasCompressionInfo, LightConfig, Space, Unpack, COMPRESSIBLE_CONFIG_SEED, + process_initialize_light_config_checked, + InitializeLightConfigParams, + process_update_light_config, UpdateLightConfigParams, CompressAs, CompressedInitSpace, + CompressionInfo, + HasCompressionInfo, LightConfig, Space, COMPRESSIBLE_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE, }; pub use light_account_checks::{self, discriminator::Discriminator as LightDiscriminator}; @@ -230,8 +232,8 @@ use solana_instruction::AccountMeta; use solana_program_error::ProgramError; use solana_pubkey::Pubkey; -// Re-export SDK extension traits -pub use crate::cpi::WithLightAccount; +// Re-export SDK traits +pub use crate::cpi::LightCpiInstruction; #[cfg(not(target_os = "solana"))] pub use crate::instruction::PackedAccountsExt; diff --git a/sdk-tests/manual-test/src/derived_light_config.rs b/sdk-tests/manual-test/src/derived_light_config.rs index babceee774..2386226303 100644 --- a/sdk-tests/manual-test/src/derived_light_config.rs +++ b/sdk-tests/manual-test/src/derived_light_config.rs @@ -2,7 +2,8 @@ use anchor_lang::prelude::*; use light_compressible::rent::RentConfig; -use light_sdk::interface::config::{process_initialize_light_config, process_update_light_config}; +use light_sdk::interface::config::process_initialize_light_config; +use light_sdk::interface::config::process_update_light_config; /// Params order matches SDK's InitializeCompressionConfigAnchorData. #[derive(AnchorSerialize, AnchorDeserialize, Clone)] @@ -54,16 +55,6 @@ pub fn process_initialize_config<'info>( .map_err(Into::into) } -#[derive(AnchorSerialize, AnchorDeserialize, Clone)] -pub struct UpdateConfigParams { - pub new_update_authority: Option, - pub new_rent_sponsor: Option, - pub new_compression_authority: Option, - pub new_rent_config: Option, - pub new_write_top_up: Option, - pub new_address_space: Option>, -} - #[derive(Accounts)] pub struct UpdateConfig<'info> { #[account(mut)] @@ -76,18 +67,11 @@ pub struct UpdateConfig<'info> { pub fn process_update_config<'info>( ctx: Context<'_, '_, '_, 'info, UpdateConfig<'info>>, - params: UpdateConfigParams, + instruction_data: Vec, ) -> Result<()> { - process_update_light_config( - &ctx.accounts.config, - &ctx.accounts.authority, - params.new_update_authority.as_ref(), - params.new_rent_sponsor.as_ref(), - params.new_compression_authority.as_ref(), - params.new_rent_config, - params.new_write_top_up, - params.new_address_space, - &crate::ID, - ) - .map_err(Into::into) + let remaining = [ + ctx.accounts.config.to_account_info(), + ctx.accounts.authority.to_account_info(), + ]; + process_update_light_config(&remaining, &instruction_data, &crate::ID).map_err(Into::into) } diff --git a/sdk-tests/manual-test/src/lib.rs b/sdk-tests/manual-test/src/lib.rs index a3c2366b27..5b1f1c5232 100644 --- a/sdk-tests/manual-test/src/lib.rs +++ b/sdk-tests/manual-test/src/lib.rs @@ -100,9 +100,9 @@ pub mod manual_test { /// Named to match SDK's expected discriminator. pub fn update_compression_config<'info>( ctx: Context<'_, '_, '_, 'info, UpdateConfig<'info>>, - params: UpdateConfigParams, + instruction_data: Vec, ) -> Result<()> { - derived_light_config::process_update_config(ctx, params) + derived_light_config::process_update_config(ctx, instruction_data) } /// Compress and close PDA accounts, returning rent to the sponsor. diff --git a/sdk-tests/single-pda-derive-test/Cargo.toml b/sdk-tests/single-pda-derive-test/Cargo.toml new file mode 100644 index 0000000000..6a6068dc3f --- /dev/null +++ b/sdk-tests/single-pda-derive-test/Cargo.toml @@ -0,0 +1,61 @@ +[package] +name = "single-pda-derive-test" +version = "0.1.0" +description = "Test for #[derive(LightProgram)] macro validation with all variant kinds" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "single_pda_derive_test" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +custom-heap = ["light-heap", "light-sdk/custom-heap"] +default = [] +idl-build = ["anchor-lang/idl-build", "light-sdk/idl-build", "light-anchor-spl/idl-build"] +test-sbf = [] + +[dependencies] +light-heap = { workspace = true, optional = true } +light-sdk = { workspace = true, features = ["anchor", "v2", "anchor-discriminator", "cpi-context"] } +light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] } +light-macros = { workspace = true, features = ["solana"] } +light-sdk-macros = { workspace = true } +bytemuck = { workspace = true, features = ["derive"] } +borsh = { workspace = true } +light-compressed-account = { workspace = true, features = ["solana"] } +light-sdk-interface = { workspace = true } +anchor-lang = { workspace = true } +light-anchor-spl = { workspace = true, features = ["metadata"] } +light-compressible = { workspace = true, features = ["anchor"] } +light-hasher = { workspace = true, features = ["solana"] } +light-token = { workspace = true, features = ["anchor"] } +light-token-types = { workspace = true, features = ["anchor"] } +solana-program = { workspace = true } +solana-pubkey = { workspace = true } +solana-msg = { workspace = true } +solana-program-error = { workspace = true } +solana-account-info = { workspace = true } + +[dev-dependencies] +light-program-test = { workspace = true, features = ["devenv"] } +light-client = { workspace = true, features = ["v2", "anchor"] } +light-test-utils = { workspace = true } +light-token = { workspace = true } +light-token-interface = { workspace = true } +tokio = { workspace = true } +solana-sdk = { workspace = true } +solana-instruction = { workspace = true } +solana-pubkey = { workspace = true } +solana-keypair = { workspace = true } +solana-signer = { workspace = true } + +[lints.rust.unexpected_cfgs] +level = "allow" +check-cfg = [ + 'cfg(target_os, values("solana"))', + 'cfg(feature, values("frozen-abi", "no-entrypoint"))', +] diff --git a/sdk-tests/single-pda-derive-test/src/instruction_accounts.rs b/sdk-tests/single-pda-derive-test/src/instruction_accounts.rs new file mode 100644 index 0000000000..5b32cd9da1 --- /dev/null +++ b/sdk-tests/single-pda-derive-test/src/instruction_accounts.rs @@ -0,0 +1,445 @@ +//! Accounts module for single-pda-derive-test. + +use anchor_lang::prelude::*; +use light_compressible::CreateAccountsProof; +use light_sdk_macros::LightAccounts; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; + +use crate::state::{MinimalRecord, ZeroCopyRecord}; +use crate::{ + MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, RECORD_SEED, VAULT_AUTH_SEED, VAULT_SEED, +}; + +// ============================================================================= +// 1. CreatePda +// ============================================================================= + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct CreatePdaParams { + pub create_accounts_proof: CreateAccountsProof, + pub owner: Pubkey, +} + +/// Minimal accounts struct for testing single PDA creation. +#[derive(Accounts, LightAccounts)] +#[instruction(params: CreatePdaParams)] +pub struct CreatePda<'info> { + #[account(mut)] + pub fee_payer: Signer<'info>, + + /// CHECK: Compression config + pub compression_config: AccountInfo<'info>, + + /// CHECK: PDA rent sponsor for rent reimbursement + #[account(mut)] + pub pda_rent_sponsor: AccountInfo<'info>, + + #[account( + init, + payer = fee_payer, + space = 8 + MinimalRecord::INIT_SPACE, + seeds = [b"minimal_record", params.owner.as_ref()], + bump, + )] + #[light_account(init)] + pub record: Account<'info, MinimalRecord>, + + pub system_program: Program<'info, System>, +} + +// ============================================================================= +// 2. CreateAta +// ============================================================================= + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct CreateAtaParams { + pub create_accounts_proof: CreateAccountsProof, + pub ata_bump: u8, +} + +/// Accounts struct for testing single ATA creation. +#[derive(Accounts, LightAccounts)] +#[instruction(params: CreateAtaParams)] +pub struct CreateAta<'info> { + #[account(mut)] + pub fee_payer: Signer<'info>, + + /// CHECK: Token mint for the ATA + pub ata_mint: AccountInfo<'info>, + + /// CHECK: Owner of the ATA + pub ata_owner: AccountInfo<'info>, + + /// ATA account - created via LightFinalize CPI. + #[account(mut)] + #[light_account(init, associated_token::authority = ata_owner, associated_token::mint = ata_mint, associated_token::bump = params.ata_bump)] + pub ata: UncheckedAccount<'info>, + + #[account(address = LIGHT_TOKEN_CONFIG)] + pub light_token_config: AccountInfo<'info>, + + #[account(mut, address = LIGHT_TOKEN_RENT_SPONSOR)] + pub light_token_rent_sponsor: AccountInfo<'info>, + + /// CHECK: Light Token Program for CPI + #[account(address = LIGHT_TOKEN_PROGRAM_ID.into())] + pub light_token_program: AccountInfo<'info>, + + pub system_program: Program<'info, System>, +} + +// ============================================================================= +// 3. CreateTokenVault +// ============================================================================= + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct CreateTokenVaultParams { + pub create_accounts_proof: CreateAccountsProof, + pub vault_bump: u8, +} + +/// Accounts struct for testing single token vault creation. +#[derive(Accounts, LightAccounts)] +#[instruction(params: CreateTokenVaultParams)] +pub struct CreateTokenVault<'info> { + #[account(mut)] + pub fee_payer: Signer<'info>, + + /// CHECK: Token mint + pub mint: AccountInfo<'info>, + + #[account( + seeds = [VAULT_AUTH_SEED], + bump, + )] + pub vault_authority: UncheckedAccount<'info>, + + /// Token vault account - created via LightFinalize CPI. + #[account( + mut, + seeds = [VAULT_SEED, mint.key().as_ref()], + bump, + )] + #[light_account(init, token::seeds = [VAULT_SEED, self.mint.key()], token::mint = mint, token::owner = vault_authority, token::owner_seeds = [VAULT_AUTH_SEED])] + pub vault: UncheckedAccount<'info>, + + #[account(address = LIGHT_TOKEN_CONFIG)] + pub light_token_config: AccountInfo<'info>, + + #[account(mut, address = LIGHT_TOKEN_RENT_SPONSOR)] + pub light_token_rent_sponsor: AccountInfo<'info>, + + /// CHECK: Light token CPI authority + pub light_token_cpi_authority: AccountInfo<'info>, + + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, + + pub system_program: Program<'info, System>, +} + +// ============================================================================= +// 4. CreateZeroCopyRecord +// ============================================================================= + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct CreateZeroCopyRecordParams { + pub create_accounts_proof: CreateAccountsProof, + pub owner: Pubkey, +} + +/// Accounts struct for creating a zero-copy record. +#[derive(Accounts, LightAccounts)] +#[instruction(params: CreateZeroCopyRecordParams)] +pub struct CreateZeroCopyRecord<'info> { + #[account(mut)] + pub fee_payer: Signer<'info>, + + /// CHECK: Compression config PDA + pub compression_config: AccountInfo<'info>, + + /// CHECK: PDA rent sponsor for rent reimbursement + #[account(mut)] + pub pda_rent_sponsor: AccountInfo<'info>, + + #[account( + init, + payer = fee_payer, + space = 8 + core::mem::size_of::(), + seeds = [RECORD_SEED, params.owner.as_ref()], + bump, + )] + #[light_account(init, zero_copy)] + pub record: AccountLoader<'info, ZeroCopyRecord>, + + pub system_program: Program<'info, System>, +} + +// ============================================================================= +// 5. CreateMint +// ============================================================================= + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct CreateMintParams { + pub create_accounts_proof: CreateAccountsProof, + pub mint_signer_bump: u8, +} + +/// Accounts struct for testing single mint creation. +#[derive(Accounts, LightAccounts)] +#[instruction(params: CreateMintParams)] +pub struct CreateMint<'info> { + #[account(mut)] + pub fee_payer: Signer<'info>, + + pub authority: Signer<'info>, + + /// CHECK: PDA derived from authority + #[account( + seeds = [MINT_SIGNER_SEED_A, authority.key().as_ref()], + bump, + )] + pub mint_signer: UncheckedAccount<'info>, + + /// CHECK: Initialized by light_mint CPI + #[account(mut)] + #[light_account(init, + mint::signer = mint_signer, + mint::authority = fee_payer, + mint::decimals = 9, + mint::seeds = &[MINT_SIGNER_SEED_A, self.authority.to_account_info().key.as_ref()], + mint::bump = params.mint_signer_bump + )] + pub mint: UncheckedAccount<'info>, + + /// CHECK: Compression config + pub compression_config: AccountInfo<'info>, + + /// CHECK: CToken config + #[account(address = LIGHT_TOKEN_CONFIG)] + pub light_token_config: AccountInfo<'info>, + + /// CHECK: CToken rent sponsor + #[account(mut, address = LIGHT_TOKEN_RENT_SPONSOR)] + pub light_token_rent_sponsor: AccountInfo<'info>, + + /// CHECK: CToken program + pub light_token_program: AccountInfo<'info>, + + /// CHECK: CToken CPI authority + pub light_token_cpi_authority: AccountInfo<'info>, + + pub system_program: Program<'info, System>, +} + +// ============================================================================= +// 6. CreateTwoMints +// ============================================================================= + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct CreateTwoMintsParams { + pub create_accounts_proof: CreateAccountsProof, + pub mint_signer_bump_a: u8, + pub mint_signer_bump_b: u8, +} + +/// Accounts struct for testing two mint creation in a single instruction. +#[derive(Accounts, LightAccounts)] +#[instruction(params: CreateTwoMintsParams)] +pub struct CreateTwoMints<'info> { + #[account(mut)] + pub fee_payer: Signer<'info>, + + pub authority: Signer<'info>, + + /// CHECK: PDA for mint A + #[account( + seeds = [MINT_SIGNER_SEED_A, authority.key().as_ref()], + bump, + )] + pub mint_signer_a: UncheckedAccount<'info>, + + /// CHECK: Mint A - initialized by light_mint CPI + #[account(mut)] + #[light_account(init, + mint::signer = mint_signer_a, + mint::authority = fee_payer, + mint::decimals = 9, + mint::seeds = &[MINT_SIGNER_SEED_A, self.authority.to_account_info().key.as_ref()], + mint::bump = params.mint_signer_bump_a + )] + pub mint_a: UncheckedAccount<'info>, + + /// CHECK: PDA for mint B + #[account( + seeds = [MINT_SIGNER_SEED_B, authority.key().as_ref()], + bump, + )] + pub mint_signer_b: UncheckedAccount<'info>, + + /// CHECK: Mint B - initialized by light_mint CPI + #[account(mut)] + #[light_account(init, + mint::signer = mint_signer_b, + mint::authority = fee_payer, + mint::decimals = 6, + mint::seeds = &[MINT_SIGNER_SEED_B, self.authority.to_account_info().key.as_ref()], + mint::bump = params.mint_signer_bump_b + )] + pub mint_b: UncheckedAccount<'info>, + + /// CHECK: Compression config + pub compression_config: AccountInfo<'info>, + + /// CHECK: CToken config + #[account(address = LIGHT_TOKEN_CONFIG)] + pub light_token_config: AccountInfo<'info>, + + /// CHECK: CToken rent sponsor + #[account(mut, address = LIGHT_TOKEN_RENT_SPONSOR)] + pub light_token_rent_sponsor: AccountInfo<'info>, + + /// CHECK: CToken program + pub light_token_program: AccountInfo<'info>, + + /// CHECK: CToken CPI authority + pub light_token_cpi_authority: AccountInfo<'info>, + + pub system_program: Program<'info, System>, +} + +// ============================================================================= +// 7. CreateAll +// ============================================================================= + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct CreateAllParams { + pub create_accounts_proof: CreateAccountsProof, + pub owner: Pubkey, + pub ata_bump: u8, + pub vault_bump: u8, + pub mint_signer_bump_a: u8, + pub mint_signer_bump_b: u8, +} + +/// Combined accounts struct exercising all variant types in one instruction. +#[derive(Accounts, LightAccounts)] +#[instruction(params: CreateAllParams)] +pub struct CreateAll<'info> { + #[account(mut)] + pub fee_payer: Signer<'info>, + + // -- PDA -- + /// CHECK: Compression config + pub compression_config: AccountInfo<'info>, + + /// CHECK: PDA rent sponsor + #[account(mut)] + pub pda_rent_sponsor: AccountInfo<'info>, + + #[account( + init, + payer = fee_payer, + space = 8 + MinimalRecord::INIT_SPACE, + seeds = [b"minimal_record", params.owner.as_ref()], + bump, + )] + #[light_account(init)] + pub record: Account<'info, MinimalRecord>, + + // -- Zero-copy -- + #[account( + init, + payer = fee_payer, + space = 8 + core::mem::size_of::(), + seeds = [RECORD_SEED, params.owner.as_ref()], + bump, + )] + #[light_account(init, zero_copy)] + pub zero_copy_record: AccountLoader<'info, ZeroCopyRecord>, + + // -- ATA -- + /// CHECK: Token mint for the ATA + pub ata_mint: AccountInfo<'info>, + + /// CHECK: Owner of the ATA + pub ata_owner: AccountInfo<'info>, + + #[account(mut)] + #[light_account(init, associated_token::authority = ata_owner, associated_token::mint = ata_mint, associated_token::bump = params.ata_bump)] + pub ata: UncheckedAccount<'info>, + + // -- Token vault -- + /// CHECK: Token mint for the vault + pub vault_mint: AccountInfo<'info>, + + #[account( + seeds = [VAULT_AUTH_SEED], + bump, + )] + pub vault_authority: UncheckedAccount<'info>, + + #[account( + mut, + seeds = [VAULT_SEED, vault_mint.key().as_ref()], + bump, + )] + #[light_account(init, token::seeds = [VAULT_SEED, self.vault_mint.key()], token::mint = vault_mint, token::owner = vault_authority, token::owner_seeds = [VAULT_AUTH_SEED])] + pub vault: UncheckedAccount<'info>, + + // -- Mint A -- + pub authority: Signer<'info>, + + /// CHECK: PDA for mint A + #[account( + seeds = [MINT_SIGNER_SEED_A, authority.key().as_ref()], + bump, + )] + pub mint_signer_a: UncheckedAccount<'info>, + + #[account(mut)] + #[light_account(init, + mint::signer = mint_signer_a, + mint::authority = fee_payer, + mint::decimals = 9, + mint::seeds = &[MINT_SIGNER_SEED_A, self.authority.to_account_info().key.as_ref()], + mint::bump = params.mint_signer_bump_a + )] + pub mint_a: UncheckedAccount<'info>, + + // -- Mint B -- + /// CHECK: PDA for mint B + #[account( + seeds = [MINT_SIGNER_SEED_B, authority.key().as_ref()], + bump, + )] + pub mint_signer_b: UncheckedAccount<'info>, + + #[account(mut)] + #[light_account(init, + mint::signer = mint_signer_b, + mint::authority = fee_payer, + mint::decimals = 6, + mint::seeds = &[MINT_SIGNER_SEED_B, self.authority.to_account_info().key.as_ref()], + mint::bump = params.mint_signer_bump_b + )] + pub mint_b: UncheckedAccount<'info>, + + // -- Infrastructure -- + /// CHECK: CToken config + #[account(address = LIGHT_TOKEN_CONFIG)] + pub light_token_config: AccountInfo<'info>, + + /// CHECK: CToken rent sponsor + #[account(mut, address = LIGHT_TOKEN_RENT_SPONSOR)] + pub light_token_rent_sponsor: AccountInfo<'info>, + + /// CHECK: CToken CPI authority + pub light_token_cpi_authority: AccountInfo<'info>, + + /// CHECK: CToken program + pub light_token_program: AccountInfo<'info>, + + pub system_program: Program<'info, System>, +} diff --git a/sdk-tests/single-pda-derive-test/src/lib.rs b/sdk-tests/single-pda-derive-test/src/lib.rs new file mode 100644 index 0000000000..a65dc880c6 --- /dev/null +++ b/sdk-tests/single-pda-derive-test/src/lib.rs @@ -0,0 +1,205 @@ +//! Test program for #[derive(LightProgram)] macro validation. +//! +//! Uses #[derive(LightProgram)] with plain #[program] (no #[light_program]). +//! Exercises all variant kinds: PDA, ATA, token, zero_copy. + +#![allow(deprecated)] + +use std::marker::PhantomData; + +use anchor_lang::prelude::*; +use light_sdk::derive_light_cpi_signer; +use light_sdk_macros::LightProgram; +use light_sdk_types::CpiSigner; + +pub mod instruction_accounts; +pub mod state; + +pub use instruction_accounts::*; +pub use state::*; + +declare_id!("DrvPda11111111111111111111111111111111111111"); + +pub const LIGHT_CPI_SIGNER: CpiSigner = + derive_light_cpi_signer!("DrvPda11111111111111111111111111111111111111"); + +pub const VAULT_AUTH_SEED: &[u8] = b"vault_auth"; +pub const VAULT_SEED: &[u8] = b"vault"; +pub const RECORD_SEED: &[u8] = b"zero_copy_record"; +pub const MINT_SIGNER_SEED_A: &[u8] = b"mint_signer_a"; +pub const MINT_SIGNER_SEED_B: &[u8] = b"mint_signer_b"; + +#[derive(LightProgram)] +pub enum ProgramAccounts { + #[light_account(pda::seeds = [b"minimal_record", ctx.owner])] + MinimalRecord(MinimalRecord), + + #[light_account(associated_token)] + Ata, + + #[light_account(token::seeds = [VAULT_SEED, ctx.mint], token::owner_seeds = [VAULT_AUTH_SEED])] + Vault, + + #[light_account(pda::seeds = [RECORD_SEED, ctx.owner], pda::zero_copy)] + ZeroCopyRecord(ZeroCopyRecord), +} + +#[program] +pub mod single_pda_derive_test { + use super::*; + + // ========================================================================= + // Compression infrastructure (generated by #[derive(LightProgram)]) + // ========================================================================= + + pub fn compress_accounts_idempotent<'info>( + ctx: Context<'_, '_, '_, 'info, EmptyAccounts<'info>>, + instruction_data: Vec, + ) -> Result<()> { + light_sdk_interface::program::compression::processor::process_compress_pda_accounts_idempotent( + ctx.remaining_accounts, + &instruction_data, + ProgramAccounts::compress_dispatch, + LIGHT_CPI_SIGNER, + &LIGHT_CPI_SIGNER.program_id, + ).map_err(|e| ProgramError::Custom(u64::from(e) as u32)) + } + + pub fn decompress_accounts_idempotent<'info>( + ctx: Context<'_, '_, '_, 'info, EmptyAccounts<'info>>, + instruction_data: Vec, + ) -> Result<()> { + ProgramAccounts::decompress_dispatch( + ctx.remaining_accounts, + &instruction_data, + LIGHT_CPI_SIGNER, + &crate::ID, + ) + .map_err(Into::into) + } + + pub fn initialize_compression_config<'info>( + ctx: Context<'_, '_, '_, 'info, EmptyAccounts<'info>>, + instruction_data: Vec, + ) -> Result<()> { + light_sdk_interface::program::config::process_initialize_light_config_checked( + ctx.remaining_accounts, + &instruction_data, + &crate::ID, + ) + .map_err(Into::into) + } + + pub fn update_compression_config<'info>( + ctx: Context<'_, '_, '_, 'info, EmptyAccounts<'info>>, + instruction_data: Vec, + ) -> Result<()> { + light_sdk_interface::program::config::process_update_light_config( + ctx.remaining_accounts, + &instruction_data, + &crate::ID, + ) + .map_err(Into::into) + } +} + +/// Accounts struct for compress instruction. +/// Uses PhantomData for the `<'info>` lifetime so Anchor's CPI codegen works. +/// All accounts are passed via remaining_accounts. +pub struct EmptyAccounts<'info>(PhantomData<&'info ()>); + +impl<'info> anchor_lang::Accounts<'info, EmptyAccountsBumps> for EmptyAccounts<'info> { + fn try_accounts( + _program_id: &anchor_lang::solana_program::pubkey::Pubkey, + _accounts: &mut &'info [anchor_lang::solana_program::account_info::AccountInfo<'info>], + _ix_data: &[u8], + _bumps: &mut EmptyAccountsBumps, + _reallocs: &mut std::collections::BTreeSet, + ) -> anchor_lang::Result { + Ok(EmptyAccounts(PhantomData)) + } +} + +#[derive(Debug, Default)] +pub struct EmptyAccountsBumps {} + +impl<'info> anchor_lang::Bumps for EmptyAccounts<'info> { + type Bumps = EmptyAccountsBumps; +} + +impl<'info> anchor_lang::ToAccountInfos<'info> for EmptyAccounts<'info> { + fn to_account_infos( + &self, + ) -> Vec> { + Vec::new() + } +} + +impl<'info> anchor_lang::ToAccountMetas for EmptyAccounts<'info> { + fn to_account_metas( + &self, + _is_signer: Option, + ) -> Vec { + Vec::new() + } +} + +impl<'info> anchor_lang::AccountsExit<'info> for EmptyAccounts<'info> { + fn exit( + &self, + _program_id: &anchor_lang::solana_program::pubkey::Pubkey, + ) -> anchor_lang::Result<()> { + Ok(()) + } +} + +#[cfg(feature = "idl-build")] +impl<'info> EmptyAccounts<'info> { + pub fn __anchor_private_gen_idl_accounts( + _accounts: &mut std::collections::BTreeMap, + _types: &mut std::collections::BTreeMap, + ) -> Vec { + Vec::new() + } +} + +pub(crate) mod __client_accounts_empty_accounts { + use super::*; + pub struct EmptyAccounts<'info>(PhantomData<&'info ()>); + impl<'info> borsh::ser::BorshSerialize for EmptyAccounts<'info> { + fn serialize( + &self, + _writer: &mut W, + ) -> ::core::result::Result<(), borsh::maybestd::io::Error> { + Ok(()) + } + } + impl<'info> anchor_lang::ToAccountMetas for EmptyAccounts<'info> { + fn to_account_metas( + &self, + _is_signer: Option, + ) -> Vec { + Vec::new() + } + } +} + +pub(crate) mod __cpi_client_accounts_empty_accounts { + use super::*; + pub struct EmptyAccounts<'info>(PhantomData<&'info ()>); + impl<'info> anchor_lang::ToAccountMetas for EmptyAccounts<'info> { + fn to_account_metas( + &self, + _is_signer: Option, + ) -> Vec { + Vec::new() + } + } + impl<'info> anchor_lang::ToAccountInfos<'info> for EmptyAccounts<'info> { + fn to_account_infos( + &self, + ) -> Vec> { + Vec::new() + } + } +} diff --git a/sdk-tests/single-pda-derive-test/src/state.rs b/sdk-tests/single-pda-derive-test/src/state.rs new file mode 100644 index 0000000000..cc61250868 --- /dev/null +++ b/sdk-tests/single-pda-derive-test/src/state.rs @@ -0,0 +1,25 @@ +//! State module for single-pda-derive-test. + +use anchor_lang::prelude::*; +use light_sdk::{interface::CompressionInfo, LightDiscriminator}; +use light_sdk_macros::LightAccount; + +/// Minimal record struct for testing PDA creation. +/// Contains only compression_info and one field. +#[derive(Default, Debug, InitSpace, LightAccount)] +#[account] +pub struct MinimalRecord { + pub compression_info: CompressionInfo, + pub owner: Pubkey, +} + +/// A zero-copy account using Pod serialization. +/// Used with AccountLoader for efficient on-chain zero-copy access. +#[derive(Default, Debug, LightAccount)] +#[account(zero_copy)] +#[repr(C)] +pub struct ZeroCopyRecord { + pub compression_info: CompressionInfo, + pub owner: Pubkey, + pub counter: u64, +} diff --git a/sdk-tests/single-pda-derive-test/tests/test.rs b/sdk-tests/single-pda-derive-test/tests/test.rs new file mode 100644 index 0000000000..ffbd5f197c --- /dev/null +++ b/sdk-tests/single-pda-derive-test/tests/test.rs @@ -0,0 +1,943 @@ +//! Integration tests for #[derive(LightProgram)] with all variant kinds. + +use anchor_lang::{InstructionData, ToAccountMetas}; +use light_client::interface::{ + get_create_accounts_proof, CreateAccountsProofInput, InitializeRentFreeConfig, +}; +use light_program_test::{ + program_test::{setup_mock_program_data, LightProgramTest}, + Indexer, ProgramTestConfig, Rpc, +}; +use light_sdk::utils::derive_rent_sponsor_pda; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use solana_instruction::Instruction; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +/// Setup helper: Creates a compressed mint using the ctoken SDK. +/// Returns (mint_pda, mint_seed_keypair) +async fn setup_create_mint( + rpc: &mut (impl Rpc + Indexer), + payer: &Keypair, + mint_authority: Pubkey, + decimals: u8, +) -> (Pubkey, Keypair) { + use light_token::instruction::{CreateMint, CreateMintParams}; + + let mint_seed = Keypair::new(); + let address_tree = rpc.get_address_tree_v2(); + let output_queue = rpc.get_random_state_tree_info().unwrap().queue; + + let compression_address = light_token::instruction::derive_mint_compressed_address( + &mint_seed.pubkey(), + &address_tree.tree, + ); + + let (mint, bump) = light_token::instruction::find_mint_address(&mint_seed.pubkey()); + + let rpc_result = rpc + .get_validity_proof( + vec![], + vec![light_client::indexer::AddressWithTree { + address: compression_address, + tree: address_tree.tree, + }], + None, + ) + .await + .unwrap() + .value; + + let params = CreateMintParams { + decimals, + address_merkle_tree_root_index: rpc_result.addresses[0].root_index, + mint_authority, + proof: rpc_result.proof.0.unwrap(), + compression_address, + mint, + bump, + freeze_authority: None, + extensions: None, + rent_payment: 16, + write_top_up: 766, + }; + + let create_mint_builder = CreateMint::new( + params, + mint_seed.pubkey(), + payer.pubkey(), + address_tree.tree, + output_queue, + ); + let instruction = create_mint_builder.instruction().unwrap(); + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[payer, &mint_seed]) + .await + .unwrap(); + + (mint, mint_seed) +} + +// ============================================================================= +// 1. Create PDA +// ============================================================================= + +#[tokio::test] +async fn test_create_single_pda_derive() { + use single_pda_derive_test::CreatePdaParams; + + let program_id = single_pda_derive_test::ID; + let mut config = + ProgramTestConfig::new_v2(true, Some(vec![("single_pda_derive_test", program_id)])); + config = config.with_light_protocol_events(); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); + + let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id); + + let (init_config_ix, config_pda) = InitializeRentFreeConfig::new( + &program_id, + &payer.pubkey(), + &program_data_pda, + rent_sponsor, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .expect("Initialize config should succeed"); + + let owner = Keypair::new().pubkey(); + + let (record_pda, _) = + Pubkey::find_program_address(&[b"minimal_record", owner.as_ref()], &program_id); + + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![CreateAccountsProofInput::pda(record_pda)], + ) + .await + .unwrap(); + + let accounts = single_pda_derive_test::accounts::CreatePda { + fee_payer: payer.pubkey(), + compression_config: config_pda, + pda_rent_sponsor: rent_sponsor, + record: record_pda, + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = single_pda_derive_test::instruction::CreatePda { + params: CreatePdaParams { + create_accounts_proof: proof_result.create_accounts_proof, + owner, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) + .await + .expect("CreatePda should succeed"); + + let record_account = rpc + .get_account(record_pda) + .await + .unwrap() + .expect("Record PDA should exist on-chain"); + + use single_pda_derive_test::MinimalRecord; + let record: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &record_account.data[8..]) + .expect("Failed to deserialize MinimalRecord"); + + assert_eq!(record.owner, owner, "Record owner should match"); + assert!( + !record.compression_info.is_compressed(), + "Record should be in decompressed state" + ); +} + +// ============================================================================= +// 2. Create ATA +// ============================================================================= + +#[tokio::test] +async fn test_create_ata_derive() { + use single_pda_derive_test::CreateAtaParams; + + let program_id = single_pda_derive_test::ID; + let mut config = + ProgramTestConfig::new_v2(true, Some(vec![("single_pda_derive_test", program_id)])); + config = config.with_light_protocol_events(); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); + + let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id); + + let (init_config_ix, _config_pda) = InitializeRentFreeConfig::new( + &program_id, + &payer.pubkey(), + &program_data_pda, + rent_sponsor, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .expect("Initialize config should succeed"); + + // Setup mint first + let (mint, _mint_seed) = setup_create_mint( + &mut rpc, + &payer, + payer.pubkey(), + 9, + ) + .await; + + let ata_owner = payer.pubkey(); + let (ata, ata_bump) = light_token::instruction::derive_token_ata(&ata_owner, &mint); + + // No PDA accounts for ATA-only instruction + let proof_result = get_create_accounts_proof(&rpc, &program_id, vec![]) + .await + .unwrap(); + + let accounts = single_pda_derive_test::accounts::CreateAta { + fee_payer: payer.pubkey(), + ata_mint: mint, + ata_owner, + ata, + light_token_config: LIGHT_TOKEN_CONFIG, + light_token_rent_sponsor: LIGHT_TOKEN_RENT_SPONSOR, + light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = single_pda_derive_test::instruction::CreateAta { + params: CreateAtaParams { + create_accounts_proof: proof_result.create_accounts_proof, + ata_bump, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) + .await + .expect("CreateAta should succeed"); + + let ata_account = rpc + .get_account(ata) + .await + .unwrap() + .expect("ATA should exist on-chain"); + + use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; + let token: Token = borsh::BorshDeserialize::deserialize(&mut &ata_account.data[..]) + .expect("Failed to deserialize Token"); + + let expected_token = Token { + mint: mint.to_bytes().into(), + owner: ata_owner.to_bytes().into(), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT, + extensions: token.extensions.clone(), + }; + + assert_eq!(token, expected_token, "ATA should match expected after creation"); +} + +// ============================================================================= +// 3. Create Token Vault +// ============================================================================= + +#[tokio::test] +async fn test_create_token_vault_derive() { + use single_pda_derive_test::{CreateTokenVaultParams, VAULT_AUTH_SEED, VAULT_SEED}; + + let program_id = single_pda_derive_test::ID; + let mut config = + ProgramTestConfig::new_v2(true, Some(vec![("single_pda_derive_test", program_id)])); + config = config.with_light_protocol_events(); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); + + let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id); + + let (init_config_ix, _config_pda) = InitializeRentFreeConfig::new( + &program_id, + &payer.pubkey(), + &program_data_pda, + rent_sponsor, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .expect("Initialize config should succeed"); + + // Setup mint first + let (mint, _mint_seed) = setup_create_mint( + &mut rpc, + &payer, + payer.pubkey(), + 9, + ) + .await; + + let (vault_authority, _auth_bump) = + Pubkey::find_program_address(&[VAULT_AUTH_SEED], &program_id); + let (vault, vault_bump) = + Pubkey::find_program_address(&[VAULT_SEED, mint.as_ref()], &program_id); + + // No PDA accounts for token-only instruction + let proof_result = get_create_accounts_proof(&rpc, &program_id, vec![]) + .await + .unwrap(); + + let accounts = single_pda_derive_test::accounts::CreateTokenVault { + fee_payer: payer.pubkey(), + mint, + vault_authority, + vault, + light_token_config: LIGHT_TOKEN_CONFIG, + light_token_rent_sponsor: LIGHT_TOKEN_RENT_SPONSOR, + light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), + light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = single_pda_derive_test::instruction::CreateTokenVault { + params: CreateTokenVaultParams { + create_accounts_proof: proof_result.create_accounts_proof, + vault_bump, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) + .await + .expect("CreateTokenVault should succeed"); + + let vault_account = rpc + .get_account(vault) + .await + .unwrap() + .expect("Token vault should exist on-chain"); + + use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; + let token: Token = borsh::BorshDeserialize::deserialize(&mut &vault_account.data[..]) + .expect("Failed to deserialize Token"); + + let expected_token = Token { + mint: mint.to_bytes().into(), + owner: vault_authority.to_bytes().into(), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT, + extensions: token.extensions.clone(), + }; + + assert_eq!( + token, expected_token, + "Token vault should match expected after creation" + ); +} + +// ============================================================================= +// 4. Create Zero-Copy Record +// ============================================================================= + +#[tokio::test] +async fn test_create_zero_copy_record_derive() { + use single_pda_derive_test::{CreateZeroCopyRecordParams, RECORD_SEED}; + + let program_id = single_pda_derive_test::ID; + let mut config = + ProgramTestConfig::new_v2(true, Some(vec![("single_pda_derive_test", program_id)])); + config = config.with_light_protocol_events(); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); + + let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id); + + let (init_config_ix, config_pda) = InitializeRentFreeConfig::new( + &program_id, + &payer.pubkey(), + &program_data_pda, + rent_sponsor, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .expect("Initialize config should succeed"); + + let owner = Keypair::new().pubkey(); + + let (record_pda, _) = + Pubkey::find_program_address(&[RECORD_SEED, owner.as_ref()], &program_id); + + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![CreateAccountsProofInput::pda(record_pda)], + ) + .await + .unwrap(); + + let accounts = single_pda_derive_test::accounts::CreateZeroCopyRecord { + fee_payer: payer.pubkey(), + compression_config: config_pda, + pda_rent_sponsor: rent_sponsor, + record: record_pda, + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = single_pda_derive_test::instruction::CreateZeroCopyRecord { + params: CreateZeroCopyRecordParams { + create_accounts_proof: proof_result.create_accounts_proof, + owner, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) + .await + .expect("CreateZeroCopyRecord should succeed"); + + let record_account = rpc + .get_account(record_pda) + .await + .unwrap() + .expect("Record PDA should exist on-chain"); + + // Parse zero-copy data using bytemuck + use single_pda_derive_test::ZeroCopyRecord; + let discriminator_len = 8; + let data = &record_account.data[discriminator_len..]; + let record: &ZeroCopyRecord = bytemuck::from_bytes(data); + + assert_eq!(record.owner, owner, "Record owner should match"); + assert_eq!(record.counter, 0, "Record counter should be 0"); +} + +// ============================================================================= +// 5. Create Mint +// ============================================================================= + +#[tokio::test] +async fn test_create_mint_derive() { + use single_pda_derive_test::{CreateMintParams, MINT_SIGNER_SEED_A}; + + let program_id = single_pda_derive_test::ID; + let mut config = + ProgramTestConfig::new_v2(true, Some(vec![("single_pda_derive_test", program_id)])); + config = config.with_light_protocol_events(); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); + + let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id); + + let (init_config_ix, config_pda) = InitializeRentFreeConfig::new( + &program_id, + &payer.pubkey(), + &program_data_pda, + rent_sponsor, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .expect("Initialize config should succeed"); + + let authority = Keypair::new(); + + let (mint_signer_pda, mint_signer_bump) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED_A, authority.pubkey().as_ref()], + &program_id, + ); + + let (mint_pda, _) = light_token::instruction::find_mint_address(&mint_signer_pda); + + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![CreateAccountsProofInput::mint(mint_signer_pda)], + ) + .await + .unwrap(); + + let accounts = single_pda_derive_test::accounts::CreateMint { + fee_payer: payer.pubkey(), + authority: authority.pubkey(), + mint_signer: mint_signer_pda, + mint: mint_pda, + compression_config: config_pda, + light_token_config: LIGHT_TOKEN_CONFIG, + light_token_rent_sponsor: LIGHT_TOKEN_RENT_SPONSOR, + light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), + light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = single_pda_derive_test::instruction::CreateMint { + params: CreateMintParams { + create_accounts_proof: proof_result.create_accounts_proof, + mint_signer_bump, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority]) + .await + .expect("CreateMint should succeed"); + + let mint_account = rpc + .get_account(mint_pda) + .await + .unwrap() + .expect("Mint should exist on-chain"); + + use light_token_interface::state::Mint; + let mint: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_account.data[..]) + .expect("Failed to deserialize Mint"); + + assert_eq!(mint.base.decimals, 9, "Mint should have 9 decimals"); + assert_eq!( + mint.base.mint_authority, + Some(payer.pubkey().to_bytes().into()), + "Mint authority should be fee_payer" + ); +} + +// ============================================================================= +// 6. Create Two Mints +// ============================================================================= + +#[tokio::test] +async fn test_create_two_mints_derive() { + use single_pda_derive_test::{CreateTwoMintsParams, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B}; + + let program_id = single_pda_derive_test::ID; + let mut config = + ProgramTestConfig::new_v2(true, Some(vec![("single_pda_derive_test", program_id)])); + config = config.with_light_protocol_events(); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); + + let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id); + + let (init_config_ix, config_pda) = InitializeRentFreeConfig::new( + &program_id, + &payer.pubkey(), + &program_data_pda, + rent_sponsor, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .expect("Initialize config should succeed"); + + let authority = Keypair::new(); + + // Derive mint A + let (mint_signer_a, mint_signer_bump_a) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED_A, authority.pubkey().as_ref()], + &program_id, + ); + let (mint_a_pda, _) = light_token::instruction::find_mint_address(&mint_signer_a); + + // Derive mint B + let (mint_signer_b, mint_signer_bump_b) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED_B, authority.pubkey().as_ref()], + &program_id, + ); + let (mint_b_pda, _) = light_token::instruction::find_mint_address(&mint_signer_b); + + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![ + CreateAccountsProofInput::mint(mint_signer_a), + CreateAccountsProofInput::mint(mint_signer_b), + ], + ) + .await + .unwrap(); + + let accounts = single_pda_derive_test::accounts::CreateTwoMints { + fee_payer: payer.pubkey(), + authority: authority.pubkey(), + mint_signer_a, + mint_a: mint_a_pda, + mint_signer_b, + mint_b: mint_b_pda, + compression_config: config_pda, + light_token_config: LIGHT_TOKEN_CONFIG, + light_token_rent_sponsor: LIGHT_TOKEN_RENT_SPONSOR, + light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), + light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = single_pda_derive_test::instruction::CreateTwoMints { + params: CreateTwoMintsParams { + create_accounts_proof: proof_result.create_accounts_proof, + mint_signer_bump_a, + mint_signer_bump_b, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority]) + .await + .expect("CreateTwoMints should succeed"); + + // Verify mint A + let mint_a_account = rpc + .get_account(mint_a_pda) + .await + .unwrap() + .expect("Mint A should exist on-chain"); + + use light_token_interface::state::Mint; + let mint_a: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_a_account.data[..]) + .expect("Failed to deserialize Mint A"); + + assert_eq!(mint_a.base.decimals, 9, "Mint A should have 9 decimals"); + assert_eq!( + mint_a.base.mint_authority, + Some(payer.pubkey().to_bytes().into()), + "Mint A authority should be fee_payer" + ); + + // Verify mint B + let mint_b_account = rpc + .get_account(mint_b_pda) + .await + .unwrap() + .expect("Mint B should exist on-chain"); + + let mint_b: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_b_account.data[..]) + .expect("Failed to deserialize Mint B"); + + assert_eq!(mint_b.base.decimals, 6, "Mint B should have 6 decimals"); + assert_eq!( + mint_b.base.mint_authority, + Some(payer.pubkey().to_bytes().into()), + "Mint B authority should be fee_payer" + ); +} + +// ============================================================================= +// 7. Create All (combined) +// ============================================================================= + +#[tokio::test] +async fn test_create_all_derive() { + use single_pda_derive_test::{ + CreateAllParams, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, RECORD_SEED, VAULT_AUTH_SEED, + VAULT_SEED, + }; + + let program_id = single_pda_derive_test::ID; + let mut config = + ProgramTestConfig::new_v2(true, Some(vec![("single_pda_derive_test", program_id)])); + config = config.with_light_protocol_events(); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); + + let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id); + + let (init_config_ix, config_pda) = InitializeRentFreeConfig::new( + &program_id, + &payer.pubkey(), + &program_data_pda, + rent_sponsor, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .expect("Initialize config should succeed"); + + // Setup pre-existing mints for ATA and vault + let (ata_mint, _) = setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; + let (vault_mint, _) = setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; + + let owner = Keypair::new().pubkey(); + let authority = Keypair::new(); + + // PDA + let (record_pda, _) = + Pubkey::find_program_address(&[b"minimal_record", owner.as_ref()], &program_id); + + // Zero-copy + let (zc_record_pda, _) = + Pubkey::find_program_address(&[RECORD_SEED, owner.as_ref()], &program_id); + + // ATA + let ata_owner = payer.pubkey(); + let (ata, ata_bump) = light_token::instruction::derive_token_ata(&ata_owner, &ata_mint); + + // Token vault + let (vault_authority, _) = Pubkey::find_program_address(&[VAULT_AUTH_SEED], &program_id); + let (vault, vault_bump) = + Pubkey::find_program_address(&[VAULT_SEED, vault_mint.as_ref()], &program_id); + + // Mint A + let (mint_signer_a, mint_signer_bump_a) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED_A, authority.pubkey().as_ref()], + &program_id, + ); + let (mint_a_pda, _) = light_token::instruction::find_mint_address(&mint_signer_a); + + // Mint B + let (mint_signer_b, mint_signer_bump_b) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED_B, authority.pubkey().as_ref()], + &program_id, + ); + let (mint_b_pda, _) = light_token::instruction::find_mint_address(&mint_signer_b); + + // Build proof inputs for all accounts + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![ + CreateAccountsProofInput::pda(record_pda), + CreateAccountsProofInput::pda(zc_record_pda), + CreateAccountsProofInput::mint(mint_signer_a), + CreateAccountsProofInput::mint(mint_signer_b), + ], + ) + .await + .unwrap(); + + let accounts = single_pda_derive_test::accounts::CreateAll { + fee_payer: payer.pubkey(), + compression_config: config_pda, + pda_rent_sponsor: rent_sponsor, + record: record_pda, + zero_copy_record: zc_record_pda, + ata_mint, + ata_owner, + ata, + vault_mint, + vault_authority, + vault, + authority: authority.pubkey(), + mint_signer_a, + mint_a: mint_a_pda, + mint_signer_b, + mint_b: mint_b_pda, + light_token_config: LIGHT_TOKEN_CONFIG, + light_token_rent_sponsor: LIGHT_TOKEN_RENT_SPONSOR, + light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), + light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = single_pda_derive_test::instruction::CreateAll { + params: CreateAllParams { + create_accounts_proof: proof_result.create_accounts_proof, + owner, + ata_bump, + vault_bump, + mint_signer_bump_a, + mint_signer_bump_b, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority]) + .await + .expect("CreateAll should succeed"); + + // Verify PDA + let record_account = rpc + .get_account(record_pda) + .await + .unwrap() + .expect("Record PDA should exist"); + use single_pda_derive_test::MinimalRecord; + let record: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &record_account.data[8..]) + .expect("Failed to deserialize MinimalRecord"); + assert_eq!(record.owner, owner, "Record owner should match"); + + // Verify zero-copy + let zc_account = rpc + .get_account(zc_record_pda) + .await + .unwrap() + .expect("Zero-copy record should exist"); + use single_pda_derive_test::ZeroCopyRecord; + let zc_record: &ZeroCopyRecord = bytemuck::from_bytes(&zc_account.data[8..]); + assert_eq!(zc_record.owner, owner, "ZC record owner should match"); + assert_eq!(zc_record.counter, 0, "ZC record counter should be 0"); + + // Verify ATA + let ata_account = rpc + .get_account(ata) + .await + .unwrap() + .expect("ATA should exist"); + use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; + let ata_token: Token = borsh::BorshDeserialize::deserialize(&mut &ata_account.data[..]) + .expect("Failed to deserialize ATA Token"); + assert_eq!( + ata_token.mint, + ata_mint.to_bytes().into(), + "ATA mint should match" + ); + assert_eq!( + ata_token.owner, + ata_owner.to_bytes().into(), + "ATA owner should match" + ); + + // Verify vault + let vault_account = rpc + .get_account(vault) + .await + .unwrap() + .expect("Vault should exist"); + let vault_token: Token = borsh::BorshDeserialize::deserialize(&mut &vault_account.data[..]) + .expect("Failed to deserialize Vault Token"); + assert_eq!( + vault_token.mint, + vault_mint.to_bytes().into(), + "Vault mint should match" + ); + assert_eq!( + vault_token.owner, + vault_authority.to_bytes().into(), + "Vault owner should match" + ); + + // Verify mint A + let mint_a_account = rpc + .get_account(mint_a_pda) + .await + .unwrap() + .expect("Mint A should exist"); + use light_token_interface::state::Mint; + let mint_a: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_a_account.data[..]) + .expect("Failed to deserialize Mint A"); + assert_eq!(mint_a.base.decimals, 9, "Mint A should have 9 decimals"); + + // Verify mint B + let mint_b_account = rpc + .get_account(mint_b_pda) + .await + .unwrap() + .expect("Mint B should exist"); + let mint_b: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_b_account.data[..]) + .expect("Failed to deserialize Mint B"); + assert_eq!(mint_b.base.decimals, 6, "Mint B should have 6 decimals"); +} From 8f379c3c106bd8bb736bc21733e7c5243aecdce7 Mon Sep 17 00:00:00 2001 From: ananas Date: Sun, 1 Feb 2026 22:33:28 +0000 Subject: [PATCH 03/15] fix light-sdk --- sdk-libs/sdk-interface/src/lib.rs | 30 +- sdk-libs/sdk/src/cpi/v1/accounts.rs | 165 ++++++++++ sdk-libs/sdk/src/cpi/v1/invoke.rs | 285 ++++++++++++++++-- sdk-libs/sdk/src/cpi/v1/mod.rs | 22 +- sdk-libs/sdk/src/cpi/v2/mod.rs | 21 +- sdk-libs/sdk/src/error.rs | 3 +- sdk-libs/sdk/src/instruction/mod.rs | 6 +- .../src/instruction/packed_accounts_ext.rs | 2 +- sdk-libs/sdk/src/instruction/tree_info.rs | 4 +- 9 files changed, 478 insertions(+), 60 deletions(-) create mode 100644 sdk-libs/sdk/src/cpi/v1/accounts.rs diff --git a/sdk-libs/sdk-interface/src/lib.rs b/sdk-libs/sdk-interface/src/lib.rs index 2416796bb7..f063e5b76d 100644 --- a/sdk-libs/sdk-interface/src/lib.rs +++ b/sdk-libs/sdk-interface/src/lib.rs @@ -2,7 +2,7 @@ #![cfg_attr(not(feature = "std"), no_std)] pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; - +// TODO: delete backup pub mod error; pub mod account; @@ -52,6 +52,8 @@ pub use cpi::{ }; // --- program/ --- +#[cfg(feature = "token")] +pub use account::token_seeds::{PackedTokenData, TokenDataWithPackedSeeds, TokenDataWithSeeds}; pub use program::compression::close::close; pub use program::compression::pda::prepare_account_for_compression; pub use program::compression::processor::{ @@ -60,28 +62,24 @@ pub use program::compression::processor::{ }; pub use program::config::{ process_initialize_light_config_checked, process_update_light_config, - InitializeLightConfigParams, LightConfig, UpdateLightConfigParams, - COMPRESSIBLE_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE, -}; -pub use program::validation::{ - extract_tail_accounts, is_pda_initialized, should_skip_compression, - split_at_system_accounts_offset, validate_compress_accounts, validate_decompress_accounts, - ValidatedPdaContext, + InitializeLightConfigParams, LightConfig, UpdateLightConfigParams, COMPRESSIBLE_CONFIG_SEED, + MAX_ADDRESS_TREES_PER_SPACE, }; pub use program::decompression::pda::prepare_account_for_decompression; +#[cfg(feature = "token")] +pub use program::decompression::processor::process_decompress_accounts_idempotent; pub use program::decompression::processor::{ process_decompress_pda_accounts_idempotent, DecompressCtx, DecompressIdempotentParams, DecompressVariant, }; +#[cfg(feature = "token")] +pub use program::decompression::token::prepare_token_account_for_decompression; +pub use program::validation::{ + extract_tail_accounts, is_pda_initialized, should_skip_compression, + split_at_system_accounts_offset, validate_compress_accounts, validate_decompress_accounts, + ValidatedPdaContext, +}; pub use program::variant::IntoVariant; pub use program::variant::{LightAccountVariantTrait, PackedLightAccountVariantTrait}; #[cfg(feature = "token")] pub use program::variant::{PackedTokenSeeds, UnpackedTokenSeeds}; -#[cfg(feature = "token")] -pub use program::decompression::processor::process_decompress_accounts_idempotent; -#[cfg(feature = "token")] -pub use program::decompression::token::prepare_token_account_for_decompression; -#[cfg(feature = "token")] -pub use account::token_seeds::{ - PackedTokenData, TokenDataWithPackedSeeds, TokenDataWithSeeds, -}; diff --git a/sdk-libs/sdk/src/cpi/v1/accounts.rs b/sdk-libs/sdk/src/cpi/v1/accounts.rs new file mode 100644 index 0000000000..e0d9dd8503 --- /dev/null +++ b/sdk-libs/sdk/src/cpi/v1/accounts.rs @@ -0,0 +1,165 @@ +pub use light_sdk_types::cpi_accounts::v1::SYSTEM_ACCOUNTS_LEN; +use light_sdk_types::{ + cpi_accounts::v1::CpiAccounts as GenericCpiAccounts, ACCOUNT_COMPRESSION_AUTHORITY_PDA, + ACCOUNT_COMPRESSION_PROGRAM_ID, LIGHT_SYSTEM_PROGRAM_ID, NOOP_PROGRAM_ID, + REGISTERED_PROGRAM_PDA, +}; + +use crate::{ + error::{LightSdkError, Result}, + AccountInfo, AccountMeta, Pubkey, +}; + +#[derive(Debug)] +pub struct CpiInstructionConfig<'a, 'info> { + pub fee_payer: Pubkey, + pub cpi_signer: Pubkey, + pub invoking_program: Pubkey, + pub sol_pool_pda_pubkey: Option, + pub sol_compression_recipient_pubkey: Option, + pub cpi_context_pubkey: Option, + pub packed_accounts: &'a [AccountInfo<'info>], +} + +/// Light system program CPI accounts struct. +/// +/// Use with [`LightSystemProgramCpi`](super::LightSystemProgramCpi) to invoke the Light system program. +pub type CpiAccounts<'c, 'info> = GenericCpiAccounts<'c, AccountInfo<'info>>; + +pub fn get_account_metas_from_config(config: CpiInstructionConfig<'_, '_>) -> Vec { + let mut account_metas = Vec::with_capacity(1 + SYSTEM_ACCOUNTS_LEN); + + // 1. Fee payer (signer, writable) + account_metas.push(AccountMeta { + pubkey: config.fee_payer, + is_signer: true, + is_writable: true, + }); + + // 2. Authority/CPI Signer (signer, readonly) + account_metas.push(AccountMeta { + pubkey: config.cpi_signer, + is_signer: true, + is_writable: false, + }); + + // 3. Registered Program PDA (readonly) - hardcoded constant + account_metas.push(AccountMeta { + pubkey: Pubkey::from(REGISTERED_PROGRAM_PDA), + is_signer: false, + is_writable: false, + }); + + // 4. Noop Program (readonly) - hardcoded constant + account_metas.push(AccountMeta { + pubkey: Pubkey::from(NOOP_PROGRAM_ID), + is_signer: false, + is_writable: false, + }); + + // 5. Account Compression Authority (readonly) - hardcoded constant + account_metas.push(AccountMeta { + pubkey: Pubkey::from(ACCOUNT_COMPRESSION_AUTHORITY_PDA), + is_signer: false, + is_writable: false, + }); + + // 6. Account Compression Program (readonly) - hardcoded constant + account_metas.push(AccountMeta { + pubkey: Pubkey::from(ACCOUNT_COMPRESSION_PROGRAM_ID), + is_signer: false, + is_writable: false, + }); + + // 7. Invoking Program (readonly) + account_metas.push(AccountMeta { + pubkey: config.invoking_program, + is_signer: false, + is_writable: false, + }); + + // 8. Light System Program (readonly) - reused for optional accounts + let create_light_system_meta = || AccountMeta { + pubkey: Pubkey::from(LIGHT_SYSTEM_PROGRAM_ID), + is_signer: false, + is_writable: false, + }; + + // 9. Sol Pool PDA (writable) OR Light System Program (readonly) + if let Some(sol_pool_pda_pubkey) = config.sol_pool_pda_pubkey { + account_metas.push(AccountMeta { + pubkey: sol_pool_pda_pubkey, + is_signer: false, + is_writable: true, + }); + } else { + account_metas.push(create_light_system_meta()); + } + + // 10. Sol Compression Recipient (writable) OR Light System Program (readonly) + if let Some(sol_compression_recipient_pubkey) = config.sol_compression_recipient_pubkey { + account_metas.push(AccountMeta { + pubkey: sol_compression_recipient_pubkey, + is_signer: false, + is_writable: true, + }); + } else { + account_metas.push(create_light_system_meta()); + } + + // 11. System Program (readonly) - always default pubkey + account_metas.push(AccountMeta { + pubkey: Pubkey::default(), + is_signer: false, + is_writable: false, + }); + + // 12. CPI Context (writable) OR Light System Program (readonly) + if let Some(cpi_context_pubkey) = config.cpi_context_pubkey { + account_metas.push(AccountMeta { + pubkey: cpi_context_pubkey, + is_signer: false, + is_writable: true, + }); + } else { + account_metas.push(create_light_system_meta()); + } + + for acc in config.packed_accounts { + account_metas.push(AccountMeta { + pubkey: *acc.key, + is_signer: false, + is_writable: acc.is_writable, + }); + } + + account_metas +} + +impl<'a, 'info> TryFrom<&'a CpiAccounts<'a, 'info>> for CpiInstructionConfig<'a, 'info> { + type Error = LightSdkError; + + fn try_from(cpi_accounts: &'a CpiAccounts<'a, 'info>) -> Result { + Ok(CpiInstructionConfig { + fee_payer: *cpi_accounts.fee_payer().key, + cpi_signer: cpi_accounts.config().cpi_signer().into(), + invoking_program: cpi_accounts.config().cpi_signer.program_id.into(), + sol_pool_pda_pubkey: if cpi_accounts.config().sol_pool_pda { + Some(*cpi_accounts.sol_pool_pda()?.key) + } else { + None + }, + sol_compression_recipient_pubkey: if cpi_accounts.config().sol_compression_recipient { + Some(*cpi_accounts.decompression_recipient()?.key) + } else { + None + }, + cpi_context_pubkey: if cpi_accounts.config().cpi_context { + Some(*cpi_accounts.cpi_context()?.key) + } else { + None + }, + packed_accounts: cpi_accounts.tree_accounts().unwrap_or(&[]), + }) + } +} diff --git a/sdk-libs/sdk/src/cpi/v1/invoke.rs b/sdk-libs/sdk/src/cpi/v1/invoke.rs index cd620aa001..b5d6d3f606 100644 --- a/sdk-libs/sdk/src/cpi/v1/invoke.rs +++ b/sdk-libs/sdk/src/cpi/v1/invoke.rs @@ -1,35 +1,169 @@ -use light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext; use light_compressed_account::instruction_data::{ - compressed_proof::ValidityProof, - cpi_context::CompressedCpiContext, + compressed_proof::ValidityProof, invoke_cpi::InstructionDataInvokeCpi, }; +use light_sdk_types::constants::{CPI_AUTHORITY_PDA_SEED, LIGHT_SYSTEM_PROGRAM_ID}; #[cfg(feature = "poseidon")] use crate::{account::poseidon::LightAccount as LightAccountPoseidon, DataHasher}; use crate::{ account::LightAccount, - cpi::{delegate_light_cpi, LightCpiInstruction}, + cpi::CpiSigner, error::LightSdkError, instruction::account_info::CompressedAccountInfoTrait, AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError, }; -// Re-export LightSystemProgramCpi from interface -pub use light_sdk_interface::cpi::v1::LightSystemProgramCpi; +use super::{ + lowlevel::{get_account_metas_from_config, CpiInstructionConfig}, + CpiAccounts, +}; + +/// Light system program CPI instruction data builder. +/// +/// Use this builder to construct instructions for compressed account operations: +/// creating, updating, closing accounts, and compressing/decompressing SOL. +/// +/// # Builder Methods +/// +/// ## Common Methods +/// +/// - [`with_light_account()`](Self::with_light_account) - Add a compressed account +/// - [`with_new_addresses()`](Self::with_new_addresses) - Create new compressed account addresses +/// - [`compress_lamports()`](Self::compress_lamports) - Compress SOL into compressed accounts +/// - [`decompress_lamports()`](Self::decompress_lamports) - Decompress SOL from compressed accounts +/// +/// **Note**: An instruction can either compress **or** decompress lamports, not both. +/// +/// ## Advanced Methods +/// +/// For fine-grained control, use these low-level methods instead of [`with_light_account()`](Self::with_light_account): +/// +/// - [`with_input_compressed_accounts_with_merkle_context()`](Self::with_input_compressed_accounts_with_merkle_context) - Manually specify input accounts +/// - [`with_output_compressed_accounts()`](Self::with_output_compressed_accounts) - Manually specify output accounts +/// +/// # Examples +/// +/// ## Create a compressed account with an address +/// ```rust,no_run +/// # use light_sdk::cpi::{v1::LightSystemProgramCpi, CpiSigner}; +/// # use light_sdk::instruction::ValidityProof; +/// # use light_compressed_account::instruction_data::data::NewAddressParamsPacked; +/// # use light_sdk::{LightAccount, LightDiscriminator}; +/// # use borsh::{BorshSerialize, BorshDeserialize}; +/// # use solana_pubkey::Pubkey; +/// # use solana_program_error::ProgramError; +/// # +/// # const LIGHT_CPI_SIGNER: CpiSigner = CpiSigner { +/// # program_id: [0; 32], +/// # cpi_signer: [0; 32], +/// # bump: 255, +/// # }; +/// # +/// # #[derive(Clone, Debug, Default, LightDiscriminator, BorshSerialize, BorshDeserialize)] +/// # pub struct MyAccount { +/// # pub value: u64, +/// # } +/// # +/// # fn example() -> Result<(), ProgramError> { +/// # let proof = ValidityProof::default(); +/// # let new_address_params = NewAddressParamsPacked::default(); +/// # let program_id = Pubkey::new_unique(); +/// # let account = LightAccount::::new_init(&program_id, None, 0); +/// # let key = Pubkey::new_unique(); +/// # let owner = Pubkey::default(); +/// # let mut lamports = 0u64; +/// # let mut data = []; +/// # let fee_payer = &solana_account_info::AccountInfo::new( +/// # &key, +/// # true, +/// # true, +/// # &mut lamports, +/// # &mut data, +/// # &owner, +/// # false, +/// # 0, +/// # ); +/// # let cpi_accounts = light_sdk::cpi::v1::CpiAccounts::new(fee_payer, &[], LIGHT_CPI_SIGNER); +/// LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof) +/// .with_new_addresses(&[new_address_params]) +/// .with_light_account(account)? +/// .invoke(cpi_accounts)?; +/// # Ok(()) +/// # } +/// ``` +/// ## Update a compressed account +/// ```rust,no_run +/// # use light_sdk::cpi::{v1::LightSystemProgramCpi, CpiSigner}; +/// # use light_sdk::instruction::ValidityProof; +/// # use light_sdk::{LightAccount, LightDiscriminator}; +/// # use light_sdk::instruction::account_meta::CompressedAccountMeta; +/// # use borsh::{BorshSerialize, BorshDeserialize}; +/// # use solana_pubkey::Pubkey; +/// # use solana_program_error::ProgramError; +/// # +/// # const LIGHT_CPI_SIGNER: CpiSigner = CpiSigner { +/// # program_id: [0; 32], +/// # cpi_signer: [0; 32], +/// # bump: 255, +/// # }; +/// # +/// # #[derive(Clone, Debug, Default, LightDiscriminator, BorshSerialize, BorshDeserialize)] +/// # pub struct MyAccount { +/// # pub value: u64, +/// # } +/// # +/// # fn example() -> Result<(), ProgramError> { +/// # let proof = ValidityProof::default(); +/// # let program_id = Pubkey::new_unique(); +/// # let account_meta = CompressedAccountMeta::default(); +/// # let account_data = MyAccount::default(); +/// # let account = LightAccount::::new_mut(&program_id, &account_meta, account_data)?; +/// # let key = Pubkey::new_unique(); +/// # let owner = Pubkey::default(); +/// # let mut lamports = 0u64; +/// # let mut data = []; +/// # let fee_payer = &solana_account_info::AccountInfo::new( +/// # &key, +/// # true, +/// # true, +/// # &mut lamports, +/// # &mut data, +/// # &owner, +/// # false, +/// # 0, +/// # ); +/// # let cpi_accounts = light_sdk::cpi::v1::CpiAccounts::new(fee_payer, &[], LIGHT_CPI_SIGNER); +/// LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof) +/// .with_light_account(account)? +/// .invoke(cpi_accounts)?; +/// # Ok(()) +/// # } +/// ``` +#[derive(Clone)] +pub struct LightSystemProgramCpi { + cpi_signer: CpiSigner, + instruction_data: InstructionDataInvokeCpi, +} -impl LightCpiInstruction for LightSystemProgramCpi { - delegate_light_cpi!(LightSystemProgramCpi); +impl LightSystemProgramCpi { + pub fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self { + Self { + cpi_signer, + instruction_data: InstructionDataInvokeCpi::new(proof.into()), + } + } - fn with_light_account(mut self, account: LightAccount) -> Result + #[must_use = "with_light_account returns a new value"] + pub fn with_light_account(mut self, account: LightAccount) -> Result where A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default, { - // Convert LightAccount to account info + use light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext; + let account_info = account.to_account_info()?; - // Handle input accounts - convert to PackedCompressedAccountWithMerkleContext if let Some(input_account) = account_info - .input_compressed_account(self.cpi_signer().program_id.into()) + .input_compressed_account(self.cpi_signer.program_id.into()) .map_err(LightSdkError::from) .map_err(ProgramError::from)? { @@ -37,20 +171,19 @@ impl LightCpiInstruction for LightSystemProgramCpi { compressed_account: input_account.compressed_account, merkle_context: input_account.merkle_context, root_index: input_account.root_index, - read_only: false, // Default to false for v1 + read_only: false, }; - self.instruction_data_mut() + self.instruction_data .input_compressed_accounts_with_merkle_context .push(packed_input); } - // Handle output accounts if let Some(output_account) = account_info - .output_compressed_account(self.cpi_signer().program_id.into()) + .output_compressed_account(self.cpi_signer.program_id.into()) .map_err(LightSdkError::from) .map_err(ProgramError::from)? { - self.instruction_data_mut() + self.instruction_data .output_compressed_accounts .push(output_account); } @@ -59,19 +192,20 @@ impl LightCpiInstruction for LightSystemProgramCpi { } #[cfg(feature = "poseidon")] - fn with_light_account_poseidon( + #[must_use = "with_light_account_poseidon returns a new value"] + pub fn with_light_account_poseidon( mut self, account: LightAccountPoseidon, ) -> Result where A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + DataHasher + Default, { - // Convert LightAccount to account info + use light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext; + let account_info = account.to_account_info()?; - // Handle input accounts - convert to PackedCompressedAccountWithMerkleContext if let Some(input_account) = account_info - .input_compressed_account(self.cpi_signer().program_id.into()) + .input_compressed_account(self.cpi_signer.program_id.into()) .map_err(LightSdkError::from) .map_err(ProgramError::from)? { @@ -79,24 +213,123 @@ impl LightCpiInstruction for LightSystemProgramCpi { compressed_account: input_account.compressed_account, merkle_context: input_account.merkle_context, root_index: input_account.root_index, - read_only: false, // Default to false for v1 + read_only: false, }; - self.instruction_data_mut() + self.instruction_data .input_compressed_accounts_with_merkle_context .push(packed_input); } - // Handle output accounts if let Some(output_account) = account_info - .output_compressed_account(self.cpi_signer().program_id.into()) + .output_compressed_account(self.cpi_signer.program_id.into()) .map_err(LightSdkError::from) .map_err(ProgramError::from)? { - self.instruction_data_mut() + self.instruction_data .output_compressed_accounts .push(output_account); } Ok(self) } + + #[must_use = "with_new_addresses returns a new value"] + pub fn with_new_addresses( + mut self, + new_address_params: &[light_compressed_account::instruction_data::data::NewAddressParamsPacked], + ) -> Self { + self.instruction_data = self.instruction_data.with_new_addresses(new_address_params); + self + } + + #[must_use = "with_input_compressed_accounts_with_merkle_context returns a new value"] + pub fn with_input_compressed_accounts_with_merkle_context( + mut self, + input_compressed_accounts_with_merkle_context: &[light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext], + ) -> Self { + self.instruction_data = self + .instruction_data + .with_input_compressed_accounts_with_merkle_context( + input_compressed_accounts_with_merkle_context, + ); + self + } + + #[must_use = "with_output_compressed_accounts returns a new value"] + pub fn with_output_compressed_accounts( + mut self, + output_compressed_accounts: &[light_compressed_account::instruction_data::data::OutputCompressedAccountWithPackedContext], + ) -> Self { + self.instruction_data = self + .instruction_data + .with_output_compressed_accounts(output_compressed_accounts); + self + } + + #[must_use = "compress_lamports returns a new value"] + pub fn compress_lamports(mut self, lamports: u64) -> Self { + self.instruction_data = self.instruction_data.compress_lamports(lamports); + self + } + + #[must_use = "decompress_lamports returns a new value"] + pub fn decompress_lamports(mut self, lamports: u64) -> Self { + self.instruction_data = self.instruction_data.decompress_lamports(lamports); + self + } + + #[cfg(feature = "cpi-context")] + #[must_use = "write_to_cpi_context_set returns a new value"] + pub fn write_to_cpi_context_set(mut self) -> Self { + self.instruction_data = self.instruction_data.write_to_cpi_context_set(); + self + } + + #[cfg(feature = "cpi-context")] + #[must_use = "write_to_cpi_context_first returns a new value"] + pub fn write_to_cpi_context_first(mut self) -> Self { + self.instruction_data = self.instruction_data.write_to_cpi_context_first(); + self + } + + #[cfg(feature = "cpi-context")] + #[must_use = "with_cpi_context returns a new value"] + pub fn with_cpi_context( + mut self, + cpi_context: light_compressed_account::instruction_data::cpi_context::CompressedCpiContext, + ) -> Self { + self.instruction_data = self.instruction_data.with_cpi_context(cpi_context); + self + } + + /// Invoke the Light system program via CPI. + pub fn invoke(self, cpi_accounts: CpiAccounts<'_, '_>) -> Result<(), ProgramError> { + use light_compressed_account::instruction_data::traits::LightInstructionData; + + let data = self + .instruction_data + .data() + .map_err(LightSdkError::from) + .map_err(ProgramError::from)?; + + let account_infos = cpi_accounts.to_account_infos(); + let config = + CpiInstructionConfig::try_from(&cpi_accounts).map_err(ProgramError::from)?; + let account_metas = get_account_metas_from_config(config); + + let instruction = solana_instruction::Instruction { + program_id: LIGHT_SYSTEM_PROGRAM_ID.into(), + accounts: account_metas, + data, + }; + let signer_seeds = [CPI_AUTHORITY_PDA_SEED, &[self.cpi_signer.bump]]; + solana_cpi::invoke_signed(&instruction, &account_infos, &[signer_seeds.as_slice()]) + } +} + +// Manual BorshSerialize implementation that only serializes instruction_data +impl AnchorSerialize for LightSystemProgramCpi { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + self.instruction_data.serialize(writer) + } } diff --git a/sdk-libs/sdk/src/cpi/v1/mod.rs b/sdk-libs/sdk/src/cpi/v1/mod.rs index cb17ae926a..b9c66a8cd4 100644 --- a/sdk-libs/sdk/src/cpi/v1/mod.rs +++ b/sdk-libs/sdk/src/cpi/v1/mod.rs @@ -10,9 +10,23 @@ //! //! For maximum flexible light system program CPIs, see the [`lowlevel`] module or use `light-compressed-account` directly. -// Re-export everything from interface's v1 module -pub use light_sdk_interface::cpi::v1::*; - -// LightCpiInstruction impl for LightSystemProgramCpi +mod accounts; mod invoke; + +pub use accounts::CpiAccounts; pub use invoke::LightSystemProgramCpi; + +/// Low-level types and functions for flexible Light system program CPIs. +/// +/// # Main Types +/// +/// For most use cases, you only need: +/// - [`LightSystemProgramCpi`] - Main CPI interface +/// - [`CpiAccounts`] - Account management +/// +/// The remaining types in this module are exported for low-level operations and internal use. +pub mod lowlevel { + pub use super::accounts::{ + get_account_metas_from_config, CpiInstructionConfig, SYSTEM_ACCOUNTS_LEN, + }; +} diff --git a/sdk-libs/sdk/src/cpi/v2/mod.rs b/sdk-libs/sdk/src/cpi/v2/mod.rs index 21bcc7394b..b3dcefd820 100644 --- a/sdk-libs/sdk/src/cpi/v2/mod.rs +++ b/sdk-libs/sdk/src/cpi/v2/mod.rs @@ -2,16 +2,21 @@ //! //! # Main Types //! -//! - [`LightSystemProgramCpi`] - CPI instruction data builder +//! - [`InstructionDataInvokeCpiWithReadOnly`] - CPI instruction with read-only account support +//! - [`InstructionDataInvokeCpiWithAccountInfo`] - CPI instruction with account info //! - [`CpiAccounts`] - CPI accounts struct -//! -//! -//! # Advanced Usage -//! -//! For maximum flexible light system program CPIs, see the [`lowlevel`] module or use `light-compressed-account` directly. -// Re-export everything from interface's v2 module -pub use light_sdk_interface::cpi::v2::*; +// CpiAccounts from sdk-types (v2) +pub use light_sdk_types::cpi_accounts::v2::CpiAccounts as GenericCpiAccounts; +pub type CpiAccounts<'c, 'info> = + GenericCpiAccounts<'c, solana_account_info::AccountInfo<'info>>; + +// Instruction types from light-compressed-account +pub use light_compressed_account::instruction_data::{ + cpi_context::CompressedCpiContext, + with_account_info::InstructionDataInvokeCpiWithAccountInfo, + with_readonly::{InAccount, InstructionDataInvokeCpiWithReadOnly}, +}; // LightCpiInstruction impls for v2 instruction types mod invoke; diff --git a/sdk-libs/sdk/src/error.rs b/sdk-libs/sdk/src/error.rs index 48ee48712e..f59f90754c 100644 --- a/sdk-libs/sdk/src/error.rs +++ b/sdk-libs/sdk/src/error.rs @@ -142,7 +142,6 @@ impl From for light_sdk_interface::error::LightPdaError { LightSdkError::Hasher(e) => InterfaceError::Hasher(e), LightSdkError::MissingCompressionInfo => InterfaceError::MissingCompressionInfo, LightSdkError::InvalidRentSponsor => InterfaceError::InvalidRentSponsor, - LightSdkError::ProgramError(e) => InterfaceError::ProgramError(e), LightSdkError::CpiAccountsIndexOutOfBounds(i) => { InterfaceError::CpiAccountsIndexOutOfBounds(i) } @@ -170,7 +169,6 @@ impl From for LightSdkError { InterfaceError::Hasher(e) => LightSdkError::Hasher(e), InterfaceError::MissingCompressionInfo => LightSdkError::MissingCompressionInfo, InterfaceError::InvalidRentSponsor => LightSdkError::InvalidRentSponsor, - InterfaceError::ProgramError(e) => LightSdkError::ProgramError(e), InterfaceError::BorshIo(_) => LightSdkError::Borsh, InterfaceError::CpiAccountsIndexOutOfBounds(i) => { LightSdkError::CpiAccountsIndexOutOfBounds(i) @@ -181,6 +179,7 @@ impl From for LightSdkError { InterfaceError::CompressedAccountError(e) => { LightSdkError::CompressedAccountError(e) } + _ => LightSdkError::ConstraintViolation, } } } diff --git a/sdk-libs/sdk/src/instruction/mod.rs b/sdk-libs/sdk/src/instruction/mod.rs index 95cb4ff6e2..fe30a0781f 100644 --- a/sdk-libs/sdk/src/instruction/mod.rs +++ b/sdk-libs/sdk/src/instruction/mod.rs @@ -40,9 +40,13 @@ // TODO: link to examples -// Re-export PackedAccounts and base instruction types from interface +// Re-export base instruction types from interface pub use light_sdk_interface::instruction::*; +// Concrete PackedAccounts type alias for solana AccountMeta +pub type PackedAccounts = + light_sdk_interface::instruction::PackedAccounts; + // SDK-specific: ValidityProof and CompressedProof pub use light_compressed_account::instruction_data::compressed_proof::{ CompressedProof, ValidityProof, diff --git a/sdk-libs/sdk/src/instruction/packed_accounts_ext.rs b/sdk-libs/sdk/src/instruction/packed_accounts_ext.rs index a02eb7186c..cf927854de 100644 --- a/sdk-libs/sdk/src/instruction/packed_accounts_ext.rs +++ b/sdk-libs/sdk/src/instruction/packed_accounts_ext.rs @@ -1,4 +1,4 @@ -use light_sdk_interface::instruction::PackedAccounts; +use super::PackedAccounts; use super::system_accounts::{get_light_system_account_metas, SystemAccountMetaConfig}; diff --git a/sdk-libs/sdk/src/instruction/tree_info.rs b/sdk-libs/sdk/src/instruction/tree_info.rs index 0281d824df..51bae81187 100644 --- a/sdk-libs/sdk/src/instruction/tree_info.rs +++ b/sdk-libs/sdk/src/instruction/tree_info.rs @@ -69,8 +69,8 @@ pub fn pack_address_tree_info( root_index: u16, ) -> PackedAddressTreeInfo { let AddressTreeInfo { tree, queue } = address_tree_info; - let address_merkle_tree_pubkey_index = remaining_accounts.insert_or_get(*tree); - let address_queue_pubkey_index = remaining_accounts.insert_or_get(*queue); + let address_merkle_tree_pubkey_index = remaining_accounts.insert_or_get(tree.to_bytes()); + let address_queue_pubkey_index = remaining_accounts.insert_or_get(queue.to_bytes()); PackedAddressTreeInfo { address_merkle_tree_pubkey_index, From 216a6d48bba9b43f50ef26ce70e47afcbfaf4364 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 2 Feb 2026 01:00:33 +0000 Subject: [PATCH 04/15] manual works --- .../src/account_info/account_meta_trait.rs | 13 +- .../src/account_info/pinocchio.rs | 10 + .../account-checks/src/account_info/solana.rs | 14 +- .../create-address-test-program/src/lib.rs | 66 +-- .../client/src/interface/initialize_config.rs | 10 +- sdk-libs/client/src/interface/instructions.rs | 34 +- .../src/interface/light_program_interface.rs | 2 +- .../client/src/interface/load_accounts.rs | 4 +- sdk-libs/client/src/interface/mod.rs | 2 +- .../compressed_token/v2/compress_and_close.rs | 23 +- .../compressed_token/v2/decompress_full.rs | 27 +- .../macros/src/light_pdas/account/derive.rs | 31 +- sdk-libs/program-test/src/compressible.rs | 12 +- .../sdk-interface/src/account/token_seeds.rs | 10 +- .../src/instruction/pack_accounts.rs | 15 +- sdk-libs/sdk/Cargo.toml | 2 +- sdk-libs/sdk/src/instruction/mod.rs | 9 +- sdk-libs/sdk/src/instruction/tree_info.rs | 9 +- sdk-libs/sdk/src/lib.rs | 5 +- .../src/compressible/compress_runtime.rs | 45 +- sdk-tests/manual-test/Cargo.toml | 2 +- .../src/account_loader/derived_accounts.rs | 280 +++++------ .../src/account_loader/derived_state.rs | 39 +- sdk-tests/manual-test/src/all/derived.rs | 451 +++++++++--------- .../manual-test/src/all/derived_accounts.rs | 106 ++-- sdk-tests/manual-test/src/ata/derived.rs | 59 +-- sdk-tests/manual-test/src/derived_compress.rs | 19 +- .../manual-test/src/derived_decompress.rs | 9 +- .../manual-test/src/derived_light_config.rs | 17 +- sdk-tests/manual-test/src/derived_variants.rs | 20 +- sdk-tests/manual-test/src/lib.rs | 24 +- .../manual-test/src/pda/derived_accounts.rs | 274 +++++------ .../manual-test/src/pda/derived_state.rs | 41 +- sdk-tests/manual-test/src/pda/state.rs | 6 +- .../manual-test/src/token_account/derived.rs | 67 +-- .../manual-test/src/two_mints/derived.rs | 305 ++++++------ 36 files changed, 1112 insertions(+), 950 deletions(-) diff --git a/program-libs/account-checks/src/account_info/account_meta_trait.rs b/program-libs/account-checks/src/account_info/account_meta_trait.rs index 51fdaf18cd..c46d0aebab 100644 --- a/program-libs/account-checks/src/account_info/account_meta_trait.rs +++ b/program-libs/account-checks/src/account_info/account_meta_trait.rs @@ -2,10 +2,17 @@ use core::fmt::Debug; /// Trait abstracting over AccountMeta implementations (solana vs pinocchio). /// -/// Uses `[u8; 32]` for pubkeys (same pattern as AccountInfoTrait::key()). -/// Implementations convert to/from native types internally. +/// The associated `Pubkey` type allows callers to pass native pubkey types +/// (e.g. `solana_pubkey::Pubkey` or `[u8; 32]`) without manual conversion. pub trait AccountMetaTrait: Clone + Debug { - fn new(pubkey: [u8; 32], is_signer: bool, is_writable: bool) -> Self; + /// The native pubkey type for this account meta implementation. + /// - `solana_pubkey::Pubkey` for `solana_instruction::AccountMeta` + /// - `[u8; 32]` for pinocchio's `OwnedAccountMeta` + type Pubkey: Copy; + + fn new(pubkey: Self::Pubkey, is_signer: bool, is_writable: bool) -> Self; + fn pubkey_to_bytes(pubkey: Self::Pubkey) -> [u8; 32]; + fn pubkey_from_bytes(bytes: [u8; 32]) -> Self::Pubkey; fn pubkey_bytes(&self) -> [u8; 32]; fn is_signer(&self) -> bool; fn is_writable(&self) -> bool; diff --git a/program-libs/account-checks/src/account_info/pinocchio.rs b/program-libs/account-checks/src/account_info/pinocchio.rs index 3b89dccaca..538b521234 100644 --- a/program-libs/account-checks/src/account_info/pinocchio.rs +++ b/program-libs/account-checks/src/account_info/pinocchio.rs @@ -14,6 +14,8 @@ pub struct OwnedAccountMeta { } impl AccountMetaTrait for OwnedAccountMeta { + type Pubkey = [u8; 32]; + fn new(pubkey: [u8; 32], is_signer: bool, is_writable: bool) -> Self { Self { pubkey, @@ -22,6 +24,14 @@ impl AccountMetaTrait for OwnedAccountMeta { } } + fn pubkey_to_bytes(pubkey: [u8; 32]) -> [u8; 32] { + pubkey + } + + fn pubkey_from_bytes(bytes: [u8; 32]) -> [u8; 32] { + bytes + } + fn pubkey_bytes(&self) -> [u8; 32] { self.pubkey } diff --git a/program-libs/account-checks/src/account_info/solana.rs b/program-libs/account-checks/src/account_info/solana.rs index 6a931dbe4e..449385ec05 100644 --- a/program-libs/account-checks/src/account_info/solana.rs +++ b/program-libs/account-checks/src/account_info/solana.rs @@ -219,14 +219,24 @@ impl AccountInfoTrait for solana_account_info::AccountInfo<'_> { } impl AccountMetaTrait for solana_instruction::AccountMeta { - fn new(pubkey: [u8; 32], is_signer: bool, is_writable: bool) -> Self { + type Pubkey = solana_pubkey::Pubkey; + + fn new(pubkey: solana_pubkey::Pubkey, is_signer: bool, is_writable: bool) -> Self { Self { - pubkey: solana_pubkey::Pubkey::from(pubkey), + pubkey, is_signer, is_writable, } } + fn pubkey_to_bytes(pubkey: solana_pubkey::Pubkey) -> [u8; 32] { + pubkey.to_bytes() + } + + fn pubkey_from_bytes(bytes: [u8; 32]) -> solana_pubkey::Pubkey { + solana_pubkey::Pubkey::from(bytes) + } + fn pubkey_bytes(&self) -> [u8; 32] { self.pubkey.to_bytes() } diff --git a/program-tests/create-address-test-program/src/lib.rs b/program-tests/create-address-test-program/src/lib.rs index 8c24f68a18..c81602e4b9 100644 --- a/program-tests/create-address-test-program/src/lib.rs +++ b/program-tests/create-address-test-program/src/lib.rs @@ -24,10 +24,10 @@ use light_compressed_account::instruction_data::{ use light_sdk::{ constants::LIGHT_SYSTEM_PROGRAM_ID, cpi::{ - invoke::invoke_light_system_program, + invoke::invoke_light_system_program, CpiAccountsTrait, v1::lowlevel::{get_account_metas_from_config, CpiInstructionConfig}, - v2::lowlevel::to_account_metas, }, + light_account_checks::CpiMeta, }; #[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, PartialEq)] pub struct CpiAccountsConfigLocal { @@ -97,38 +97,38 @@ pub mod system_cpi_test { ) -> Result<()> { let fee_payer = ctx.accounts.signer.to_account_info(); - let (account_infos, account_metas) = if v2_ix { + let (account_infos, cpi_metas) = if v2_ix { let cpi_accounts = CpiAccounts::new_with_config(&fee_payer, ctx.remaining_accounts, config.into()); let account_infos = cpi_accounts.to_account_infos(); - let account_metas = if !write_cpi_context { - to_account_metas(&cpi_accounts).map_err(|_| ErrorCode::AccountNotEnoughKeys)? + let cpi_metas = if !write_cpi_context { + CpiAccountsTrait::to_account_metas(&cpi_accounts) + .map_err(|_| ErrorCode::AccountNotEnoughKeys)? } else { require!( ctx.remaining_accounts.len() >= 3, ErrorCode::AccountNotEnoughKeys ); - let mut account_metas = vec![]; - account_metas.push(AccountMeta { - pubkey: *cpi_accounts.fee_payer().key, - is_signer: true, - is_writable: true, - }); - account_metas.push(AccountMeta { - pubkey: *ctx.remaining_accounts[1].key, - is_signer: true, - is_writable: false, - }); - let account = &ctx.remaining_accounts[2]; - account_metas.push(AccountMeta { - pubkey: *account.key, - is_signer: false, - is_writable: true, - }); - account_metas + vec![ + CpiMeta { + pubkey: cpi_accounts.fee_payer().key.to_bytes(), + is_signer: true, + is_writable: true, + }, + CpiMeta { + pubkey: ctx.remaining_accounts[1].key.to_bytes(), + is_signer: true, + is_writable: false, + }, + CpiMeta { + pubkey: ctx.remaining_accounts[2].key.to_bytes(), + is_signer: false, + is_writable: true, + }, + ] }; - (account_infos, account_metas) + (account_infos, cpi_metas) } else { use light_sdk::cpi::v1::CpiAccounts; let cpi_accounts = @@ -139,15 +139,19 @@ pub mod system_cpi_test { let config = CpiInstructionConfig::try_from(&cpi_accounts) .map_err(|_| ErrorCode::AccountNotEnoughKeys)?; let account_metas = get_account_metas_from_config(config); - (account_infos, account_metas) - }; - let instruction = Instruction { - program_id: LIGHT_SYSTEM_PROGRAM_ID.into(), - accounts: account_metas, - data: inputs, + let cpi_metas: Vec = account_metas + .into_iter() + .map(|m| CpiMeta { + pubkey: m.pubkey.to_bytes(), + is_signer: m.is_signer, + is_writable: m.is_writable, + }) + .collect(); + (account_infos, cpi_metas) }; let cpi_config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); - invoke_light_system_program(&account_infos, instruction, cpi_config.bump())?; + invoke_light_system_program(&account_infos, &cpi_metas, &inputs, cpi_config.bump()) + .map_err(|_| anchor_lang::error::ErrorCode::AccountNotEnoughKeys)?; Ok(()) } diff --git a/sdk-libs/client/src/interface/initialize_config.rs b/sdk-libs/client/src/interface/initialize_config.rs index a9145bf828..7b038dc4eb 100644 --- a/sdk-libs/client/src/interface/initialize_config.rs +++ b/sdk-libs/client/src/interface/initialize_config.rs @@ -4,7 +4,6 @@ use anchor_lang::{AnchorDeserialize, AnchorSerialize}; #[cfg(not(feature = "anchor"))] use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; -use light_sdk::interface::config::LightConfig; use solana_instruction::{AccountMeta, Instruction}; use solana_pubkey::Pubkey; @@ -88,7 +87,14 @@ impl InitializeRentFreeConfig { pub fn build(self) -> (Instruction, Pubkey) { let authority = self.authority.unwrap_or(self.fee_payer); - let (config_pda, _) = LightConfig::derive_pda(&self.program_id, self.config_bump); + let config_bump_u16 = self.config_bump as u16; + let (config_pda, _) = Pubkey::find_program_address( + &[ + light_sdk::COMPRESSIBLE_CONFIG_SEED, + &config_bump_u16.to_le_bytes(), + ], + &self.program_id, + ); let accounts = vec![ AccountMeta::new(self.fee_payer, true), // payer diff --git a/sdk-libs/client/src/interface/instructions.rs b/sdk-libs/client/src/interface/instructions.rs index c6d2369b01..5113534963 100644 --- a/sdk-libs/client/src/interface/instructions.rs +++ b/sdk-libs/client/src/interface/instructions.rs @@ -5,12 +5,12 @@ use anchor_lang::{AnchorDeserialize, AnchorSerialize}; #[cfg(not(feature = "anchor"))] use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; use light_sdk::{ - compressible::{compression_info::CompressedAccountData, config::LightConfig, Pack}, instruction::{ account_meta::CompressedAccountMetaNoLamportsNoAddress, PackedAccounts, SystemAccountMetaConfig, ValidityProof, }, - PackedAccountsExt, + CompressedAccountData, InitializeLightConfigParams, Pack, PackedAccountsExt, + UpdateLightConfigParams, }; use light_token::constants::{ LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_CPI_AUTHORITY, LIGHT_TOKEN_PROGRAM_ID, @@ -30,8 +30,6 @@ fn get_output_queue(tree_info: &TreeInfo) -> Pubkey { .unwrap_or(tree_info.queue) } -use light_sdk::{InitializeLightConfigParams, UpdateLightConfigParams}; - #[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] pub struct LoadAccountsData { pub system_accounts_offset: u8, @@ -103,7 +101,14 @@ pub fn initialize_config( config_bump: Option, ) -> Instruction { let config_bump = config_bump.unwrap_or(0); - let (config_pda, _) = LightConfig::derive_pda(program_id, config_bump); + let config_bump_u16 = config_bump as u16; + let (config_pda, _) = Pubkey::find_program_address( + &[ + light_sdk::COMPRESSIBLE_CONFIG_SEED, + &config_bump_u16.to_le_bytes(), + ], + program_id, + ); let bpf_loader = solana_pubkey::pubkey!("BPFLoaderUpgradeab1e11111111111111111111111"); let (program_data_pda, _) = Pubkey::find_program_address(&[program_id.as_ref()], &bpf_loader); @@ -118,11 +123,11 @@ pub fn initialize_config( ]; let params = InitializeLightConfigParams { - rent_sponsor, - compression_authority: *authority, + rent_sponsor: rent_sponsor.to_bytes(), + compression_authority: authority.to_bytes(), rent_config: Default::default(), write_top_up: 0, - address_space, + address_space: address_space.iter().map(|p| p.to_bytes()).collect(), config_bump, }; // Serialize params, then wrap as Vec for Anchor's borsh deserialization @@ -147,7 +152,10 @@ pub fn update_config( new_address_space: Option>, new_update_authority: Option, ) -> Instruction { - let (config_pda, _) = LightConfig::derive_pda(program_id, 0); + let (config_pda, _) = Pubkey::find_program_address( + &[light_sdk::COMPRESSIBLE_CONFIG_SEED, &0u16.to_le_bytes()], + program_id, + ); let accounts = vec![ AccountMeta::new(config_pda, false), @@ -155,12 +163,12 @@ pub fn update_config( ]; let params = UpdateLightConfigParams { - new_update_authority, - new_rent_sponsor, + new_update_authority: new_update_authority.map(|p| p.to_bytes()), + new_rent_sponsor: new_rent_sponsor.map(|p| p.to_bytes()), new_compression_authority: None, new_rent_config: None, new_write_top_up: None, - new_address_space, + new_address_space: new_address_space.map(|v| v.iter().map(|p| p.to_bytes()).collect()), }; // Serialize params, then wrap as Vec for Anchor's borsh deserialization let params_bytes: Vec = params.try_to_vec().expect("serialize params"); @@ -187,7 +195,7 @@ pub fn create_decompress_accounts_idempotent_instruction( proof: ValidityProofWithContext, ) -> Result> where - T: Pack + Clone + std::fmt::Debug, + T: Pack + Clone + std::fmt::Debug, { if cold_accounts.is_empty() { return Err("cold_accounts cannot be empty".into()); diff --git a/sdk-libs/client/src/interface/light_program_interface.rs b/sdk-libs/client/src/interface/light_program_interface.rs index 3817140a23..9abe63a033 100644 --- a/sdk-libs/client/src/interface/light_program_interface.rs +++ b/sdk-libs/client/src/interface/light_program_interface.rs @@ -233,7 +233,7 @@ pub fn all_hot(specs: &[AccountSpec]) -> bool { /// Trait for programs to give clients a unified API to load cold program accounts. pub trait LightProgramInterface: Sized { /// The program's interface account variant enum. - type Variant: Pack + Clone + Debug; + type Variant: Pack + Clone + Debug; /// Program-specific instruction enum. type Instruction; diff --git a/sdk-libs/client/src/interface/load_accounts.rs b/sdk-libs/client/src/interface/load_accounts.rs index fbf4684d58..8aad58b249 100644 --- a/sdk-libs/client/src/interface/load_accounts.rs +++ b/sdk-libs/client/src/interface/load_accounts.rs @@ -81,7 +81,7 @@ pub async fn create_load_instructions( indexer: &I, ) -> Result, LoadAccountsError> where - V: Pack + Clone + std::fmt::Debug, + V: Pack + Clone + std::fmt::Debug, I: Indexer, { if !super::light_program_interface::any_cold(specs) { @@ -239,7 +239,7 @@ fn build_pda_load( compression_config: Pubkey, ) -> Result where - V: Pack + Clone + std::fmt::Debug, + V: Pack + Clone + std::fmt::Debug, { let has_tokens = specs.iter().any(|s| { s.compressed() diff --git a/sdk-libs/client/src/interface/mod.rs b/sdk-libs/client/src/interface/mod.rs index b8847c6e98..4c08394609 100644 --- a/sdk-libs/client/src/interface/mod.rs +++ b/sdk-libs/client/src/interface/mod.rs @@ -26,7 +26,7 @@ pub use light_program_interface::{ all_hot, any_cold, discriminator, matches_discriminator, AccountSpec, AccountToFetch, ColdContext, LightProgramInterface, PdaSpec, }; -pub use light_sdk::interface::config::LightConfig; +pub use light_sdk::LightConfig; pub use light_token::compat::TokenData; pub use load_accounts::{create_load_instructions, LoadAccountsError}; pub use pack::{pack_proof, PackError, PackedProofResult}; diff --git a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs index efd7c0b965..047a6d1533 100644 --- a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs +++ b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs @@ -1,12 +1,12 @@ use light_program_profiler::profile; // PackedAccounts and AccountMetasVec are only available off-chain (client-side) #[cfg(not(target_os = "solana"))] +use light_sdk::interface::error::LightPdaError; +#[cfg(not(target_os = "solana"))] use light_sdk::{ instruction::{AccountMetasVec, PackedAccounts, SystemAccountMetaConfig}, PackedAccountsExt, }; -#[cfg(not(target_os = "solana"))] -use solana_program_error::ProgramError as PackedAccountsError; use light_token_interface::instructions::transfer2::CompressedCpiContext; #[cfg(not(target_os = "solana"))] use light_token_interface::state::Token; @@ -49,8 +49,10 @@ pub fn pack_for_compress_and_close( ) -> Result { let (ctoken_account, _) = Token::zero_copy_at(ctoken_account_data)?; let source_index = packed_accounts.insert_or_get(ctoken_account_pubkey); - let mint_index = packed_accounts.insert_or_get(Pubkey::from(ctoken_account.mint.to_bytes())); - let owner_index = packed_accounts.insert_or_get(Pubkey::from(ctoken_account.owner.to_bytes())); + let mint_index = + packed_accounts.insert_or_get(Pubkey::from(ctoken_account.mint.to_bytes())); + let owner_index = + packed_accounts.insert_or_get(Pubkey::from(ctoken_account.owner.to_bytes())); // Get compression info from Compressible extension let compressible_ext = ctoken_account @@ -400,14 +402,11 @@ impl CompressAndCloseAccounts { } #[cfg(not(target_os = "solana"))] -impl AccountMetasVec for CompressAndCloseAccounts { +impl AccountMetasVec for CompressAndCloseAccounts { /// Adds: /// 1. system accounts if not set /// 2. compressed token program and ctoken cpi authority pda to pre accounts - fn get_account_metas_vec( - &self, - accounts: &mut PackedAccounts, - ) -> Result<(), PackedAccountsError> { + fn get_account_metas_vec(&self, accounts: &mut PackedAccounts) -> Result<(), LightPdaError> { if !accounts.system_accounts_set() { let mut config = SystemAccountMetaConfig::default(); config.self_program = self.self_program; @@ -419,10 +418,12 @@ impl AccountMetasVec for CompressAndCloseAccounts { { if self.cpi_context.is_some() { msg!("Error: cpi_context is set but 'cpi-context' feature is not enabled"); - return Err(PackedAccountsError::InvalidArgument); + return Err(LightPdaError::InvalidInstructionData); } } - accounts.add_system_accounts_v2(config).map_err(PackedAccountsError::from)?; + accounts + .add_system_accounts_v2(config) + .map_err(LightPdaError::from)?; } // Add both accounts in one operation for better performance accounts.pre_accounts.extend_from_slice(&[ diff --git a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs index 3e2bb25739..e25a9221c7 100644 --- a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs +++ b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs @@ -3,14 +3,13 @@ use light_compressed_account::compressed_account::PackedMerkleContext; use light_compressed_account::instruction_data::compressed_proof::ValidityProof; use light_program_profiler::profile; use light_sdk::{instruction::PackedStateTreeInfo, Unpack}; +use light_sdk::interface::error::LightPdaError; // Pack and PackedAccounts only available off-chain (client-side) #[cfg(not(target_os = "solana"))] use light_sdk::{ instruction::{AccountMetasVec, PackedAccounts, SystemAccountMetaConfig}, Pack, PackedAccountsExt, }; -#[cfg(not(target_os = "solana"))] -use solana_program_error::ProgramError as PackedAccountsError; use light_token_interface::instructions::{ extensions::ExtensionInstructionData, transfer2::{CompressedCpiContext, MultiInputTokenDataWithContext}, @@ -59,13 +58,13 @@ pub struct DecompressFullInput { } #[cfg(not(target_os = "solana"))] -impl Pack for DecompressFullInput { +impl Pack for DecompressFullInput { type Packed = DecompressFullIndices; fn pack( &self, remaining_accounts: &mut PackedAccounts, - ) -> Result { + ) -> Result { let owner_is_signer = !self.is_ata; let source = MultiInputTokenDataWithContext { @@ -101,26 +100,26 @@ impl Pack for DecompressFullInput { } } -impl Unpack for DecompressFullIndices { +impl<'a> Unpack> for DecompressFullIndices { type Unpacked = DecompressFullInput; fn unpack( &self, - remaining_accounts: &[AccountInfo], - ) -> Result { + remaining_accounts: &[AccountInfo<'a>], + ) -> Result { let owner = *remaining_accounts .get(self.source.owner as usize) - .ok_or(solana_program_error::ProgramError::InvalidAccountData)? + .ok_or(LightPdaError::InvalidInstructionData)? .key; let mint = *remaining_accounts .get(self.source.mint as usize) - .ok_or(solana_program_error::ProgramError::InvalidAccountData)? + .ok_or(LightPdaError::InvalidInstructionData)? .key; let delegate = if self.source.has_delegate { Some( *remaining_accounts .get(self.source.delegate as usize) - .ok_or(solana_program_error::ProgramError::InvalidAccountData)? + .ok_or(LightPdaError::InvalidInstructionData)? .key, ) } else { @@ -128,7 +127,7 @@ impl Unpack for DecompressFullIndices { }; let destination = *remaining_accounts .get(self.destination_index as usize) - .ok_or(solana_program_error::ProgramError::InvalidAccountData)? + .ok_or(LightPdaError::InvalidInstructionData)? .key; Ok(DecompressFullInput { @@ -350,14 +349,14 @@ impl DecompressFullAccounts { } #[cfg(not(target_os = "solana"))] -impl AccountMetasVec for DecompressFullAccounts { +impl AccountMetasVec for DecompressFullAccounts { /// Adds: /// 1. system accounts if not set /// 2. compressed token program and ctoken cpi authority pda to pre accounts fn get_account_metas_vec( &self, accounts: &mut PackedAccounts, - ) -> Result<(), PackedAccountsError> { + ) -> Result<(), LightPdaError> { if !accounts.system_accounts_set() { #[cfg(feature = "cpi-context")] let config = { @@ -375,7 +374,7 @@ impl AccountMetasVec for DecompressFullAccounts { accounts .add_system_accounts_v2(config) - .map_err(PackedAccountsError::from)?; + .map_err(LightPdaError::from)?; } // Add both accounts in one operation for better performance accounts.pre_accounts.extend_from_slice(&[ diff --git a/sdk-libs/macros/src/light_pdas/account/derive.rs b/sdk-libs/macros/src/light_pdas/account/derive.rs index 1c24271eb4..2362b8fe3d 100644 --- a/sdk-libs/macros/src/light_pdas/account/derive.rs +++ b/sdk-libs/macros/src/light_pdas/account/derive.rs @@ -283,11 +283,12 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { #compress_as_assignments } + #[cfg(not(target_os = "solana"))] #[inline(never)] - fn pack( + fn pack( &self, - accounts: &mut light_sdk::instruction::PackedAccounts, - ) -> std::result::Result { + accounts: &mut light_sdk::interface::instruction::PackedAccounts, + ) -> std::result::Result { #pack_body } @@ -295,7 +296,7 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { fn unpack( packed: &Self::Packed, accounts: &light_sdk::light_account_checks::packed_accounts::ProgramPackedAccounts, - ) -> std::result::Result { + ) -> std::result::Result { #unpack_body } } @@ -303,25 +304,25 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { // V1 compatibility: Pack trait (delegates to LightAccount::pack) // Pack trait is only available off-chain (client-side) #[cfg(not(target_os = "solana"))] - impl light_sdk::interface::Pack for #struct_name { + impl light_sdk::interface::Pack for #struct_name { type Packed = #packed_struct_name; fn pack( &self, - remaining_accounts: &mut light_sdk::instruction::PackedAccounts, - ) -> std::result::Result { + remaining_accounts: &mut light_sdk::interface::instruction::PackedAccounts, + ) -> std::result::Result { ::pack(self, remaining_accounts) } } // V1 compatibility: Unpack trait for packed struct - impl light_sdk::interface::Unpack for #packed_struct_name { + impl light_sdk::interface::Unpack for #packed_struct_name { type Unpacked = #struct_name; fn unpack( &self, - remaining_accounts: &[solana_account_info::AccountInfo], - ) -> std::result::Result { + remaining_accounts: &[AI], + ) -> std::result::Result { // Create a ProgramPackedAccounts wrapper from remaining_accounts let accounts = light_sdk::light_account_checks::packed_accounts::ProgramPackedAccounts { accounts: remaining_accounts @@ -332,11 +333,11 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { // V1 compatibility: HasCompressionInfo trait (wraps non-Option compression_info) impl light_sdk::interface::HasCompressionInfo for #struct_name { - fn compression_info(&self) -> std::result::Result<&light_sdk::interface::CompressionInfo, solana_program_error::ProgramError> { + fn compression_info(&self) -> std::result::Result<&light_sdk::interface::CompressionInfo, light_sdk::interface::error::LightPdaError> { Ok(&self.compression_info) } - fn compression_info_mut(&mut self) -> std::result::Result<&mut light_sdk::interface::CompressionInfo, solana_program_error::ProgramError> { + fn compression_info_mut(&mut self) -> std::result::Result<&mut light_sdk::interface::CompressionInfo, light_sdk::interface::error::LightPdaError> { Ok(&mut self.compression_info) } @@ -346,7 +347,7 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { panic!("compression_info_mut_opt not supported for LightAccount types (use compression_info_mut instead)") } - fn set_compression_info_none(&mut self) -> std::result::Result<(), solana_program_error::ProgramError> { + fn set_compression_info_none(&mut self) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { // V2 types use non-Option CompressionInfo // Setting to "compressed" state is the equivalent of "None" for V1 self.compression_info = light_sdk::compressible::CompressionInfo::compressed(); @@ -468,7 +469,7 @@ fn generate_pack_body( let field_type = &field.ty; Some(if is_pubkey_type(field_type) { - quote! { #field_name: accounts.insert_or_get_read_only(self.#field_name) } + quote! { #field_name: accounts.insert_or_get_read_only(self.#field_name.to_bytes()) } } else if is_copy_type(field_type) { quote! { #field_name: self.#field_name } } else { @@ -518,7 +519,7 @@ fn generate_unpack_body( #field_name: { let account = accounts .get_u8(packed.#field_name, #error_msg) - .map_err(|_| solana_program_error::ProgramError::InvalidAccountData)?; + .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; solana_pubkey::Pubkey::from(account.key()) } } diff --git a/sdk-libs/program-test/src/compressible.rs b/sdk-libs/program-test/src/compressible.rs index adcc868a76..ccd25ab032 100644 --- a/sdk-libs/program-test/src/compressible.rs +++ b/sdk-libs/program-test/src/compressible.rs @@ -271,7 +271,13 @@ pub async fn auto_compress_program_pdas( let payer = rpc.get_payer().insecure_clone(); - let config_pda = LightConfig::derive_pda(&program_id, 0).0; + let (config_pda, _) = Pubkey::find_program_address( + &[ + light_sdk::COMPRESSIBLE_CONFIG_SEED, + &0u16.to_le_bytes(), + ], + &program_id, + ); let cfg_acc_opt = rpc.get_account(config_pda).await?; let Some(cfg_acc) = cfg_acc_opt else { @@ -279,10 +285,10 @@ pub async fn auto_compress_program_pdas( }; let cfg = LightConfig::try_from_slice(&cfg_acc.data[DISCRIMINATOR_LEN..]) .map_err(|e| RpcError::CustomError(format!("config deserialize: {e:?}")))?; - let rent_sponsor = cfg.rent_sponsor; + let rent_sponsor = Pubkey::from(cfg.rent_sponsor); // compression_authority is the payer by default for auto-compress let compression_authority = payer.pubkey(); - let address_tree = cfg.address_space[0]; + let address_tree = Pubkey::from(cfg.address_space[0]); let program_accounts = rpc.context.get_program_accounts(&program_id); diff --git a/sdk-libs/sdk-interface/src/account/token_seeds.rs b/sdk-libs/sdk-interface/src/account/token_seeds.rs index 2a7b98f527..8b048cfa32 100644 --- a/sdk-libs/sdk-interface/src/account/token_seeds.rs +++ b/sdk-libs/sdk-interface/src/account/token_seeds.rs @@ -136,7 +136,8 @@ where ) -> Result { let seeds = self.seeds.pack(remaining_accounts)?; - let owner_index = remaining_accounts.insert_or_get(self.token_data.owner.to_bytes()); + let owner_index = remaining_accounts + .insert_or_get(AM::pubkey_from_bytes(self.token_data.owner.to_bytes())); let token_data = PackedTokenData { owner: owner_index, @@ -145,9 +146,12 @@ where delegate: self .token_data .delegate - .map(|d| remaining_accounts.insert_or_get(d.to_bytes())) + .map(|d| { + remaining_accounts.insert_or_get(AM::pubkey_from_bytes(d.to_bytes())) + }) .unwrap_or(0), - mint: remaining_accounts.insert_or_get(self.token_data.mint.to_bytes()), + mint: remaining_accounts + .insert_or_get(AM::pubkey_from_bytes(self.token_data.mint.to_bytes())), version: TokenDataVersion::ShaFlat as u8, }; diff --git a/sdk-libs/sdk-interface/src/instruction/pack_accounts.rs b/sdk-libs/sdk-interface/src/instruction/pack_accounts.rs index 381d70a67b..9ddd5ced93 100644 --- a/sdk-libs/sdk-interface/src/instruction/pack_accounts.rs +++ b/sdk-libs/sdk-interface/src/instruction/pack_accounts.rs @@ -51,11 +51,11 @@ impl PackedAccounts { self.system_accounts_set } - pub fn add_pre_accounts_signer(&mut self, pubkey: [u8; 32]) { + pub fn add_pre_accounts_signer(&mut self, pubkey: AM::Pubkey) { self.pre_accounts.push(AM::new(pubkey, true, false)); } - pub fn add_pre_accounts_signer_mut(&mut self, pubkey: [u8; 32]) { + pub fn add_pre_accounts_signer_mut(&mut self, pubkey: AM::Pubkey) { self.pre_accounts.push(AM::new(pubkey, true, true)); } @@ -79,21 +79,22 @@ impl PackedAccounts { /// /// If the provided `pubkey` already exists in the collection, its already /// existing index is returned. - pub fn insert_or_get(&mut self, pubkey: [u8; 32]) -> u8 { + pub fn insert_or_get(&mut self, pubkey: AM::Pubkey) -> u8 { self.insert_or_get_config(pubkey, false, true) } - pub fn insert_or_get_read_only(&mut self, pubkey: [u8; 32]) -> u8 { + pub fn insert_or_get_read_only(&mut self, pubkey: AM::Pubkey) -> u8 { self.insert_or_get_config(pubkey, false, false) } pub fn insert_or_get_config( &mut self, - pubkey: [u8; 32], + pubkey: AM::Pubkey, is_signer: bool, is_writable: bool, ) -> u8 { - match self.map.get_mut(&pubkey) { + let bytes = AM::pubkey_to_bytes(pubkey); + match self.map.get_mut(&bytes) { Some((index, entry)) => { if !entry.is_writable() { entry.set_is_writable(is_writable); @@ -107,7 +108,7 @@ impl PackedAccounts { let index = self.next_index; self.next_index += 1; self.map - .insert(pubkey, (index, AM::new(pubkey, is_signer, is_writable))); + .insert(bytes, (index, AM::new(pubkey, is_signer, is_writable))); index } } diff --git a/sdk-libs/sdk/Cargo.toml b/sdk-libs/sdk/Cargo.toml index 5840967686..e95561ab7d 100644 --- a/sdk-libs/sdk/Cargo.toml +++ b/sdk-libs/sdk/Cargo.toml @@ -75,7 +75,7 @@ light-concurrent-merkle-tree = { workspace = true, optional = true } light-compressible = { workspace = true } light-heap = { workspace = true, optional = true } light-token-interface = { workspace = true } # TODO: make optional -light-sdk-interface = { workspace = true, features = ["std"] } +light-sdk-interface = { workspace = true, features = ["std", "token"] } [dev-dependencies] num-bigint = { workspace = true } diff --git a/sdk-libs/sdk/src/instruction/mod.rs b/sdk-libs/sdk/src/instruction/mod.rs index fe30a0781f..cc2c77054b 100644 --- a/sdk-libs/sdk/src/instruction/mod.rs +++ b/sdk-libs/sdk/src/instruction/mod.rs @@ -40,10 +40,15 @@ // TODO: link to examples -// Re-export base instruction types from interface +// Re-export instruction types from sdk-types (available on all targets) +pub use light_sdk_types::instruction::*; + +// Re-export pack_accounts utilities from interface (off-chain only) +#[cfg(not(target_os = "solana"))] pub use light_sdk_interface::instruction::*; -// Concrete PackedAccounts type alias for solana AccountMeta +// Concrete PackedAccounts type alias for solana AccountMeta (off-chain only) +#[cfg(not(target_os = "solana"))] pub type PackedAccounts = light_sdk_interface::instruction::PackedAccounts; diff --git a/sdk-libs/sdk/src/instruction/tree_info.rs b/sdk-libs/sdk/src/instruction/tree_info.rs index 51bae81187..cd91c585e0 100644 --- a/sdk-libs/sdk/src/instruction/tree_info.rs +++ b/sdk-libs/sdk/src/instruction/tree_info.rs @@ -1,6 +1,7 @@ pub use light_compressed_account::compressed_account::{MerkleContext, PackedMerkleContext}; use light_sdk_types::instruction::PackedAddressTreeInfo; +#[cfg(not(target_os = "solana"))] use super::PackedAccounts; use crate::{AccountInfo, AnchorDeserialize, AnchorSerialize, Pubkey}; @@ -10,6 +11,7 @@ pub struct AddressTreeInfo { pub queue: Pubkey, } +#[cfg(not(target_os = "solana"))] #[deprecated(since = "0.13.0", note = "please use PackedStateTreeInfo")] pub fn pack_merkle_contexts<'a, I>( merkle_contexts: I, @@ -22,6 +24,7 @@ where merkle_contexts.map(|x| pack_merkle_context(x, remaining_accounts)) } +#[cfg(not(target_os = "solana"))] #[deprecated(since = "0.13.0", note = "please use PackedStateTreeInfo")] pub fn pack_merkle_context( merkle_context: &MerkleContext, @@ -48,6 +51,7 @@ pub fn pack_merkle_context( /// Returns an iterator of [`PackedAddressTreeInfo`] and fills up /// `remaining_accounts` based on the given `merkle_contexts`. +#[cfg(not(target_os = "solana"))] pub fn pack_address_tree_infos<'a>( address_tree_infos: &'a [AddressTreeInfo], root_index: &'a [u16], @@ -63,14 +67,15 @@ pub fn pack_address_tree_infos<'a>( /// based on the given `merkle_context`. /// Packs Merkle tree account first. /// Packs queue account second. +#[cfg(not(target_os = "solana"))] pub fn pack_address_tree_info( address_tree_info: &AddressTreeInfo, remaining_accounts: &mut PackedAccounts, root_index: u16, ) -> PackedAddressTreeInfo { let AddressTreeInfo { tree, queue } = address_tree_info; - let address_merkle_tree_pubkey_index = remaining_accounts.insert_or_get(tree.to_bytes()); - let address_queue_pubkey_index = remaining_accounts.insert_or_get(queue.to_bytes()); + let address_merkle_tree_pubkey_index = remaining_accounts.insert_or_get(*tree); + let address_queue_pubkey_index = remaining_accounts.insert_or_get(*queue); PackedAddressTreeInfo { address_merkle_tree_pubkey_index, diff --git a/sdk-libs/sdk/src/lib.rs b/sdk-libs/sdk/src/lib.rs index ac3962d6b3..043b941e54 100644 --- a/sdk-libs/sdk/src/lib.rs +++ b/sdk-libs/sdk/src/lib.rs @@ -208,11 +208,12 @@ use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSeria // Pack trait is only available off-chain (client-side) - uses PackedAccounts #[cfg(not(target_os = "solana"))] pub use light_sdk_interface::Pack; +pub use light_sdk_interface::Unpack; pub use light_sdk_interface::{ process_initialize_light_config_checked, InitializeLightConfigParams, - process_update_light_config, UpdateLightConfigParams, CompressAs, CompressedInitSpace, - CompressionInfo, + process_update_light_config, UpdateLightConfigParams, CompressAs, CompressedAccountData, + CompressedInitSpace, CompressionInfo, HasCompressionInfo, LightConfig, Space, COMPRESSIBLE_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE, }; diff --git a/sdk-libs/token-sdk/src/compressible/compress_runtime.rs b/sdk-libs/token-sdk/src/compressible/compress_runtime.rs index 11f29e8c41..e44b7a4d73 100644 --- a/sdk-libs/token-sdk/src/compressible/compress_runtime.rs +++ b/sdk-libs/token-sdk/src/compressible/compress_runtime.rs @@ -1,15 +1,11 @@ //! Runtime helpers for compressing PDAs to Light Protocol. use light_compressed_account::instruction_data::{ - data::NewAddressParamsAssignedPacked, with_account_info::CompressedAccountInfo, -}; -use light_sdk::{ - cpi::{ - v2::{CpiAccounts, LightSystemProgramCpi}, - InvokeLightSystemProgram, LightCpiInstruction, - }, - instruction::ValidityProof, + cpi_context::CompressedCpiContext, + data::NewAddressParamsAssignedPacked, + with_account_info::{CompressedAccountInfo, InstructionDataInvokeCpiWithAccountInfo}, }; +use light_sdk::cpi::{v2::CpiAccounts, InvokeLightSystemProgram}; use light_sdk_types::CpiSigner; use solana_program_error::ProgramError; @@ -28,28 +24,41 @@ use crate::error::LightTokenError; /// * `cpi_accounts` - CPI accounts with CPI context enabled pub fn invoke_write_pdas_to_cpi_context<'info>( cpi_signer: CpiSigner, - proof: ValidityProof, + proof: light_sdk::instruction::ValidityProof, new_addresses: &[NewAddressParamsAssignedPacked], compressed_infos: &[CompressedAccountInfo], cpi_accounts: &CpiAccounts<'_, 'info>, ) -> Result<(), ProgramError> { - let cpi_context_account = cpi_accounts - .cpi_context() - .map_err(|_| LightTokenError::MissingCpiContext)?; let cpi_context_accounts = light_sdk_types::cpi_context_write::CpiContextWriteAccounts { fee_payer: cpi_accounts.fee_payer(), authority: cpi_accounts .authority() .map_err(|_| LightTokenError::MissingCpiAuthority)?, - cpi_context: cpi_context_account, + cpi_context: cpi_accounts + .cpi_context() + .map_err(|_| LightTokenError::MissingCpiContext)?, cpi_signer, }; - LightSystemProgramCpi::new_cpi(cpi_signer, proof) - .with_new_addresses(new_addresses) - .with_account_infos(compressed_infos) - .write_to_cpi_context_first() - .invoke_write_to_cpi_context_first(cpi_context_accounts)?; + let instruction_data = InstructionDataInvokeCpiWithAccountInfo { + mode: 1, + bump: cpi_signer.bump, + invoking_program_id: cpi_signer.program_id.into(), + compress_or_decompress_lamports: 0, + is_compress: false, + with_cpi_context: true, + with_transaction_hash: false, + cpi_context: CompressedCpiContext::first(), + proof: proof.0, + new_address_params: new_addresses.to_vec(), + account_infos: compressed_infos.to_vec(), + read_only_addresses: vec![], + read_only_accounts: vec![], + }; + + instruction_data + .invoke_write_to_cpi_context_first(cpi_context_accounts) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; Ok(()) } diff --git a/sdk-tests/manual-test/Cargo.toml b/sdk-tests/manual-test/Cargo.toml index 424af4861d..42a018275a 100644 --- a/sdk-tests/manual-test/Cargo.toml +++ b/sdk-tests/manual-test/Cargo.toml @@ -32,6 +32,7 @@ light-compressible = { workspace = true, features = ["anchor"] } light-hasher = { workspace = true, features = ["solana"] } light-token = { workspace = true, features = ["anchor"] } light-token-types = { workspace = true, features = ["anchor"] } +light-token-interface = { workspace = true } solana-program = { workspace = true } solana-pubkey = { workspace = true } solana-msg = { workspace = true } @@ -44,7 +45,6 @@ light-client = { workspace = true, features = ["v2", "anchor"] } light-test-utils = { workspace = true } light-token = { workspace = true } light-token-client = { workspace = true } -light-token-interface = { workspace = true } tokio = { workspace = true } solana-sdk = { workspace = true } solana-instruction = { workspace = true } diff --git a/sdk-tests/manual-test/src/account_loader/derived_accounts.rs b/sdk-tests/manual-test/src/account_loader/derived_accounts.rs index 02c8f2c903..498f5a7489 100644 --- a/sdk-tests/manual-test/src/account_loader/derived_accounts.rs +++ b/sdk-tests/manual-test/src/account_loader/derived_accounts.rs @@ -10,15 +10,14 @@ use light_compressed_account::instruction_data::{ use light_sdk::{ cpi::{v2::CpiAccounts, CpiAccountsConfig, InvokeLightSystemProgram}, error::LightSdkError, - instruction::{PackedAccounts, PackedAddressTreeInfoExt}, + instruction::PackedAddressTreeInfoExt, interface::{ prepare_compressed_account_on_init, LightAccount, LightAccountVariantTrait, LightFinalize, LightPreInit, PackedLightAccountVariantTrait, }, - light_account_checks::packed_accounts::ProgramPackedAccounts, + light_account_checks::{self, packed_accounts::ProgramPackedAccounts}, sdk_types::CpiContextWriteAccounts, }; -use solana_program_error::ProgramError; use super::{ accounts::{CreateZeroCopy, CreateZeroCopyParams}, @@ -42,113 +41,118 @@ const _: () = { // Manual LightPreInit Implementation // ============================================================================ -impl<'info> LightPreInit<'info, CreateZeroCopyParams> for CreateZeroCopy<'info> { +impl<'info> LightPreInit, CreateZeroCopyParams> for CreateZeroCopy<'info> { fn light_pre_init( &mut self, remaining_accounts: &[AccountInfo<'info>], params: &CreateZeroCopyParams, - ) -> std::result::Result { - use light_sdk::interface::{config::LightConfig, LightAccount}; - use solana_program::{clock::Clock, sysvar::Sysvar}; - use solana_program_error::ProgramError; - - // 1. Build CPI accounts (slice remaining_accounts at system_accounts_offset) - let system_accounts_offset = params.create_accounts_proof.system_accounts_offset as usize; - if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkError::FewerAccountsThanSystemAccounts); - } - let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - &self.fee_payer, - &remaining_accounts[system_accounts_offset..], - config, - ); - - // 2. Get address tree pubkey from packed tree info - let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; - let address_tree_pubkey = address_tree_info - .get_tree_pubkey(&cpi_accounts) - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; - let output_tree_index = params.create_accounts_proof.output_state_tree_index; - let current_account_index: u8 = 0; - // Is true if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. - const WITH_CPI_CONTEXT: bool = false; - // Is first if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. - let cpi_context = if WITH_CPI_CONTEXT { - CompressedCpiContext::first() - } else { - CompressedCpiContext::default() - }; - const NUM_LIGHT_PDAS: usize = 1; - let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); - let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); - - // 3. Load config and get current slot - let light_config = LightConfig::load_checked(&self.compression_config, &crate::ID) - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; - let current_slot = Clock::get() - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))? - .slot; - - // 4. Prepare compressed account using helper function - // Get the record's key from AccountLoader - let record_key = self.record.key(); - prepare_compressed_account_on_init( - &record_key, - &address_tree_pubkey, - address_tree_info, - output_tree_index, - current_account_index, - &crate::ID, - &mut new_address_params, - &mut account_infos, - )?; - - // 5. Set compression_info on the zero-copy record - // For AccountLoader, we need to use load_init() which was already called by Anchor - { - let mut record = self - .record - .load_init() - .map_err(|_| LightSdkError::from(ProgramError::AccountBorrowFailed))?; - record.set_decompressed(&light_config, current_slot); - } - - // 6. Build instruction data manually (no builder pattern) - let instruction_data = InstructionDataInvokeCpiWithAccountInfo { - mode: 1, // V2 mode - bump: crate::LIGHT_CPI_SIGNER.bump, - invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), - compress_or_decompress_lamports: 0, - is_compress: false, - with_cpi_context: WITH_CPI_CONTEXT, - with_transaction_hash: false, - cpi_context, - proof: params.create_accounts_proof.proof.0, - new_address_params, - account_infos, - read_only_addresses: vec![], - read_only_accounts: vec![], - }; - if !WITH_CPI_CONTEXT { - // 7. Invoke Light System Program CPI - instruction_data - .invoke(cpi_accounts) - .map_err(LightSdkError::from)?; - } else { - // For flows that combine light mints with light PDAs, write to CPI context first. - let cpi_context_accounts = CpiContextWriteAccounts { - fee_payer: cpi_accounts.fee_payer(), - authority: cpi_accounts.authority().map_err(LightSdkError::from)?, - cpi_context: cpi_accounts.cpi_context().map_err(LightSdkError::from)?, - cpi_signer: crate::LIGHT_CPI_SIGNER, + ) -> std::result::Result { + let inner = || -> std::result::Result { + use light_sdk::interface::{LightAccount, LightConfig}; + use solana_program::{clock::Clock, sysvar::Sysvar}; + use solana_program_error::ProgramError; + + // 1. Build CPI accounts (slice remaining_accounts at system_accounts_offset) + let system_accounts_offset = + params.create_accounts_proof.system_accounts_offset as usize; + if remaining_accounts.len() < system_accounts_offset { + return Err(LightSdkError::FewerAccountsThanSystemAccounts); + } + let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); + let cpi_accounts = CpiAccounts::new_with_config( + &self.fee_payer, + &remaining_accounts[system_accounts_offset..], + config, + ); + + // 2. Get address tree pubkey from packed tree info + let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; + let address_tree_pubkey = address_tree_info + .get_tree_pubkey(&cpi_accounts) + .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; + let output_tree_index = params.create_accounts_proof.output_state_tree_index; + let current_account_index: u8 = 0; + // Is true if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. + const WITH_CPI_CONTEXT: bool = false; + // Is first if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. + let cpi_context = if WITH_CPI_CONTEXT { + CompressedCpiContext::first() + } else { + CompressedCpiContext::default() }; - instruction_data - .invoke_write_to_cpi_context_first(cpi_context_accounts) - .map_err(LightSdkError::from)?; - } - - Ok(false) // No mints, so no CPI context write + const NUM_LIGHT_PDAS: usize = 1; + let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); + let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); + + // 3. Load config and get current slot + let light_config = + LightConfig::load_checked(&self.compression_config, &crate::ID.to_bytes()) + .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; + let current_slot = Clock::get() + .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))? + .slot; + + // 4. Prepare compressed account using helper function + // Get the record's key from AccountLoader + let record_key = self.record.key(); + prepare_compressed_account_on_init( + &record_key.to_bytes(), + &address_tree_pubkey.to_bytes(), + address_tree_info, + output_tree_index, + current_account_index, + &crate::ID.to_bytes(), + &mut new_address_params, + &mut account_infos, + )?; + + // 5. Set compression_info on the zero-copy record + // For AccountLoader, we need to use load_init() which was already called by Anchor + { + let mut record = self + .record + .load_init() + .map_err(|_| LightSdkError::from(ProgramError::AccountBorrowFailed))?; + record.set_decompressed(&light_config, current_slot); + } + + // 6. Build instruction data manually (no builder pattern) + let instruction_data = InstructionDataInvokeCpiWithAccountInfo { + mode: 1, // V2 mode + bump: crate::LIGHT_CPI_SIGNER.bump, + invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), + compress_or_decompress_lamports: 0, + is_compress: false, + with_cpi_context: WITH_CPI_CONTEXT, + with_transaction_hash: false, + cpi_context, + proof: params.create_accounts_proof.proof.0, + new_address_params, + account_infos, + read_only_addresses: vec![], + read_only_accounts: vec![], + }; + if !WITH_CPI_CONTEXT { + // 7. Invoke Light System Program CPI + instruction_data + .invoke(cpi_accounts) + .map_err(LightSdkError::from)?; + } else { + // For flows that combine light mints with light PDAs, write to CPI context first. + let cpi_context_accounts = CpiContextWriteAccounts { + fee_payer: cpi_accounts.fee_payer(), + authority: cpi_accounts.authority().map_err(LightSdkError::from)?, + cpi_context: cpi_accounts.cpi_context().map_err(LightSdkError::from)?, + cpi_signer: crate::LIGHT_CPI_SIGNER, + }; + instruction_data + .invoke_write_to_cpi_context_first(cpi_context_accounts) + .map_err(LightSdkError::from)?; + } + + Ok(false) // No mints, so no CPI context write + }; + inner().map_err(Into::into) } } @@ -156,13 +160,13 @@ impl<'info> LightPreInit<'info, CreateZeroCopyParams> for CreateZeroCopy<'info> // Manual LightFinalize Implementation (no-op for PDA-only flow) // ============================================================================ -impl<'info> LightFinalize<'info, CreateZeroCopyParams> for CreateZeroCopy<'info> { +impl<'info> LightFinalize, CreateZeroCopyParams> for CreateZeroCopy<'info> { fn light_finalize( &mut self, _remaining_accounts: &[AccountInfo<'info>], _params: &CreateZeroCopyParams, _has_pre_init: bool, - ) -> std::result::Result<(), LightSdkError> { + ) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { // No-op for PDA-only flow - compression CPI already executed in light_pre_init Ok(()) } @@ -213,7 +217,7 @@ pub struct PackedZeroCopyRecordVariant { // ============================================================================ impl LightAccountVariantTrait<4> for ZeroCopyRecordVariant { - const PROGRAM_ID: Pubkey = crate::ID; + const PROGRAM_ID: [u8; 32] = crate::ID.to_bytes(); type Seeds = ZeroCopyRecordSeeds; type Data = ZeroCopyRecord; @@ -259,52 +263,53 @@ impl PackedLightAccountVariantTrait<4> for PackedZeroCopyRecordVariant { self.seeds.bump } - fn unpack(&self, accounts: &[AccountInfo]) -> Result { + fn unpack( + &self, + accounts: &[AI], + ) -> std::result::Result { let owner = accounts .get(self.seeds.owner_idx as usize) - .ok_or(anchor_lang::error::ErrorCode::AccountNotEnoughKeys)?; + .ok_or(light_sdk::interface::error::LightPdaError::NotEnoughAccountKeys)?; // Build ProgramPackedAccounts for LightAccount::unpack let packed_accounts = ProgramPackedAccounts { accounts }; let data = ZeroCopyRecord::unpack(&self.data, &packed_accounts) - .map_err(|_| anchor_lang::error::ErrorCode::InvalidProgramId)?; + .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; Ok(ZeroCopyRecordVariant { seeds: ZeroCopyRecordSeeds { - owner: *owner.key, + owner: Pubkey::from(owner.key()), name: self.seeds.name.clone(), }, data, }) } - fn seed_refs_with_bump<'a>( + fn seed_refs_with_bump<'a, AI: light_account_checks::AccountInfoTrait>( &'a self, - accounts: &'a [AccountInfo], - bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; 4], ProgramError> { - let owner = accounts - .get(self.seeds.owner_idx as usize) - .ok_or(ProgramError::InvalidAccountData)?; - Ok([ - b"zero_copy", - owner.key.as_ref(), - self.seeds.name.as_bytes(), - bump_storage, - ]) + _accounts: &'a [AI], + _bump_storage: &'a [u8; 1], + ) -> std::result::Result<[&'a [u8]; 4], light_sdk::interface::error::LightPdaError> { + Err(light_sdk::interface::error::LightPdaError::InvalidSeeds) } fn into_in_token_data( &self, _tree_info: &light_sdk::instruction::PackedStateTreeInfo, _output_queue_index: u8, - ) -> Result { - Err(ProgramError::InvalidAccountData.into()) + ) -> std::result::Result< + light_token_interface::instructions::transfer2::MultiInputTokenDataWithContext, + light_sdk::interface::error::LightPdaError, + > { + Err(light_sdk::interface::error::LightPdaError::InvalidInstructionData) } fn into_in_tlv( &self, - ) -> Result>> { + ) -> std::result::Result< + Option>, + light_sdk::interface::error::LightPdaError, + > { Ok(None) } } @@ -320,15 +325,16 @@ impl light_sdk::interface::IntoVariant for ZeroCopyRecord fn into_variant( self, data: &[u8], - ) -> std::result::Result { + ) -> std::result::Result + { // For ZeroCopy (Pod) accounts, data is the full Pod bytes including compression_info. // We deserialize using AnchorDeserialize (which ZeroCopyRecord implements). let record: ZeroCopyRecord = AnchorDeserialize::deserialize(&mut &data[..]) - .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize)?; + .map_err(|_| light_sdk::interface::error::LightPdaError::Borsh)?; // Verify the owner in data matches the seed if Pubkey::new_from_array(record.owner) != self.owner { - return Err(anchor_lang::error::ErrorCode::ConstraintSeeds.into()); + return Err(light_sdk::interface::error::LightPdaError::InvalidSeeds); } Ok(ZeroCopyRecordVariant { @@ -345,19 +351,21 @@ impl light_sdk::interface::IntoVariant for ZeroCopyRecord /// Implement Pack trait to allow ZeroCopyRecordVariant to be used with `create_load_instructions`. /// Transforms the variant into PackedLightAccountVariant for efficient serialization. #[cfg(not(target_os = "solana"))] -impl light_sdk::compressible::Pack for ZeroCopyRecordVariant { +impl light_sdk::interface::Pack + for ZeroCopyRecordVariant +{ type Packed = crate::derived_variants::PackedLightAccountVariant; fn pack( &self, - accounts: &mut PackedAccounts, - ) -> std::result::Result { + accounts: &mut light_sdk::instruction::PackedAccounts, + ) -> std::result::Result { use light_sdk::interface::LightAccountVariantTrait; - let (_, bump) = self.derive_pda(); + let (_, bump) = self.derive_pda::(); let packed_data = self .data .pack(accounts) - .map_err(|_| ProgramError::InvalidAccountData)?; + .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; Ok( crate::derived_variants::PackedLightAccountVariant::ZeroCopyRecord { seeds: PackedZeroCopyRecordSeeds { diff --git a/sdk-tests/manual-test/src/account_loader/derived_state.rs b/sdk-tests/manual-test/src/account_loader/derived_state.rs index 1a58462e73..9f1b6964fa 100644 --- a/sdk-tests/manual-test/src/account_loader/derived_state.rs +++ b/sdk-tests/manual-test/src/account_loader/derived_state.rs @@ -6,11 +6,10 @@ use anchor_lang::prelude::*; use light_sdk::{ compressible::CompressionInfo, - instruction::PackedAccounts, - interface::{AccountType, LightAccount, LightConfig}, - light_account_checks::{packed_accounts::ProgramPackedAccounts, AccountInfoTrait}, + interface::{AccountType, HasCompressionInfo, LightAccount, LightConfig}, + light_account_checks::{packed_accounts::ProgramPackedAccounts, AccountInfoTrait, AccountMetaTrait}, }; -use solana_program_error::ProgramError; +use light_sdk::interface::error::LightPdaError; use super::state::ZeroCopyRecord; @@ -52,13 +51,14 @@ impl LightAccount for ZeroCopyRecord { self.compression_info = CompressionInfo::new_from_config(config, current_slot); } - fn pack( + #[cfg(not(target_os = "solana"))] + fn pack( &self, - accounts: &mut PackedAccounts, - ) -> std::result::Result { + accounts: &mut light_sdk::interface::instruction::PackedAccounts, + ) -> std::result::Result { // compression_info excluded from packed struct (same as Borsh accounts) Ok(PackedZeroCopyRecord { - owner: accounts.insert_or_get(Pubkey::new_from_array(self.owner)), + owner: accounts.insert_or_get(AM::pubkey_from_bytes(self.owner)), value: self.value, }) } @@ -66,11 +66,11 @@ impl LightAccount for ZeroCopyRecord { fn unpack( packed: &Self::Packed, accounts: &ProgramPackedAccounts, - ) -> std::result::Result { + ) -> std::result::Result { // Use get_u8 with a descriptive name for better error messages let owner_account = accounts .get_u8(packed.owner, "ZeroCopyRecord: owner") - .map_err(|_| ProgramError::InvalidAccountData)?; + .map_err(|_| LightPdaError::InvalidInstructionData)?; // Set compression_info to compressed() for hash verification at decompress // (Same pattern as Borsh accounts - canonical compressed state for hashing) @@ -82,3 +82,22 @@ impl LightAccount for ZeroCopyRecord { }) } } + +impl HasCompressionInfo for ZeroCopyRecord { + fn compression_info(&self) -> std::result::Result<&CompressionInfo, LightPdaError> { + Ok(&self.compression_info) + } + + fn compression_info_mut(&mut self) -> std::result::Result<&mut CompressionInfo, LightPdaError> { + Ok(&mut self.compression_info) + } + + fn compression_info_mut_opt(&mut self) -> &mut Option { + panic!("compression_info_mut_opt not supported for LightAccount types (use compression_info_mut instead)") + } + + fn set_compression_info_none(&mut self) -> std::result::Result<(), LightPdaError> { + self.compression_info = CompressionInfo::compressed(); + Ok(()) + } +} diff --git a/sdk-tests/manual-test/src/all/derived.rs b/sdk-tests/manual-test/src/all/derived.rs index 2a764b9f2a..44bef31934 100644 --- a/sdk-tests/manual-test/src/all/derived.rs +++ b/sdk-tests/manual-test/src/all/derived.rs @@ -36,257 +36,264 @@ use super::accounts::{ // LightPreInit Implementation - Creates all accounts at START of instruction // ============================================================================ -impl<'info> LightPreInit<'info, CreateAllParams> for CreateAllAccounts<'info> { +impl<'info> LightPreInit, CreateAllParams> for CreateAllAccounts<'info> { fn light_pre_init( &mut self, remaining_accounts: &[AccountInfo<'info>], params: &CreateAllParams, - ) -> std::result::Result { - use light_sdk::interface::config::LightConfig; - use solana_program::{clock::Clock, sysvar::Sysvar}; + ) -> std::result::Result { + let mut inner = || -> std::result::Result { + use light_sdk::interface::LightConfig; + use solana_program::{clock::Clock, sysvar::Sysvar}; - // Constants for this instruction - const NUM_LIGHT_PDAS: usize = 2; - const NUM_LIGHT_MINTS: usize = 1; - const WITH_CPI_CONTEXT: bool = NUM_LIGHT_PDAS > 0 && NUM_LIGHT_MINTS > 0; // true + // Constants for this instruction + const NUM_LIGHT_PDAS: usize = 2; + const NUM_LIGHT_MINTS: usize = 1; + const WITH_CPI_CONTEXT: bool = NUM_LIGHT_PDAS > 0 && NUM_LIGHT_MINTS > 0; // true - // ==================================================================== - // 1. Build CPI accounts with cpi_context config - // ==================================================================== - let system_accounts_offset = params.create_accounts_proof.system_accounts_offset as usize; - if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkError::FewerAccountsThanSystemAccounts); - } - let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - &self.payer, - &remaining_accounts[system_accounts_offset..], - config, - ); + // ==================================================================== + // 1. Build CPI accounts with cpi_context config + // ==================================================================== + let system_accounts_offset = + params.create_accounts_proof.system_accounts_offset as usize; + if remaining_accounts.len() < system_accounts_offset { + return Err(LightSdkError::FewerAccountsThanSystemAccounts); + } + let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); + let cpi_accounts = CpiAccounts::new_with_config( + &self.payer, + &remaining_accounts[system_accounts_offset..], + config, + ); - // ==================================================================== - // 2. Get address tree info - // ==================================================================== - let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; - let address_tree_pubkey = address_tree_info - .get_tree_pubkey(&cpi_accounts) - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; - let output_tree_index = params.create_accounts_proof.output_state_tree_index; + // ==================================================================== + // 2. Get address tree info + // ==================================================================== + let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; + let address_tree_pubkey = address_tree_info + .get_tree_pubkey(&cpi_accounts) + .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; + let output_tree_index = params.create_accounts_proof.output_state_tree_index; - // ==================================================================== - // 3. Load config, get current slot - // ==================================================================== - let light_config = LightConfig::load_checked(&self.compression_config, &crate::ID) - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; - let current_slot = Clock::get() - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))? - .slot; + // ==================================================================== + // 3. Load config, get current slot + // ==================================================================== + let light_config = LightConfig::load_checked(&self.compression_config, &crate::ID.to_bytes()) + .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; + let current_slot = Clock::get() + .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))? + .slot; - // ==================================================================== - // 4. Create PDAs via invoke_write_to_cpi_context_first() - // ==================================================================== - { - // CPI context for PDAs - set to first() since we have mints coming after - let cpi_context = CompressedCpiContext::first(); - let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); - let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); + // ==================================================================== + // 4. Create PDAs via invoke_write_to_cpi_context_first() + // ==================================================================== + { + // CPI context for PDAs - set to first() since we have mints coming after + let cpi_context = CompressedCpiContext::first(); + let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); + let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); - // 4a. Prepare Borsh PDA (index 0) - let borsh_record_key = self.borsh_record.key(); - prepare_compressed_account_on_init( - &borsh_record_key, - &address_tree_pubkey, - address_tree_info, - output_tree_index, - 0, // assigned_account_index = 0 - &crate::ID, - &mut new_address_params, - &mut account_infos, - )?; - self.borsh_record - .set_decompressed(&light_config, current_slot); + // 4a. Prepare Borsh PDA (index 0) + let borsh_record_key = self.borsh_record.key(); + prepare_compressed_account_on_init( + &borsh_record_key.to_bytes(), + &address_tree_pubkey.to_bytes(), + address_tree_info, + output_tree_index, + 0, // assigned_account_index = 0 + &crate::ID.to_bytes(), + &mut new_address_params, + &mut account_infos, + )?; + self.borsh_record + .set_decompressed(&light_config, current_slot); - // 4b. Prepare ZeroCopy PDA (index 1) - let zero_copy_record_key = self.zero_copy_record.key(); - prepare_compressed_account_on_init( - &zero_copy_record_key, - &address_tree_pubkey, - address_tree_info, - output_tree_index, - 1, // assigned_account_index = 1 - &crate::ID, - &mut new_address_params, - &mut account_infos, - )?; - { - let mut record = self - .zero_copy_record - .load_init() - .map_err(|_| LightSdkError::from(ProgramError::AccountBorrowFailed))?; - record.set_decompressed(&light_config, current_slot); - } + // 4b. Prepare ZeroCopy PDA (index 1) + let zero_copy_record_key = self.zero_copy_record.key(); + prepare_compressed_account_on_init( + &zero_copy_record_key.to_bytes(), + &address_tree_pubkey.to_bytes(), + address_tree_info, + output_tree_index, + 1, // assigned_account_index = 1 + &crate::ID.to_bytes(), + &mut new_address_params, + &mut account_infos, + )?; + { + let mut record = self + .zero_copy_record + .load_init() + .map_err(|_| LightSdkError::from(ProgramError::AccountBorrowFailed))?; + record.set_decompressed(&light_config, current_slot); + } - // 4c. Build instruction data and write to CPI context (doesn't execute yet) - let instruction_data = InstructionDataInvokeCpiWithAccountInfo { - mode: 1, // V2 mode - bump: crate::LIGHT_CPI_SIGNER.bump, - invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), - compress_or_decompress_lamports: 0, - is_compress: false, - with_cpi_context: WITH_CPI_CONTEXT, - with_transaction_hash: false, - cpi_context, - proof: params.create_accounts_proof.proof.0, - new_address_params, - account_infos, - read_only_addresses: vec![], - read_only_accounts: vec![], - }; + // 4c. Build instruction data and write to CPI context (doesn't execute yet) + let instruction_data = InstructionDataInvokeCpiWithAccountInfo { + mode: 1, // V2 mode + bump: crate::LIGHT_CPI_SIGNER.bump, + invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), + compress_or_decompress_lamports: 0, + is_compress: false, + with_cpi_context: WITH_CPI_CONTEXT, + with_transaction_hash: false, + cpi_context, + proof: params.create_accounts_proof.proof.0, + new_address_params, + account_infos, + read_only_addresses: vec![], + read_only_accounts: vec![], + }; - // Write to CPI context first (combined execution happens with mints) - let cpi_context_accounts = CpiContextWriteAccounts { - fee_payer: cpi_accounts.fee_payer(), - authority: cpi_accounts.authority().map_err(LightSdkError::from)?, - cpi_context: cpi_accounts.cpi_context().map_err(LightSdkError::from)?, - cpi_signer: crate::LIGHT_CPI_SIGNER, - }; - instruction_data - .invoke_write_to_cpi_context_first(cpi_context_accounts) - .map_err(LightSdkError::from)?; - } + // Write to CPI context first (combined execution happens with mints) + let cpi_context_accounts = CpiContextWriteAccounts { + fee_payer: cpi_accounts.fee_payer(), + authority: cpi_accounts.authority().map_err(LightSdkError::from)?, + cpi_context: cpi_accounts.cpi_context().map_err(LightSdkError::from)?, + cpi_signer: crate::LIGHT_CPI_SIGNER, + }; + instruction_data + .invoke_write_to_cpi_context_first(cpi_context_accounts) + .map_err(LightSdkError::from)?; + } - // ==================================================================== - // 5. Create Mint via invoke_create_mints() with offset - // ==================================================================== - { - let authority = self.authority.key(); - let mint_signer_key = self.mint_signer.key(); + // ==================================================================== + // 5. Create Mint via invoke_create_mints() with offset + // ==================================================================== + { + let authority = self.authority.key(); + let mint_signer_key = self.mint_signer.key(); - // Derive mint PDA - let (mint_pda, mint_bump) = find_mint_address(&solana_pubkey::Pubkey::new_from_array( - mint_signer_key.to_bytes(), - )); + // Derive mint PDA + let (mint_pda, mint_bump) = + find_mint_address(&solana_pubkey::Pubkey::new_from_array( + mint_signer_key.to_bytes(), + )); - // Derive compression address - let compression_address = derive_mint_compressed_address( - &solana_pubkey::Pubkey::new_from_array(mint_signer_key.to_bytes()), - &solana_pubkey::Pubkey::new_from_array(address_tree_pubkey.to_bytes()), - ); + // Derive compression address + let compression_address = derive_mint_compressed_address( + &solana_pubkey::Pubkey::new_from_array(mint_signer_key.to_bytes()), + &solana_pubkey::Pubkey::new_from_array(address_tree_pubkey.to_bytes()), + ); - // Build mint signer seeds - let mint_signer_seeds: &[&[u8]] = &[ - ALL_MINT_SIGNER_SEED, - authority.as_ref(), - &[params.mint_signer_bump], - ]; + // Build mint signer seeds + let mint_signer_seeds: &[&[u8]] = &[ + ALL_MINT_SIGNER_SEED, + authority.as_ref(), + &[params.mint_signer_bump], + ]; - // Build SingleMintParams - let sdk_mints: [SingleMintParams<'_>; NUM_LIGHT_MINTS] = [SingleMintParams { - decimals: 6, // mint::decimals = 6 - address_merkle_tree_root_index: address_tree_info.root_index, - mint_authority: solana_pubkey::Pubkey::new_from_array(authority.to_bytes()), - compression_address, - mint: mint_pda, - bump: mint_bump, - freeze_authority: None, - mint_seed_pubkey: solana_pubkey::Pubkey::new_from_array(mint_signer_key.to_bytes()), - authority_seeds: None, - mint_signer_seeds: Some(mint_signer_seeds), - token_metadata: None, - }]; + // Build SingleMintParams + let sdk_mints: [SingleMintParams<'_>; NUM_LIGHT_MINTS] = [SingleMintParams { + decimals: 6, // mint::decimals = 6 + address_merkle_tree_root_index: address_tree_info.root_index, + mint_authority: solana_pubkey::Pubkey::new_from_array(authority.to_bytes()), + compression_address, + mint: mint_pda, + bump: mint_bump, + freeze_authority: None, + mint_seed_pubkey: solana_pubkey::Pubkey::new_from_array( + mint_signer_key.to_bytes(), + ), + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_seeds), + token_metadata: None, + }]; - // Get state_tree_index - let state_tree_index = params - .create_accounts_proof - .state_tree_index - .ok_or(LightSdkError::from(ProgramError::InvalidArgument))?; + // Get state_tree_index + let state_tree_index = params + .create_accounts_proof + .state_tree_index + .ok_or(LightSdkError::from(ProgramError::InvalidArgument))?; - let proof = params - .create_accounts_proof - .proof - .0 - .ok_or(LightSdkError::from(ProgramError::InvalidArgument))?; + let proof = params + .create_accounts_proof + .proof + .0 + .ok_or(LightSdkError::from(ProgramError::InvalidArgument))?; - // Build SDK params with cpi_context_offset - let sdk_params = SdkCreateMintsParams::new(&sdk_mints, proof) - .with_output_queue_index(params.create_accounts_proof.output_state_tree_index) - .with_address_tree_index(address_tree_info.address_merkle_tree_pubkey_index) - .with_state_tree_index(state_tree_index) - .with_cpi_context_offset(NUM_LIGHT_PDAS as u8); // Offset by PDA count + // Build SDK params with cpi_context_offset + let sdk_params = SdkCreateMintsParams::new(&sdk_mints, proof) + .with_output_queue_index(params.create_accounts_proof.output_state_tree_index) + .with_address_tree_index(address_tree_info.address_merkle_tree_pubkey_index) + .with_state_tree_index(state_tree_index) + .with_cpi_context_offset(NUM_LIGHT_PDAS as u8); // Offset by PDA count - // Build infra accounts - let infra = CreateMintsInfraAccounts { - fee_payer: self.payer.to_account_info(), - compressible_config: self.compressible_config.clone(), - rent_sponsor: self.rent_sponsor.clone(), - cpi_authority: self.cpi_authority.clone(), - }; + // Build infra accounts + let infra = CreateMintsInfraAccounts { + fee_payer: self.payer.to_account_info(), + compressible_config: self.compressible_config.clone(), + rent_sponsor: self.rent_sponsor.clone(), + cpi_authority: self.cpi_authority.clone(), + }; - // Build mint account arrays - let mint_seed_accounts = [self.mint_signer.to_account_info()]; - let mint_accounts = [self.mint.to_account_info()]; + // Build mint account arrays + let mint_seed_accounts = [self.mint_signer.to_account_info()]; + let mint_accounts = [self.mint.to_account_info()]; - // This executes the combined CPI (PDAs + Mint) - invoke_create_mints( - &mint_seed_accounts, - &mint_accounts, - sdk_params, - infra, - &cpi_accounts, - )?; - } + // This executes the combined CPI (PDAs + Mint) + invoke_create_mints( + &mint_seed_accounts, + &mint_accounts, + sdk_params, + infra, + &cpi_accounts, + )?; + } - // ==================================================================== - // 6. Create Token Vault via CreateTokenAccountCpi - // ==================================================================== - { - let mint_key = self.mint.key(); - let vault_seeds: &[&[u8]] = &[ - ALL_TOKEN_VAULT_SEED, - mint_key.as_ref(), - &[params.token_vault_bump], - ]; + // ==================================================================== + // 6. Create Token Vault via CreateTokenAccountCpi + // ==================================================================== + { + let mint_key = self.mint.key(); + let vault_seeds: &[&[u8]] = &[ + ALL_TOKEN_VAULT_SEED, + mint_key.as_ref(), + &[params.token_vault_bump], + ]; - CreateTokenAccountCpi { - payer: self.payer.to_account_info(), - account: self.token_vault.to_account_info(), - mint: self.mint.to_account_info(), - owner: *self.vault_owner.key, + CreateTokenAccountCpi { + payer: self.payer.to_account_info(), + account: self.token_vault.to_account_info(), + mint: self.mint.to_account_info(), + owner: *self.vault_owner.key, + } + .rent_free( + self.compressible_config.clone(), + self.rent_sponsor.clone(), + self.system_program.to_account_info(), + &crate::ID, + ) + .invoke_signed(vault_seeds)?; } - .rent_free( - self.compressible_config.clone(), - self.rent_sponsor.clone(), - self.system_program.to_account_info(), - &crate::ID, - ) - .invoke_signed(vault_seeds)?; - } - // ==================================================================== - // 7. Create ATA via CreateTokenAtaCpi - // ==================================================================== - { - let (_, ata_bump) = light_token::instruction::derive_associated_token_account( - self.ata_owner.key, - self.mint.key, - ); + // ==================================================================== + // 7. Create ATA via CreateTokenAtaCpi + // ==================================================================== + { + let (_, ata_bump) = light_token::instruction::derive_associated_token_account( + self.ata_owner.key, + self.mint.key, + ); - CreateTokenAtaCpi { - payer: self.payer.to_account_info(), - owner: self.ata_owner.clone(), - mint: self.mint.to_account_info(), - ata: self.user_ata.to_account_info(), - bump: ata_bump, + CreateTokenAtaCpi { + payer: self.payer.to_account_info(), + owner: self.ata_owner.clone(), + mint: self.mint.to_account_info(), + ata: self.user_ata.to_account_info(), + bump: ata_bump, + } + .rent_free( + self.compressible_config.clone(), + self.rent_sponsor.clone(), + self.system_program.to_account_info(), + ) + .invoke()?; } - .rent_free( - self.compressible_config.clone(), - self.rent_sponsor.clone(), - self.system_program.to_account_info(), - ) - .invoke()?; - } - Ok(WITH_CPI_CONTEXT) + Ok(WITH_CPI_CONTEXT) + }; + inner().map_err(Into::into) } } @@ -294,13 +301,13 @@ impl<'info> LightPreInit<'info, CreateAllParams> for CreateAllAccounts<'info> { // LightFinalize Implementation - No-op for this flow // ============================================================================ -impl<'info> LightFinalize<'info, CreateAllParams> for CreateAllAccounts<'info> { +impl<'info> LightFinalize, CreateAllParams> for CreateAllAccounts<'info> { fn light_finalize( &mut self, _remaining_accounts: &[AccountInfo<'info>], _params: &CreateAllParams, _has_pre_init: bool, - ) -> std::result::Result<(), LightSdkError> { + ) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { // All accounts were created in light_pre_init Ok(()) } diff --git a/sdk-tests/manual-test/src/all/derived_accounts.rs b/sdk-tests/manual-test/src/all/derived_accounts.rs index 7e69759ef6..717e0623f4 100644 --- a/sdk-tests/manual-test/src/all/derived_accounts.rs +++ b/sdk-tests/manual-test/src/all/derived_accounts.rs @@ -3,11 +3,9 @@ use anchor_lang::prelude::*; use light_sdk::{ - instruction::PackedAccounts, interface::{LightAccount, LightAccountVariantTrait, PackedLightAccountVariantTrait}, - light_account_checks::packed_accounts::ProgramPackedAccounts, + light_account_checks::{self, packed_accounts::ProgramPackedAccounts}, }; -use solana_program_error::ProgramError; use super::accounts::{ALL_BORSH_SEED, ALL_ZERO_COPY_SEED}; use crate::{ @@ -56,7 +54,7 @@ pub struct PackedAllBorshVariant { // ============================================================================ impl LightAccountVariantTrait<3> for AllBorshVariant { - const PROGRAM_ID: Pubkey = crate::ID; + const PROGRAM_ID: [u8; 32] = crate::ID.to_bytes(); type Seeds = AllBorshSeeds; type Data = MinimalRecord; @@ -96,45 +94,46 @@ impl PackedLightAccountVariantTrait<3> for PackedAllBorshVariant { self.seeds.bump } - fn unpack(&self, accounts: &[AccountInfo]) -> Result { + fn unpack( + &self, + accounts: &[AI], + ) -> std::result::Result { let owner = accounts .get(self.seeds.owner_idx as usize) - .ok_or(anchor_lang::error::ErrorCode::AccountNotEnoughKeys)?; + .ok_or(light_sdk::interface::error::LightPdaError::NotEnoughAccountKeys)?; // Build ProgramPackedAccounts for LightAccount::unpack let packed_accounts = ProgramPackedAccounts { accounts }; let data = MinimalRecord::unpack(&self.data, &packed_accounts) - .map_err(|_| anchor_lang::error::ErrorCode::InvalidProgramId)?; + .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; Ok(AllBorshVariant { - seeds: AllBorshSeeds { owner: *owner.key }, + seeds: AllBorshSeeds { + owner: Pubkey::from(owner.key()), + }, data, }) } - fn seed_refs_with_bump<'a>( + fn seed_refs_with_bump<'a, AI: light_account_checks::AccountInfoTrait>( &'a self, - accounts: &'a [AccountInfo], - bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; 3], ProgramError> { - let owner = accounts - .get(self.seeds.owner_idx as usize) - .ok_or(ProgramError::InvalidAccountData)?; - Ok([ALL_BORSH_SEED, owner.key.as_ref(), bump_storage]) + _accounts: &'a [AI], + _bump_storage: &'a [u8; 1], + ) -> std::result::Result<[&'a [u8]; 3], light_sdk::interface::error::LightPdaError> { + Err(light_sdk::interface::error::LightPdaError::InvalidSeeds) } fn into_in_token_data( &self, _tree_info: &light_sdk::instruction::PackedStateTreeInfo, _output_queue_index: u8, - ) -> anchor_lang::Result { - Err(ProgramError::InvalidAccountData.into()) + ) -> std::result::Result { + Err(light_sdk::interface::error::LightPdaError::InvalidInstructionData) } fn into_in_tlv( &self, - ) -> anchor_lang::Result>> - { + ) -> std::result::Result>, light_sdk::interface::error::LightPdaError> { Ok(None) } } @@ -180,7 +179,7 @@ pub struct PackedAllZeroCopyVariant { // ============================================================================ impl LightAccountVariantTrait<3> for AllZeroCopyVariant { - const PROGRAM_ID: Pubkey = crate::ID; + const PROGRAM_ID: [u8; 32] = crate::ID.to_bytes(); type Seeds = AllZeroCopySeeds; type Data = ZeroCopyRecord; @@ -220,45 +219,46 @@ impl PackedLightAccountVariantTrait<3> for PackedAllZeroCopyVariant { self.seeds.bump } - fn unpack(&self, accounts: &[AccountInfo]) -> Result { + fn unpack( + &self, + accounts: &[AI], + ) -> std::result::Result { let owner = accounts .get(self.seeds.owner_idx as usize) - .ok_or(anchor_lang::error::ErrorCode::AccountNotEnoughKeys)?; + .ok_or(light_sdk::interface::error::LightPdaError::NotEnoughAccountKeys)?; // Build ProgramPackedAccounts for LightAccount::unpack let packed_accounts = ProgramPackedAccounts { accounts }; let data = ZeroCopyRecord::unpack(&self.data, &packed_accounts) - .map_err(|_| anchor_lang::error::ErrorCode::InvalidProgramId)?; + .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; Ok(AllZeroCopyVariant { - seeds: AllZeroCopySeeds { owner: *owner.key }, + seeds: AllZeroCopySeeds { + owner: Pubkey::from(owner.key()), + }, data, }) } - fn seed_refs_with_bump<'a>( + fn seed_refs_with_bump<'a, AI: light_account_checks::AccountInfoTrait>( &'a self, - accounts: &'a [AccountInfo], - bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; 3], ProgramError> { - let owner = accounts - .get(self.seeds.owner_idx as usize) - .ok_or(ProgramError::InvalidAccountData)?; - Ok([ALL_ZERO_COPY_SEED, owner.key.as_ref(), bump_storage]) + _accounts: &'a [AI], + _bump_storage: &'a [u8; 1], + ) -> std::result::Result<[&'a [u8]; 3], light_sdk::interface::error::LightPdaError> { + Err(light_sdk::interface::error::LightPdaError::InvalidSeeds) } fn into_in_token_data( &self, _tree_info: &light_sdk::instruction::PackedStateTreeInfo, _output_queue_index: u8, - ) -> anchor_lang::Result { - Err(ProgramError::InvalidAccountData.into()) + ) -> std::result::Result { + Err(light_sdk::interface::error::LightPdaError::InvalidInstructionData) } fn into_in_tlv( &self, - ) -> anchor_lang::Result>> - { + ) -> std::result::Result>, light_sdk::interface::error::LightPdaError> { Ok(None) } } @@ -274,14 +274,14 @@ impl light_sdk::interface::IntoVariant for AllBorshSeeds { fn into_variant( self, data: &[u8], - ) -> std::result::Result { + ) -> std::result::Result { // Deserialize the compressed data (which includes compression_info) let record: MinimalRecord = AnchorDeserialize::deserialize(&mut &data[..]) - .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize)?; + .map_err(|_| light_sdk::interface::error::LightPdaError::Borsh)?; // Verify the owner in data matches the seed if record.owner != self.owner { - return Err(anchor_lang::error::ErrorCode::ConstraintSeeds.into()); + return Err(light_sdk::interface::error::LightPdaError::InvalidSeeds); } Ok(AllBorshVariant { @@ -298,19 +298,19 @@ impl light_sdk::interface::IntoVariant for AllBorshSeeds { /// Implement Pack trait to allow AllBorshVariant to be used with `create_load_instructions`. /// Transforms the variant into PackedLightAccountVariant for efficient serialization. #[cfg(not(target_os = "solana"))] -impl light_sdk::compressible::Pack for AllBorshVariant { +impl light_sdk::interface::Pack for AllBorshVariant { type Packed = crate::derived_variants::PackedLightAccountVariant; fn pack( &self, - accounts: &mut PackedAccounts, - ) -> std::result::Result { + accounts: &mut light_sdk::instruction::PackedAccounts, + ) -> std::result::Result { use light_sdk::interface::LightAccountVariantTrait; - let (_, bump) = self.derive_pda(); + let (_, bump) = self.derive_pda::(); let packed_data = self .data .pack(accounts) - .map_err(|_| ProgramError::InvalidAccountData)?; + .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; Ok( crate::derived_variants::PackedLightAccountVariant::AllBorsh { seeds: PackedAllBorshSeeds { @@ -334,15 +334,15 @@ impl light_sdk::interface::IntoVariant for AllZeroCopySeeds fn into_variant( self, data: &[u8], - ) -> std::result::Result { + ) -> std::result::Result { // For ZeroCopy (Pod) accounts, data is the full Pod bytes including compression_info. // We deserialize using AnchorDeserialize (which ZeroCopyRecord implements). let record: ZeroCopyRecord = AnchorDeserialize::deserialize(&mut &data[..]) - .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize)?; + .map_err(|_| light_sdk::interface::error::LightPdaError::Borsh)?; // Verify the owner in data matches the seed if Pubkey::new_from_array(record.owner) != self.owner { - return Err(anchor_lang::error::ErrorCode::ConstraintSeeds.into()); + return Err(light_sdk::interface::error::LightPdaError::InvalidSeeds); } Ok(AllZeroCopyVariant { @@ -359,19 +359,19 @@ impl light_sdk::interface::IntoVariant for AllZeroCopySeeds /// Implement Pack trait to allow AllZeroCopyVariant to be used with `create_load_instructions`. /// Transforms the variant into PackedLightAccountVariant for efficient serialization. #[cfg(not(target_os = "solana"))] -impl light_sdk::compressible::Pack for AllZeroCopyVariant { +impl light_sdk::interface::Pack for AllZeroCopyVariant { type Packed = crate::derived_variants::PackedLightAccountVariant; fn pack( &self, - accounts: &mut PackedAccounts, - ) -> std::result::Result { + accounts: &mut light_sdk::instruction::PackedAccounts, + ) -> std::result::Result { use light_sdk::interface::LightAccountVariantTrait; - let (_, bump) = self.derive_pda(); + let (_, bump) = self.derive_pda::(); let packed_data = self .data .pack(accounts) - .map_err(|_| ProgramError::InvalidAccountData)?; + .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; Ok( crate::derived_variants::PackedLightAccountVariant::AllZeroCopy { seeds: PackedAllZeroCopySeeds { diff --git a/sdk-tests/manual-test/src/ata/derived.rs b/sdk-tests/manual-test/src/ata/derived.rs index e5ea0e0d8d..e4e27aa599 100644 --- a/sdk-tests/manual-test/src/ata/derived.rs +++ b/sdk-tests/manual-test/src/ata/derived.rs @@ -14,38 +14,41 @@ use super::accounts::{CreateAtaAccounts, CreateAtaParams}; // LightPreInit Implementation - Creates ATA at START of instruction // ============================================================================ -impl<'info> LightPreInit<'info, CreateAtaParams> for CreateAtaAccounts<'info> { +impl<'info> LightPreInit, CreateAtaParams> for CreateAtaAccounts<'info> { fn light_pre_init( &mut self, _remaining_accounts: &[AccountInfo<'info>], _params: &CreateAtaParams, - ) -> std::result::Result { - // Derive the ATA bump on-chain - let (_, bump) = light_token::instruction::derive_associated_token_account( - self.ata_owner.key, - self.mint.key, - ); + ) -> std::result::Result { + let inner = || -> std::result::Result { + // Derive the ATA bump on-chain + let (_, bump) = light_token::instruction::derive_associated_token_account( + self.ata_owner.key, + self.mint.key, + ); - // Create ATA via CPI with idempotent + rent-free mode - // NOTE: Unlike token vaults, ATAs use .invoke() not .invoke_signed() - // because ATAs are derived from [owner, token_program, mint], not program PDAs - CreateTokenAtaCpi { - payer: self.payer.to_account_info(), - owner: self.ata_owner.clone(), - mint: self.mint.clone(), - ata: self.user_ata.to_account_info(), - bump, - } - .idempotent() // Safe: won't fail if ATA already exists - .rent_free( - self.compressible_config.clone(), - self.rent_sponsor.clone(), - self.system_program.to_account_info(), - ) - .invoke()?; + // Create ATA via CPI with idempotent + rent-free mode + // NOTE: Unlike token vaults, ATAs use .invoke() not .invoke_signed() + // because ATAs are derived from [owner, token_program, mint], not program PDAs + CreateTokenAtaCpi { + payer: self.payer.to_account_info(), + owner: self.ata_owner.clone(), + mint: self.mint.clone(), + ata: self.user_ata.to_account_info(), + bump, + } + .idempotent() // Safe: won't fail if ATA already exists + .rent_free( + self.compressible_config.clone(), + self.rent_sponsor.clone(), + self.system_program.to_account_info(), + ) + .invoke()?; - // ATAs don't use CPI context, return false - Ok(false) + // ATAs don't use CPI context, return false + Ok(false) + }; + inner().map_err(Into::into) } } @@ -53,13 +56,13 @@ impl<'info> LightPreInit<'info, CreateAtaParams> for CreateAtaAccounts<'info> { // LightFinalize Implementation - No-op for ATA only flow // ============================================================================ -impl<'info> LightFinalize<'info, CreateAtaParams> for CreateAtaAccounts<'info> { +impl<'info> LightFinalize, CreateAtaParams> for CreateAtaAccounts<'info> { fn light_finalize( &mut self, _remaining_accounts: &[AccountInfo<'info>], _params: &CreateAtaParams, _has_pre_init: bool, - ) -> std::result::Result<(), LightSdkError> { + ) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { Ok(()) } } diff --git a/sdk-tests/manual-test/src/derived_compress.rs b/sdk-tests/manual-test/src/derived_compress.rs index 95ae3706bd..86b157fb0b 100644 --- a/sdk-tests/manual-test/src/derived_compress.rs +++ b/sdk-tests/manual-test/src/derived_compress.rs @@ -8,12 +8,13 @@ use std::marker::PhantomData; use anchor_lang::prelude::*; use light_sdk::{ interface::{ - prepare_account_for_compression, process_compress_pda_accounts_idempotent, CompressCtx, + error::LightPdaError, prepare_account_for_compression, + process_compress_pda_accounts_idempotent, CompressCtx, }, LightDiscriminator, }; use light_sdk_types::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress; -use solana_program_error::ProgramError; +use solana_account_info::AccountInfo; use crate::{account_loader::ZeroCopyRecord, pda::MinimalRecord}; @@ -128,20 +129,20 @@ fn compress_dispatch<'info>( account_info: &AccountInfo<'info>, meta: &CompressedAccountMetaNoLamportsNoAddress, index: usize, - ctx: &mut CompressCtx<'_, 'info>, -) -> std::result::Result<(), ProgramError> { - let data = account_info.try_borrow_data()?; + ctx: &mut CompressCtx<'_, AccountInfo<'info>>, +) -> std::result::Result<(), LightPdaError> { + let data = account_info.try_borrow_data().map_err(|_| LightPdaError::Borsh)?; // Read discriminator from first 8 bytes let discriminator: [u8; 8] = data[..8] .try_into() - .map_err(|_| ProgramError::InvalidAccountData)?; + .map_err(|_| LightPdaError::InvalidInstructionData)?; match discriminator { d if d == MinimalRecord::LIGHT_DISCRIMINATOR => { // Borsh path: deserialize using try_from_slice let mut account_data = MinimalRecord::try_from_slice(&data[8..]) - .map_err(|_| ProgramError::InvalidAccountData)?; + .map_err(|_| LightPdaError::Borsh)?; drop(data); // Call prepare with deserialized data @@ -173,7 +174,7 @@ pub fn process_compress_and_close<'info>( instruction_data, compress_dispatch, crate::LIGHT_CPI_SIGNER, - &crate::ID, + &crate::ID.to_bytes(), ) - .map_err(Into::into) + .map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::Custom(u32::from(e)))) } diff --git a/sdk-tests/manual-test/src/derived_decompress.rs b/sdk-tests/manual-test/src/derived_decompress.rs index 1a5aadf599..d26b040b14 100644 --- a/sdk-tests/manual-test/src/derived_decompress.rs +++ b/sdk-tests/manual-test/src/derived_decompress.rs @@ -118,11 +118,14 @@ pub fn process_decompress_idempotent<'info>( remaining_accounts: &[AccountInfo<'info>], instruction_data: &[u8], ) -> Result<()> { - process_decompress_pda_accounts_idempotent::( + use solana_program::{clock::Clock, sysvar::Sysvar}; + let current_slot = Clock::get()?.slot; + process_decompress_pda_accounts_idempotent::<_, PackedLightAccountVariant>( remaining_accounts, instruction_data, crate::LIGHT_CPI_SIGNER, - &crate::ID, + &crate::ID.to_bytes(), + current_slot, ) - .map_err(Into::into) + .map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::Custom(u32::from(e)))) } diff --git a/sdk-tests/manual-test/src/derived_light_config.rs b/sdk-tests/manual-test/src/derived_light_config.rs index 2386226303..2d5236dd11 100644 --- a/sdk-tests/manual-test/src/derived_light_config.rs +++ b/sdk-tests/manual-test/src/derived_light_config.rs @@ -2,8 +2,8 @@ use anchor_lang::prelude::*; use light_compressible::rent::RentConfig; -use light_sdk::interface::config::process_initialize_light_config; -use light_sdk::interface::config::process_update_light_config; +use light_sdk::interface::program::config::create::process_initialize_light_config; +use solana_program_error::ProgramError; /// Params order matches SDK's InitializeCompressionConfigAnchorData. #[derive(AnchorSerialize, AnchorDeserialize, Clone)] @@ -42,17 +42,17 @@ pub fn process_initialize_config<'info>( process_initialize_light_config( &ctx.accounts.config, &ctx.accounts.authority, - ¶ms.rent_sponsor, - ¶ms.compression_authority, + ¶ms.rent_sponsor.to_bytes(), + ¶ms.compression_authority.to_bytes(), params.rent_config, params.write_top_up, - params.address_space, + params.address_space.iter().map(|p| p.to_bytes()).collect(), 0, // config_bump &ctx.accounts.fee_payer, &ctx.accounts.system_program, - &crate::ID, + &crate::ID.to_bytes(), ) - .map_err(Into::into) + .map_err(|e| anchor_lang::error::Error::from(ProgramError::Custom(u32::from(e)))) } #[derive(Accounts)] @@ -73,5 +73,6 @@ pub fn process_update_config<'info>( ctx.accounts.config.to_account_info(), ctx.accounts.authority.to_account_info(), ]; - process_update_light_config(&remaining, &instruction_data, &crate::ID).map_err(Into::into) + light_sdk::interface::process_update_light_config(&remaining, &instruction_data, &crate::ID.to_bytes()) + .map_err(|e| anchor_lang::error::Error::from(ProgramError::Custom(u32::from(e)))) } diff --git a/sdk-tests/manual-test/src/derived_variants.rs b/sdk-tests/manual-test/src/derived_variants.rs index cf02bb3ce3..dee543aab9 100644 --- a/sdk-tests/manual-test/src/derived_variants.rs +++ b/sdk-tests/manual-test/src/derived_variants.rs @@ -3,9 +3,11 @@ //! This module contains the code that would be generated by the `#[light_program]` macro. use anchor_lang::prelude::*; -use light_sdk::interface::{prepare_account_for_decompression, DecompressCtx, DecompressVariant}; +use light_sdk::interface::{ + error::LightPdaError, prepare_account_for_decompression, DecompressCtx, DecompressVariant, +}; use light_sdk_types::instruction::PackedStateTreeInfo; -use solana_program_error::ProgramError; +use solana_account_info::AccountInfo; use crate::{ account_loader::derived_accounts::{ @@ -75,13 +77,13 @@ pub enum PackedLightAccountVariant { /// Implementation for PackedLightAccountVariant. /// Implements on the inner variant type to satisfy orphan rules. -impl<'info> DecompressVariant<'info> for PackedLightAccountVariant { +impl<'info> DecompressVariant> for PackedLightAccountVariant { fn decompress( &self, tree_info: &PackedStateTreeInfo, pda_account: &AccountInfo<'info>, - ctx: &mut DecompressCtx<'_, 'info>, - ) -> std::result::Result<(), ProgramError> { + ctx: &mut DecompressCtx<'_, AccountInfo<'info>>, + ) -> std::result::Result<(), LightPdaError> { let output_queue_index = ctx.output_queue_index; match self { PackedLightAccountVariant::MinimalRecord { seeds, data } => { @@ -89,7 +91,7 @@ impl<'info> DecompressVariant<'info> for PackedLightAccountVariant { seeds: seeds.clone(), data: data.clone(), }; - prepare_account_for_decompression::<4, PackedMinimalRecordVariant>( + prepare_account_for_decompression::<4, PackedMinimalRecordVariant, AccountInfo<'info>>( &packed_data, tree_info, output_queue_index, @@ -102,7 +104,7 @@ impl<'info> DecompressVariant<'info> for PackedLightAccountVariant { seeds: seeds.clone(), data: data.clone(), }; - prepare_account_for_decompression::<4, PackedZeroCopyRecordVariant>( + prepare_account_for_decompression::<4, PackedZeroCopyRecordVariant, AccountInfo<'info>>( &packed_data, tree_info, output_queue_index, @@ -115,7 +117,7 @@ impl<'info> DecompressVariant<'info> for PackedLightAccountVariant { seeds: seeds.clone(), data: data.clone(), }; - prepare_account_for_decompression::<3, PackedAllBorshVariant>( + prepare_account_for_decompression::<3, PackedAllBorshVariant, AccountInfo<'info>>( &packed_data, tree_info, output_queue_index, @@ -128,7 +130,7 @@ impl<'info> DecompressVariant<'info> for PackedLightAccountVariant { seeds: seeds.clone(), data: data.clone(), }; - prepare_account_for_decompression::<3, PackedAllZeroCopyVariant>( + prepare_account_for_decompression::<3, PackedAllZeroCopyVariant, AccountInfo<'info>>( &packed_data, tree_info, output_queue_index, diff --git a/sdk-tests/manual-test/src/lib.rs b/sdk-tests/manual-test/src/lib.rs index 5b1f1c5232..64f66a01a0 100644 --- a/sdk-tests/manual-test/src/lib.rs +++ b/sdk-tests/manual-test/src/lib.rs @@ -74,7 +74,7 @@ pub mod manual_test { let has_pre_init = ctx .accounts .light_pre_init(ctx.remaining_accounts, ¶ms) - .map_err(|e| anchor_lang::error::Error::from(ProgramError::from(e)))?; + .map_err(|e| anchor_lang::error::Error::from(ProgramError::Custom(u32::from(e))))?; // 2. Business logic: set account data ctx.accounts.record.owner = params.owner; @@ -82,7 +82,7 @@ pub mod manual_test { // 3. Finalize: no-op for PDA-only flow ctx.accounts .light_finalize(ctx.remaining_accounts, ¶ms, has_pre_init) - .map_err(|e| anchor_lang::error::Error::from(ProgramError::from(e)))?; + .map_err(|e| anchor_lang::error::Error::from(ProgramError::Custom(u32::from(e))))?; Ok(()) } @@ -140,7 +140,7 @@ pub mod manual_test { let has_pre_init = ctx .accounts .light_pre_init(ctx.remaining_accounts, ¶ms) - .map_err(|e| anchor_lang::error::Error::from(ProgramError::from(e)))?; + .map_err(|e| anchor_lang::error::Error::from(ProgramError::Custom(u32::from(e))))?; // 2. Business logic: set account data using load_init() pattern { @@ -152,7 +152,7 @@ pub mod manual_test { // 3. Finalize: no-op for PDA-only flow ctx.accounts .light_finalize(ctx.remaining_accounts, ¶ms, has_pre_init) - .map_err(|e| anchor_lang::error::Error::from(ProgramError::from(e)))?; + .map_err(|e| anchor_lang::error::Error::from(ProgramError::Custom(u32::from(e))))?; Ok(()) } @@ -168,14 +168,14 @@ pub mod manual_test { let has_pre_init = ctx .accounts .light_pre_init(ctx.remaining_accounts, ¶ms) - .map_err(|e| anchor_lang::error::Error::from(ProgramError::from(e)))?; + .map_err(|e| anchor_lang::error::Error::from(ProgramError::Custom(u32::from(e))))?; // 2. No business logic for mint-only creation // 3. Finalize: no-op for mint-only flow ctx.accounts .light_finalize(ctx.remaining_accounts, ¶ms, has_pre_init) - .map_err(|e| anchor_lang::error::Error::from(ProgramError::from(e)))?; + .map_err(|e| anchor_lang::error::Error::from(ProgramError::Custom(u32::from(e))))?; Ok(()) } @@ -191,14 +191,14 @@ pub mod manual_test { let has_pre_init = ctx .accounts .light_pre_init(ctx.remaining_accounts, ¶ms) - .map_err(|e| anchor_lang::error::Error::from(ProgramError::from(e)))?; + .map_err(|e| anchor_lang::error::Error::from(ProgramError::Custom(u32::from(e))))?; // 2. No business logic for token vault-only creation // 3. Finalize: no-op for token vault-only flow ctx.accounts .light_finalize(ctx.remaining_accounts, ¶ms, has_pre_init) - .map_err(|e| anchor_lang::error::Error::from(ProgramError::from(e)))?; + .map_err(|e| anchor_lang::error::Error::from(ProgramError::Custom(u32::from(e))))?; Ok(()) } @@ -214,14 +214,14 @@ pub mod manual_test { let has_pre_init = ctx .accounts .light_pre_init(ctx.remaining_accounts, ¶ms) - .map_err(|e| anchor_lang::error::Error::from(ProgramError::from(e)))?; + .map_err(|e| anchor_lang::error::Error::from(ProgramError::Custom(u32::from(e))))?; // 2. No business logic for ATA-only creation // 3. Finalize: no-op for ATA-only flow ctx.accounts .light_finalize(ctx.remaining_accounts, ¶ms, has_pre_init) - .map_err(|e| anchor_lang::error::Error::from(ProgramError::from(e)))?; + .map_err(|e| anchor_lang::error::Error::from(ProgramError::Custom(u32::from(e))))?; Ok(()) } @@ -243,7 +243,7 @@ pub mod manual_test { let has_pre_init = ctx .accounts .light_pre_init(ctx.remaining_accounts, ¶ms) - .map_err(|e| anchor_lang::error::Error::from(ProgramError::from(e)))?; + .map_err(|e| anchor_lang::error::Error::from(ProgramError::Custom(u32::from(e))))?; // 2. Business logic: set PDA data ctx.accounts.borsh_record.owner = params.owner; @@ -256,7 +256,7 @@ pub mod manual_test { // 3. Finalize: no-op for this flow ctx.accounts .light_finalize(ctx.remaining_accounts, ¶ms, has_pre_init) - .map_err(|e| anchor_lang::error::Error::from(ProgramError::from(e)))?; + .map_err(|e| anchor_lang::error::Error::from(ProgramError::Custom(u32::from(e))))?; Ok(()) } diff --git a/sdk-tests/manual-test/src/pda/derived_accounts.rs b/sdk-tests/manual-test/src/pda/derived_accounts.rs index 81d7e5a74d..cc359dafa2 100644 --- a/sdk-tests/manual-test/src/pda/derived_accounts.rs +++ b/sdk-tests/manual-test/src/pda/derived_accounts.rs @@ -5,15 +5,14 @@ use light_compressed_account::instruction_data::{ use light_sdk::{ cpi::{v2::CpiAccounts, CpiAccountsConfig, InvokeLightSystemProgram}, error::LightSdkError, - instruction::{PackedAccounts, PackedAddressTreeInfoExt}, + instruction::PackedAddressTreeInfoExt, interface::{ prepare_compressed_account_on_init, LightAccount, LightAccountVariantTrait, LightFinalize, LightPreInit, PackedLightAccountVariantTrait, }, - light_account_checks::packed_accounts::ProgramPackedAccounts, + light_account_checks::{self, packed_accounts::ProgramPackedAccounts}, sdk_types::CpiContextWriteAccounts, }; -use solana_program_error::ProgramError; use super::{ accounts::{CreatePda, CreatePdaParams}, @@ -38,115 +37,119 @@ const _: () = { // Manual LightPreInit Implementation // ============================================================================ -impl<'info> LightPreInit<'info, CreatePdaParams> for CreatePda<'info> { +impl<'info> LightPreInit, CreatePdaParams> for CreatePda<'info> { fn light_pre_init( &mut self, remaining_accounts: &[AccountInfo<'info>], params: &CreatePdaParams, - ) -> std::result::Result { - use light_sdk::interface::{config::LightConfig, LightAccount}; - use solana_program::{clock::Clock, sysvar::Sysvar}; - use solana_program_error::ProgramError; - - // 1. Build CPI accounts (slice remaining_accounts at system_accounts_offset) - let system_accounts_offset = params.create_accounts_proof.system_accounts_offset as usize; - if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkError::FewerAccountsThanSystemAccounts); - } - let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - &self.fee_payer, - &remaining_accounts[system_accounts_offset..], - config, - ); - - // 2. Get address tree pubkey from packed tree info - let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; - let address_tree_pubkey = address_tree_info - .get_tree_pubkey(&cpi_accounts) - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; - let output_tree_index = params.create_accounts_proof.output_state_tree_index; - let current_account_index: u8 = 0; - // Is true if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. - const WITH_CPI_CONTEXT: bool = false; - - const NUM_LIGHT_PDAS: usize = 1; - - // 6. Set compression_info from config - let light_config = LightConfig::load_checked(&self.compression_config, &crate::ID) - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; - let current_slot = Clock::get() - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))? - .slot; - // Dynamic derived light pda specific. Only exists if NUM_LIGHT_PDAS > 0 - // ===================================================================== - { - // Is first if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. - let cpi_context = if WITH_CPI_CONTEXT { - CompressedCpiContext::first() - } else { - CompressedCpiContext::default() - }; - let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); - let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); - // 3. Prepare compressed account using helper function - // Dynamic code 0-N variants depending on the accounts struct - // ===================================================================== - prepare_compressed_account_on_init( - &self.record.key(), - &address_tree_pubkey, - address_tree_info, - output_tree_index, - current_account_index, - &crate::ID, - &mut new_address_params, - &mut account_infos, - )?; - self.record.set_decompressed(&light_config, current_slot); + ) -> std::result::Result { + let mut inner = || -> std::result::Result { + use light_sdk::interface::{LightConfig, LightAccount}; + use solana_program::{clock::Clock, sysvar::Sysvar}; + use solana_program_error::ProgramError; + + // 1. Build CPI accounts (slice remaining_accounts at system_accounts_offset) + let system_accounts_offset = + params.create_accounts_proof.system_accounts_offset as usize; + if remaining_accounts.len() < system_accounts_offset { + return Err(LightSdkError::FewerAccountsThanSystemAccounts); + } + let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); + let cpi_accounts = CpiAccounts::new_with_config( + &self.fee_payer, + &remaining_accounts[system_accounts_offset..], + config, + ); + + // 2. Get address tree pubkey from packed tree info + let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; + let address_tree_pubkey = address_tree_info + .get_tree_pubkey(&cpi_accounts) + .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; + let output_tree_index = params.create_accounts_proof.output_state_tree_index; + let current_account_index: u8 = 0; + // Is true if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. + const WITH_CPI_CONTEXT: bool = false; + + const NUM_LIGHT_PDAS: usize = 1; + + // 6. Set compression_info from config + let light_config = LightConfig::load_checked(&self.compression_config, &crate::ID.to_bytes()) + .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; + let current_slot = Clock::get() + .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))? + .slot; + // Dynamic derived light pda specific. Only exists if NUM_LIGHT_PDAS > 0 // ===================================================================== - - // current_account_index += 1; - // For multiple accounts, repeat the pattern: - // let prepared2 = prepare_compressed_account_on_init(..., current_account_index, ...)?; - // current_account_index += 1; - - // 4. Build instruction data manually (no builder pattern) - let instruction_data = InstructionDataInvokeCpiWithAccountInfo { - mode: 1, // V2 mode - bump: crate::LIGHT_CPI_SIGNER.bump, - invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), - compress_or_decompress_lamports: 0, - is_compress: false, - with_cpi_context: WITH_CPI_CONTEXT, - with_transaction_hash: false, - cpi_context, - proof: params.create_accounts_proof.proof.0, - new_address_params, - account_infos, - read_only_addresses: vec![], - read_only_accounts: vec![], - }; - if !WITH_CPI_CONTEXT { - // 5. Invoke Light System Program CPI - instruction_data - .invoke(cpi_accounts) - .map_err(LightSdkError::from)?; - } else { - // For flows that combine light mints with light PDAs, write to CPI context first. - // The authority and cpi_context accounts must be provided in remaining_accounts. - let cpi_context_accounts = CpiContextWriteAccounts { - fee_payer: cpi_accounts.fee_payer(), - authority: cpi_accounts.authority().map_err(LightSdkError::from)?, - cpi_context: cpi_accounts.cpi_context().map_err(LightSdkError::from)?, - cpi_signer: crate::LIGHT_CPI_SIGNER, + { + // Is first if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. + let cpi_context = if WITH_CPI_CONTEXT { + CompressedCpiContext::first() + } else { + CompressedCpiContext::default() + }; + let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); + let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); + // 3. Prepare compressed account using helper function + // Dynamic code 0-N variants depending on the accounts struct + // ===================================================================== + prepare_compressed_account_on_init( + &self.record.key().to_bytes(), + &address_tree_pubkey.to_bytes(), + address_tree_info, + output_tree_index, + current_account_index, + &crate::ID.to_bytes(), + &mut new_address_params, + &mut account_infos, + )?; + self.record.set_decompressed(&light_config, current_slot); + // ===================================================================== + + // current_account_index += 1; + // For multiple accounts, repeat the pattern: + // let prepared2 = prepare_compressed_account_on_init(..., current_account_index, ...)?; + // current_account_index += 1; + + // 4. Build instruction data manually (no builder pattern) + let instruction_data = InstructionDataInvokeCpiWithAccountInfo { + mode: 1, // V2 mode + bump: crate::LIGHT_CPI_SIGNER.bump, + invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), + compress_or_decompress_lamports: 0, + is_compress: false, + with_cpi_context: WITH_CPI_CONTEXT, + with_transaction_hash: false, + cpi_context, + proof: params.create_accounts_proof.proof.0, + new_address_params, + account_infos, + read_only_addresses: vec![], + read_only_accounts: vec![], }; - instruction_data - .invoke_write_to_cpi_context_first(cpi_context_accounts) - .map_err(LightSdkError::from)?; + if !WITH_CPI_CONTEXT { + // 5. Invoke Light System Program CPI + instruction_data + .invoke(cpi_accounts) + .map_err(LightSdkError::from)?; + } else { + // For flows that combine light mints with light PDAs, write to CPI context first. + // The authority and cpi_context accounts must be provided in remaining_accounts. + let cpi_context_accounts = CpiContextWriteAccounts { + fee_payer: cpi_accounts.fee_payer(), + authority: cpi_accounts.authority().map_err(LightSdkError::from)?, + cpi_context: cpi_accounts.cpi_context().map_err(LightSdkError::from)?, + cpi_signer: crate::LIGHT_CPI_SIGNER, + }; + instruction_data + .invoke_write_to_cpi_context_first(cpi_context_accounts) + .map_err(LightSdkError::from)?; + } } - } - // ===================================================================== - Ok(false) // No mints, so no CPI context write + // ===================================================================== + Ok(false) // No mints, so no CPI context write + }; + inner().map_err(Into::into) } } @@ -154,13 +157,13 @@ impl<'info> LightPreInit<'info, CreatePdaParams> for CreatePda<'info> { // Manual LightFinalize Implementation (no-op for PDA-only flow) // ============================================================================ -impl<'info> LightFinalize<'info, CreatePdaParams> for CreatePda<'info> { +impl<'info> LightFinalize, CreatePdaParams> for CreatePda<'info> { fn light_finalize( &mut self, _remaining_accounts: &[AccountInfo<'info>], _params: &CreatePdaParams, _has_pre_init: bool, - ) -> std::result::Result<(), LightSdkError> { + ) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { // No-op for PDA-only flow - compression CPI already executed in light_pre_init Ok(()) } @@ -211,7 +214,7 @@ pub struct PackedMinimalRecordVariant { // ============================================================================ impl LightAccountVariantTrait<4> for MinimalRecordVariant { - const PROGRAM_ID: Pubkey = crate::ID; + const PROGRAM_ID: [u8; 32] = crate::ID.to_bytes(); type Seeds = MinimalRecordSeeds; type Data = MinimalRecord; @@ -255,52 +258,51 @@ impl PackedLightAccountVariantTrait<4> for PackedMinimalRecordVariant { self.seeds.bump } - fn unpack(&self, accounts: &[AccountInfo]) -> Result { + fn unpack( + &self, + accounts: &[AI], + ) -> std::result::Result { let owner = accounts .get(self.seeds.owner_idx as usize) - .ok_or(anchor_lang::error::ErrorCode::AccountNotEnoughKeys)?; + .ok_or(light_sdk::interface::error::LightPdaError::NotEnoughAccountKeys)?; // Build ProgramPackedAccounts for LightAccount::unpack let packed_accounts = ProgramPackedAccounts { accounts }; let data = MinimalRecord::unpack(&self.data, &packed_accounts) - .map_err(|_| anchor_lang::error::ErrorCode::InvalidProgramId)?; // TODO: propagate error + .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; Ok(MinimalRecordVariant { seeds: MinimalRecordSeeds { - owner: *owner.key, + owner: Pubkey::from(owner.key()), nonce: u64::from_le_bytes(self.seeds.nonce_bytes), }, data, }) } - fn seed_refs_with_bump<'a>( + fn seed_refs_with_bump<'a, AI: light_account_checks::AccountInfoTrait>( &'a self, - accounts: &'a [AccountInfo], - bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; 4], ProgramError> { - let owner = accounts - .get(self.seeds.owner_idx as usize) - .ok_or(ProgramError::InvalidAccountData)?; - Ok([ - b"minimal_record", - owner.key.as_ref(), - &self.seeds.nonce_bytes, - bump_storage, - ]) + _accounts: &'a [AI], + _bump_storage: &'a [u8; 1], + ) -> std::result::Result<[&'a [u8]; 4], light_sdk::interface::error::LightPdaError> { + // PDA variants use seed_vec() in the decompression path, not seed_refs_with_bump. + // Returning a reference to the account key requires a key_ref() method on + // AccountInfoTrait, which is not yet available. Since this method is only + // called for token account variants, PDA variants return an error. + Err(light_sdk::interface::error::LightPdaError::InvalidSeeds) } fn into_in_token_data( &self, _tree_info: &light_sdk::instruction::PackedStateTreeInfo, _output_queue_index: u8, - ) -> Result { - Err(ProgramError::InvalidAccountData.into()) + ) -> std::result::Result { + Err(light_sdk::interface::error::LightPdaError::InvalidInstructionData) } fn into_in_tlv( &self, - ) -> Result>> { + ) -> std::result::Result>, light_sdk::interface::error::LightPdaError> { Ok(None) } } @@ -316,14 +318,14 @@ impl light_sdk::interface::IntoVariant for MinimalRecordSe fn into_variant( self, data: &[u8], - ) -> std::result::Result { + ) -> std::result::Result { // Deserialize the compressed data (which includes compression_info) let record: MinimalRecord = AnchorDeserialize::deserialize(&mut &data[..]) - .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize)?; + .map_err(|_| light_sdk::interface::error::LightPdaError::Borsh)?; // Verify the owner in data matches the seed if record.owner != self.owner { - return Err(anchor_lang::error::ErrorCode::ConstraintSeeds.into()); + return Err(light_sdk::interface::error::LightPdaError::InvalidSeeds); } Ok(MinimalRecordVariant { @@ -340,19 +342,19 @@ impl light_sdk::interface::IntoVariant for MinimalRecordSe /// Implement Pack trait to allow MinimalRecordVariant to be used with `create_load_instructions`. /// Transforms the variant into PackedLightAccountVariant for efficient serialization. #[cfg(not(target_os = "solana"))] -impl light_sdk::compressible::Pack for MinimalRecordVariant { +impl light_sdk::interface::Pack for MinimalRecordVariant { type Packed = crate::derived_variants::PackedLightAccountVariant; fn pack( &self, - accounts: &mut PackedAccounts, - ) -> std::result::Result { + accounts: &mut light_sdk::instruction::PackedAccounts, + ) -> std::result::Result { use light_sdk::interface::LightAccountVariantTrait; - let (_, bump) = self.derive_pda(); + let (_, bump) = self.derive_pda::(); let packed_data = self .data .pack(accounts) - .map_err(|_| ProgramError::InvalidAccountData)?; + .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; Ok( crate::derived_variants::PackedLightAccountVariant::MinimalRecord { seeds: PackedMinimalRecordSeeds { diff --git a/sdk-tests/manual-test/src/pda/derived_state.rs b/sdk-tests/manual-test/src/pda/derived_state.rs index e8d9b05a64..3d74fba30c 100644 --- a/sdk-tests/manual-test/src/pda/derived_state.rs +++ b/sdk-tests/manual-test/src/pda/derived_state.rs @@ -1,11 +1,10 @@ use anchor_lang::prelude::*; use light_sdk::{ compressible::CompressionInfo, - instruction::PackedAccounts, - interface::{AccountType, LightAccount, LightConfig}, - light_account_checks::{packed_accounts::ProgramPackedAccounts, AccountInfoTrait}, + interface::{AccountType, HasCompressionInfo, LightAccount, LightConfig}, + light_account_checks::{packed_accounts::ProgramPackedAccounts, AccountInfoTrait, AccountMetaTrait}, }; -use solana_program_error::ProgramError; +use light_sdk::interface::error::LightPdaError; use super::state::MinimalRecord; @@ -31,7 +30,7 @@ impl LightAccount for MinimalRecord { type Packed = PackedMinimalRecord; // CompressionInfo (24) + Pubkey (32) = 56 bytes - const INIT_SPACE: usize = CompressionInfo::INIT_SPACE + 32; + const INIT_SPACE: usize = core::mem::size_of::() + 32; fn compression_info(&self) -> &CompressionInfo { &self.compression_info @@ -45,24 +44,25 @@ impl LightAccount for MinimalRecord { self.compression_info = CompressionInfo::new_from_config(config, current_slot); } - fn pack( + #[cfg(not(target_os = "solana"))] + fn pack( &self, - accounts: &mut PackedAccounts, - ) -> std::result::Result { + accounts: &mut light_sdk::interface::instruction::PackedAccounts, + ) -> std::result::Result { // compression_info excluded from packed struct Ok(PackedMinimalRecord { - owner: accounts.insert_or_get(self.owner), + owner: accounts.insert_or_get(AM::pubkey_from_bytes(self.owner.to_bytes())), }) } fn unpack( packed: &Self::Packed, accounts: &ProgramPackedAccounts, - ) -> std::result::Result { + ) -> std::result::Result { // Use get_u8 with a descriptive name for better error messages let owner_account = accounts .get_u8(packed.owner, "MinimalRecord: owner") - .map_err(|_| ProgramError::InvalidAccountData)?; + .map_err(|_| LightPdaError::InvalidInstructionData)?; // Set compression_info to compressed() for hash verification at decompress Ok(MinimalRecord { @@ -71,3 +71,22 @@ impl LightAccount for MinimalRecord { }) } } + +impl HasCompressionInfo for MinimalRecord { + fn compression_info(&self) -> std::result::Result<&CompressionInfo, LightPdaError> { + Ok(&self.compression_info) + } + + fn compression_info_mut(&mut self) -> std::result::Result<&mut CompressionInfo, LightPdaError> { + Ok(&mut self.compression_info) + } + + fn compression_info_mut_opt(&mut self) -> &mut Option { + panic!("compression_info_mut_opt not supported for LightAccount types (use compression_info_mut instead)") + } + + fn set_compression_info_none(&mut self) -> std::result::Result<(), LightPdaError> { + self.compression_info = CompressionInfo::compressed(); + Ok(()) + } +} diff --git a/sdk-tests/manual-test/src/pda/state.rs b/sdk-tests/manual-test/src/pda/state.rs index a94f3f2785..421451171c 100644 --- a/sdk-tests/manual-test/src/pda/state.rs +++ b/sdk-tests/manual-test/src/pda/state.rs @@ -10,9 +10,13 @@ use light_sdk::{compressible::CompressionInfo, LightDiscriminator, LightHasherSh /// Minimal record struct for testing PDA creation. /// Contains only compression_info and one field. /// -#[derive(Default, Debug, InitSpace, LightDiscriminator, LightHasherSha)] // LightAccount +#[derive(Default, Debug, LightDiscriminator, LightHasherSha)] // LightAccount #[account] pub struct MinimalRecord { pub compression_info: CompressionInfo, pub owner: Pubkey, } + +impl anchor_lang::Space for MinimalRecord { + const INIT_SPACE: usize = core::mem::size_of::() + 32; +} diff --git a/sdk-tests/manual-test/src/token_account/derived.rs b/sdk-tests/manual-test/src/token_account/derived.rs index b245287943..4e7b3df367 100644 --- a/sdk-tests/manual-test/src/token_account/derived.rs +++ b/sdk-tests/manual-test/src/token_account/derived.rs @@ -4,8 +4,10 @@ use anchor_lang::prelude::*; use light_sdk::{ error::LightSdkError, interface::{LightFinalize, LightPreInit}, - Pack, Unpack, + Unpack, }; +#[cfg(not(target_os = "solana"))] +use light_sdk::Pack; use light_token::instruction::CreateTokenAccountCpi; use solana_account_info::AccountInfo; @@ -15,33 +17,37 @@ use super::accounts::{CreateTokenVaultAccounts, CreateTokenVaultParams, TOKEN_VA // LightPreInit Implementation - Creates token account at START of instruction // ============================================================================ -impl<'info> LightPreInit<'info, CreateTokenVaultParams> for CreateTokenVaultAccounts<'info> { +impl<'info> LightPreInit, CreateTokenVaultParams> for CreateTokenVaultAccounts<'info> { fn light_pre_init( &mut self, _remaining_accounts: &[AccountInfo<'info>], params: &CreateTokenVaultParams, - ) -> std::result::Result { - // Build PDA seeds: [TOKEN_VAULT_SEED, mint.key(), &[bump]] - let mint_key = self.mint.key(); - let vault_seeds: &[&[u8]] = &[TOKEN_VAULT_SEED, mint_key.as_ref(), &[params.vault_bump]]; + ) -> std::result::Result { + let inner = || -> std::result::Result { + // Build PDA seeds: [TOKEN_VAULT_SEED, mint.key(), &[bump]] + let mint_key = self.mint.key(); + let vault_seeds: &[&[u8]] = + &[TOKEN_VAULT_SEED, mint_key.as_ref(), &[params.vault_bump]]; - // Create token account via CPI with rent-free mode - CreateTokenAccountCpi { - payer: self.payer.to_account_info(), - account: self.token_vault.to_account_info(), - mint: self.mint.clone(), - owner: *self.vault_owner.key, - } - .rent_free( - self.compressible_config.clone(), - self.rent_sponsor.clone(), - self.system_program.to_account_info(), - &crate::ID, - ) - .invoke_signed(vault_seeds)?; + // Create token account via CPI with rent-free mode + CreateTokenAccountCpi { + payer: self.payer.to_account_info(), + account: self.token_vault.to_account_info(), + mint: self.mint.clone(), + owner: *self.vault_owner.key, + } + .rent_free( + self.compressible_config.clone(), + self.rent_sponsor.clone(), + self.system_program.to_account_info(), + &crate::ID, + ) + .invoke_signed(vault_seeds)?; - // Token accounts don't use CPI context, return false - Ok(false) + // Token accounts don't use CPI context, return false + Ok(false) + }; + inner().map_err(Into::into) } } @@ -49,13 +55,13 @@ impl<'info> LightPreInit<'info, CreateTokenVaultParams> for CreateTokenVaultAcco // LightFinalize Implementation - No-op for token account only flow // ============================================================================ -impl<'info> LightFinalize<'info, CreateTokenVaultParams> for CreateTokenVaultAccounts<'info> { +impl<'info> LightFinalize, CreateTokenVaultParams> for CreateTokenVaultAccounts<'info> { fn light_finalize( &mut self, _remaining_accounts: &[AccountInfo<'info>], _params: &CreateTokenVaultParams, _has_pre_init: bool, - ) -> std::result::Result<(), LightSdkError> { + ) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { Ok(()) } } @@ -70,12 +76,13 @@ pub struct TokenVaultSeeds { pub mint: Pubkey, } -impl Pack for TokenVaultSeeds { +#[cfg(not(target_os = "solana"))] +impl Pack for TokenVaultSeeds { type Packed = PackedTokenVaultSeeds; fn pack( &self, remaining_accounts: &mut light_sdk::instruction::PackedAccounts, - ) -> std::result::Result { + ) -> std::result::Result { Ok(PackedTokenVaultSeeds { mint_idx: remaining_accounts.insert_or_get(self.mint), bump: 0, @@ -90,16 +97,16 @@ pub struct PackedTokenVaultSeeds { pub bump: u8, } -impl Unpack for PackedTokenVaultSeeds { +impl<'a> Unpack> for PackedTokenVaultSeeds { type Unpacked = TokenVaultSeeds; fn unpack( &self, - remaining_accounts: &[AccountInfo], - ) -> std::result::Result { + remaining_accounts: &[AccountInfo<'a>], + ) -> std::result::Result { let mint = *remaining_accounts .get(self.mint_idx as usize) - .ok_or(ProgramError::InvalidAccountData)? + .ok_or(light_sdk::interface::error::LightPdaError::NotEnoughAccountKeys)? .key; Ok(TokenVaultSeeds { mint }) } diff --git a/sdk-tests/manual-test/src/two_mints/derived.rs b/sdk-tests/manual-test/src/two_mints/derived.rs index 668fcb498d..5309affc12 100644 --- a/sdk-tests/manual-test/src/two_mints/derived.rs +++ b/sdk-tests/manual-test/src/two_mints/derived.rs @@ -25,162 +25,171 @@ use super::accounts::{ // LightPreInit Implementation - Creates mints at START of instruction // ============================================================================ -impl<'info> LightPreInit<'info, CreateDerivedMintsParams> for CreateDerivedMintsAccounts<'info> { +impl<'info> LightPreInit, CreateDerivedMintsParams> for CreateDerivedMintsAccounts<'info> { fn light_pre_init( &mut self, remaining_accounts: &[AccountInfo<'info>], params: &CreateDerivedMintsParams, - ) -> std::result::Result { - use solana_program_error::ProgramError; - - // ==================================================================== - // STATIC BOILERPLATE (same across all LightPreInit implementations) - // ==================================================================== - - // 1. Build CPI accounts (slice remaining_accounts at system_accounts_offset) - let system_accounts_offset = params.create_accounts_proof.system_accounts_offset as usize; - if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkError::FewerAccountsThanSystemAccounts); - } - let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - &self.payer, - &remaining_accounts[system_accounts_offset..], - config, - ); - - // 2. Get address tree pubkey from packed tree info - let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; - let address_tree_pubkey = address_tree_info - .get_tree_pubkey(&cpi_accounts) - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; - - // Constants for this instruction (mirrors macro-generated code) - const NUM_LIGHT_MINTS: usize = 2; - const NUM_LIGHT_PDAS: usize = 0; // Set to actual PDA count when combining PDAs + mints - #[allow(clippy::absurd_extreme_comparisons)] - const WITH_CPI_CONTEXT: bool = NUM_LIGHT_PDAS > 0 && NUM_LIGHT_MINTS > 0; // true if combining mints + PDAs - - // ==================================================================== - // DYNAMIC CODE (specific to this accounts struct) - // ==================================================================== - { - let authority = self.authority.key(); - - // Get mint signer pubkeys from accounts - let mint_signer_0 = self.mint_signer_0.key(); - let mint_signer_1 = self.mint_signer_1.key(); - - // Derive mint PDAs (light-token derives mint PDA from mint_signer) - let (mint_0_pda, mint_0_bump) = find_mint_address( - &solana_pubkey::Pubkey::new_from_array(mint_signer_0.to_bytes()), - ); - let (mint_1_pda, mint_1_bump) = find_mint_address( - &solana_pubkey::Pubkey::new_from_array(mint_signer_1.to_bytes()), - ); + ) -> std::result::Result { + let inner = || -> std::result::Result { + use solana_program_error::ProgramError; - // Derive compression addresses (from mint_signer + address_tree) - let compression_address_0 = derive_mint_compressed_address( - &solana_pubkey::Pubkey::new_from_array(mint_signer_0.to_bytes()), - &solana_pubkey::Pubkey::new_from_array(address_tree_pubkey.to_bytes()), - ); - let compression_address_1 = derive_mint_compressed_address( - &solana_pubkey::Pubkey::new_from_array(mint_signer_1.to_bytes()), - &solana_pubkey::Pubkey::new_from_array(address_tree_pubkey.to_bytes()), + // ==================================================================== + // STATIC BOILERPLATE (same across all LightPreInit implementations) + // ==================================================================== + + // 1. Build CPI accounts (slice remaining_accounts at system_accounts_offset) + let system_accounts_offset = + params.create_accounts_proof.system_accounts_offset as usize; + if remaining_accounts.len() < system_accounts_offset { + return Err(LightSdkError::FewerAccountsThanSystemAccounts); + } + let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); + let cpi_accounts = CpiAccounts::new_with_config( + &self.payer, + &remaining_accounts[system_accounts_offset..], + config, ); - // Build mint signer seeds for CPI (mint::seeds + bump) - let mint_signer_0_seeds: &[&[u8]] = &[ - MINT_SIGNER_0_SEED, - authority.as_ref(), - &[params.mint_signer_0_bump], - ]; - let mint_signer_1_seeds: &[&[u8]] = &[ - MINT_SIGNER_1_SEED, - authority.as_ref(), - &[params.mint_signer_1_bump], - ]; - - // Fixed-size array with values from accounts/attributes - let sdk_mints: [SingleMintParams<'_>; NUM_LIGHT_MINTS] = [ - SingleMintParams { - decimals: 6, // mint::decimals = 6 - address_merkle_tree_root_index: address_tree_info.root_index, - mint_authority: solana_pubkey::Pubkey::new_from_array(authority.to_bytes()), - compression_address: compression_address_0, - mint: mint_0_pda, - bump: mint_0_bump, - freeze_authority: None, - mint_seed_pubkey: solana_pubkey::Pubkey::new_from_array( - mint_signer_0.to_bytes(), - ), - authority_seeds: None, - mint_signer_seeds: Some(mint_signer_0_seeds), - token_metadata: None, - }, - SingleMintParams { - decimals: 9, // mint::decimals = 9 - address_merkle_tree_root_index: address_tree_info.root_index, - mint_authority: solana_pubkey::Pubkey::new_from_array(authority.to_bytes()), - compression_address: compression_address_1, - mint: mint_1_pda, - bump: mint_1_bump, - freeze_authority: None, - mint_seed_pubkey: solana_pubkey::Pubkey::new_from_array( - mint_signer_1.to_bytes(), - ), - authority_seeds: None, - mint_signer_seeds: Some(mint_signer_1_seeds), - token_metadata: None, - }, - ]; + // 2. Get address tree pubkey from packed tree info + let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; + let address_tree_pubkey = address_tree_info + .get_tree_pubkey(&cpi_accounts) + .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; + + // Constants for this instruction (mirrors macro-generated code) + const NUM_LIGHT_MINTS: usize = 2; + const NUM_LIGHT_PDAS: usize = 0; // Set to actual PDA count when combining PDAs + mints + #[allow(clippy::absurd_extreme_comparisons)] + const WITH_CPI_CONTEXT: bool = NUM_LIGHT_PDAS > 0 && NUM_LIGHT_MINTS > 0; // true if combining mints + PDAs // ==================================================================== - // INVOKE invoke_create_mints + // DYNAMIC CODE (specific to this accounts struct) // ==================================================================== - - // Get state_tree_index (required for decompress discriminator validation) - let state_tree_index = params - .create_accounts_proof - .state_tree_index - .ok_or(LightSdkError::from(ProgramError::InvalidArgument))?; - - let proof = params - .create_accounts_proof - .proof - .0 - .ok_or(LightSdkError::from(ProgramError::InvalidArgument))?; - - let sdk_params = SdkCreateMintsParams::new(&sdk_mints, proof) - .with_output_queue_index(params.create_accounts_proof.output_state_tree_index) - .with_address_tree_index(address_tree_info.address_merkle_tree_pubkey_index) - .with_state_tree_index(state_tree_index) - .with_cpi_context_offset(NUM_LIGHT_PDAS as u8); // Offset by PDA count - - // Build infra accounts from Accounts struct - let infra = CreateMintsInfraAccounts { - fee_payer: self.payer.to_account_info(), - compressible_config: self.compressible_config.clone(), - rent_sponsor: self.rent_sponsor.clone(), - cpi_authority: self.cpi_authority.clone(), - }; - - // Build mint account arrays - let mint_seed_accounts = [ - self.mint_signer_0.to_account_info(), - self.mint_signer_1.to_account_info(), - ]; - let mint_accounts = [self.mint_0.to_account_info(), self.mint_1.to_account_info()]; - - invoke_create_mints( - &mint_seed_accounts, - &mint_accounts, - sdk_params, - infra, - &cpi_accounts, - )?; - } - Ok(WITH_CPI_CONTEXT) // false = mint-only, no CPI context write + { + let authority = self.authority.key(); + + // Get mint signer pubkeys from accounts + let mint_signer_0 = self.mint_signer_0.key(); + let mint_signer_1 = self.mint_signer_1.key(); + + // Derive mint PDAs (light-token derives mint PDA from mint_signer) + let (mint_0_pda, mint_0_bump) = find_mint_address( + &solana_pubkey::Pubkey::new_from_array(mint_signer_0.to_bytes()), + ); + let (mint_1_pda, mint_1_bump) = find_mint_address( + &solana_pubkey::Pubkey::new_from_array(mint_signer_1.to_bytes()), + ); + + // Derive compression addresses (from mint_signer + address_tree) + let compression_address_0 = derive_mint_compressed_address( + &solana_pubkey::Pubkey::new_from_array(mint_signer_0.to_bytes()), + &solana_pubkey::Pubkey::new_from_array(address_tree_pubkey.to_bytes()), + ); + let compression_address_1 = derive_mint_compressed_address( + &solana_pubkey::Pubkey::new_from_array(mint_signer_1.to_bytes()), + &solana_pubkey::Pubkey::new_from_array(address_tree_pubkey.to_bytes()), + ); + + // Build mint signer seeds for CPI (mint::seeds + bump) + let mint_signer_0_seeds: &[&[u8]] = &[ + MINT_SIGNER_0_SEED, + authority.as_ref(), + &[params.mint_signer_0_bump], + ]; + let mint_signer_1_seeds: &[&[u8]] = &[ + MINT_SIGNER_1_SEED, + authority.as_ref(), + &[params.mint_signer_1_bump], + ]; + + // Fixed-size array with values from accounts/attributes + let sdk_mints: [SingleMintParams<'_>; NUM_LIGHT_MINTS] = [ + SingleMintParams { + decimals: 6, // mint::decimals = 6 + address_merkle_tree_root_index: address_tree_info.root_index, + mint_authority: solana_pubkey::Pubkey::new_from_array( + authority.to_bytes(), + ), + compression_address: compression_address_0, + mint: mint_0_pda, + bump: mint_0_bump, + freeze_authority: None, + mint_seed_pubkey: solana_pubkey::Pubkey::new_from_array( + mint_signer_0.to_bytes(), + ), + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_0_seeds), + token_metadata: None, + }, + SingleMintParams { + decimals: 9, // mint::decimals = 9 + address_merkle_tree_root_index: address_tree_info.root_index, + mint_authority: solana_pubkey::Pubkey::new_from_array( + authority.to_bytes(), + ), + compression_address: compression_address_1, + mint: mint_1_pda, + bump: mint_1_bump, + freeze_authority: None, + mint_seed_pubkey: solana_pubkey::Pubkey::new_from_array( + mint_signer_1.to_bytes(), + ), + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_1_seeds), + token_metadata: None, + }, + ]; + + // ==================================================================== + // INVOKE invoke_create_mints + // ==================================================================== + + // Get state_tree_index (required for decompress discriminator validation) + let state_tree_index = params + .create_accounts_proof + .state_tree_index + .ok_or(LightSdkError::from(ProgramError::InvalidArgument))?; + + let proof = params + .create_accounts_proof + .proof + .0 + .ok_or(LightSdkError::from(ProgramError::InvalidArgument))?; + + let sdk_params = SdkCreateMintsParams::new(&sdk_mints, proof) + .with_output_queue_index(params.create_accounts_proof.output_state_tree_index) + .with_address_tree_index(address_tree_info.address_merkle_tree_pubkey_index) + .with_state_tree_index(state_tree_index) + .with_cpi_context_offset(NUM_LIGHT_PDAS as u8); // Offset by PDA count + + // Build infra accounts from Accounts struct + let infra = CreateMintsInfraAccounts { + fee_payer: self.payer.to_account_info(), + compressible_config: self.compressible_config.clone(), + rent_sponsor: self.rent_sponsor.clone(), + cpi_authority: self.cpi_authority.clone(), + }; + + // Build mint account arrays + let mint_seed_accounts = [ + self.mint_signer_0.to_account_info(), + self.mint_signer_1.to_account_info(), + ]; + let mint_accounts = + [self.mint_0.to_account_info(), self.mint_1.to_account_info()]; + + invoke_create_mints( + &mint_seed_accounts, + &mint_accounts, + sdk_params, + infra, + &cpi_accounts, + )?; + } + Ok(WITH_CPI_CONTEXT) // false = mint-only, no CPI context write + }; + inner().map_err(Into::into) } } @@ -188,13 +197,13 @@ impl<'info> LightPreInit<'info, CreateDerivedMintsParams> for CreateDerivedMints // LightFinalize Implementation - No-op for mint-only flow // ============================================================================ -impl<'info> LightFinalize<'info, CreateDerivedMintsParams> for CreateDerivedMintsAccounts<'info> { +impl<'info> LightFinalize, CreateDerivedMintsParams> for CreateDerivedMintsAccounts<'info> { fn light_finalize( &mut self, _remaining_accounts: &[AccountInfo<'info>], _params: &CreateDerivedMintsParams, _has_pre_init: bool, - ) -> std::result::Result<(), LightSdkError> { + ) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { // No-op for mint-only flow - create_mints already executed in light_pre_init Ok(()) } From 91bb91ed6af5fe47ecc7b9b9522abd3ff8fa1a49 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 2 Feb 2026 03:21:33 +0000 Subject: [PATCH 05/15] create light-account --- Cargo.lock | 49 ++- Cargo.toml | 4 +- program-libs/account-checks/src/create_pda.rs | 129 ++++++ program-libs/compressible/src/lib.rs | 26 +- sdk-libs/account/Cargo.toml | 34 ++ sdk-libs/account/src/lib.rs | 176 ++++++++ sdk-libs/client/Cargo.toml | 1 + .../src/interface/create_accounts_proof.rs | 2 +- sdk-libs/client/src/interface/mod.rs | 2 +- .../compressed_token/v2/compress_and_close.rs | 17 +- .../compressed_token/v2/decompress_full.rs | 18 +- sdk-libs/macros/src/lib.rs | 2 +- .../macros/src/light_pdas/account/derive.rs | 56 +-- .../macros/src/light_pdas/account/traits.rs | 10 +- .../macros/src/light_pdas/accounts/builder.rs | 20 +- .../macros/src/light_pdas/accounts/pda.rs | 16 +- .../macros/src/light_pdas/accounts/variant.rs | 18 +- .../macros/src/light_pdas/program/compress.rs | 16 +- .../src/light_pdas/program/decompress.rs | 8 +- .../src/light_pdas/program/instructions.rs | 20 +- .../macros/src/light_pdas/program/parsing.rs | 10 +- .../src/light_pdas/program/variant_enum.rs | 20 +- sdk-libs/sdk-interface/Cargo.toml | 41 -- .../src/.backup/account/token_seeds.rs | 261 ------------ .../src/.backup/accounts/create_pda.rs | 126 ------ .../sdk-interface/src/.backup/cpi/account.rs | 85 ---- .../sdk-interface/src/.backup/cpi/invoke.rs | 187 -------- .../src/.backup/cpi/v1/accounts.rs | 165 -------- .../src/.backup/cpi/v1/invoke.rs | 186 -------- .../sdk-interface/src/.backup/cpi/v1/mod.rs | 32 -- .../src/.backup/cpi/v2/accounts.rs | 98 ----- .../.backup/cpi/v2/accounts_cpi_context.rs | 13 - .../src/.backup/cpi/v2/invoke.rs | 100 ----- .../sdk-interface/src/.backup/cpi/v2/mod.rs | 70 --- .../src/.backup/program/config/create.rs | 305 ------------- .../src/.backup/program/config/update.rs | 102 ----- .../decompression/create_token_account.rs | 160 ------- .../src/.backup/program/decompression/mod.rs | 13 - .../src/.backup/program/decompression/pda.rs | 181 -------- .../program/decompression/processor.rs | 400 ------------------ .../.backup/program/decompression/token.rs | 149 ------- .../src/.backup/program/variant.rs | 187 -------- sdk-libs/sdk-interface/src/error.rs | 79 ---- sdk-libs/sdk-types/Cargo.toml | 12 + sdk-libs/sdk-types/src/error.rs | 50 ++- .../interface}/account/compression_info.rs | 47 +- .../src/interface}/account/light_account.rs | 12 +- .../src/interface}/account/mod.rs | 0 .../src/interface}/account/pack.rs | 8 +- .../src/interface}/account/pda_seeds.rs | 4 +- .../src/interface}/account/token_seeds.rs | 39 +- .../src/interface}/accounts/finalize.rs | 4 +- .../accounts/init_compressed_account.rs | 10 +- .../src/interface}/accounts/mod.rs | 0 .../src/interface}/cpi/account.rs | 21 +- .../src/interface}/cpi/impls.rs | 0 .../src/interface}/cpi/instruction.rs | 2 +- .../src/interface}/cpi/invoke.rs | 46 +- .../src/interface}/cpi/mod.rs | 2 +- .../src/interface/create_accounts_proof.rs | 22 + .../src/interface}/instruction/mod.rs | 2 +- .../interface}/instruction/pack_accounts.rs | 9 +- .../lib.rs => sdk-types/src/interface/mod.rs} | 14 +- .../interface}/program/compression/close.rs | 5 +- .../src/interface}/program/compression/mod.rs | 0 .../src/interface}/program/compression/pda.rs | 30 +- .../program/compression/processor.rs | 23 +- .../src/interface}/program/config/create.rs | 44 +- .../src/interface}/program/config/mod.rs | 27 +- .../src/interface}/program/config/state.rs | 28 +- .../src/interface}/program/config/update.rs | 18 +- .../decompression/create_token_account.rs | 12 +- .../interface}/program/decompression/mod.rs | 0 .../interface}/program/decompression/pda.rs | 46 +- .../program/decompression/processor.rs | 81 ++-- .../interface}/program/decompression/token.rs | 35 +- .../src/interface}/program/mod.rs | 0 .../src/interface}/program/validation.rs | 29 +- .../src/interface}/program/variant.rs | 25 +- sdk-libs/sdk-types/src/lib.rs | 10 +- sdk-libs/sdk/Cargo.toml | 9 +- sdk-libs/sdk/src/cpi/mod.rs | 20 +- sdk-libs/sdk/src/error.rs | 87 ++-- sdk-libs/sdk/src/instruction/mod.rs | 4 +- sdk-libs/sdk/src/lib.rs | 14 +- .../csdk-anchor-full-derived-test/Cargo.toml | 1 + .../src/amm_test/initialize.rs | 2 +- .../src/instruction_accounts.rs | 2 +- .../d10_token_accounts/single_ata.rs | 2 +- .../d10_token_accounts/single_vault.rs | 2 +- .../d11_zero_copy/mixed_zc_borsh.rs | 2 +- .../instructions/d11_zero_copy/multiple_zc.rs | 2 +- .../instructions/d11_zero_copy/with_ata.rs | 2 +- .../d11_zero_copy/with_ctx_seeds.rs | 2 +- .../d11_zero_copy/with_mint_to.rs | 2 +- .../d11_zero_copy/with_params_seeds.rs | 2 +- .../instructions/d11_zero_copy/with_vault.rs | 2 +- .../src/instructions/d5_markers/all.rs | 2 +- .../instructions/d5_markers/rentfree_bare.rs | 2 +- .../instructions/d6_account_types/account.rs | 2 +- .../src/instructions/d6_account_types/all.rs | 2 +- .../instructions/d6_account_types/boxed.rs | 2 +- .../src/instructions/d7_infra_names/all.rs | 2 +- .../instructions/d7_infra_names/creator.rs | 2 +- .../src/instructions/d7_infra_names/payer.rs | 2 +- .../src/instructions/d8_builder_paths/all.rs | 2 +- .../d8_builder_paths/multi_rentfree.rs | 2 +- .../instructions/d8_builder_paths/pda_only.rs | 2 +- .../src/instructions/d9_seeds/all.rs | 2 +- .../src/instructions/d9_seeds/array_bumps.rs | 2 +- .../instructions/d9_seeds/complex_mixed.rs | 2 +- .../instructions/d9_seeds/const_patterns.rs | 2 +- .../src/instructions/d9_seeds/constant.rs | 2 +- .../src/instructions/d9_seeds/ctx_account.rs | 2 +- .../src/instructions/d9_seeds/edge_cases.rs | 2 +- .../instructions/d9_seeds/external_paths.rs | 2 +- .../instructions/d9_seeds/function_call.rs | 2 +- .../instructions/d9_seeds/instruction_data.rs | 2 +- .../src/instructions/d9_seeds/literal.rs | 2 +- .../instructions/d9_seeds/method_chains.rs | 2 +- .../src/instructions/d9_seeds/mixed.rs | 2 +- .../src/instructions/d9_seeds/nested_seeds.rs | 2 +- .../src/instructions/d9_seeds/param.rs | 2 +- .../src/instructions/d9_seeds/param_bytes.rs | 2 +- .../instructions/d9_seeds/qualified_paths.rs | 2 +- .../tests/failing_tests.rs | 4 +- .../tests/instruction_decoder_test.rs | 4 +- sdk-tests/manual-test/Cargo.toml | 7 +- .../src/account_loader/accounts.rs | 2 +- .../src/account_loader/derived_accounts.rs | 84 ++-- .../src/account_loader/derived_state.rs | 27 +- .../manual-test/src/account_loader/state.rs | 2 +- sdk-tests/manual-test/src/all/accounts.rs | 2 +- sdk-tests/manual-test/src/all/derived.rs | 61 ++- .../manual-test/src/all/derived_accounts.rs | 92 ++-- sdk-tests/manual-test/src/ata/derived.rs | 15 +- sdk-tests/manual-test/src/derived_compress.rs | 29 +- .../manual-test/src/derived_decompress.rs | 2 +- .../manual-test/src/derived_light_config.rs | 4 +- sdk-tests/manual-test/src/derived_variants.rs | 16 +- sdk-tests/manual-test/src/lib.rs | 8 +- sdk-tests/manual-test/src/pda/accounts.rs | 2 +- .../manual-test/src/pda/derived_accounts.rs | 90 ++-- .../manual-test/src/pda/derived_state.rs | 27 +- sdk-tests/manual-test/src/pda/state.rs | 2 +- .../manual-test/src/token_account/derived.rs | 34 +- .../manual-test/src/two_mints/accounts.rs | 2 +- .../manual-test/src/two_mints/derived.rs | 47 +- sdk-tests/manual-test/tests/account_loader.rs | 4 +- sdk-tests/manual-test/tests/shared.rs | 2 +- sdk-tests/manual-test/tests/test.rs | 4 +- .../single-account-loader-test/Cargo.toml | 1 + .../single-account-loader-test/src/lib.rs | 2 +- sdk-tests/single-ata-test/Cargo.toml | 1 + sdk-tests/single-ata-test/src/lib.rs | 2 +- sdk-tests/single-mint-test/Cargo.toml | 1 + sdk-tests/single-mint-test/src/lib.rs | 2 +- sdk-tests/single-pda-derive-test/Cargo.toml | 9 +- .../src/instruction_accounts.rs | 6 +- sdk-tests/single-pda-derive-test/src/lib.rs | 6 +- sdk-tests/single-pda-test/Cargo.toml | 1 + .../src/instruction_accounts.rs | 2 +- sdk-tests/single-token-test/Cargo.toml | 1 + sdk-tests/single-token-test/src/lib.rs | 2 +- 164 files changed, 1287 insertions(+), 3846 deletions(-) create mode 100644 program-libs/account-checks/src/create_pda.rs create mode 100644 sdk-libs/account/Cargo.toml create mode 100644 sdk-libs/account/src/lib.rs delete mode 100644 sdk-libs/sdk-interface/Cargo.toml delete mode 100644 sdk-libs/sdk-interface/src/.backup/account/token_seeds.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/accounts/create_pda.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/cpi/account.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/cpi/invoke.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/cpi/v1/accounts.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/cpi/v1/invoke.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/cpi/v1/mod.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/cpi/v2/accounts.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/cpi/v2/accounts_cpi_context.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/cpi/v2/invoke.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/cpi/v2/mod.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/program/config/create.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/program/config/update.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/program/decompression/create_token_account.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/program/decompression/mod.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/program/decompression/pda.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/program/decompression/processor.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/program/decompression/token.rs delete mode 100644 sdk-libs/sdk-interface/src/.backup/program/variant.rs delete mode 100644 sdk-libs/sdk-interface/src/error.rs rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/account/compression_info.rs (90%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/account/light_account.rs (83%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/account/mod.rs (100%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/account/pack.rs (75%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/account/pda_seeds.rs (83%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/account/token_seeds.rs (91%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/accounts/finalize.rs (94%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/accounts/init_compressed_account.rs (92%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/accounts/mod.rs (100%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/cpi/account.rs (87%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/cpi/impls.rs (100%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/cpi/instruction.rs (96%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/cpi/invoke.rs (79%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/cpi/mod.rs (88%) create mode 100644 sdk-libs/sdk-types/src/interface/create_accounts_proof.rs rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/instruction/mod.rs (86%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/instruction/pack_accounts.rs (96%) rename sdk-libs/{sdk-interface/src/lib.rs => sdk-types/src/interface/mod.rs} (87%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/program/compression/close.rs (62%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/program/compression/mod.rs (100%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/program/compression/pda.rs (85%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/program/compression/processor.rs (91%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/program/config/create.rs (84%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/program/config/mod.rs (87%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/program/config/state.rs (83%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/program/config/update.rs (80%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/program/decompression/create_token_account.rs (94%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/program/decompression/mod.rs (100%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/program/decompression/pda.rs (84%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/program/decompression/processor.rs (91%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/program/decompression/token.rs (84%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/program/mod.rs (100%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/program/validation.rs (84%) rename sdk-libs/{sdk-interface/src => sdk-types/src/interface}/program/variant.rs (91%) diff --git a/Cargo.lock b/Cargo.lock index 562ac31e5e..828194d562 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1622,6 +1622,7 @@ dependencies = [ "borsh 0.10.4", "bytemuck", "csdk-anchor-full-derived-test-sdk", + "light-account", "light-anchor-spl", "light-batched-merkle-tree", "light-client", @@ -3460,6 +3461,21 @@ dependencies = [ "libsecp256k1-core", ] +[[package]] +name = "light-account" +version = "0.1.0" +dependencies = [ + "light-account-checks", + "light-compressed-account", + "light-hasher", + "light-macros", + "light-sdk-macros", + "light-sdk-types", + "solana-account-info", + "solana-instruction", + "solana-pubkey 2.4.0", +] + [[package]] name = "light-account-checks" version = "0.7.0" @@ -3576,6 +3592,7 @@ dependencies = [ "light-merkle-tree-metadata", "light-prover-client", "light-sdk", + "light-sdk-types", "light-token", "light-token-interface", "litesvm", @@ -4063,7 +4080,6 @@ dependencies = [ "light-heap", "light-macros", "light-program-profiler", - "light-sdk-interface", "light-sdk-macros", "light-sdk-types", "light-token-interface", @@ -4083,21 +4099,6 @@ dependencies = [ "thiserror 2.0.17", ] -[[package]] -name = "light-sdk-interface" -version = "0.19.0" -dependencies = [ - "borsh 0.10.4", - "bytemuck", - "light-account-checks", - "light-compressed-account", - "light-compressible", - "light-hasher", - "light-sdk-types", - "light-token-interface", - "thiserror 2.0.17", -] - [[package]] name = "light-sdk-macros" version = "0.19.0" @@ -4141,10 +4142,13 @@ version = "0.19.0" dependencies = [ "anchor-lang", "borsh 0.10.4", + "bytemuck", "light-account-checks", "light-compressed-account", + "light-compressible", "light-hasher", "light-macros", + "light-token-interface", "solana-msg 2.2.1", "solana-pubkey 2.4.0", "thiserror 2.0.17", @@ -4506,16 +4510,14 @@ dependencies = [ "anchor-lang", "borsh 0.10.4", "bytemuck", + "light-account", "light-client", "light-compressed-account", "light-compressible", "light-hasher", - "light-heap", "light-macros", "light-program-test", - "light-sdk", "light-sdk-macros", - "light-sdk-types", "light-test-utils", "light-token", "light-token-client", @@ -6554,6 +6556,7 @@ dependencies = [ "anchor-lang", "borsh 0.10.4", "bytemuck", + "light-account", "light-client", "light-compressed-account", "light-compressible", @@ -6584,6 +6587,7 @@ version = "0.1.0" dependencies = [ "anchor-lang", "borsh 0.10.4", + "light-account", "light-client", "light-compressed-account", "light-compressible", @@ -6616,6 +6620,7 @@ version = "0.1.0" dependencies = [ "anchor-lang", "borsh 0.10.4", + "light-account", "light-anchor-spl", "light-client", "light-compressed-account", @@ -6650,16 +6655,14 @@ dependencies = [ "anchor-lang", "borsh 0.10.4", "bytemuck", + "light-account", "light-anchor-spl", "light-client", "light-compressed-account", "light-compressible", "light-hasher", - "light-heap", "light-macros", "light-program-test", - "light-sdk", - "light-sdk-interface", "light-sdk-macros", "light-sdk-types", "light-test-utils", @@ -6684,6 +6687,7 @@ version = "0.1.0" dependencies = [ "anchor-lang", "borsh 0.10.4", + "light-account", "light-client", "light-compressed-account", "light-compressible", @@ -6715,6 +6719,7 @@ version = "0.1.0" dependencies = [ "anchor-lang", "borsh 0.10.4", + "light-account", "light-client", "light-compressed-account", "light-compressible", diff --git a/Cargo.toml b/Cargo.toml index 92f83dd1df..cef51406e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,8 +29,8 @@ members = [ "sdk-libs/event", "sdk-libs/token-client", "sdk-libs/macros", + "sdk-libs/account", "sdk-libs/sdk", - "sdk-libs/sdk-interface", "sdk-libs/sdk-pinocchio", "sdk-libs/sdk-types", "sdk-libs/photon-api", @@ -200,8 +200,8 @@ light-macros = { path = "program-libs/macros", version = "2.2.0" } light-merkle-tree-reference = { path = "program-tests/merkle-tree", version = "4.0.0" } light-heap = { path = "program-libs/heap", version = "2.0.0" } light-prover-client = { path = "prover/client", version = "6.0.0" } +light-account = { path = "sdk-libs/account", version = "0.1.0", default-features = false } light-sdk = { path = "sdk-libs/sdk", version = "0.19.0" } -light-sdk-interface = { path = "sdk-libs/sdk-interface", version = "0.19.0", default-features = false } light-sdk-pinocchio = { path = "sdk-libs/sdk-pinocchio", version = "0.19.0" } light-sdk-macros = { path = "sdk-libs/macros", version = "0.19.0" } light-sdk-types = { path = "sdk-libs/sdk-types", version = "0.19.0", default-features = false } diff --git a/program-libs/account-checks/src/create_pda.rs b/program-libs/account-checks/src/create_pda.rs new file mode 100644 index 0000000000..bd0c33d776 --- /dev/null +++ b/program-libs/account-checks/src/create_pda.rs @@ -0,0 +1,129 @@ +use crate::error::LightSdkTypesError; +pub mod solana { + + impl AccountInfoTrait for solana_account_info::AccountInfo { + + /// Creates a PDA account, handling the case where the account already has lamports. + /// + /// This function handles the edge case where an attacker might have donated lamports + /// to the PDA address before decompression. In that case, `CreateAccount` would fail, + /// so we fall back to `Assign + Allocate + Transfer`. + /// + /// # Arguments + /// * `rent_sponsor` - Account paying for rent (must be a PDA derived from the calling program) + /// * `rent_sponsor_seeds` - Seeds for the rent sponsor PDA (including bump) for signing + /// * `solana_account` - The PDA account to create + /// * `lamports` - Amount of lamports for rent-exemption + /// * `space` - Size of the account in bytes + /// * `owner` - Program that will own the account + /// * `seeds` - Seeds for the target PDA (including bump) for signing + /// * `system_program` - System program + #[inline(never)] + #[allow(clippy::too_many_arguments)] + pub fn create_pda<'info>( + rent_sponsor: &AccountInfo<'info>, + rent_sponsor_seeds: &[&[u8]], + solana_account: &AccountInfo<'info>, + lamports: u64, + space: u64, + owner: &Pubkey, + seeds: &[&[u8]], + ) -> Result<(), LightSdkTypesError> { + use solana_cpi::invoke_signed; + use solana_pubkey::Pubkey; + use solana_system_interface::instruction as system_instruction; + + use crate::AccountInfoTrait; + + /// Cold path: Account already has lamports (e.g., attacker donation). + /// Uses Assign + Allocate + Transfer instead of CreateAccount which would fail. + #[cold] + #[allow(clippy::too_many_arguments)] + fn create_pda_account_with_lamports<'info>( + rent_sponsor: &AccountInfo<'info>, + rent_sponsor_seeds: &[&[u8]], + solana_account: &AccountInfo<'info>, + lamports: u64, + space: u64, + owner: &Pubkey, + seeds: &[&[u8]], + ) -> Result<(), LightSdkTypesError> { + let current_lamports = solana_account.lamports(); + + // Assign owner + let assign_ix = system_instruction::assign(solana_account.key, owner); + invoke_signed( + &assign_ix, + &[solana_account.clone()], + &[seeds], + ) + .map_err(LightSdkTypesError::ProgramError)?; + + // Allocate space + let allocate_ix = system_instruction::allocate(solana_account.key, space); + invoke_signed( + &allocate_ix, + &[solana_account.clone()], + &[seeds], + ) + .map_err(LightSdkTypesError::ProgramError)?; + + // Transfer remaining lamports for rent-exemption if needed + if lamports > current_lamports { + let transfer_ix = system_instruction::transfer( + rent_sponsor.key, + solana_account.key, + lamports - current_lamports, + ); + // Include rent sponsor seeds so the PDA can sign for the transfer + invoke_signed( + &transfer_ix, + &[ + rent_sponsor.clone(), + solana_account.clone(), + system_program.clone(), + ], + &[rent_sponsor_seeds], + ) + .map_err(LightSdkTypesError::ProgramError)?; + } + + Ok(()) + } + + // Cold path: account already has lamports (e.g., attacker donation) + if solana_account.lamports() > 0 { + return create_pda_account_with_lamports( + rent_sponsor, + rent_sponsor_seeds, + solana_account, + lamports, + space, + owner, + seeds, + ); + } + + // Normal path: CreateAccount + // Include both rent sponsor seeds (payer) and PDA seeds (new account) + let create_account_ix = system_instruction::create_account( + rent_sponsor.key, + solana_account.key, + lamports, + space, + owner, + ); + + invoke_signed( + &create_account_ix, + &[ + rent_sponsor.clone(), + solana_account.clone(), + ], + &[rent_sponsor_seeds, seeds], + ) + .map_err(LightSdkTypesError::ProgramError) + } + } + } +} diff --git a/program-libs/compressible/src/lib.rs b/program-libs/compressible/src/lib.rs index 4973a69d8b..2f0a0e217e 100644 --- a/program-libs/compressible/src/lib.rs +++ b/program-libs/compressible/src/lib.rs @@ -13,7 +13,6 @@ //! |------|-------------| //! | [`CompressionInfo`](compression_info::CompressionInfo) | Rent state, authorities, and compression config per account | //! | [`CompressibleConfig`](config::CompressibleConfig) | Program-level config: rent sponsor, authorities, address space | -//! | [`CreateAccountsProof`] | Validity proof and tree info for account init | //! | [`RentConfig`](rent::RentConfig) | Rent function parameters for compression eligibility | //! | [`compression_info`] | `is_compressible`, `claim`, and top-up logic | //! | [`registry_instructions`] | Instructions for the compression registry | @@ -31,27 +30,6 @@ pub mod rent; pub const DECOMPRESSED_PDA_DISCRIMINATOR: [u8; 8] = [255, 255, 255, 255, 255, 255, 255, 0]; #[cfg(feature = "anchor")] -use anchor_lang::{AnchorDeserialize, AnchorSerialize}; +pub(crate) use anchor_lang::{AnchorDeserialize, AnchorSerialize}; #[cfg(not(feature = "anchor"))] -use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; -use light_compressed_account::instruction_data::{ - compressed_proof::ValidityProof, data::PackedAddressTreeInfo, -}; - -/// Proof data for instruction params when creating new compressed accounts. -/// Used in the INIT flow - pass directly to instruction data. -/// All accounts use the same address tree, so only one `address_tree_info` is needed. -#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] -pub struct CreateAccountsProof { - /// The validity proof. - pub proof: ValidityProof, - /// Single packed address tree info (all accounts use same tree). - pub address_tree_info: PackedAddressTreeInfo, - /// Output state tree index for new compressed accounts. - pub output_state_tree_index: u8, - /// State merkle tree index (needed for mint creation decompress validation). - /// This is optional to maintain backwards compatibility. - pub state_tree_index: Option, - /// Offset in remaining_accounts where Light system accounts start. - pub system_accounts_offset: u8, -} +pub(crate) use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; diff --git a/sdk-libs/account/Cargo.toml b/sdk-libs/account/Cargo.toml new file mode 100644 index 0000000000..61a4a827f9 --- /dev/null +++ b/sdk-libs/account/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "light-account" +version = "0.1.0" +description = "Light Protocol account types with Solana AccountInfo specializations" +repository = "https://github.com/Lightprotocol/light-protocol" +license = "Apache-2.0" +edition = "2021" + +[features] +default = ["std"] +std = ["light-sdk-types/std", "light-compressed-account/std"] +alloc = ["light-sdk-types/alloc", "light-compressed-account/alloc"] +token = ["light-sdk-types/token"] +poseidon = ["light-sdk-types/poseidon", "light-hasher/poseidon"] +sha256 = ["light-sdk-types/sha256", "light-hasher/sha256"] +anchor = ["light-sdk-types/anchor"] + +[dependencies] +light-sdk-types = { workspace = true, features = ["std", "v2", "cpi-context"] } +light-sdk-macros = { workspace = true } +light-macros = { workspace = true } +light-account-checks = { workspace = true, features = ["solana"] } +light-hasher = { workspace = true, default-features = false } +light-compressed-account = { workspace = true } +solana-account-info = { workspace = true } +solana-instruction = { workspace = true } +solana-pubkey = { workspace = true } + +[lints.rust.unexpected_cfgs] +level = "allow" +check-cfg = [ + 'cfg(target_os, values("solana"))', + 'cfg(feature, values("frozen-abi", "no-entrypoint"))', +] diff --git a/sdk-libs/account/src/lib.rs b/sdk-libs/account/src/lib.rs new file mode 100644 index 0000000000..ad3b93ed94 --- /dev/null +++ b/sdk-libs/account/src/lib.rs @@ -0,0 +1,176 @@ +//! Light Protocol account types specialized for Solana's AccountInfo. + +pub use solana_account_info::AccountInfo; + +// ===== TYPE ALIASES (structs generic over AI, specialized with AccountInfo) ===== + +pub type CpiAccounts<'c, 'info> = + light_sdk_types::cpi_accounts::v2::CpiAccounts<'c, AccountInfo<'info>>; + +pub type CpiAccountsV1<'c, 'info> = + light_sdk_types::cpi_accounts::v1::CpiAccounts<'c, AccountInfo<'info>>; + +pub type CompressCtx<'a, 'info> = + light_sdk_types::interface::program::compression::processor::CompressCtx< + 'a, + AccountInfo<'info>, + >; + +pub type CompressDispatchFn<'info> = + light_sdk_types::interface::program::compression::processor::CompressDispatchFn< + AccountInfo<'info>, + >; + +pub type DecompressCtx<'a, 'info> = + light_sdk_types::interface::program::decompression::processor::DecompressCtx< + 'a, + AccountInfo<'info>, + >; + +pub type ValidatedPdaContext<'info> = + light_sdk_types::interface::program::validation::ValidatedPdaContext>; + +#[cfg(not(target_os = "solana"))] +pub type PackedAccounts = + light_sdk_types::interface::instruction::PackedAccounts; + +// ===== RE-EXPORTED TRAITS (generic over AI, used with explicit AccountInfo in impls) ===== + +pub use light_sdk_types::interface::accounts::finalize::{LightFinalize, LightPreInit}; +pub use light_sdk_types::interface::cpi::{ + account::CpiAccountsTrait, invoke::InvokeLightSystemProgram, LightCpi, +}; +pub use light_sdk_types::interface::program::decompression::processor::DecompressVariant; + +// ===== RE-EXPORTED CONCRETE TRAITS (no AI parameter) ===== + +pub use light_sdk_types::interface::account::compression_info::{ + claim_completed_epoch_rent, CompressAs, CompressedAccountData, CompressedInitSpace, + CompressionInfo, CompressionInfoField, CompressionState, HasCompressionInfo, Space, + COMPRESSION_INFO_SIZE, OPTION_COMPRESSION_INFO_SPACE, +}; +pub use light_sdk_types::interface::account::light_account::{AccountType, LightAccount}; +#[cfg(not(target_os = "solana"))] +pub use light_sdk_types::interface::account::pack::Pack; +pub use light_sdk_types::interface::account::pack::Unpack; +pub use light_sdk_types::interface::account::pda_seeds::{HasTokenVariant, PdaSeedDerivation}; +pub use light_sdk_types::interface::accounts::init_compressed_account::{ + prepare_compressed_account_on_init, reimburse_rent, +}; +pub use light_sdk_types::interface::create_accounts_proof::CreateAccountsProof; +pub use light_sdk_types::interface::program::variant::{ + IntoVariant, LightAccountVariantTrait, PackedLightAccountVariantTrait, +}; +pub use light_sdk_types::interface::rent; + +// ===== RE-EXPORTED GENERIC FUNCTIONS (AI inferred from call-site args) ===== + +pub use light_sdk_types::interface::cpi::invoke::invoke_light_system_program; +pub use light_sdk_types::interface::program::compression::close::close; +pub use light_sdk_types::interface::program::compression::pda::prepare_account_for_compression; +pub use light_sdk_types::interface::program::compression::processor::{ + process_compress_pda_accounts_idempotent, CompressAndCloseParams, +}; +pub use light_sdk_types::interface::program::config::{ + process_initialize_light_config_checked, process_update_light_config, + InitializeLightConfigParams, LightConfig, UpdateLightConfigParams, COMPRESSIBLE_CONFIG_SEED, + MAX_ADDRESS_TREES_PER_SPACE, +}; +pub use light_sdk_types::interface::program::decompression::pda::prepare_account_for_decompression; +pub use light_sdk_types::interface::program::decompression::processor::{ + process_decompress_pda_accounts_idempotent, DecompressIdempotentParams, +}; +pub use light_sdk_types::interface::program::validation::{ + extract_tail_accounts, is_pda_initialized, should_skip_compression, + split_at_system_accounts_offset, validate_compress_accounts, validate_decompress_accounts, +}; + +// ===== TOKEN-GATED RE-EXPORTS ===== + +#[cfg(feature = "token")] +pub use light_sdk_types::interface::account::token_seeds::{ + PackedTokenData, TokenDataWithPackedSeeds, TokenDataWithSeeds, +}; +#[cfg(feature = "token")] +pub use light_sdk_types::interface::program::decompression::processor::process_decompress_accounts_idempotent; +#[cfg(feature = "token")] +pub use light_sdk_types::interface::program::decompression::token::prepare_token_account_for_decompression; +#[cfg(feature = "token")] +pub use light_sdk_types::interface::program::variant::{PackedTokenSeeds, UnpackedTokenSeeds}; + +/// Token sub-module for paths like `light_account::token::TokenDataWithSeeds`. +#[cfg(feature = "token")] +pub mod token { + pub use light_sdk_types::interface::account::token_seeds::{ + ExtensionInstructionData, MultiInputTokenDataWithContext, PackedTokenData, + TokenDataWithPackedSeeds, TokenDataWithSeeds, + }; + pub use light_sdk_types::interface::program::decompression::token::prepare_token_account_for_decompression; +} + +/// Compression info sub-module for paths like `light_account::compression_info::CompressedInitSpace`. +pub mod compression_info { + pub use light_sdk_types::interface::account::compression_info::*; +} + +// ===== CPI / SDK-TYPES RE-EXPORTS ===== + +pub use light_sdk_types::cpi_accounts::CpiAccountsConfig; +pub use light_sdk_types::cpi_context_write::CpiContextWriteAccounts; +pub use light_sdk_types::interface::program::config::create::process_initialize_light_config; + +/// Sub-module for generic `PackedAccounts` (not specialized to AccountMeta). +#[cfg(not(target_os = "solana"))] +pub mod interface { + pub mod instruction { + pub use light_sdk_types::interface::instruction::PackedAccounts; + } +} + +/// Sub-module for account_meta types (e.g. `CompressedAccountMetaNoLamportsNoAddress`). +pub mod account_meta { + pub use light_sdk_types::instruction::account_meta::*; +} + +// ===== ACCOUNT-CHECKS RE-EXPORTS (used by macro-generated code) ===== + +/// Re-export `light_account_checks` so consumers can use `light_account::light_account_checks::*`. +pub extern crate light_account_checks; +pub use light_account_checks::packed_accounts; +pub use light_account_checks::{AccountInfoTrait, AccountMetaTrait}; + +// ===== CONVENIENCE RE-EXPORTS ===== + +pub use light_account_checks::discriminator::Discriminator as LightDiscriminator; +pub use light_compressed_account::instruction_data::compressed_proof::ValidityProof; +pub use light_macros::{derive_light_cpi_signer, derive_light_cpi_signer_pda}; +pub use light_sdk_macros::{ + // Attribute macros + account, + // Proc macros + derive_light_rent_sponsor, + derive_light_rent_sponsor_pda, + light_program, + // Derive macros + CompressAs, + Compressible, + HasCompressionInfo, + LightAccount, + LightAccounts, + LightDiscriminator, + LightHasher, + LightHasherSha, + LightProgram, +}; +pub use light_sdk_types::error::LightSdkTypesError; +pub use light_sdk_types::instruction::*; +pub use light_sdk_types::{constants, CpiSigner}; + +// ===== UTILITY FUNCTIONS ===== + +/// Derives the rent sponsor PDA for a given program. +/// +/// Seeds: `["rent_sponsor"]` +pub fn derive_rent_sponsor_pda(program_id: &solana_pubkey::Pubkey) -> (solana_pubkey::Pubkey, u8) { + solana_pubkey::Pubkey::find_program_address(&[constants::RENT_SPONSOR_SEED], program_id) +} diff --git a/sdk-libs/client/Cargo.toml b/sdk-libs/client/Cargo.toml index fa614d97fe..3ee84cc237 100644 --- a/sdk-libs/client/Cargo.toml +++ b/sdk-libs/client/Cargo.toml @@ -52,6 +52,7 @@ light-compressed-token-sdk = { workspace = true } light-token-interface = { workspace = true } light-event = { workspace = true } light-compressible = { workspace = true } +light-sdk-types = { workspace = true } photon-api = { workspace = true } light-prover-client = { workspace = true } diff --git a/sdk-libs/client/src/interface/create_accounts_proof.rs b/sdk-libs/client/src/interface/create_accounts_proof.rs index 85a5d7f380..aaac726e38 100644 --- a/sdk-libs/client/src/interface/create_accounts_proof.rs +++ b/sdk-libs/client/src/interface/create_accounts_proof.rs @@ -92,7 +92,7 @@ impl CreateAccountsProofInput { } } -pub use light_compressible::CreateAccountsProof; +pub use light_sdk_types::interface::CreateAccountsProof; /// Result of `get_create_accounts_proof`. pub struct CreateAccountsProofResult { diff --git a/sdk-libs/client/src/interface/mod.rs b/sdk-libs/client/src/interface/mod.rs index 4c08394609..51947a6d7b 100644 --- a/sdk-libs/client/src/interface/mod.rs +++ b/sdk-libs/client/src/interface/mod.rs @@ -21,12 +21,12 @@ pub use decompress_mint::{ DecompressMintError, MintInterface, MintState, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, }; pub use initialize_config::InitializeRentFreeConfig; -pub use light_compressible::CreateAccountsProof; pub use light_program_interface::{ all_hot, any_cold, discriminator, matches_discriminator, AccountSpec, AccountToFetch, ColdContext, LightProgramInterface, PdaSpec, }; pub use light_sdk::LightConfig; +pub use light_sdk_types::interface::CreateAccountsProof; pub use light_token::compat::TokenData; pub use load_accounts::{create_load_instructions, LoadAccountsError}; pub use pack::{pack_proof, PackError, PackedProofResult}; diff --git a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs index 047a6d1533..20722e625c 100644 --- a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs +++ b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs @@ -1,7 +1,7 @@ use light_program_profiler::profile; // PackedAccounts and AccountMetasVec are only available off-chain (client-side) #[cfg(not(target_os = "solana"))] -use light_sdk::interface::error::LightPdaError; +use light_sdk_types::error::LightSdkTypesError; #[cfg(not(target_os = "solana"))] use light_sdk::{ instruction::{AccountMetasVec, PackedAccounts, SystemAccountMetaConfig}, @@ -49,10 +49,8 @@ pub fn pack_for_compress_and_close( ) -> Result { let (ctoken_account, _) = Token::zero_copy_at(ctoken_account_data)?; let source_index = packed_accounts.insert_or_get(ctoken_account_pubkey); - let mint_index = - packed_accounts.insert_or_get(Pubkey::from(ctoken_account.mint.to_bytes())); - let owner_index = - packed_accounts.insert_or_get(Pubkey::from(ctoken_account.owner.to_bytes())); + let mint_index = packed_accounts.insert_or_get(Pubkey::from(ctoken_account.mint.to_bytes())); + let owner_index = packed_accounts.insert_or_get(Pubkey::from(ctoken_account.owner.to_bytes())); // Get compression info from Compressible extension let compressible_ext = ctoken_account @@ -406,7 +404,10 @@ impl AccountMetasVec for CompressAndCloseAccounts { /// Adds: /// 1. system accounts if not set /// 2. compressed token program and ctoken cpi authority pda to pre accounts - fn get_account_metas_vec(&self, accounts: &mut PackedAccounts) -> Result<(), LightPdaError> { + fn get_account_metas_vec( + &self, + accounts: &mut PackedAccounts, + ) -> Result<(), LightSdkTypesError> { if !accounts.system_accounts_set() { let mut config = SystemAccountMetaConfig::default(); config.self_program = self.self_program; @@ -418,12 +419,12 @@ impl AccountMetasVec for CompressAndCloseAccounts { { if self.cpi_context.is_some() { msg!("Error: cpi_context is set but 'cpi-context' feature is not enabled"); - return Err(LightPdaError::InvalidInstructionData); + return Err(LightSdkTypesError::InvalidInstructionData); } } accounts .add_system_accounts_v2(config) - .map_err(LightPdaError::from)?; + .map_err(LightSdkTypesError::from)?; } // Add both accounts in one operation for better performance accounts.pre_accounts.extend_from_slice(&[ diff --git a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs index e25a9221c7..5f310f4d97 100644 --- a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs +++ b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs @@ -2,8 +2,8 @@ use light_compressed_account::compressed_account::PackedMerkleContext; use light_compressed_account::instruction_data::compressed_proof::ValidityProof; use light_program_profiler::profile; +use light_sdk_types::error::LightSdkTypesError; use light_sdk::{instruction::PackedStateTreeInfo, Unpack}; -use light_sdk::interface::error::LightPdaError; // Pack and PackedAccounts only available off-chain (client-side) #[cfg(not(target_os = "solana"))] use light_sdk::{ @@ -64,7 +64,7 @@ impl Pack for DecompressFullInput { fn pack( &self, remaining_accounts: &mut PackedAccounts, - ) -> Result { + ) -> Result { let owner_is_signer = !self.is_ata; let source = MultiInputTokenDataWithContext { @@ -106,20 +106,20 @@ impl<'a> Unpack> for DecompressFullIndices { fn unpack( &self, remaining_accounts: &[AccountInfo<'a>], - ) -> Result { + ) -> Result { let owner = *remaining_accounts .get(self.source.owner as usize) - .ok_or(LightPdaError::InvalidInstructionData)? + .ok_or(LightSdkTypesError::InvalidInstructionData)? .key; let mint = *remaining_accounts .get(self.source.mint as usize) - .ok_or(LightPdaError::InvalidInstructionData)? + .ok_or(LightSdkTypesError::InvalidInstructionData)? .key; let delegate = if self.source.has_delegate { Some( *remaining_accounts .get(self.source.delegate as usize) - .ok_or(LightPdaError::InvalidInstructionData)? + .ok_or(LightSdkTypesError::InvalidInstructionData)? .key, ) } else { @@ -127,7 +127,7 @@ impl<'a> Unpack> for DecompressFullIndices { }; let destination = *remaining_accounts .get(self.destination_index as usize) - .ok_or(LightPdaError::InvalidInstructionData)? + .ok_or(LightSdkTypesError::InvalidInstructionData)? .key; Ok(DecompressFullInput { @@ -356,7 +356,7 @@ impl AccountMetasVec for DecompressFullAccounts { fn get_account_metas_vec( &self, accounts: &mut PackedAccounts, - ) -> Result<(), LightPdaError> { + ) -> Result<(), LightSdkTypesError> { if !accounts.system_accounts_set() { #[cfg(feature = "cpi-context")] let config = { @@ -374,7 +374,7 @@ impl AccountMetasVec for DecompressFullAccounts { accounts .add_system_accounts_v2(config) - .map_err(LightPdaError::from)?; + .map_err(LightSdkTypesError::from)?; } // Add both accounts in one operation for better performance accounts.pre_accounts.extend_from_slice(&[ diff --git a/sdk-libs/macros/src/lib.rs b/sdk-libs/macros/src/lib.rs index 9a04d70371..c26ce1238d 100644 --- a/sdk-libs/macros/src/lib.rs +++ b/sdk-libs/macros/src/lib.rs @@ -394,7 +394,7 @@ pub fn derive_light_rent_sponsor(input: TokenStream) -> TokenStream { /// - Accounts marked with `#[light_account(init, mint, ...)]` (compressed mints) /// - Accounts marked with `#[light_account(token, ...)]` (rent-free token accounts) /// -/// The trait is defined in `light_sdk::interface::LightFinalize`. +/// The trait is defined in `light_account::LightFinalize`. /// /// ## Usage - PDAs /// diff --git a/sdk-libs/macros/src/light_pdas/account/derive.rs b/sdk-libs/macros/src/light_pdas/account/derive.rs index 2362b8fe3d..c4fad94821 100644 --- a/sdk-libs/macros/src/light_pdas/account/derive.rs +++ b/sdk-libs/macros/src/light_pdas/account/derive.rs @@ -237,7 +237,7 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { ); }; }, - quote! { light_sdk::interface::AccountType::PdaZeroCopy }, + quote! { light_account::AccountType::PdaZeroCopy }, quote! { core::mem::size_of::() }, ) } else { @@ -250,7 +250,7 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { ); }; }, - quote! { light_sdk::interface::AccountType::Pda }, + quote! { light_account::AccountType::Pda }, quote! { ::INIT_SPACE }, ) }; @@ -261,8 +261,8 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { #size_assertion - impl light_sdk::interface::LightAccount for #struct_name { - const ACCOUNT_TYPE: light_sdk::interface::AccountType = #account_type_token; + impl light_account::LightAccount for #struct_name { + const ACCOUNT_TYPE: light_account::AccountType = #account_type_token; type Packed = #packed_struct_name; @@ -278,25 +278,25 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { &mut self.compression_info } - fn set_decompressed(&mut self, config: &light_sdk::interface::LightConfig, current_slot: u64) { + fn set_decompressed(&mut self, config: &light_account::LightConfig, current_slot: u64) { self.compression_info = light_sdk::compressible::CompressionInfo::new_from_config(config, current_slot); #compress_as_assignments } #[cfg(not(target_os = "solana"))] #[inline(never)] - fn pack( + fn pack( &self, accounts: &mut light_sdk::interface::instruction::PackedAccounts, - ) -> std::result::Result { + ) -> std::result::Result { #pack_body } #[inline(never)] - fn unpack( + fn unpack( packed: &Self::Packed, - accounts: &light_sdk::light_account_checks::packed_accounts::ProgramPackedAccounts, - ) -> std::result::Result { + accounts: &light_account::packed_accounts::ProgramPackedAccounts, + ) -> std::result::Result { #unpack_body } } @@ -304,50 +304,50 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { // V1 compatibility: Pack trait (delegates to LightAccount::pack) // Pack trait is only available off-chain (client-side) #[cfg(not(target_os = "solana"))] - impl light_sdk::interface::Pack for #struct_name { + impl light_account::Pack for #struct_name { type Packed = #packed_struct_name; fn pack( &self, remaining_accounts: &mut light_sdk::interface::instruction::PackedAccounts, - ) -> std::result::Result { - ::pack(self, remaining_accounts) + ) -> std::result::Result { + ::pack(self, remaining_accounts) } } // V1 compatibility: Unpack trait for packed struct - impl light_sdk::interface::Unpack for #packed_struct_name { + impl light_account::Unpack for #packed_struct_name { type Unpacked = #struct_name; fn unpack( &self, remaining_accounts: &[AI], - ) -> std::result::Result { + ) -> std::result::Result { // Create a ProgramPackedAccounts wrapper from remaining_accounts - let accounts = light_sdk::light_account_checks::packed_accounts::ProgramPackedAccounts { + let accounts = light_account::packed_accounts::ProgramPackedAccounts { accounts: remaining_accounts }; - <#struct_name as light_sdk::interface::LightAccount>::unpack(self, &accounts) + <#struct_name as light_account::LightAccount>::unpack(self, &accounts) } } // V1 compatibility: HasCompressionInfo trait (wraps non-Option compression_info) - impl light_sdk::interface::HasCompressionInfo for #struct_name { - fn compression_info(&self) -> std::result::Result<&light_sdk::interface::CompressionInfo, light_sdk::interface::error::LightPdaError> { + impl light_account::HasCompressionInfo for #struct_name { + fn compression_info(&self) -> std::result::Result<&light_account::CompressionInfo, light_sdk_types::error::LightSdkTypesError> { Ok(&self.compression_info) } - fn compression_info_mut(&mut self) -> std::result::Result<&mut light_sdk::interface::CompressionInfo, light_sdk::interface::error::LightPdaError> { + fn compression_info_mut(&mut self) -> std::result::Result<&mut light_account::CompressionInfo, light_sdk_types::error::LightSdkTypesError> { Ok(&mut self.compression_info) } - fn compression_info_mut_opt(&mut self) -> &mut Option { + fn compression_info_mut_opt(&mut self) -> &mut Option { // V2 types use non-Option CompressionInfo, so this can't return a reference // This method is only used by V1 code paths that expect Option panic!("compression_info_mut_opt not supported for LightAccount types (use compression_info_mut instead)") } - fn set_compression_info_none(&mut self) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { + fn set_compression_info_none(&mut self) -> std::result::Result<(), light_sdk_types::error::LightSdkTypesError> { // V2 types use non-Option CompressionInfo // Setting to "compressed" state is the equivalent of "None" for V1 self.compression_info = light_sdk::compressible::CompressionInfo::compressed(); @@ -359,12 +359,12 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { impl light_sdk::account::Size for #struct_name { #[inline] fn size(&self) -> std::result::Result { - Ok(::INIT_SPACE) + Ok(::INIT_SPACE) } } // V1 compatibility: CompressAs trait - impl light_sdk::interface::CompressAs for #struct_name { + impl light_account::CompressAs for #struct_name { type Output = Self; fn compress_as(&self) -> std::borrow::Cow<'_, Self::Output> { @@ -373,8 +373,8 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { } // V1 compatibility: CompressedInitSpace trait - impl light_sdk::interface::CompressedInitSpace for #struct_name { - const COMPRESSED_INIT_SPACE: usize = ::INIT_SPACE; + impl light_account::CompressedInitSpace for #struct_name { + const COMPRESSED_INIT_SPACE: usize = ::INIT_SPACE; } }; @@ -519,7 +519,7 @@ fn generate_unpack_body( #field_name: { let account = accounts .get_u8(packed.#field_name, #error_msg) - .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; + .map_err(|_| light_sdk_types::error::LightSdkTypesError::InvalidInstructionData)?; solana_pubkey::Pubkey::from(account.key()) } } @@ -684,7 +684,7 @@ mod tests { // Should contain unified LightAccount implementation assert!( - output.contains("impl light_sdk :: interface :: LightAccount for UserRecord"), + output.contains("impl light_account :: LightAccount for UserRecord"), "Should implement LightAccount trait" ); diff --git a/sdk-libs/macros/src/light_pdas/account/traits.rs b/sdk-libs/macros/src/light_pdas/account/traits.rs index 674321c9d1..48aff1a5ce 100644 --- a/sdk-libs/macros/src/light_pdas/account/traits.rs +++ b/sdk-libs/macros/src/light_pdas/account/traits.rs @@ -69,13 +69,13 @@ fn generate_has_compression_info_impl( compression_info_first: bool, ) -> TokenStream { quote! { - impl light_sdk::interface::CompressionInfoField for #struct_name { + impl light_account::CompressionInfoField for #struct_name { const COMPRESSION_INFO_FIRST: bool = #compression_info_first; - fn compression_info_field(&self) -> &Option { + fn compression_info_field(&self) -> &Option { &self.compression_info } - fn compression_info_field_mut(&mut self) -> &mut Option { + fn compression_info_field_mut(&mut self) -> &mut Option { &mut self.compression_info } } @@ -135,7 +135,7 @@ fn generate_compress_as_impl( field_assignments: &[TokenStream], ) -> TokenStream { quote! { - impl light_sdk::interface::CompressAs for #struct_name { + impl light_account::CompressAs for #struct_name { type Output = Self; fn compress_as(&self) -> std::borrow::Cow<'_, Self::Output> { @@ -170,7 +170,7 @@ fn generate_size_impl(struct_name: &Ident) -> TokenStream { /// Generates the CompressedInitSpace trait implementation fn generate_compressed_init_space_impl(struct_name: &Ident) -> TokenStream { quote! { - impl light_sdk::interface::CompressedInitSpace for #struct_name { + impl light_account::CompressedInitSpace for #struct_name { const COMPRESSED_INIT_SPACE: usize = Self::LIGHT_DISCRIMINATOR.len() + Self::INIT_SPACE; } } diff --git a/sdk-libs/macros/src/light_pdas/accounts/builder.rs b/sdk-libs/macros/src/light_pdas/accounts/builder.rs index b65eaad3a8..88668a8849 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/builder.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/builder.rs @@ -144,24 +144,24 @@ impl LightAccountsBuilder { Ok(quote! { #[automatically_derived] - impl #impl_generics light_sdk::interface::LightPreInit<'info, ()> for #struct_name #ty_generics #where_clause { + impl #impl_generics light_account::LightPreInit, ()> for #struct_name #ty_generics #where_clause { fn light_pre_init( &mut self, _remaining: &[solana_account_info::AccountInfo<'info>], _params: &(), - ) -> std::result::Result { + ) -> std::result::Result { Ok(false) } } #[automatically_derived] - impl #impl_generics light_sdk::interface::LightFinalize<'info, ()> for #struct_name #ty_generics #where_clause { + impl #impl_generics light_account::LightFinalize, ()> for #struct_name #ty_generics #where_clause { fn light_finalize( &mut self, _remaining: &[solana_account_info::AccountInfo<'info>], _params: &(), _has_pre_init: bool, - ) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { + ) -> std::result::Result<(), light_sdk_types::error::LightSdkTypesError> { Ok(()) } } @@ -304,7 +304,7 @@ impl LightAccountsBuilder { _remaining, light_sdk::cpi::CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER), ); - let compression_config_data = light_sdk::interface::LightConfig::load_checked( + let compression_config_data = light_account::LightConfig::load_checked( &self.#compression_config, &crate::ID, )?; @@ -349,7 +349,7 @@ impl LightAccountsBuilder { _remaining, crate::LIGHT_CPI_SIGNER, ); - let compression_config_data = light_sdk::interface::LightConfig::load_checked( + let compression_config_data = light_account::LightConfig::load_checked( &self.#compression_config, &crate::ID, )?; @@ -405,12 +405,12 @@ impl LightAccountsBuilder { Ok(quote! { #[automatically_derived] - impl #impl_generics light_sdk::interface::LightPreInit<'info, #params_type> for #struct_name #ty_generics #where_clause { + impl #impl_generics light_account::LightPreInit, #params_type> for #struct_name #ty_generics #where_clause { fn light_pre_init( &mut self, _remaining: &[solana_account_info::AccountInfo<'info>], #params_ident: &#params_type, - ) -> std::result::Result { + ) -> std::result::Result { use anchor_lang::ToAccountInfo; #body } @@ -430,13 +430,13 @@ impl LightAccountsBuilder { Ok(quote! { #[automatically_derived] - impl #impl_generics light_sdk::interface::LightFinalize<'info, #params_type> for #struct_name #ty_generics #where_clause { + impl #impl_generics light_account::LightFinalize, #params_type> for #struct_name #ty_generics #where_clause { fn light_finalize( &mut self, _remaining: &[solana_account_info::AccountInfo<'info>], #params_ident: &#params_type, _has_pre_init: bool, - ) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { + ) -> std::result::Result<(), light_sdk_types::error::LightSdkTypesError> { use anchor_lang::ToAccountInfo; #body } diff --git a/sdk-libs/macros/src/light_pdas/accounts/pda.rs b/sdk-libs/macros/src/light_pdas/accounts/pda.rs index 813cbc845e..d31b51753e 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/pda.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/pda.rs @@ -105,7 +105,7 @@ impl<'a> PdaBlockBuilder<'a> { } else if self.field.is_boxed { quote! { { - use light_sdk::interface::LightAccount; + use light_account::LightAccount; use anchor_lang::AnchorSerialize; let current_slot = anchor_lang::solana_program::sysvar::clock::Clock::get()?.slot; // Get account info BEFORE mutable borrow @@ -119,15 +119,15 @@ impl<'a> PdaBlockBuilder<'a> { // Now serialize - the mutable borrow above is released let mut data = account_info .try_borrow_mut_data() - .map_err(|_| light_sdk::interface::error::LightPdaError::ConstraintViolation)?; + .map_err(|_| light_sdk_types::error::LightSdkTypesError::ConstraintViolation)?; self.#ident.serialize(&mut &mut data[8..]) - .map_err(|_| light_sdk::interface::error::LightPdaError::ConstraintViolation)?; + .map_err(|_| light_sdk_types::error::LightSdkTypesError::ConstraintViolation)?; } } } else { quote! { { - use light_sdk::interface::LightAccount; + use light_account::LightAccount; use anchor_lang::AnchorSerialize; let current_slot = anchor_lang::solana_program::sysvar::clock::Clock::get()?.slot; // Get account info BEFORE mutable borrow @@ -141,9 +141,9 @@ impl<'a> PdaBlockBuilder<'a> { // Now serialize - the mutable borrow above is released let mut data = account_info .try_borrow_mut_data() - .map_err(|_| light_sdk::interface::error::LightPdaError::ConstraintViolation)?; + .map_err(|_| light_sdk_types::error::LightSdkTypesError::ConstraintViolation)?; self.#ident.serialize(&mut &mut data[8..]) - .map_err(|_| light_sdk::interface::error::LightPdaError::ConstraintViolation)?; + .map_err(|_| light_sdk_types::error::LightSdkTypesError::ConstraintViolation)?; } } } @@ -170,7 +170,7 @@ impl<'a> PdaBlockBuilder<'a> { // Explicit type annotation for tree_info let tree_info: &::light_sdk::sdk_types::PackedAddressTreeInfo = &#addr_tree_info; - ::light_sdk::interface::prepare_compressed_account_on_init( + ::light_account::prepare_compressed_account_on_init( &#account_key, &#address_tree_pubkey, tree_info, @@ -248,7 +248,7 @@ pub(super) fn generate_rent_reimbursement_block( let __created_accounts: [solana_account_info::AccountInfo<'info>; #count] = [ #(#account_info_exprs),* ]; - ::light_sdk::interface::reimburse_rent( + ::light_account::reimburse_rent( &__created_accounts, &self.#fee_payer.to_account_info(), &self.#rent_sponsor.to_account_info(), diff --git a/sdk-libs/macros/src/light_pdas/accounts/variant.rs b/sdk-libs/macros/src/light_pdas/accounts/variant.rs index 677bd74163..84138837bf 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/variant.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/variant.rs @@ -240,7 +240,7 @@ impl VariantBuilder { // NOTE: pack() is NOT generated here - it's in the Pack trait impl (off-chain only) quote! { - impl light_sdk::interface::LightAccountVariantTrait<#seed_count> for #variant_name { + impl light_account::LightAccountVariantTrait<#seed_count> for #variant_name { const PROGRAM_ID: Pubkey = crate::ID; type Seeds = #seeds_struct_name; @@ -282,17 +282,17 @@ impl VariantBuilder { let unpack_data = quote! { { let packed_accounts = light_sdk::light_account_checks::packed_accounts::ProgramPackedAccounts { accounts }; - <#inner_type as light_sdk::interface::LightAccount>::unpack(&self.data, &packed_accounts) + <#inner_type as light_account::LightAccount>::unpack(&self.data, &packed_accounts) .map_err(|_| anchor_lang::error::ErrorCode::InvalidProgramId)? } }; quote! { - impl light_sdk::interface::PackedLightAccountVariantTrait<#seed_count> for #packed_variant_name { + impl light_account::PackedLightAccountVariantTrait<#seed_count> for #packed_variant_name { type Unpacked = #variant_name; - const ACCOUNT_TYPE: light_sdk::interface::AccountType = - <#inner_type as light_sdk::interface::LightAccount>::ACCOUNT_TYPE; + const ACCOUNT_TYPE: light_account::AccountType = + <#inner_type as light_account::LightAccount>::ACCOUNT_TYPE; fn bump(&self) -> u8 { self.seeds.bump @@ -317,11 +317,11 @@ impl VariantBuilder { Ok([#(#packed_seed_refs_items,)* bump_storage]) } - fn into_in_token_data(&self, _tree_info: &light_sdk::instruction::PackedStateTreeInfo, _output_queue_index: u8) -> anchor_lang::Result { + fn into_in_token_data(&self, _tree_info: &light_sdk::instruction::PackedStateTreeInfo, _output_queue_index: u8) -> anchor_lang::Result { Err(solana_program_error::ProgramError::InvalidAccountData.into()) } - fn into_in_tlv(&self) -> anchor_lang::Result>> { + fn into_in_tlv(&self) -> anchor_lang::Result>> { Ok(None) } } @@ -342,7 +342,7 @@ impl VariantBuilder { // Use LightAccount::pack for all accounts (including zero-copy) let pack_data = quote! { - <#inner_type as light_sdk::interface::LightAccount>::pack(&self.data, accounts) + <#inner_type as light_account::LightAccount>::pack(&self.data, accounts) .map_err(|_| solana_program_error::ProgramError::InvalidAccountData)? }; @@ -356,7 +356,7 @@ impl VariantBuilder { &self, accounts: &mut light_sdk::instruction::PackedAccounts, ) -> std::result::Result { - use light_sdk::interface::LightAccountVariantTrait; + use light_account::LightAccountVariantTrait; let (_, bump) = self.derive_pda(); Ok(#packed_variant_name { seeds: #packed_seeds_struct_name { diff --git a/sdk-libs/macros/src/light_pdas/program/compress.rs b/sdk-libs/macros/src/light_pdas/program/compress.rs index ceb2064b10..54a841a9bb 100644 --- a/sdk-libs/macros/src/light_pdas/program/compress.rs +++ b/sdk-libs/macros/src/light_pdas/program/compress.rs @@ -101,7 +101,7 @@ impl CompressBuilder { let pod_bytes = &data[8..8 + core::mem::size_of::<#name>()]; let mut account_data: #name = *bytemuck::from_bytes(pod_bytes); drop(data); - light_sdk::interface::prepare_account_for_compression( + light_account::prepare_account_for_compression( account_info, &mut account_data, meta, index, ctx, ) } @@ -116,7 +116,7 @@ impl CompressBuilder { let mut account_data = #name::deserialize(&mut reader) .map_err(|_| solana_program_error::ProgramError::InvalidAccountData)?; drop(data); - light_sdk::interface::prepare_account_for_compression( + light_account::prepare_account_for_compression( account_info, &mut account_data, meta, index, ctx, ) } @@ -129,7 +129,7 @@ impl CompressBuilder { account_info: &anchor_lang::prelude::AccountInfo<'info>, meta: &light_sdk::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, index: usize, - ctx: &mut light_sdk::interface::CompressCtx<'_, 'info>, + ctx: &mut light_account::CompressCtx<'_, 'info>, ) -> std::result::Result<(), solana_program_error::ProgramError> { use light_sdk::LightDiscriminator; use borsh::BorshDeserialize; @@ -153,7 +153,7 @@ impl CompressBuilder { remaining_accounts: &[solana_account_info::AccountInfo<'info>], instruction_data: &[u8], ) -> Result<()> { - light_sdk::interface::process_compress_pda_accounts_idempotent( + light_account::process_compress_pda_accounts_idempotent( remaining_accounts, instruction_data, __compress_dispatch, @@ -326,7 +326,7 @@ impl CompressBuilder { let pod_bytes = &data[8..8 + core::mem::size_of::<#name>()]; let mut account_data: #name = *bytemuck::from_bytes(pod_bytes); drop(data); - light_sdk::interface::prepare_account_for_compression( + light_account::prepare_account_for_compression( account_info, &mut account_data, meta, index, ctx, ) } @@ -338,7 +338,7 @@ impl CompressBuilder { let mut account_data = #name::deserialize(&mut reader) .map_err(|_| solana_program_error::ProgramError::InvalidAccountData)?; drop(data); - light_sdk::interface::prepare_account_for_compression( + light_account::prepare_account_for_compression( account_info, &mut account_data, meta, index, ctx, ) } @@ -352,7 +352,7 @@ impl CompressBuilder { account_info: &anchor_lang::prelude::AccountInfo<'info>, meta: &light_sdk::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, index: usize, - ctx: &mut light_sdk::interface::CompressCtx<'_, 'info>, + ctx: &mut light_account::CompressCtx<'_, 'info>, ) -> std::result::Result<(), solana_program_error::ProgramError> { use light_sdk::LightDiscriminator; use borsh::BorshDeserialize; @@ -390,7 +390,7 @@ impl CompressBuilder { // For Borsh types, use CompressedInitSpace trait quote! { const _: () = { - const COMPRESSED_SIZE: usize = 8 + <#qualified_type as light_sdk::interface::compression_info::CompressedInitSpace>::COMPRESSED_INIT_SPACE; + const COMPRESSED_SIZE: usize = 8 + <#qualified_type as light_account::compression_info::CompressedInitSpace>::COMPRESSED_INIT_SPACE; if COMPRESSED_SIZE > 800 { panic!(concat!( "Compressed account '", stringify!(#qualified_type), "' exceeds 800-byte compressible account size limit. If you need support for larger accounts, send a message to team@lightprotocol.com" diff --git a/sdk-libs/macros/src/light_pdas/program/decompress.rs b/sdk-libs/macros/src/light_pdas/program/decompress.rs index ea4d2cc1ac..6b2d141279 100644 --- a/sdk-libs/macros/src/light_pdas/program/decompress.rs +++ b/sdk-libs/macros/src/light_pdas/program/decompress.rs @@ -57,7 +57,7 @@ impl DecompressBuilder { remaining_accounts: &[solana_account_info::AccountInfo<'info>], instruction_data: &[u8], ) -> Result<()> { - light_sdk::interface::process_decompress_pda_accounts_idempotent::( + light_account::process_decompress_pda_accounts_idempotent::( remaining_accounts, instruction_data, LIGHT_CPI_SIGNER, @@ -296,7 +296,7 @@ impl DecompressBuilder { quote! { #ctx_seeds_struct - impl light_sdk::interface::PdaSeedDerivation<#ctx_seeds_struct_name, SeedParams> for #inner_type { + impl light_account::PdaSeedDerivation<#ctx_seeds_struct_name, SeedParams> for #inner_type { fn derive_pda_seeds_with_accounts( &self, program_id: &solana_pubkey::Pubkey, @@ -311,7 +311,7 @@ impl DecompressBuilder { quote! { #ctx_seeds_struct - impl light_sdk::interface::PdaSeedDerivation<#ctx_seeds_struct_name, SeedParams> for #inner_type { + impl light_account::PdaSeedDerivation<#ctx_seeds_struct_name, SeedParams> for #inner_type { fn derive_pda_seeds_with_accounts( &self, program_id: &solana_pubkey::Pubkey, @@ -349,7 +349,7 @@ impl DecompressBuilder { cpi_signer: light_sdk_types::CpiSigner, program_id: &solana_pubkey::Pubkey, ) -> std::result::Result<(), solana_program_error::ProgramError> { - light_sdk::interface::process_decompress_pda_accounts_idempotent::( + light_account::process_decompress_pda_accounts_idempotent::( remaining_accounts, instruction_data, cpi_signer, diff --git a/sdk-libs/macros/src/light_pdas/program/instructions.rs b/sdk-libs/macros/src/light_pdas/program/instructions.rs index c3551caa63..0834e23847 100644 --- a/sdk-libs/macros/src/light_pdas/program/instructions.rs +++ b/sdk-libs/macros/src/light_pdas/program/instructions.rs @@ -138,16 +138,16 @@ pub(crate) fn generate_light_program_items( const LIGHT_DISCRIMINATOR_SLICE: &'static [u8] = &Self::LIGHT_DISCRIMINATOR; } - impl light_sdk::interface::HasCompressionInfo for LightAccountVariant { - fn compression_info(&self) -> std::result::Result<&light_sdk::interface::CompressionInfo, solana_program_error::ProgramError> { + impl light_account::HasCompressionInfo for LightAccountVariant { + fn compression_info(&self) -> std::result::Result<&light_account::CompressionInfo, solana_program_error::ProgramError> { Err(solana_program_error::ProgramError::InvalidAccountData) } - fn compression_info_mut(&mut self) -> std::result::Result<&mut light_sdk::interface::CompressionInfo, solana_program_error::ProgramError> { + fn compression_info_mut(&mut self) -> std::result::Result<&mut light_account::CompressionInfo, solana_program_error::ProgramError> { Err(solana_program_error::ProgramError::InvalidAccountData) } - fn compression_info_mut_opt(&mut self) -> &mut Option { + fn compression_info_mut_opt(&mut self) -> &mut Option { panic!("compression_info_mut_opt not supported for mint-only programs") } @@ -327,7 +327,7 @@ pub(crate) fn generate_light_program_items( }) } } - impl light_sdk::interface::IntoVariant for #seeds_struct_name { + impl light_account::IntoVariant for #seeds_struct_name { fn into_variant(self, data: &[u8]) -> std::result::Result { LightAccountVariant::#constructor_name(data, self) } @@ -393,7 +393,7 @@ pub(crate) fn generate_light_program_items( mod __trait_impls { use super::*; - impl light_sdk::interface::HasTokenVariant for LightAccountData { + impl light_account::HasTokenVariant for LightAccountData { fn is_packed_token(&self) -> bool { match &self.data { #(#token_match_arms)* @@ -416,7 +416,7 @@ pub(crate) fn generate_light_program_items( mod __trait_impls { use super::*; - impl light_sdk::interface::HasTokenVariant for LightAccountData { + impl light_account::HasTokenVariant for LightAccountData { fn is_packed_token(&self) -> bool { // PDA-only programs have no token variants false @@ -437,7 +437,7 @@ pub(crate) fn generate_light_program_items( mod __trait_impls { use super::*; - impl light_sdk::interface::HasTokenVariant for LightAccountData { + impl light_account::HasTokenVariant for LightAccountData { fn is_packed_token(&self) -> bool { match &self.data { LightAccountVariant::Empty => false, @@ -516,7 +516,7 @@ pub(crate) fn generate_light_program_items( ctx.accounts.authority.to_account_info(), ctx.accounts.system_program.to_account_info(), ]; - light_sdk::interface::process_initialize_light_config_checked( + light_account::process_initialize_light_config_checked( &remaining, &instruction_data, &crate::ID, @@ -535,7 +535,7 @@ pub(crate) fn generate_light_program_items( ctx.accounts.config.to_account_info(), ctx.accounts.update_authority.to_account_info(), ]; - light_sdk::interface::process_update_light_config( + light_account::process_update_light_config( &remaining, &instruction_data, &crate::ID, diff --git a/sdk-libs/macros/src/light_pdas/program/parsing.rs b/sdk-libs/macros/src/light_pdas/program/parsing.rs index 2865605b7c..075bf78667 100644 --- a/sdk-libs/macros/src/light_pdas/program/parsing.rs +++ b/sdk-libs/macros/src/light_pdas/program/parsing.rs @@ -682,9 +682,9 @@ pub fn wrap_function_with_light( #(#fn_attrs)* #fn_vis #fn_sig { // Phase 1: Pre-init (creates mints via CPI context write, registers compressed addresses) - use light_sdk::interface::{LightPreInit, LightFinalize}; + use light_account::{LightPreInit, LightFinalize}; let _ = #ctx_name.accounts.light_pre_init(#ctx_name.remaining_accounts, &#params_ident) - .map_err(|e: light_sdk::interface::error::LightPdaError| -> solana_program_error::ProgramError { + .map_err(|e: light_sdk_types::error::LightSdkTypesError| -> solana_program_error::ProgramError { e.into() })?; @@ -698,9 +698,9 @@ pub fn wrap_function_with_light( #(#fn_attrs)* #fn_vis #fn_sig { // Phase 1: Pre-init (creates mints via CPI context write, registers compressed addresses) - use light_sdk::interface::{LightPreInit, LightFinalize}; + use light_account::{LightPreInit, LightFinalize}; let __has_pre_init = #ctx_name.accounts.light_pre_init(#ctx_name.remaining_accounts, &#params_ident) - .map_err(|e: light_sdk::interface::error::LightPdaError| -> solana_program_error::ProgramError { + .map_err(|e: light_sdk_types::error::LightSdkTypesError| -> solana_program_error::ProgramError { e.into() })?; @@ -711,7 +711,7 @@ pub fn wrap_function_with_light( // Phase 2: Finalize (creates token accounts/ATAs via CPI) #ctx_name.accounts.light_finalize(#ctx_name.remaining_accounts, &#params_ident, __has_pre_init) - .map_err(|e: light_sdk::interface::error::LightPdaError| -> solana_program_error::ProgramError { + .map_err(|e: light_sdk_types::error::LightSdkTypesError| -> solana_program_error::ProgramError { e.into() })?; diff --git a/sdk-libs/macros/src/light_pdas/program/variant_enum.rs b/sdk-libs/macros/src/light_pdas/program/variant_enum.rs index 452d94aed4..64083cefa1 100644 --- a/sdk-libs/macros/src/light_pdas/program/variant_enum.rs +++ b/sdk-libs/macros/src/light_pdas/program/variant_enum.rs @@ -243,7 +243,7 @@ impl<'a> LightVariantBuilder<'a> { // ========================================================================= /// Generate `UnpackedTokenSeeds` and `PackedTokenSeeds` impls - /// on the local seed structs. The blanket impls in `light_sdk::interface::token` + /// on the local seed structs. The blanket impls in `light_account::token` /// then provide `LightAccountVariantTrait` / `PackedLightAccountVariantTrait`. fn generate_token_variant_trait_impls(&self) -> TokenStream { let impls: Vec<_> = self @@ -337,7 +337,7 @@ impl<'a> LightVariantBuilder<'a> { }; quote! { - impl light_sdk::interface::UnpackedTokenSeeds<#seed_count> + impl light_account::UnpackedTokenSeeds<#seed_count> for #seeds_name { type Packed = #packed_seeds_name; @@ -353,7 +353,7 @@ impl<'a> LightVariantBuilder<'a> { } } - impl light_sdk::interface::PackedTokenSeeds<#seed_count> + impl light_account::PackedTokenSeeds<#seed_count> for #packed_seeds_name { fn bump(&self) -> u8 { @@ -404,7 +404,7 @@ impl<'a> LightVariantBuilder<'a> { let variant_name = &spec.variant; let seeds_name = format_ident!("{}Seeds", variant_name); quote! { - #variant_name(light_sdk::interface::token::TokenDataWithSeeds<#seeds_name>) + #variant_name(light_account::token::TokenDataWithSeeds<#seeds_name>) } }) .collect(); @@ -446,7 +446,7 @@ impl<'a> LightVariantBuilder<'a> { let variant_name = &spec.variant; let packed_seeds_name = format_ident!("Packed{}Seeds", variant_name); quote! { - #variant_name(light_sdk::interface::token::TokenDataWithPackedSeeds<#packed_seeds_name>) + #variant_name(light_account::token::TokenDataWithPackedSeeds<#packed_seeds_name>) } }) .collect(); @@ -478,7 +478,7 @@ impl<'a> LightVariantBuilder<'a> { quote! { Self::#variant_name { seeds, data } => { let packed_data = #packed_variant_type { seeds: seeds.clone(), data: data.clone() }; - light_sdk::interface::prepare_account_for_decompression::<#seed_count, #packed_variant_type>( + light_account::prepare_account_for_decompression::<#seed_count, #packed_variant_type>( &packed_data, tree_info, output_queue_index, @@ -500,9 +500,9 @@ impl<'a> LightVariantBuilder<'a> { quote! { Self::#variant_name(packed_data) => { - light_sdk::interface::token::prepare_token_account_for_decompression::< + light_account::token::prepare_token_account_for_decompression::< #seed_count, - light_sdk::interface::token::TokenDataWithPackedSeeds<#packed_seeds_name>, + light_account::token::TokenDataWithPackedSeeds<#packed_seeds_name>, >( packed_data, tree_info, @@ -516,12 +516,12 @@ impl<'a> LightVariantBuilder<'a> { .collect(); quote! { - impl<'info> light_sdk::interface::DecompressVariant<'info> for PackedLightAccountVariant { + impl<'info> light_account::DecompressVariant> for PackedLightAccountVariant { fn decompress( &self, tree_info: &light_sdk::instruction::PackedStateTreeInfo, pda_account: &anchor_lang::prelude::AccountInfo<'info>, - ctx: &mut light_sdk::interface::DecompressCtx<'_, 'info>, + ctx: &mut light_account::DecompressCtx<'_, 'info>, ) -> std::result::Result<(), solana_program_error::ProgramError> { let output_queue_index = ctx.output_queue_index; match self { diff --git a/sdk-libs/sdk-interface/Cargo.toml b/sdk-libs/sdk-interface/Cargo.toml deleted file mode 100644 index 052131ebb7..0000000000 --- a/sdk-libs/sdk-interface/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -name = "light-sdk-interface" -version = "0.19.0" -description = "Framework-agnostic interface for Light Protocol compressible accounts." -repository = "https://github.com/Lightprotocol/light-protocol" -license = "Apache-2.0" -edition = "2021" - -[lib] -crate-type = ["cdylib", "lib"] - -[features] -default = ["std"] -std = [ - "alloc", - "light-compressed-account/std", - "light-sdk-types/std", -] -alloc = ["light-compressed-account/alloc"] -poseidon = ["light-hasher/poseidon", "light-compressed-account/poseidon"] -keccak = ["light-hasher/keccak", "light-compressed-account/keccak"] -sha256 = ["light-hasher/sha256", "light-compressed-account/sha256"] -token = ["dep:light-token-interface"] - -[dependencies] -light-account-checks = { workspace = true, default-features = false } -light-sdk-types = { workspace = true, default-features = false, features = ["v2", "cpi-context"] } -light-compressed-account = { workspace = true, default-features = false } -light-compressible = { workspace = true, default-features = false } -light-hasher = { workspace = true, default-features = false } -light-token-interface = { workspace = true, optional = true } -borsh = { workspace = true } -bytemuck = { workspace = true } -thiserror = { workspace = true } - -[lints.rust.unexpected_cfgs] -level = "allow" -check-cfg = [ - 'cfg(target_os, values("solana"))', - 'cfg(feature, values("frozen-abi", "no-entrypoint", "token"))', -] diff --git a/sdk-libs/sdk-interface/src/.backup/account/token_seeds.rs b/sdk-libs/sdk-interface/src/.backup/account/token_seeds.rs deleted file mode 100644 index c6cb257279..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/account/token_seeds.rs +++ /dev/null @@ -1,261 +0,0 @@ -use light_compressed_account::compressed_account::PackedMerkleContext; -use light_sdk_types::instruction::PackedStateTreeInfo; -pub use light_token_interface::{ - instructions::{ - extensions::{CompressedOnlyExtensionInstructionData, ExtensionInstructionData}, - transfer2::MultiInputTokenDataWithContext, - }, - state::{ - extensions::{CompressedOnlyExtension, ExtensionStruct}, - AccountState, Token, TokenDataVersion, - }, -}; -use solana_account_info::AccountInfo; -use solana_program_error::ProgramError; -use solana_pubkey::Pubkey; - -use super::pack::Unpack; -// Pack trait and PackedAccounts only available off-chain (client-side packing) -#[cfg(not(target_os = "solana"))] -use crate::{account::pack::Pack, instruction::PackedAccounts}; -use crate::{ - program::variant::{ - LightAccountVariantTrait, PackedLightAccountVariantTrait, PackedTokenSeeds, - UnpackedTokenSeeds, - }, - account::light_account::AccountType, - AnchorDeserialize, AnchorSerialize, -}; - -#[derive(Clone, Debug, AnchorSerialize, AnchorDeserialize)] -pub struct TokenDataWithSeeds { - pub seeds: S, - pub token_data: Token, -} -#[repr(C)] -#[derive(Debug, Copy, Clone, Default, PartialEq, AnchorSerialize, AnchorDeserialize)] -pub struct PackedTokenData { - pub owner: u8, - pub amount: u64, - pub has_delegate: bool, // Optional delegate is set - pub delegate: u8, - pub mint: u8, - pub version: u8, -} - -#[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone)] -pub struct TokenDataWithPackedSeeds< - S: Unpack + AnchorSerialize + AnchorDeserialize + Clone + std::fmt::Debug, -> { - pub seeds: S, - pub token_data: PackedTokenData, - pub extension: Option, -} - -#[cfg(not(target_os = "solana"))] -impl Pack for TokenDataWithSeeds -where - S: Pack, - S::Packed: Unpack + AnchorDeserialize + AnchorSerialize + Clone + std::fmt::Debug, -{ - type Packed = TokenDataWithPackedSeeds; - - fn pack(&self, remaining_accounts: &mut PackedAccounts) -> Result { - let seeds = self.seeds.pack(remaining_accounts)?; - - let owner_index = remaining_accounts - .insert_or_get(Pubkey::new_from_array(self.token_data.owner.to_bytes())); - - let token_data = PackedTokenData { - owner: owner_index, - amount: self.token_data.amount, - has_delegate: self.token_data.delegate.is_some(), - delegate: self - .token_data - .delegate - .map(|d| remaining_accounts.insert_or_get(Pubkey::new_from_array(d.to_bytes()))) - .unwrap_or(0), - mint: remaining_accounts - .insert_or_get(Pubkey::new_from_array(self.token_data.mint.to_bytes())), - version: TokenDataVersion::ShaFlat as u8, - }; - - // Extract CompressedOnly extension from Token state if present. - let extension = self.token_data.extensions.as_ref().and_then(|exts| { - exts.iter().find_map(|ext| { - if let ExtensionStruct::CompressedOnly(co) = ext { - Some(CompressedOnlyExtensionInstructionData { - delegated_amount: co.delegated_amount, - withheld_transfer_fee: co.withheld_transfer_fee, - is_frozen: self.token_data.state == AccountState::Frozen, - compression_index: 0, - is_ata: co.is_ata != 0, - bump: 0, - owner_index, - }) - } else { - None - } - }) - }); - - Ok(TokenDataWithPackedSeeds { - seeds, - token_data, - extension, - }) - } -} - -impl Unpack for TokenDataWithPackedSeeds -where - S: Unpack + AnchorSerialize + AnchorDeserialize + Clone + std::fmt::Debug, -{ - type Unpacked = TokenDataWithSeeds; - - fn unpack(&self, remaining_accounts: &[AccountInfo]) -> Result { - let seeds = self.seeds.unpack(remaining_accounts)?; - - let owner_key = remaining_accounts - .get(self.token_data.owner as usize) - .ok_or(ProgramError::InvalidAccountData)? - .key; - let mint_key = remaining_accounts - .get(self.token_data.mint as usize) - .ok_or(ProgramError::InvalidAccountData)? - .key; - let delegate = if self.token_data.has_delegate { - let delegate_key = remaining_accounts - .get(self.token_data.delegate as usize) - .ok_or(ProgramError::InvalidAccountData)? - .key; - Some(light_compressed_account::Pubkey::from( - delegate_key.to_bytes(), - )) - } else { - None - }; - - // Reconstruct extensions from instruction extension data. - let extensions = self.extension.map(|ext| { - vec![ExtensionStruct::CompressedOnly(CompressedOnlyExtension { - delegated_amount: ext.delegated_amount, - withheld_transfer_fee: ext.withheld_transfer_fee, - is_ata: ext.is_ata as u8, - })] - }); - - let state = self.extension.map_or(AccountState::Initialized, |ext| { - if ext.is_frozen { - AccountState::Frozen - } else { - AccountState::Initialized - } - }); - - let delegated_amount = self.extension.map_or(0, |ext| ext.delegated_amount); - - let token_data = Token { - mint: light_compressed_account::Pubkey::from(mint_key.to_bytes()), - owner: light_compressed_account::Pubkey::from(owner_key.to_bytes()), - amount: self.token_data.amount, - delegate, - state, - is_native: None, - delegated_amount, - close_authority: None, - account_type: TokenDataVersion::ShaFlat as u8, - extensions, - }; - - Ok(TokenDataWithSeeds { seeds, token_data }) - } -} - -// ============================================================================= -// Blanket impls: LightAccountVariantTrait / PackedLightAccountVariantTrait -// for TokenDataWithSeeds / TokenDataWithPackedSeeds -// where S implements the seed-specific helper traits. -// ============================================================================= - -impl LightAccountVariantTrait for TokenDataWithSeeds -where - S: UnpackedTokenSeeds, - S::Packed: PackedTokenSeeds + Unpack, -{ - const PROGRAM_ID: Pubkey = S::PROGRAM_ID; - type Seeds = S; - type Data = Token; - type Packed = TokenDataWithPackedSeeds; - - fn data(&self) -> &Self::Data { - &self.token_data - } - - fn seed_vec(&self) -> Vec> { - self.seeds.seed_vec() - } - - fn seed_refs_with_bump<'a>(&'a self, bump_storage: &'a [u8; 1]) -> [&'a [u8]; N] { - self.seeds.seed_refs_with_bump(bump_storage) - } -} - -impl PackedLightAccountVariantTrait for TokenDataWithPackedSeeds -where - S: PackedTokenSeeds, - S::Unpacked: UnpackedTokenSeeds, -{ - type Unpacked = TokenDataWithSeeds; - - const ACCOUNT_TYPE: AccountType = AccountType::Token; - - fn bump(&self) -> u8 { - self.seeds.bump() - } - - fn unpack(&self, accounts: &[AccountInfo]) -> anchor_lang::Result { - ::unpack(self, accounts).map_err(anchor_lang::error::Error::from) - } - - fn seed_refs_with_bump<'a>( - &'a self, - accounts: &'a [AccountInfo], - bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; N], ProgramError> { - self.seeds.seed_refs_with_bump(accounts, bump_storage) - } - - fn into_in_token_data( - &self, - tree_info: &PackedStateTreeInfo, - output_queue_index: u8, - ) -> anchor_lang::Result { - Ok(MultiInputTokenDataWithContext { - amount: self.token_data.amount, - mint: self.token_data.mint, - owner: self.token_data.owner, - version: self.token_data.version, - has_delegate: self.token_data.has_delegate, - delegate: self.token_data.delegate, - root_index: tree_info.root_index, - merkle_context: PackedMerkleContext { - merkle_tree_pubkey_index: tree_info.merkle_tree_pubkey_index, - queue_pubkey_index: output_queue_index, - leaf_index: tree_info.leaf_index, - prove_by_index: tree_info.prove_by_index, - }, - }) - } - - fn into_in_tlv(&self) -> anchor_lang::Result>> { - Ok(self - .extension - .as_ref() - .map(|ext| vec![ExtensionInstructionData::CompressedOnly(*ext)])) - } - - fn derive_owner(&self) -> Pubkey { - self.seeds.derive_owner() - } -} diff --git a/sdk-libs/sdk-interface/src/.backup/accounts/create_pda.rs b/sdk-libs/sdk-interface/src/.backup/accounts/create_pda.rs deleted file mode 100644 index 0440e6c094..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/accounts/create_pda.rs +++ /dev/null @@ -1,126 +0,0 @@ -use solana_account_info::AccountInfo; -use solana_cpi::invoke_signed; -use solana_pubkey::Pubkey; -use solana_system_interface::instruction as system_instruction; - -use crate::error::LightPdaError; - -/// Cold path: Account already has lamports (e.g., attacker donation). -/// Uses Assign + Allocate + Transfer instead of CreateAccount which would fail. -#[cold] -#[allow(clippy::too_many_arguments)] -fn create_pda_account_with_lamports<'info>( - rent_sponsor: &AccountInfo<'info>, - rent_sponsor_seeds: &[&[u8]], - solana_account: &AccountInfo<'info>, - lamports: u64, - space: u64, - owner: &Pubkey, - seeds: &[&[u8]], - system_program: &AccountInfo<'info>, -) -> Result<(), LightPdaError> { - let current_lamports = solana_account.lamports(); - - // Assign owner - let assign_ix = system_instruction::assign(solana_account.key, owner); - invoke_signed( - &assign_ix, - &[solana_account.clone(), system_program.clone()], - &[seeds], - ) - .map_err(LightPdaError::ProgramError)?; - - // Allocate space - let allocate_ix = system_instruction::allocate(solana_account.key, space); - invoke_signed( - &allocate_ix, - &[solana_account.clone(), system_program.clone()], - &[seeds], - ) - .map_err(LightPdaError::ProgramError)?; - - // Transfer remaining lamports for rent-exemption if needed - if lamports > current_lamports { - let transfer_ix = system_instruction::transfer( - rent_sponsor.key, - solana_account.key, - lamports - current_lamports, - ); - // Include rent sponsor seeds so the PDA can sign for the transfer - invoke_signed( - &transfer_ix, - &[ - rent_sponsor.clone(), - solana_account.clone(), - system_program.clone(), - ], - &[rent_sponsor_seeds], - ) - .map_err(LightPdaError::ProgramError)?; - } - - Ok(()) -} - -/// Creates a PDA account, handling the case where the account already has lamports. -/// -/// This function handles the edge case where an attacker might have donated lamports -/// to the PDA address before decompression. In that case, `CreateAccount` would fail, -/// so we fall back to `Assign + Allocate + Transfer`. -/// -/// # Arguments -/// * `rent_sponsor` - Account paying for rent (must be a PDA derived from the calling program) -/// * `rent_sponsor_seeds` - Seeds for the rent sponsor PDA (including bump) for signing -/// * `solana_account` - The PDA account to create -/// * `lamports` - Amount of lamports for rent-exemption -/// * `space` - Size of the account in bytes -/// * `owner` - Program that will own the account -/// * `seeds` - Seeds for the target PDA (including bump) for signing -/// * `system_program` - System program -#[inline(never)] -#[allow(clippy::too_many_arguments)] -pub fn create_pda_account<'info>( - rent_sponsor: &AccountInfo<'info>, - rent_sponsor_seeds: &[&[u8]], - solana_account: &AccountInfo<'info>, - lamports: u64, - space: u64, - owner: &Pubkey, - seeds: &[&[u8]], - system_program: &AccountInfo<'info>, -) -> Result<(), LightPdaError> { - // Cold path: account already has lamports (e.g., attacker donation) - if solana_account.lamports() > 0 { - return create_pda_account_with_lamports( - rent_sponsor, - rent_sponsor_seeds, - solana_account, - lamports, - space, - owner, - seeds, - system_program, - ); - } - - // Normal path: CreateAccount - // Include both rent sponsor seeds (payer) and PDA seeds (new account) - let create_account_ix = system_instruction::create_account( - rent_sponsor.key, - solana_account.key, - lamports, - space, - owner, - ); - - invoke_signed( - &create_account_ix, - &[ - rent_sponsor.clone(), - solana_account.clone(), - system_program.clone(), - ], - &[rent_sponsor_seeds, seeds], - ) - .map_err(LightPdaError::ProgramError) -} diff --git a/sdk-libs/sdk-interface/src/.backup/cpi/account.rs b/sdk-libs/sdk-interface/src/.backup/cpi/account.rs deleted file mode 100644 index 00c425fba8..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/cpi/account.rs +++ /dev/null @@ -1,85 +0,0 @@ -use light_sdk_types::cpi_context_write::CpiContextWriteAccounts; - -use crate::cpi::v2::get_account_metas_from_config_cpi_context; -use crate::cpi::v1::{ - lowlevel::{get_account_metas_from_config, CpiInstructionConfig}, - CpiAccounts, -}; -use solana_account_info::AccountInfo; -use solana_instruction::AccountMeta; -use solana_program_error::ProgramError; - -/// Trait for types that can provide account information for CPI calls -pub trait CpiAccountsTrait<'info> { - /// Convert to a vector of AccountInfo references - fn to_account_infos(&self) -> Vec>; - - /// Generate account metas - fn to_account_metas(&self) -> Result, ProgramError>; - - /// Get the mode for the instruction (0 for v1, 1 for v2, None if unknown) - fn get_mode(&self) -> Option; -} - -// Implementation for CpiAccounts -impl<'info> CpiAccountsTrait<'info> for CpiAccounts<'_, 'info> { - fn to_account_infos(&self) -> Vec> { - self.to_account_infos() - } - - fn to_account_metas(&self) -> Result, ProgramError> { - let config = CpiInstructionConfig::try_from(self).map_err(ProgramError::from)?; - Ok(get_account_metas_from_config(config)) - } - - fn get_mode(&self) -> Option { - Some(0) // v1 mode - } -} - -// Implementation for &[AccountInfo] -impl<'info> CpiAccountsTrait<'info> for &[AccountInfo<'info>] { - fn to_account_infos(&self) -> Vec> { - self.to_vec() - } - - fn to_account_metas(&self) -> Result, ProgramError> { - // For raw account info slices, create simple account metas - // preserving the original signer and writable flags - Ok(self - .iter() - .map(|account| AccountMeta { - pubkey: *account.key, - is_signer: account.is_signer, - is_writable: account.is_writable, - }) - .collect()) - } - - fn get_mode(&self) -> Option { - None // Unknown mode for raw slices - } -} - -// Implementation for CpiContextWriteAccounts -impl<'a, 'info> CpiAccountsTrait<'info> for CpiContextWriteAccounts<'a, AccountInfo<'info>> { - fn to_account_infos(&self) -> Vec> { - vec![ - self.fee_payer.clone(), - self.authority.clone(), - self.cpi_context.clone(), - ] - } - - fn to_account_metas(&self) -> Result, ProgramError> { - // Use the helper function to generate the account metas - let metas = get_account_metas_from_config_cpi_context(self.clone()); - Ok(metas.to_vec()) - } - - fn get_mode(&self) -> Option { - // CPI context write accounts always use v2 mode (1) - // This type requires both the `v2` and `cpi-context` features - Some(1) - } -} diff --git a/sdk-libs/sdk-interface/src/.backup/cpi/invoke.rs b/sdk-libs/sdk-interface/src/.backup/cpi/invoke.rs deleted file mode 100644 index 768d6ccfed..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/cpi/invoke.rs +++ /dev/null @@ -1,187 +0,0 @@ -pub use light_compressed_account::LightInstructionData; -use light_sdk_types::constants::{CPI_AUTHORITY_PDA_SEED, LIGHT_SYSTEM_PROGRAM_ID}; -use solana_instruction::AccountMeta; -use solana_account_info::AccountInfo; -use solana_instruction::Instruction; -use solana_program_error::ProgramError; - -use crate::{ - cpi::{account::CpiAccountsTrait, instruction::LightCpiInstruction}, - error::LightPdaError, -}; -use solana_cpi::invoke_signed; - -pub trait InvokeLightSystemProgram { - fn invoke<'info>(self, accounts: impl CpiAccountsTrait<'info>) -> Result<(), ProgramError>; - fn invoke_write_to_cpi_context_first<'info>( - self, - accounts: impl CpiAccountsTrait<'info>, - ) -> Result<(), ProgramError>; - fn invoke_write_to_cpi_context_set<'info>( - self, - accounts: impl CpiAccountsTrait<'info>, - ) -> Result<(), ProgramError>; - fn invoke_execute_cpi_context<'info>( - self, - accounts: impl CpiAccountsTrait<'info>, - ) -> Result<(), ProgramError>; -} - -// Blanket implementation for types that implement both LightInstructionData and LightCpiInstruction -impl InvokeLightSystemProgram for T -where - T: LightInstructionData + LightCpiInstruction, -{ - fn invoke<'info>(self, accounts: impl CpiAccountsTrait<'info>) -> Result<(), ProgramError> { - { - // Check if CPI context operations are being attempted - use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; - if self.get_with_cpi_context() - || *self.get_cpi_context() == CompressedCpiContext::set() - || *self.get_cpi_context() == CompressedCpiContext::first() - { - solana_msg::msg!( - "CPI context operations not supported in invoke(). Use invoke_write_to_cpi_context_first(), invoke_write_to_cpi_context_set(), or invoke_execute_cpi_context() instead" - ); - return Err(ProgramError::InvalidInstructionData); - } - } - - // Validate mode consistency - if let Some(account_mode) = accounts.get_mode() { - if account_mode != self.get_mode() { - solana_msg::msg!( - "Mode mismatch: accounts have mode {} but instruction data has mode {}", - account_mode, - self.get_mode() - ); - return Err(ProgramError::InvalidInstructionData); - } - } - - // Serialize instruction data with discriminator - let data = self - .data() - .map_err(LightPdaError::from) - .map_err(ProgramError::from)?; - - // Get account infos and metas - let account_infos = accounts.to_account_infos(); - let account_metas = accounts.to_account_metas()?; - - let instruction = Instruction { - program_id: LIGHT_SYSTEM_PROGRAM_ID.into(), - accounts: account_metas, - data, - }; - invoke_light_system_program(&account_infos, instruction, self.get_bump()) - } - fn invoke_write_to_cpi_context_first<'info>( - self, - accounts: impl CpiAccountsTrait<'info>, - ) -> Result<(), ProgramError> { - let instruction_data = self.write_to_cpi_context_first(); - inner_invoke_write_to_cpi_context_typed(instruction_data, accounts) - } - fn invoke_write_to_cpi_context_set<'info>( - self, - accounts: impl CpiAccountsTrait<'info>, - ) -> Result<(), ProgramError> { - let instruction_data = self.write_to_cpi_context_set(); - inner_invoke_write_to_cpi_context_typed(instruction_data, accounts) - } - fn invoke_execute_cpi_context<'info>( - self, - accounts: impl CpiAccountsTrait<'info>, - ) -> Result<(), ProgramError> { - let instruction_data = self.execute_with_cpi_context(); - // Serialize instruction data with discriminator - let data = instruction_data - .data() - .map_err(LightPdaError::from) - .map_err(ProgramError::from)?; - - // Get account infos and metas - let account_infos = accounts.to_account_infos(); - let account_metas = accounts.to_account_metas()?; - - let instruction = Instruction { - program_id: LIGHT_SYSTEM_PROGRAM_ID.into(), - accounts: account_metas, - data, - }; - invoke_light_system_program(&account_infos, instruction, instruction_data.get_bump()) - } -} - -// Generic inner helper for write_to_cpi_context operations -#[inline(always)] -fn inner_invoke_write_to_cpi_context_typed<'info, T>( - instruction_data: T, - accounts: impl CpiAccountsTrait<'info>, -) -> Result<(), ProgramError> -where - T: LightInstructionData + LightCpiInstruction, -{ - // Check if read-only accounts are present - if instruction_data.has_read_only_accounts() { - solana_msg::msg!( - "Read-only accounts are not supported in write_to_cpi_context operations. Use invoke_execute_cpi_context() instead." - ); - return Err(LightPdaError::ReadOnlyAccountsNotSupportedInCpiContext.into()); - } - - // Serialize instruction data with discriminator - let data = instruction_data - .data() - .map_err(LightPdaError::from) - .map_err(ProgramError::from)?; - - // Get account infos and metas - let account_infos = accounts.to_account_infos(); - - // Extract account pubkeys from account_infos - // Assuming order: [fee_payer, authority, cpi_context, ...] - if account_infos.len() < 3 { - return Err(ProgramError::NotEnoughAccountKeys); - } - - let instruction = Instruction { - program_id: LIGHT_SYSTEM_PROGRAM_ID.into(), - accounts: vec![ - AccountMeta { - pubkey: *account_infos[0].key, // fee_payer - is_writable: true, - is_signer: true, - }, - AccountMeta { - pubkey: *account_infos[1].key, // authority - is_writable: false, - is_signer: true, - }, - AccountMeta { - pubkey: *account_infos[2].key, // cpi_context - is_writable: true, - is_signer: false, - }, - ], - data, - }; - - invoke_light_system_program(&account_infos, instruction, instruction_data.get_bump()) -} - -/// Low-level function to invoke the Light system program with a PDA signer. -/// -/// **Note**: This is a low-level function. In most cases, you should use the -/// [`InvokeLightSystemProgram`] trait methods instead, which provide a higher-level -/// interface with better type safety and ergonomics. -#[inline(always)] -pub fn invoke_light_system_program( - account_infos: &[AccountInfo], - instruction: Instruction, - bump: u8, -) -> Result<(), ProgramError> { - let signer_seeds = [CPI_AUTHORITY_PDA_SEED, &[bump]]; - invoke_signed(&instruction, account_infos, &[signer_seeds.as_slice()]) -} diff --git a/sdk-libs/sdk-interface/src/.backup/cpi/v1/accounts.rs b/sdk-libs/sdk-interface/src/.backup/cpi/v1/accounts.rs deleted file mode 100644 index a845fafb76..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/cpi/v1/accounts.rs +++ /dev/null @@ -1,165 +0,0 @@ -pub use light_sdk_types::cpi_accounts::v1::SYSTEM_ACCOUNTS_LEN; -use light_sdk_types::{ - cpi_accounts::v1::CpiAccounts as GenericCpiAccounts, ACCOUNT_COMPRESSION_AUTHORITY_PDA, - ACCOUNT_COMPRESSION_PROGRAM_ID, LIGHT_SYSTEM_PROGRAM_ID, NOOP_PROGRAM_ID, - REGISTERED_PROGRAM_PDA, -}; - -use crate::error::{LightPdaError, Result}; -use solana_account_info::AccountInfo; -use solana_instruction::AccountMeta; -use solana_pubkey::Pubkey; - -#[derive(Debug)] -pub struct CpiInstructionConfig<'a, 'info> { - pub fee_payer: Pubkey, - pub cpi_signer: Pubkey, - pub invoking_program: Pubkey, - pub sol_pool_pda_pubkey: Option, - pub sol_compression_recipient_pubkey: Option, - pub cpi_context_pubkey: Option, - pub packed_accounts: &'a [AccountInfo<'info>], -} - -/// Light system program CPI accounts struct. -/// -/// Use with [`LightSystemProgramCpi`](super::LightSystemProgramCpi) to invoke the Light system program. -pub type CpiAccounts<'c, 'info> = GenericCpiAccounts<'c, AccountInfo<'info>>; - -pub fn get_account_metas_from_config(config: CpiInstructionConfig<'_, '_>) -> Vec { - let mut account_metas = Vec::with_capacity(1 + SYSTEM_ACCOUNTS_LEN); - - // 1. Fee payer (signer, writable) - account_metas.push(AccountMeta { - pubkey: config.fee_payer, - is_signer: true, - is_writable: true, - }); - - // 2. Authority/CPI Signer (signer, readonly) - account_metas.push(AccountMeta { - pubkey: config.cpi_signer, - is_signer: true, - is_writable: false, - }); - - // 3. Registered Program PDA (readonly) - hardcoded constant - account_metas.push(AccountMeta { - pubkey: Pubkey::from(REGISTERED_PROGRAM_PDA), - is_signer: false, - is_writable: false, - }); - - // 4. Noop Program (readonly) - hardcoded constant - account_metas.push(AccountMeta { - pubkey: Pubkey::from(NOOP_PROGRAM_ID), - is_signer: false, - is_writable: false, - }); - - // 5. Account Compression Authority (readonly) - hardcoded constant - account_metas.push(AccountMeta { - pubkey: Pubkey::from(ACCOUNT_COMPRESSION_AUTHORITY_PDA), - is_signer: false, - is_writable: false, - }); - - // 6. Account Compression Program (readonly) - hardcoded constant - account_metas.push(AccountMeta { - pubkey: Pubkey::from(ACCOUNT_COMPRESSION_PROGRAM_ID), - is_signer: false, - is_writable: false, - }); - - // 7. Invoking Program (readonly) - account_metas.push(AccountMeta { - pubkey: config.invoking_program, - is_signer: false, - is_writable: false, - }); - - // 8. Light System Program (readonly) - reused for optional accounts - let create_light_system_meta = || AccountMeta { - pubkey: Pubkey::from(LIGHT_SYSTEM_PROGRAM_ID), - is_signer: false, - is_writable: false, - }; - - // 9. Sol Pool PDA (writable) OR Light System Program (readonly) - if let Some(sol_pool_pda_pubkey) = config.sol_pool_pda_pubkey { - account_metas.push(AccountMeta { - pubkey: sol_pool_pda_pubkey, - is_signer: false, - is_writable: true, - }); - } else { - account_metas.push(create_light_system_meta()); - } - - // 10. Sol Compression Recipient (writable) OR Light System Program (readonly) - if let Some(sol_compression_recipient_pubkey) = config.sol_compression_recipient_pubkey { - account_metas.push(AccountMeta { - pubkey: sol_compression_recipient_pubkey, - is_signer: false, - is_writable: true, - }); - } else { - account_metas.push(create_light_system_meta()); - } - - // 11. System Program (readonly) - always default pubkey - account_metas.push(AccountMeta { - pubkey: Pubkey::default(), - is_signer: false, - is_writable: false, - }); - - // 12. CPI Context (writable) OR Light System Program (readonly) - if let Some(cpi_context_pubkey) = config.cpi_context_pubkey { - account_metas.push(AccountMeta { - pubkey: cpi_context_pubkey, - is_signer: false, - is_writable: true, - }); - } else { - account_metas.push(create_light_system_meta()); - } - - for acc in config.packed_accounts { - account_metas.push(AccountMeta { - pubkey: *acc.key, - is_signer: false, - is_writable: acc.is_writable, - }); - } - - account_metas -} - -impl<'a, 'info> TryFrom<&'a CpiAccounts<'a, 'info>> for CpiInstructionConfig<'a, 'info> { - type Error = LightPdaError; - - fn try_from(cpi_accounts: &'a CpiAccounts<'a, 'info>) -> Result { - Ok(CpiInstructionConfig { - fee_payer: *cpi_accounts.fee_payer().key, - cpi_signer: cpi_accounts.config().cpi_signer().into(), - invoking_program: cpi_accounts.config().cpi_signer.program_id.into(), - sol_pool_pda_pubkey: if cpi_accounts.config().sol_pool_pda { - Some(*cpi_accounts.sol_pool_pda()?.key) - } else { - None - }, - sol_compression_recipient_pubkey: if cpi_accounts.config().sol_compression_recipient { - Some(*cpi_accounts.decompression_recipient()?.key) - } else { - None - }, - cpi_context_pubkey: if cpi_accounts.config().cpi_context { - Some(*cpi_accounts.cpi_context()?.key) - } else { - None - }, - packed_accounts: cpi_accounts.tree_accounts().unwrap_or(&[]), - }) - } -} diff --git a/sdk-libs/sdk-interface/src/.backup/cpi/v1/invoke.rs b/sdk-libs/sdk-interface/src/.backup/cpi/v1/invoke.rs deleted file mode 100644 index b90c1c8db3..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/cpi/v1/invoke.rs +++ /dev/null @@ -1,186 +0,0 @@ -use light_compressed_account::instruction_data::{ - compressed_proof::ValidityProof, invoke_cpi::InstructionDataInvokeCpi, -}; - -use crate::{ - cpi::{instruction::LightCpiInstruction, invoke::LightInstructionData, CpiSigner}, - AnchorSerialize, -}; - -/// Light system program CPI instruction data builder. -/// -/// Use this builder to construct instructions for compressed account operations: -/// creating, updating, closing accounts, and compressing/decompressing SOL. -/// -/// # Builder Methods -/// -/// ## Common Methods -/// -/// - [`with_new_addresses()`](Self::with_new_addresses) - Create new compressed account addresses -/// - [`compress_lamports()`](Self::compress_lamports) - Compress SOL into compressed accounts -/// - [`decompress_lamports()`](Self::decompress_lamports) - Decompress SOL from compressed accounts -/// -/// **Note**: An instruction can either compress **or** decompress lamports, not both. -/// -/// ## Advanced Methods -/// -/// For fine-grained control: -/// -/// - [`with_input_compressed_accounts_with_merkle_context()`](Self::with_input_compressed_accounts_with_merkle_context) - Manually specify input accounts -/// - [`with_output_compressed_accounts()`](Self::with_output_compressed_accounts) - Manually specify output accounts -#[derive(Clone)] -pub struct LightSystemProgramCpi { - cpi_signer: CpiSigner, - instruction_data: InstructionDataInvokeCpi, -} - -impl LightSystemProgramCpi { - #[must_use = "with_new_addresses returns a new value"] - pub fn with_new_addresses( - mut self, - new_address_params: &[light_compressed_account::instruction_data::data::NewAddressParamsPacked], - ) -> Self { - self.instruction_data = self.instruction_data.with_new_addresses(new_address_params); - self - } - - #[must_use = "with_input_compressed_accounts_with_merkle_context returns a new value"] - pub fn with_input_compressed_accounts_with_merkle_context( - mut self, - input_compressed_accounts_with_merkle_context: &[light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext], - ) -> Self { - self.instruction_data = self - .instruction_data - .with_input_compressed_accounts_with_merkle_context( - input_compressed_accounts_with_merkle_context, - ); - self - } - - #[must_use = "with_output_compressed_accounts returns a new value"] - pub fn with_output_compressed_accounts( - mut self, - output_compressed_accounts: &[light_compressed_account::instruction_data::data::OutputCompressedAccountWithPackedContext], - ) -> Self { - self.instruction_data = self - .instruction_data - .with_output_compressed_accounts(output_compressed_accounts); - self - } - - #[must_use = "compress_lamports returns a new value"] - pub fn compress_lamports(mut self, lamports: u64) -> Self { - self.instruction_data = self.instruction_data.compress_lamports(lamports); - self - } - - #[must_use = "decompress_lamports returns a new value"] - pub fn decompress_lamports(mut self, lamports: u64) -> Self { - self.instruction_data = self.instruction_data.decompress_lamports(lamports); - self - } - #[must_use = "write_to_cpi_context_set returns a new value"] - pub fn write_to_cpi_context_set(mut self) -> Self { - self.instruction_data = self.instruction_data.write_to_cpi_context_set(); - self - } - #[must_use = "write_to_cpi_context_first returns a new value"] - pub fn write_to_cpi_context_first(mut self) -> Self { - self.instruction_data = self.instruction_data.write_to_cpi_context_first(); - self - } - #[must_use = "with_cpi_context returns a new value"] - pub fn with_cpi_context( - mut self, - cpi_context: light_compressed_account::instruction_data::cpi_context::CompressedCpiContext, - ) -> Self { - self.instruction_data = self.instruction_data.with_cpi_context(cpi_context); - self - } - - /// Returns a reference to the inner instruction data. - pub fn instruction_data(&self) -> &InstructionDataInvokeCpi { - &self.instruction_data - } - - /// Returns a mutable reference to the inner instruction data. - pub fn instruction_data_mut(&mut self) -> &mut InstructionDataInvokeCpi { - &mut self.instruction_data - } - - /// Returns the CPI signer. - pub fn cpi_signer(&self) -> &CpiSigner { - &self.cpi_signer - } -} - -impl LightCpiInstruction for LightSystemProgramCpi { - fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self { - Self { - cpi_signer, - instruction_data: InstructionDataInvokeCpi::new(proof.into()), - } - } - - fn get_mode(&self) -> u8 { - 0 // V1 uses regular mode by default - } - - fn get_bump(&self) -> u8 { - self.cpi_signer.bump - } - fn write_to_cpi_context_first(mut self) -> Self { - self.instruction_data = self.instruction_data.write_to_cpi_context_first(); - self - } - fn write_to_cpi_context_set(mut self) -> Self { - self.instruction_data = self.instruction_data.write_to_cpi_context_set(); - self - } - fn execute_with_cpi_context(self) -> Self { - // V1 doesn't have a direct execute context, just return self - // The execute happens through the invoke call - self - } - fn get_with_cpi_context(&self) -> bool { - self.instruction_data.cpi_context.is_some() - } - fn get_cpi_context( - &self, - ) -> &light_compressed_account::instruction_data::cpi_context::CompressedCpiContext { - use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; - // Use a static default with all fields set to false/0 - static DEFAULT: CompressedCpiContext = CompressedCpiContext { - set_context: false, - first_set_context: false, - cpi_context_account_index: 0, - }; - self.instruction_data - .cpi_context - .as_ref() - .unwrap_or(&DEFAULT) - } - fn has_read_only_accounts(&self) -> bool { - // V1 doesn't support read-only accounts - false - } -} - -// Manual BorshSerialize implementation that only serializes instruction_data -impl AnchorSerialize for LightSystemProgramCpi { - fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - self.instruction_data.serialize(writer) - } -} - -impl light_compressed_account::InstructionDiscriminator for LightSystemProgramCpi { - fn discriminator(&self) -> &'static [u8] { - self.instruction_data.discriminator() - } -} - -impl LightInstructionData for LightSystemProgramCpi { - fn data(&self) -> Result, light_compressed_account::CompressedAccountError> { - self.instruction_data.data() - } -} diff --git a/sdk-libs/sdk-interface/src/.backup/cpi/v1/mod.rs b/sdk-libs/sdk-interface/src/.backup/cpi/v1/mod.rs deleted file mode 100644 index b9c66a8cd4..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/cpi/v1/mod.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! V1 CPI for Light system program. -//! -//! # Main Types -//! -//! - [`LightSystemProgramCpi`] - CPI instruction data builder -//! - [`CpiAccounts`] - CPI accounts struct -//! -//! -//! # Advanced Usage -//! -//! For maximum flexible light system program CPIs, see the [`lowlevel`] module or use `light-compressed-account` directly. - -mod accounts; -mod invoke; - -pub use accounts::CpiAccounts; -pub use invoke::LightSystemProgramCpi; - -/// Low-level types and functions for flexible Light system program CPIs. -/// -/// # Main Types -/// -/// For most use cases, you only need: -/// - [`LightSystemProgramCpi`] - Main CPI interface -/// - [`CpiAccounts`] - Account management -/// -/// The remaining types in this module are exported for low-level operations and internal use. -pub mod lowlevel { - pub use super::accounts::{ - get_account_metas_from_config, CpiInstructionConfig, SYSTEM_ACCOUNTS_LEN, - }; -} diff --git a/sdk-libs/sdk-interface/src/.backup/cpi/v2/accounts.rs b/sdk-libs/sdk-interface/src/.backup/cpi/v2/accounts.rs deleted file mode 100644 index 592878b59b..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/cpi/v2/accounts.rs +++ /dev/null @@ -1,98 +0,0 @@ -use light_sdk_types::cpi_accounts::v2::{ - CompressionCpiAccountIndex, CpiAccounts as GenericCpiAccounts, PROGRAM_ACCOUNTS_LEN, -}; - -use crate::error::Result; -use solana_account_info::AccountInfo; -use solana_instruction::AccountMeta; - -/// Light system program CPI accounts struct. -/// -/// Use with [`LightSystemProgramCpi`](super::LightSystemProgramCpi) to invoke the Light system program. -pub type CpiAccounts<'c, 'info> = GenericCpiAccounts<'c, AccountInfo<'info>>; - -pub fn to_account_metas(cpi_accounts: &CpiAccounts<'_, '_>) -> Result> { - // TODO: do a version with a const array instead of vector. - let mut account_metas = - Vec::with_capacity(1 + cpi_accounts.account_infos().len() - PROGRAM_ACCOUNTS_LEN); - - account_metas.push(AccountMeta { - pubkey: *cpi_accounts.fee_payer().key, - is_signer: true, - is_writable: true, - }); - account_metas.push(AccountMeta { - pubkey: *cpi_accounts.authority()?.key, - is_signer: true, - is_writable: false, - }); - - account_metas.push(AccountMeta { - pubkey: *cpi_accounts.registered_program_pda()?.key, - is_signer: false, - is_writable: false, - }); - account_metas.push(AccountMeta { - pubkey: *cpi_accounts.account_compression_authority()?.key, - is_signer: false, - is_writable: false, - }); - account_metas.push(AccountMeta { - pubkey: *cpi_accounts.account_compression_program()?.key, - is_signer: false, - is_writable: false, - }); - account_metas.push(AccountMeta { - pubkey: *cpi_accounts.system_program()?.key, - is_signer: false, - is_writable: false, - }); - let accounts = cpi_accounts.account_infos(); - let mut index = CompressionCpiAccountIndex::SolPoolPda as usize; - - if cpi_accounts.config().sol_pool_pda { - let account = cpi_accounts.get_account_info(index)?; - account_metas.push(AccountMeta { - pubkey: *account.key, - is_signer: false, - is_writable: true, - }); - index += 1; - } - - if cpi_accounts.config().sol_compression_recipient { - let account = cpi_accounts.get_account_info(index)?; - account_metas.push(AccountMeta { - pubkey: *account.key, - is_signer: false, - is_writable: true, - }); - index += 1; - } - - if cpi_accounts.config().cpi_context { - let account = cpi_accounts.get_account_info(index)?; - account_metas.push(AccountMeta { - pubkey: *account.key, - is_signer: false, - is_writable: true, - }); - index += 1; - } - assert_eq!(cpi_accounts.system_accounts_end_offset(), index); - - let tree_accounts = - accounts - .get(index..) - .ok_or(crate::error::LightPdaError::CpiAccountsIndexOutOfBounds( - index, - ))?; - tree_accounts.iter().for_each(|acc| { - account_metas.push(AccountMeta { - pubkey: *acc.key, - is_signer: acc.is_signer, - is_writable: acc.is_writable, - }); - }); - Ok(account_metas) -} diff --git a/sdk-libs/sdk-interface/src/.backup/cpi/v2/accounts_cpi_context.rs b/sdk-libs/sdk-interface/src/.backup/cpi/v2/accounts_cpi_context.rs deleted file mode 100644 index 46b6ccd7a2..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/cpi/v2/accounts_cpi_context.rs +++ /dev/null @@ -1,13 +0,0 @@ -use light_sdk_types::cpi_context_write::CpiContextWriteAccounts; -use solana_account_info::AccountInfo; -use solana_instruction::AccountMeta; - -pub fn get_account_metas_from_config_cpi_context( - config: CpiContextWriteAccounts, -) -> [AccountMeta; 3] { - [ - AccountMeta::new(*config.fee_payer.key, true), - AccountMeta::new_readonly(config.cpi_signer.cpi_signer.into(), true), - AccountMeta::new(*config.cpi_context.key, false), - ] -} diff --git a/sdk-libs/sdk-interface/src/.backup/cpi/v2/invoke.rs b/sdk-libs/sdk-interface/src/.backup/cpi/v2/invoke.rs deleted file mode 100644 index ac73d3dc89..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/cpi/v2/invoke.rs +++ /dev/null @@ -1,100 +0,0 @@ -use light_compressed_account::instruction_data::{ - compressed_proof::ValidityProof, with_account_info::InstructionDataInvokeCpiWithAccountInfo, -}; -use light_sdk_types::CpiSigner; -use super::lowlevel::CompressedCpiContext; -use super::lowlevel::{to_account_metas, InstructionDataInvokeCpiWithReadOnly}; -use crate::cpi::{account::CpiAccountsTrait, instruction::LightCpiInstruction, v2::CpiAccounts}; -use solana_account_info::AccountInfo; -use solana_instruction::AccountMeta; -use solana_program_error::ProgramError; - -impl<'info> CpiAccountsTrait<'info> for CpiAccounts<'_, 'info> { - fn to_account_infos(&self) -> Vec> { - self.to_account_infos() - } - - fn to_account_metas(&self) -> Result, ProgramError> { - to_account_metas(self).map_err(ProgramError::from) - } - - fn get_mode(&self) -> Option { - Some(1) // v2 mode - } -} - -impl LightCpiInstruction for InstructionDataInvokeCpiWithReadOnly { - fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self { - Self { - bump: cpi_signer.bump, - invoking_program_id: cpi_signer.program_id.into(), - proof: proof.into(), - mode: 1, - ..Default::default() - } - } - fn write_to_cpi_context_first(self) -> Self { - self.write_to_cpi_context_first() - } - fn write_to_cpi_context_set(self) -> Self { - self.write_to_cpi_context_set() - } - fn execute_with_cpi_context(self) -> Self { - self.execute_with_cpi_context() - } - - fn get_mode(&self) -> u8 { - self.mode - } - fn get_with_cpi_context(&self) -> bool { - self.with_cpi_context - } - fn get_cpi_context(&self) -> &CompressedCpiContext { - &self.cpi_context - } - - fn get_bump(&self) -> u8 { - self.bump - } - fn has_read_only_accounts(&self) -> bool { - !self.read_only_accounts.is_empty() - } -} - -impl LightCpiInstruction for InstructionDataInvokeCpiWithAccountInfo { - fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self { - Self { - bump: cpi_signer.bump, - invoking_program_id: cpi_signer.program_id.into(), - proof: proof.into(), - mode: 1, - ..Default::default() - } - } - fn write_to_cpi_context_first(self) -> Self { - self.write_to_cpi_context_first() - } - fn write_to_cpi_context_set(self) -> Self { - self.write_to_cpi_context_set() - } - fn execute_with_cpi_context(self) -> Self { - self.execute_with_cpi_context() - } - - fn get_mode(&self) -> u8 { - self.mode - } - fn get_with_cpi_context(&self) -> bool { - self.with_cpi_context - } - fn get_cpi_context(&self) -> &CompressedCpiContext { - &self.cpi_context - } - - fn get_bump(&self) -> u8 { - self.bump - } - fn has_read_only_accounts(&self) -> bool { - !self.read_only_accounts.is_empty() - } -} diff --git a/sdk-libs/sdk-interface/src/.backup/cpi/v2/mod.rs b/sdk-libs/sdk-interface/src/.backup/cpi/v2/mod.rs deleted file mode 100644 index 3bc79d43bf..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/cpi/v2/mod.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! V2 CPI for Light system program - optimized for compressed PDAs. -//! -//! # Main Types -//! -//! - [`LightSystemProgramCpi`] - CPI instruction data builder -//! - [`CpiAccounts`] - CPI accounts struct -//! -//! -//! # Advanced Usage -//! -//! For maximum flexible light system program CPIs, see the [`lowlevel`] module or use `light-compressed-account` directly. - -mod accounts; -mod accounts_cpi_context; -mod invoke; - -pub use accounts::CpiAccounts; -pub use accounts_cpi_context::*; -/// Light system program CPI instruction data builder. -/// -/// Use this builder to construct instructions for compressed account operations: -/// creating, updating, closing accounts, and compressing/decompressing SOL. -/// -/// # Builder Methods -/// -/// ## Common Methods -/// -/// - [`with_new_addresses()`](crate::cpi::v2::LightSystemProgramCpi::with_new_addresses) - Create new compressed account addresses. -/// - [`with_read_only_addresses()`](crate::cpi::v2::LightSystemProgramCpi::with_read_only_addresses) - Validate that addresses don't exist without creating them. -/// - [`with_read_only_accounts()`](crate::cpi::v2::LightSystemProgramCpi::with_read_only_accounts) - Validate that compressed account state exists without updating it. -/// - [`compress_lamports()`](crate::cpi::v2::LightSystemProgramCpi::compress_lamports) - Compress SOL into compressed accounts. -/// - [`decompress_lamports()`](crate::cpi::v2::LightSystemProgramCpi::decompress_lamports) - Decompress SOL from compressed accounts. -/// -/// **Note**: An instruction can either compress **or** decompress lamports, not both. -/// ## Advanced Methods -/// -/// For fine-grained control: -/// -/// - [`with_account_infos()`](crate::cpi::v2::LightSystemProgramCpi::with_account_infos) - Manually specify CompressedAccountInfos. -pub use light_compressed_account::instruction_data::with_account_info::InstructionDataInvokeCpiWithAccountInfo as LightSystemProgramCpi; - -/// Low-level types and functions for flexible Light system program CPIs. -/// -/// # Main Types -/// -/// For most use cases, you only need: -/// - [`LightSystemProgramCpi`] - Main CPI interface -/// - [`CpiAccounts`] - Account management -/// -/// The remaining types in this module are exported for low-level operations and internal use. -pub mod lowlevel { - - /// CPI context for batched compressed account operations. - pub use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; - /// Account information for compressed accounts in CPI operations. - pub use light_compressed_account::instruction_data::with_account_info::CompressedAccountInfo; - /// Input account information for compressed accounts. - pub use light_compressed_account::instruction_data::with_account_info::InAccountInfo; - /// Output account information for compressed accounts. - pub use light_compressed_account::instruction_data::with_account_info::OutAccountInfo; - /// Input compressed account for read-only operations. - pub use light_compressed_account::instruction_data::with_readonly::InAccount; - /// V2 CPI instruction data for read-only compressed account operations. - /// - /// Provides more flexibility for complex operations such as changing the compressed account owner. - /// Most users should use [`crate::cpi::v2::LightSystemProgramCpi`] instead. - pub use light_compressed_account::instruction_data::with_readonly::InstructionDataInvokeCpiWithReadOnly; - - pub use crate::cpi::v2::accounts::to_account_metas; -} diff --git a/sdk-libs/sdk-interface/src/.backup/program/config/create.rs b/sdk-libs/sdk-interface/src/.backup/program/config/create.rs deleted file mode 100644 index c86c3a9997..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/program/config/create.rs +++ /dev/null @@ -1,305 +0,0 @@ -//! Config initialization instructions. - -use light_account_checks::{ - checks::check_signer, - discriminator::{Discriminator, DISCRIMINATOR_LEN}, -}; -use light_compressible::rent::RentConfig; -use solana_account_info::AccountInfo; -use solana_cpi::invoke_signed; -use solana_loader_v3_interface::state::UpgradeableLoaderState; -use solana_msg::msg; -use solana_program_error::ProgramError; -use solana_pubkey::Pubkey; -use solana_system_interface::instruction as system_instruction; -use solana_sysvar::{rent::Rent, Sysvar}; - -use super::{state::LightConfig, validate_address_space_no_duplicates, COMPRESSIBLE_CONFIG_SEED}; -use crate::{error::LightPdaError, AnchorSerialize}; - -const BPF_LOADER_UPGRADEABLE_ID: Pubkey = - Pubkey::from_str_const("BPFLoaderUpgradeab1e11111111111111111111111"); - -/// Creates a new compressible config PDA -/// -/// # Security - Solana Best Practice -/// This function follows the standard Solana pattern where only the program's -/// upgrade authority can create the initial config. This prevents unauthorized -/// parties from hijacking the config system. -/// -/// # Arguments -/// * `config_account` - The config PDA account to initialize -/// * `update_authority` - Authority that can update the config after creation -/// * `rent_sponsor` - Account that receives rent from compressed PDAs -/// * `compression_authority` - Authority that can compress/close PDAs -/// * `rent_config` - Rent function parameters -/// * `write_top_up` - Lamports to top up on each write -/// * `address_space` - Address space for compressed accounts (currently 1 address_tree allowed) -/// * `config_bump` - Config bump seed (must be 0 for now) -/// * `payer` - Account paying for the PDA creation -/// * `system_program` - System program -/// * `program_id` - The program that owns the config -/// -/// # Required Validation (must be done by caller) -/// The caller MUST validate that the signer is the program's upgrade authority -/// by checking against the program data account. This cannot be done in the SDK -/// due to dependency constraints. -/// -/// # Returns -/// * `Ok(())` if config was created successfully -/// * `Err(ProgramError)` if there was an error -#[allow(clippy::too_many_arguments)] -pub fn process_initialize_light_config<'info>( - config_account: &AccountInfo<'info>, - update_authority: &AccountInfo<'info>, - rent_sponsor: &Pubkey, - compression_authority: &Pubkey, - rent_config: RentConfig, - write_top_up: u32, - address_space: Vec, - config_bump: u8, - payer: &AccountInfo<'info>, - system_program: &AccountInfo<'info>, - program_id: &Pubkey, -) -> Result<(), ProgramError> { - // CHECK: only 1 address_space - if config_bump != 0 { - msg!("Config bump must be 0 for now, found: {}", config_bump); - return Err(LightPdaError::ConstraintViolation.into()); - } - - // CHECK: not already initialized - if config_account.data_len() > 0 { - msg!("Config account already initialized"); - return Err(LightPdaError::ConstraintViolation.into()); - } - - // CHECK: only 1 address_space - if address_space.len() != 1 { - msg!( - "Address space must contain exactly 1 pubkey, found: {}", - address_space.len() - ); - return Err(LightPdaError::ConstraintViolation.into()); - } - - // CHECK: unique pubkeys in address_space - validate_address_space_no_duplicates(&address_space)?; - - // CHECK: signer - check_signer(update_authority).inspect_err(|_| { - msg!("Update authority must be signer for initial config creation"); - })?; - - // CHECK: pda derivation - let (derived_pda, bump) = LightConfig::derive_pda(program_id, config_bump); - if derived_pda != *config_account.key { - msg!("Invalid config PDA"); - return Err(LightPdaError::ConstraintViolation.into()); - } - - // Derive rent_sponsor_bump for storage - let (derived_rent_sponsor, rent_sponsor_bump) = - LightConfig::derive_rent_sponsor_pda(program_id); - if *rent_sponsor != derived_rent_sponsor { - msg!( - "rent_sponsor must be derived PDA: expected {:?}, got {:?}", - derived_rent_sponsor, - rent_sponsor - ); - return Err(LightPdaError::InvalidRentSponsor.into()); - } - - let rent = Rent::get().map_err(LightPdaError::from)?; - let account_size = LightConfig::size_for_address_space(address_space.len()); - let rent_lamports = rent.minimum_balance(account_size); - - // Use u16 to_le_bytes to match derive_pda (2 bytes instead of 1) - let config_bump_bytes = (config_bump as u16).to_le_bytes(); - let seeds = &[ - COMPRESSIBLE_CONFIG_SEED, - config_bump_bytes.as_ref(), - &[bump], - ]; - let create_account_ix = system_instruction::create_account( - payer.key, - config_account.key, - rent_lamports, - account_size as u64, - program_id, - ); - - invoke_signed( - &create_account_ix, - &[ - payer.clone(), - config_account.clone(), - system_program.clone(), - ], - &[seeds], - ) - .map_err(LightPdaError::from)?; - - let config = LightConfig { - version: 1, - write_top_up, - update_authority: *update_authority.key, - rent_sponsor: *rent_sponsor, - compression_authority: *compression_authority, - rent_config, - config_bump, - bump, - rent_sponsor_bump, - address_space, - }; - - let mut data = config_account - .try_borrow_mut_data() - .map_err(LightPdaError::from)?; - - // Write discriminator first (using trait constant) - data[..DISCRIMINATOR_LEN].copy_from_slice(&LightConfig::LIGHT_DISCRIMINATOR); - - // Serialize config data after discriminator - config - .serialize(&mut &mut data[DISCRIMINATOR_LEN..]) - .map_err(|_| LightPdaError::Borsh)?; - - Ok(()) -} - -/// Checks that the signer is the program's upgrade authority -/// -/// # Arguments -/// * `program_id` - The program to check -/// * `program_data_account` - The program's data account (ProgramData) -/// * `authority` - The authority to verify -/// -/// # Returns -/// * `Ok(())` if authority is valid -/// * `Err(LightPdaError)` if authority is invalid or verification fails -pub fn check_program_upgrade_authority( - program_id: &Pubkey, - program_data_account: &AccountInfo, - authority: &AccountInfo, -) -> Result<(), ProgramError> { - // CHECK: program data PDA - let (expected_program_data, _) = - Pubkey::find_program_address(&[program_id.as_ref()], &BPF_LOADER_UPGRADEABLE_ID); - if program_data_account.key != &expected_program_data { - msg!("Invalid program data account"); - return Err(LightPdaError::ConstraintViolation.into()); - } - - let data = program_data_account.try_borrow_data()?; - let program_state: UpgradeableLoaderState = bincode::deserialize(&data).map_err(|_| { - msg!("Failed to deserialize program data account"); - LightPdaError::ConstraintViolation - })?; - - // Extract upgrade authority - let upgrade_authority = match program_state { - UpgradeableLoaderState::ProgramData { - slot: _, - upgrade_authority_address, - } => { - match upgrade_authority_address { - Some(auth) => { - // Check for invalid zero authority when authority exists - if auth == Pubkey::default() { - msg!("Invalid state: authority is zero pubkey"); - return Err(LightPdaError::ConstraintViolation.into()); - } - auth - } - None => { - msg!("Program has no upgrade authority"); - return Err(LightPdaError::ConstraintViolation.into()); - } - } - } - _ => { - msg!("Account is not ProgramData, found: {:?}", program_state); - return Err(LightPdaError::ConstraintViolation.into()); - } - }; - - // CHECK: upgrade authority is signer - check_signer(authority).inspect_err(|_| { - msg!("Authority must be signer"); - })?; - - // CHECK: upgrade authority is program's upgrade authority - if *authority.key != upgrade_authority { - msg!( - "Signer is not the program's upgrade authority. Signer: {:?}, Expected Authority: {:?}", - authority.key, - upgrade_authority - ); - return Err(LightPdaError::ConstraintViolation.into()); - } - - Ok(()) -} - -/// Creates a new compressible config PDA. -/// -/// # Arguments -/// * `config_account` - The config PDA account to initialize -/// * `update_authority` - Must be the program's upgrade authority -/// * `program_data_account` - The program's data account for validation -/// * `rent_sponsor` - Account that receives rent from compressed PDAs -/// * `compression_authority` - Authority that can compress/close PDAs -/// * `rent_config` - Rent function parameters -/// * `write_top_up` - Lamports to top up on each write -/// * `address_space` - Address spaces for compressed accounts (exactly 1 -/// allowed) -/// * `config_bump` - Config bump seed (must be 0 for now) -/// * `payer` - Account paying for the PDA creation -/// * `system_program` - System program -/// * `program_id` - The program that owns the config -/// -/// # Returns -/// * `Ok(())` if config was created successfully -/// * `Err(ProgramError)` if there was an error or authority validation fails -#[allow(clippy::too_many_arguments)] -pub fn process_initialize_light_config_checked<'info>( - config_account: &AccountInfo<'info>, - update_authority: &AccountInfo<'info>, - program_data_account: &AccountInfo<'info>, - rent_sponsor: &Pubkey, - compression_authority: &Pubkey, - rent_config: RentConfig, - write_top_up: u32, - address_space: Vec, - config_bump: u8, - payer: &AccountInfo<'info>, - system_program: &AccountInfo<'info>, - program_id: &Pubkey, -) -> Result<(), ProgramError> { - msg!( - "create_compression_config_checked program_data_account: {:?}", - program_data_account.key - ); - msg!( - "create_compression_config_checked program_id: {:?}", - program_id - ); - // Verify the signer is the program's upgrade authority - check_program_upgrade_authority(program_id, program_data_account, update_authority)?; - - // Create the config with validated authority - process_initialize_light_config( - config_account, - update_authority, - rent_sponsor, - compression_authority, - rent_config, - write_top_up, - address_space, - config_bump, - payer, - system_program, - program_id, - ) -} diff --git a/sdk-libs/sdk-interface/src/.backup/program/config/update.rs b/sdk-libs/sdk-interface/src/.backup/program/config/update.rs deleted file mode 100644 index a5571b2577..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/program/config/update.rs +++ /dev/null @@ -1,102 +0,0 @@ -//! Config update instruction. - -use light_account_checks::{checks::check_signer, discriminator::DISCRIMINATOR_LEN}; -use light_compressible::rent::RentConfig; -use solana_account_info::AccountInfo; -use solana_msg::msg; -use solana_program_error::ProgramError; -use solana_pubkey::Pubkey; - -use super::{ - state::LightConfig, validate_address_space_no_duplicates, validate_address_space_only_adds, - MAX_ADDRESS_TREES_PER_SPACE, -}; -use crate::{error::LightPdaError, AnchorSerialize}; - -/// Updates an existing compressible config -/// -/// # Arguments -/// * `config_account` - The config PDA account to update -/// * `authority` - Current update authority (must match config) -/// * `new_update_authority` - Optional new update authority -/// * `new_rent_sponsor` - Optional new rent recipient -/// * `new_compression_authority` - Optional new compression authority -/// * `new_rent_config` - Optional new rent function parameters -/// * `new_write_top_up` - Optional new write top-up amount -/// * `new_address_space` - Optional new address space (currently 1 address_tree allowed) -/// * `owner_program_id` - The program that owns the config -/// -/// # Returns -/// * `Ok(())` if config was updated successfully -/// * `Err(ProgramError)` if there was an error -#[allow(clippy::too_many_arguments)] -pub fn process_update_light_config<'info>( - config_account: &AccountInfo<'info>, - authority: &AccountInfo<'info>, - new_update_authority: Option<&Pubkey>, - new_rent_sponsor: Option<&Pubkey>, - new_compression_authority: Option<&Pubkey>, - new_rent_config: Option, - new_write_top_up: Option, - new_address_space: Option>, - owner_program_id: &Pubkey, -) -> Result<(), ProgramError> { - // CHECK: PDA derivation - let mut config = LightConfig::load_checked(config_account, owner_program_id)?; - - // CHECK: signer - check_signer(authority).inspect_err(|_| { - msg!("Update authority must be signer"); - })?; - // CHECK: authority - if *authority.key != config.update_authority { - msg!("Invalid update authority"); - return Err(LightPdaError::ConstraintViolation.into()); - } - - if let Some(new_authority) = new_update_authority { - config.update_authority = *new_authority; - } - if let Some(new_recipient) = new_rent_sponsor { - config.rent_sponsor = *new_recipient; - } - if let Some(new_auth) = new_compression_authority { - config.compression_authority = *new_auth; - } - if let Some(new_rcfg) = new_rent_config { - config.rent_config = new_rcfg; - } - if let Some(new_top_up) = new_write_top_up { - config.write_top_up = new_top_up; - } - if let Some(new_address_space) = new_address_space { - // CHECK: address space length - if new_address_space.len() != MAX_ADDRESS_TREES_PER_SPACE { - msg!( - "New address space must contain exactly 1 pubkey, found: {}", - new_address_space.len() - ); - return Err(LightPdaError::ConstraintViolation.into()); - } - - validate_address_space_no_duplicates(&new_address_space)?; - - validate_address_space_only_adds(&config.address_space, &new_address_space)?; - - config.address_space = new_address_space; - } - - let mut data = config_account.try_borrow_mut_data().map_err(|e| { - msg!("Failed to borrow mut data for config_account: {:?}", e); - LightPdaError::from(e) - })?; - // Serialize after discriminator (discriminator is preserved from init) - config - .serialize(&mut &mut data[DISCRIMINATOR_LEN..]) - .map_err(|e| { - msg!("Failed to serialize updated config: {:?}", e); - LightPdaError::Borsh - })?; - - Ok(()) -} diff --git a/sdk-libs/sdk-interface/src/.backup/program/decompression/create_token_account.rs b/sdk-libs/sdk-interface/src/.backup/program/decompression/create_token_account.rs deleted file mode 100644 index ef03341a25..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/program/decompression/create_token_account.rs +++ /dev/null @@ -1,160 +0,0 @@ -//! ATA and token account creation helpers for decompression. - -use light_token_interface::instructions::{ - create_token_account::CreateTokenAccountInstructionData, - extensions::{CompressToPubkey, CompressibleExtensionInstructionData}, -}; -use solana_instruction::{AccountMeta, Instruction}; -use solana_program_error::ProgramError; -use solana_pubkey::Pubkey; - -use crate::AnchorSerialize; - -/// Build a CreateAssociatedTokenAccountIdempotent instruction for ATA decompression. -/// -/// Creates a compressible ATA with compression_only mode (required for ATA decompression). -/// -/// # Account order (per on-chain handler): -/// 0. owner (non-mut, non-signer) - The wallet owner -/// 1. mint (non-mut, non-signer) - The token mint -/// 2. fee_payer (signer, writable) - Pays for account creation -/// 3. associated_token_account (writable, NOT signer) - The ATA to create -/// 4. system_program (readonly) - System program -/// 5. compressible_config (readonly) - Compressible config PDA -/// 6. rent_payer (writable) - Rent sponsor account -/// -/// # Arguments -/// * `wallet_owner` - The wallet owner (ATA derivation seed) -/// * `mint` - The token mint -/// * `fee_payer` - Pays for account creation -/// * `ata` - The ATA pubkey (derived from wallet_owner, program_id, mint) -/// * `bump` - The ATA derivation bump -/// * `compressible_config` - Compressible config PDA -/// * `rent_sponsor` - Rent sponsor account -/// * `write_top_up` - Lamports per write for top-up -#[allow(clippy::too_many_arguments)] -pub fn build_create_ata_instruction( - wallet_owner: &Pubkey, - mint: &Pubkey, - fee_payer: &Pubkey, - ata: &Pubkey, - bump: u8, - compressible_config: &Pubkey, - rent_sponsor: &Pubkey, - write_top_up: u32, -) -> Result { - use light_token_interface::instructions::{ - create_associated_token_account::CreateAssociatedTokenAccountInstructionData, - extensions::CompressibleExtensionInstructionData, - }; - - let instruction_data = CreateAssociatedTokenAccountInstructionData { - bump, - compressible_config: Some(CompressibleExtensionInstructionData { - token_account_version: 3, // ShaFlat version (required) - rent_payment: 16, // 24h, TODO: make configurable - compression_only: 1, // Required for ATA - write_top_up, - compress_to_account_pubkey: None, // Required to be None for ATA - }), - }; - - let mut data = Vec::new(); - data.push(102u8); // CreateAssociatedTokenAccountIdempotent discriminator - instruction_data - .serialize(&mut data) - .map_err(|e| ProgramError::BorshIoError(e.to_string()))?; - - let accounts = vec![ - AccountMeta::new_readonly(*wallet_owner, false), - AccountMeta::new_readonly(*mint, false), - AccountMeta::new(*fee_payer, true), - AccountMeta::new(*ata, false), // NOT a signer - ATA is derived - AccountMeta::new_readonly(Pubkey::default(), false), // system_program - AccountMeta::new_readonly(*compressible_config, false), - AccountMeta::new(*rent_sponsor, false), - ]; - - Ok(Instruction { - program_id: light_token_interface::LIGHT_TOKEN_PROGRAM_ID.into(), - accounts, - data, - }) -} - -/// Build a CreateTokenAccount instruction for decompression. -/// -/// Creates a compressible token account with ShaFlat version (required by light token program). -/// -/// # Account order: -/// 0. token_account (signer, writable) - The token account PDA to create -/// 1. mint (readonly) - The token mint -/// 2. fee_payer (signer, writable) - Pays for account creation -/// 3. compressible_config (readonly) - Compressible config PDA -/// 4. system_program (readonly) - System program -/// 5. rent_sponsor (writable) - Rent sponsor account -/// -/// # Arguments -/// * `signer_seeds` - Seeds including bump for the token account PDA -/// * `program_id` - Program ID that owns the token account PDA -#[allow(clippy::too_many_arguments)] -pub fn build_create_token_account_instruction( - token_account: &Pubkey, - mint: &Pubkey, - owner: &Pubkey, - fee_payer: &Pubkey, - compressible_config: &Pubkey, - rent_sponsor: &Pubkey, - write_top_up: u32, - signer_seeds: &[&[u8]], - program_id: &Pubkey, -) -> Result { - // Build CompressToPubkey from signer_seeds (last seed is bump) - let bump = signer_seeds - .last() - .and_then(|s| s.first().copied()) - .ok_or(ProgramError::InvalidSeeds)?; - let seeds_without_bump: Vec> = signer_seeds - .iter() - .take(signer_seeds.len().saturating_sub(1)) - .map(|s| s.to_vec()) - .collect(); - - let compress_to_account_pubkey = CompressToPubkey { - bump, - program_id: program_id.to_bytes(), - seeds: seeds_without_bump, - }; - - let instruction_data = CreateTokenAccountInstructionData { - owner: light_compressed_account::Pubkey::from(owner.to_bytes()), - compressible_config: Some(CompressibleExtensionInstructionData { - token_account_version: 3, // ShaFlat version (required) - rent_payment: 16, // 24h, TODO: make configurable - compression_only: 0, // Regular tokens can be transferred, not compression-only - write_top_up, - compress_to_account_pubkey: Some(compress_to_account_pubkey), - }), - }; - - let mut data = Vec::new(); - data.push(18u8); // InitializeAccount3 opcode - instruction_data - .serialize(&mut data) - .map_err(|e| ProgramError::BorshIoError(e.to_string()))?; - - let accounts = vec![ - AccountMeta::new(*token_account, true), - AccountMeta::new_readonly(*mint, false), - AccountMeta::new(*fee_payer, true), - AccountMeta::new_readonly(*compressible_config, false), - AccountMeta::new_readonly(Pubkey::default(), false), // system_program - AccountMeta::new(*rent_sponsor, false), - ]; - - Ok(Instruction { - program_id: light_token_interface::LIGHT_TOKEN_PROGRAM_ID.into(), - accounts, - data, - }) -} diff --git a/sdk-libs/sdk-interface/src/.backup/program/decompression/mod.rs b/sdk-libs/sdk-interface/src/.backup/program/decompression/mod.rs deleted file mode 100644 index d6d99bfac8..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/program/decompression/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Decompression functions for PDA and token accounts. - -#[cfg(feature = "anchor")] -pub mod create_token_account; - -#[cfg(feature = "anchor")] -pub mod processor; - -#[cfg(feature = "anchor")] -pub mod pda; - -#[cfg(feature = "anchor")] -pub mod token; diff --git a/sdk-libs/sdk-interface/src/.backup/program/decompression/pda.rs b/sdk-libs/sdk-interface/src/.backup/program/decompression/pda.rs deleted file mode 100644 index 2fd0cca988..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/program/decompression/pda.rs +++ /dev/null @@ -1,181 +0,0 @@ -use anchor_lang::prelude::*; -use light_compressed_account::{ - address::derive_address, - compressed_account::PackedMerkleContext, - instruction_data::with_account_info::{CompressedAccountInfo, InAccountInfo, OutAccountInfo}, -}; -use light_compressible::DECOMPRESSED_PDA_DISCRIMINATOR; -use light_hasher::{sha256::Sha256BE, Hasher, Sha256}; -use light_sdk_types::{constants::RENT_SPONSOR_SEED, instruction::PackedStateTreeInfo}; -use solana_account_info::AccountInfo; -use solana_program_error::ProgramError; -use solana_pubkey::Pubkey; - -use crate::{ - accounts::create_pda::create_pda_account, - program::decompression::processor::DecompressCtx, - account::light_account::LightAccount, - program::variant::{LightAccountVariantTrait, PackedLightAccountVariantTrait}, - LightDiscriminator, -}; - -/// Generic prepare_account_for_decompression. -/// -/// Takes a packed variant and metadata, handles: -/// 1. Validating PDA derivation (security check - MUST be first) -/// 2. Checking idempotency (skip if already initialized) -/// 3. Getting seeds from packed variant -/// 4. Unpacking data -/// 5. Creating PDA and writing data -/// 6. Deriving compressed address from PDA key -/// 7. Building CompressedAccountInfo for CPI -/// -/// # Security -/// PDA validation MUST run before idempotency check to prevent accepting -/// wrong PDAs that happen to be already initialized. -/// -/// # Type Parameters -/// * `SEED_COUNT` - Number of seeds including bump -/// * `P` - Packed variant type implementing PackedLightAccountVariantTrait -pub fn prepare_account_for_decompression<'info, const SEED_COUNT: usize, P>( - packed: &P, - tree_info: &PackedStateTreeInfo, - output_queue_index: u8, - pda_account: &AccountInfo<'info>, - ctx: &mut DecompressCtx<'_, 'info>, -) -> std::result::Result<(), ProgramError> -where - P: PackedLightAccountVariantTrait, - >::Data: - LightAccount + LightDiscriminator + Clone + AnchorSerialize + AnchorDeserialize, -{ - // 1. Unpack to get seeds (must happen first for PDA validation) - let packed_accounts = ctx - .cpi_accounts - .packed_accounts() - .map_err(|_| ProgramError::NotEnoughAccountKeys)?; - - let unpacked = packed - .unpack(packed_accounts) - .map_err(|_| ProgramError::InvalidAccountData)?; - let account_data = unpacked.data().clone(); - - // 2. Get seeds from unpacked variant using seed_vec() (owned data, no lifetime issues) - let bump = packed.bump(); - let bump_bytes = [bump]; - let mut seed_vecs = unpacked.seed_vec(); - seed_vecs.push(bump_bytes.to_vec()); - let seed_slices: Vec<&[u8]> = seed_vecs.iter().map(|v| v.as_slice()).collect(); - - // 3. SECURITY: Validate PDA derivation FIRST (defense-in-depth) - // This MUST run before idempotency check to prevent accepting wrong PDAs - let expected_pda = Pubkey::create_program_address(&seed_slices, ctx.program_id) - .map_err(|_| ProgramError::InvalidSeeds)?; - - if pda_account.key != &expected_pda { - solana_msg::msg!( - "PDA key mismatch: expected {:?}, got {:?}", - expected_pda, - pda_account.key - ); - return Err(ProgramError::InvalidSeeds); - } - - // 4. Idempotency check - if PDA already has data (non-zero discriminator), skip - // IMPORTANT: This runs AFTER PDA validation so wrong PDAs cannot bypass validation - if crate::program::validation::is_pda_initialized(pda_account)? { - return Ok(()); - } - - // 5. Hash with canonical CompressionInfo::compressed() for input verification - let data_bytes = account_data - .try_to_vec() - .map_err(|_| ProgramError::InvalidAccountData)?; - let data_len = data_bytes.len(); - let mut input_data_hash = Sha256::hash(&data_bytes).map_err(|_| ProgramError::Custom(100))?; - input_data_hash[0] = 0; // Zero first byte per protocol convention - - // 6. Calculate space and create PDA - type Data = - <

>::Unpacked as LightAccountVariantTrait>::Data; - let discriminator_len = 8; - let space = discriminator_len + data_len.max( as LightAccount>::INIT_SPACE); - let rent_minimum = ctx.rent.minimum_balance(space); - - let system_program = ctx - .cpi_accounts - .system_program() - .map_err(|_| ProgramError::InvalidAccountData)?; - - // Construct rent sponsor seeds for PDA signing - let rent_sponsor_bump_bytes = [ctx.rent_sponsor_bump]; - let rent_sponsor_seeds: &[&[u8]] = &[RENT_SPONSOR_SEED, &rent_sponsor_bump_bytes]; - - create_pda_account( - ctx.rent_sponsor, - rent_sponsor_seeds, - pda_account, - rent_minimum, - space as u64, - ctx.program_id, - &seed_slices, - system_program, - )?; - - // 7. Write discriminator + data to PDA - let mut pda_data = pda_account.try_borrow_mut_data()?; - pda_data[..8] - .copy_from_slice(& as LightDiscriminator>::LIGHT_DISCRIMINATOR); - - // 8. Set decompressed state and serialize - let mut decompressed = account_data; - decompressed.set_decompressed(ctx.light_config, ctx.current_slot); - let writer = &mut &mut pda_data[8..]; - decompressed - .serialize(writer) - .map_err(|_| ProgramError::InvalidAccountData)?; - - // 9. Derive compressed address from PDA key (saves instruction data size) - let address = derive_address( - &pda_account.key.to_bytes(), - &ctx.light_config.address_space[0].to_bytes(), - &ctx.program_id.to_bytes(), - ); - - // 10. Build CompressedAccountInfo for CPI - let input = InAccountInfo { - data_hash: input_data_hash, - lamports: 0, - merkle_context: PackedMerkleContext { - merkle_tree_pubkey_index: tree_info.merkle_tree_pubkey_index, - queue_pubkey_index: tree_info.queue_pubkey_index, - leaf_index: tree_info.leaf_index, - prove_by_index: tree_info.prove_by_index, - }, - root_index: tree_info.root_index, - discriminator: as LightDiscriminator>::LIGHT_DISCRIMINATOR, - }; - - // Output is a DECOMPRESSED_PDA placeholder (same as init creates). - // This allows CompressAccountsIdempotent to re-compress the account - // in a future cycle by finding and nullifying this placeholder. - let pda_pubkey_bytes = pda_account.key.to_bytes(); - let output_data_hash = - Sha256BE::hash(&pda_pubkey_bytes).map_err(|_| ProgramError::Custom(101))?; - let output = OutAccountInfo { - lamports: 0, - output_merkle_tree_index: output_queue_index, - discriminator: DECOMPRESSED_PDA_DISCRIMINATOR, - data: pda_pubkey_bytes.to_vec(), - data_hash: output_data_hash, - }; - - // 11. Push to ctx's internal vec - ctx.compressed_account_infos.push(CompressedAccountInfo { - address: Some(address), - input: Some(input), - output: Some(output), - }); - - Ok(()) -} diff --git a/sdk-libs/sdk-interface/src/.backup/program/decompression/processor.rs b/sdk-libs/sdk-interface/src/.backup/program/decompression/processor.rs deleted file mode 100644 index 85ff61b499..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/program/decompression/processor.rs +++ /dev/null @@ -1,400 +0,0 @@ -//! SDK generic decompression functions. -//! -//! These functions are generic over account types and can be reused by the macro. -//! The decompress flow creates PDAs from compressed state (needs validity proof, packed data, seeds). - -use anchor_lang::{ - prelude::*, - solana_program::{clock::Clock, program::invoke_signed, rent::Rent, sysvar::Sysvar}, -}; -use light_compressed_account::instruction_data::{ - cpi_context::CompressedCpiContext, - compressed_proof::ValidityProof, - with_account_info::CompressedAccountInfo, -}; -use light_sdk_types::cpi_context_write::CpiContextWriteAccounts; -use light_sdk_types::{ - cpi_accounts::CpiAccountsConfig, instruction::PackedStateTreeInfo, CpiSigner, - ACCOUNT_COMPRESSION_AUTHORITY_PDA, ACCOUNT_COMPRESSION_PROGRAM_ID, LIGHT_SYSTEM_PROGRAM_ID, - REGISTERED_PROGRAM_PDA, -}; -use light_token_interface::{ - instructions::{ - extensions::ExtensionInstructionData, - transfer2::{ - CompressedTokenInstructionDataTransfer2, Compression, MultiInputTokenDataWithContext, - }, - }, - CPI_AUTHORITY, LIGHT_TOKEN_PROGRAM_ID, TRANSFER2, -}; -use solana_instruction::Instruction; -use solana_program_error::ProgramError; - -use crate::{ - account::compression_info::CompressedAccountData, - cpi::{v2::CpiAccounts, InvokeLightSystemProgram}, - program::config::LightConfig, -}; - -// ============================================================================ -// DecompressVariant Trait (implemented by program's PackedProgramAccountVariant) -// ============================================================================ - -/// Trait for packed program account variants that support decompression. -/// -/// This trait is implemented by the program's `PackedProgramAccountVariant` enum -/// to handle type-specific dispatch during decompression. -/// -/// MACRO-GENERATED: The implementation contains a match statement routing each -/// enum variant to the appropriate `prepare_account_for_decompression` call. -pub trait DecompressVariant<'info>: AnchorSerialize + AnchorDeserialize + Clone { - /// Decompress this variant into a PDA account. - /// - /// The implementation should match on the enum variant and call - /// `prepare_account_for_decompression::(packed, pda_account, ctx)`. - fn decompress( - &self, - meta: &PackedStateTreeInfo, - pda_account: &AccountInfo<'info>, - ctx: &mut DecompressCtx<'_, 'info>, - ) -> std::result::Result<(), ProgramError>; -} - -// ============================================================================ -// Parameters and Context -// ============================================================================ - -/// Parameters for decompress_idempotent instruction. -/// Generic over the variant type - each program defines its own `PackedProgramAccountVariant`. -/// -/// Field order matches `LoadAccountsData` from light-client for compatibility. -#[derive(AnchorSerialize, AnchorDeserialize, Clone)] -pub struct DecompressIdempotentParams -where - V: AnchorSerialize + AnchorDeserialize + Clone, -{ - /// Offset into remaining_accounts where Light system accounts begin - pub system_accounts_offset: u8, - /// All account variants less than offset are pda acccounts. - /// 255 if no token accounts - pub token_accounts_offset: u8, - /// Packed index of the output queue in remaining_accounts. - pub output_queue_index: u8, - /// Validity proof for compressed account verification - pub proof: ValidityProof, - /// Accounts to decompress - wrapped in CompressedAccountData for metadata - pub accounts: Vec>, -} - -/// Context struct holding all data needed for decompression. -/// Contains internal vec for collecting CompressedAccountInfo results. -pub struct DecompressCtx<'a, 'info> { - pub program_id: &'a Pubkey, - pub cpi_accounts: &'a CpiAccounts<'a, 'info>, - pub remaining_accounts: &'a [AccountInfo<'info>], - pub rent_sponsor: &'a AccountInfo<'info>, - /// Rent sponsor PDA bump for signing - pub rent_sponsor_bump: u8, - pub light_config: &'a LightConfig, - /// Token (ctoken) rent sponsor for creating token accounts - pub ctoken_rent_sponsor: &'a AccountInfo<'info>, - /// Token (ctoken) compressible config for creating token accounts - pub ctoken_compressible_config: &'a AccountInfo<'info>, - pub rent: &'a Rent, - pub current_slot: u64, - /// Packed index of the output queue in remaining_accounts. - pub output_queue_index: u8, - /// Internal vec - dispatch functions push results here - pub compressed_account_infos: Vec, - pub in_token_data: Vec, - pub in_tlv: Option>>, - pub token_seeds: Vec>, -} - -// ============================================================================ -// Processor Function -// ============================================================================ - -/// Remaining accounts layout: -/// [0]: fee_payer (Signer, mut) -/// [1]: config (LightConfig PDA) -/// [2]: rent_sponsor (mut) -/// [system_accounts_offset..]: Light system accounts for CPI -/// [remaining_accounts.len() - num_pda_accounts..]: PDA accounts to decompress -/// -/// Runtime processor - handles all the plumbing, dispatches via DecompressVariant trait. -/// -/// **Takes raw instruction data** and deserializes internally - minimizes macro code. -/// **Uses only remaining_accounts** - no Context struct needed. -/// **Generic over V** - the program's `PackedProgramAccountVariant` enum. -pub fn process_decompress_pda_accounts_idempotent<'info, V>( - remaining_accounts: &[AccountInfo<'info>], - instruction_data: &[u8], - cpi_signer: CpiSigner, - program_id: &Pubkey, -) -> std::result::Result<(), ProgramError> -where - V: DecompressVariant<'info>, -{ - // Deserialize params internally - let params = DecompressIdempotentParams::::try_from_slice(instruction_data) - .map_err(|_| ProgramError::InvalidInstructionData)?; - - // Extract and validate accounts using shared validation - let validated_ctx = - crate::program::validation::validate_decompress_accounts(remaining_accounts, program_id)?; - let fee_payer = &validated_ctx.fee_payer; - let rent_sponsor = &validated_ctx.rent_sponsor; - let rent_sponsor_bump = validated_ctx.rent_sponsor_bump; - let light_config = validated_ctx.light_config; - - let rent = Rent::get()?; - let current_slot = Clock::get()?.slot; - - let system_accounts_offset_usize = params.system_accounts_offset as usize; - if system_accounts_offset_usize > remaining_accounts.len() { - return Err(ProgramError::InvalidInstructionData); - } - let (pda_accounts, token_accounts) = params - .accounts - .split_at_checked(params.token_accounts_offset as usize) - .ok_or(ProgramError::NotEnoughAccountKeys)?; - - // PDA and token account infos are at the tail of remaining_accounts. - let num_hot_accounts = params.accounts.len(); - let hot_accounts_start = remaining_accounts - .len() - .checked_sub(num_hot_accounts) - .ok_or(ProgramError::NotEnoughAccountKeys)?; - let hot_account_infos = &remaining_accounts[hot_accounts_start..]; - let (pda_account_infos, token_account_infos) = hot_account_infos - .split_at_checked(params.token_accounts_offset as usize) - .ok_or(ProgramError::NotEnoughAccountKeys)?; - - let has_pda_accounts = !pda_accounts.is_empty(); - let has_token_accounts = !token_accounts.is_empty(); - let cpi_context = has_pda_accounts && has_token_accounts; - let config = CpiAccountsConfig { - sol_compression_recipient: false, - sol_pool_pda: false, - cpi_context, - cpi_signer, - }; - let cpi_accounts = CpiAccounts::new_with_config( - fee_payer, - &remaining_accounts[system_accounts_offset_usize..], - config, - ); - - // Token (ctoken) accounts layout in remaining_accounts: - // [0]fee_payer, [1]pda_config, [2]pda_rent_sponsor, [3]ctoken_rent_sponsor, - // [4]light_token_program, [5]cpi_authority, [6]ctoken_compressible_config - let ctoken_rent_sponsor = remaining_accounts - .get(3) - .ok_or(ProgramError::NotEnoughAccountKeys)?; - let ctoken_compressible_config = remaining_accounts - .get(6) - .ok_or(ProgramError::NotEnoughAccountKeys)?; - - // Build context struct with all needed data (includes internal vec) - let mut decompress_ctx = DecompressCtx { - program_id, - cpi_accounts: &cpi_accounts, - remaining_accounts, - rent_sponsor, - rent_sponsor_bump, - light_config: &light_config, - ctoken_rent_sponsor, - ctoken_compressible_config, - rent: &rent, - current_slot, - output_queue_index: params.output_queue_index, - compressed_account_infos: Vec::new(), - in_token_data: Vec::new(), - in_tlv: None, - token_seeds: Vec::new(), - }; - - // Process each account using trait dispatch on inner variant - for (pda_account, pda_account_info) in pda_accounts.iter().zip(pda_account_infos) { - pda_account.data.decompress( - &pda_account.tree_info, - pda_account_info, - &mut decompress_ctx, - )?; - } - // Process token accounts - for (token_account, token_account_info) in token_accounts.iter().zip(token_account_infos) { - token_account.data.decompress( - &token_account.tree_info, - token_account_info, - &mut decompress_ctx, - )?; - } - - if has_pda_accounts { - // CPI to Light System Program with proof - let pda_only = !cpi_context; - - if pda_only { - // Manual construction to avoid extra allocations - let instruction_data = light_compressed_account::instruction_data::with_account_info::InstructionDataInvokeCpiWithAccountInfo { - mode: 1, - bump: cpi_signer.bump, - invoking_program_id: cpi_signer.program_id.into(), - compress_or_decompress_lamports: 0, - is_compress: false, - with_cpi_context: false, - with_transaction_hash: false, - cpi_context: CompressedCpiContext::default(), - proof: params.proof.0, - new_address_params: Vec::new(), - account_infos: decompress_ctx.compressed_account_infos, - read_only_addresses: Vec::new(), - read_only_accounts: Vec::new(), - }; - instruction_data.invoke(cpi_accounts.clone())?; - } else { - { - // PDAs + tokens - write to CPI context first, tokens will execute - let authority = cpi_accounts - .authority() - .map_err(|_| ProgramError::MissingRequiredSignature)?; - let cpi_context_account = cpi_accounts - .cpi_context() - .map_err(|_| ProgramError::MissingRequiredSignature)?; - let system_cpi_accounts = CpiContextWriteAccounts { - fee_payer, - authority, - cpi_context: cpi_context_account, - cpi_signer, - }; - - // Manual construction to avoid extra allocations - let instruction_data = light_compressed_account::instruction_data::with_account_info::InstructionDataInvokeCpiWithAccountInfo { - mode: 1, - bump: cpi_signer.bump, - invoking_program_id: cpi_signer.program_id.into(), - compress_or_decompress_lamports: 0, - is_compress: false, - with_cpi_context: true, - with_transaction_hash: false, - cpi_context: CompressedCpiContext::first(), - proof: None, - new_address_params: Vec::new(), - account_infos: decompress_ctx.compressed_account_infos, - read_only_addresses: Vec::new(), - read_only_accounts: Vec::new(), - }; - instruction_data.invoke_write_to_cpi_context_first(system_cpi_accounts)?; - } - } - } - - if has_token_accounts { - let mut compressions = Vec::new(); - // Assumes is compressed to pubkey. - decompress_ctx - .in_token_data - .iter() - .for_each(|a| compressions.push(Compression::decompress(a.amount, a.mint, a.owner))); - let mut cpi = CompressedTokenInstructionDataTransfer2 { - with_transaction_hash: false, - in_token_data: decompress_ctx.in_token_data.clone(), - in_tlv: decompress_ctx.in_tlv.clone(), - with_lamports_change_account_merkle_tree_index: false, - lamports_change_account_merkle_tree_index: 0, - lamports_change_account_owner_index: 0, - output_queue: 0, - max_top_up: 0, - cpi_context: None, - compressions: Some(compressions), - proof: params.proof.0, - out_token_data: Vec::new(), - in_lamports: None, - out_lamports: None, - out_tlv: None, - }; - if has_pda_accounts { - cpi.cpi_context = Some( - light_token_interface::instructions::transfer2::CompressedCpiContext { - set_context: false, - first_set_context: false, - }, - ) - } - - // Build Transfer2 account_metas in the order the handler expects: - // [0] light_system_program (readonly) - // [1] fee_payer (signer, writable) - // [2] cpi_authority_pda (readonly) - // [3] registered_program_pda (readonly) - // [4] account_compression_authority (readonly) - // [5] account_compression_program (readonly) - // [6] system_program (readonly) - // [7] cpi_context (optional, writable) - // [N+] packed_accounts - let mut account_metas = vec![ - AccountMeta::new_readonly(Pubkey::new_from_array(LIGHT_SYSTEM_PROGRAM_ID), false), - AccountMeta::new(*fee_payer.key, true), - AccountMeta::new_readonly(Pubkey::new_from_array(CPI_AUTHORITY), false), - AccountMeta::new_readonly(Pubkey::new_from_array(REGISTERED_PROGRAM_PDA), false), - AccountMeta::new_readonly( - Pubkey::new_from_array(ACCOUNT_COMPRESSION_AUTHORITY_PDA), - false, - ), - AccountMeta::new_readonly( - Pubkey::new_from_array(ACCOUNT_COMPRESSION_PROGRAM_ID), - false, - ), - AccountMeta::new_readonly(Pubkey::default(), false), - ]; - if cpi_context { - let cpi_ctx = cpi_accounts - .cpi_context() - .map_err(|_| ProgramError::NotEnoughAccountKeys)?; - account_metas.push(AccountMeta::new(*cpi_ctx.key, false)); - } - let transfer2_packed_start = account_metas.len(); - let packed_accounts_offset = - system_accounts_offset_usize + cpi_accounts.system_accounts_end_offset(); - for account in &remaining_accounts[packed_accounts_offset..] { - account_metas.push(AccountMeta { - pubkey: *account.key, - is_signer: account.is_signer, - is_writable: account.is_writable, - }); - } - cpi.in_token_data.iter().for_each(|data| { - account_metas[data.owner as usize + transfer2_packed_start].is_signer = true; - }); - let mut instruction_data = vec![TRANSFER2]; - cpi.serialize(&mut instruction_data).unwrap(); - let instruction = Instruction { - program_id: LIGHT_TOKEN_PROGRAM_ID.into(), - accounts: account_metas, - data: instruction_data, - }; - // For ATAs, no PDA signing is needed (wallet owner signed at transaction level). - // For regular token accounts, use invoke_signed with PDA seeds. - if decompress_ctx.token_seeds.is_empty() { - // All tokens are ATAs - use regular invoke (no PDA signing needed) - anchor_lang::solana_program::program::invoke(&instruction, remaining_accounts)?; - } else { - // At least one regular token account - use invoke_signed with PDA seeds - let signer_seed_refs: Vec<&[u8]> = decompress_ctx - .token_seeds - .iter() - .map(|s| s.as_slice()) - .collect(); - - invoke_signed( - &instruction, - remaining_accounts, - &[signer_seed_refs.as_slice()], - )?; - } - } - - Ok(()) -} diff --git a/sdk-libs/sdk-interface/src/.backup/program/decompression/token.rs b/sdk-libs/sdk-interface/src/.backup/program/decompression/token.rs deleted file mode 100644 index 014046af96..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/program/decompression/token.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! Token account decompression. - -use light_sdk_types::instruction::PackedStateTreeInfo; -use light_token_interface::instructions::extensions::ExtensionInstructionData; -use solana_account_info::AccountInfo; -use solana_program_error::ProgramError; - -use super::create_token_account::{ - build_create_ata_instruction, build_create_token_account_instruction, -}; -use crate::program::{ - decompression::processor::DecompressCtx, - variant::PackedLightAccountVariantTrait, -}; - -pub fn prepare_token_account_for_decompression<'info, const SEED_COUNT: usize, P>( - packed: &P, - tree_info: &PackedStateTreeInfo, - output_queue_index: u8, - token_account_info: &AccountInfo<'info>, - ctx: &mut DecompressCtx<'_, 'info>, -) -> std::result::Result<(), ProgramError> -where - P: PackedLightAccountVariantTrait, -{ - let packed_accounts = ctx - .cpi_accounts - .packed_accounts() - .map_err(|_| ProgramError::NotEnoughAccountKeys)?; - let token_data = packed.into_in_token_data(tree_info, output_queue_index)?; - - // Get TLV extension early to detect ATA - let in_tlv: Option> = packed.into_in_tlv()?; - - // Extract ATA info from TLV if present - let ata_info = in_tlv.as_ref().and_then(|exts| { - exts.iter().find_map(|ext| { - if let ExtensionInstructionData::CompressedOnly(co) = ext { - if co.is_ata { - Some((co.bump, co.owner_index)) - } else { - None - } - } else { - None - } - }) - }); - - // Resolve mint pubkey from packed index - let mint_pubkey = packed_accounts - .get(token_data.mint as usize) - .ok_or(ProgramError::InvalidAccountData)? - .key; - - let fee_payer = ctx.cpi_accounts.fee_payer(); - - // Helper to check if token account is already initialized - // State byte at offset 108: 0=Uninitialized, 1=Initialized, 2=Frozen - const STATE_OFFSET: usize = 108; - let is_already_initialized = !token_account_info.data_is_empty() - && token_account_info.data_len() > STATE_OFFSET - && token_account_info.try_borrow_data()?[STATE_OFFSET] != 0; - - if let Some((ata_bump, wallet_owner_index)) = ata_info { - // ATA path: use invoke() without signer seeds - // Resolve wallet owner pubkey from packed index - let wallet_owner_pubkey = packed_accounts - .get(wallet_owner_index as usize) - .ok_or(ProgramError::InvalidAccountData)? - .key; - - // Idempotency check: only create ATA if it doesn't exist - // For ATAs, we still continue with decompression even if account exists - if token_account_info.data_is_empty() { - let instruction = build_create_ata_instruction( - wallet_owner_pubkey, - mint_pubkey, - fee_payer.key, - token_account_info.key, - ata_bump, - ctx.ctoken_compressible_config.key, - ctx.ctoken_rent_sponsor.key, - ctx.light_config.write_top_up, - )?; - - // Invoke WITHOUT signer seeds - ATA is derived from light token program, not our program - anchor_lang::solana_program::program::invoke(&instruction, ctx.remaining_accounts)?; - } - - // Don't extend token_seeds for ATAs (invoke, not invoke_signed) - } else { - // Regular token vault path: use invoke_signed with PDA seeds - // For regular vaults, if already initialized, skip BOTH creation AND decompression (full idempotency) - if is_already_initialized { - solana_msg::msg!("Token vault is already decompressed, skipping"); - return Ok(()); - } - - let bump = &[packed.bump()]; - let seeds = packed - .seed_refs_with_bump(packed_accounts, bump) - .map_err(|_| ProgramError::InvalidSeeds)?; - - // Derive owner pubkey from constant owner_seeds - let owner = packed.derive_owner(); - - let signer_seeds: Vec<&[u8]> = seeds.iter().copied().collect(); - - let instruction = build_create_token_account_instruction( - token_account_info.key, - mint_pubkey, - &owner, - fee_payer.key, - ctx.ctoken_compressible_config.key, - ctx.ctoken_rent_sponsor.key, - ctx.light_config.write_top_up, - &signer_seeds, - ctx.program_id, - )?; - - // Invoke with PDA seeds - anchor_lang::solana_program::program::invoke_signed( - &instruction, - ctx.remaining_accounts, - &[signer_seeds.as_slice()], - )?; - - // Push seeds for the Transfer2 CPI (needed for invoke_signed) - ctx.token_seeds.extend(seeds.iter().map(|s| s.to_vec())); - } - - // Push token data for the Transfer2 CPI (common for both ATA and regular paths) - ctx.in_token_data.push(token_data); - - // Push TLV data - if let Some(ctx_in_tlv) = ctx.in_tlv.as_mut() { - ctx_in_tlv.push(in_tlv.unwrap_or_default()); - } else if let Some(in_tlv) = in_tlv { - let mut ctx_in_tlv = vec![]; - for _ in 0..ctx.in_token_data.len() - 1 { - ctx_in_tlv.push(vec![]); - } - ctx_in_tlv.push(in_tlv); - ctx.in_tlv = Some(ctx_in_tlv); - } - - Ok(()) -} diff --git a/sdk-libs/sdk-interface/src/.backup/program/variant.rs b/sdk-libs/sdk-interface/src/.backup/program/variant.rs deleted file mode 100644 index 9b3ab25e37..0000000000 --- a/sdk-libs/sdk-interface/src/.backup/program/variant.rs +++ /dev/null @@ -1,187 +0,0 @@ -//! Traits for decompression variant construction and manual Light Protocol implementation. -//! -//! This module contains traits for typed compressed account handling: -//! - Base traits (`IntoVariant`) - always available -//! - Variant traits (`LightAccountVariantTrait`, `PackedLightAccountVariantTrait`) - anchor-gated -//! - Token seed traits (`UnpackedTokenSeeds`, `PackedTokenSeeds`) - anchor-gated - -// --- Base traits (always available) --- - -#[cfg(feature = "anchor")] -use anchor_lang::error::Error; -#[cfg(not(feature = "anchor"))] -use solana_program_error::ProgramError as Error; - -/// Trait for seeds that can construct a compressed account variant. -/// -/// Implemented by generated `XxxSeeds` structs (e.g., `UserRecordSeeds`). -/// The macro generates impls that deserialize account data and verify seeds match. -/// -/// # Example (generated code) -/// ```ignore -/// impl IntoVariant for UserRecordSeeds { -/// fn into_variant(self, data: &[u8]) -> Result { -/// RentFreeAccountVariant::user_record(data, self) -/// } -/// } -/// ``` -pub trait IntoVariant { - /// Construct variant from compressed account data bytes and these seeds. - /// - /// # Arguments - /// * `data` - Raw compressed account data bytes - /// - /// # Returns - /// The constructed variant on success, or an error if: - /// - Deserialization fails - /// - Seed verification fails (data.* seeds don't match account data) - fn into_variant(self, data: &[u8]) -> Result; -} - -// --- Anchor-gated variant traits --- - -#[cfg(feature = "anchor")] -mod anchor_traits { - use anchor_lang::prelude::*; - use light_sdk_types::instruction::PackedStateTreeInfo; - use light_token_interface::instructions::{ - extensions::ExtensionInstructionData, transfer2::MultiInputTokenDataWithContext, - }; - use solana_program_error::ProgramError; - - use crate::account::light_account::AccountType; - - /// Trait for unpacked compressed account variants with seeds. - /// - /// Implementations are generated by the `#[light_program]` macro for each - /// account type marked with `#[light_account(init)]`. - /// - /// # Type Parameters - /// * `SEED_COUNT` - Number of seeds including bump for CPI signing - /// * `Seeds` - The seeds struct type (e.g., `UserRecordSeeds`) - /// * `Data` - The account data type (e.g., `UserRecord`) - /// * `Packed` - The packed variant type for serialization - pub trait LightAccountVariantTrait: - Sized + Clone + AnchorSerialize + AnchorDeserialize - { - /// The program ID that owns accounts of this variant type. - const PROGRAM_ID: Pubkey; - - /// The seeds struct type containing seed values. - type Seeds; - - /// The account data type. - type Data; - - /// The packed variant type for efficient serialization. - type Packed: PackedLightAccountVariantTrait; - - /// Get a reference to the account data. - fn data(&self) -> &Self::Data; - - /// Get seed values as owned byte vectors for PDA derivation. - fn seed_vec(&self) -> Vec>; - - /// Get seed references with bump for CPI signing. - /// Returns a fixed-size array that can be passed to invoke_signed. - fn seed_refs_with_bump<'a>(&'a self, bump_storage: &'a [u8; 1]) -> [&'a [u8]; SEED_COUNT]; - - /// Derive the PDA address and bump seed using PROGRAM_ID. - fn derive_pda(&self) -> (Pubkey, u8) { - let seeds = self.seed_vec(); - let seed_slices: Vec<&[u8]> = seeds.iter().map(|s| s.as_slice()).collect(); - Pubkey::find_program_address(&seed_slices, &Self::PROGRAM_ID) - } - } - - /// Trait for packed compressed account variants. - /// - /// Packed variants use u8 indices instead of 32-byte Pubkeys for efficient - /// serialization. They can be unpacked back to full variants using account info. - #[allow(clippy::wrong_self_convention)] - pub trait PackedLightAccountVariantTrait: - Sized + Clone + AnchorSerialize + AnchorDeserialize - { - /// The unpacked variant type with full Pubkey values. - type Unpacked: LightAccountVariantTrait; - - /// The account type (Pda, Token, Ata, etc.) for dispatch. - const ACCOUNT_TYPE: AccountType; - - /// Get the PDA bump seed. - fn bump(&self) -> u8; - - /// Unpack this variant by resolving u8 indices to Pubkeys. - fn unpack(&self, accounts: &[AccountInfo]) -> Result; - - /// Get seed references with bump for CPI signing. - /// Resolves u8 indices to pubkey refs from accounts slice. - fn seed_refs_with_bump<'a>( - &'a self, - accounts: &'a [AccountInfo], - bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; SEED_COUNT], ProgramError>; - - /// Extract token data for compressed token CPI. - /// - /// Returns the packed token data needed for the token transfer instruction. - /// Only meaningful for token account variants; PDA variants should not override. - fn into_in_token_data( - &self, - tree_info: &PackedStateTreeInfo, - output_queue_index: u8, - ) -> Result; - - /// Extract TLV extension data for compressed token CPI. - /// - /// Returns extension instruction data if the token account has extensions. - /// Only meaningful for token account variants; PDA variants return `None`. - fn into_in_tlv(&self) -> Result>>; - - /// Derive the owner pubkey from constant owner_seeds and program ID. - /// Only meaningful for token account variants; PDA variants return default. - fn derive_owner(&self) -> Pubkey { - Pubkey::default() - } - } - - /// Trait for unpacked token seed structs. - /// - /// Generated by the `#[light_program]` macro on per-variant seed structs - /// (e.g., `TokenVaultSeeds`). Provides seed-specific behavior for the blanket - /// `LightAccountVariantTrait` impl on `TokenDataWithSeeds`. - pub trait UnpackedTokenSeeds: - Clone + std::fmt::Debug + AnchorSerialize + AnchorDeserialize - { - /// The packed seeds type. - type Packed: PackedTokenSeeds; - - const PROGRAM_ID: Pubkey; - fn seed_vec(&self) -> Vec>; - fn seed_refs_with_bump<'a>(&'a self, bump_storage: &'a [u8; 1]) -> [&'a [u8]; N]; - } - - /// Trait for packed token seed structs. - /// - /// Generated by the `#[light_program]` macro on per-variant packed seed structs - /// (e.g., `PackedTokenVaultSeeds`). Provides seed-specific behavior for the blanket - /// `PackedLightAccountVariantTrait` impl on `TokenDataWithPackedSeeds`. - pub trait PackedTokenSeeds: - crate::account::pack::Unpack + Clone + std::fmt::Debug + AnchorSerialize + AnchorDeserialize - { - fn bump(&self) -> u8; - fn seed_refs_with_bump<'a>( - &'a self, - accounts: &'a [AccountInfo], - bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; N], ProgramError>; - - /// Derive the owner pubkey from constant owner_seeds and program ID. - fn derive_owner(&self) -> Pubkey; - } -} - -#[cfg(feature = "anchor")] -pub use anchor_traits::{ - LightAccountVariantTrait, PackedLightAccountVariantTrait, PackedTokenSeeds, UnpackedTokenSeeds, -}; diff --git a/sdk-libs/sdk-interface/src/error.rs b/sdk-libs/sdk-interface/src/error.rs deleted file mode 100644 index ad6b3066fe..0000000000 --- a/sdk-libs/sdk-interface/src/error.rs +++ /dev/null @@ -1,79 +0,0 @@ -use light_account_checks::error::AccountError; -use light_compressed_account::CompressedAccountError; -use light_hasher::HasherError; -use light_sdk_types::error::LightSdkTypesError; -use thiserror::Error; - -#[derive(Debug, Error, PartialEq)] -pub enum LightPdaError { - #[error("Constraint violation")] - ConstraintViolation, - #[error("Borsh error.")] - Borsh, - #[error("Account check error: {0}")] - AccountCheck(#[from] AccountError), - #[error("Hasher error: {0}")] - Hasher(#[from] HasherError), - #[error("Missing compression_info field")] - MissingCompressionInfo, - #[error("Rent sponsor account does not match the expected PDA from config")] - InvalidRentSponsor, - #[error("Borsh IO error: {0}")] - BorshIo(String), - #[error("CPI accounts index out of bounds: {0}")] - CpiAccountsIndexOutOfBounds(usize), - #[error("Read-only accounts are not supported in write_to_cpi_context operations")] - ReadOnlyAccountsNotSupportedInCpiContext, - #[error("Compressed account error: {0}")] - CompressedAccountError(#[from] CompressedAccountError), - #[error("Account data too small")] - AccountDataTooSmall, - #[error("Invalid instruction data")] - InvalidInstructionData, - #[error("Invalid seeds")] - InvalidSeeds, - #[error("CPI invocation failed")] - CpiFailed, - #[error("Not enough account keys")] - NotEnoughAccountKeys, - #[error("Missing required signature")] - MissingRequiredSignature, -} - -pub type Result = core::result::Result; - -impl From for LightPdaError { - fn from(e: LightSdkTypesError) -> Self { - match e { - LightSdkTypesError::CpiAccountsIndexOutOfBounds(index) => { - LightPdaError::CpiAccountsIndexOutOfBounds(index) - } - LightSdkTypesError::AccountError(e) => LightPdaError::AccountCheck(e), - LightSdkTypesError::Hasher(e) => LightPdaError::Hasher(e), - _ => LightPdaError::ConstraintViolation, - } - } -} - -impl From for u32 { - fn from(e: LightPdaError) -> Self { - match e { - LightPdaError::ConstraintViolation => 17001, - LightPdaError::Borsh => 17002, - LightPdaError::AccountCheck(e) => e.into(), - LightPdaError::Hasher(e) => e.into(), - LightPdaError::MissingCompressionInfo => 17003, - LightPdaError::InvalidRentSponsor => 17004, - LightPdaError::BorshIo(_) => 17005, - LightPdaError::CpiAccountsIndexOutOfBounds(_) => 17006, - LightPdaError::ReadOnlyAccountsNotSupportedInCpiContext => 17007, - LightPdaError::CompressedAccountError(e) => e.into(), - LightPdaError::AccountDataTooSmall => 17008, - LightPdaError::InvalidInstructionData => 17009, - LightPdaError::InvalidSeeds => 17010, - LightPdaError::CpiFailed => 17011, - LightPdaError::NotEnoughAccountKeys => 17012, - LightPdaError::MissingRequiredSignature => 17013, - } - } -} diff --git a/sdk-libs/sdk-types/Cargo.toml b/sdk-libs/sdk-types/Cargo.toml index 7bad4c1b9b..e0e3059e8b 100644 --- a/sdk-libs/sdk-types/Cargo.toml +++ b/sdk-libs/sdk-types/Cargo.toml @@ -11,6 +11,8 @@ default = ["std", "v2"] std = ["alloc", "light-compressed-account/std", "solana-msg"] alloc = ["light-compressed-account/alloc"] keccak = ["light-hasher/keccak"] +sha256 = ["light-hasher/sha256", "light-compressed-account/sha256"] +token = ["dep:light-token-interface"] anchor = ["anchor-lang", "light-compressed-account/anchor"] idl-build = ["anchor-lang/idl-build", "anchor"] v2 = [] @@ -23,12 +25,22 @@ anchor-lang = { workspace = true, optional = true } light-account-checks = { workspace = true } light-hasher = { workspace = true } light-compressed-account = { workspace = true } +light-compressible = { workspace = true } light-macros = { workspace = true } +light-token-interface = { workspace = true, optional = true } solana-msg = { workspace = true, optional = true } # External dependencies borsh = { workspace = true } +bytemuck = { workspace = true } thiserror = { workspace = true } [dev-dependencies] solana-pubkey = { workspace = true } + +[lints.rust.unexpected_cfgs] +level = "allow" +check-cfg = [ + 'cfg(target_os, values("solana"))', + 'cfg(feature, values("frozen-abi", "no-entrypoint"))', +] diff --git a/sdk-libs/sdk-types/src/error.rs b/sdk-libs/sdk-types/src/error.rs index d766449ce8..49db3c6dd0 100644 --- a/sdk-libs/sdk-types/src/error.rs +++ b/sdk-libs/sdk-types/src/error.rs @@ -1,7 +1,11 @@ use light_account_checks::error::AccountError; +use light_compressed_account::CompressedAccountError; use light_hasher::HasherError; use thiserror::Error; +#[cfg(all(not(feature = "std"), feature = "alloc"))] +use alloc::string::String; + pub type Result = core::result::Result; #[derive(Debug, Error, PartialEq)] @@ -35,9 +39,37 @@ pub enum LightSdkTypesError { #[error("CpigAccounts accounts slice starts with an invalid account. It should start with LightSystemProgram SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7.")] InvalidCpiAccountsOffset, #[error(transparent)] - AccountError(#[from] AccountError), + AccountCheck(#[from] AccountError), #[error(transparent)] Hasher(#[from] HasherError), + // --- Variants merged from LightSdkTypesError (sdk-interface) --- + #[error("Constraint violation")] + ConstraintViolation, + #[error("Borsh serialization/deserialization error")] + Borsh, + #[error("Missing compression info")] + MissingCompressionInfo, + #[error("Invalid rent sponsor")] + InvalidRentSponsor, + #[cfg(feature = "alloc")] + #[error("Borsh IO error: {0}")] + BorshIo(String), + #[error("Read-only accounts not supported in CPI context")] + ReadOnlyAccountsNotSupportedInCpiContext, + #[error(transparent)] + CompressedAccountError(#[from] CompressedAccountError), + #[error("Account data too small")] + AccountDataTooSmall, + #[error("Invalid instruction data")] + InvalidInstructionData, + #[error("Invalid seeds")] + InvalidSeeds, + #[error("CPI failed")] + CpiFailed, + #[error("Not enough account keys")] + NotEnoughAccountKeys, + #[error("Missing required signature")] + MissingRequiredSignature, } impl From for u32 { @@ -57,8 +89,22 @@ impl From for u32 { LightSdkTypesError::InvalidCpiContextAccount => 14032, LightSdkTypesError::InvalidSolPoolPdaAccount => 14033, LightSdkTypesError::InvalidCpiAccountsOffset => 14034, - LightSdkTypesError::AccountError(e) => e.into(), + LightSdkTypesError::AccountCheck(e) => e.into(), LightSdkTypesError::Hasher(e) => e.into(), + LightSdkTypesError::ConstraintViolation => 17001, + LightSdkTypesError::Borsh => 17002, + LightSdkTypesError::MissingCompressionInfo => 17003, + LightSdkTypesError::InvalidRentSponsor => 17004, + #[cfg(feature = "alloc")] + LightSdkTypesError::BorshIo(_) => 17005, + LightSdkTypesError::ReadOnlyAccountsNotSupportedInCpiContext => 17006, + LightSdkTypesError::CompressedAccountError(_) => 17007, + LightSdkTypesError::AccountDataTooSmall => 17008, + LightSdkTypesError::InvalidInstructionData => 17009, + LightSdkTypesError::InvalidSeeds => 17010, + LightSdkTypesError::CpiFailed => 17011, + LightSdkTypesError::NotEnoughAccountKeys => 17012, + LightSdkTypesError::MissingRequiredSignature => 17013, } } } diff --git a/sdk-libs/sdk-interface/src/account/compression_info.rs b/sdk-libs/sdk-types/src/interface/account/compression_info.rs similarity index 90% rename from sdk-libs/sdk-interface/src/account/compression_info.rs rename to sdk-libs/sdk-types/src/interface/account/compression_info.rs index 915d53f739..eaaf3cda68 100644 --- a/sdk-libs/sdk-interface/src/account/compression_info.rs +++ b/sdk-libs/sdk-types/src/interface/account/compression_info.rs @@ -1,18 +1,18 @@ extern crate alloc; use alloc::borrow::Cow; +use crate::instruction::PackedStateTreeInfo; use bytemuck::{Pod, Zeroable}; use light_account_checks::AccountInfoTrait; use light_compressible::rent::RentConfig; -use light_sdk_types::instruction::PackedStateTreeInfo; -use crate::{error::LightPdaError, AnchorDeserialize, AnchorSerialize}; +use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; pub trait HasCompressionInfo { - fn compression_info(&self) -> Result<&CompressionInfo, LightPdaError>; - fn compression_info_mut(&mut self) -> Result<&mut CompressionInfo, LightPdaError>; + fn compression_info(&self) -> Result<&CompressionInfo, LightSdkTypesError>; + fn compression_info_mut(&mut self) -> Result<&mut CompressionInfo, LightSdkTypesError>; fn compression_info_mut_opt(&mut self) -> &mut Option; - fn set_compression_info_none(&mut self) -> Result<(), LightPdaError>; + fn set_compression_info_none(&mut self) -> Result<(), LightSdkTypesError>; } /// Simple field accessor trait for types with a `compression_info: Option` field. @@ -40,7 +40,7 @@ pub trait CompressionInfoField { fn write_decompressed_info_to_slice( data: &mut [u8], current_slot: u64, - ) -> Result<(), LightPdaError> { + ) -> Result<(), LightSdkTypesError> { use crate::AnchorSerialize; let info = CompressionInfo { @@ -62,7 +62,7 @@ pub trait CompressionInfoField { }; if data.len() < offset + option_size { - return Err(LightPdaError::AccountDataTooSmall); + return Err(LightSdkTypesError::AccountDataTooSmall); } let target = &mut data[offset..offset + option_size]; @@ -70,30 +70,30 @@ pub trait CompressionInfoField { target[0] = 1; // Write CompressionInfo info.serialize(&mut &mut target[1..]) - .map_err(|e| LightPdaError::BorshIo(e.to_string()))?; + .map_err(|e| LightSdkTypesError::BorshIo(e.to_string()))?; Ok(()) } } impl HasCompressionInfo for T { - fn compression_info(&self) -> Result<&CompressionInfo, LightPdaError> { + fn compression_info(&self) -> Result<&CompressionInfo, LightSdkTypesError> { self.compression_info_field() .as_ref() - .ok_or(LightPdaError::MissingCompressionInfo) + .ok_or(LightSdkTypesError::MissingCompressionInfo) } - fn compression_info_mut(&mut self) -> Result<&mut CompressionInfo, LightPdaError> { + fn compression_info_mut(&mut self) -> Result<&mut CompressionInfo, LightSdkTypesError> { self.compression_info_field_mut() .as_mut() - .ok_or(LightPdaError::MissingCompressionInfo) + .ok_or(LightSdkTypesError::MissingCompressionInfo) } fn compression_info_mut_opt(&mut self) -> &mut Option { self.compression_info_field_mut() } - fn set_compression_info_none(&mut self) -> Result<(), LightPdaError> { + fn set_compression_info_none(&mut self) -> Result<(), LightSdkTypesError> { *self.compression_info_field_mut() = None; Ok(()) } @@ -190,7 +190,10 @@ impl CompressionInfo { /// Rent sponsor is always the config's rent_sponsor (not stored per-account). /// This means rent always flows to the protocol's rent pool upon compression, /// regardless of who paid for account creation. - pub fn new_from_config(cfg: &crate::program::config::LightConfig, current_slot: u64) -> Self { + pub fn new_from_config( + cfg: &crate::interface::program::config::LightConfig, + current_slot: u64, + ) -> Self { Self { last_claimed_slot: current_slot, lamports_per_write: cfg.write_top_up, @@ -291,12 +294,12 @@ impl CompressionInfo { &self, account_info: &AI, payer_info: &AI, - ) -> Result<(), LightPdaError> { + ) -> Result<(), LightSdkTypesError> { let bytes = account_info.data_len() as u64; let current_lamports = account_info.lamports(); - let current_slot = AI::get_current_slot().map_err(LightPdaError::AccountCheck)?; + let current_slot = AI::get_current_slot().map_err(LightSdkTypesError::AccountCheck)?; let rent_exemption_lamports = - AI::get_min_rent_balance(bytes as usize).map_err(LightPdaError::AccountCheck)?; + AI::get_min_rent_balance(bytes as usize).map_err(LightSdkTypesError::AccountCheck)?; let top_up = self.calculate_top_up_lamports( bytes, @@ -309,7 +312,7 @@ impl CompressionInfo { // Use System Program CPI to transfer lamports (payer is a signer, pass empty seeds) payer_info .transfer_lamports_cpi(account_info, top_up, &[]) - .map_err(LightPdaError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountCheck)?; } Ok(()) @@ -347,18 +350,18 @@ pub fn claim_completed_epoch_rent( account_info: &AI, account_data: &mut A, rent_sponsor: &AI, -) -> Result, LightPdaError> +) -> Result, LightSdkTypesError> where AI: AccountInfoTrait, A: HasCompressionInfo, { use light_compressible::rent::{AccountRentState, SLOTS_PER_EPOCH}; - let current_slot = AI::get_current_slot().map_err(LightPdaError::AccountCheck)?; + let current_slot = AI::get_current_slot().map_err(LightSdkTypesError::AccountCheck)?; let bytes = account_info.data_len() as u64; let current_lamports = account_info.lamports(); let rent_exemption_lamports = - AI::get_min_rent_balance(bytes as usize).map_err(LightPdaError::AccountCheck)?; + AI::get_min_rent_balance(bytes as usize).map_err(LightSdkTypesError::AccountCheck)?; let ci = account_data.compression_info_mut()?; let state = AccountRentState { @@ -391,7 +394,7 @@ where // since the program owns the account) account_info .transfer_lamports(rent_sponsor, amount) - .map_err(LightPdaError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountCheck)?; return Ok(Some(amount)); } } diff --git a/sdk-libs/sdk-interface/src/account/light_account.rs b/sdk-libs/sdk-types/src/interface/account/light_account.rs similarity index 83% rename from sdk-libs/sdk-interface/src/account/light_account.rs rename to sdk-libs/sdk-types/src/interface/account/light_account.rs index 13e5f56b70..e60a2c7b23 100644 --- a/sdk-libs/sdk-interface/src/account/light_account.rs +++ b/sdk-libs/sdk-types/src/interface/account/light_account.rs @@ -5,10 +5,8 @@ use light_account_checks::{ }; use light_hasher::DataHasher; -use crate::{ - account::compression_info::CompressionInfo, error::LightPdaError, - program::config::LightConfig, AnchorDeserialize, AnchorSerialize, -}; +use crate::interface::{account::compression_info::CompressionInfo, program::config::LightConfig}; +use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; pub enum AccountType { Pda, @@ -52,13 +50,13 @@ pub trait LightAccount: #[cfg(not(target_os = "solana"))] fn pack( &self, - accounts: &mut crate::instruction::PackedAccounts, - ) -> Result; + accounts: &mut crate::interface::instruction::PackedAccounts, + ) -> Result; /// Convert from packed form (indices -> Pubkeys). /// Generic over AccountInfoTrait for runtime-agnostic unpacking. fn unpack( packed: &Self::Packed, accounts: &ProgramPackedAccounts, - ) -> Result; + ) -> Result; } diff --git a/sdk-libs/sdk-interface/src/account/mod.rs b/sdk-libs/sdk-types/src/interface/account/mod.rs similarity index 100% rename from sdk-libs/sdk-interface/src/account/mod.rs rename to sdk-libs/sdk-types/src/interface/account/mod.rs diff --git a/sdk-libs/sdk-interface/src/account/pack.rs b/sdk-libs/sdk-types/src/interface/account/pack.rs similarity index 75% rename from sdk-libs/sdk-interface/src/account/pack.rs rename to sdk-libs/sdk-types/src/interface/account/pack.rs index 5e0e50fb58..3f6cb42e6e 100644 --- a/sdk-libs/sdk-interface/src/account/pack.rs +++ b/sdk-libs/sdk-types/src/interface/account/pack.rs @@ -2,7 +2,7 @@ use light_account_checks::AccountInfoTrait; -use crate::error::LightPdaError; +use crate::error::LightSdkTypesError; #[cfg(not(target_os = "solana"))] use light_account_checks::AccountMetaTrait; @@ -15,12 +15,12 @@ pub trait Pack { fn pack( &self, - remaining_accounts: &mut crate::instruction::PackedAccounts, - ) -> Result; + remaining_accounts: &mut crate::interface::instruction::PackedAccounts, + ) -> Result; } pub trait Unpack { type Unpacked; - fn unpack(&self, remaining_accounts: &[AI]) -> Result; + fn unpack(&self, remaining_accounts: &[AI]) -> Result; } diff --git a/sdk-libs/sdk-interface/src/account/pda_seeds.rs b/sdk-libs/sdk-types/src/interface/account/pda_seeds.rs similarity index 83% rename from sdk-libs/sdk-interface/src/account/pda_seeds.rs rename to sdk-libs/sdk-types/src/interface/account/pda_seeds.rs index a22d85a78c..83b3b5b54f 100644 --- a/sdk-libs/sdk-interface/src/account/pda_seeds.rs +++ b/sdk-libs/sdk-types/src/interface/account/pda_seeds.rs @@ -1,6 +1,6 @@ //! PDA seed derivation traits. -use crate::error::LightPdaError; +use crate::error::LightSdkTypesError; /// Trait for account variants that can be checked for token or PDA type. pub trait HasTokenVariant { @@ -15,5 +15,5 @@ pub trait PdaSeedDerivation { program_id: &[u8; 32], accounts: &A, seed_params: &S, - ) -> Result<(Vec>, [u8; 32]), LightPdaError>; + ) -> Result<(Vec>, [u8; 32]), LightSdkTypesError>; } diff --git a/sdk-libs/sdk-interface/src/account/token_seeds.rs b/sdk-libs/sdk-types/src/interface/account/token_seeds.rs similarity index 91% rename from sdk-libs/sdk-interface/src/account/token_seeds.rs rename to sdk-libs/sdk-types/src/interface/account/token_seeds.rs index 8b048cfa32..14744fea67 100644 --- a/sdk-libs/sdk-interface/src/account/token_seeds.rs +++ b/sdk-libs/sdk-types/src/interface/account/token_seeds.rs @@ -3,8 +3,8 @@ //! Provides `TokenDataWithSeeds`, `PackedTokenData`, and `TokenDataWithPackedSeeds` //! along with Pack/Unpack impls and blanket impls for variant traits. +use crate::instruction::PackedStateTreeInfo; use light_compressed_account::compressed_account::PackedMerkleContext; -use light_sdk_types::instruction::PackedStateTreeInfo; pub use light_token_interface::{ instructions::{ extensions::{CompressedOnlyExtensionInstructionData, ExtensionInstructionData}, @@ -19,19 +19,18 @@ pub use light_token_interface::{ use light_account_checks::AccountInfoTrait; use super::pack::Unpack; -#[cfg(not(target_os = "solana"))] -use light_account_checks::AccountMetaTrait; -#[cfg(not(target_os = "solana"))] -use crate::{account::pack::Pack, instruction::PackedAccounts}; -use crate::{ +use crate::interface::{ account::light_account::AccountType, - error::LightPdaError, program::variant::{ LightAccountVariantTrait, PackedLightAccountVariantTrait, PackedTokenSeeds, UnpackedTokenSeeds, }, - AnchorDeserialize, AnchorSerialize, }; +#[cfg(not(target_os = "solana"))] +use crate::interface::{account::pack::Pack, instruction::PackedAccounts}; +use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; +#[cfg(not(target_os = "solana"))] +use light_account_checks::AccountMetaTrait; #[derive(Clone, Debug, AnchorSerialize, AnchorDeserialize)] pub struct TokenDataWithSeeds { @@ -67,19 +66,19 @@ fn unpack_token_data_from_packed( packed: &PackedTokenData, extension: &Option, accounts: &[AI], -) -> Result { +) -> Result { let owner_key = accounts .get(packed.owner as usize) - .ok_or(LightPdaError::InvalidInstructionData)? + .ok_or(LightSdkTypesError::InvalidInstructionData)? .key(); let mint_key = accounts .get(packed.mint as usize) - .ok_or(LightPdaError::InvalidInstructionData)? + .ok_or(LightSdkTypesError::InvalidInstructionData)? .key(); let delegate = if packed.has_delegate { let delegate_key = accounts .get(packed.delegate as usize) - .ok_or(LightPdaError::InvalidInstructionData)? + .ok_or(LightSdkTypesError::InvalidInstructionData)? .key(); Some(light_compressed_account::Pubkey::from(delegate_key)) } else { @@ -133,7 +132,7 @@ where fn pack( &self, remaining_accounts: &mut PackedAccounts, - ) -> Result { + ) -> Result { let seeds = self.seeds.pack(remaining_accounts)?; let owner_index = remaining_accounts @@ -146,9 +145,7 @@ where delegate: self .token_data .delegate - .map(|d| { - remaining_accounts.insert_or_get(AM::pubkey_from_bytes(d.to_bytes())) - }) + .map(|d| remaining_accounts.insert_or_get(AM::pubkey_from_bytes(d.to_bytes()))) .unwrap_or(0), mint: remaining_accounts .insert_or_get(AM::pubkey_from_bytes(self.token_data.mint.to_bytes())), @@ -191,7 +188,7 @@ where { type Unpacked = TokenDataWithSeeds<>::Unpacked>; - fn unpack(&self, remaining_accounts: &[AI]) -> Result { + fn unpack(&self, remaining_accounts: &[AI]) -> Result { let seeds = self.seeds.unpack(remaining_accounts)?; let token_data = unpack_token_data_from_packed(&self.token_data, &self.extension, remaining_accounts)?; @@ -244,7 +241,7 @@ where fn unpack( &self, accounts: &[AI], - ) -> Result { + ) -> Result { let seeds = self.seeds.unpack_seeds::(accounts)?; let token_data = unpack_token_data_from_packed(&self.token_data, &self.extension, accounts)?; @@ -255,7 +252,7 @@ where &'a self, accounts: &'a [AI], bump_storage: &'a [u8; 1], - ) -> Result<[&'a [u8]; N], LightPdaError> { + ) -> Result<[&'a [u8]; N], LightSdkTypesError> { self.seeds.seed_refs_with_bump(accounts, bump_storage) } @@ -263,7 +260,7 @@ where &self, tree_info: &PackedStateTreeInfo, output_queue_index: u8, - ) -> Result { + ) -> Result { Ok(MultiInputTokenDataWithContext { amount: self.token_data.amount, mint: self.token_data.mint, @@ -281,7 +278,7 @@ where }) } - fn into_in_tlv(&self) -> Result>, LightPdaError> { + fn into_in_tlv(&self) -> Result>, LightSdkTypesError> { Ok(self .extension .as_ref() diff --git a/sdk-libs/sdk-interface/src/accounts/finalize.rs b/sdk-libs/sdk-types/src/interface/accounts/finalize.rs similarity index 94% rename from sdk-libs/sdk-interface/src/accounts/finalize.rs rename to sdk-libs/sdk-types/src/interface/accounts/finalize.rs index a795da87d1..82a3ab335d 100644 --- a/sdk-libs/sdk-interface/src/accounts/finalize.rs +++ b/sdk-libs/sdk-types/src/interface/accounts/finalize.rs @@ -29,7 +29,7 @@ pub trait LightPreInit { &mut self, remaining_accounts: &[AI], params: &P, - ) -> Result; + ) -> Result; } /// Trait for finalizing compression operations on accounts. @@ -44,5 +44,5 @@ pub trait LightFinalize { remaining_accounts: &[AI], params: &P, has_pre_init: bool, - ) -> Result<(), crate::error::LightPdaError>; + ) -> Result<(), crate::error::LightSdkTypesError>; } diff --git a/sdk-libs/sdk-interface/src/accounts/init_compressed_account.rs b/sdk-libs/sdk-types/src/interface/accounts/init_compressed_account.rs similarity index 92% rename from sdk-libs/sdk-interface/src/accounts/init_compressed_account.rs rename to sdk-libs/sdk-types/src/interface/accounts/init_compressed_account.rs index a1428df368..54094059d5 100644 --- a/sdk-libs/sdk-interface/src/accounts/init_compressed_account.rs +++ b/sdk-libs/sdk-types/src/interface/accounts/init_compressed_account.rs @@ -1,5 +1,6 @@ //! Helper functions for preparing compressed accounts on init. +use crate::instruction::PackedAddressTreeInfo; use light_account_checks::AccountInfoTrait; use light_compressed_account::{ address::derive_address, @@ -10,9 +11,8 @@ use light_compressed_account::{ }; use light_compressible::DECOMPRESSED_PDA_DISCRIMINATOR; use light_hasher::{errors::HasherError, sha256::Sha256BE, Hasher}; -use light_sdk_types::instruction::PackedAddressTreeInfo; -use crate::error::LightPdaError; +use crate::error::LightSdkTypesError; /// Prepare a compressed account for a PDA during initialization. /// @@ -82,18 +82,18 @@ pub fn reimburse_rent( fee_payer: &AI, rent_sponsor: &AI, _program_id: &[u8; 32], -) -> Result<(), LightPdaError> { +) -> Result<(), LightSdkTypesError> { let mut total_rent: u64 = 0; for account in created_accounts { total_rent = total_rent .checked_add(account.lamports()) - .ok_or(LightPdaError::ConstraintViolation)?; + .ok_or(LightSdkTypesError::ConstraintViolation)?; } if total_rent > 0 { rent_sponsor .transfer_lamports(fee_payer, total_rent) - .map_err(LightPdaError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountCheck)?; } Ok(()) diff --git a/sdk-libs/sdk-interface/src/accounts/mod.rs b/sdk-libs/sdk-types/src/interface/accounts/mod.rs similarity index 100% rename from sdk-libs/sdk-interface/src/accounts/mod.rs rename to sdk-libs/sdk-types/src/interface/accounts/mod.rs diff --git a/sdk-libs/sdk-interface/src/cpi/account.rs b/sdk-libs/sdk-types/src/interface/cpi/account.rs similarity index 87% rename from sdk-libs/sdk-interface/src/cpi/account.rs rename to sdk-libs/sdk-types/src/interface/cpi/account.rs index a356fcd640..92257a184f 100644 --- a/sdk-libs/sdk-interface/src/cpi/account.rs +++ b/sdk-libs/sdk-types/src/interface/cpi/account.rs @@ -1,17 +1,17 @@ //! Generic CPI accounts trait and implementations. +use crate::cpi_accounts::v2::{CompressionCpiAccountIndex, CpiAccounts, PROGRAM_ACCOUNTS_LEN}; +use crate::cpi_context_write::CpiContextWriteAccounts; use light_account_checks::{AccountInfoTrait, CpiMeta}; -use light_sdk_types::cpi_accounts::v2::{CompressionCpiAccountIndex, CpiAccounts, PROGRAM_ACCOUNTS_LEN}; -use light_sdk_types::cpi_context_write::CpiContextWriteAccounts; -use crate::error::LightPdaError; +use crate::error::LightSdkTypesError; /// Trait for types that can provide account infos and metas for Light system program CPI. /// /// Generic over `AI: AccountInfoTrait` to work with both solana and pinocchio backends. pub trait CpiAccountsTrait { fn to_account_infos(&self) -> Vec; - fn to_account_metas(&self) -> Result, LightPdaError>; + fn to_account_metas(&self) -> Result, LightSdkTypesError>; fn get_mode(&self) -> Option; } @@ -21,7 +21,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CpiAccountsTrait for CpiAccounts<'a, CpiAccounts::to_account_infos(self) } - fn to_account_metas(&self) -> Result, LightPdaError> { + fn to_account_metas(&self) -> Result, LightSdkTypesError> { to_cpi_metas(self) } @@ -36,7 +36,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CpiAccountsTrait for CpiContextWriteA self.to_account_infos().to_vec() } - fn to_account_metas(&self) -> Result, LightPdaError> { + fn to_account_metas(&self) -> Result, LightSdkTypesError> { let infos = self.to_account_info_refs(); Ok(vec![ CpiMeta { @@ -66,7 +66,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CpiAccountsTrait for CpiContextWriteA /// expected by the Light system program. fn to_cpi_metas( cpi_accounts: &CpiAccounts<'_, AI>, -) -> Result, LightPdaError> { +) -> Result, LightSdkTypesError> { let mut metas = Vec::with_capacity(1 + cpi_accounts.account_infos().len() - PROGRAM_ACCOUNTS_LEN); @@ -135,10 +135,9 @@ fn to_cpi_metas( } assert_eq!(cpi_accounts.system_accounts_end_offset(), index); - let tree_accounts = - accounts - .get(index..) - .ok_or(LightPdaError::CpiAccountsIndexOutOfBounds(index))?; + let tree_accounts = accounts + .get(index..) + .ok_or(LightSdkTypesError::CpiAccountsIndexOutOfBounds(index))?; tree_accounts.iter().for_each(|acc| { metas.push(CpiMeta { pubkey: acc.key(), diff --git a/sdk-libs/sdk-interface/src/cpi/impls.rs b/sdk-libs/sdk-types/src/interface/cpi/impls.rs similarity index 100% rename from sdk-libs/sdk-interface/src/cpi/impls.rs rename to sdk-libs/sdk-types/src/interface/cpi/impls.rs diff --git a/sdk-libs/sdk-interface/src/cpi/instruction.rs b/sdk-libs/sdk-types/src/interface/cpi/instruction.rs similarity index 96% rename from sdk-libs/sdk-interface/src/cpi/instruction.rs rename to sdk-libs/sdk-types/src/interface/cpi/instruction.rs index a49cd44acf..7b1d9d28e8 100644 --- a/sdk-libs/sdk-interface/src/cpi/instruction.rs +++ b/sdk-libs/sdk-types/src/interface/cpi/instruction.rs @@ -13,7 +13,7 @@ pub trait LightCpi: Sized { /// # Arguments /// * `cpi_signer` - The CPI signer containing program ID and bump seed /// * `proof` - Validity proof for compressed account operations - fn new_cpi(cpi_signer: crate::cpi::CpiSigner, proof: ValidityProof) -> Self; + fn new_cpi(cpi_signer: crate::CpiSigner, proof: ValidityProof) -> Self; /// Returns the instruction mode (0 for v1, 1 for v2). fn get_mode(&self) -> u8; diff --git a/sdk-libs/sdk-interface/src/cpi/invoke.rs b/sdk-libs/sdk-types/src/interface/cpi/invoke.rs similarity index 79% rename from sdk-libs/sdk-interface/src/cpi/invoke.rs rename to sdk-libs/sdk-types/src/interface/cpi/invoke.rs index 1c270ea7d5..b9b91c7a22 100644 --- a/sdk-libs/sdk-interface/src/cpi/invoke.rs +++ b/sdk-libs/sdk-types/src/interface/cpi/invoke.rs @@ -1,13 +1,11 @@ //! Generic Light system program invocation. -pub use light_compressed_account::LightInstructionData; +use crate::constants::{CPI_AUTHORITY_PDA_SEED, LIGHT_SYSTEM_PROGRAM_ID}; use light_account_checks::{AccountInfoTrait, CpiMeta}; -use light_sdk_types::constants::{CPI_AUTHORITY_PDA_SEED, LIGHT_SYSTEM_PROGRAM_ID}; +pub use light_compressed_account::LightInstructionData; -use crate::{ - cpi::{account::CpiAccountsTrait, instruction::LightCpi}, - error::LightPdaError, -}; +use crate::error::LightSdkTypesError; +use crate::interface::cpi::{account::CpiAccountsTrait, instruction::LightCpi}; /// Trait for invoking the Light system program via CPI. /// @@ -19,19 +17,19 @@ pub trait InvokeLightSystemProgram { fn invoke( self, accounts: impl CpiAccountsTrait, - ) -> Result<(), LightPdaError>; + ) -> Result<(), LightSdkTypesError>; fn invoke_write_to_cpi_context_first( self, accounts: impl CpiAccountsTrait, - ) -> Result<(), LightPdaError>; + ) -> Result<(), LightSdkTypesError>; fn invoke_write_to_cpi_context_set( self, accounts: impl CpiAccountsTrait, - ) -> Result<(), LightPdaError>; + ) -> Result<(), LightSdkTypesError>; fn invoke_execute_cpi_context( self, accounts: impl CpiAccountsTrait, - ) -> Result<(), LightPdaError>; + ) -> Result<(), LightSdkTypesError>; } impl InvokeLightSystemProgram for T @@ -41,7 +39,7 @@ where fn invoke( self, accounts: impl CpiAccountsTrait, - ) -> Result<(), LightPdaError> { + ) -> Result<(), LightSdkTypesError> { // Check if CPI context operations are being attempted { use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; @@ -49,18 +47,18 @@ where || *self.get_cpi_context() == CompressedCpiContext::set() || *self.get_cpi_context() == CompressedCpiContext::first() { - return Err(LightPdaError::InvalidInstructionData); + return Err(LightSdkTypesError::InvalidInstructionData); } } // Validate mode consistency if let Some(account_mode) = accounts.get_mode() { if account_mode != self.get_mode() { - return Err(LightPdaError::InvalidInstructionData); + return Err(LightSdkTypesError::InvalidInstructionData); } } - let data = self.data().map_err(LightPdaError::from)?; + let data = self.data().map_err(LightSdkTypesError::from)?; let account_infos = accounts.to_account_infos(); let account_metas = accounts.to_account_metas()?; @@ -70,7 +68,7 @@ where fn invoke_write_to_cpi_context_first( self, accounts: impl CpiAccountsTrait, - ) -> Result<(), LightPdaError> { + ) -> Result<(), LightSdkTypesError> { let instruction_data = self.write_to_cpi_context_first(); inner_invoke_write_to_cpi_context_typed(instruction_data, accounts) } @@ -78,7 +76,7 @@ where fn invoke_write_to_cpi_context_set( self, accounts: impl CpiAccountsTrait, - ) -> Result<(), LightPdaError> { + ) -> Result<(), LightSdkTypesError> { let instruction_data = self.write_to_cpi_context_set(); inner_invoke_write_to_cpi_context_typed(instruction_data, accounts) } @@ -86,10 +84,10 @@ where fn invoke_execute_cpi_context( self, accounts: impl CpiAccountsTrait, - ) -> Result<(), LightPdaError> { + ) -> Result<(), LightSdkTypesError> { let instruction_data = self.execute_with_cpi_context(); - let data = instruction_data.data().map_err(LightPdaError::from)?; + let data = instruction_data.data().map_err(LightSdkTypesError::from)?; let account_infos = accounts.to_account_infos(); let account_metas = accounts.to_account_metas()?; @@ -106,20 +104,20 @@ where fn inner_invoke_write_to_cpi_context_typed( instruction_data: T, accounts: impl CpiAccountsTrait, -) -> Result<(), LightPdaError> +) -> Result<(), LightSdkTypesError> where AI: AccountInfoTrait + Clone, T: LightInstructionData + LightCpi, { if instruction_data.has_read_only_accounts() { - return Err(LightPdaError::ReadOnlyAccountsNotSupportedInCpiContext); + return Err(LightSdkTypesError::ReadOnlyAccountsNotSupportedInCpiContext); } - let data = instruction_data.data().map_err(LightPdaError::from)?; + let data = instruction_data.data().map_err(LightSdkTypesError::from)?; let account_infos = accounts.to_account_infos(); if account_infos.len() < 3 { - return Err(LightPdaError::NotEnoughAccountKeys); + return Err(LightSdkTypesError::NotEnoughAccountKeys); } let account_metas = vec![ @@ -157,7 +155,7 @@ pub fn invoke_light_system_program( account_metas: &[CpiMeta], data: &[u8], bump: u8, -) -> Result<(), LightPdaError> { +) -> Result<(), LightSdkTypesError> { let signer_seeds: &[&[u8]] = &[CPI_AUTHORITY_PDA_SEED, &[bump]]; AI::invoke_cpi( &LIGHT_SYSTEM_PROGRAM_ID, @@ -166,5 +164,5 @@ pub fn invoke_light_system_program( account_infos, &[signer_seeds], ) - .map_err(|_| LightPdaError::CpiFailed) + .map_err(|_| LightSdkTypesError::CpiFailed) } diff --git a/sdk-libs/sdk-interface/src/cpi/mod.rs b/sdk-libs/sdk-types/src/interface/cpi/mod.rs similarity index 88% rename from sdk-libs/sdk-interface/src/cpi/mod.rs rename to sdk-libs/sdk-types/src/interface/cpi/mod.rs index 2377bd8480..6bbda4fba7 100644 --- a/sdk-libs/sdk-interface/src/cpi/mod.rs +++ b/sdk-libs/sdk-types/src/interface/cpi/mod.rs @@ -12,5 +12,5 @@ pub use account::CpiAccountsTrait; pub use instruction::LightCpi; pub use invoke::{invoke_light_system_program, InvokeLightSystemProgram}; pub use light_compressed_account::instruction_data::traits::LightInstructionData; -pub use light_sdk_types::{cpi_accounts::CpiAccountsConfig, CpiSigner}; +pub use crate::{cpi_accounts::CpiAccountsConfig, CpiSigner}; // TODO: move all of this to light-sdk-types diff --git a/sdk-libs/sdk-types/src/interface/create_accounts_proof.rs b/sdk-libs/sdk-types/src/interface/create_accounts_proof.rs new file mode 100644 index 0000000000..2bbbb1959f --- /dev/null +++ b/sdk-libs/sdk-types/src/interface/create_accounts_proof.rs @@ -0,0 +1,22 @@ +use crate::{AnchorDeserialize, AnchorSerialize}; +use light_compressed_account::instruction_data::{ + compressed_proof::ValidityProof, data::PackedAddressTreeInfo, +}; + +/// Proof data for instruction params when creating new compressed accounts. +/// Used in the INIT flow - pass directly to instruction data. +/// All accounts use the same address tree, so only one `address_tree_info` is needed. +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] +pub struct CreateAccountsProof { + /// The validity proof. + pub proof: ValidityProof, + /// Single packed address tree info (all accounts use same tree). + pub address_tree_info: PackedAddressTreeInfo, + /// Output state tree index for new compressed accounts. + pub output_state_tree_index: u8, + /// State merkle tree index (needed for mint creation decompress validation). + /// This is optional to maintain backwards compatibility. + pub state_tree_index: Option, + /// Offset in remaining_accounts where Light system accounts start. + pub system_accounts_offset: u8, +} diff --git a/sdk-libs/sdk-interface/src/instruction/mod.rs b/sdk-libs/sdk-types/src/interface/instruction/mod.rs similarity index 86% rename from sdk-libs/sdk-interface/src/instruction/mod.rs rename to sdk-libs/sdk-types/src/interface/instruction/mod.rs index 49b45eefaa..e3619684e4 100644 --- a/sdk-libs/sdk-interface/src/instruction/mod.rs +++ b/sdk-libs/sdk-types/src/interface/instruction/mod.rs @@ -5,6 +5,6 @@ mod pack_accounts; /// Re-exports from light-sdk-types instruction types. -pub use light_sdk_types::instruction::*; +pub use crate::instruction::*; pub use pack_accounts::*; // TODO: move all of this to light-sdk-types diff --git a/sdk-libs/sdk-interface/src/instruction/pack_accounts.rs b/sdk-libs/sdk-types/src/interface/instruction/pack_accounts.rs similarity index 96% rename from sdk-libs/sdk-interface/src/instruction/pack_accounts.rs rename to sdk-libs/sdk-types/src/interface/instruction/pack_accounts.rs index 9ddd5ced93..03faedff85 100644 --- a/sdk-libs/sdk-interface/src/instruction/pack_accounts.rs +++ b/sdk-libs/sdk-types/src/interface/instruction/pack_accounts.rs @@ -10,7 +10,7 @@ use std::collections::HashMap; use light_account_checks::AccountMetaTrait; -use crate::error::LightPdaError; +use crate::error::LightSdkTypesError; /// Builder to collect accounts for compressed account instructions. /// @@ -165,11 +165,14 @@ impl PackedAccounts { pub fn add_custom_system_accounts>( &mut self, accounts: T, - ) -> Result<(), LightPdaError> { + ) -> Result<(), LightSdkTypesError> { accounts.get_account_metas_vec(self) } } pub trait AccountMetasVec { - fn get_account_metas_vec(&self, accounts: &mut PackedAccounts) -> Result<(), LightPdaError>; + fn get_account_metas_vec( + &self, + accounts: &mut PackedAccounts, + ) -> Result<(), LightSdkTypesError>; } diff --git a/sdk-libs/sdk-interface/src/lib.rs b/sdk-libs/sdk-types/src/interface/mod.rs similarity index 87% rename from sdk-libs/sdk-interface/src/lib.rs rename to sdk-libs/sdk-types/src/interface/mod.rs index f063e5b76d..42da86b381 100644 --- a/sdk-libs/sdk-interface/src/lib.rs +++ b/sdk-libs/sdk-types/src/interface/mod.rs @@ -1,12 +1,8 @@ //! Framework-agnostic interface for Light Protocol compressible accounts. -#![cfg_attr(not(feature = "std"), no_std)] - -pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; -// TODO: delete backup -pub mod error; pub mod account; pub mod accounts; +pub mod create_accounts_proof; pub mod program; // LightCpi trait + CPI builder (no runtime dep) @@ -16,11 +12,11 @@ pub mod cpi; #[cfg(not(target_os = "solana"))] pub mod instruction; -// --- Re-exports from light-account-checks --- -pub use light_account_checks::{self, discriminator::Discriminator as LightDiscriminator}; +// --- Re-exports from light-compressible --- +pub use light_compressible::rent; -// --- Re-exports from external crates --- -pub use light_compressible::{rent, CreateAccountsProof}; +// --- Re-exports --- +pub use create_accounts_proof::CreateAccountsProof; // ============================================================================= // FLAT RE-EXPORTS diff --git a/sdk-libs/sdk-interface/src/program/compression/close.rs b/sdk-libs/sdk-types/src/interface/program/compression/close.rs similarity index 62% rename from sdk-libs/sdk-interface/src/program/compression/close.rs rename to sdk-libs/sdk-types/src/interface/program/compression/close.rs index e46ff3b5c0..c50abd5ee1 100644 --- a/sdk-libs/sdk-interface/src/program/compression/close.rs +++ b/sdk-libs/sdk-types/src/interface/program/compression/close.rs @@ -1,8 +1,9 @@ use light_account_checks::AccountInfoTrait; -use crate::error::{LightPdaError, Result}; +use crate::error::{LightSdkTypesError, Result}; // TODO: remove and use directly from light-account-checks /// Close a native Solana account by transferring lamports and clearing data. pub fn close(info: &AI, sol_destination: &AI) -> Result<()> { - light_account_checks::close_account(info, sol_destination).map_err(LightPdaError::AccountCheck) + light_account_checks::close_account(info, sol_destination) + .map_err(LightSdkTypesError::AccountCheck) } diff --git a/sdk-libs/sdk-interface/src/program/compression/mod.rs b/sdk-libs/sdk-types/src/interface/program/compression/mod.rs similarity index 100% rename from sdk-libs/sdk-interface/src/program/compression/mod.rs rename to sdk-libs/sdk-types/src/interface/program/compression/mod.rs diff --git a/sdk-libs/sdk-interface/src/program/compression/pda.rs b/sdk-libs/sdk-types/src/interface/program/compression/pda.rs similarity index 85% rename from sdk-libs/sdk-interface/src/program/compression/pda.rs rename to sdk-libs/sdk-types/src/interface/program/compression/pda.rs index 052a52649e..4deabd2865 100644 --- a/sdk-libs/sdk-interface/src/program/compression/pda.rs +++ b/sdk-libs/sdk-types/src/interface/program/compression/pda.rs @@ -3,6 +3,7 @@ //! These functions are generic over account types and can be reused by the macro. //! The compress flow uses a dispatch callback pattern (same as decompress). +use crate::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress; use light_account_checks::AccountInfoTrait; use light_compressed_account::{ address::derive_address, @@ -11,16 +12,13 @@ use light_compressed_account::{ }; use light_compressible::{rent::AccountRentState, DECOMPRESSED_PDA_DISCRIMINATOR}; use light_hasher::{sha256::Sha256BE, Hasher, Sha256}; -use light_sdk_types::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress; -use light_sdk_types::instruction::account_meta::{CompressedAccountMeta, CompressedAccountMetaTrait}; +use crate::instruction::account_meta::{CompressedAccountMeta, CompressedAccountMetaTrait}; -use crate::{ - account::compression_info::HasCompressionInfo, - error::LightPdaError, - program::compression::processor::CompressCtx, - LightDiscriminator, +use crate::interface::{ + account::compression_info::HasCompressionInfo, program::compression::processor::CompressCtx, }; +use crate::{error::LightSdkTypesError, LightDiscriminator}; /// Generic prepare_account_for_compression. /// @@ -43,7 +41,7 @@ pub fn prepare_account_for_compression( compressed_account_meta: &CompressedAccountMetaNoLamportsNoAddress, pda_index: usize, ctx: &mut CompressCtx<'_, AI>, -) -> Result<(), LightPdaError> +) -> Result<(), LightSdkTypesError> where AI: AccountInfoTrait, A: HasCompressionInfo + LightDiscriminator + Clone + borsh::BorshSerialize, @@ -62,11 +60,11 @@ where output_state_tree_index: compressed_account_meta.output_state_tree_index, }; - let current_slot = AI::get_current_slot().map_err(LightPdaError::AccountCheck)?; + let current_slot = AI::get_current_slot().map_err(LightSdkTypesError::AccountCheck)?; let bytes = account_info.data_len() as u64; let current_lamports = account_info.lamports(); let rent_exemption_lamports = - AI::get_min_rent_balance(bytes as usize).map_err(LightPdaError::AccountCheck)?; + AI::get_min_rent_balance(bytes as usize).map_err(LightSdkTypesError::AccountCheck)?; let ci = account_data.compression_info()?; let last_claimed_slot = ci.last_claimed_slot(); @@ -95,30 +93,30 @@ where { let mut data = account_info .try_borrow_mut_data() - .map_err(LightPdaError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountCheck)?; // Write discriminator first data[..8].copy_from_slice(&A::LIGHT_DISCRIMINATOR); // Write serialized account data after discriminator let writer = &mut &mut data[8..]; account_data .serialize(writer) - .map_err(|_| LightPdaError::Borsh)?; + .map_err(|_| LightSdkTypesError::Borsh)?; } // Create compressed account with canonical compressed CompressionInfo for hashing let mut compressed_data = account_data.clone(); *compressed_data.compression_info_mut()? = - crate::account::compression_info::CompressionInfo::compressed(); + crate::interface::account::compression_info::CompressionInfo::compressed(); // Hash the data (discriminator NOT included per protocol convention) - let data_bytes = borsh::to_vec(&compressed_data).map_err(|_| LightPdaError::Borsh)?; - let mut output_data_hash = Sha256::hash(&data_bytes).map_err(LightPdaError::Hasher)?; + let data_bytes = borsh::to_vec(&compressed_data).map_err(|_| LightSdkTypesError::Borsh)?; + let mut output_data_hash = Sha256::hash(&data_bytes).map_err(LightSdkTypesError::Hasher)?; output_data_hash[0] = 0; // Zero first byte per protocol convention // Build input account info (placeholder compressed account from init) // The init created a placeholder with DECOMPRESSED_PDA_DISCRIMINATOR and PDA pubkey as data let tree_info = compressed_account_meta.tree_info; - let input_data_hash = Sha256BE::hash(&account_key).map_err(LightPdaError::Hasher)?; + let input_data_hash = Sha256BE::hash(&account_key).map_err(LightSdkTypesError::Hasher)?; let input_account_info = InAccountInfo { data_hash: input_data_hash, lamports: 0, diff --git a/sdk-libs/sdk-interface/src/program/compression/processor.rs b/sdk-libs/sdk-types/src/interface/program/compression/processor.rs similarity index 91% rename from sdk-libs/sdk-interface/src/program/compression/processor.rs rename to sdk-libs/sdk-types/src/interface/program/compression/processor.rs index 6204d9bdee..410e03f2ba 100644 --- a/sdk-libs/sdk-interface/src/program/compression/processor.rs +++ b/sdk-libs/sdk-types/src/interface/program/compression/processor.rs @@ -1,21 +1,20 @@ //! Compression instruction processor. +use crate::{ + cpi_accounts::v2::CpiAccounts, + instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, CpiSigner, +}; use light_account_checks::AccountInfoTrait; use light_compressed_account::instruction_data::{ compressed_proof::ValidityProof, with_account_info::{CompressedAccountInfo, InstructionDataInvokeCpiWithAccountInfo}, }; -use light_sdk_types::{ - cpi_accounts::v2::CpiAccounts, instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, - CpiSigner, -}; -use crate::{ +use crate::interface::{ cpi::InvokeLightSystemProgram, - error::LightPdaError, program::{compression::close::close, config::LightConfig}, - AnchorDeserialize, AnchorSerialize, }; +use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; /// Account indices within remaining_accounts for compress instructions. const FEE_PAYER_INDEX: usize = 0; @@ -57,7 +56,7 @@ pub type CompressDispatchFn = fn( compressed_account_meta: &CompressedAccountMetaNoLamportsNoAddress, index: usize, ctx: &mut CompressCtx<'_, AI>, -) -> Result<(), LightPdaError>; +) -> Result<(), LightSdkTypesError>; /// Process compress-and-close for PDA accounts (idempotent). /// @@ -74,16 +73,16 @@ pub fn process_compress_pda_accounts_idempotent( dispatch_fn: CompressDispatchFn, cpi_signer: CpiSigner, program_id: &[u8; 32], -) -> Result<(), LightPdaError> { +) -> Result<(), LightSdkTypesError> { // 1. Deserialize params let params = CompressAndCloseParams::try_from_slice(instruction_data) - .map_err(|_| LightPdaError::Borsh)?; + .map_err(|_| LightSdkTypesError::Borsh)?; let system_accounts_offset = params.system_accounts_offset as usize; let num_pdas = params.compressed_accounts.len(); if num_pdas == 0 { - return Err(LightPdaError::InvalidInstructionData); + return Err(LightSdkTypesError::InvalidInstructionData); } // 2. Load and validate config @@ -97,7 +96,7 @@ pub fn process_compress_pda_accounts_idempotent( let pda_start = remaining_accounts .len() .checked_sub(num_pdas) - .ok_or(LightPdaError::NotEnoughAccountKeys)?; + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)?; // 5. Run dispatch for each PDA let (compressed_account_infos, pda_indices_to_close, has_non_compressible) = { diff --git a/sdk-libs/sdk-interface/src/program/config/create.rs b/sdk-libs/sdk-types/src/interface/program/config/create.rs similarity index 84% rename from sdk-libs/sdk-interface/src/program/config/create.rs rename to sdk-libs/sdk-types/src/interface/program/config/create.rs index dd9a989895..e2da7bfc32 100644 --- a/sdk-libs/sdk-interface/src/program/config/create.rs +++ b/sdk-libs/sdk-types/src/interface/program/config/create.rs @@ -8,7 +8,7 @@ use light_account_checks::{ use light_compressible::rent::RentConfig; use super::{state::LightConfig, validate_address_space_no_duplicates, COMPRESSIBLE_CONFIG_SEED}; -use crate::{error::LightPdaError, AnchorSerialize}; +use crate::{error::LightSdkTypesError, AnchorSerialize}; /// BPFLoaderUpgradeab1e11111111111111111111111 as raw bytes. const BPF_LOADER_UPGRADEABLE_ID: [u8; 32] = [ @@ -42,44 +42,44 @@ pub fn process_initialize_light_config( payer: &AI, system_program: &AI, program_id: &[u8; 32], -) -> Result<(), LightPdaError> { +) -> Result<(), LightSdkTypesError> { // CHECK: config_bump must be 0 if config_bump != 0 { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } // CHECK: not already initialized if !config_account.data_is_empty() { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } // CHECK: exactly 1 address space if address_space.len() != 1 { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } // CHECK: unique pubkeys in address_space validate_address_space_no_duplicates(&address_space)?; // CHECK: signer - check_signer(update_authority).map_err(LightPdaError::AccountCheck)?; + check_signer(update_authority).map_err(LightSdkTypesError::AccountCheck)?; // CHECK: PDA derivation let (derived_pda, bump) = LightConfig::derive_pda_bytes::(program_id, config_bump); if derived_pda != config_account.key() { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } // Derive rent_sponsor_bump for storage let (derived_rent_sponsor, rent_sponsor_bump) = LightConfig::derive_rent_sponsor_pda_bytes::(program_id); if *rent_sponsor != derived_rent_sponsor { - return Err(LightPdaError::InvalidRentSponsor); + return Err(LightSdkTypesError::InvalidRentSponsor); } let account_size = LightConfig::size_for_address_space(address_space.len()); let rent_lamports = - AI::get_min_rent_balance(account_size).map_err(LightPdaError::AccountCheck)?; + AI::get_min_rent_balance(account_size).map_err(LightSdkTypesError::AccountCheck)?; // Create PDA using AccountInfoTrait let config_bump_bytes = (config_bump as u16).to_le_bytes(); @@ -114,7 +114,7 @@ pub fn process_initialize_light_config( let mut data = config_account .try_borrow_mut_data() - .map_err(LightPdaError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountCheck)?; // Write discriminator first data[..DISCRIMINATOR_LEN].copy_from_slice(&LightConfig::LIGHT_DISCRIMINATOR); @@ -122,7 +122,7 @@ pub fn process_initialize_light_config( // Serialize config data after discriminator config .serialize(&mut &mut data[DISCRIMINATOR_LEN..]) - .map_err(|_| LightPdaError::Borsh)?; + .map_err(|_| LightSdkTypesError::Borsh)?; Ok(()) } @@ -135,26 +135,26 @@ pub fn check_program_upgrade_authority( program_id: &[u8; 32], program_data_account: &AI, authority: &AI, -) -> Result<(), LightPdaError> { +) -> Result<(), LightSdkTypesError> { // CHECK: program data PDA let (expected_program_data, _) = AI::find_program_address(&[program_id], &BPF_LOADER_UPGRADEABLE_ID); if program_data_account.key() != expected_program_data { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } let data = program_data_account .try_borrow_data() - .map_err(LightPdaError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountCheck)?; if data.len() < PROGRAM_DATA_MIN_LEN { - return Err(LightPdaError::AccountDataTooSmall); + return Err(LightSdkTypesError::AccountDataTooSmall); } // Parse variant tag (4 bytes, u32 LE) let variant_tag = u32::from_le_bytes(data[0..4].try_into().unwrap()); if variant_tag != PROGRAM_DATA_VARIANT_TAG { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } // Parse Option at offset 12 @@ -162,28 +162,28 @@ pub fn check_program_upgrade_authority( let upgrade_authority: [u8; 32] = match option_discriminant { 0 => { // None - program has no upgrade authority - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } 1 => { let mut auth = [0u8; 32]; auth.copy_from_slice(&data[13..45]); // Check for invalid zero authority if auth == [0u8; 32] { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } auth } _ => { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } }; // CHECK: authority is signer - check_signer(authority).map_err(LightPdaError::AccountCheck)?; + check_signer(authority).map_err(LightSdkTypesError::AccountCheck)?; // CHECK: authority matches upgrade authority if authority.key() != upgrade_authority { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } Ok(()) @@ -204,7 +204,7 @@ pub fn process_initialize_light_config_checked( payer: &AI, system_program: &AI, program_id: &[u8; 32], -) -> Result<(), LightPdaError> { +) -> Result<(), LightSdkTypesError> { check_program_upgrade_authority::(program_id, program_data_account, update_authority)?; process_initialize_light_config( diff --git a/sdk-libs/sdk-interface/src/program/config/mod.rs b/sdk-libs/sdk-types/src/interface/program/config/mod.rs similarity index 87% rename from sdk-libs/sdk-interface/src/program/config/mod.rs rename to sdk-libs/sdk-types/src/interface/program/config/mod.rs index 4df3e108f7..a07cd09d6b 100644 --- a/sdk-libs/sdk-interface/src/program/config/mod.rs +++ b/sdk-libs/sdk-types/src/interface/program/config/mod.rs @@ -3,10 +3,7 @@ use light_account_checks::AccountInfoTrait; use light_compressible::rent::RentConfig; -use crate::{ - error::LightPdaError, - AnchorDeserialize, AnchorSerialize, -}; +use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; pub mod create; mod state; @@ -19,8 +16,8 @@ pub const MAX_ADDRESS_TREES_PER_SPACE: usize = 1; // --- Re-exports --- // Re-export Discriminator trait so users can access LightConfig::LIGHT_DISCRIMINATOR +pub use crate::constants::RENT_SPONSOR_SEED; pub use light_account_checks::discriminator::Discriminator; -pub use light_sdk_types::constants::RENT_SPONSOR_SEED; pub use state::LightConfig; // ============================================================================= @@ -66,13 +63,13 @@ pub fn process_initialize_light_config_checked( remaining_accounts: &[AI], instruction_data: &[u8], program_id: &[u8; 32], -) -> Result<(), LightPdaError> { +) -> Result<(), LightSdkTypesError> { if remaining_accounts.len() < 5 { - return Err(LightPdaError::NotEnoughAccountKeys); + return Err(LightSdkTypesError::NotEnoughAccountKeys); } let params = InitializeLightConfigParams::try_from_slice(instruction_data) - .map_err(|_| LightPdaError::Borsh)?; + .map_err(|_| LightSdkTypesError::Borsh)?; create::process_initialize_light_config_checked( &remaining_accounts[1], // config_account @@ -99,13 +96,13 @@ pub fn process_update_light_config( remaining_accounts: &[AI], instruction_data: &[u8], program_id: &[u8; 32], -) -> Result<(), LightPdaError> { +) -> Result<(), LightSdkTypesError> { if remaining_accounts.len() < 2 { - return Err(LightPdaError::NotEnoughAccountKeys); + return Err(LightSdkTypesError::NotEnoughAccountKeys); } let params = UpdateLightConfigParams::try_from_slice(instruction_data) - .map_err(|_| LightPdaError::Borsh)?; + .map_err(|_| LightSdkTypesError::Borsh)?; update::process_update_light_config( &remaining_accounts[0], // config_account @@ -125,12 +122,12 @@ pub fn process_update_light_config( /// Validates that address_space contains no duplicate pubkeys pub(super) fn validate_address_space_no_duplicates( address_space: &[[u8; 32]], -) -> Result<(), LightPdaError> { +) -> Result<(), LightSdkTypesError> { use std::collections::HashSet; let mut seen = HashSet::new(); for pubkey in address_space { if !seen.insert(pubkey) { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } } Ok(()) @@ -140,10 +137,10 @@ pub(super) fn validate_address_space_no_duplicates( pub(super) fn validate_address_space_only_adds( existing_address_space: &[[u8; 32]], new_address_space: &[[u8; 32]], -) -> Result<(), LightPdaError> { +) -> Result<(), LightSdkTypesError> { for existing_pubkey in existing_address_space { if !new_address_space.contains(existing_pubkey) { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } } Ok(()) diff --git a/sdk-libs/sdk-interface/src/program/config/state.rs b/sdk-libs/sdk-types/src/interface/program/config/state.rs similarity index 83% rename from sdk-libs/sdk-interface/src/program/config/state.rs rename to sdk-libs/sdk-types/src/interface/program/config/state.rs index 178dc59c21..a7155fd8dd 100644 --- a/sdk-libs/sdk-interface/src/program/config/state.rs +++ b/sdk-libs/sdk-types/src/interface/program/config/state.rs @@ -8,7 +8,7 @@ use light_account_checks::{ use light_compressible::rent::RentConfig; use super::{COMPRESSIBLE_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE}; -use crate::{error::LightPdaError, AnchorDeserialize, AnchorSerialize}; +use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; /// Global configuration for compressible accounts #[derive(Clone, AnchorDeserialize, AnchorSerialize, Debug)] @@ -97,24 +97,24 @@ impl LightConfig { pub fn validate_rent_sponsor_account( &self, rent_sponsor: &AI, - ) -> Result { + ) -> Result { if rent_sponsor.key() != self.rent_sponsor { - return Err(LightPdaError::InvalidRentSponsor); + return Err(LightSdkTypesError::InvalidRentSponsor); } Ok(self.rent_sponsor_bump) } /// Checks the config account - pub fn validate(&self) -> Result<(), LightPdaError> { + pub fn validate(&self) -> Result<(), LightSdkTypesError> { if self.version != 1 { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } if self.address_space.len() != 1 { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } // For now, only allow config_bump = 0 to keep it simple if self.config_bump != 0 { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } Ok(()) } @@ -125,22 +125,22 @@ impl LightConfig { pub fn load_checked( account: &AI, program_id: &[u8; 32], - ) -> Result { + ) -> Result { // CHECK: Owner if !account.is_owned_by(program_id) { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } let data = account .try_borrow_data() - .map_err(|_| LightPdaError::ConstraintViolation)?; + .map_err(|_| LightSdkTypesError::ConstraintViolation)?; // CHECK: Discriminator using light-account-checks - check_discriminator::(&data).map_err(|_| LightPdaError::ConstraintViolation)?; + check_discriminator::(&data).map_err(|_| LightSdkTypesError::ConstraintViolation)?; // Deserialize from offset after discriminator - let config = - Self::try_from_slice(&data[DISCRIMINATOR_LEN..]).map_err(|_| LightPdaError::Borsh)?; + let config = Self::try_from_slice(&data[DISCRIMINATOR_LEN..]) + .map_err(|_| LightSdkTypesError::Borsh)?; config.validate()?; // CHECK: PDA derivation @@ -152,7 +152,7 @@ impl LightConfig { program_id, ); if expected_pda != account.key() { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } Ok(config) diff --git a/sdk-libs/sdk-interface/src/program/config/update.rs b/sdk-libs/sdk-types/src/interface/program/config/update.rs similarity index 80% rename from sdk-libs/sdk-interface/src/program/config/update.rs rename to sdk-libs/sdk-types/src/interface/program/config/update.rs index 7f1e41d4db..e0e382f1bb 100644 --- a/sdk-libs/sdk-interface/src/program/config/update.rs +++ b/sdk-libs/sdk-types/src/interface/program/config/update.rs @@ -1,13 +1,15 @@ //! Config update instruction (generic over AccountInfoTrait). -use light_account_checks::{checks::check_signer, discriminator::DISCRIMINATOR_LEN, AccountInfoTrait}; +use light_account_checks::{ + checks::check_signer, discriminator::DISCRIMINATOR_LEN, AccountInfoTrait, +}; use light_compressible::rent::RentConfig; use super::{ state::LightConfig, validate_address_space_no_duplicates, validate_address_space_only_adds, MAX_ADDRESS_TREES_PER_SPACE, }; -use crate::{error::LightPdaError, AnchorSerialize}; +use crate::{error::LightSdkTypesError, AnchorSerialize}; /// Updates an existing compressible config. #[allow(clippy::too_many_arguments)] @@ -21,16 +23,16 @@ pub fn process_update_light_config( new_write_top_up: Option, new_address_space: Option>, owner_program_id: &[u8; 32], -) -> Result<(), LightPdaError> { +) -> Result<(), LightSdkTypesError> { // CHECK: PDA derivation + discriminator + owner let mut config = LightConfig::load_checked(config_account, owner_program_id)?; // CHECK: signer - check_signer(authority).map_err(LightPdaError::AccountCheck)?; + check_signer(authority).map_err(LightSdkTypesError::AccountCheck)?; // CHECK: authority if authority.key() != config.update_authority { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } if let Some(new_authority) = new_update_authority { @@ -51,7 +53,7 @@ pub fn process_update_light_config( if let Some(new_address_space) = new_address_space { // CHECK: address space length if new_address_space.len() != MAX_ADDRESS_TREES_PER_SPACE { - return Err(LightPdaError::ConstraintViolation); + return Err(LightSdkTypesError::ConstraintViolation); } validate_address_space_no_duplicates(&new_address_space)?; @@ -62,11 +64,11 @@ pub fn process_update_light_config( let mut data = config_account .try_borrow_mut_data() - .map_err(LightPdaError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountCheck)?; // Serialize after discriminator (discriminator is preserved from init) config .serialize(&mut &mut data[DISCRIMINATOR_LEN..]) - .map_err(|_| LightPdaError::Borsh)?; + .map_err(|_| LightSdkTypesError::Borsh)?; Ok(()) } diff --git a/sdk-libs/sdk-interface/src/program/decompression/create_token_account.rs b/sdk-libs/sdk-types/src/interface/program/decompression/create_token_account.rs similarity index 94% rename from sdk-libs/sdk-interface/src/program/decompression/create_token_account.rs rename to sdk-libs/sdk-types/src/interface/program/decompression/create_token_account.rs index 429dafc2d4..e561c53cb4 100644 --- a/sdk-libs/sdk-interface/src/program/decompression/create_token_account.rs +++ b/sdk-libs/sdk-types/src/interface/program/decompression/create_token_account.rs @@ -12,7 +12,7 @@ use light_token_interface::{ LIGHT_TOKEN_PROGRAM_ID, }; -use crate::{error::LightPdaError, AnchorSerialize}; +use crate::{error::LightSdkTypesError, AnchorSerialize}; /// Build instruction data and account metas for creating a compressible ATA. /// @@ -36,7 +36,7 @@ pub fn build_create_ata_instruction( compressible_config: &[u8; 32], rent_sponsor: &[u8; 32], write_top_up: u32, -) -> Result<(Vec, Vec), LightPdaError> { +) -> Result<(Vec, Vec), LightSdkTypesError> { let instruction_data = CreateAssociatedTokenAccountInstructionData { bump, compressible_config: Some(CompressibleExtensionInstructionData { @@ -52,7 +52,7 @@ pub fn build_create_ata_instruction( data.push(102u8); // CreateAssociatedTokenAccountIdempotent discriminator instruction_data .serialize(&mut data) - .map_err(|_| LightPdaError::Borsh)?; + .map_err(|_| LightSdkTypesError::Borsh)?; let accounts = vec![ CpiMeta { @@ -117,11 +117,11 @@ pub fn build_create_token_account_instruction( write_top_up: u32, signer_seeds: &[&[u8]], program_id: &[u8; 32], -) -> Result<(Vec, Vec), LightPdaError> { +) -> Result<(Vec, Vec), LightSdkTypesError> { let bump = signer_seeds .last() .and_then(|s| s.first().copied()) - .ok_or(LightPdaError::InvalidSeeds)?; + .ok_or(LightSdkTypesError::InvalidSeeds)?; let seeds_without_bump: Vec> = signer_seeds .iter() .take(signer_seeds.len().saturating_sub(1)) @@ -149,7 +149,7 @@ pub fn build_create_token_account_instruction( data.push(18u8); // InitializeAccount3 opcode instruction_data .serialize(&mut data) - .map_err(|_| LightPdaError::Borsh)?; + .map_err(|_| LightSdkTypesError::Borsh)?; let accounts = vec![ CpiMeta { diff --git a/sdk-libs/sdk-interface/src/program/decompression/mod.rs b/sdk-libs/sdk-types/src/interface/program/decompression/mod.rs similarity index 100% rename from sdk-libs/sdk-interface/src/program/decompression/mod.rs rename to sdk-libs/sdk-types/src/interface/program/decompression/mod.rs diff --git a/sdk-libs/sdk-interface/src/program/decompression/pda.rs b/sdk-libs/sdk-types/src/interface/program/decompression/pda.rs similarity index 84% rename from sdk-libs/sdk-interface/src/program/decompression/pda.rs rename to sdk-libs/sdk-types/src/interface/program/decompression/pda.rs index 09cc845eab..fd6a3ee4a6 100644 --- a/sdk-libs/sdk-interface/src/program/decompression/pda.rs +++ b/sdk-libs/sdk-types/src/interface/program/decompression/pda.rs @@ -1,5 +1,6 @@ //! Generic prepare_account_for_decompression. +use crate::{constants::RENT_SPONSOR_SEED, instruction::PackedStateTreeInfo}; use light_account_checks::AccountInfoTrait; use light_compressed_account::{ address::derive_address, @@ -8,17 +9,17 @@ use light_compressed_account::{ }; use light_compressible::DECOMPRESSED_PDA_DISCRIMINATOR; use light_hasher::{sha256::Sha256BE, Hasher}; -use light_sdk_types::{constants::RENT_SPONSOR_SEED, instruction::PackedStateTreeInfo}; -use crate::{ +use crate::interface::{ account::light_account::LightAccount, - error::LightPdaError, - light_account_checks::discriminator::Discriminator as LightDiscriminator, program::{ decompression::processor::DecompressCtx, variant::{LightAccountVariantTrait, PackedLightAccountVariantTrait}, }, - AnchorSerialize, +}; +use crate::{ + error::LightSdkTypesError, + light_account_checks::discriminator::Discriminator as LightDiscriminator, AnchorSerialize, }; /// Generic prepare_account_for_decompression. @@ -47,7 +48,7 @@ pub fn prepare_account_for_decompression( output_queue_index: u8, pda_account: &AI, ctx: &mut DecompressCtx<'_, AI>, -) -> Result<(), LightPdaError> +) -> Result<(), LightSdkTypesError> where AI: AccountInfoTrait + Clone, P: PackedLightAccountVariantTrait, @@ -61,11 +62,11 @@ where let packed_accounts = ctx .cpi_accounts .packed_accounts() - .map_err(LightPdaError::from)?; + .map_err(LightSdkTypesError::from)?; let unpacked = packed .unpack(packed_accounts) - .map_err(|_| LightPdaError::InvalidInstructionData)?; + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; let account_data = unpacked.data().clone(); // 2. Get seeds from unpacked variant using seed_vec() (owned data, no lifetime issues) @@ -77,39 +78,36 @@ where // 3. SECURITY: Validate PDA derivation FIRST (defense-in-depth) // This MUST run before idempotency check to prevent accepting wrong PDAs - let expected_pda = - AI::create_program_address(&seed_slices, ctx.program_id).map_err(|_| { - LightPdaError::InvalidSeeds - })?; + let expected_pda = AI::create_program_address(&seed_slices, ctx.program_id) + .map_err(|_| LightSdkTypesError::InvalidSeeds)?; if pda_account.key() != expected_pda { - return Err(LightPdaError::InvalidSeeds); + return Err(LightSdkTypesError::InvalidSeeds); } // 4. Idempotency check - if PDA already has data (non-zero discriminator), skip // IMPORTANT: This runs AFTER PDA validation so wrong PDAs cannot bypass validation - if crate::program::validation::is_pda_initialized(pda_account)? { + if crate::interface::program::validation::is_pda_initialized(pda_account)? { return Ok(()); } // 5. Hash with canonical CompressionInfo::compressed() for input verification let data_bytes = account_data .try_to_vec() - .map_err(|_| LightPdaError::Borsh)?; + .map_err(|_| LightSdkTypesError::Borsh)?; let data_len = data_bytes.len(); let mut input_data_hash = Sha256BE::hash(&data_bytes)?; input_data_hash[0] = 0; // Zero first byte per protocol convention // 6. Calculate space and create PDA let discriminator_len = 8; - let space = - discriminator_len + data_len.max( as LightAccount>::INIT_SPACE); + let space = discriminator_len + data_len.max( as LightAccount>::INIT_SPACE); let rent_minimum = AI::get_min_rent_balance(space)?; let system_program = ctx .cpi_accounts .system_program() - .map_err(LightPdaError::from)?; + .map_err(LightSdkTypesError::from)?; // Construct rent sponsor seeds for PDA signing let rent_sponsor_bump_bytes = [ctx.rent_sponsor_bump]; @@ -125,12 +123,12 @@ where rent_sponsor_seeds, system_program, ) - .map_err(|_| LightPdaError::CpiFailed)?; + .map_err(|_| LightSdkTypesError::CpiFailed)?; // 7. Write discriminator + data to PDA let mut pda_data = pda_account .try_borrow_mut_data() - .map_err(|_| LightPdaError::ConstraintViolation)?; + .map_err(|_| LightSdkTypesError::ConstraintViolation)?; pda_data[..8] .copy_from_slice(& as LightDiscriminator>::LIGHT_DISCRIMINATOR); @@ -140,15 +138,11 @@ where let writer = &mut &mut pda_data[8..]; decompressed .serialize(writer) - .map_err(|_| LightPdaError::Borsh)?; + .map_err(|_| LightSdkTypesError::Borsh)?; // 9. Derive compressed address from PDA key let pda_key = pda_account.key(); - let address = derive_address( - &pda_key, - &ctx.light_config.address_space[0], - ctx.program_id, - ); + let address = derive_address(&pda_key, &ctx.light_config.address_space[0], ctx.program_id); // 10. Build CompressedAccountInfo for CPI let input = InAccountInfo { diff --git a/sdk-libs/sdk-interface/src/program/decompression/processor.rs b/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs similarity index 91% rename from sdk-libs/sdk-interface/src/program/decompression/processor.rs rename to sdk-libs/sdk-types/src/interface/program/decompression/processor.rs index 6b767cb7f8..469e29d6f5 100644 --- a/sdk-libs/sdk-interface/src/program/decompression/processor.rs +++ b/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs @@ -1,41 +1,37 @@ //! Decompression instruction processor. +use crate::{cpi_accounts::v2::CpiAccounts, instruction::PackedStateTreeInfo, CpiSigner}; use light_account_checks::AccountInfoTrait; use light_compressed_account::instruction_data::{ compressed_proof::ValidityProof, with_account_info::{CompressedAccountInfo, InstructionDataInvokeCpiWithAccountInfo}, }; -use light_sdk_types::{ - cpi_accounts::v2::CpiAccounts, instruction::PackedStateTreeInfo, CpiSigner, -}; -use crate::{ - account::compression_info::CompressedAccountData, - cpi::InvokeLightSystemProgram, - error::LightPdaError, +use crate::interface::{ + account::compression_info::CompressedAccountData, cpi::InvokeLightSystemProgram, program::config::LightConfig, - AnchorDeserialize, AnchorSerialize, }; +use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; #[cfg(feature = "token")] -use light_account_checks::CpiMeta; -#[cfg(feature = "token")] -use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; -#[cfg(feature = "token")] -use light_sdk_types::{ - cpi_accounts::CpiAccountsConfig, - cpi_context_write::CpiContextWriteAccounts, +use crate::{ constants::{ - ACCOUNT_COMPRESSION_AUTHORITY_PDA, ACCOUNT_COMPRESSION_PROGRAM_ID, - LIGHT_SYSTEM_PROGRAM_ID, REGISTERED_PROGRAM_PDA, + ACCOUNT_COMPRESSION_AUTHORITY_PDA, ACCOUNT_COMPRESSION_PROGRAM_ID, LIGHT_SYSTEM_PROGRAM_ID, + REGISTERED_PROGRAM_PDA, }, + cpi_accounts::CpiAccountsConfig, + cpi_context_write::CpiContextWriteAccounts, }; #[cfg(feature = "token")] +use light_account_checks::CpiMeta; +#[cfg(feature = "token")] +use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; +#[cfg(feature = "token")] use light_token_interface::{ instructions::{ extensions::ExtensionInstructionData, transfer2::{ - Compression, CompressedTokenInstructionDataTransfer2, MultiInputTokenDataWithContext, + CompressedTokenInstructionDataTransfer2, Compression, MultiInputTokenDataWithContext, }, }, CPI_AUTHORITY, LIGHT_TOKEN_PROGRAM_ID, TRANSFER2, @@ -69,7 +65,7 @@ pub trait DecompressVariant: meta: &PackedStateTreeInfo, pda_account: &AI, ctx: &mut DecompressCtx<'_, AI>, - ) -> Result<(), LightPdaError>; + ) -> Result<(), LightSdkTypesError>; } // ============================================================================ @@ -150,18 +146,18 @@ pub fn process_decompress_pda_accounts_idempotent( cpi_signer: CpiSigner, program_id: &[u8; 32], current_slot: u64, -) -> Result<(), LightPdaError> +) -> Result<(), LightSdkTypesError> where AI: AccountInfoTrait + Clone, V: DecompressVariant, { // 1. Deserialize params let params = DecompressIdempotentParams::::try_from_slice(instruction_data) - .map_err(|_| LightPdaError::Borsh)?; + .map_err(|_| LightSdkTypesError::Borsh)?; let system_accounts_offset = params.system_accounts_offset as usize; if system_accounts_offset > remaining_accounts.len() { - return Err(LightPdaError::InvalidInstructionData); + return Err(LightSdkTypesError::InvalidInstructionData); } // PDA accounts: all accounts up to token_accounts_offset @@ -169,10 +165,10 @@ where let pda_accounts = params .accounts .get(..num_pda_accounts) - .ok_or(LightPdaError::InvalidInstructionData)?; + .ok_or(LightSdkTypesError::InvalidInstructionData)?; if pda_accounts.is_empty() { - return Err(LightPdaError::InvalidInstructionData); + return Err(LightSdkTypesError::InvalidInstructionData); } // 2. Load and validate config @@ -185,11 +181,11 @@ where let hot_accounts_start = remaining_accounts .len() .checked_sub(num_hot_accounts) - .ok_or(LightPdaError::NotEnoughAccountKeys)?; + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)?; let hot_account_infos = &remaining_accounts[hot_accounts_start..]; let pda_account_infos = hot_account_infos .get(..num_pda_accounts) - .ok_or(LightPdaError::NotEnoughAccountKeys)?; + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)?; // 4. Build CpiAccounts (system + tree accounts, excluding hot accounts) let cpi_accounts = CpiAccounts::new( @@ -279,25 +275,25 @@ pub fn process_decompress_accounts_idempotent( cpi_signer: CpiSigner, program_id: &[u8; 32], current_slot: u64, -) -> Result<(), LightPdaError> +) -> Result<(), LightSdkTypesError> where AI: AccountInfoTrait + Clone, V: DecompressVariant, { // 1. Deserialize params let params = DecompressIdempotentParams::::try_from_slice(instruction_data) - .map_err(|_| LightPdaError::Borsh)?; + .map_err(|_| LightSdkTypesError::Borsh)?; let system_accounts_offset = params.system_accounts_offset as usize; if system_accounts_offset > remaining_accounts.len() { - return Err(LightPdaError::InvalidInstructionData); + return Err(LightSdkTypesError::InvalidInstructionData); } // 2. Split accounts into PDA and token let (pda_accounts, token_accounts) = params .accounts .split_at_checked(params.token_accounts_offset as usize) - .ok_or(LightPdaError::InvalidInstructionData)?; + .ok_or(LightSdkTypesError::InvalidInstructionData)?; // 3. Load and validate config let config = LightConfig::load_checked(&remaining_accounts[CONFIG_INDEX], program_id)?; @@ -309,11 +305,11 @@ where let hot_accounts_start = remaining_accounts .len() .checked_sub(num_hot_accounts) - .ok_or(LightPdaError::NotEnoughAccountKeys)?; + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)?; let hot_account_infos = &remaining_accounts[hot_accounts_start..]; let (pda_account_infos, token_account_infos) = hot_account_infos .split_at_checked(params.token_accounts_offset as usize) - .ok_or(LightPdaError::NotEnoughAccountKeys)?; + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)?; let has_pda_accounts = !pda_accounts.is_empty(); let has_token_accounts = !token_accounts.is_empty(); @@ -336,10 +332,10 @@ where // [3] ctoken_rent_sponsor, [6] ctoken_compressible_config let ctoken_rent_sponsor = remaining_accounts .get(3) - .ok_or(LightPdaError::NotEnoughAccountKeys)?; + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)?; let ctoken_compressible_config = remaining_accounts .get(6) - .ok_or(LightPdaError::NotEnoughAccountKeys)?; + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)?; // 6. Build context and dispatch (scoped to release borrows before CPI) let (compressed_account_infos, in_token_data, in_tlv, token_seeds) = { @@ -402,12 +398,10 @@ where cpi_ix_data.invoke::(cpi_accounts.clone())?; } else { // PDAs + tokens: write PDA data to CPI context first, tokens will execute - let authority = cpi_accounts - .authority() - .map_err(LightPdaError::from)?; + let authority = cpi_accounts.authority().map_err(LightSdkTypesError::from)?; let cpi_context_account = cpi_accounts .cpi_context() - .map_err(LightPdaError::from)?; + .map_err(LightSdkTypesError::from)?; let system_cpi_accounts = CpiContextWriteAccounts { fee_payer: &remaining_accounts[FEE_PAYER_INDEX], authority, @@ -520,7 +514,7 @@ where if cpi_context { let cpi_ctx = cpi_accounts .cpi_context() - .map_err(LightPdaError::from)?; + .map_err(LightSdkTypesError::from)?; account_metas.push(CpiMeta { pubkey: cpi_ctx.key(), is_signer: false, @@ -547,7 +541,7 @@ where // Serialize instruction data let mut transfer2_data = vec![TRANSFER2]; cpi.serialize(&mut transfer2_data) - .map_err(|_| LightPdaError::Borsh)?; + .map_err(|_| LightSdkTypesError::Borsh)?; // Invoke the light token program if token_seeds.is_empty() { @@ -559,11 +553,10 @@ where remaining_accounts, &[], ) - .map_err(|_| LightPdaError::CpiFailed)?; + .map_err(|_| LightSdkTypesError::CpiFailed)?; } else { // At least one regular token account - use invoke_signed with PDA seeds - let signer_seed_refs: Vec<&[u8]> = - token_seeds.iter().map(|s| s.as_slice()).collect(); + let signer_seed_refs: Vec<&[u8]> = token_seeds.iter().map(|s| s.as_slice()).collect(); AI::invoke_cpi( &LIGHT_TOKEN_PROGRAM_ID, &transfer2_data, @@ -571,7 +564,7 @@ where remaining_accounts, &[signer_seed_refs.as_slice()], ) - .map_err(|_| LightPdaError::CpiFailed)?; + .map_err(|_| LightSdkTypesError::CpiFailed)?; } } diff --git a/sdk-libs/sdk-interface/src/program/decompression/token.rs b/sdk-libs/sdk-types/src/interface/program/decompression/token.rs similarity index 84% rename from sdk-libs/sdk-interface/src/program/decompression/token.rs rename to sdk-libs/sdk-types/src/interface/program/decompression/token.rs index 256d9dc6df..c913ef3882 100644 --- a/sdk-libs/sdk-interface/src/program/decompression/token.rs +++ b/sdk-libs/sdk-types/src/interface/program/decompression/token.rs @@ -1,18 +1,17 @@ //! Token account decompression. +use crate::instruction::PackedStateTreeInfo; use light_account_checks::AccountInfoTrait; -use light_sdk_types::instruction::PackedStateTreeInfo; -use light_token_interface::{instructions::extensions::ExtensionInstructionData, LIGHT_TOKEN_PROGRAM_ID}; +use light_token_interface::{ + instructions::extensions::ExtensionInstructionData, LIGHT_TOKEN_PROGRAM_ID, +}; use super::create_token_account::{ build_create_ata_instruction, build_create_token_account_instruction, }; -use crate::{ - error::LightPdaError, - program::{ - decompression::processor::DecompressCtx, - variant::PackedLightAccountVariantTrait, - }, +use crate::error::LightSdkTypesError; +use crate::interface::program::{ + decompression::processor::DecompressCtx, variant::PackedLightAccountVariantTrait, }; pub fn prepare_token_account_for_decompression( @@ -21,7 +20,7 @@ pub fn prepare_token_account_for_decompression( output_queue_index: u8, token_account_info: &AI, ctx: &mut DecompressCtx<'_, AI>, -) -> Result<(), LightPdaError> +) -> Result<(), LightSdkTypesError> where AI: AccountInfoTrait + Clone, P: PackedLightAccountVariantTrait, @@ -29,7 +28,7 @@ where let packed_accounts = ctx .cpi_accounts .packed_accounts() - .map_err(LightPdaError::from)?; + .map_err(LightSdkTypesError::from)?; let token_data = packed.into_in_token_data(tree_info, output_queue_index)?; // Get TLV extension early to detect ATA @@ -53,7 +52,7 @@ where // Resolve mint pubkey from packed index let mint_key = packed_accounts .get(token_data.mint as usize) - .ok_or(LightPdaError::InvalidInstructionData)? + .ok_or(LightSdkTypesError::InvalidInstructionData)? .key(); let fee_payer_key = ctx.cpi_accounts.fee_payer().key(); @@ -64,7 +63,7 @@ where let is_already_initialized = token_account_info.data_len() > STATE_OFFSET && { let data = token_account_info .try_borrow_data() - .map_err(|_| LightPdaError::ConstraintViolation)?; + .map_err(|_| LightSdkTypesError::ConstraintViolation)?; data[STATE_OFFSET] != 0 }; @@ -72,19 +71,19 @@ where let ctoken_compressible_config_key = ctx .ctoken_compressible_config .as_ref() - .ok_or(LightPdaError::NotEnoughAccountKeys)? + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)? .key(); let ctoken_rent_sponsor_key = ctx .ctoken_rent_sponsor .as_ref() - .ok_or(LightPdaError::NotEnoughAccountKeys)? + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)? .key(); if let Some((ata_bump, wallet_owner_index)) = ata_info { // ATA path: use invoke() without signer seeds let wallet_owner_key = packed_accounts .get(wallet_owner_index as usize) - .ok_or(LightPdaError::InvalidInstructionData)? + .ok_or(LightSdkTypesError::InvalidInstructionData)? .key(); // Idempotency: only create ATA if it doesn't exist @@ -108,7 +107,7 @@ where ctx.remaining_accounts, &[], ) - .map_err(|_| LightPdaError::CpiFailed)?; + .map_err(|_| LightSdkTypesError::CpiFailed)?; } // Don't extend token_seeds for ATAs (invoke, not invoke_signed) } else { @@ -120,7 +119,7 @@ where let bump = &[packed.bump()]; let seeds = packed .seed_refs_with_bump(packed_accounts, bump) - .map_err(|_| LightPdaError::InvalidSeeds)?; + .map_err(|_| LightSdkTypesError::InvalidSeeds)?; // Derive owner pubkey from constant owner_seeds let owner = packed.derive_owner(); @@ -147,7 +146,7 @@ where ctx.remaining_accounts, &[signer_seeds.as_slice()], ) - .map_err(|_| LightPdaError::CpiFailed)?; + .map_err(|_| LightSdkTypesError::CpiFailed)?; // Push seeds for the Transfer2 CPI (needed for invoke_signed) ctx.token_seeds.extend(seeds.iter().map(|s| s.to_vec())); diff --git a/sdk-libs/sdk-interface/src/program/mod.rs b/sdk-libs/sdk-types/src/interface/program/mod.rs similarity index 100% rename from sdk-libs/sdk-interface/src/program/mod.rs rename to sdk-libs/sdk-types/src/interface/program/mod.rs diff --git a/sdk-libs/sdk-interface/src/program/validation.rs b/sdk-libs/sdk-types/src/interface/program/validation.rs similarity index 84% rename from sdk-libs/sdk-interface/src/program/validation.rs rename to sdk-libs/sdk-types/src/interface/program/validation.rs index 3e374a7efb..66be522ed3 100644 --- a/sdk-libs/sdk-interface/src/program/validation.rs +++ b/sdk-libs/sdk-types/src/interface/program/validation.rs @@ -4,7 +4,8 @@ use light_account_checks::{ account_iterator::AccountIterator, checks::check_data_is_zeroed, AccountInfoTrait, }; -use crate::{error::LightPdaError, program::config::LightConfig}; +use crate::error::LightSdkTypesError; +use crate::interface::program::config::LightConfig; /// Validated PDA context after account extraction and config validation. pub struct ValidatedPdaContext { @@ -26,7 +27,7 @@ pub struct ValidatedPdaContext { pub fn validate_compress_accounts( remaining_accounts: &[AI], program_id: &[u8; 32], -) -> Result, LightPdaError> { +) -> Result, LightSdkTypesError> { validate_pda_common_accounts_inner::(remaining_accounts, program_id) } @@ -39,7 +40,7 @@ pub fn validate_compress_accounts( pub fn validate_decompress_accounts( remaining_accounts: &[AI], program_id: &[u8; 32], -) -> Result, LightPdaError> { +) -> Result, LightSdkTypesError> { validate_pda_common_accounts_inner::(remaining_accounts, program_id) } @@ -47,7 +48,7 @@ pub fn validate_decompress_accounts( fn validate_pda_common_accounts_inner( remaining_accounts: &[AI], program_id: &[u8; 32], -) -> Result, LightPdaError> +) -> Result, LightSdkTypesError> where AI: AccountInfoTrait + Clone, { @@ -55,19 +56,19 @@ where let fee_payer = account_iter .next_signer_mut("fee_payer") - .map_err(LightPdaError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountCheck)?; let config = account_iter .next_non_mut("config") - .map_err(LightPdaError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountCheck)?; let rent_sponsor = account_iter .next_mut("rent_sponsor") - .map_err(LightPdaError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountCheck)?; let compression_authority = if EXTRACT_COMPRESSION_AUTHORITY { Some( account_iter .next_account("compression_authority") - .map_err(LightPdaError::AccountCheck)? + .map_err(LightSdkTypesError::AccountCheck)? .clone(), ) } else { @@ -93,22 +94,22 @@ where pub fn split_at_system_accounts_offset( remaining_accounts: &[AI], system_accounts_offset: u8, -) -> Result<(&[AI], &[AI]), LightPdaError> { +) -> Result<(&[AI], &[AI]), LightSdkTypesError> { let offset = system_accounts_offset as usize; remaining_accounts .split_at_checked(offset) - .ok_or(LightPdaError::ConstraintViolation) + .ok_or(LightSdkTypesError::ConstraintViolation) } /// Extract PDA accounts from the tail of remaining_accounts. pub fn extract_tail_accounts( remaining_accounts: &[AI], num_pda_accounts: usize, -) -> Result<&[AI], LightPdaError> { +) -> Result<&[AI], LightSdkTypesError> { let start = remaining_accounts .len() .checked_sub(num_pda_accounts) - .ok_or(LightPdaError::ConstraintViolation)?; + .ok_or(LightSdkTypesError::ConstraintViolation)?; Ok(&remaining_accounts[start..]) } @@ -117,7 +118,7 @@ pub fn extract_tail_accounts( /// Returns: /// - `Ok(true)` if account has data and non-zero discriminator (initialized) /// - `Ok(false)` if account is empty or has zeroed discriminator (not initialized) -pub fn is_pda_initialized(account: &AI) -> Result { +pub fn is_pda_initialized(account: &AI) -> Result { use light_account_checks::discriminator::DISCRIMINATOR_LEN; if account.data_is_empty() { @@ -125,7 +126,7 @@ pub fn is_pda_initialized(account: &AI) -> Result { /// Construct variant from compressed account data bytes and these seeds. - fn into_variant(self, data: &[u8]) -> Result; + fn into_variant(self, data: &[u8]) -> Result; } // --- Variant traits --- @@ -87,7 +84,7 @@ pub trait PackedLightAccountVariantTrait: fn unpack( &self, accounts: &[AI], - ) -> Result; + ) -> Result; /// Get seed references with bump for CPI signing. /// Resolves u8 indices to pubkey refs from accounts slice. @@ -95,18 +92,18 @@ pub trait PackedLightAccountVariantTrait: &'a self, accounts: &'a [AI], bump_storage: &'a [u8; 1], - ) -> Result<[&'a [u8]; SEED_COUNT], LightPdaError>; + ) -> Result<[&'a [u8]; SEED_COUNT], LightSdkTypesError>; /// Extract token data for compressed token CPI. /// Only meaningful for token account variants; PDA variants should return an error. #[cfg(feature = "token")] fn into_in_token_data( &self, - tree_info: &light_sdk_types::instruction::PackedStateTreeInfo, + tree_info: &crate::instruction::PackedStateTreeInfo, output_queue_index: u8, ) -> Result< light_token_interface::instructions::transfer2::MultiInputTokenDataWithContext, - LightPdaError, + LightSdkTypesError, >; /// Extract TLV extension data for compressed token CPI. @@ -116,7 +113,7 @@ pub trait PackedLightAccountVariantTrait: &self, ) -> Result< Option>, - LightPdaError, + LightSdkTypesError, >; /// Derive the owner pubkey from constant owner_seeds and program ID. @@ -133,7 +130,7 @@ pub trait PackedLightAccountVariantTrait: mod token_traits { use light_account_checks::AccountInfoTrait; - use crate::{AnchorDeserialize, AnchorSerialize, error::LightPdaError}; + use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; /// Trait for unpacked token seed structs. /// @@ -170,14 +167,14 @@ mod token_traits { fn unpack_seeds( &self, accounts: &[AI], - ) -> Result; + ) -> Result; /// Get seed references with bump for CPI signing. fn seed_refs_with_bump<'a, AI: AccountInfoTrait>( &'a self, accounts: &'a [AI], bump_storage: &'a [u8; 1], - ) -> Result<[&'a [u8]; N], LightPdaError>; + ) -> Result<[&'a [u8]; N], LightSdkTypesError>; /// Derive the owner pubkey from constant owner_seeds and program ID. fn derive_owner(&self) -> [u8; 32]; diff --git a/sdk-libs/sdk-types/src/lib.rs b/sdk-libs/sdk-types/src/lib.rs index e1c2fcae88..02013e8df4 100644 --- a/sdk-libs/sdk-types/src/lib.rs +++ b/sdk-libs/sdk-types/src/lib.rs @@ -18,17 +18,21 @@ extern crate alloc; pub mod address; pub mod constants; pub mod cpi_accounts; -#[cfg(feature = "cpi-context")] pub mod cpi_context_write; pub mod error; pub mod instruction; +#[cfg(feature = "std")] +pub mod interface; + // Re-exports #[cfg(feature = "anchor")] -use anchor_lang::{AnchorDeserialize, AnchorSerialize}; +pub use anchor_lang::{AnchorDeserialize, AnchorSerialize}; #[cfg(not(feature = "anchor"))] -use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; +pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; pub use constants::*; +pub use light_account_checks; +pub use light_account_checks::discriminator::Discriminator as LightDiscriminator; pub use light_compressed_account::CpiSigner; #[derive(Clone, Copy, Debug, PartialEq, Eq, AnchorSerialize, AnchorDeserialize)] diff --git a/sdk-libs/sdk/Cargo.toml b/sdk-libs/sdk/Cargo.toml index e95561ab7d..23d813f6df 100644 --- a/sdk-libs/sdk/Cargo.toml +++ b/sdk-libs/sdk/Cargo.toml @@ -31,9 +31,9 @@ anchor = [ v2 = ["light-sdk-types/v2"] cpi-context = ["light-sdk-types/cpi-context"] devnet = [] -poseidon = ["light-hasher/poseidon", "light-compressed-account/poseidon", "light-sdk-interface/poseidon"] -keccak = ["light-hasher/keccak", "light-compressed-account/keccak", "light-sdk-interface/keccak"] -sha256 = ["light-hasher/sha256", "light-compressed-account/sha256", "light-sdk-interface/sha256"] +poseidon = ["light-hasher/poseidon", "light-compressed-account/poseidon", "light-sdk-types/poseidon"] +keccak = ["light-hasher/keccak", "light-compressed-account/keccak", "light-sdk-types/keccak"] +sha256 = ["light-hasher/sha256", "light-compressed-account/sha256", "light-sdk-types/sha256"] merkle-tree = ["light-concurrent-merkle-tree/solana"] anchor-discriminator = ["light-sdk-macros/anchor-discriminator"] custom-heap = ["light-heap"] @@ -65,7 +65,7 @@ bincode = "1" light-program-profiler = { workspace = true } light-sdk-macros = { workspace = true } -light-sdk-types = { workspace = true, features = ["std"] } +light-sdk-types = { workspace = true, features = ["std", "token"] } light-macros = { workspace = true } light-compressed-account = { workspace = true, features = ["std"] } light-hasher = { workspace = true, features = ["std"] } @@ -75,7 +75,6 @@ light-concurrent-merkle-tree = { workspace = true, optional = true } light-compressible = { workspace = true } light-heap = { workspace = true, optional = true } light-token-interface = { workspace = true } # TODO: make optional -light-sdk-interface = { workspace = true, features = ["std", "token"] } [dev-dependencies] num-bigint = { workspace = true } diff --git a/sdk-libs/sdk/src/cpi/mod.rs b/sdk-libs/sdk/src/cpi/mod.rs index ad154cd423..4fb5dce11e 100644 --- a/sdk-libs/sdk/src/cpi/mod.rs +++ b/sdk-libs/sdk/src/cpi/mod.rs @@ -36,7 +36,7 @@ //! ``` // Re-export everything from interface's CPI module (LightCpi, InvokeLightSystemProgram, etc.) -pub use light_sdk_interface::cpi::*; +pub use light_sdk_types::interface::cpi::*; use light_compressed_account::instruction_data::{ compressed_proof::ValidityProof, @@ -112,31 +112,31 @@ macro_rules! delegate_light_cpi { cpi_signer: crate::cpi::CpiSigner, proof: ValidityProof, ) -> Self { - <$ty as light_sdk_interface::cpi::LightCpi>::new_cpi(cpi_signer, proof) + <$ty as light_sdk_types::interface::cpi::LightCpi>::new_cpi(cpi_signer, proof) } fn get_mode(&self) -> u8 { - <$ty as light_sdk_interface::cpi::LightCpi>::get_mode(self) + <$ty as light_sdk_types::interface::cpi::LightCpi>::get_mode(self) } fn get_bump(&self) -> u8 { - <$ty as light_sdk_interface::cpi::LightCpi>::get_bump(self) + <$ty as light_sdk_types::interface::cpi::LightCpi>::get_bump(self) } fn write_to_cpi_context_first(self) -> Self { - <$ty as light_sdk_interface::cpi::LightCpi>::write_to_cpi_context_first(self) + <$ty as light_sdk_types::interface::cpi::LightCpi>::write_to_cpi_context_first(self) } fn write_to_cpi_context_set(self) -> Self { - <$ty as light_sdk_interface::cpi::LightCpi>::write_to_cpi_context_set(self) + <$ty as light_sdk_types::interface::cpi::LightCpi>::write_to_cpi_context_set(self) } fn execute_with_cpi_context(self) -> Self { - <$ty as light_sdk_interface::cpi::LightCpi>::execute_with_cpi_context(self) + <$ty as light_sdk_types::interface::cpi::LightCpi>::execute_with_cpi_context(self) } fn get_with_cpi_context(&self) -> bool { - <$ty as light_sdk_interface::cpi::LightCpi>::get_with_cpi_context(self) + <$ty as light_sdk_types::interface::cpi::LightCpi>::get_with_cpi_context(self) } fn get_cpi_context(&self) -> &CompressedCpiContext { - <$ty as light_sdk_interface::cpi::LightCpi>::get_cpi_context(self) + <$ty as light_sdk_types::interface::cpi::LightCpi>::get_cpi_context(self) } fn has_read_only_accounts(&self) -> bool { - <$ty as light_sdk_interface::cpi::LightCpi>::has_read_only_accounts(self) + <$ty as light_sdk_types::interface::cpi::LightCpi>::has_read_only_accounts(self) } }; } diff --git a/sdk-libs/sdk/src/error.rs b/sdk-libs/sdk/src/error.rs index f59f90754c..8f3f9ed187 100644 --- a/sdk-libs/sdk/src/error.rs +++ b/sdk-libs/sdk/src/error.rs @@ -129,61 +129,33 @@ impl From for ProgramError { } } -/// Convert from SDK's LightSdkError to interface's LightPdaError. -/// This allows SDK error types to be used where interface error types are expected +/// Convert from SDK's LightSdkError to LightSdkTypesError. +/// This allows SDK error types to be used where types error types are expected /// (e.g., in trait impls for LightPreInit, LightFinalize, AccountMetasVec). -impl From for light_sdk_interface::error::LightPdaError { +impl From for LightSdkTypesError { fn from(e: LightSdkError) -> Self { - use light_sdk_interface::error::LightPdaError as InterfaceError; match e { - LightSdkError::ConstraintViolation => InterfaceError::ConstraintViolation, - LightSdkError::Borsh => InterfaceError::Borsh, - LightSdkError::AccountError(e) => InterfaceError::AccountCheck(e), - LightSdkError::Hasher(e) => InterfaceError::Hasher(e), - LightSdkError::MissingCompressionInfo => InterfaceError::MissingCompressionInfo, - LightSdkError::InvalidRentSponsor => InterfaceError::InvalidRentSponsor, + LightSdkError::ConstraintViolation => LightSdkTypesError::ConstraintViolation, + LightSdkError::Borsh => LightSdkTypesError::Borsh, + LightSdkError::AccountError(e) => LightSdkTypesError::AccountCheck(e), + LightSdkError::Hasher(e) => LightSdkTypesError::Hasher(e), + LightSdkError::MissingCompressionInfo => LightSdkTypesError::MissingCompressionInfo, + LightSdkError::InvalidRentSponsor => LightSdkTypesError::InvalidRentSponsor, LightSdkError::CpiAccountsIndexOutOfBounds(i) => { - InterfaceError::CpiAccountsIndexOutOfBounds(i) + LightSdkTypesError::CpiAccountsIndexOutOfBounds(i) } LightSdkError::ReadOnlyAccountsNotSupportedInCpiContext => { - InterfaceError::ReadOnlyAccountsNotSupportedInCpiContext + LightSdkTypesError::ReadOnlyAccountsNotSupportedInCpiContext } LightSdkError::CompressedAccountError(e) => { - InterfaceError::CompressedAccountError(e) + LightSdkTypesError::CompressedAccountError(e) } - // SDK-specific variants that don't have exact interface equivalents - // are converted to ConstraintViolation as a fallback - _ => InterfaceError::ConstraintViolation, - } - } -} - -/// Convert from interface's LightPdaError to SDK's LightSdkError. -impl From for LightSdkError { - fn from(e: light_sdk_interface::error::LightPdaError) -> Self { - use light_sdk_interface::error::LightPdaError as InterfaceError; - match e { - InterfaceError::ConstraintViolation => LightSdkError::ConstraintViolation, - InterfaceError::Borsh => LightSdkError::Borsh, - InterfaceError::AccountCheck(e) => LightSdkError::AccountError(e), - InterfaceError::Hasher(e) => LightSdkError::Hasher(e), - InterfaceError::MissingCompressionInfo => LightSdkError::MissingCompressionInfo, - InterfaceError::InvalidRentSponsor => LightSdkError::InvalidRentSponsor, - InterfaceError::BorshIo(_) => LightSdkError::Borsh, - InterfaceError::CpiAccountsIndexOutOfBounds(i) => { - LightSdkError::CpiAccountsIndexOutOfBounds(i) - } - InterfaceError::ReadOnlyAccountsNotSupportedInCpiContext => { - LightSdkError::ReadOnlyAccountsNotSupportedInCpiContext - } - InterfaceError::CompressedAccountError(e) => { - LightSdkError::CompressedAccountError(e) - } - _ => LightSdkError::ConstraintViolation, + _ => LightSdkTypesError::ConstraintViolation, } } } +/// Convert from LightSdkTypesError to SDK's LightSdkError. impl From for LightSdkError { fn from(e: LightSdkTypesError) -> Self { match e { @@ -206,11 +178,34 @@ impl From for LightSdkError { LightSdkTypesError::CpiAccountsIndexOutOfBounds(index) => { LightSdkError::CpiAccountsIndexOutOfBounds(index) } - LightSdkTypesError::InvalidSolPoolPdaAccount => LightSdkError::InvalidSolPoolPdaAccount, - LightSdkTypesError::InvalidCpiContextAccount => LightSdkError::InvalidCpiContextAccount, - LightSdkTypesError::InvalidCpiAccountsOffset => LightSdkError::InvalidCpiAccountsOffset, - LightSdkTypesError::AccountError(e) => LightSdkError::AccountError(e), + LightSdkTypesError::InvalidSolPoolPdaAccount => { + LightSdkError::InvalidSolPoolPdaAccount + } + LightSdkTypesError::InvalidCpiContextAccount => { + LightSdkError::InvalidCpiContextAccount + } + LightSdkTypesError::InvalidCpiAccountsOffset => { + LightSdkError::InvalidCpiAccountsOffset + } + LightSdkTypesError::AccountCheck(e) => LightSdkError::AccountError(e), LightSdkTypesError::Hasher(e) => LightSdkError::Hasher(e), + LightSdkTypesError::ConstraintViolation => LightSdkError::ConstraintViolation, + LightSdkTypesError::Borsh => LightSdkError::Borsh, + LightSdkTypesError::MissingCompressionInfo => LightSdkError::MissingCompressionInfo, + LightSdkTypesError::InvalidRentSponsor => LightSdkError::InvalidRentSponsor, + LightSdkTypesError::BorshIo(_) => LightSdkError::Borsh, + LightSdkTypesError::ReadOnlyAccountsNotSupportedInCpiContext => { + LightSdkError::ReadOnlyAccountsNotSupportedInCpiContext + } + LightSdkTypesError::CompressedAccountError(e) => { + LightSdkError::CompressedAccountError(e) + } + LightSdkTypesError::AccountDataTooSmall + | LightSdkTypesError::InvalidInstructionData + | LightSdkTypesError::InvalidSeeds + | LightSdkTypesError::CpiFailed + | LightSdkTypesError::NotEnoughAccountKeys + | LightSdkTypesError::MissingRequiredSignature => LightSdkError::ConstraintViolation, } } } diff --git a/sdk-libs/sdk/src/instruction/mod.rs b/sdk-libs/sdk/src/instruction/mod.rs index cc2c77054b..0b3b5eeb36 100644 --- a/sdk-libs/sdk/src/instruction/mod.rs +++ b/sdk-libs/sdk/src/instruction/mod.rs @@ -45,12 +45,12 @@ pub use light_sdk_types::instruction::*; // Re-export pack_accounts utilities from interface (off-chain only) #[cfg(not(target_os = "solana"))] -pub use light_sdk_interface::instruction::*; +pub use light_sdk_types::interface::instruction::*; // Concrete PackedAccounts type alias for solana AccountMeta (off-chain only) #[cfg(not(target_os = "solana"))] pub type PackedAccounts = - light_sdk_interface::instruction::PackedAccounts; + light_sdk_types::interface::instruction::PackedAccounts; // SDK-specific: ValidityProof and CompressedProof pub use light_compressed_account::instruction_data::compressed_proof::{ diff --git a/sdk-libs/sdk/src/lib.rs b/sdk-libs/sdk/src/lib.rs index 043b941e54..fee062e5b4 100644 --- a/sdk-libs/sdk/src/lib.rs +++ b/sdk-libs/sdk/src/lib.rs @@ -165,9 +165,9 @@ pub mod utils; pub use proof::borsh_compat; /// Backward-compat alias for the interface module -pub use light_sdk_interface as compressible; -/// Re-export the interface crate -pub use light_sdk_interface as interface; +pub use light_sdk_types::interface as compressible; +/// Re-export the interface module +pub use light_sdk_types::interface; #[cfg(feature = "merkle-tree")] pub mod merkle_tree; @@ -204,12 +204,12 @@ pub mod sdk_types { use anchor_lang::{AnchorDeserialize, AnchorSerialize}; #[cfg(not(feature = "anchor"))] use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; -// Re-export interface types from light-sdk-interface +// Re-export interface types from light-sdk-types::interface // Pack trait is only available off-chain (client-side) - uses PackedAccounts #[cfg(not(target_os = "solana"))] -pub use light_sdk_interface::Pack; -pub use light_sdk_interface::Unpack; -pub use light_sdk_interface::{ +pub use light_sdk_types::interface::Pack; +pub use light_sdk_types::interface::Unpack; +pub use light_sdk_types::interface::{ process_initialize_light_config_checked, InitializeLightConfigParams, process_update_light_config, UpdateLightConfigParams, CompressAs, CompressedAccountData, diff --git a/sdk-tests/csdk-anchor-full-derived-test/Cargo.toml b/sdk-tests/csdk-anchor-full-derived-test/Cargo.toml index 8648dec6ca..8d58c69d90 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/Cargo.toml +++ b/sdk-tests/csdk-anchor-full-derived-test/Cargo.toml @@ -20,6 +20,7 @@ test-sbf = [] [dependencies] light-heap = { workspace = true, optional = true } +light-account = { workspace = true } light-sdk = { workspace = true, features = ["anchor", "v2", "anchor-discriminator", "cpi-context"] } light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] } light-hasher = { workspace = true, features = ["solana"] } diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs b/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs index da246ce61f..a3317c75b8 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs @@ -9,8 +9,8 @@ use anchor_lang::prelude::*; use light_anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use light_token::instruction::{ CreateTokenAccountCpi, CreateTokenAtaCpi, MintToCpi, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR, diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instruction_accounts.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instruction_accounts.rs index adccbac8a6..3389bf9387 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instruction_accounts.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instruction_accounts.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::*; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata.rs index 361686d52b..9c04cdbf0a 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata.rs @@ -7,8 +7,8 @@ //! Here the macro should generate CreateTokenAtaCpi call automatically. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_vault.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_vault.rs index 7c4f917ebc..a717895eac 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_vault.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_vault.rs @@ -7,8 +7,8 @@ //! Here the macro should generate the CreateTokenAccountCpi call automatically. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; /// Seed for the vault authority PDA diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/mixed_zc_borsh.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/mixed_zc_borsh.rs index 53f3349d94..6c490fe86b 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/mixed_zc_borsh.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/mixed_zc_borsh.rs @@ -4,8 +4,8 @@ //! Verifies that mixed serialization types work together in the same instruction. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::{ d11_zero_copy::ZcBasicRecord, d1_field_types::single_pubkey::SinglePubkeyRecord, diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/multiple_zc.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/multiple_zc.rs index ad41a0419c..f1d5bd1d0e 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/multiple_zc.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/multiple_zc.rs @@ -4,8 +4,8 @@ //! Verifies that the macro handles multiple AccountLoader fields correctly. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d11_zero_copy::ZcBasicRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ata.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ata.rs index 6b5188a965..fa6f603bfe 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ata.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ata.rs @@ -4,8 +4,8 @@ //! Verifies that zero-copy PDAs work alongside associated token account creation macros. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use crate::state::d11_zero_copy::ZcBasicRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ctx_seeds.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ctx_seeds.rs index ba3fdf4ea1..08ee3ad453 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ctx_seeds.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ctx_seeds.rs @@ -4,8 +4,8 @@ //! Verifies that context account seeds work correctly with zero-copy accounts. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d11_zero_copy::ZcWithSeedsRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_mint_to.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_mint_to.rs index 1ddefed75a..6853f45cb1 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_mint_to.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_mint_to.rs @@ -4,8 +4,8 @@ //! Verifies that zero-copy PDAs work alongside token vault creation and MintTo CPI. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use crate::state::d11_zero_copy::ZcBasicRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_params_seeds.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_params_seeds.rs index cf0ffc1e86..e07136bdc8 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_params_seeds.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_params_seeds.rs @@ -4,8 +4,8 @@ //! Verifies that seed fields not present on the struct work correctly. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d11_zero_copy::ZcWithParamsRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_vault.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_vault.rs index 238b5c20e2..acf50b231e 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_vault.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_vault.rs @@ -4,8 +4,8 @@ //! Verifies that zero-copy PDAs work alongside token account creation macros. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use crate::state::d11_zero_copy::ZcBasicRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/all.rs index b80cb7b129..6f8508abd1 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/all.rs @@ -4,8 +4,8 @@ //! Note: #[light_account(init)] is tested separately in amm_test/initialize.rs. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/rentfree_bare.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/rentfree_bare.rs index 2b5be208df..d2d2daf01a 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/rentfree_bare.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/rentfree_bare.rs @@ -7,8 +7,8 @@ //! because the RentFree derive macro generates code that accesses this field. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/account.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/account.rs index 313cf0db6e..37281ac3e6 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/account.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/account.rs @@ -3,8 +3,8 @@ //! Tests that #[light_account(init)] works with Account<'info, T> directly (not boxed). use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/all.rs index 4eac80b3c9..b4933a47a2 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/all.rs @@ -3,8 +3,8 @@ //! Tests that both account type variants work in the same struct. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::{ d1_field_types::single_pubkey::SinglePubkeyRecord, diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/boxed.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/boxed.rs index 5afbd7ebba..b5dcbc7ae8 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/boxed.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/boxed.rs @@ -4,8 +4,8 @@ //! This exercises the Box unwrap path in seed_extraction.rs with is_boxed = true. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/all.rs index ca99e0c695..d4370e7ad2 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/all.rs @@ -3,8 +3,8 @@ //! Tests that different naming conventions work together in one struct. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/creator.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/creator.rs index e4807b49a9..5a3e83f1fd 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/creator.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/creator.rs @@ -3,8 +3,8 @@ //! Tests that #[light_account(init)] works when the payer field is named `creator` instead of `fee_payer`. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/payer.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/payer.rs index a66c158810..aa333c04c5 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/payer.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/payer.rs @@ -3,8 +3,8 @@ //! Tests that #[light_account(init)] works when the payer field is named `payer` instead of `fee_payer`. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/all.rs index 7ea6e2e7b9..8143ffab42 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/all.rs @@ -3,8 +3,8 @@ //! Tests the builder path with multiple #[light_account(init)] fields of different state types. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::{ d1_field_types::single_pubkey::SinglePubkeyRecord, diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/multi_rentfree.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/multi_rentfree.rs index 89e38cebcb..c655d9a7fc 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/multi_rentfree.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/multi_rentfree.rs @@ -3,8 +3,8 @@ //! Tests the builder path with multiple #[light_account(init)] PDA accounts of the same type. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/pda_only.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/pda_only.rs index fbca73d371..e86cac31e4 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/pda_only.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/pda_only.rs @@ -4,8 +4,8 @@ //! are marked with #[light_account(init)], without any token accounts. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/all.rs index bb2dbc9003..146f0dfdd0 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/all.rs @@ -9,8 +9,8 @@ //! - FunctionCall: max_key(&a, &b) use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/array_bumps.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/array_bumps.rs index 7043588d1e..a69704f9b4 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/array_bumps.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/array_bumps.rs @@ -5,8 +5,8 @@ //! not by including &[bump] in the seeds array. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/complex_mixed.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/complex_mixed.rs index 4171cc66a2..ce00141206 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/complex_mixed.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/complex_mixed.rs @@ -6,8 +6,8 @@ //! - Maximum seed complexity use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/const_patterns.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/const_patterns.rs index 69aef9712c..cd41ba93f1 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/const_patterns.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/const_patterns.rs @@ -7,8 +7,8 @@ //! - Trait associated constants: ::CONSTANT use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/constant.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/constant.rs index 24ba9848db..df34deb50f 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/constant.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/constant.rs @@ -3,8 +3,8 @@ //! Tests ClassifiedSeed::Constant with constant identifier seeds. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/ctx_account.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/ctx_account.rs index 3fe3258a49..9cc9e49575 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/ctx_account.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/ctx_account.rs @@ -3,8 +3,8 @@ //! Tests ClassifiedSeed::CtxAccount with authority.key() seeds. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/edge_cases.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/edge_cases.rs index 53640c3062..017eb8a49c 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/edge_cases.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/edge_cases.rs @@ -9,8 +9,8 @@ //! - Many literals in same seeds array use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/external_paths.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/external_paths.rs index c06c509f31..8df2d13438 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/external_paths.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/external_paths.rs @@ -6,8 +6,8 @@ //! - Complex nested external paths use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/function_call.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/function_call.rs index f594857d37..3109bca47c 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/function_call.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/function_call.rs @@ -3,8 +3,8 @@ //! Tests ClassifiedSeed::FunctionCall with max_key(&a, &b) seeds. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/instruction_data.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/instruction_data.rs index 398e3f00d7..c35951a909 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/instruction_data.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/instruction_data.rs @@ -9,8 +9,8 @@ //! so we test naming variations within the seed expressions, not the param struct name. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/literal.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/literal.rs index d6f7909a49..8977c479ec 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/literal.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/literal.rs @@ -3,8 +3,8 @@ //! Tests ClassifiedSeed::Literal with byte literal seeds. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/method_chains.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/method_chains.rs index 7ad91d6d4a..f62971d635 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/method_chains.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/method_chains.rs @@ -8,8 +8,8 @@ //! - Method chains on qualified paths use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/mixed.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/mixed.rs index d58a845169..946f821d85 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/mixed.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/mixed.rs @@ -3,8 +3,8 @@ //! Tests multiple seed types combined: literal + ctx_account + param. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/nested_seeds.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/nested_seeds.rs index 4e2ebd7db8..b802e430f7 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/nested_seeds.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/nested_seeds.rs @@ -6,8 +6,8 @@ //! - Complex nested struct paths use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param.rs index 1acdbf0555..5919222fa6 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param.rs @@ -3,8 +3,8 @@ //! Tests ClassifiedSeed::DataField with params.owner.as_ref() seeds. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param_bytes.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param_bytes.rs index 9295c55d6f..259131d7f2 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param_bytes.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param_bytes.rs @@ -3,8 +3,8 @@ //! Tests ClassifiedSeed::DataField with params.id.to_le_bytes() conversion. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/qualified_paths.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/qualified_paths.rs index c6616fd4a8..9971c330d0 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/qualified_paths.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/qualified_paths.rs @@ -7,8 +7,8 @@ //! - Nested module paths use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/failing_tests.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/failing_tests.rs index 59cb380ccf..7c281cdc01 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/failing_tests.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/failing_tests.rs @@ -7,8 +7,8 @@ //! - 8: MissingRequiredSignature //! - 11: NotEnoughAccountKeys //! - 14: InvalidSeeds -//! - 17001: LightPdaError::ConstraintViolation -//! - 17004: LightPdaError::InvalidRentSponsor +//! - 17001: LightSdkTypesError::ConstraintViolation +//! - 17004: LightSdkTypesError::InvalidRentSponsor mod shared; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/instruction_decoder_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/instruction_decoder_test.rs index 1c2b8c7744..0f778056b8 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/instruction_decoder_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/instruction_decoder_test.rs @@ -186,8 +186,8 @@ fn test_enhanced_decoder_params_decoding() { instruction_accounts::CreateTwoMintsParams, CsdkTestInstructionDecoder, }; use light_compressed_account::instruction_data::compressed_proof::ValidityProof; - use light_compressible::CreateAccountsProof; use light_sdk_types::instruction::PackedAddressTreeInfo; + use light_sdk_types::interface::CreateAccountsProof; let decoder = CsdkTestInstructionDecoder; @@ -394,9 +394,9 @@ fn test_attribute_macro_decoder_with_instruction_data() { instruction_accounts::CreateTwoMintsParams, CsdkAnchorFullDerivedTestInstructionDecoder, }; use light_compressed_account::instruction_data::compressed_proof::ValidityProof; - use light_compressible::CreateAccountsProof; use light_program_test::logging::InstructionDecoder; use light_sdk_types::instruction::PackedAddressTreeInfo; + use light_sdk_types::interface::CreateAccountsProof; let decoder = CsdkAnchorFullDerivedTestInstructionDecoder; diff --git a/sdk-tests/manual-test/Cargo.toml b/sdk-tests/manual-test/Cargo.toml index 42a018275a..201c31fa58 100644 --- a/sdk-tests/manual-test/Cargo.toml +++ b/sdk-tests/manual-test/Cargo.toml @@ -13,15 +13,12 @@ no-entrypoint = [] no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] -custom-heap = ["light-heap", "light-sdk/custom-heap"] default = [] -idl-build = ["anchor-lang/idl-build", "light-sdk/idl-build"] +idl-build = ["anchor-lang/idl-build"] test-sbf = [] [dependencies] -light-heap = { workspace = true, optional = true } -light-sdk = { workspace = true, features = ["anchor", "v2", "anchor-discriminator", "cpi-context"] } -light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] } +light-account = { workspace = true } light-macros = { workspace = true, features = ["solana"] } light-sdk-macros = { workspace = true } borsh = { workspace = true } diff --git a/sdk-tests/manual-test/src/account_loader/accounts.rs b/sdk-tests/manual-test/src/account_loader/accounts.rs index cbf1ec7edc..1d8c3662ec 100644 --- a/sdk-tests/manual-test/src/account_loader/accounts.rs +++ b/sdk-tests/manual-test/src/account_loader/accounts.rs @@ -1,7 +1,7 @@ //! Accounts module for zero-copy account instruction. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; +use light_account::CreateAccountsProof; use super::state::ZeroCopyRecord; diff --git a/sdk-tests/manual-test/src/account_loader/derived_accounts.rs b/sdk-tests/manual-test/src/account_loader/derived_accounts.rs index 498f5a7489..dce41ea610 100644 --- a/sdk-tests/manual-test/src/account_loader/derived_accounts.rs +++ b/sdk-tests/manual-test/src/account_loader/derived_accounts.rs @@ -7,16 +7,12 @@ use anchor_lang::prelude::*; use light_compressed_account::instruction_data::{ cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, }; -use light_sdk::{ - cpi::{v2::CpiAccounts, CpiAccountsConfig, InvokeLightSystemProgram}, - error::LightSdkError, - instruction::PackedAddressTreeInfoExt, - interface::{ - prepare_compressed_account_on_init, LightAccount, LightAccountVariantTrait, LightFinalize, - LightPreInit, PackedLightAccountVariantTrait, - }, +use light_account::{ light_account_checks::{self, packed_accounts::ProgramPackedAccounts}, - sdk_types::CpiContextWriteAccounts, + prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, + CpiContextWriteAccounts, InvokeLightSystemProgram, LightAccount, LightAccountVariantTrait, + LightFinalize, LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, + PackedLightAccountVariantTrait, }; use super::{ @@ -46,17 +42,16 @@ impl<'info> LightPreInit, CreateZeroCopyParams> for CreateZer &mut self, remaining_accounts: &[AccountInfo<'info>], params: &CreateZeroCopyParams, - ) -> std::result::Result { - let inner = || -> std::result::Result { - use light_sdk::interface::{LightAccount, LightConfig}; + ) -> std::result::Result { + let inner = || -> std::result::Result { + use light_account::{LightAccount, LightConfig}; use solana_program::{clock::Clock, sysvar::Sysvar}; - use solana_program_error::ProgramError; // 1. Build CPI accounts (slice remaining_accounts at system_accounts_offset) let system_accounts_offset = params.create_accounts_proof.system_accounts_offset as usize; if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkError::FewerAccountsThanSystemAccounts); + return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); } let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); let cpi_accounts = CpiAccounts::new_with_config( @@ -69,7 +64,7 @@ impl<'info> LightPreInit, CreateZeroCopyParams> for CreateZer let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; let address_tree_pubkey = address_tree_info .get_tree_pubkey(&cpi_accounts) - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; let output_tree_index = params.create_accounts_proof.output_state_tree_index; let current_account_index: u8 = 0; // Is true if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. @@ -87,9 +82,9 @@ impl<'info> LightPreInit, CreateZeroCopyParams> for CreateZer // 3. Load config and get current slot let light_config = LightConfig::load_checked(&self.compression_config, &crate::ID.to_bytes()) - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; let current_slot = Clock::get() - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))? + .map_err(|_| LightSdkTypesError::InvalidInstructionData)? .slot; // 4. Prepare compressed account using helper function @@ -112,7 +107,7 @@ impl<'info> LightPreInit, CreateZeroCopyParams> for CreateZer let mut record = self .record .load_init() - .map_err(|_| LightSdkError::from(ProgramError::AccountBorrowFailed))?; + .map_err(|_| LightSdkTypesError::Borsh)?; record.set_decompressed(&light_config, current_slot); } @@ -134,25 +129,22 @@ impl<'info> LightPreInit, CreateZeroCopyParams> for CreateZer }; if !WITH_CPI_CONTEXT { // 7. Invoke Light System Program CPI - instruction_data - .invoke(cpi_accounts) - .map_err(LightSdkError::from)?; + instruction_data.invoke(cpi_accounts)?; } else { // For flows that combine light mints with light PDAs, write to CPI context first. let cpi_context_accounts = CpiContextWriteAccounts { fee_payer: cpi_accounts.fee_payer(), - authority: cpi_accounts.authority().map_err(LightSdkError::from)?, - cpi_context: cpi_accounts.cpi_context().map_err(LightSdkError::from)?, + authority: cpi_accounts.authority()?, + cpi_context: cpi_accounts.cpi_context()?, cpi_signer: crate::LIGHT_CPI_SIGNER, }; instruction_data - .invoke_write_to_cpi_context_first(cpi_context_accounts) - .map_err(LightSdkError::from)?; + .invoke_write_to_cpi_context_first(cpi_context_accounts)?; } Ok(false) // No mints, so no CPI context write }; - inner().map_err(Into::into) + inner() } } @@ -166,7 +158,7 @@ impl<'info> LightFinalize, CreateZeroCopyParams> for CreateZe _remaining_accounts: &[AccountInfo<'info>], _params: &CreateZeroCopyParams, _has_pre_init: bool, - ) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { + ) -> std::result::Result<(), LightSdkTypesError> { // No-op for PDA-only flow - compression CPI already executed in light_pre_init Ok(()) } @@ -256,7 +248,7 @@ impl LightAccountVariantTrait<4> for ZeroCopyRecordVariant { impl PackedLightAccountVariantTrait<4> for PackedZeroCopyRecordVariant { type Unpacked = ZeroCopyRecordVariant; - const ACCOUNT_TYPE: light_sdk::interface::AccountType = + const ACCOUNT_TYPE: light_account::AccountType = ::ACCOUNT_TYPE; fn bump(&self) -> u8 { @@ -266,15 +258,15 @@ impl PackedLightAccountVariantTrait<4> for PackedZeroCopyRecordVariant { fn unpack( &self, accounts: &[AI], - ) -> std::result::Result { + ) -> std::result::Result { let owner = accounts .get(self.seeds.owner_idx as usize) - .ok_or(light_sdk::interface::error::LightPdaError::NotEnoughAccountKeys)?; + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)?; // Build ProgramPackedAccounts for LightAccount::unpack let packed_accounts = ProgramPackedAccounts { accounts }; let data = ZeroCopyRecord::unpack(&self.data, &packed_accounts) - .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; Ok(ZeroCopyRecordVariant { seeds: ZeroCopyRecordSeeds { @@ -289,26 +281,26 @@ impl PackedLightAccountVariantTrait<4> for PackedZeroCopyRecordVariant { &'a self, _accounts: &'a [AI], _bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; 4], light_sdk::interface::error::LightPdaError> { - Err(light_sdk::interface::error::LightPdaError::InvalidSeeds) + ) -> std::result::Result<[&'a [u8]; 4], LightSdkTypesError> { + Err(LightSdkTypesError::InvalidSeeds) } fn into_in_token_data( &self, - _tree_info: &light_sdk::instruction::PackedStateTreeInfo, + _tree_info: &light_account::PackedStateTreeInfo, _output_queue_index: u8, ) -> std::result::Result< light_token_interface::instructions::transfer2::MultiInputTokenDataWithContext, - light_sdk::interface::error::LightPdaError, + LightSdkTypesError, > { - Err(light_sdk::interface::error::LightPdaError::InvalidInstructionData) + Err(LightSdkTypesError::InvalidInstructionData) } fn into_in_tlv( &self, ) -> std::result::Result< Option>, - light_sdk::interface::error::LightPdaError, + LightSdkTypesError, > { Ok(None) } @@ -321,20 +313,20 @@ impl PackedLightAccountVariantTrait<4> for PackedZeroCopyRecordVariant { /// Implement IntoVariant to allow building variant from seeds + compressed data. /// This enables the high-level `create_load_instructions` API. #[cfg(not(target_os = "solana"))] -impl light_sdk::interface::IntoVariant for ZeroCopyRecordSeeds { +impl light_account::IntoVariant for ZeroCopyRecordSeeds { fn into_variant( self, data: &[u8], - ) -> std::result::Result + ) -> std::result::Result { // For ZeroCopy (Pod) accounts, data is the full Pod bytes including compression_info. // We deserialize using AnchorDeserialize (which ZeroCopyRecord implements). let record: ZeroCopyRecord = AnchorDeserialize::deserialize(&mut &data[..]) - .map_err(|_| light_sdk::interface::error::LightPdaError::Borsh)?; + .map_err(|_| LightSdkTypesError::Borsh)?; // Verify the owner in data matches the seed if Pubkey::new_from_array(record.owner) != self.owner { - return Err(light_sdk::interface::error::LightPdaError::InvalidSeeds); + return Err(LightSdkTypesError::InvalidSeeds); } Ok(ZeroCopyRecordVariant { @@ -351,21 +343,21 @@ impl light_sdk::interface::IntoVariant for ZeroCopyRecord /// Implement Pack trait to allow ZeroCopyRecordVariant to be used with `create_load_instructions`. /// Transforms the variant into PackedLightAccountVariant for efficient serialization. #[cfg(not(target_os = "solana"))] -impl light_sdk::interface::Pack +impl light_account::Pack for ZeroCopyRecordVariant { type Packed = crate::derived_variants::PackedLightAccountVariant; fn pack( &self, - accounts: &mut light_sdk::instruction::PackedAccounts, - ) -> std::result::Result { - use light_sdk::interface::LightAccountVariantTrait; + accounts: &mut light_account::PackedAccounts, + ) -> std::result::Result { + use light_account::LightAccountVariantTrait; let (_, bump) = self.derive_pda::(); let packed_data = self .data .pack(accounts) - .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; Ok( crate::derived_variants::PackedLightAccountVariant::ZeroCopyRecord { seeds: PackedZeroCopyRecordSeeds { diff --git a/sdk-tests/manual-test/src/account_loader/derived_state.rs b/sdk-tests/manual-test/src/account_loader/derived_state.rs index 9f1b6964fa..24e8f6c9df 100644 --- a/sdk-tests/manual-test/src/account_loader/derived_state.rs +++ b/sdk-tests/manual-test/src/account_loader/derived_state.rs @@ -4,12 +4,13 @@ //! but for a Pod/zero-copy account type. use anchor_lang::prelude::*; -use light_sdk::{ - compressible::CompressionInfo, - interface::{AccountType, HasCompressionInfo, LightAccount, LightConfig}, - light_account_checks::{packed_accounts::ProgramPackedAccounts, AccountInfoTrait, AccountMetaTrait}, +use light_account::{ + light_account_checks::{ + packed_accounts::ProgramPackedAccounts, AccountInfoTrait, AccountMetaTrait, + }, + AccountType, CompressionInfo, HasCompressionInfo, LightAccount, LightConfig, + LightSdkTypesError, }; -use light_sdk::interface::error::LightPdaError; use super::state::ZeroCopyRecord; @@ -54,8 +55,8 @@ impl LightAccount for ZeroCopyRecord { #[cfg(not(target_os = "solana"))] fn pack( &self, - accounts: &mut light_sdk::interface::instruction::PackedAccounts, - ) -> std::result::Result { + accounts: &mut light_account::interface::instruction::PackedAccounts, + ) -> std::result::Result { // compression_info excluded from packed struct (same as Borsh accounts) Ok(PackedZeroCopyRecord { owner: accounts.insert_or_get(AM::pubkey_from_bytes(self.owner)), @@ -66,11 +67,11 @@ impl LightAccount for ZeroCopyRecord { fn unpack( packed: &Self::Packed, accounts: &ProgramPackedAccounts, - ) -> std::result::Result { + ) -> std::result::Result { // Use get_u8 with a descriptive name for better error messages let owner_account = accounts .get_u8(packed.owner, "ZeroCopyRecord: owner") - .map_err(|_| LightPdaError::InvalidInstructionData)?; + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; // Set compression_info to compressed() for hash verification at decompress // (Same pattern as Borsh accounts - canonical compressed state for hashing) @@ -84,11 +85,13 @@ impl LightAccount for ZeroCopyRecord { } impl HasCompressionInfo for ZeroCopyRecord { - fn compression_info(&self) -> std::result::Result<&CompressionInfo, LightPdaError> { + fn compression_info(&self) -> std::result::Result<&CompressionInfo, LightSdkTypesError> { Ok(&self.compression_info) } - fn compression_info_mut(&mut self) -> std::result::Result<&mut CompressionInfo, LightPdaError> { + fn compression_info_mut( + &mut self, + ) -> std::result::Result<&mut CompressionInfo, LightSdkTypesError> { Ok(&mut self.compression_info) } @@ -96,7 +99,7 @@ impl HasCompressionInfo for ZeroCopyRecord { panic!("compression_info_mut_opt not supported for LightAccount types (use compression_info_mut instead)") } - fn set_compression_info_none(&mut self) -> std::result::Result<(), LightPdaError> { + fn set_compression_info_none(&mut self) -> std::result::Result<(), LightSdkTypesError> { self.compression_info = CompressionInfo::compressed(); Ok(()) } diff --git a/sdk-tests/manual-test/src/account_loader/state.rs b/sdk-tests/manual-test/src/account_loader/state.rs index a5e90e08c2..a74adf2d0c 100644 --- a/sdk-tests/manual-test/src/account_loader/state.rs +++ b/sdk-tests/manual-test/src/account_loader/state.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; use borsh::{BorshDeserialize, BorshSerialize}; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator, LightHasherSha}; +use light_account::{CompressionInfo, LightDiscriminator, LightHasherSha}; /// Zero-copy account for demonstrating AccountLoader integration. /// diff --git a/sdk-tests/manual-test/src/all/accounts.rs b/sdk-tests/manual-test/src/all/accounts.rs index 5154413813..a72ab92c7c 100644 --- a/sdk-tests/manual-test/src/all/accounts.rs +++ b/sdk-tests/manual-test/src/all/accounts.rs @@ -1,7 +1,7 @@ //! Accounts module for create_all instruction. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; +use light_account::CreateAccountsProof; use solana_account_info::AccountInfo; use crate::{account_loader::ZeroCopyRecord, pda::MinimalRecord}; diff --git a/sdk-tests/manual-test/src/all/derived.rs b/sdk-tests/manual-test/src/all/derived.rs index 44bef31934..5895791886 100644 --- a/sdk-tests/manual-test/src/all/derived.rs +++ b/sdk-tests/manual-test/src/all/derived.rs @@ -10,12 +10,10 @@ use anchor_lang::prelude::*; use light_compressed_account::instruction_data::{ cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, }; -use light_sdk::{ - cpi::{v2::CpiAccounts, CpiAccountsConfig, InvokeLightSystemProgram}, - error::LightSdkError, - instruction::PackedAddressTreeInfoExt, - interface::{prepare_compressed_account_on_init, LightAccount, LightFinalize, LightPreInit}, - sdk_types::CpiContextWriteAccounts, +use light_account::{ + prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, + CpiContextWriteAccounts, InvokeLightSystemProgram, LightAccount, LightFinalize, + LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, }; use light_token::{ compressible::{invoke_create_mints, CreateMintsInfraAccounts}, @@ -26,7 +24,6 @@ use light_token::{ }, }; use solana_account_info::AccountInfo; -use solana_program_error::ProgramError; use super::accounts::{ CreateAllAccounts, CreateAllParams, ALL_MINT_SIGNER_SEED, ALL_TOKEN_VAULT_SEED, @@ -41,9 +38,9 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou &mut self, remaining_accounts: &[AccountInfo<'info>], params: &CreateAllParams, - ) -> std::result::Result { - let mut inner = || -> std::result::Result { - use light_sdk::interface::LightConfig; + ) -> std::result::Result { + let mut inner = || -> std::result::Result { + use light_account::LightConfig; use solana_program::{clock::Clock, sysvar::Sysvar}; // Constants for this instruction @@ -57,7 +54,7 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou let system_accounts_offset = params.create_accounts_proof.system_accounts_offset as usize; if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkError::FewerAccountsThanSystemAccounts); + return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); } let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); let cpi_accounts = CpiAccounts::new_with_config( @@ -72,16 +69,17 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; let address_tree_pubkey = address_tree_info .get_tree_pubkey(&cpi_accounts) - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; let output_tree_index = params.create_accounts_proof.output_state_tree_index; // ==================================================================== // 3. Load config, get current slot // ==================================================================== - let light_config = LightConfig::load_checked(&self.compression_config, &crate::ID.to_bytes()) - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; + let light_config = + LightConfig::load_checked(&self.compression_config, &crate::ID.to_bytes()) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; let current_slot = Clock::get() - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))? + .map_err(|_| LightSdkTypesError::InvalidInstructionData)? .slot; // ==================================================================== @@ -124,7 +122,7 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou let mut record = self .zero_copy_record .load_init() - .map_err(|_| LightSdkError::from(ProgramError::AccountBorrowFailed))?; + .map_err(|_| LightSdkTypesError::Borsh)?; record.set_decompressed(&light_config, current_slot); } @@ -148,13 +146,12 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou // Write to CPI context first (combined execution happens with mints) let cpi_context_accounts = CpiContextWriteAccounts { fee_payer: cpi_accounts.fee_payer(), - authority: cpi_accounts.authority().map_err(LightSdkError::from)?, - cpi_context: cpi_accounts.cpi_context().map_err(LightSdkError::from)?, + authority: cpi_accounts.authority()?, + cpi_context: cpi_accounts.cpi_context()?, cpi_signer: crate::LIGHT_CPI_SIGNER, }; instruction_data - .invoke_write_to_cpi_context_first(cpi_context_accounts) - .map_err(LightSdkError::from)?; + .invoke_write_to_cpi_context_first(cpi_context_accounts)?; } // ==================================================================== @@ -165,10 +162,9 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou let mint_signer_key = self.mint_signer.key(); // Derive mint PDA - let (mint_pda, mint_bump) = - find_mint_address(&solana_pubkey::Pubkey::new_from_array( - mint_signer_key.to_bytes(), - )); + let (mint_pda, mint_bump) = find_mint_address( + &solana_pubkey::Pubkey::new_from_array(mint_signer_key.to_bytes()), + ); // Derive compression address let compression_address = derive_mint_compressed_address( @@ -204,13 +200,13 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou let state_tree_index = params .create_accounts_proof .state_tree_index - .ok_or(LightSdkError::from(ProgramError::InvalidArgument))?; + .ok_or(LightSdkTypesError::InvalidInstructionData)?; let proof = params .create_accounts_proof .proof .0 - .ok_or(LightSdkError::from(ProgramError::InvalidArgument))?; + .ok_or(LightSdkTypesError::InvalidInstructionData)?; // Build SDK params with cpi_context_offset let sdk_params = SdkCreateMintsParams::new(&sdk_mints, proof) @@ -238,7 +234,8 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou sdk_params, infra, &cpi_accounts, - )?; + ) + .map_err(|_| LightSdkTypesError::CpiFailed)?; } // ==================================================================== @@ -264,7 +261,8 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou self.system_program.to_account_info(), &crate::ID, ) - .invoke_signed(vault_seeds)?; + .invoke_signed(vault_seeds) + .map_err(|_| LightSdkTypesError::CpiFailed)?; } // ==================================================================== @@ -288,12 +286,13 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou self.rent_sponsor.clone(), self.system_program.to_account_info(), ) - .invoke()?; + .invoke() + .map_err(|_| LightSdkTypesError::CpiFailed)?; } Ok(WITH_CPI_CONTEXT) }; - inner().map_err(Into::into) + inner() } } @@ -307,7 +306,7 @@ impl<'info> LightFinalize, CreateAllParams> for CreateAllAcco _remaining_accounts: &[AccountInfo<'info>], _params: &CreateAllParams, _has_pre_init: bool, - ) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { + ) -> std::result::Result<(), LightSdkTypesError> { // All accounts were created in light_pre_init Ok(()) } diff --git a/sdk-tests/manual-test/src/all/derived_accounts.rs b/sdk-tests/manual-test/src/all/derived_accounts.rs index 717e0623f4..2b5a30ba85 100644 --- a/sdk-tests/manual-test/src/all/derived_accounts.rs +++ b/sdk-tests/manual-test/src/all/derived_accounts.rs @@ -2,9 +2,9 @@ //! Uses different seeds than pda/account_loader modules but reuses the data types. use anchor_lang::prelude::*; -use light_sdk::{ - interface::{LightAccount, LightAccountVariantTrait, PackedLightAccountVariantTrait}, +use light_account::{ light_account_checks::{self, packed_accounts::ProgramPackedAccounts}, + LightAccount, LightAccountVariantTrait, PackedLightAccountVariantTrait, }; use super::accounts::{ALL_BORSH_SEED, ALL_ZERO_COPY_SEED}; @@ -87,7 +87,7 @@ impl LightAccountVariantTrait<3> for AllBorshVariant { impl PackedLightAccountVariantTrait<3> for PackedAllBorshVariant { type Unpacked = AllBorshVariant; - const ACCOUNT_TYPE: light_sdk::interface::AccountType = + const ACCOUNT_TYPE: light_account::AccountType = ::ACCOUNT_TYPE; fn bump(&self) -> u8 { @@ -97,15 +97,15 @@ impl PackedLightAccountVariantTrait<3> for PackedAllBorshVariant { fn unpack( &self, accounts: &[AI], - ) -> std::result::Result { + ) -> std::result::Result { let owner = accounts .get(self.seeds.owner_idx as usize) - .ok_or(light_sdk::interface::error::LightPdaError::NotEnoughAccountKeys)?; + .ok_or(light_account::LightSdkTypesError::NotEnoughAccountKeys)?; // Build ProgramPackedAccounts for LightAccount::unpack let packed_accounts = ProgramPackedAccounts { accounts }; let data = MinimalRecord::unpack(&self.data, &packed_accounts) - .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; + .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)?; Ok(AllBorshVariant { seeds: AllBorshSeeds { @@ -119,21 +119,27 @@ impl PackedLightAccountVariantTrait<3> for PackedAllBorshVariant { &'a self, _accounts: &'a [AI], _bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; 3], light_sdk::interface::error::LightPdaError> { - Err(light_sdk::interface::error::LightPdaError::InvalidSeeds) + ) -> std::result::Result<[&'a [u8]; 3], light_account::LightSdkTypesError> { + Err(light_account::LightSdkTypesError::InvalidSeeds) } fn into_in_token_data( &self, - _tree_info: &light_sdk::instruction::PackedStateTreeInfo, + _tree_info: &light_account::PackedStateTreeInfo, _output_queue_index: u8, - ) -> std::result::Result { - Err(light_sdk::interface::error::LightPdaError::InvalidInstructionData) + ) -> std::result::Result< + light_token_interface::instructions::transfer2::MultiInputTokenDataWithContext, + light_account::LightSdkTypesError, + > { + Err(light_account::LightSdkTypesError::InvalidInstructionData) } fn into_in_tlv( &self, - ) -> std::result::Result>, light_sdk::interface::error::LightPdaError> { + ) -> std::result::Result< + Option>, + light_account::LightSdkTypesError, + > { Ok(None) } } @@ -212,7 +218,7 @@ impl LightAccountVariantTrait<3> for AllZeroCopyVariant { impl PackedLightAccountVariantTrait<3> for PackedAllZeroCopyVariant { type Unpacked = AllZeroCopyVariant; - const ACCOUNT_TYPE: light_sdk::interface::AccountType = + const ACCOUNT_TYPE: light_account::AccountType = ::ACCOUNT_TYPE; fn bump(&self) -> u8 { @@ -222,15 +228,15 @@ impl PackedLightAccountVariantTrait<3> for PackedAllZeroCopyVariant { fn unpack( &self, accounts: &[AI], - ) -> std::result::Result { + ) -> std::result::Result { let owner = accounts .get(self.seeds.owner_idx as usize) - .ok_or(light_sdk::interface::error::LightPdaError::NotEnoughAccountKeys)?; + .ok_or(light_account::LightSdkTypesError::NotEnoughAccountKeys)?; // Build ProgramPackedAccounts for LightAccount::unpack let packed_accounts = ProgramPackedAccounts { accounts }; let data = ZeroCopyRecord::unpack(&self.data, &packed_accounts) - .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; + .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)?; Ok(AllZeroCopyVariant { seeds: AllZeroCopySeeds { @@ -244,21 +250,27 @@ impl PackedLightAccountVariantTrait<3> for PackedAllZeroCopyVariant { &'a self, _accounts: &'a [AI], _bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; 3], light_sdk::interface::error::LightPdaError> { - Err(light_sdk::interface::error::LightPdaError::InvalidSeeds) + ) -> std::result::Result<[&'a [u8]; 3], light_account::LightSdkTypesError> { + Err(light_account::LightSdkTypesError::InvalidSeeds) } fn into_in_token_data( &self, - _tree_info: &light_sdk::instruction::PackedStateTreeInfo, + _tree_info: &light_account::PackedStateTreeInfo, _output_queue_index: u8, - ) -> std::result::Result { - Err(light_sdk::interface::error::LightPdaError::InvalidInstructionData) + ) -> std::result::Result< + light_token_interface::instructions::transfer2::MultiInputTokenDataWithContext, + light_account::LightSdkTypesError, + > { + Err(light_account::LightSdkTypesError::InvalidInstructionData) } fn into_in_tlv( &self, - ) -> std::result::Result>, light_sdk::interface::error::LightPdaError> { + ) -> std::result::Result< + Option>, + light_account::LightSdkTypesError, + > { Ok(None) } } @@ -270,18 +282,18 @@ impl PackedLightAccountVariantTrait<3> for PackedAllZeroCopyVariant { /// Implement IntoVariant to allow building variant from seeds + compressed data. /// This enables the high-level `create_load_instructions` API. #[cfg(not(target_os = "solana"))] -impl light_sdk::interface::IntoVariant for AllBorshSeeds { +impl light_account::IntoVariant for AllBorshSeeds { fn into_variant( self, data: &[u8], - ) -> std::result::Result { + ) -> std::result::Result { // Deserialize the compressed data (which includes compression_info) let record: MinimalRecord = AnchorDeserialize::deserialize(&mut &data[..]) - .map_err(|_| light_sdk::interface::error::LightPdaError::Borsh)?; + .map_err(|_| light_account::LightSdkTypesError::Borsh)?; // Verify the owner in data matches the seed if record.owner != self.owner { - return Err(light_sdk::interface::error::LightPdaError::InvalidSeeds); + return Err(light_account::LightSdkTypesError::InvalidSeeds); } Ok(AllBorshVariant { @@ -298,19 +310,19 @@ impl light_sdk::interface::IntoVariant for AllBorshSeeds { /// Implement Pack trait to allow AllBorshVariant to be used with `create_load_instructions`. /// Transforms the variant into PackedLightAccountVariant for efficient serialization. #[cfg(not(target_os = "solana"))] -impl light_sdk::interface::Pack for AllBorshVariant { +impl light_account::Pack for AllBorshVariant { type Packed = crate::derived_variants::PackedLightAccountVariant; fn pack( &self, - accounts: &mut light_sdk::instruction::PackedAccounts, - ) -> std::result::Result { - use light_sdk::interface::LightAccountVariantTrait; + accounts: &mut light_account::PackedAccounts, + ) -> std::result::Result { + use light_account::LightAccountVariantTrait; let (_, bump) = self.derive_pda::(); let packed_data = self .data .pack(accounts) - .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; + .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)?; Ok( crate::derived_variants::PackedLightAccountVariant::AllBorsh { seeds: PackedAllBorshSeeds { @@ -330,19 +342,19 @@ impl light_sdk::interface::Pack for Al /// Implement IntoVariant to allow building variant from seeds + compressed data. /// This enables the high-level `create_load_instructions` API. #[cfg(not(target_os = "solana"))] -impl light_sdk::interface::IntoVariant for AllZeroCopySeeds { +impl light_account::IntoVariant for AllZeroCopySeeds { fn into_variant( self, data: &[u8], - ) -> std::result::Result { + ) -> std::result::Result { // For ZeroCopy (Pod) accounts, data is the full Pod bytes including compression_info. // We deserialize using AnchorDeserialize (which ZeroCopyRecord implements). let record: ZeroCopyRecord = AnchorDeserialize::deserialize(&mut &data[..]) - .map_err(|_| light_sdk::interface::error::LightPdaError::Borsh)?; + .map_err(|_| light_account::LightSdkTypesError::Borsh)?; // Verify the owner in data matches the seed if Pubkey::new_from_array(record.owner) != self.owner { - return Err(light_sdk::interface::error::LightPdaError::InvalidSeeds); + return Err(light_account::LightSdkTypesError::InvalidSeeds); } Ok(AllZeroCopyVariant { @@ -359,19 +371,19 @@ impl light_sdk::interface::IntoVariant for AllZeroCopySeeds /// Implement Pack trait to allow AllZeroCopyVariant to be used with `create_load_instructions`. /// Transforms the variant into PackedLightAccountVariant for efficient serialization. #[cfg(not(target_os = "solana"))] -impl light_sdk::interface::Pack for AllZeroCopyVariant { +impl light_account::Pack for AllZeroCopyVariant { type Packed = crate::derived_variants::PackedLightAccountVariant; fn pack( &self, - accounts: &mut light_sdk::instruction::PackedAccounts, - ) -> std::result::Result { - use light_sdk::interface::LightAccountVariantTrait; + accounts: &mut light_account::PackedAccounts, + ) -> std::result::Result { + use light_account::LightAccountVariantTrait; let (_, bump) = self.derive_pda::(); let packed_data = self .data .pack(accounts) - .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; + .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)?; Ok( crate::derived_variants::PackedLightAccountVariant::AllZeroCopy { seeds: PackedAllZeroCopySeeds { diff --git a/sdk-tests/manual-test/src/ata/derived.rs b/sdk-tests/manual-test/src/ata/derived.rs index e4e27aa599..7bfe357afc 100644 --- a/sdk-tests/manual-test/src/ata/derived.rs +++ b/sdk-tests/manual-test/src/ata/derived.rs @@ -1,10 +1,7 @@ //! Derived code - what the macro would generate for associated token accounts. use anchor_lang::prelude::*; -use light_sdk::{ - error::LightSdkError, - interface::{LightFinalize, LightPreInit}, -}; +use light_account::{LightFinalize, LightPreInit, LightSdkTypesError}; use light_token::instruction::CreateTokenAtaCpi; use solana_account_info::AccountInfo; @@ -19,8 +16,8 @@ impl<'info> LightPreInit, CreateAtaParams> for CreateAtaAccou &mut self, _remaining_accounts: &[AccountInfo<'info>], _params: &CreateAtaParams, - ) -> std::result::Result { - let inner = || -> std::result::Result { + ) -> std::result::Result { + let inner = || -> std::result::Result { // Derive the ATA bump on-chain let (_, bump) = light_token::instruction::derive_associated_token_account( self.ata_owner.key, @@ -43,12 +40,12 @@ impl<'info> LightPreInit, CreateAtaParams> for CreateAtaAccou self.rent_sponsor.clone(), self.system_program.to_account_info(), ) - .invoke()?; + .invoke().map_err(|_| LightSdkTypesError::CpiFailed)?; // ATAs don't use CPI context, return false Ok(false) }; - inner().map_err(Into::into) + inner() } } @@ -62,7 +59,7 @@ impl<'info> LightFinalize, CreateAtaParams> for CreateAtaAcco _remaining_accounts: &[AccountInfo<'info>], _params: &CreateAtaParams, _has_pre_init: bool, - ) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { + ) -> std::result::Result<(), LightSdkTypesError> { Ok(()) } } diff --git a/sdk-tests/manual-test/src/derived_compress.rs b/sdk-tests/manual-test/src/derived_compress.rs index 86b157fb0b..495adfa480 100644 --- a/sdk-tests/manual-test/src/derived_compress.rs +++ b/sdk-tests/manual-test/src/derived_compress.rs @@ -6,14 +6,11 @@ use std::marker::PhantomData; use anchor_lang::prelude::*; -use light_sdk::{ - interface::{ - error::LightPdaError, prepare_account_for_compression, - process_compress_pda_accounts_idempotent, CompressCtx, - }, - LightDiscriminator, +use light_account::{ + account_meta::CompressedAccountMetaNoLamportsNoAddress, + prepare_account_for_compression, process_compress_pda_accounts_idempotent, CompressCtx, + LightDiscriminator, LightSdkTypesError, }; -use light_sdk_types::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress; use solana_account_info::AccountInfo; use crate::{account_loader::ZeroCopyRecord, pda::MinimalRecord}; @@ -129,20 +126,22 @@ fn compress_dispatch<'info>( account_info: &AccountInfo<'info>, meta: &CompressedAccountMetaNoLamportsNoAddress, index: usize, - ctx: &mut CompressCtx<'_, AccountInfo<'info>>, -) -> std::result::Result<(), LightPdaError> { - let data = account_info.try_borrow_data().map_err(|_| LightPdaError::Borsh)?; + ctx: &mut CompressCtx<'_, 'info>, +) -> std::result::Result<(), LightSdkTypesError> { + let data = account_info + .try_borrow_data() + .map_err(|_| LightSdkTypesError::Borsh)?; // Read discriminator from first 8 bytes let discriminator: [u8; 8] = data[..8] .try_into() - .map_err(|_| LightPdaError::InvalidInstructionData)?; + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; match discriminator { d if d == MinimalRecord::LIGHT_DISCRIMINATOR => { // Borsh path: deserialize using try_from_slice - let mut account_data = MinimalRecord::try_from_slice(&data[8..]) - .map_err(|_| LightPdaError::Borsh)?; + let mut account_data = + MinimalRecord::try_from_slice(&data[8..]).map_err(|_| LightSdkTypesError::Borsh)?; drop(data); // Call prepare with deserialized data @@ -176,5 +175,7 @@ pub fn process_compress_and_close<'info>( crate::LIGHT_CPI_SIGNER, &crate::ID.to_bytes(), ) - .map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::Custom(u32::from(e)))) + .map_err(|e| { + anchor_lang::error::Error::from(solana_program_error::ProgramError::Custom(u32::from(e))) + }) } diff --git a/sdk-tests/manual-test/src/derived_decompress.rs b/sdk-tests/manual-test/src/derived_decompress.rs index d26b040b14..96f65cf5b0 100644 --- a/sdk-tests/manual-test/src/derived_decompress.rs +++ b/sdk-tests/manual-test/src/derived_decompress.rs @@ -6,7 +6,7 @@ use std::marker::PhantomData; use anchor_lang::prelude::*; -use light_sdk::interface::process_decompress_pda_accounts_idempotent; +use light_account::process_decompress_pda_accounts_idempotent; use crate::derived_variants::PackedLightAccountVariant; diff --git a/sdk-tests/manual-test/src/derived_light_config.rs b/sdk-tests/manual-test/src/derived_light_config.rs index 2d5236dd11..3181865faa 100644 --- a/sdk-tests/manual-test/src/derived_light_config.rs +++ b/sdk-tests/manual-test/src/derived_light_config.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; use light_compressible::rent::RentConfig; -use light_sdk::interface::program::config::create::process_initialize_light_config; +use light_account::process_initialize_light_config; use solana_program_error::ProgramError; /// Params order matches SDK's InitializeCompressionConfigAnchorData. @@ -73,6 +73,6 @@ pub fn process_update_config<'info>( ctx.accounts.config.to_account_info(), ctx.accounts.authority.to_account_info(), ]; - light_sdk::interface::process_update_light_config(&remaining, &instruction_data, &crate::ID.to_bytes()) + light_account::process_update_light_config(&remaining, &instruction_data, &crate::ID.to_bytes()) .map_err(|e| anchor_lang::error::Error::from(ProgramError::Custom(u32::from(e)))) } diff --git a/sdk-tests/manual-test/src/derived_variants.rs b/sdk-tests/manual-test/src/derived_variants.rs index dee543aab9..31ff7253d4 100644 --- a/sdk-tests/manual-test/src/derived_variants.rs +++ b/sdk-tests/manual-test/src/derived_variants.rs @@ -3,10 +3,10 @@ //! This module contains the code that would be generated by the `#[light_program]` macro. use anchor_lang::prelude::*; -use light_sdk::interface::{ - error::LightPdaError, prepare_account_for_decompression, DecompressCtx, DecompressVariant, +use light_account::{ + prepare_account_for_decompression, DecompressCtx, DecompressVariant, LightSdkTypesError, + PackedStateTreeInfo, }; -use light_sdk_types::instruction::PackedStateTreeInfo; use solana_account_info::AccountInfo; use crate::{ @@ -82,8 +82,8 @@ impl<'info> DecompressVariant> for PackedLightAccountVariant &self, tree_info: &PackedStateTreeInfo, pda_account: &AccountInfo<'info>, - ctx: &mut DecompressCtx<'_, AccountInfo<'info>>, - ) -> std::result::Result<(), LightPdaError> { + ctx: &mut DecompressCtx<'_, 'info>, + ) -> std::result::Result<(), LightSdkTypesError> { let output_queue_index = ctx.output_queue_index; match self { PackedLightAccountVariant::MinimalRecord { seeds, data } => { @@ -104,7 +104,11 @@ impl<'info> DecompressVariant> for PackedLightAccountVariant seeds: seeds.clone(), data: data.clone(), }; - prepare_account_for_decompression::<4, PackedZeroCopyRecordVariant, AccountInfo<'info>>( + prepare_account_for_decompression::< + 4, + PackedZeroCopyRecordVariant, + AccountInfo<'info>, + >( &packed_data, tree_info, output_queue_index, diff --git a/sdk-tests/manual-test/src/lib.rs b/sdk-tests/manual-test/src/lib.rs index 64f66a01a0..bebb0a0206 100644 --- a/sdk-tests/manual-test/src/lib.rs +++ b/sdk-tests/manual-test/src/lib.rs @@ -9,11 +9,7 @@ #![allow(deprecated)] use anchor_lang::prelude::*; -use light_sdk::{ - derive_light_cpi_signer, - interface::{LightFinalize, LightPreInit}, -}; -use light_sdk_types::CpiSigner; +use light_account::{derive_light_cpi_signer, CpiSigner, LightFinalize, LightPreInit}; use solana_program_error::ProgramError; pub mod account_loader; @@ -41,7 +37,7 @@ pub use derived_compress::*; pub use derived_decompress::*; pub use derived_light_config::*; pub use derived_variants::{LightAccountVariant, PackedLightAccountVariant}; -pub use light_sdk::interface::{ +pub use light_account::{ AccountType, CompressAndCloseParams, DecompressIdempotentParams, DecompressVariant, LightAccount, }; diff --git a/sdk-tests/manual-test/src/pda/accounts.rs b/sdk-tests/manual-test/src/pda/accounts.rs index 62b05baa18..8acb913177 100644 --- a/sdk-tests/manual-test/src/pda/accounts.rs +++ b/sdk-tests/manual-test/src/pda/accounts.rs @@ -1,7 +1,7 @@ //! Accounts module for single-pda-test. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::pda::MinimalRecord; diff --git a/sdk-tests/manual-test/src/pda/derived_accounts.rs b/sdk-tests/manual-test/src/pda/derived_accounts.rs index cc359dafa2..1049c8a74c 100644 --- a/sdk-tests/manual-test/src/pda/derived_accounts.rs +++ b/sdk-tests/manual-test/src/pda/derived_accounts.rs @@ -2,16 +2,12 @@ use anchor_lang::prelude::*; use light_compressed_account::instruction_data::{ cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, }; -use light_sdk::{ - cpi::{v2::CpiAccounts, CpiAccountsConfig, InvokeLightSystemProgram}, - error::LightSdkError, - instruction::PackedAddressTreeInfoExt, - interface::{ - prepare_compressed_account_on_init, LightAccount, LightAccountVariantTrait, LightFinalize, - LightPreInit, PackedLightAccountVariantTrait, - }, +use light_account::{ light_account_checks::{self, packed_accounts::ProgramPackedAccounts}, - sdk_types::CpiContextWriteAccounts, + prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, + CpiContextWriteAccounts, InvokeLightSystemProgram, LightAccount, LightAccountVariantTrait, + LightFinalize, LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, + PackedLightAccountVariantTrait, }; use super::{ @@ -42,17 +38,16 @@ impl<'info> LightPreInit, CreatePdaParams> for CreatePda<'inf &mut self, remaining_accounts: &[AccountInfo<'info>], params: &CreatePdaParams, - ) -> std::result::Result { - let mut inner = || -> std::result::Result { - use light_sdk::interface::{LightConfig, LightAccount}; + ) -> std::result::Result { + let mut inner = || -> std::result::Result { + use light_account::{LightAccount, LightConfig}; use solana_program::{clock::Clock, sysvar::Sysvar}; - use solana_program_error::ProgramError; // 1. Build CPI accounts (slice remaining_accounts at system_accounts_offset) let system_accounts_offset = params.create_accounts_proof.system_accounts_offset as usize; if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkError::FewerAccountsThanSystemAccounts); + return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); } let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); let cpi_accounts = CpiAccounts::new_with_config( @@ -65,7 +60,7 @@ impl<'info> LightPreInit, CreatePdaParams> for CreatePda<'inf let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; let address_tree_pubkey = address_tree_info .get_tree_pubkey(&cpi_accounts) - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; let output_tree_index = params.create_accounts_proof.output_state_tree_index; let current_account_index: u8 = 0; // Is true if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. @@ -74,10 +69,11 @@ impl<'info> LightPreInit, CreatePdaParams> for CreatePda<'inf const NUM_LIGHT_PDAS: usize = 1; // 6. Set compression_info from config - let light_config = LightConfig::load_checked(&self.compression_config, &crate::ID.to_bytes()) - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; + let light_config = + LightConfig::load_checked(&self.compression_config, &crate::ID.to_bytes()) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; let current_slot = Clock::get() - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))? + .map_err(|_| LightSdkTypesError::InvalidInstructionData)? .slot; // Dynamic derived light pda specific. Only exists if NUM_LIGHT_PDAS > 0 // ===================================================================== @@ -130,26 +126,24 @@ impl<'info> LightPreInit, CreatePdaParams> for CreatePda<'inf if !WITH_CPI_CONTEXT { // 5. Invoke Light System Program CPI instruction_data - .invoke(cpi_accounts) - .map_err(LightSdkError::from)?; + .invoke(cpi_accounts)?; } else { // For flows that combine light mints with light PDAs, write to CPI context first. // The authority and cpi_context accounts must be provided in remaining_accounts. let cpi_context_accounts = CpiContextWriteAccounts { fee_payer: cpi_accounts.fee_payer(), - authority: cpi_accounts.authority().map_err(LightSdkError::from)?, - cpi_context: cpi_accounts.cpi_context().map_err(LightSdkError::from)?, + authority: cpi_accounts.authority()?, + cpi_context: cpi_accounts.cpi_context()?, cpi_signer: crate::LIGHT_CPI_SIGNER, }; instruction_data - .invoke_write_to_cpi_context_first(cpi_context_accounts) - .map_err(LightSdkError::from)?; + .invoke_write_to_cpi_context_first(cpi_context_accounts)?; } } // ===================================================================== Ok(false) // No mints, so no CPI context write }; - inner().map_err(Into::into) + inner() } } @@ -163,7 +157,7 @@ impl<'info> LightFinalize, CreatePdaParams> for CreatePda<'in _remaining_accounts: &[AccountInfo<'info>], _params: &CreatePdaParams, _has_pre_init: bool, - ) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { + ) -> std::result::Result<(), LightSdkTypesError> { // No-op for PDA-only flow - compression CPI already executed in light_pre_init Ok(()) } @@ -251,7 +245,7 @@ impl LightAccountVariantTrait<4> for MinimalRecordVariant { impl PackedLightAccountVariantTrait<4> for PackedMinimalRecordVariant { type Unpacked = MinimalRecordVariant; - const ACCOUNT_TYPE: light_sdk::interface::AccountType = + const ACCOUNT_TYPE: light_account::AccountType = ::ACCOUNT_TYPE; fn bump(&self) -> u8 { @@ -261,15 +255,15 @@ impl PackedLightAccountVariantTrait<4> for PackedMinimalRecordVariant { fn unpack( &self, accounts: &[AI], - ) -> std::result::Result { + ) -> std::result::Result { let owner = accounts .get(self.seeds.owner_idx as usize) - .ok_or(light_sdk::interface::error::LightPdaError::NotEnoughAccountKeys)?; + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)?; // Build ProgramPackedAccounts for LightAccount::unpack let packed_accounts = ProgramPackedAccounts { accounts }; let data = MinimalRecord::unpack(&self.data, &packed_accounts) - .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; Ok(MinimalRecordVariant { seeds: MinimalRecordSeeds { @@ -284,25 +278,31 @@ impl PackedLightAccountVariantTrait<4> for PackedMinimalRecordVariant { &'a self, _accounts: &'a [AI], _bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; 4], light_sdk::interface::error::LightPdaError> { + ) -> std::result::Result<[&'a [u8]; 4], LightSdkTypesError> { // PDA variants use seed_vec() in the decompression path, not seed_refs_with_bump. // Returning a reference to the account key requires a key_ref() method on // AccountInfoTrait, which is not yet available. Since this method is only // called for token account variants, PDA variants return an error. - Err(light_sdk::interface::error::LightPdaError::InvalidSeeds) + Err(LightSdkTypesError::InvalidSeeds) } fn into_in_token_data( &self, - _tree_info: &light_sdk::instruction::PackedStateTreeInfo, + _tree_info: &light_account::PackedStateTreeInfo, _output_queue_index: u8, - ) -> std::result::Result { - Err(light_sdk::interface::error::LightPdaError::InvalidInstructionData) + ) -> std::result::Result< + light_token_interface::instructions::transfer2::MultiInputTokenDataWithContext, + LightSdkTypesError, + > { + Err(LightSdkTypesError::InvalidInstructionData) } fn into_in_tlv( &self, - ) -> std::result::Result>, light_sdk::interface::error::LightPdaError> { + ) -> std::result::Result< + Option>, + LightSdkTypesError, + > { Ok(None) } } @@ -314,18 +314,18 @@ impl PackedLightAccountVariantTrait<4> for PackedMinimalRecordVariant { /// Implement IntoVariant to allow building variant from seeds + compressed data. /// This enables the high-level `create_load_instructions` API. #[cfg(not(target_os = "solana"))] -impl light_sdk::interface::IntoVariant for MinimalRecordSeeds { +impl light_account::IntoVariant for MinimalRecordSeeds { fn into_variant( self, data: &[u8], - ) -> std::result::Result { + ) -> std::result::Result { // Deserialize the compressed data (which includes compression_info) let record: MinimalRecord = AnchorDeserialize::deserialize(&mut &data[..]) - .map_err(|_| light_sdk::interface::error::LightPdaError::Borsh)?; + .map_err(|_| LightSdkTypesError::Borsh)?; // Verify the owner in data matches the seed if record.owner != self.owner { - return Err(light_sdk::interface::error::LightPdaError::InvalidSeeds); + return Err(LightSdkTypesError::InvalidSeeds); } Ok(MinimalRecordVariant { @@ -342,19 +342,19 @@ impl light_sdk::interface::IntoVariant for MinimalRecordSe /// Implement Pack trait to allow MinimalRecordVariant to be used with `create_load_instructions`. /// Transforms the variant into PackedLightAccountVariant for efficient serialization. #[cfg(not(target_os = "solana"))] -impl light_sdk::interface::Pack for MinimalRecordVariant { +impl light_account::Pack for MinimalRecordVariant { type Packed = crate::derived_variants::PackedLightAccountVariant; fn pack( &self, - accounts: &mut light_sdk::instruction::PackedAccounts, - ) -> std::result::Result { - use light_sdk::interface::LightAccountVariantTrait; + accounts: &mut light_account::PackedAccounts, + ) -> std::result::Result { + use light_account::LightAccountVariantTrait; let (_, bump) = self.derive_pda::(); let packed_data = self .data .pack(accounts) - .map_err(|_| light_sdk::interface::error::LightPdaError::InvalidInstructionData)?; + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; Ok( crate::derived_variants::PackedLightAccountVariant::MinimalRecord { seeds: PackedMinimalRecordSeeds { diff --git a/sdk-tests/manual-test/src/pda/derived_state.rs b/sdk-tests/manual-test/src/pda/derived_state.rs index 3d74fba30c..c1101cb3dd 100644 --- a/sdk-tests/manual-test/src/pda/derived_state.rs +++ b/sdk-tests/manual-test/src/pda/derived_state.rs @@ -1,10 +1,11 @@ use anchor_lang::prelude::*; -use light_sdk::{ - compressible::CompressionInfo, - interface::{AccountType, HasCompressionInfo, LightAccount, LightConfig}, - light_account_checks::{packed_accounts::ProgramPackedAccounts, AccountInfoTrait, AccountMetaTrait}, +use light_account::{ + light_account_checks::{ + packed_accounts::ProgramPackedAccounts, AccountInfoTrait, AccountMetaTrait, + }, + AccountType, CompressionInfo, HasCompressionInfo, LightAccount, LightConfig, + LightSdkTypesError, }; -use light_sdk::interface::error::LightPdaError; use super::state::MinimalRecord; @@ -47,8 +48,8 @@ impl LightAccount for MinimalRecord { #[cfg(not(target_os = "solana"))] fn pack( &self, - accounts: &mut light_sdk::interface::instruction::PackedAccounts, - ) -> std::result::Result { + accounts: &mut light_account::interface::instruction::PackedAccounts, + ) -> std::result::Result { // compression_info excluded from packed struct Ok(PackedMinimalRecord { owner: accounts.insert_or_get(AM::pubkey_from_bytes(self.owner.to_bytes())), @@ -58,11 +59,11 @@ impl LightAccount for MinimalRecord { fn unpack( packed: &Self::Packed, accounts: &ProgramPackedAccounts, - ) -> std::result::Result { + ) -> std::result::Result { // Use get_u8 with a descriptive name for better error messages let owner_account = accounts .get_u8(packed.owner, "MinimalRecord: owner") - .map_err(|_| LightPdaError::InvalidInstructionData)?; + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; // Set compression_info to compressed() for hash verification at decompress Ok(MinimalRecord { @@ -73,11 +74,13 @@ impl LightAccount for MinimalRecord { } impl HasCompressionInfo for MinimalRecord { - fn compression_info(&self) -> std::result::Result<&CompressionInfo, LightPdaError> { + fn compression_info(&self) -> std::result::Result<&CompressionInfo, LightSdkTypesError> { Ok(&self.compression_info) } - fn compression_info_mut(&mut self) -> std::result::Result<&mut CompressionInfo, LightPdaError> { + fn compression_info_mut( + &mut self, + ) -> std::result::Result<&mut CompressionInfo, LightSdkTypesError> { Ok(&mut self.compression_info) } @@ -85,7 +88,7 @@ impl HasCompressionInfo for MinimalRecord { panic!("compression_info_mut_opt not supported for LightAccount types (use compression_info_mut instead)") } - fn set_compression_info_none(&mut self) -> std::result::Result<(), LightPdaError> { + fn set_compression_info_none(&mut self) -> std::result::Result<(), LightSdkTypesError> { self.compression_info = CompressionInfo::compressed(); Ok(()) } diff --git a/sdk-tests/manual-test/src/pda/state.rs b/sdk-tests/manual-test/src/pda/state.rs index 421451171c..ee86784f0d 100644 --- a/sdk-tests/manual-test/src/pda/state.rs +++ b/sdk-tests/manual-test/src/pda/state.rs @@ -1,7 +1,7 @@ //! State module for single-pda-test. use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator, LightHasherSha}; +use light_account::{CompressionInfo, LightDiscriminator, LightHasherSha}; // ============================================================================ // MinimalRecord with derive macros diff --git a/sdk-tests/manual-test/src/token_account/derived.rs b/sdk-tests/manual-test/src/token_account/derived.rs index 4e7b3df367..f6e4775a90 100644 --- a/sdk-tests/manual-test/src/token_account/derived.rs +++ b/sdk-tests/manual-test/src/token_account/derived.rs @@ -1,13 +1,9 @@ //! Derived code - what the macro would generate for token accounts. use anchor_lang::prelude::*; -use light_sdk::{ - error::LightSdkError, - interface::{LightFinalize, LightPreInit}, - Unpack, -}; #[cfg(not(target_os = "solana"))] -use light_sdk::Pack; +use light_account::Pack; +use light_account::{LightFinalize, LightPreInit, LightSdkTypesError, Unpack}; use light_token::instruction::CreateTokenAccountCpi; use solana_account_info::AccountInfo; @@ -17,13 +13,15 @@ use super::accounts::{CreateTokenVaultAccounts, CreateTokenVaultParams, TOKEN_VA // LightPreInit Implementation - Creates token account at START of instruction // ============================================================================ -impl<'info> LightPreInit, CreateTokenVaultParams> for CreateTokenVaultAccounts<'info> { +impl<'info> LightPreInit, CreateTokenVaultParams> + for CreateTokenVaultAccounts<'info> +{ fn light_pre_init( &mut self, _remaining_accounts: &[AccountInfo<'info>], params: &CreateTokenVaultParams, - ) -> std::result::Result { - let inner = || -> std::result::Result { + ) -> std::result::Result { + let inner = || -> std::result::Result { // Build PDA seeds: [TOKEN_VAULT_SEED, mint.key(), &[bump]] let mint_key = self.mint.key(); let vault_seeds: &[&[u8]] = @@ -42,12 +40,12 @@ impl<'info> LightPreInit, CreateTokenVaultParams> for CreateT self.system_program.to_account_info(), &crate::ID, ) - .invoke_signed(vault_seeds)?; + .invoke_signed(vault_seeds).map_err(|_| LightSdkTypesError::CpiFailed)?; // Token accounts don't use CPI context, return false Ok(false) }; - inner().map_err(Into::into) + inner() } } @@ -55,13 +53,15 @@ impl<'info> LightPreInit, CreateTokenVaultParams> for CreateT // LightFinalize Implementation - No-op for token account only flow // ============================================================================ -impl<'info> LightFinalize, CreateTokenVaultParams> for CreateTokenVaultAccounts<'info> { +impl<'info> LightFinalize, CreateTokenVaultParams> + for CreateTokenVaultAccounts<'info> +{ fn light_finalize( &mut self, _remaining_accounts: &[AccountInfo<'info>], _params: &CreateTokenVaultParams, _has_pre_init: bool, - ) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { + ) -> std::result::Result<(), LightSdkTypesError> { Ok(()) } } @@ -81,8 +81,8 @@ impl Pack for TokenVaultSeeds { type Packed = PackedTokenVaultSeeds; fn pack( &self, - remaining_accounts: &mut light_sdk::instruction::PackedAccounts, - ) -> std::result::Result { + remaining_accounts: &mut light_account::PackedAccounts, + ) -> std::result::Result { Ok(PackedTokenVaultSeeds { mint_idx: remaining_accounts.insert_or_get(self.mint), bump: 0, @@ -103,10 +103,10 @@ impl<'a> Unpack> for PackedTokenVaultSeeds { fn unpack( &self, remaining_accounts: &[AccountInfo<'a>], - ) -> std::result::Result { + ) -> std::result::Result { let mint = *remaining_accounts .get(self.mint_idx as usize) - .ok_or(light_sdk::interface::error::LightPdaError::NotEnoughAccountKeys)? + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)? .key; Ok(TokenVaultSeeds { mint }) } diff --git a/sdk-tests/manual-test/src/two_mints/accounts.rs b/sdk-tests/manual-test/src/two_mints/accounts.rs index 1efbfeb388..dd0846d855 100644 --- a/sdk-tests/manual-test/src/two_mints/accounts.rs +++ b/sdk-tests/manual-test/src/two_mints/accounts.rs @@ -1,7 +1,7 @@ //! Standard Anchor accounts struct for create_derived_mints instruction. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; +use light_account::CreateAccountsProof; use solana_account_info::AccountInfo; /// Seed constants diff --git a/sdk-tests/manual-test/src/two_mints/derived.rs b/sdk-tests/manual-test/src/two_mints/derived.rs index 5309affc12..c22ed9bc4c 100644 --- a/sdk-tests/manual-test/src/two_mints/derived.rs +++ b/sdk-tests/manual-test/src/two_mints/derived.rs @@ -2,11 +2,9 @@ //! Contains LightPreInit/LightFinalize trait implementations. use anchor_lang::prelude::*; -use light_sdk::{ - cpi::{v2::CpiAccounts, CpiAccountsConfig}, - error::LightSdkError, - instruction::PackedAddressTreeInfoExt, - interface::{LightFinalize, LightPreInit}, +use light_account::{ + CpiAccounts, CpiAccountsConfig, LightFinalize, LightPreInit, LightSdkTypesError, + PackedAddressTreeInfoExt, }; use light_token::{ compressible::{invoke_create_mints, CreateMintsInfraAccounts}, @@ -25,14 +23,15 @@ use super::accounts::{ // LightPreInit Implementation - Creates mints at START of instruction // ============================================================================ -impl<'info> LightPreInit, CreateDerivedMintsParams> for CreateDerivedMintsAccounts<'info> { +impl<'info> LightPreInit, CreateDerivedMintsParams> + for CreateDerivedMintsAccounts<'info> +{ fn light_pre_init( &mut self, remaining_accounts: &[AccountInfo<'info>], params: &CreateDerivedMintsParams, - ) -> std::result::Result { - let inner = || -> std::result::Result { - use solana_program_error::ProgramError; + ) -> std::result::Result { + let inner = || -> std::result::Result { // ==================================================================== // STATIC BOILERPLATE (same across all LightPreInit implementations) @@ -42,7 +41,7 @@ impl<'info> LightPreInit, CreateDerivedMintsParams> for Creat let system_accounts_offset = params.create_accounts_proof.system_accounts_offset as usize; if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkError::FewerAccountsThanSystemAccounts); + return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); } let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); let cpi_accounts = CpiAccounts::new_with_config( @@ -55,7 +54,7 @@ impl<'info> LightPreInit, CreateDerivedMintsParams> for Creat let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; let address_tree_pubkey = address_tree_info .get_tree_pubkey(&cpi_accounts) - .map_err(|_| LightSdkError::from(ProgramError::InvalidAccountData))?; + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; // Constants for this instruction (mirrors macro-generated code) const NUM_LIGHT_MINTS: usize = 2; @@ -108,9 +107,7 @@ impl<'info> LightPreInit, CreateDerivedMintsParams> for Creat SingleMintParams { decimals: 6, // mint::decimals = 6 address_merkle_tree_root_index: address_tree_info.root_index, - mint_authority: solana_pubkey::Pubkey::new_from_array( - authority.to_bytes(), - ), + mint_authority: solana_pubkey::Pubkey::new_from_array(authority.to_bytes()), compression_address: compression_address_0, mint: mint_0_pda, bump: mint_0_bump, @@ -125,9 +122,7 @@ impl<'info> LightPreInit, CreateDerivedMintsParams> for Creat SingleMintParams { decimals: 9, // mint::decimals = 9 address_merkle_tree_root_index: address_tree_info.root_index, - mint_authority: solana_pubkey::Pubkey::new_from_array( - authority.to_bytes(), - ), + mint_authority: solana_pubkey::Pubkey::new_from_array(authority.to_bytes()), compression_address: compression_address_1, mint: mint_1_pda, bump: mint_1_bump, @@ -149,13 +144,13 @@ impl<'info> LightPreInit, CreateDerivedMintsParams> for Creat let state_tree_index = params .create_accounts_proof .state_tree_index - .ok_or(LightSdkError::from(ProgramError::InvalidArgument))?; + .ok_or(LightSdkTypesError::InvalidInstructionData)?; let proof = params .create_accounts_proof .proof .0 - .ok_or(LightSdkError::from(ProgramError::InvalidArgument))?; + .ok_or(LightSdkTypesError::InvalidInstructionData)?; let sdk_params = SdkCreateMintsParams::new(&sdk_mints, proof) .with_output_queue_index(params.create_accounts_proof.output_state_tree_index) @@ -176,8 +171,7 @@ impl<'info> LightPreInit, CreateDerivedMintsParams> for Creat self.mint_signer_0.to_account_info(), self.mint_signer_1.to_account_info(), ]; - let mint_accounts = - [self.mint_0.to_account_info(), self.mint_1.to_account_info()]; + let mint_accounts = [self.mint_0.to_account_info(), self.mint_1.to_account_info()]; invoke_create_mints( &mint_seed_accounts, @@ -185,11 +179,12 @@ impl<'info> LightPreInit, CreateDerivedMintsParams> for Creat sdk_params, infra, &cpi_accounts, - )?; + ) + .map_err(|_| LightSdkTypesError::CpiFailed)?; } Ok(WITH_CPI_CONTEXT) // false = mint-only, no CPI context write }; - inner().map_err(Into::into) + inner() } } @@ -197,13 +192,15 @@ impl<'info> LightPreInit, CreateDerivedMintsParams> for Creat // LightFinalize Implementation - No-op for mint-only flow // ============================================================================ -impl<'info> LightFinalize, CreateDerivedMintsParams> for CreateDerivedMintsAccounts<'info> { +impl<'info> LightFinalize, CreateDerivedMintsParams> + for CreateDerivedMintsAccounts<'info> +{ fn light_finalize( &mut self, _remaining_accounts: &[AccountInfo<'info>], _params: &CreateDerivedMintsParams, _has_pre_init: bool, - ) -> std::result::Result<(), light_sdk::interface::error::LightPdaError> { + ) -> std::result::Result<(), LightSdkTypesError> { // No-op for mint-only flow - create_mints already executed in light_pre_init Ok(()) } diff --git a/sdk-tests/manual-test/tests/account_loader.rs b/sdk-tests/manual-test/tests/account_loader.rs index bcb345011c..e96cf6ddcf 100644 --- a/sdk-tests/manual-test/tests/account_loader.rs +++ b/sdk-tests/manual-test/tests/account_loader.rs @@ -12,7 +12,7 @@ use light_client::interface::{ }; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Indexer, Rpc}; -use light_sdk::interface::IntoVariant; +use light_account::IntoVariant; use manual_test::{ CreateZeroCopyParams, ZeroCopyRecord, ZeroCopyRecordSeeds, ZeroCopyRecordVariant, }; @@ -185,7 +185,7 @@ async fn test_zero_copy_create_compress_decompress() { ); // state should be Decompressed after decompression - use light_sdk::compressible::CompressionState; + use light_account::CompressionState; assert_eq!( record.compression_info.state, CompressionState::Decompressed, diff --git a/sdk-tests/manual-test/tests/shared.rs b/sdk-tests/manual-test/tests/shared.rs index b4b6086f03..b029279189 100644 --- a/sdk-tests/manual-test/tests/shared.rs +++ b/sdk-tests/manual-test/tests/shared.rs @@ -8,7 +8,7 @@ use light_program_test::{ program_test::{setup_mock_program_data, LightProgramTest}, ProgramTestConfig, Rpc, }; -use light_sdk::utils::derive_rent_sponsor_pda; +use light_account::derive_rent_sponsor_pda; use solana_keypair::Keypair; use solana_pubkey::Pubkey; use solana_signer::Signer; diff --git a/sdk-tests/manual-test/tests/test.rs b/sdk-tests/manual-test/tests/test.rs index 6bf6314a94..07e3aa811a 100644 --- a/sdk-tests/manual-test/tests/test.rs +++ b/sdk-tests/manual-test/tests/test.rs @@ -11,7 +11,7 @@ use light_client::interface::{ }; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Indexer, Rpc}; -use light_sdk::interface::IntoVariant; +use light_account::IntoVariant; use manual_test::{ pda::{MinimalRecord, MinimalRecordSeeds, MinimalRecordVariant}, CreatePdaParams, @@ -157,7 +157,7 @@ async fn test_create_compress_decompress() { assert_eq!(record.owner, owner, "Record owner should match"); // state should be Decompressed after decompression - use light_sdk::compressible::CompressionState; + use light_account::CompressionState; assert_eq!( record.compression_info.state, CompressionState::Decompressed, diff --git a/sdk-tests/single-account-loader-test/Cargo.toml b/sdk-tests/single-account-loader-test/Cargo.toml index e0155e694e..a116be3b34 100644 --- a/sdk-tests/single-account-loader-test/Cargo.toml +++ b/sdk-tests/single-account-loader-test/Cargo.toml @@ -20,6 +20,7 @@ test-sbf = [] [dependencies] light-heap = { workspace = true, optional = true } +light-account = { workspace = true } light-sdk = { workspace = true, features = ["anchor", "v2", "anchor-discriminator", "cpi-context"] } light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] } light-sdk-macros = { workspace = true } diff --git a/sdk-tests/single-account-loader-test/src/lib.rs b/sdk-tests/single-account-loader-test/src/lib.rs index 337832593f..183011f617 100644 --- a/sdk-tests/single-account-loader-test/src/lib.rs +++ b/sdk-tests/single-account-loader-test/src/lib.rs @@ -6,9 +6,9 @@ #![allow(deprecated)] use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk::derive_light_cpi_signer; use light_sdk_macros::{light_program, LightAccounts}; +use light_sdk_types::interface::CreateAccountsProof; use light_sdk_types::CpiSigner; pub mod state; diff --git a/sdk-tests/single-ata-test/Cargo.toml b/sdk-tests/single-ata-test/Cargo.toml index c5b92a8fa1..ec89b0055f 100644 --- a/sdk-tests/single-ata-test/Cargo.toml +++ b/sdk-tests/single-ata-test/Cargo.toml @@ -20,6 +20,7 @@ test-sbf = [] [dependencies] light-heap = { workspace = true, optional = true } +light-account = { workspace = true } light-sdk = { workspace = true, features = ["anchor", "v2", "anchor-discriminator", "cpi-context"] } light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] } light-macros = { workspace = true, features = ["solana"] } diff --git a/sdk-tests/single-ata-test/src/lib.rs b/sdk-tests/single-ata-test/src/lib.rs index e785565e4b..c6f9fa98c1 100644 --- a/sdk-tests/single-ata-test/src/lib.rs +++ b/sdk-tests/single-ata-test/src/lib.rs @@ -6,9 +6,9 @@ #![allow(deprecated)] use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk::derive_light_cpi_signer; use light_sdk_macros::{light_program, LightAccounts}; +use light_sdk_types::interface::CreateAccountsProof; use light_sdk_types::{CpiSigner, LIGHT_TOKEN_PROGRAM_ID}; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; diff --git a/sdk-tests/single-mint-test/Cargo.toml b/sdk-tests/single-mint-test/Cargo.toml index d55460696a..034b031dee 100644 --- a/sdk-tests/single-mint-test/Cargo.toml +++ b/sdk-tests/single-mint-test/Cargo.toml @@ -20,6 +20,7 @@ test-sbf = [] [dependencies] light-heap = { workspace = true, optional = true } +light-account = { workspace = true } light-sdk = { workspace = true, features = ["anchor", "v2", "anchor-discriminator", "cpi-context"] } light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] } light-macros = { workspace = true, features = ["solana"] } diff --git a/sdk-tests/single-mint-test/src/lib.rs b/sdk-tests/single-mint-test/src/lib.rs index a440f42a79..f0f2e10434 100644 --- a/sdk-tests/single-mint-test/src/lib.rs +++ b/sdk-tests/single-mint-test/src/lib.rs @@ -6,9 +6,9 @@ #![allow(deprecated)] use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk::derive_light_cpi_signer; use light_sdk_macros::{light_program, LightAccounts}; +use light_sdk_types::interface::CreateAccountsProof; use light_sdk_types::CpiSigner; declare_id!("Mint111111111111111111111111111111111111111"); diff --git a/sdk-tests/single-pda-derive-test/Cargo.toml b/sdk-tests/single-pda-derive-test/Cargo.toml index 6a6068dc3f..d91610c5e4 100644 --- a/sdk-tests/single-pda-derive-test/Cargo.toml +++ b/sdk-tests/single-pda-derive-test/Cargo.toml @@ -13,21 +13,20 @@ no-entrypoint = [] no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] -custom-heap = ["light-heap", "light-sdk/custom-heap"] +custom-heap = [] default = [] -idl-build = ["anchor-lang/idl-build", "light-sdk/idl-build", "light-anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build", "light-anchor-spl/idl-build"] test-sbf = [] [dependencies] -light-heap = { workspace = true, optional = true } -light-sdk = { workspace = true, features = ["anchor", "v2", "anchor-discriminator", "cpi-context"] } +light-account = { workspace = true } +# light-sdk = { workspace = true, features = ["anchor", "v2", "anchor-discriminator", "cpi-context"] } light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] } light-macros = { workspace = true, features = ["solana"] } light-sdk-macros = { workspace = true } bytemuck = { workspace = true, features = ["derive"] } borsh = { workspace = true } light-compressed-account = { workspace = true, features = ["solana"] } -light-sdk-interface = { workspace = true } anchor-lang = { workspace = true } light-anchor-spl = { workspace = true, features = ["metadata"] } light-compressible = { workspace = true, features = ["anchor"] } diff --git a/sdk-tests/single-pda-derive-test/src/instruction_accounts.rs b/sdk-tests/single-pda-derive-test/src/instruction_accounts.rs index 5b32cd9da1..86b4834e8a 100644 --- a/sdk-tests/single-pda-derive-test/src/instruction_accounts.rs +++ b/sdk-tests/single-pda-derive-test/src/instruction_accounts.rs @@ -1,15 +1,13 @@ //! Accounts module for single-pda-derive-test. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use crate::state::{MinimalRecord, ZeroCopyRecord}; -use crate::{ - MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, RECORD_SEED, VAULT_AUTH_SEED, VAULT_SEED, -}; +use crate::{MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, RECORD_SEED, VAULT_AUTH_SEED, VAULT_SEED}; // ============================================================================= // 1. CreatePda diff --git a/sdk-tests/single-pda-derive-test/src/lib.rs b/sdk-tests/single-pda-derive-test/src/lib.rs index a65dc880c6..8e40445151 100644 --- a/sdk-tests/single-pda-derive-test/src/lib.rs +++ b/sdk-tests/single-pda-derive-test/src/lib.rs @@ -56,7 +56,7 @@ pub mod single_pda_derive_test { ctx: Context<'_, '_, '_, 'info, EmptyAccounts<'info>>, instruction_data: Vec, ) -> Result<()> { - light_sdk_interface::program::compression::processor::process_compress_pda_accounts_idempotent( + light_sdk::interface::program::compression::processor::process_compress_pda_accounts_idempotent( ctx.remaining_accounts, &instruction_data, ProgramAccounts::compress_dispatch, @@ -82,7 +82,7 @@ pub mod single_pda_derive_test { ctx: Context<'_, '_, '_, 'info, EmptyAccounts<'info>>, instruction_data: Vec, ) -> Result<()> { - light_sdk_interface::program::config::process_initialize_light_config_checked( + light_sdk::interface::program::config::process_initialize_light_config_checked( ctx.remaining_accounts, &instruction_data, &crate::ID, @@ -94,7 +94,7 @@ pub mod single_pda_derive_test { ctx: Context<'_, '_, '_, 'info, EmptyAccounts<'info>>, instruction_data: Vec, ) -> Result<()> { - light_sdk_interface::program::config::process_update_light_config( + light_sdk::interface::program::config::process_update_light_config( ctx.remaining_accounts, &instruction_data, &crate::ID, diff --git a/sdk-tests/single-pda-test/Cargo.toml b/sdk-tests/single-pda-test/Cargo.toml index b870fc445d..a41814fa09 100644 --- a/sdk-tests/single-pda-test/Cargo.toml +++ b/sdk-tests/single-pda-test/Cargo.toml @@ -20,6 +20,7 @@ test-sbf = [] [dependencies] light-heap = { workspace = true, optional = true } +light-account = { workspace = true } light-sdk = { workspace = true, features = ["anchor", "v2", "anchor-discriminator", "cpi-context"] } light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] } light-macros = { workspace = true, features = ["solana"] } diff --git a/sdk-tests/single-pda-test/src/instruction_accounts.rs b/sdk-tests/single-pda-test/src/instruction_accounts.rs index 5470aeef8a..2ddbeb7be4 100644 --- a/sdk-tests/single-pda-test/src/instruction_accounts.rs +++ b/sdk-tests/single-pda-test/src/instruction_accounts.rs @@ -1,8 +1,8 @@ //! Accounts module for single-pda-test. use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk_macros::LightAccounts; +use light_sdk_types::interface::CreateAccountsProof; use crate::state::MinimalRecord; diff --git a/sdk-tests/single-token-test/Cargo.toml b/sdk-tests/single-token-test/Cargo.toml index a972a60826..dc65be90a5 100644 --- a/sdk-tests/single-token-test/Cargo.toml +++ b/sdk-tests/single-token-test/Cargo.toml @@ -20,6 +20,7 @@ test-sbf = [] [dependencies] light-heap = { workspace = true, optional = true } +light-account = { workspace = true } light-sdk = { workspace = true, features = ["anchor", "v2", "anchor-discriminator", "cpi-context"] } light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] } light-macros = { workspace = true, features = ["solana"] } diff --git a/sdk-tests/single-token-test/src/lib.rs b/sdk-tests/single-token-test/src/lib.rs index 9a14465b05..4c981a10ea 100644 --- a/sdk-tests/single-token-test/src/lib.rs +++ b/sdk-tests/single-token-test/src/lib.rs @@ -6,9 +6,9 @@ #![allow(deprecated)] use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; use light_sdk::derive_light_cpi_signer; use light_sdk_macros::{light_program, LightAccounts}; +use light_sdk_types::interface::CreateAccountsProof; use light_sdk_types::CpiSigner; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; From ae0f859cc404fd4684e08e0dfee8fed2082ea3cf Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 2 Feb 2026 04:07:00 +0000 Subject: [PATCH 06/15] feat: add sdk-types-pinocchio --- Cargo.lock | 13 ++ Cargo.toml | 2 + sdk-libs/account-pinocchio/Cargo.toml | 31 ++++ sdk-libs/account-pinocchio/src/lib.rs | 161 ++++++++++++++++++ sdk-libs/account/src/lib.rs | 1 + .../macros/src/light_pdas/accounts/builder.rs | 2 +- sdk-libs/sdk-pinocchio/src/error.rs | 45 +++++ sdk-libs/sdk-types/src/error.rs | 30 ++-- .../src/interface/account/compression_info.rs | 13 +- .../src/interface/account/light_account.rs | 8 +- .../sdk-types/src/interface/account/pack.rs | 4 +- .../src/interface/account/pda_seeds.rs | 2 + .../src/interface/account/token_seeds.rs | 9 +- .../accounts/init_compressed_account.rs | 4 +- .../sdk-types/src/interface/cpi/account.rs | 3 + .../sdk-types/src/interface/cpi/invoke.rs | 28 +++ sdk-libs/sdk-types/src/interface/cpi/mod.rs | 2 + sdk-libs/sdk-types/src/interface/mod.rs | 6 +- .../interface/program/compression/close.rs | 2 +- .../src/interface/program/compression/pda.rs | 6 +- .../program/compression/processor.rs | 2 + .../src/interface/program/config/create.rs | 12 +- .../src/interface/program/config/mod.rs | 6 +- .../src/interface/program/config/state.rs | 2 + .../src/interface/program/config/update.rs | 6 +- .../decompression/create_token_account.rs | 3 + .../interface/program/decompression/pda.rs | 2 + .../program/decompression/processor.rs | 4 + .../interface/program/decompression/token.rs | 3 + .../src/interface/program/validation.rs | 8 +- .../src/interface/program/variant.rs | 3 + sdk-libs/sdk-types/src/lib.rs | 4 +- sdk-libs/sdk/src/error.rs | 4 +- .../src/compressible/compress_runtime.rs | 64 ------- sdk-libs/token-sdk/src/compressible/mod.rs | 2 - 35 files changed, 375 insertions(+), 122 deletions(-) create mode 100644 sdk-libs/account-pinocchio/Cargo.toml create mode 100644 sdk-libs/account-pinocchio/src/lib.rs delete mode 100644 sdk-libs/token-sdk/src/compressible/compress_runtime.rs diff --git a/Cargo.lock b/Cargo.lock index 828194d562..5c62849421 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3495,6 +3495,19 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "light-account-pinocchio" +version = "0.1.0" +dependencies = [ + "light-account-checks", + "light-compressed-account", + "light-hasher", + "light-macros", + "light-sdk-macros", + "light-sdk-types", + "pinocchio", +] + [[package]] name = "light-anchor-spl" version = "0.31.1" diff --git a/Cargo.toml b/Cargo.toml index cef51406e2..00e97b60be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "sdk-libs/token-client", "sdk-libs/macros", "sdk-libs/account", + "sdk-libs/account-pinocchio", "sdk-libs/sdk", "sdk-libs/sdk-pinocchio", "sdk-libs/sdk-types", @@ -201,6 +202,7 @@ light-merkle-tree-reference = { path = "program-tests/merkle-tree", version = "4 light-heap = { path = "program-libs/heap", version = "2.0.0" } light-prover-client = { path = "prover/client", version = "6.0.0" } light-account = { path = "sdk-libs/account", version = "0.1.0", default-features = false } +light-account-pinocchio = { path = "sdk-libs/account-pinocchio", version = "0.1.0", default-features = false } light-sdk = { path = "sdk-libs/sdk", version = "0.19.0" } light-sdk-pinocchio = { path = "sdk-libs/sdk-pinocchio", version = "0.19.0" } light-sdk-macros = { path = "sdk-libs/macros", version = "0.19.0" } diff --git a/sdk-libs/account-pinocchio/Cargo.toml b/sdk-libs/account-pinocchio/Cargo.toml new file mode 100644 index 0000000000..2a521d7ea6 --- /dev/null +++ b/sdk-libs/account-pinocchio/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "light-account-pinocchio" +version = "0.1.0" +description = "Light Protocol account types with pinocchio AccountInfo specializations" +repository = "https://github.com/Lightprotocol/light-protocol" +license = "Apache-2.0" +edition = "2021" + +[features] +default = ["alloc"] +std = ["alloc", "light-sdk-types/std", "light-compressed-account/std"] +alloc = ["light-sdk-types/alloc", "light-compressed-account/alloc"] +token = ["light-sdk-types/token"] +poseidon = ["light-sdk-types/poseidon", "light-hasher/poseidon"] +sha256 = ["light-sdk-types/sha256", "light-hasher/sha256"] + +[dependencies] +light-sdk-types = { workspace = true, default-features = false, features = ["alloc", "v2", "cpi-context"] } +light-sdk-macros = { workspace = true } +light-macros = { workspace = true } +light-account-checks = { workspace = true, default-features = false, features = ["pinocchio"] } +light-hasher = { workspace = true, default-features = false } +light-compressed-account = { workspace = true, default-features = false } +pinocchio = { workspace = true } + +[lints.rust.unexpected_cfgs] +level = "allow" +check-cfg = [ + 'cfg(target_os, values("solana"))', + 'cfg(feature, values("frozen-abi", "no-entrypoint"))', +] diff --git a/sdk-libs/account-pinocchio/src/lib.rs b/sdk-libs/account-pinocchio/src/lib.rs new file mode 100644 index 0000000000..568fed00f4 --- /dev/null +++ b/sdk-libs/account-pinocchio/src/lib.rs @@ -0,0 +1,161 @@ +//! Light Protocol account types specialized for pinocchio's AccountInfo. + +pub use pinocchio::account_info::AccountInfo; + +// ===== TYPE ALIASES (structs generic over AI, specialized with pinocchio AccountInfo) ===== +// Note: pinocchio's AccountInfo has no lifetime parameter, so aliases have fewer lifetimes. + +pub type CpiAccounts<'c> = + light_sdk_types::cpi_accounts::v2::CpiAccounts<'c, AccountInfo>; + +pub type CpiAccountsV1<'c> = + light_sdk_types::cpi_accounts::v1::CpiAccounts<'c, AccountInfo>; + +pub type CompressCtx<'a> = + light_sdk_types::interface::program::compression::processor::CompressCtx<'a, AccountInfo>; + +pub type CompressDispatchFn = + light_sdk_types::interface::program::compression::processor::CompressDispatchFn; + +pub type DecompressCtx<'a> = + light_sdk_types::interface::program::decompression::processor::DecompressCtx<'a, AccountInfo>; + +pub type ValidatedPdaContext = + light_sdk_types::interface::program::validation::ValidatedPdaContext; + +pub type CpiContextWriteAccounts<'a> = + light_sdk_types::cpi_context_write::CpiContextWriteAccounts<'a, AccountInfo>; + +#[cfg(all(not(target_os = "solana"), feature = "std"))] +pub type PackedAccounts = + light_sdk_types::interface::instruction::PackedAccounts< + light_account_checks::account_info::pinocchio::OwnedAccountMeta, + >; + +// ===== RE-EXPORTED TRAITS (generic over AI, used with explicit AccountInfo in impls) ===== + +pub use light_sdk_types::interface::accounts::finalize::{LightFinalize, LightPreInit}; +pub use light_sdk_types::interface::cpi::{ + account::CpiAccountsTrait, invoke::InvokeLightSystemProgram, LightCpi, +}; +pub use light_sdk_types::interface::program::decompression::processor::DecompressVariant; + +// ===== RE-EXPORTED CONCRETE TRAITS (no AI parameter) ===== + +pub use light_sdk_types::interface::account::compression_info::{ + claim_completed_epoch_rent, CompressAs, CompressedAccountData, CompressedInitSpace, + CompressionInfo, CompressionInfoField, CompressionState, HasCompressionInfo, Space, + COMPRESSION_INFO_SIZE, OPTION_COMPRESSION_INFO_SPACE, +}; +pub use light_sdk_types::interface::account::light_account::{AccountType, LightAccount}; +#[cfg(all(not(target_os = "solana"), feature = "std"))] +pub use light_sdk_types::interface::account::pack::Pack; +pub use light_sdk_types::interface::account::pack::Unpack; +pub use light_sdk_types::interface::account::pda_seeds::{HasTokenVariant, PdaSeedDerivation}; +pub use light_sdk_types::interface::accounts::init_compressed_account::{ + prepare_compressed_account_on_init, reimburse_rent, +}; +pub use light_sdk_types::interface::create_accounts_proof::CreateAccountsProof; +pub use light_sdk_types::interface::program::variant::{ + IntoVariant, LightAccountVariantTrait, PackedLightAccountVariantTrait, +}; +pub use light_sdk_types::interface::rent; + +// ===== RE-EXPORTED GENERIC FUNCTIONS (AI inferred from call-site args) ===== + +pub use light_sdk_types::interface::cpi::invoke::invoke_light_system_program; +pub use light_sdk_types::interface::cpi::invoke::invoke_write_pdas_to_cpi_context; +pub use light_sdk_types::interface::program::compression::close::close; +pub use light_sdk_types::interface::program::compression::pda::prepare_account_for_compression; +pub use light_sdk_types::interface::program::compression::processor::{ + process_compress_pda_accounts_idempotent, CompressAndCloseParams, +}; +pub use light_sdk_types::interface::program::config::{ + process_initialize_light_config_checked, process_update_light_config, + InitializeLightConfigParams, LightConfig, UpdateLightConfigParams, COMPRESSIBLE_CONFIG_SEED, + MAX_ADDRESS_TREES_PER_SPACE, +}; +pub use light_sdk_types::interface::program::config::create::process_initialize_light_config; +pub use light_sdk_types::interface::program::decompression::pda::prepare_account_for_decompression; +pub use light_sdk_types::interface::program::decompression::processor::{ + process_decompress_pda_accounts_idempotent, DecompressIdempotentParams, +}; +pub use light_sdk_types::interface::program::validation::{ + extract_tail_accounts, is_pda_initialized, should_skip_compression, + split_at_system_accounts_offset, validate_compress_accounts, validate_decompress_accounts, +}; + +// ===== TOKEN-GATED RE-EXPORTS ===== + +#[cfg(feature = "token")] +pub use light_sdk_types::interface::account::token_seeds::{ + PackedTokenData, TokenDataWithPackedSeeds, TokenDataWithSeeds, +}; +#[cfg(feature = "token")] +pub use light_sdk_types::interface::program::decompression::processor::process_decompress_accounts_idempotent; +#[cfg(feature = "token")] +pub use light_sdk_types::interface::program::decompression::token::prepare_token_account_for_decompression; +#[cfg(feature = "token")] +pub use light_sdk_types::interface::program::variant::{PackedTokenSeeds, UnpackedTokenSeeds}; + +#[cfg(feature = "token")] +pub mod token { + pub use light_sdk_types::interface::account::token_seeds::{ + ExtensionInstructionData, MultiInputTokenDataWithContext, PackedTokenData, + TokenDataWithPackedSeeds, TokenDataWithSeeds, + }; + pub use light_sdk_types::interface::program::decompression::token::prepare_token_account_for_decompression; +} + +pub mod compression_info { + pub use light_sdk_types::interface::account::compression_info::*; +} + +// ===== CPI / SDK-TYPES RE-EXPORTS ===== + +pub use light_sdk_types::cpi_accounts::CpiAccountsConfig; + +#[cfg(all(not(target_os = "solana"), feature = "std"))] +pub mod interface { + pub mod instruction { + pub use light_sdk_types::interface::instruction::PackedAccounts; + } +} + +pub mod account_meta { + pub use light_sdk_types::instruction::account_meta::*; +} + +// ===== ACCOUNT-CHECKS RE-EXPORTS (used by macro-generated code) ===== + +pub extern crate light_account_checks; +pub use light_account_checks::packed_accounts; +pub use light_account_checks::{AccountInfoTrait, AccountMetaTrait}; +pub use light_account_checks::account_info::pinocchio::OwnedAccountMeta; + +// ===== CONVENIENCE RE-EXPORTS ===== + +pub use light_account_checks::discriminator::Discriminator as LightDiscriminator; +pub use light_compressed_account::instruction_data::compressed_proof::ValidityProof; +pub use light_macros::{derive_light_cpi_signer, derive_light_cpi_signer_pda}; +pub use light_sdk_macros::{ + CompressAs, Compressible, HasCompressionInfo, + LightAccount, LightDiscriminator, LightHasher, LightHasherSha, + LightProgram, +}; +pub use light_sdk_types::error::LightSdkTypesError; +pub use light_sdk_types::instruction::*; +pub use light_sdk_types::{constants, CpiSigner}; + +// ===== UTILITY FUNCTIONS ===== + +/// Derives the rent sponsor PDA for a given program. +/// +/// Seeds: `["rent_sponsor"]` +/// Returns `([u8; 32], u8)` since pinocchio uses raw byte array pubkeys. +pub fn derive_rent_sponsor_pda(program_id: &[u8; 32]) -> ([u8; 32], u8) { + ::find_program_address( + &[constants::RENT_SPONSOR_SEED], + program_id, + ) +} diff --git a/sdk-libs/account/src/lib.rs b/sdk-libs/account/src/lib.rs index ad3b93ed94..dc89d1ae33 100644 --- a/sdk-libs/account/src/lib.rs +++ b/sdk-libs/account/src/lib.rs @@ -66,6 +66,7 @@ pub use light_sdk_types::interface::rent; // ===== RE-EXPORTED GENERIC FUNCTIONS (AI inferred from call-site args) ===== pub use light_sdk_types::interface::cpi::invoke::invoke_light_system_program; +pub use light_sdk_types::interface::cpi::invoke::invoke_write_pdas_to_cpi_context; pub use light_sdk_types::interface::program::compression::close::close; pub use light_sdk_types::interface::program::compression::pda::prepare_account_for_compression; pub use light_sdk_types::interface::program::compression::processor::{ diff --git a/sdk-libs/macros/src/light_pdas/accounts/builder.rs b/sdk-libs/macros/src/light_pdas/accounts/builder.rs index 88668a8849..9253f25d76 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/builder.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/builder.rs @@ -316,7 +316,7 @@ impl LightAccountsBuilder { // Reimburse fee payer for rent paid during PDA creation #rent_reimbursement - light_token::compressible::invoke_write_pdas_to_cpi_context( + light_account::invoke_write_pdas_to_cpi_context( crate::LIGHT_CPI_SIGNER, #proof_access.proof.clone(), &all_new_address_params, diff --git a/sdk-libs/sdk-pinocchio/src/error.rs b/sdk-libs/sdk-pinocchio/src/error.rs index 921b079374..19256d249f 100644 --- a/sdk-libs/sdk-pinocchio/src/error.rs +++ b/sdk-libs/sdk-pinocchio/src/error.rs @@ -84,6 +84,26 @@ pub enum LightSdkError { ProgramError(ProgramError), #[error(transparent)] AccountError(#[from] AccountError), + #[error("Missing compression info")] + MissingCompressionInfo, + #[error("Invalid rent sponsor")] + InvalidRentSponsor, + #[error("Borsh IO error: {0}")] + BorshIo(String), + #[error("Read-only accounts not supported in CPI context")] + ReadOnlyAccountsNotSupportedInCpiContext, + #[error("Account data too small")] + AccountDataTooSmall, + #[error("Invalid instruction data")] + InvalidInstructionData, + #[error("Invalid seeds")] + InvalidSeeds, + #[error("CPI failed")] + CpiFailed, + #[error("Not enough account keys")] + NotEnoughAccountKeys, + #[error("Missing required signature")] + MissingRequiredSignature, } impl From for LightSdkError { @@ -131,6 +151,21 @@ impl From for LightSdkError { LightSdkTypesError::InvalidSolPoolPdaAccount => LightSdkError::InvalidSolPoolPdaAccount, LightSdkTypesError::AccountError(e) => LightSdkError::AccountError(e), LightSdkTypesError::InvalidCpiAccountsOffset => LightSdkError::InvalidCpiAccountsOffset, + LightSdkTypesError::ConstraintViolation => LightSdkError::ConstraintViolation, + LightSdkTypesError::Borsh => LightSdkError::Borsh, + LightSdkTypesError::MissingCompressionInfo => LightSdkError::MissingCompressionInfo, + LightSdkTypesError::InvalidRentSponsor => LightSdkError::InvalidRentSponsor, + LightSdkTypesError::BorshIo(s) => LightSdkError::BorshIo(s), + LightSdkTypesError::ReadOnlyAccountsNotSupportedInCpiContext => { + LightSdkError::ReadOnlyAccountsNotSupportedInCpiContext + } + LightSdkTypesError::CompressedAccountError(e) => LightSdkError::CompressedAccount(e), + LightSdkTypesError::AccountDataTooSmall => LightSdkError::AccountDataTooSmall, + LightSdkTypesError::InvalidInstructionData => LightSdkError::InvalidInstructionData, + LightSdkTypesError::InvalidSeeds => LightSdkError::InvalidSeeds, + LightSdkTypesError::CpiFailed => LightSdkError::CpiFailed, + LightSdkTypesError::NotEnoughAccountKeys => LightSdkError::NotEnoughAccountKeys, + LightSdkTypesError::MissingRequiredSignature => LightSdkError::MissingRequiredSignature, } } } @@ -176,6 +211,16 @@ impl From for u32 { LightSdkError::CompressedAccount(_) => 16036, LightSdkError::ProgramError(e) => u64::from(e) as u32, LightSdkError::AccountError(e) => e.into(), + LightSdkError::MissingCompressionInfo => 16037, + LightSdkError::InvalidRentSponsor => 16038, + LightSdkError::BorshIo(_) => 16039, + LightSdkError::ReadOnlyAccountsNotSupportedInCpiContext => 16040, + LightSdkError::AccountDataTooSmall => 16041, + LightSdkError::InvalidInstructionData => 16042, + LightSdkError::InvalidSeeds => 16043, + LightSdkError::CpiFailed => 16044, + LightSdkError::NotEnoughAccountKeys => 16045, + LightSdkError::MissingRequiredSignature => 16046, } } } diff --git a/sdk-libs/sdk-types/src/error.rs b/sdk-libs/sdk-types/src/error.rs index 49db3c6dd0..44a63107bb 100644 --- a/sdk-libs/sdk-types/src/error.rs +++ b/sdk-libs/sdk-types/src/error.rs @@ -39,7 +39,7 @@ pub enum LightSdkTypesError { #[error("CpigAccounts accounts slice starts with an invalid account. It should start with LightSystemProgram SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7.")] InvalidCpiAccountsOffset, #[error(transparent)] - AccountCheck(#[from] AccountError), + AccountError(#[from] AccountError), #[error(transparent)] Hasher(#[from] HasherError), // --- Variants merged from LightSdkTypesError (sdk-interface) --- @@ -89,22 +89,22 @@ impl From for u32 { LightSdkTypesError::InvalidCpiContextAccount => 14032, LightSdkTypesError::InvalidSolPoolPdaAccount => 14033, LightSdkTypesError::InvalidCpiAccountsOffset => 14034, - LightSdkTypesError::AccountCheck(e) => e.into(), + LightSdkTypesError::AccountError(e) => e.into(), LightSdkTypesError::Hasher(e) => e.into(), - LightSdkTypesError::ConstraintViolation => 17001, - LightSdkTypesError::Borsh => 17002, - LightSdkTypesError::MissingCompressionInfo => 17003, - LightSdkTypesError::InvalidRentSponsor => 17004, + LightSdkTypesError::ConstraintViolation => 14035, + LightSdkTypesError::Borsh => 14036, + LightSdkTypesError::MissingCompressionInfo => 14037, + LightSdkTypesError::InvalidRentSponsor => 14038, #[cfg(feature = "alloc")] - LightSdkTypesError::BorshIo(_) => 17005, - LightSdkTypesError::ReadOnlyAccountsNotSupportedInCpiContext => 17006, - LightSdkTypesError::CompressedAccountError(_) => 17007, - LightSdkTypesError::AccountDataTooSmall => 17008, - LightSdkTypesError::InvalidInstructionData => 17009, - LightSdkTypesError::InvalidSeeds => 17010, - LightSdkTypesError::CpiFailed => 17011, - LightSdkTypesError::NotEnoughAccountKeys => 17012, - LightSdkTypesError::MissingRequiredSignature => 17013, + LightSdkTypesError::BorshIo(_) => 14039, + LightSdkTypesError::ReadOnlyAccountsNotSupportedInCpiContext => 14040, + LightSdkTypesError::CompressedAccountError(_) => 14041, + LightSdkTypesError::AccountDataTooSmall => 14042, + LightSdkTypesError::InvalidInstructionData => 14043, + LightSdkTypesError::InvalidSeeds => 14044, + LightSdkTypesError::CpiFailed => 14045, + LightSdkTypesError::NotEnoughAccountKeys => 14046, + LightSdkTypesError::MissingRequiredSignature => 14047, } } } diff --git a/sdk-libs/sdk-types/src/interface/account/compression_info.rs b/sdk-libs/sdk-types/src/interface/account/compression_info.rs index eaaf3cda68..d3d16f48f5 100644 --- a/sdk-libs/sdk-types/src/interface/account/compression_info.rs +++ b/sdk-libs/sdk-types/src/interface/account/compression_info.rs @@ -1,5 +1,6 @@ extern crate alloc; use alloc::borrow::Cow; +use alloc::string::ToString; use crate::instruction::PackedStateTreeInfo; use bytemuck::{Pod, Zeroable}; @@ -297,9 +298,9 @@ impl CompressionInfo { ) -> Result<(), LightSdkTypesError> { let bytes = account_info.data_len() as u64; let current_lamports = account_info.lamports(); - let current_slot = AI::get_current_slot().map_err(LightSdkTypesError::AccountCheck)?; + let current_slot = AI::get_current_slot().map_err(LightSdkTypesError::AccountError)?; let rent_exemption_lamports = - AI::get_min_rent_balance(bytes as usize).map_err(LightSdkTypesError::AccountCheck)?; + AI::get_min_rent_balance(bytes as usize).map_err(LightSdkTypesError::AccountError)?; let top_up = self.calculate_top_up_lamports( bytes, @@ -312,7 +313,7 @@ impl CompressionInfo { // Use System Program CPI to transfer lamports (payer is a signer, pass empty seeds) payer_info .transfer_lamports_cpi(account_info, top_up, &[]) - .map_err(LightSdkTypesError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountError)?; } Ok(()) @@ -357,11 +358,11 @@ where { use light_compressible::rent::{AccountRentState, SLOTS_PER_EPOCH}; - let current_slot = AI::get_current_slot().map_err(LightSdkTypesError::AccountCheck)?; + let current_slot = AI::get_current_slot().map_err(LightSdkTypesError::AccountError)?; let bytes = account_info.data_len() as u64; let current_lamports = account_info.lamports(); let rent_exemption_lamports = - AI::get_min_rent_balance(bytes as usize).map_err(LightSdkTypesError::AccountCheck)?; + AI::get_min_rent_balance(bytes as usize).map_err(LightSdkTypesError::AccountError)?; let ci = account_data.compression_info_mut()?; let state = AccountRentState { @@ -394,7 +395,7 @@ where // since the program owns the account) account_info .transfer_lamports(rent_sponsor, amount) - .map_err(LightSdkTypesError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountError)?; return Ok(Some(amount)); } } diff --git a/sdk-libs/sdk-types/src/interface/account/light_account.rs b/sdk-libs/sdk-types/src/interface/account/light_account.rs index e60a2c7b23..3cc96ad3c0 100644 --- a/sdk-libs/sdk-types/src/interface/account/light_account.rs +++ b/sdk-libs/sdk-types/src/interface/account/light_account.rs @@ -1,8 +1,8 @@ //! LightAccount trait definition for compressible account data structs. -use light_account_checks::{ - packed_accounts::ProgramPackedAccounts, AccountInfoTrait, AccountMetaTrait, -}; +use light_account_checks::{packed_accounts::ProgramPackedAccounts, AccountInfoTrait}; +#[cfg(all(not(target_os = "solana"), feature = "std"))] +use light_account_checks::AccountMetaTrait; use light_hasher::DataHasher; use crate::interface::{account::compression_info::CompressionInfo, program::config::LightConfig}; @@ -47,7 +47,7 @@ pub trait LightAccount: /// Convert to packed form (Pubkeys -> indices). /// Generic over AccountMetaTrait for runtime-agnostic packing. - #[cfg(not(target_os = "solana"))] + #[cfg(all(not(target_os = "solana"), feature = "std"))] fn pack( &self, accounts: &mut crate::interface::instruction::PackedAccounts, diff --git a/sdk-libs/sdk-types/src/interface/account/pack.rs b/sdk-libs/sdk-types/src/interface/account/pack.rs index 3f6cb42e6e..c6ec822cb8 100644 --- a/sdk-libs/sdk-types/src/interface/account/pack.rs +++ b/sdk-libs/sdk-types/src/interface/account/pack.rs @@ -4,12 +4,12 @@ use light_account_checks::AccountInfoTrait; use crate::error::LightSdkTypesError; -#[cfg(not(target_os = "solana"))] +#[cfg(all(not(target_os = "solana"), feature = "std"))] use light_account_checks::AccountMetaTrait; /// Replace 32-byte Pubkeys with 1-byte indices to save space. /// If your type has no Pubkeys, just return self. -#[cfg(not(target_os = "solana"))] +#[cfg(all(not(target_os = "solana"), feature = "std"))] pub trait Pack { type Packed: crate::AnchorSerialize + Clone + core::fmt::Debug; diff --git a/sdk-libs/sdk-types/src/interface/account/pda_seeds.rs b/sdk-libs/sdk-types/src/interface/account/pda_seeds.rs index 83b3b5b54f..eac2cb8ff4 100644 --- a/sdk-libs/sdk-types/src/interface/account/pda_seeds.rs +++ b/sdk-libs/sdk-types/src/interface/account/pda_seeds.rs @@ -1,5 +1,7 @@ //! PDA seed derivation traits. +use alloc::vec::Vec; + use crate::error::LightSdkTypesError; /// Trait for account variants that can be checked for token or PDA type. diff --git a/sdk-libs/sdk-types/src/interface/account/token_seeds.rs b/sdk-libs/sdk-types/src/interface/account/token_seeds.rs index 14744fea67..1b57980b3e 100644 --- a/sdk-libs/sdk-types/src/interface/account/token_seeds.rs +++ b/sdk-libs/sdk-types/src/interface/account/token_seeds.rs @@ -3,6 +3,9 @@ //! Provides `TokenDataWithSeeds`, `PackedTokenData`, and `TokenDataWithPackedSeeds` //! along with Pack/Unpack impls and blanket impls for variant traits. +use alloc::vec; +use alloc::vec::Vec; + use crate::instruction::PackedStateTreeInfo; use light_compressed_account::compressed_account::PackedMerkleContext; pub use light_token_interface::{ @@ -26,10 +29,10 @@ use crate::interface::{ UnpackedTokenSeeds, }, }; -#[cfg(not(target_os = "solana"))] +#[cfg(all(not(target_os = "solana"), feature = "std"))] use crate::interface::{account::pack::Pack, instruction::PackedAccounts}; use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; -#[cfg(not(target_os = "solana"))] +#[cfg(all(not(target_os = "solana"), feature = "std"))] use light_account_checks::AccountMetaTrait; #[derive(Clone, Debug, AnchorSerialize, AnchorDeserialize)] @@ -121,7 +124,7 @@ fn unpack_token_data_from_packed( // Pack impl (client-side only) // ============================================================================= -#[cfg(not(target_os = "solana"))] +#[cfg(all(not(target_os = "solana"), feature = "std"))] impl Pack for TokenDataWithSeeds where S: Pack, diff --git a/sdk-libs/sdk-types/src/interface/accounts/init_compressed_account.rs b/sdk-libs/sdk-types/src/interface/accounts/init_compressed_account.rs index 54094059d5..288212b637 100644 --- a/sdk-libs/sdk-types/src/interface/accounts/init_compressed_account.rs +++ b/sdk-libs/sdk-types/src/interface/accounts/init_compressed_account.rs @@ -1,5 +1,7 @@ //! Helper functions for preparing compressed accounts on init. +use alloc::vec::Vec; + use crate::instruction::PackedAddressTreeInfo; use light_account_checks::AccountInfoTrait; use light_compressed_account::{ @@ -93,7 +95,7 @@ pub fn reimburse_rent( if total_rent > 0 { rent_sponsor .transfer_lamports(fee_payer, total_rent) - .map_err(LightSdkTypesError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountError)?; } Ok(()) diff --git a/sdk-libs/sdk-types/src/interface/cpi/account.rs b/sdk-libs/sdk-types/src/interface/cpi/account.rs index 92257a184f..54992eff3a 100644 --- a/sdk-libs/sdk-types/src/interface/cpi/account.rs +++ b/sdk-libs/sdk-types/src/interface/cpi/account.rs @@ -1,5 +1,8 @@ //! Generic CPI accounts trait and implementations. +use alloc::vec; +use alloc::vec::Vec; + use crate::cpi_accounts::v2::{CompressionCpiAccountIndex, CpiAccounts, PROGRAM_ACCOUNTS_LEN}; use crate::cpi_context_write::CpiContextWriteAccounts; use light_account_checks::{AccountInfoTrait, CpiMeta}; diff --git a/sdk-libs/sdk-types/src/interface/cpi/invoke.rs b/sdk-libs/sdk-types/src/interface/cpi/invoke.rs index b9b91c7a22..0cb4723178 100644 --- a/sdk-libs/sdk-types/src/interface/cpi/invoke.rs +++ b/sdk-libs/sdk-types/src/interface/cpi/invoke.rs @@ -1,5 +1,7 @@ //! Generic Light system program invocation. +use alloc::vec; + use crate::constants::{CPI_AUTHORITY_PDA_SEED, LIGHT_SYSTEM_PROGRAM_ID}; use light_account_checks::{AccountInfoTrait, CpiMeta}; pub use light_compressed_account::LightInstructionData; @@ -166,3 +168,29 @@ pub fn invoke_light_system_program( ) .map_err(|_| LightSdkTypesError::CpiFailed) } + +/// Write compressed PDA data to CPI context for chaining with subsequent operations. +/// +/// Use this when PDAs need to be written to CPI context first, which will be +/// consumed by subsequent operations (e.g., mint CPIs). +/// +/// Generic over `AccountInfoTrait` to work with both solana and pinocchio backends. +#[cfg(feature = "cpi-context")] +pub fn invoke_write_pdas_to_cpi_context( + cpi_signer: crate::CpiSigner, + proof: light_compressed_account::instruction_data::compressed_proof::ValidityProof, + new_addresses: &[light_compressed_account::instruction_data::data::NewAddressParamsAssignedPacked], + compressed_infos: &[light_compressed_account::instruction_data::with_account_info::CompressedAccountInfo], + cpi_accounts: &crate::cpi_accounts::v2::CpiAccounts<'_, AI>, +) -> Result<(), LightSdkTypesError> { + use light_compressed_account::instruction_data::with_account_info::InstructionDataInvokeCpiWithAccountInfo; + + let cpi_context_accounts = + crate::cpi_context_write::CpiContextWriteAccounts::try_from(cpi_accounts)?; + + let instruction_data = InstructionDataInvokeCpiWithAccountInfo::new_cpi(cpi_signer, proof) + .with_new_addresses(new_addresses) + .with_account_infos(compressed_infos); + + instruction_data.invoke_write_to_cpi_context_first(cpi_context_accounts) +} diff --git a/sdk-libs/sdk-types/src/interface/cpi/mod.rs b/sdk-libs/sdk-types/src/interface/cpi/mod.rs index 6bbda4fba7..8fef72b18a 100644 --- a/sdk-libs/sdk-types/src/interface/cpi/mod.rs +++ b/sdk-libs/sdk-types/src/interface/cpi/mod.rs @@ -11,6 +11,8 @@ pub mod invoke; pub use account::CpiAccountsTrait; pub use instruction::LightCpi; pub use invoke::{invoke_light_system_program, InvokeLightSystemProgram}; +#[cfg(feature = "cpi-context")] +pub use invoke::invoke_write_pdas_to_cpi_context; pub use light_compressed_account::instruction_data::traits::LightInstructionData; pub use crate::{cpi_accounts::CpiAccountsConfig, CpiSigner}; // TODO: move all of this to light-sdk-types diff --git a/sdk-libs/sdk-types/src/interface/mod.rs b/sdk-libs/sdk-types/src/interface/mod.rs index 42da86b381..c53c7e886c 100644 --- a/sdk-libs/sdk-types/src/interface/mod.rs +++ b/sdk-libs/sdk-types/src/interface/mod.rs @@ -8,8 +8,8 @@ pub mod program; // LightCpi trait + CPI builder (no runtime dep) pub mod cpi; -// Client-side instruction building (not available on Solana BPF) -#[cfg(not(target_os = "solana"))] +// Client-side instruction building (not available on Solana BPF, requires std for HashMap) +#[cfg(all(not(target_os = "solana"), feature = "std"))] pub mod instruction; // --- Re-exports from light-compressible --- @@ -29,7 +29,7 @@ pub use account::compression_info::{ COMPRESSION_INFO_SIZE, OPTION_COMPRESSION_INFO_SPACE, }; pub use account::light_account::{AccountType, LightAccount}; -#[cfg(not(target_os = "solana"))] +#[cfg(all(not(target_os = "solana"), feature = "std"))] pub use account::pack::Pack; pub use account::pack::Unpack; pub use account::pda_seeds::{HasTokenVariant, PdaSeedDerivation}; diff --git a/sdk-libs/sdk-types/src/interface/program/compression/close.rs b/sdk-libs/sdk-types/src/interface/program/compression/close.rs index c50abd5ee1..c15aa191ab 100644 --- a/sdk-libs/sdk-types/src/interface/program/compression/close.rs +++ b/sdk-libs/sdk-types/src/interface/program/compression/close.rs @@ -5,5 +5,5 @@ use crate::error::{LightSdkTypesError, Result}; /// Close a native Solana account by transferring lamports and clearing data. pub fn close(info: &AI, sol_destination: &AI) -> Result<()> { light_account_checks::close_account(info, sol_destination) - .map_err(LightSdkTypesError::AccountCheck) + .map_err(LightSdkTypesError::AccountError) } diff --git a/sdk-libs/sdk-types/src/interface/program/compression/pda.rs b/sdk-libs/sdk-types/src/interface/program/compression/pda.rs index 4deabd2865..2825963565 100644 --- a/sdk-libs/sdk-types/src/interface/program/compression/pda.rs +++ b/sdk-libs/sdk-types/src/interface/program/compression/pda.rs @@ -60,11 +60,11 @@ where output_state_tree_index: compressed_account_meta.output_state_tree_index, }; - let current_slot = AI::get_current_slot().map_err(LightSdkTypesError::AccountCheck)?; + let current_slot = AI::get_current_slot().map_err(LightSdkTypesError::AccountError)?; let bytes = account_info.data_len() as u64; let current_lamports = account_info.lamports(); let rent_exemption_lamports = - AI::get_min_rent_balance(bytes as usize).map_err(LightSdkTypesError::AccountCheck)?; + AI::get_min_rent_balance(bytes as usize).map_err(LightSdkTypesError::AccountError)?; let ci = account_data.compression_info()?; let last_claimed_slot = ci.last_claimed_slot(); @@ -93,7 +93,7 @@ where { let mut data = account_info .try_borrow_mut_data() - .map_err(LightSdkTypesError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountError)?; // Write discriminator first data[..8].copy_from_slice(&A::LIGHT_DISCRIMINATOR); // Write serialized account data after discriminator diff --git a/sdk-libs/sdk-types/src/interface/program/compression/processor.rs b/sdk-libs/sdk-types/src/interface/program/compression/processor.rs index 410e03f2ba..ea78d9d287 100644 --- a/sdk-libs/sdk-types/src/interface/program/compression/processor.rs +++ b/sdk-libs/sdk-types/src/interface/program/compression/processor.rs @@ -1,5 +1,7 @@ //! Compression instruction processor. +use alloc::vec::Vec; + use crate::{ cpi_accounts::v2::CpiAccounts, instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, CpiSigner, diff --git a/sdk-libs/sdk-types/src/interface/program/config/create.rs b/sdk-libs/sdk-types/src/interface/program/config/create.rs index e2da7bfc32..7546b18197 100644 --- a/sdk-libs/sdk-types/src/interface/program/config/create.rs +++ b/sdk-libs/sdk-types/src/interface/program/config/create.rs @@ -1,5 +1,7 @@ //! Config initialization instructions (generic over AccountInfoTrait). +use alloc::vec::Vec; + use light_account_checks::{ checks::check_signer, discriminator::{Discriminator, DISCRIMINATOR_LEN}, @@ -62,7 +64,7 @@ pub fn process_initialize_light_config( validate_address_space_no_duplicates(&address_space)?; // CHECK: signer - check_signer(update_authority).map_err(LightSdkTypesError::AccountCheck)?; + check_signer(update_authority).map_err(LightSdkTypesError::AccountError)?; // CHECK: PDA derivation let (derived_pda, bump) = LightConfig::derive_pda_bytes::(program_id, config_bump); @@ -79,7 +81,7 @@ pub fn process_initialize_light_config( let account_size = LightConfig::size_for_address_space(address_space.len()); let rent_lamports = - AI::get_min_rent_balance(account_size).map_err(LightSdkTypesError::AccountCheck)?; + AI::get_min_rent_balance(account_size).map_err(LightSdkTypesError::AccountError)?; // Create PDA using AccountInfoTrait let config_bump_bytes = (config_bump as u16).to_le_bytes(); @@ -114,7 +116,7 @@ pub fn process_initialize_light_config( let mut data = config_account .try_borrow_mut_data() - .map_err(LightSdkTypesError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountError)?; // Write discriminator first data[..DISCRIMINATOR_LEN].copy_from_slice(&LightConfig::LIGHT_DISCRIMINATOR); @@ -145,7 +147,7 @@ pub fn check_program_upgrade_authority( let data = program_data_account .try_borrow_data() - .map_err(LightSdkTypesError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountError)?; if data.len() < PROGRAM_DATA_MIN_LEN { return Err(LightSdkTypesError::AccountDataTooSmall); @@ -179,7 +181,7 @@ pub fn check_program_upgrade_authority( }; // CHECK: authority is signer - check_signer(authority).map_err(LightSdkTypesError::AccountCheck)?; + check_signer(authority).map_err(LightSdkTypesError::AccountError)?; // CHECK: authority matches upgrade authority if authority.key() != upgrade_authority { diff --git a/sdk-libs/sdk-types/src/interface/program/config/mod.rs b/sdk-libs/sdk-types/src/interface/program/config/mod.rs index a07cd09d6b..f87abb8832 100644 --- a/sdk-libs/sdk-types/src/interface/program/config/mod.rs +++ b/sdk-libs/sdk-types/src/interface/program/config/mod.rs @@ -1,5 +1,7 @@ //! LightConfig management for compressible accounts. +use alloc::vec::Vec; + use light_account_checks::AccountInfoTrait; use light_compressible::rent::RentConfig; @@ -123,8 +125,8 @@ pub fn process_update_light_config( pub(super) fn validate_address_space_no_duplicates( address_space: &[[u8; 32]], ) -> Result<(), LightSdkTypesError> { - use std::collections::HashSet; - let mut seen = HashSet::new(); + use alloc::collections::BTreeSet; + let mut seen = BTreeSet::new(); for pubkey in address_space { if !seen.insert(pubkey) { return Err(LightSdkTypesError::ConstraintViolation); diff --git a/sdk-libs/sdk-types/src/interface/program/config/state.rs b/sdk-libs/sdk-types/src/interface/program/config/state.rs index a7155fd8dd..61d413246d 100644 --- a/sdk-libs/sdk-types/src/interface/program/config/state.rs +++ b/sdk-libs/sdk-types/src/interface/program/config/state.rs @@ -1,5 +1,7 @@ //! LightConfig state struct and methods. +use alloc::vec::Vec; + use light_account_checks::{ checks::check_discriminator, discriminator::{Discriminator, DISCRIMINATOR_LEN}, diff --git a/sdk-libs/sdk-types/src/interface/program/config/update.rs b/sdk-libs/sdk-types/src/interface/program/config/update.rs index e0e382f1bb..2d11bc62d4 100644 --- a/sdk-libs/sdk-types/src/interface/program/config/update.rs +++ b/sdk-libs/sdk-types/src/interface/program/config/update.rs @@ -1,5 +1,7 @@ //! Config update instruction (generic over AccountInfoTrait). +use alloc::vec::Vec; + use light_account_checks::{ checks::check_signer, discriminator::DISCRIMINATOR_LEN, AccountInfoTrait, }; @@ -28,7 +30,7 @@ pub fn process_update_light_config( let mut config = LightConfig::load_checked(config_account, owner_program_id)?; // CHECK: signer - check_signer(authority).map_err(LightSdkTypesError::AccountCheck)?; + check_signer(authority).map_err(LightSdkTypesError::AccountError)?; // CHECK: authority if authority.key() != config.update_authority { @@ -64,7 +66,7 @@ pub fn process_update_light_config( let mut data = config_account .try_borrow_mut_data() - .map_err(LightSdkTypesError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountError)?; // Serialize after discriminator (discriminator is preserved from init) config .serialize(&mut &mut data[DISCRIMINATOR_LEN..]) diff --git a/sdk-libs/sdk-types/src/interface/program/decompression/create_token_account.rs b/sdk-libs/sdk-types/src/interface/program/decompression/create_token_account.rs index e561c53cb4..cbf1ff2ee2 100644 --- a/sdk-libs/sdk-types/src/interface/program/decompression/create_token_account.rs +++ b/sdk-libs/sdk-types/src/interface/program/decompression/create_token_account.rs @@ -2,6 +2,9 @@ //! //! Returns `(instruction_data, account_metas)` tuples for use with `AI::invoke_cpi()`. +use alloc::vec; +use alloc::vec::Vec; + use light_account_checks::CpiMeta; use light_token_interface::{ instructions::{ diff --git a/sdk-libs/sdk-types/src/interface/program/decompression/pda.rs b/sdk-libs/sdk-types/src/interface/program/decompression/pda.rs index fd6a3ee4a6..8dfc7aa0d4 100644 --- a/sdk-libs/sdk-types/src/interface/program/decompression/pda.rs +++ b/sdk-libs/sdk-types/src/interface/program/decompression/pda.rs @@ -10,6 +10,8 @@ use light_compressed_account::{ use light_compressible::DECOMPRESSED_PDA_DISCRIMINATOR; use light_hasher::{sha256::Sha256BE, Hasher}; +use alloc::vec::Vec; + use crate::interface::{ account::light_account::LightAccount, program::{ diff --git a/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs b/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs index 469e29d6f5..e35de53153 100644 --- a/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs +++ b/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs @@ -1,5 +1,9 @@ //! Decompression instruction processor. +#[cfg(feature = "token")] +use alloc::vec; +use alloc::vec::Vec; + use crate::{cpi_accounts::v2::CpiAccounts, instruction::PackedStateTreeInfo, CpiSigner}; use light_account_checks::AccountInfoTrait; use light_compressed_account::instruction_data::{ diff --git a/sdk-libs/sdk-types/src/interface/program/decompression/token.rs b/sdk-libs/sdk-types/src/interface/program/decompression/token.rs index c913ef3882..a54d08e221 100644 --- a/sdk-libs/sdk-types/src/interface/program/decompression/token.rs +++ b/sdk-libs/sdk-types/src/interface/program/decompression/token.rs @@ -1,5 +1,8 @@ //! Token account decompression. +use alloc::vec; +use alloc::vec::Vec; + use crate::instruction::PackedStateTreeInfo; use light_account_checks::AccountInfoTrait; use light_token_interface::{ diff --git a/sdk-libs/sdk-types/src/interface/program/validation.rs b/sdk-libs/sdk-types/src/interface/program/validation.rs index 66be522ed3..1b669afe7c 100644 --- a/sdk-libs/sdk-types/src/interface/program/validation.rs +++ b/sdk-libs/sdk-types/src/interface/program/validation.rs @@ -56,19 +56,19 @@ where let fee_payer = account_iter .next_signer_mut("fee_payer") - .map_err(LightSdkTypesError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountError)?; let config = account_iter .next_non_mut("config") - .map_err(LightSdkTypesError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountError)?; let rent_sponsor = account_iter .next_mut("rent_sponsor") - .map_err(LightSdkTypesError::AccountCheck)?; + .map_err(LightSdkTypesError::AccountError)?; let compression_authority = if EXTRACT_COMPRESSION_AUTHORITY { Some( account_iter .next_account("compression_authority") - .map_err(LightSdkTypesError::AccountCheck)? + .map_err(LightSdkTypesError::AccountError)? .clone(), ) } else { diff --git a/sdk-libs/sdk-types/src/interface/program/variant.rs b/sdk-libs/sdk-types/src/interface/program/variant.rs index eeaeaba069..85712d08e9 100644 --- a/sdk-libs/sdk-types/src/interface/program/variant.rs +++ b/sdk-libs/sdk-types/src/interface/program/variant.rs @@ -5,6 +5,8 @@ //! - Variant traits (`LightAccountVariantTrait`, `PackedLightAccountVariantTrait`) - always available //! - Token seed traits (`UnpackedTokenSeeds`, `PackedTokenSeeds`) - behind `token` feature +use alloc::vec::Vec; + use light_account_checks::AccountInfoTrait; use crate::interface::account::light_account::AccountType; @@ -128,6 +130,7 @@ pub trait PackedLightAccountVariantTrait: #[cfg(feature = "token")] mod token_traits { + use alloc::vec::Vec; use light_account_checks::AccountInfoTrait; use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; diff --git a/sdk-libs/sdk-types/src/lib.rs b/sdk-libs/sdk-types/src/lib.rs index 02013e8df4..1bf497be86 100644 --- a/sdk-libs/sdk-types/src/lib.rs +++ b/sdk-libs/sdk-types/src/lib.rs @@ -12,7 +12,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -#[cfg(all(not(feature = "std"), feature = "alloc"))] +#[cfg(feature = "alloc")] extern crate alloc; pub mod address; @@ -22,7 +22,7 @@ pub mod cpi_context_write; pub mod error; pub mod instruction; -#[cfg(feature = "std")] +#[cfg(feature = "alloc")] pub mod interface; // Re-exports diff --git a/sdk-libs/sdk/src/error.rs b/sdk-libs/sdk/src/error.rs index 8f3f9ed187..0f44812889 100644 --- a/sdk-libs/sdk/src/error.rs +++ b/sdk-libs/sdk/src/error.rs @@ -137,7 +137,7 @@ impl From for LightSdkTypesError { match e { LightSdkError::ConstraintViolation => LightSdkTypesError::ConstraintViolation, LightSdkError::Borsh => LightSdkTypesError::Borsh, - LightSdkError::AccountError(e) => LightSdkTypesError::AccountCheck(e), + LightSdkError::AccountError(e) => LightSdkTypesError::AccountError(e), LightSdkError::Hasher(e) => LightSdkTypesError::Hasher(e), LightSdkError::MissingCompressionInfo => LightSdkTypesError::MissingCompressionInfo, LightSdkError::InvalidRentSponsor => LightSdkTypesError::InvalidRentSponsor, @@ -187,7 +187,7 @@ impl From for LightSdkError { LightSdkTypesError::InvalidCpiAccountsOffset => { LightSdkError::InvalidCpiAccountsOffset } - LightSdkTypesError::AccountCheck(e) => LightSdkError::AccountError(e), + LightSdkTypesError::AccountError(e) => LightSdkError::AccountError(e), LightSdkTypesError::Hasher(e) => LightSdkError::Hasher(e), LightSdkTypesError::ConstraintViolation => LightSdkError::ConstraintViolation, LightSdkTypesError::Borsh => LightSdkError::Borsh, diff --git a/sdk-libs/token-sdk/src/compressible/compress_runtime.rs b/sdk-libs/token-sdk/src/compressible/compress_runtime.rs deleted file mode 100644 index e44b7a4d73..0000000000 --- a/sdk-libs/token-sdk/src/compressible/compress_runtime.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! Runtime helpers for compressing PDAs to Light Protocol. - -use light_compressed_account::instruction_data::{ - cpi_context::CompressedCpiContext, - data::NewAddressParamsAssignedPacked, - with_account_info::{CompressedAccountInfo, InstructionDataInvokeCpiWithAccountInfo}, -}; -use light_sdk::cpi::{v2::CpiAccounts, InvokeLightSystemProgram}; -use light_sdk_types::CpiSigner; -use solana_program_error::ProgramError; - -use crate::error::LightTokenError; -// TODO: rename file -/// Write PDAs to CPI context for chaining with mint operations. -/// -/// Use this when PDAs need to be written to CPI context first, which will be -/// consumed by subsequent mint operations (e.g., CreateMintsCpi). -/// -/// # Arguments -/// * `cpi_signer` - CPI signer for the invoking program -/// * `proof` - Validity proof for the compression operation -/// * `new_addresses` - New address parameters for each PDA -/// * `compressed_infos` - Compressed account info for each PDA -/// * `cpi_accounts` - CPI accounts with CPI context enabled -pub fn invoke_write_pdas_to_cpi_context<'info>( - cpi_signer: CpiSigner, - proof: light_sdk::instruction::ValidityProof, - new_addresses: &[NewAddressParamsAssignedPacked], - compressed_infos: &[CompressedAccountInfo], - cpi_accounts: &CpiAccounts<'_, 'info>, -) -> Result<(), ProgramError> { - let cpi_context_accounts = light_sdk_types::cpi_context_write::CpiContextWriteAccounts { - fee_payer: cpi_accounts.fee_payer(), - authority: cpi_accounts - .authority() - .map_err(|_| LightTokenError::MissingCpiAuthority)?, - cpi_context: cpi_accounts - .cpi_context() - .map_err(|_| LightTokenError::MissingCpiContext)?, - cpi_signer, - }; - - let instruction_data = InstructionDataInvokeCpiWithAccountInfo { - mode: 1, - bump: cpi_signer.bump, - invoking_program_id: cpi_signer.program_id.into(), - compress_or_decompress_lamports: 0, - is_compress: false, - with_cpi_context: true, - with_transaction_hash: false, - cpi_context: CompressedCpiContext::first(), - proof: proof.0, - new_address_params: new_addresses.to_vec(), - account_infos: compressed_infos.to_vec(), - read_only_addresses: vec![], - read_only_accounts: vec![], - }; - - instruction_data - .invoke_write_to_cpi_context_first(cpi_context_accounts) - .map_err(|e| ProgramError::Custom(u32::from(e)))?; - - Ok(()) -} diff --git a/sdk-libs/token-sdk/src/compressible/mod.rs b/sdk-libs/token-sdk/src/compressible/mod.rs index 31cdee3b36..12a2dbe286 100644 --- a/sdk-libs/token-sdk/src/compressible/mod.rs +++ b/sdk-libs/token-sdk/src/compressible/mod.rs @@ -1,9 +1,7 @@ //! Compressible token utilities for runtime compression and decompression. -mod compress_runtime; mod mint_runtime; -pub use compress_runtime::*; pub use mint_runtime::*; use solana_account_info::AccountInfo; From d3f3adbeadd0a453f8871b9b230c7cbac6ab755f Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 2 Feb 2026 04:59:15 +0000 Subject: [PATCH 07/15] feat: add tokens to light-sdk-types --- Cargo.lock | 2 + sdk-libs/account-pinocchio/Cargo.toml | 3 +- sdk-libs/account-pinocchio/src/lib.rs | 53 ++ sdk-libs/account/Cargo.toml | 3 +- sdk-libs/account/src/lib.rs | 57 ++ .../macros/src/light_pdas/accounts/mint.rs | 53 +- .../src/interface/cpi/create_mints.rs | 860 ++++++++++++++++++ .../interface/cpi/create_token_accounts.rs | 452 +++++++++ sdk-libs/sdk-types/src/interface/cpi/mod.rs | 4 + sdk-tests/manual-test/Cargo.toml | 7 +- sdk-tests/manual-test/src/all/derived.rs | 97 +- sdk-tests/manual-test/src/ata/derived.rs | 27 +- .../manual-test/src/token_account/derived.rs | 24 +- .../manual-test/src/two_mints/derived.rs | 73 +- 14 files changed, 1588 insertions(+), 127 deletions(-) create mode 100644 sdk-libs/sdk-types/src/interface/cpi/create_mints.rs create mode 100644 sdk-libs/sdk-types/src/interface/cpi/create_token_accounts.rs diff --git a/Cargo.lock b/Cargo.lock index 5c62849421..f24003a0e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3471,6 +3471,7 @@ dependencies = [ "light-macros", "light-sdk-macros", "light-sdk-types", + "light-token-interface", "solana-account-info", "solana-instruction", "solana-pubkey 2.4.0", @@ -3505,6 +3506,7 @@ dependencies = [ "light-macros", "light-sdk-macros", "light-sdk-types", + "light-token-interface", "pinocchio", ] diff --git a/sdk-libs/account-pinocchio/Cargo.toml b/sdk-libs/account-pinocchio/Cargo.toml index 2a521d7ea6..f21ef36af9 100644 --- a/sdk-libs/account-pinocchio/Cargo.toml +++ b/sdk-libs/account-pinocchio/Cargo.toml @@ -10,7 +10,7 @@ edition = "2021" default = ["alloc"] std = ["alloc", "light-sdk-types/std", "light-compressed-account/std"] alloc = ["light-sdk-types/alloc", "light-compressed-account/alloc"] -token = ["light-sdk-types/token"] +token = ["light-sdk-types/token", "dep:light-token-interface"] poseidon = ["light-sdk-types/poseidon", "light-hasher/poseidon"] sha256 = ["light-sdk-types/sha256", "light-hasher/sha256"] @@ -21,6 +21,7 @@ light-macros = { workspace = true } light-account-checks = { workspace = true, default-features = false, features = ["pinocchio"] } light-hasher = { workspace = true, default-features = false } light-compressed-account = { workspace = true, default-features = false } +light-token-interface = { workspace = true, optional = true } pinocchio = { workspace = true } [lints.rust.unexpected_cfgs] diff --git a/sdk-libs/account-pinocchio/src/lib.rs b/sdk-libs/account-pinocchio/src/lib.rs index 568fed00f4..0ba975b587 100644 --- a/sdk-libs/account-pinocchio/src/lib.rs +++ b/sdk-libs/account-pinocchio/src/lib.rs @@ -98,6 +98,31 @@ pub use light_sdk_types::interface::program::decompression::token::prepare_token #[cfg(feature = "token")] pub use light_sdk_types::interface::program::variant::{PackedTokenSeeds, UnpackedTokenSeeds}; +// Mint creation CPI types and functions +#[cfg(feature = "token")] +pub use light_sdk_types::interface::cpi::create_mints::{ + CreateMintsCpi, CreateMintsInfraAccounts, CreateMintsParams, SingleMintParams, + get_output_queue_next_index, invoke_create_mints, + DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, + derive_mint_compressed_address as derive_mint_compressed_address_generic, +}; + +// Token account/ATA creation CPI types and functions +#[cfg(feature = "token")] +pub use light_sdk_types::interface::cpi::create_token_accounts::{ + CreateTokenAccountCpi, CreateTokenAccountRentFreeCpi, + CreateTokenAtaCpi, CreateTokenAtaCpiIdempotent, CreateTokenAtaRentFreeCpi, + derive_associated_token_account as derive_associated_token_account_generic, +}; + +// Token-interface re-exports for macro-generated code +#[cfg(feature = "token")] +pub use light_token_interface::instructions::extensions::TokenMetadataInstructionData; +#[cfg(feature = "token")] +pub use light_token_interface::instructions::extensions::ExtensionInstructionData as TokenExtensionInstructionData; +#[cfg(feature = "token")] +pub use light_compressed_account::instruction_data::compressed_proof::CompressedProof; + #[cfg(feature = "token")] pub mod token { pub use light_sdk_types::interface::account::token_seeds::{ @@ -159,3 +184,31 @@ pub fn derive_rent_sponsor_pda(program_id: &[u8; 32]) -> ([u8; 32], u8) { program_id, ) } + +/// Find the mint PDA address for a given mint seed. +/// +/// Returns `([u8; 32], u8)` -- the PDA address and bump. +#[cfg(feature = "token")] +pub fn find_mint_address(mint_seed: &[u8; 32]) -> ([u8; 32], u8) { + light_sdk_types::interface::cpi::create_mints::find_mint_address::(mint_seed) +} + +/// Derive the compressed mint address from a mint seed and address tree pubkey. +#[cfg(feature = "token")] +pub fn derive_mint_compressed_address( + mint_seed: &[u8; 32], + address_tree_pubkey: &[u8; 32], +) -> [u8; 32] { + derive_mint_compressed_address_generic::(mint_seed, address_tree_pubkey) +} + +/// Derive the associated token account address for a given owner and mint. +/// +/// Returns `([u8; 32], u8)` -- the ATA address and bump seed. +#[cfg(feature = "token")] +pub fn derive_associated_token_account( + owner: &[u8; 32], + mint: &[u8; 32], +) -> ([u8; 32], u8) { + derive_associated_token_account_generic::(owner, mint) +} diff --git a/sdk-libs/account/Cargo.toml b/sdk-libs/account/Cargo.toml index 61a4a827f9..be440c367c 100644 --- a/sdk-libs/account/Cargo.toml +++ b/sdk-libs/account/Cargo.toml @@ -10,7 +10,7 @@ edition = "2021" default = ["std"] std = ["light-sdk-types/std", "light-compressed-account/std"] alloc = ["light-sdk-types/alloc", "light-compressed-account/alloc"] -token = ["light-sdk-types/token"] +token = ["light-sdk-types/token", "dep:light-token-interface"] poseidon = ["light-sdk-types/poseidon", "light-hasher/poseidon"] sha256 = ["light-sdk-types/sha256", "light-hasher/sha256"] anchor = ["light-sdk-types/anchor"] @@ -22,6 +22,7 @@ light-macros = { workspace = true } light-account-checks = { workspace = true, features = ["solana"] } light-hasher = { workspace = true, default-features = false } light-compressed-account = { workspace = true } +light-token-interface = { workspace = true, optional = true } solana-account-info = { workspace = true } solana-instruction = { workspace = true } solana-pubkey = { workspace = true } diff --git a/sdk-libs/account/src/lib.rs b/sdk-libs/account/src/lib.rs index dc89d1ae33..e4dab4bef1 100644 --- a/sdk-libs/account/src/lib.rs +++ b/sdk-libs/account/src/lib.rs @@ -99,6 +99,31 @@ pub use light_sdk_types::interface::program::decompression::token::prepare_token #[cfg(feature = "token")] pub use light_sdk_types::interface::program::variant::{PackedTokenSeeds, UnpackedTokenSeeds}; +// Mint creation CPI types and functions +#[cfg(feature = "token")] +pub use light_sdk_types::interface::cpi::create_mints::{ + CreateMintsCpi, CreateMintsInfraAccounts, CreateMintsParams, SingleMintParams, + get_output_queue_next_index, invoke_create_mints, + DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, + derive_mint_compressed_address as derive_mint_compressed_address_generic, +}; + +// Token account/ATA creation CPI types and functions +#[cfg(feature = "token")] +pub use light_sdk_types::interface::cpi::create_token_accounts::{ + CreateTokenAccountCpi, CreateTokenAccountRentFreeCpi, + CreateTokenAtaCpi, CreateTokenAtaCpiIdempotent, CreateTokenAtaRentFreeCpi, + derive_associated_token_account as derive_associated_token_account_generic, +}; + +// Token-interface re-exports for macro-generated code +#[cfg(feature = "token")] +pub use light_token_interface::instructions::extensions::TokenMetadataInstructionData; +#[cfg(feature = "token")] +pub use light_token_interface::instructions::extensions::ExtensionInstructionData as TokenExtensionInstructionData; +#[cfg(feature = "token")] +pub use light_compressed_account::instruction_data::compressed_proof::CompressedProof; + /// Token sub-module for paths like `light_account::token::TokenDataWithSeeds`. #[cfg(feature = "token")] pub mod token { @@ -175,3 +200,35 @@ pub use light_sdk_types::{constants, CpiSigner}; pub fn derive_rent_sponsor_pda(program_id: &solana_pubkey::Pubkey) -> (solana_pubkey::Pubkey, u8) { solana_pubkey::Pubkey::find_program_address(&[constants::RENT_SPONSOR_SEED], program_id) } + +/// Find the mint PDA address for a given mint seed. +/// +/// Returns `([u8; 32], u8)` -- the PDA address and bump. +#[cfg(feature = "token")] +pub fn find_mint_address(mint_seed: &[u8; 32]) -> ([u8; 32], u8) { + light_sdk_types::interface::cpi::create_mints::find_mint_address::>( + mint_seed, + ) +} + +/// Derive the compressed mint address from a mint seed and address tree pubkey. +#[cfg(feature = "token")] +pub fn derive_mint_compressed_address( + mint_seed: &[u8; 32], + address_tree_pubkey: &[u8; 32], +) -> [u8; 32] { + derive_mint_compressed_address_generic::>(mint_seed, address_tree_pubkey) +} + +/// Derive the associated token account address for a given owner and mint. +/// +/// Returns `(Pubkey, u8)` -- the ATA address and bump seed. +#[cfg(feature = "token")] +pub fn derive_associated_token_account( + owner: &solana_pubkey::Pubkey, + mint: &solana_pubkey::Pubkey, +) -> (solana_pubkey::Pubkey, u8) { + let (bytes, bump) = + derive_associated_token_account_generic::>(&owner.to_bytes(), &mint.to_bytes()); + (solana_pubkey::Pubkey::from(bytes), bump) +} diff --git a/sdk-libs/macros/src/light_pdas/accounts/mint.rs b/sdk-libs/macros/src/light_pdas/accounts/mint.rs index d04f7618a4..5e06e4ac5c 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/mint.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/mint.rs @@ -205,7 +205,7 @@ fn generate_mints_invocation(builder: &LightMintsBuilder) -> TokenStream { let freeze_authority = mint .freeze_authority .as_ref() - .map(|f| quote! { Some(*self.#f.to_account_info().key) }) + .map(|f| quote! { Some(self.#f.to_account_info().key.to_bytes()) }) .unwrap_or_else(|| quote! { None }); let mint_seeds = &mint.mint_seeds; let authority_seeds = &mint.authority_seeds; @@ -285,8 +285,8 @@ fn generate_mints_invocation(builder: &LightMintsBuilder) -> TokenStream { .unwrap_or_else(|| quote! { None }); quote! { - let #token_metadata_ident: Option = Some( - light_token::TokenMetadataInstructionData { + let #token_metadata_ident: Option = Some( + light_account::TokenMetadataInstructionData { update_authority: #update_authority_expr, name: #name_expr, symbol: #symbol_expr, @@ -297,14 +297,14 @@ fn generate_mints_invocation(builder: &LightMintsBuilder) -> TokenStream { } } else { quote! { - let #token_metadata_ident: Option = None; + let #token_metadata_ident: Option = None; } }; quote! { // Mint #idx: derive PDA and build params - let #signer_key_ident = *self.#mint_signer.to_account_info().key; - let (#pda_ident, #bump_ident) = light_token::instruction::find_mint_address(&#signer_key_ident); + let #signer_key_ident: [u8; 32] = self.#mint_signer.to_account_info().key.to_bytes(); + let (#pda_ident, #bump_ident) = light_account::find_mint_address(&#signer_key_ident); // Bind base mint_seeds (WITHOUT bump) and derive/get bump let #mint_seeds_ident: &[&[u8]] = #mint_seeds; @@ -319,11 +319,11 @@ fn generate_mints_invocation(builder: &LightMintsBuilder) -> TokenStream { let __tree_info = &#address_tree_info; - let #idx_ident = light_token::instruction::SingleMintParams { + let #idx_ident = light_account::SingleMintParams { decimals: #decimals, address_merkle_tree_root_index: __tree_info.root_index, - mint_authority: *self.#authority.to_account_info().key, - compression_address: #pda_ident.to_bytes(), + mint_authority: self.#authority.to_account_info().key.to_bytes(), + compression_address: #pda_ident, mint: #pda_ident, bump: #bump_ident, freeze_authority: #freeze_authority, @@ -381,19 +381,32 @@ fn generate_mints_invocation(builder: &LightMintsBuilder) -> TokenStream { }) .collect(); + // Generate base_leaf_index reading for N>1 mints + let base_leaf_index_setup = if mint_count > 1 { + quote! { + let __base_leaf_index = light_account::get_output_queue_next_index( + cpi_accounts.get_tree_account_info(__output_queue_index as usize)? + )?; + } + } else { + quote! { + let __base_leaf_index: u32 = 0; + } + }; + quote! { { #output_tree_setup // Extract proof from instruction params - let __proof: light_token::CompressedProof = #proof_access.proof.0.clone() + let __proof: light_account::CompressedProof = #proof_access.proof.0.clone() .expect("proof is required for mint creation"); // Build SingleMintParams for each mint #(#mint_params_builds)* // Array of mint params - let __mint_params: [light_token::instruction::SingleMintParams<'_>; #mint_count] = [ + let __mint_params: [light_account::SingleMintParams<'_>; #mint_count] = [ #(#param_idents),* ]; @@ -414,14 +427,17 @@ fn generate_mints_invocation(builder: &LightMintsBuilder) -> TokenStream { .ok_or(anchor_lang::prelude::ProgramError::InvalidArgument)?; let __address_tree_index: u8 = __tree_info.address_merkle_tree_pubkey_index; + // Read base_leaf_index from output queue (needed for N>1 mints) + #base_leaf_index_setup + // Check authority signers for mints without authority_seeds #(#authority_signer_checks)* // Build params and invoke CreateMintsCpi via helper - light_token::compressible::invoke_create_mints( + light_account::invoke_create_mints( &__mint_seed_accounts, &__mint_accounts, - light_token::instruction::CreateMintsParams { + light_account::CreateMintsParams { mints: &__mint_params, proof: __proof, rent_payment: #rent_payment, @@ -430,12 +446,13 @@ fn generate_mints_invocation(builder: &LightMintsBuilder) -> TokenStream { output_queue_index: __output_queue_index, address_tree_index: __address_tree_index, state_tree_index: __state_tree_index, + base_leaf_index: __base_leaf_index, }, - light_token::compressible::CreateMintsInfraAccounts { - fee_payer: self.#fee_payer.to_account_info(), - compressible_config: self.#light_token_config.to_account_info(), - rent_sponsor: self.#light_token_rent_sponsor.to_account_info(), - cpi_authority: self.#light_token_cpi_authority.to_account_info(), + light_account::CreateMintsInfraAccounts { + fee_payer: &self.#fee_payer.to_account_info(), + compressible_config: &self.#light_token_config.to_account_info(), + rent_sponsor: &self.#light_token_rent_sponsor.to_account_info(), + cpi_authority: &self.#light_token_cpi_authority.to_account_info(), }, &cpi_accounts, )?; diff --git a/sdk-libs/sdk-types/src/interface/cpi/create_mints.rs b/sdk-libs/sdk-types/src/interface/cpi/create_mints.rs new file mode 100644 index 0000000000..ae5a68700d --- /dev/null +++ b/sdk-libs/sdk-types/src/interface/cpi/create_mints.rs @@ -0,0 +1,860 @@ +//! Generic CPI for creating multiple compressed mints. +//! +//! This module provides framework-agnostic mint creation via `CreateMintsCpi`, +//! generic over `AccountInfoTrait`. Account order matches the cToken program +//! expectations (see `MintActionMetaConfig::to_account_metas` for reference). +//! +//! # Flow +//! +//! - N=1 (no CPI context offset): Single CPI (create + decompress) +//! - N>1 or offset>0: 2N-1 CPIs (N-1 writes + 1 execute with decompress + N-1 decompress) + +use alloc::vec; +use alloc::vec::Vec; + +use light_account_checks::{AccountInfoTrait, CpiMeta}; +use light_compressed_account::instruction_data::{ + compressed_proof::CompressedProof, traits::LightInstructionData, +}; +use light_token_interface::{ + instructions::{ + extensions::{ExtensionInstructionData, TokenMetadataInstructionData}, + mint_action::{ + Action, CpiContext, CreateMint, DecompressMintAction, + MintActionCompressedInstructionData, MintInstructionData, + }, + }, + state::MintMetadata, + COMPRESSED_MINT_SEED, LIGHT_TOKEN_PROGRAM_ID, +}; + +use crate::error::LightSdkTypesError; + +/// Default rent payment epochs (~24 hours). +pub const DEFAULT_RENT_PAYMENT: u8 = 16; +/// Default lamports for write operations (~3 hours per write). +pub const DEFAULT_WRITE_TOP_UP: u32 = 766; + +// ============================================================================ +// Types +// ============================================================================ + +/// Parameters for a single mint within a batch creation. +/// +/// All pubkeys are `[u8; 32]` for framework independence. +#[derive(Debug, Clone)] +pub struct SingleMintParams<'a> { + pub decimals: u8, + pub address_merkle_tree_root_index: u16, + pub mint_authority: [u8; 32], + pub compression_address: [u8; 32], + pub mint: [u8; 32], + pub bump: u8, + pub freeze_authority: Option<[u8; 32]>, + /// Mint seed pubkey (signer) for this mint. + pub mint_seed_pubkey: [u8; 32], + /// Optional authority seeds for PDA signing. + pub authority_seeds: Option<&'a [&'a [u8]]>, + /// Optional mint signer seeds for PDA signing. + pub mint_signer_seeds: Option<&'a [&'a [u8]]>, + /// Optional token metadata for the mint (reference to avoid stack overflow). + pub token_metadata: Option<&'a TokenMetadataInstructionData>, +} + +/// Parameters for creating one or more compressed mints with decompression. +/// +/// Creates N compressed mints and decompresses all to Solana Mint accounts. +/// Uses CPI context pattern when N > 1 for efficiency. +#[derive(Debug, Clone)] +pub struct CreateMintsParams<'a> { + /// Parameters for each mint to create. + pub mints: &'a [SingleMintParams<'a>], + /// Single proof covering all new addresses. + pub proof: CompressedProof, + /// Rent payment in epochs for the Mint account (must be 0 or >= 2). + /// Default: 16 (~24 hours). + pub rent_payment: u8, + /// Lamports allocated for future write operations. + /// Default: 766 (~3 hours per write). + pub write_top_up: u32, + /// Offset for assigned_account_index when sharing CPI context with other accounts. + /// When creating mints alongside PDAs, this offset should be set to the number of + /// PDAs already written to the CPI context. + /// Default: 0 (no offset). + pub cpi_context_offset: u8, + /// Index of the output queue in tree accounts. + /// Default: 0. + pub output_queue_index: u8, + /// Index of the address merkle tree in tree accounts. + /// Default: 1. + pub address_tree_index: u8, + /// Index of the state merkle tree in tree accounts. + /// Required for decompress operations (discriminator validation). + /// Default: 2. + pub state_tree_index: u8, + /// Base leaf index from the output queue (required when N > 1). + /// Read from the queue's batch_metadata.next_index before any CPIs. + /// For N=1 with offset=0, pass 0. + pub base_leaf_index: u32, +} + +/// Infrastructure accounts needed for mint creation CPI. +/// +/// These accounts are passed from the user's Accounts struct. +pub struct CreateMintsInfraAccounts<'a, AI: AccountInfoTrait + Clone> { + /// Fee payer for the transaction. + pub fee_payer: &'a AI, + /// CompressibleConfig account for the light-token program. + pub compressible_config: &'a AI, + /// Rent sponsor PDA. + pub rent_sponsor: &'a AI, + /// CPI authority PDA for signing. + pub cpi_authority: &'a AI, +} + +/// CPI struct for creating multiple compressed mints. +/// +/// Generic over `AccountInfoTrait` to work with both solana and pinocchio backends. +/// Uses named account fields for clarity and safety. +pub struct CreateMintsCpi<'a, AI: AccountInfoTrait + Clone> { + /// Mint seed accounts (signers) - one per mint. + pub mint_seed_accounts: &'a [AI], + /// Fee payer (also used as authority). + pub payer: &'a AI, + /// Address tree for new mint addresses. + pub address_tree: &'a AI, + /// Output queue for compressed accounts. + pub output_queue: &'a AI, + /// State merkle tree (required for decompress discriminator validation). + pub state_merkle_tree: &'a AI, + /// CompressibleConfig account. + pub compressible_config: &'a AI, + /// Mint PDA accounts (writable) - one per mint. + pub mints: &'a [AI], + /// Rent sponsor PDA. + pub rent_sponsor: &'a AI, + /// Light system program. + pub light_system_program: &'a AI, + /// CPI authority PDA. + pub cpi_authority_pda: &'a AI, + /// Registered program PDA. + pub registered_program_pda: &'a AI, + /// Account compression authority. + pub account_compression_authority: &'a AI, + /// Account compression program. + pub account_compression_program: &'a AI, + /// System program. + pub system_program: &'a AI, + /// CPI context account. + pub cpi_context_account: &'a AI, + /// Parameters. + pub params: CreateMintsParams<'a>, +} + +impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { + /// Validate that the struct is properly constructed. + #[inline(never)] + fn validate(&self) -> Result<(), LightSdkTypesError> { + let n = self.params.mints.len(); + if n == 0 { + return Err(LightSdkTypesError::InvalidInstructionData); + } + if self.mint_seed_accounts.len() != n { + return Err(LightSdkTypesError::InvalidInstructionData); + } + if self.mints.len() != n { + return Err(LightSdkTypesError::InvalidInstructionData); + } + Ok(()) + } + + /// Execute all CPIs to create and decompress all mints. + #[inline(never)] + pub fn invoke(self) -> Result<(), LightSdkTypesError> { + self.validate()?; + let n = self.params.mints.len(); + + // Use single mint path only when: + // - N=1 AND + // - No CPI context offset (no PDAs were written to CPI context first) + if n == 1 && self.params.cpi_context_offset == 0 { + self.invoke_single_mint() + } else { + self.invoke_multiple_mints() + } + } + + /// Handle the single mint case: create + decompress in one CPI. + #[inline(never)] + fn invoke_single_mint(self) -> Result<(), LightSdkTypesError> { + let mint_params = &self.params.mints[0]; + + let mint_data = build_mint_instruction_data(mint_params, &self.mint_seed_accounts[0].key()); + + let decompress_action = DecompressMintAction { + rent_payment: self.params.rent_payment, + write_top_up: self.params.write_top_up, + }; + + let instruction_data = MintActionCompressedInstructionData::new_mint( + mint_params.address_merkle_tree_root_index, + self.params.proof, + mint_data, + ) + .with_decompress_mint(decompress_action); + + let ix_data = instruction_data + .data() + .map_err(|_| LightSdkTypesError::Borsh)?; + + // Account metas for create_mint + decompress (single mint path) + // Order matches MintActionMetaConfig::to_account_metas with compressible_mint + input_queue + let metas = self.build_mint_action_metas(0, true, true, false); + let account_infos = self.collect_mint_action_infos(0); + + self.invoke_mint_action_raw(&ix_data, &account_infos, &metas, 0) + } + + /// Handle the multiple mints case: N-1 writes + 1 execute + N-1 decompress. + #[inline(never)] + fn invoke_multiple_mints(self) -> Result<(), LightSdkTypesError> { + let n = self.params.mints.len(); + let base_leaf_index = self.params.base_leaf_index; + + let decompress_action = DecompressMintAction { + rent_payment: self.params.rent_payment, + write_top_up: self.params.write_top_up, + }; + + // Write mints 0..N-2 to CPI context + for i in 0..(n - 1) { + self.invoke_cpi_write(i)?; + } + + // Execute: create last mint + decompress it + self.invoke_execute(n - 1, &decompress_action)?; + + // Decompress remaining mints (0..N-2) + for i in 0..(n - 1) { + self.invoke_decompress(i, base_leaf_index, &decompress_action)?; + } + + Ok(()) + } + + /// Invoke a CPI write instruction for a single mint. + #[inline(never)] + fn invoke_cpi_write(&self, index: usize) -> Result<(), LightSdkTypesError> { + let mint_params = &self.params.mints[index]; + let offset = self.params.cpi_context_offset; + + let cpi_context = CpiContext { + set_context: index > 0 || offset > 0, + first_set_context: index == 0 && offset == 0, + in_tree_index: self.params.address_tree_index, + in_queue_index: self.params.output_queue_index, + out_queue_index: self.params.output_queue_index, + token_out_queue_index: 0, + assigned_account_index: offset + index as u8, + read_only_address_trees: [0; 4], + address_tree_pubkey: self.address_tree.key(), + }; + + let mint_data = + build_mint_instruction_data(mint_params, &self.mint_seed_accounts[index].key()); + + let instruction_data = MintActionCompressedInstructionData::new_mint_write_to_cpi_context( + mint_params.address_merkle_tree_root_index, + mint_data, + cpi_context, + ); + + let ix_data = instruction_data + .data() + .map_err(|_| LightSdkTypesError::Borsh)?; + + // CPI write account order: + // [0]: light_system_program + // [1]: mint_signer + // [2]: authority (payer) + // [3]: fee_payer (payer) + // [4]: cpi_authority_pda + // [5]: cpi_context + let metas = vec![ + CpiMeta { + pubkey: self.light_system_program.key(), + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: self.mint_seed_accounts[index].key(), + is_signer: true, + is_writable: false, + }, + CpiMeta { + pubkey: self.payer.key(), + is_signer: true, + is_writable: false, + }, + CpiMeta { + pubkey: self.payer.key(), + is_signer: true, + is_writable: true, + }, + CpiMeta { + pubkey: self.cpi_authority_pda.key(), + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: self.cpi_context_account.key(), + is_signer: false, + is_writable: true, + }, + ]; + + let account_infos = vec![ + self.light_system_program.clone(), + self.mint_seed_accounts[index].clone(), + self.payer.clone(), + self.payer.clone(), + self.cpi_authority_pda.clone(), + self.cpi_context_account.clone(), + ]; + + self.invoke_mint_action_raw(&ix_data, &account_infos, &metas, index) + } + + /// Invoke the execute instruction (create last mint + decompress). + #[inline(never)] + fn invoke_execute( + &self, + last_idx: usize, + decompress_action: &DecompressMintAction, + ) -> Result<(), LightSdkTypesError> { + let mint_params = &self.params.mints[last_idx]; + let offset = self.params.cpi_context_offset; + + let mint_data = + build_mint_instruction_data(mint_params, &self.mint_seed_accounts[last_idx].key()); + + let instruction_data = MintActionCompressedInstructionData { + leaf_index: 0, + prove_by_index: false, + root_index: mint_params.address_merkle_tree_root_index, + max_top_up: 0, + create_mint: Some(CreateMint::default()), + actions: vec![Action::DecompressMint(*decompress_action)], + proof: Some(self.params.proof), + cpi_context: Some(CpiContext { + set_context: false, + first_set_context: false, + in_tree_index: self.params.address_tree_index, + in_queue_index: self.params.address_tree_index, + out_queue_index: self.params.output_queue_index, + token_out_queue_index: 0, + assigned_account_index: offset + last_idx as u8, + read_only_address_trees: [0; 4], + address_tree_pubkey: self.address_tree.key(), + }), + mint: Some(mint_data), + }; + + let ix_data = instruction_data + .data() + .map_err(|_| LightSdkTypesError::Borsh)?; + + // Execute uses full account set with cpi_context + input_queue + let metas = self.build_mint_action_metas(last_idx, true, true, true); + let account_infos = self.collect_mint_action_infos(last_idx); + + self.invoke_mint_action_raw(&ix_data, &account_infos, &metas, last_idx) + } + + /// Invoke decompress for a single mint. + #[inline(never)] + fn invoke_decompress( + &self, + index: usize, + base_leaf_index: u32, + decompress_action: &DecompressMintAction, + ) -> Result<(), LightSdkTypesError> { + let mint_params = &self.params.mints[index]; + + let mint_data = + build_mint_instruction_data(mint_params, &self.mint_seed_accounts[index].key()); + + let instruction_data = MintActionCompressedInstructionData { + leaf_index: base_leaf_index + index as u32, + prove_by_index: true, + root_index: 0, + max_top_up: 0, + create_mint: None, + actions: vec![Action::DecompressMint(*decompress_action)], + proof: None, + cpi_context: None, + mint: Some(mint_data), + }; + + let ix_data = instruction_data + .data() + .map_err(|_| LightSdkTypesError::Borsh)?; + + // Decompress uses state_merkle_tree as tree_pubkey, no cpi_context, with input_queue + let metas = self.build_decompress_metas(index); + let account_infos = self.collect_decompress_infos(index); + + self.invoke_mint_action_raw(&ix_data, &account_infos, &metas, index) + } + + /// Low-level invoke: build signer seeds from mint params and call CPI. + #[inline(never)] + fn invoke_mint_action_raw( + &self, + ix_data: &[u8], + account_infos: &[AI], + metas: &[CpiMeta], + mint_index: usize, + ) -> Result<(), LightSdkTypesError> { + let mint_params = &self.params.mints[mint_index]; + + // Build signer seeds - pack present seeds at start of array + let mut seeds: [&[&[u8]]; 2] = [&[], &[]]; + let mut num_signers = 0; + if let Some(s) = mint_params.mint_signer_seeds { + seeds[num_signers] = s; + num_signers += 1; + } + if let Some(s) = mint_params.authority_seeds { + seeds[num_signers] = s; + num_signers += 1; + } + + AI::invoke_cpi( + &LIGHT_TOKEN_PROGRAM_ID, + ix_data, + metas, + account_infos, + &seeds[..num_signers], + ) + .map_err(|_| LightSdkTypesError::CpiFailed) + } + + /// Build account metas for a full mint action instruction. + /// + /// Order matches `MintActionMetaConfig::to_account_metas`: + /// light_system_program, [mint_signer], authority, [compressible_config], + /// [mint], [rent_sponsor], fee_payer, cpi_authority_pda, registered_program_pda, + /// account_compression_authority, account_compression_program, system_program, + /// [cpi_context], output_queue, tree_pubkey, [input_queue] + #[inline(never)] + fn build_mint_action_metas( + &self, + mint_index: usize, + has_input_queue: bool, + has_compressible: bool, + has_cpi_context: bool, + ) -> Vec { + let mut metas = Vec::with_capacity(16); + + // light_system_program + metas.push(CpiMeta { + pubkey: self.light_system_program.key(), + is_signer: false, + is_writable: false, + }); + + // mint_signer (always present for create_mint, must sign) + metas.push(CpiMeta { + pubkey: self.mint_seed_accounts[mint_index].key(), + is_signer: true, + is_writable: false, + }); + + // authority (payer is authority) + metas.push(CpiMeta { + pubkey: self.payer.key(), + is_signer: true, + is_writable: false, + }); + + if has_compressible { + // compressible_config + metas.push(CpiMeta { + pubkey: self.compressible_config.key(), + is_signer: false, + is_writable: false, + }); + + // mint PDA (writable) + metas.push(CpiMeta { + pubkey: self.mints[mint_index].key(), + is_signer: false, + is_writable: true, + }); + + // rent_sponsor (writable) + metas.push(CpiMeta { + pubkey: self.rent_sponsor.key(), + is_signer: false, + is_writable: true, + }); + } + + // fee_payer (signer, writable) + metas.push(CpiMeta { + pubkey: self.payer.key(), + is_signer: true, + is_writable: true, + }); + + // cpi_authority_pda + metas.push(CpiMeta { + pubkey: self.cpi_authority_pda.key(), + is_signer: false, + is_writable: false, + }); + + // registered_program_pda + metas.push(CpiMeta { + pubkey: self.registered_program_pda.key(), + is_signer: false, + is_writable: false, + }); + + // account_compression_authority + metas.push(CpiMeta { + pubkey: self.account_compression_authority.key(), + is_signer: false, + is_writable: false, + }); + + // account_compression_program + metas.push(CpiMeta { + pubkey: self.account_compression_program.key(), + is_signer: false, + is_writable: false, + }); + + // system_program + metas.push(CpiMeta { + pubkey: self.system_program.key(), + is_signer: false, + is_writable: false, + }); + + // cpi_context (optional) + if has_cpi_context { + metas.push(CpiMeta { + pubkey: self.cpi_context_account.key(), + is_signer: false, + is_writable: true, + }); + } + + // output_queue (writable) + metas.push(CpiMeta { + pubkey: self.output_queue.key(), + is_signer: false, + is_writable: true, + }); + + // tree_pubkey (address_tree for create_mint) + metas.push(CpiMeta { + pubkey: self.address_tree.key(), + is_signer: false, + is_writable: true, + }); + + // input_queue (optional, same as output_queue for create_mint) + if has_input_queue { + metas.push(CpiMeta { + pubkey: self.output_queue.key(), + is_signer: false, + is_writable: true, + }); + } + + metas + } + + /// Build account metas for a decompress instruction. + /// + /// For prove_by_index, tree_pubkey must be state_merkle_tree for discriminator validation. + #[inline(never)] + fn build_decompress_metas(&self, mint_index: usize) -> Vec { + // Decompress uses MintActionMetaConfig::new() (existing mint path) with compressible_mint + // Order: light_system_program, authority, compressible_config, mint, rent_sponsor, + // fee_payer, cpi_authority_pda, registered_program_pda, + // account_compression_authority, account_compression_program, system_program, + // output_queue, tree_pubkey(=state_merkle_tree), input_queue(=output_queue) + let mut metas = Vec::with_capacity(14); + + // light_system_program + metas.push(CpiMeta { + pubkey: self.light_system_program.key(), + is_signer: false, + is_writable: false, + }); + + // NOTE: No mint_signer for decompress (mint_signer_must_sign = false in MintActionMetaConfig::new) + // But we still have it as non-signing in the original code? Let me check... + // Actually, MintActionMetaConfig::new() sets mint_signer to None. So no mint_signer meta. + + // authority (payer is authority, signer) + metas.push(CpiMeta { + pubkey: self.payer.key(), + is_signer: true, + is_writable: false, + }); + + // compressible_config + metas.push(CpiMeta { + pubkey: self.compressible_config.key(), + is_signer: false, + is_writable: false, + }); + + // mint PDA (writable) + metas.push(CpiMeta { + pubkey: self.mints[mint_index].key(), + is_signer: false, + is_writable: true, + }); + + // rent_sponsor (writable) + metas.push(CpiMeta { + pubkey: self.rent_sponsor.key(), + is_signer: false, + is_writable: true, + }); + + // fee_payer (signer, writable) + metas.push(CpiMeta { + pubkey: self.payer.key(), + is_signer: true, + is_writable: true, + }); + + // cpi_authority_pda + metas.push(CpiMeta { + pubkey: self.cpi_authority_pda.key(), + is_signer: false, + is_writable: false, + }); + + // registered_program_pda + metas.push(CpiMeta { + pubkey: self.registered_program_pda.key(), + is_signer: false, + is_writable: false, + }); + + // account_compression_authority + metas.push(CpiMeta { + pubkey: self.account_compression_authority.key(), + is_signer: false, + is_writable: false, + }); + + // account_compression_program + metas.push(CpiMeta { + pubkey: self.account_compression_program.key(), + is_signer: false, + is_writable: false, + }); + + // system_program + metas.push(CpiMeta { + pubkey: self.system_program.key(), + is_signer: false, + is_writable: false, + }); + + // No cpi_context for decompress + + // output_queue (writable) + metas.push(CpiMeta { + pubkey: self.output_queue.key(), + is_signer: false, + is_writable: true, + }); + + // tree_pubkey = state_merkle_tree for prove_by_index discriminator check + metas.push(CpiMeta { + pubkey: self.state_merkle_tree.key(), + is_signer: false, + is_writable: true, + }); + + // input_queue = output_queue + metas.push(CpiMeta { + pubkey: self.output_queue.key(), + is_signer: false, + is_writable: true, + }); + + metas + } + + /// Collect all account infos needed for a full mint action CPI. + #[inline(never)] + fn collect_mint_action_infos(&self, _mint_index: usize) -> Vec { + let mut infos = vec![self.payer.clone()]; + + // System accounts + infos.push(self.light_system_program.clone()); + + // All mint seeds + for mint_seed in self.mint_seed_accounts { + infos.push(mint_seed.clone()); + } + + // More system accounts + infos.push(self.cpi_authority_pda.clone()); + infos.push(self.registered_program_pda.clone()); + infos.push(self.account_compression_authority.clone()); + infos.push(self.account_compression_program.clone()); + infos.push(self.system_program.clone()); + + // CPI context, queues, trees + infos.push(self.cpi_context_account.clone()); + infos.push(self.output_queue.clone()); + infos.push(self.state_merkle_tree.clone()); + infos.push(self.address_tree.clone()); + infos.push(self.compressible_config.clone()); + infos.push(self.rent_sponsor.clone()); + + // All mint PDAs + for mint in self.mints { + infos.push(mint.clone()); + } + + infos + } + + /// Collect account infos for a decompress CPI. + #[inline(never)] + fn collect_decompress_infos(&self, _mint_index: usize) -> Vec { + // Same set as mint_action_infos - runtime resolves from metas + self.collect_mint_action_infos(_mint_index) + } +} + +// ============================================================================ +// Helpers +// ============================================================================ + +/// Build `MintInstructionData` for a single mint. +#[inline(never)] +fn build_mint_instruction_data( + mint_params: &SingleMintParams<'_>, + mint_signer: &[u8; 32], +) -> MintInstructionData { + let extensions = mint_params + .token_metadata + .cloned() + .map(|metadata| vec![ExtensionInstructionData::TokenMetadata(metadata)]); + + MintInstructionData { + supply: 0, + decimals: mint_params.decimals, + metadata: MintMetadata { + version: 3, + mint: mint_params.mint.into(), + mint_decompressed: false, + mint_signer: *mint_signer, + bump: mint_params.bump, + }, + mint_authority: Some(mint_params.mint_authority.into()), + freeze_authority: mint_params.freeze_authority.map(|a| a.into()), + extensions, + } +} + +/// Find the mint PDA address for a given mint seed. +/// +/// Generic over `AccountInfoTrait` to use the correct backend for PDA derivation. +/// Returns `([u8; 32], u8)` -- the PDA address bytes and bump. +pub fn find_mint_address(mint_seed: &[u8; 32]) -> ([u8; 32], u8) { + AI::find_program_address( + &[COMPRESSED_MINT_SEED, mint_seed.as_ref()], + &LIGHT_TOKEN_PROGRAM_ID, + ) +} + +/// Derive the compressed mint address from a mint seed and address tree pubkey. +/// +/// This computes `derive_address(find_mint_address(mint_seed).0, address_tree, LIGHT_TOKEN_PROGRAM_ID)`. +pub fn derive_mint_compressed_address( + mint_seed: &[u8; 32], + address_tree_pubkey: &[u8; 32], +) -> [u8; 32] { + light_compressed_account::address::derive_address( + &find_mint_address::(mint_seed).0, + address_tree_pubkey, + &LIGHT_TOKEN_PROGRAM_ID, + ) +} + +/// Read the next_index from a batched output queue account. +/// +/// Offset 288 = 8 (discriminator) + 232 (QueueMetadata) + 48 (6 x u64 in QueueBatches). +/// This reads the raw bytes to avoid depending on `light-batched-merkle-tree`. +pub fn get_output_queue_next_index( + queue: &AI, +) -> Result { + const NEXT_INDEX_OFFSET: usize = 288; + let data = queue + .try_borrow_data() + .map_err(LightSdkTypesError::AccountError)?; + if data.len() < NEXT_INDEX_OFFSET + 8 { + return Err(LightSdkTypesError::AccountDataTooSmall); + } + let next_index = u64::from_le_bytes( + data[NEXT_INDEX_OFFSET..NEXT_INDEX_OFFSET + 8] + .try_into() + .unwrap(), + ); + Ok(next_index as u32) +} + +/// Convenience function that extracts accounts from CpiAccounts and invokes CreateMintsCpi. +#[cfg(feature = "cpi-context")] +pub fn invoke_create_mints<'a, AI: AccountInfoTrait + Clone>( + mint_seed_accounts: &'a [AI], + mint_accounts: &'a [AI], + params: CreateMintsParams<'a>, + infra: CreateMintsInfraAccounts<'a, AI>, + cpi_accounts: &crate::cpi_accounts::v2::CpiAccounts<'_, AI>, +) -> Result<(), LightSdkTypesError> { + let output_queue = cpi_accounts + .get_tree_account_info(params.output_queue_index as usize)? + .clone(); + let state_merkle_tree = cpi_accounts + .get_tree_account_info(params.state_tree_index as usize)? + .clone(); + let address_tree = cpi_accounts + .get_tree_account_info(params.address_tree_index as usize)? + .clone(); + + CreateMintsCpi { + mint_seed_accounts, + payer: infra.fee_payer, + address_tree: &address_tree, + output_queue: &output_queue, + state_merkle_tree: &state_merkle_tree, + compressible_config: infra.compressible_config, + mints: mint_accounts, + rent_sponsor: infra.rent_sponsor, + light_system_program: cpi_accounts.light_system_program()?, + cpi_authority_pda: infra.cpi_authority, + registered_program_pda: cpi_accounts.registered_program_pda()?, + account_compression_authority: cpi_accounts.account_compression_authority()?, + account_compression_program: cpi_accounts.account_compression_program()?, + system_program: cpi_accounts.system_program()?, + cpi_context_account: cpi_accounts.cpi_context()?, + params, + } + .invoke() +} diff --git a/sdk-libs/sdk-types/src/interface/cpi/create_token_accounts.rs b/sdk-libs/sdk-types/src/interface/cpi/create_token_accounts.rs new file mode 100644 index 0000000000..1de5e9ccce --- /dev/null +++ b/sdk-libs/sdk-types/src/interface/cpi/create_token_accounts.rs @@ -0,0 +1,452 @@ +//! Generic CPI builders for creating CToken accounts and ATAs. +//! +//! Provides `CreateTokenAccountCpi` and `CreateTokenAtaCpi`, both generic over +//! `AccountInfoTrait` so they work with both `solana_account_info::AccountInfo` +//! and `pinocchio::account_info::AccountInfo`. + +use alloc::vec; +use alloc::vec::Vec; + +use borsh::BorshSerialize; +use light_account_checks::{AccountInfoTrait, CpiMeta}; +use light_token_interface::{ + instructions::{ + create_associated_token_account::CreateAssociatedTokenAccountInstructionData, + create_token_account::CreateTokenAccountInstructionData, + extensions::{CompressToPubkey, CompressibleExtensionInstructionData}, + }, + LIGHT_TOKEN_PROGRAM_ID, +}; + +use crate::error::LightSdkTypesError; + +/// Discriminator for `InitializeAccount3` (create token account). +const CREATE_TOKEN_ACCOUNT_DISCRIMINATOR: u8 = 18; +/// Discriminator for `CreateAssociatedTokenAccount`. +const CREATE_ATA_DISCRIMINATOR: u8 = 100; +/// Discriminator for `CreateAssociatedTokenAccountIdempotent`. +const CREATE_ATA_IDEMPOTENT_DISCRIMINATOR: u8 = 102; + +/// Default rent payment epochs (~24 hours). +const DEFAULT_PRE_PAY_NUM_EPOCHS: u8 = 16; +/// Default lamports for write operations (~3 hours per write). +const DEFAULT_LAMPORTS_PER_WRITE: u32 = 766; +/// Default token account version (ShaFlat = 3). +const DEFAULT_TOKEN_ACCOUNT_VERSION: u8 = 3; + +// ============================================================================ +// derive_associated_token_account +// ============================================================================ + +/// Derive the associated token account address for a given owner and mint. +/// +/// Returns `([u8; 32], u8)` -- the ATA address and bump seed. +pub fn derive_associated_token_account( + owner: &[u8; 32], + mint: &[u8; 32], +) -> ([u8; 32], u8) { + AI::find_program_address( + &[ + owner.as_ref(), + LIGHT_TOKEN_PROGRAM_ID.as_ref(), + mint.as_ref(), + ], + &LIGHT_TOKEN_PROGRAM_ID, + ) +} + +// ============================================================================ +// CreateTokenAccountCpi +// ============================================================================ + +/// CPI builder for creating CToken accounts (vaults). +/// +/// Generic over `AccountInfoTrait` for framework independence. +/// +/// # Example +/// ```rust,ignore +/// CreateTokenAccountCpi { +/// payer: &ctx.accounts.payer, +/// account: &ctx.accounts.vault, +/// mint: &ctx.accounts.mint, +/// owner: ctx.accounts.vault_authority.key(), +/// } +/// .rent_free( +/// &ctx.accounts.ctoken_config, +/// &ctx.accounts.rent_sponsor, +/// &ctx.accounts.system_program, +/// &crate::ID.to_bytes(), +/// ) +/// .invoke_signed(vault_seeds)?; +/// ``` +pub struct CreateTokenAccountCpi<'a, AI: AccountInfoTrait + Clone> { + pub payer: &'a AI, + pub account: &'a AI, + pub mint: &'a AI, + pub owner: [u8; 32], +} + +impl<'a, AI: AccountInfoTrait + Clone> CreateTokenAccountCpi<'a, AI> { + /// Enable rent-free mode with compressible config. + /// + /// Returns a builder that can call `.invoke()` or `.invoke_signed(seeds)`. + /// When using `invoke_signed`, the seeds are used for both PDA signing + /// and deriving the compress_to address. + pub fn rent_free( + self, + config: &'a AI, + sponsor: &'a AI, + system_program: &'a AI, + program_id: &[u8; 32], + ) -> CreateTokenAccountRentFreeCpi<'a, AI> { + CreateTokenAccountRentFreeCpi { + base: self, + config, + sponsor, + system_program, + program_id: *program_id, + } + } +} + +/// Rent-free enabled CToken account creation CPI. +pub struct CreateTokenAccountRentFreeCpi<'a, AI: AccountInfoTrait + Clone> { + base: CreateTokenAccountCpi<'a, AI>, + config: &'a AI, + sponsor: &'a AI, + system_program: &'a AI, + program_id: [u8; 32], +} + +impl<'a, AI: AccountInfoTrait + Clone> CreateTokenAccountRentFreeCpi<'a, AI> { + /// Invoke CPI for non-program-owned accounts. + pub fn invoke(self) -> Result<(), LightSdkTypesError> { + let (data, metas, account_infos) = self.build_instruction_inner(None)?; + AI::invoke_cpi( + &LIGHT_TOKEN_PROGRAM_ID, + &data, + &metas, + &account_infos, + &[], + ) + .map_err(|_| LightSdkTypesError::CpiFailed) + } + + /// Invoke CPI with PDA signing for program-owned accounts. + /// + /// Seeds are used for both signing AND deriving the compress_to address. + pub fn invoke_signed(self, seeds: &[&[u8]]) -> Result<(), LightSdkTypesError> { + // Build CompressToPubkey from signer seeds + let bump = seeds.last().and_then(|s| s.first()).copied().unwrap_or(0); + + let seed_vecs: Vec> = seeds + .iter() + .take(seeds.len().saturating_sub(1)) + .map(|s| s.to_vec()) + .collect(); + + let compress_to = CompressToPubkey { + bump, + program_id: self.program_id, + seeds: seed_vecs, + }; + + let (data, metas, account_infos) = self.build_instruction_inner(Some(compress_to))?; + AI::invoke_cpi( + &LIGHT_TOKEN_PROGRAM_ID, + &data, + &metas, + &account_infos, + &[seeds], + ) + .map_err(|_| LightSdkTypesError::CpiFailed) + } + + /// Build instruction data, account metas, and account infos. + fn build_instruction_inner( + &self, + compress_to: Option, + ) -> Result<(Vec, Vec, Vec), LightSdkTypesError> { + let instruction_data = CreateTokenAccountInstructionData { + owner: self.base.owner.into(), + compressible_config: Some(CompressibleExtensionInstructionData { + token_account_version: DEFAULT_TOKEN_ACCOUNT_VERSION, + rent_payment: DEFAULT_PRE_PAY_NUM_EPOCHS, + compression_only: 0, // false + write_top_up: DEFAULT_LAMPORTS_PER_WRITE, + compress_to_account_pubkey: compress_to, + }), + }; + + let mut data = Vec::new(); + data.push(CREATE_TOKEN_ACCOUNT_DISCRIMINATOR); + instruction_data + .serialize(&mut data) + .map_err(|_| LightSdkTypesError::Borsh)?; + + // Account order matches the cToken program: + // [0] account (signer, writable) + // [1] mint (readonly) + // [2] payer (signer, writable) + // [3] compressible_config (readonly) + // [4] system_program (readonly) + // [5] rent_sponsor (writable) + let metas = vec![ + CpiMeta { + pubkey: self.base.account.key(), + is_signer: true, + is_writable: true, + }, + CpiMeta { + pubkey: self.base.mint.key(), + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: self.base.payer.key(), + is_signer: true, + is_writable: true, + }, + CpiMeta { + pubkey: self.config.key(), + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: self.system_program.key(), + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: self.sponsor.key(), + is_signer: false, + is_writable: true, + }, + ]; + + let account_infos = vec![ + self.base.account.clone(), + self.base.mint.clone(), + self.base.payer.clone(), + self.config.clone(), + self.system_program.clone(), + self.sponsor.clone(), + ]; + + Ok((data, metas, account_infos)) + } +} + +// ============================================================================ +// CreateTokenAtaCpi +// ============================================================================ + +/// CPI builder for creating CToken ATAs. +/// +/// Generic over `AccountInfoTrait` for framework independence. +/// +/// # Example - Rent-free ATA (idempotent) +/// ```rust,ignore +/// CreateTokenAtaCpi { +/// payer: &ctx.accounts.payer, +/// owner: &ctx.accounts.owner, +/// mint: &ctx.accounts.mint, +/// ata: &ctx.accounts.user_ata, +/// bump: params.user_ata_bump, +/// } +/// .idempotent() +/// .rent_free( +/// &ctx.accounts.ctoken_config, +/// &ctx.accounts.rent_sponsor, +/// &ctx.accounts.system_program, +/// ) +/// .invoke()?; +/// ``` +pub struct CreateTokenAtaCpi<'a, AI: AccountInfoTrait + Clone> { + pub payer: &'a AI, + pub owner: &'a AI, + pub mint: &'a AI, + pub ata: &'a AI, + pub bump: u8, +} + +impl<'a, AI: AccountInfoTrait + Clone> CreateTokenAtaCpi<'a, AI> { + /// Make this an idempotent create (won't fail if ATA already exists). + pub fn idempotent(self) -> CreateTokenAtaCpiIdempotent<'a, AI> { + CreateTokenAtaCpiIdempotent { base: self } + } + + /// Enable rent-free mode with compressible config. + pub fn rent_free( + self, + config: &'a AI, + sponsor: &'a AI, + system_program: &'a AI, + ) -> CreateTokenAtaRentFreeCpi<'a, AI> { + CreateTokenAtaRentFreeCpi { + payer: self.payer, + owner: self.owner, + mint: self.mint, + ata: self.ata, + bump: self.bump, + idempotent: false, + config, + sponsor, + system_program, + } + } +} + +/// Idempotent ATA creation (intermediate type). +pub struct CreateTokenAtaCpiIdempotent<'a, AI: AccountInfoTrait + Clone> { + base: CreateTokenAtaCpi<'a, AI>, +} + +impl<'a, AI: AccountInfoTrait + Clone> CreateTokenAtaCpiIdempotent<'a, AI> { + /// Enable rent-free mode with compressible config. + pub fn rent_free( + self, + config: &'a AI, + sponsor: &'a AI, + system_program: &'a AI, + ) -> CreateTokenAtaRentFreeCpi<'a, AI> { + CreateTokenAtaRentFreeCpi { + payer: self.base.payer, + owner: self.base.owner, + mint: self.base.mint, + ata: self.base.ata, + bump: self.base.bump, + idempotent: true, + config, + sponsor, + system_program, + } + } +} + +/// Rent-free enabled CToken ATA creation CPI. +pub struct CreateTokenAtaRentFreeCpi<'a, AI: AccountInfoTrait + Clone> { + payer: &'a AI, + owner: &'a AI, + mint: &'a AI, + ata: &'a AI, + bump: u8, + idempotent: bool, + config: &'a AI, + sponsor: &'a AI, + system_program: &'a AI, +} + +impl<'a, AI: AccountInfoTrait + Clone> CreateTokenAtaRentFreeCpi<'a, AI> { + /// Invoke CPI. + pub fn invoke(self) -> Result<(), LightSdkTypesError> { + let (data, metas, account_infos) = self.build_instruction_inner()?; + AI::invoke_cpi( + &LIGHT_TOKEN_PROGRAM_ID, + &data, + &metas, + &account_infos, + &[], + ) + .map_err(|_| LightSdkTypesError::CpiFailed) + } + + /// Invoke CPI with signer seeds (when caller needs to sign for another account). + pub fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), LightSdkTypesError> { + let (data, metas, account_infos) = self.build_instruction_inner()?; + AI::invoke_cpi( + &LIGHT_TOKEN_PROGRAM_ID, + &data, + &metas, + &account_infos, + signer_seeds, + ) + .map_err(|_| LightSdkTypesError::CpiFailed) + } + + /// Build instruction data, account metas, and account infos. + fn build_instruction_inner( + &self, + ) -> Result<(Vec, Vec, Vec), LightSdkTypesError> { + let instruction_data = CreateAssociatedTokenAccountInstructionData { + bump: self.bump, + compressible_config: Some(CompressibleExtensionInstructionData { + token_account_version: DEFAULT_TOKEN_ACCOUNT_VERSION, + rent_payment: DEFAULT_PRE_PAY_NUM_EPOCHS, + compression_only: 1, // ATAs are always compression_only + write_top_up: DEFAULT_LAMPORTS_PER_WRITE, + compress_to_account_pubkey: None, + }), + }; + + let discriminator = if self.idempotent { + CREATE_ATA_IDEMPOTENT_DISCRIMINATOR + } else { + CREATE_ATA_DISCRIMINATOR + }; + + let mut data = Vec::new(); + data.push(discriminator); + instruction_data + .serialize(&mut data) + .map_err(|_| LightSdkTypesError::Borsh)?; + + // Account order matches the cToken program: + // [0] owner (readonly) + // [1] mint (readonly) + // [2] payer (signer, writable) + // [3] associated_token_account (writable) + // [4] system_program (readonly) + // [5] compressible_config (readonly) + // [6] rent_sponsor (writable) + let metas = vec![ + CpiMeta { + pubkey: self.owner.key(), + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: self.mint.key(), + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: self.payer.key(), + is_signer: true, + is_writable: true, + }, + CpiMeta { + pubkey: self.ata.key(), + is_signer: false, + is_writable: true, + }, + CpiMeta { + pubkey: self.system_program.key(), + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: self.config.key(), + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: self.sponsor.key(), + is_signer: false, + is_writable: true, + }, + ]; + + let account_infos = vec![ + self.owner.clone(), + self.mint.clone(), + self.payer.clone(), + self.ata.clone(), + self.system_program.clone(), + self.config.clone(), + self.sponsor.clone(), + ]; + + Ok((data, metas, account_infos)) + } +} diff --git a/sdk-libs/sdk-types/src/interface/cpi/mod.rs b/sdk-libs/sdk-types/src/interface/cpi/mod.rs index 8fef72b18a..5ee4f0d626 100644 --- a/sdk-libs/sdk-types/src/interface/cpi/mod.rs +++ b/sdk-libs/sdk-types/src/interface/cpi/mod.rs @@ -4,6 +4,10 @@ //! All CPI calls go through `AI::invoke_cpi()` for framework independence. pub mod account; +#[cfg(feature = "token")] +pub mod create_mints; +#[cfg(feature = "token")] +pub mod create_token_accounts; pub mod impls; mod instruction; pub mod invoke; diff --git a/sdk-tests/manual-test/Cargo.toml b/sdk-tests/manual-test/Cargo.toml index 201c31fa58..fa2238723f 100644 --- a/sdk-tests/manual-test/Cargo.toml +++ b/sdk-tests/manual-test/Cargo.toml @@ -18,16 +18,15 @@ idl-build = ["anchor-lang/idl-build"] test-sbf = [] [dependencies] -light-account = { workspace = true } +light-account = { workspace = true, features = ["token"] } light-macros = { workspace = true, features = ["solana"] } -light-sdk-macros = { workspace = true } +light-sdk-macros = { workspace = true, features = ["anchor-discriminator"] } borsh = { workspace = true } bytemuck = { workspace = true } light-compressed-account = { workspace = true, features = ["solana"] } anchor-lang = { workspace = true } light-compressible = { workspace = true, features = ["anchor"] } light-hasher = { workspace = true, features = ["solana"] } -light-token = { workspace = true, features = ["anchor"] } light-token-types = { workspace = true, features = ["anchor"] } light-token-interface = { workspace = true } solana-program = { workspace = true } @@ -40,7 +39,7 @@ solana-account-info = { workspace = true } light-program-test = { workspace = true, features = ["devenv"] } light-client = { workspace = true, features = ["v2", "anchor"] } light-test-utils = { workspace = true } -light-token = { workspace = true } +light-token = { workspace = true, features = ["anchor"] } light-token-client = { workspace = true } tokio = { workspace = true } solana-sdk = { workspace = true } diff --git a/sdk-tests/manual-test/src/all/derived.rs b/sdk-tests/manual-test/src/all/derived.rs index 5895791886..66a1ba24dd 100644 --- a/sdk-tests/manual-test/src/all/derived.rs +++ b/sdk-tests/manual-test/src/all/derived.rs @@ -14,14 +14,10 @@ use light_account::{ prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, CpiContextWriteAccounts, InvokeLightSystemProgram, LightAccount, LightFinalize, LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, -}; -use light_token::{ - compressible::{invoke_create_mints, CreateMintsInfraAccounts}, - instruction::{ - derive_mint_compressed_address, find_mint_address, - CreateMintsParams as SdkCreateMintsParams, CreateTokenAccountCpi, CreateTokenAtaCpi, - SingleMintParams, - }, + invoke_create_mints, CreateMintsInfraAccounts, CreateMintsParams as SdkCreateMintsParams, + SingleMintParams, derive_mint_compressed_address, find_mint_address, + DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, + CreateTokenAccountCpi, CreateTokenAtaCpi, derive_associated_token_account, }; use solana_account_info::AccountInfo; @@ -162,14 +158,12 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou let mint_signer_key = self.mint_signer.key(); // Derive mint PDA - let (mint_pda, mint_bump) = find_mint_address( - &solana_pubkey::Pubkey::new_from_array(mint_signer_key.to_bytes()), - ); + let (mint_pda, mint_bump) = find_mint_address(&mint_signer_key.to_bytes()); // Derive compression address let compression_address = derive_mint_compressed_address( - &solana_pubkey::Pubkey::new_from_array(mint_signer_key.to_bytes()), - &solana_pubkey::Pubkey::new_from_array(address_tree_pubkey.to_bytes()), + &mint_signer_key.to_bytes(), + &address_tree_pubkey.to_bytes(), ); // Build mint signer seeds @@ -183,14 +177,12 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou let sdk_mints: [SingleMintParams<'_>; NUM_LIGHT_MINTS] = [SingleMintParams { decimals: 6, // mint::decimals = 6 address_merkle_tree_root_index: address_tree_info.root_index, - mint_authority: solana_pubkey::Pubkey::new_from_array(authority.to_bytes()), + mint_authority: authority.to_bytes(), compression_address, mint: mint_pda, bump: mint_bump, freeze_authority: None, - mint_seed_pubkey: solana_pubkey::Pubkey::new_from_array( - mint_signer_key.to_bytes(), - ), + mint_seed_pubkey: mint_signer_key.to_bytes(), authority_seeds: None, mint_signer_seeds: Some(mint_signer_seeds), token_metadata: None, @@ -209,18 +201,25 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou .ok_or(LightSdkTypesError::InvalidInstructionData)?; // Build SDK params with cpi_context_offset - let sdk_params = SdkCreateMintsParams::new(&sdk_mints, proof) - .with_output_queue_index(params.create_accounts_proof.output_state_tree_index) - .with_address_tree_index(address_tree_info.address_merkle_tree_pubkey_index) - .with_state_tree_index(state_tree_index) - .with_cpi_context_offset(NUM_LIGHT_PDAS as u8); // Offset by PDA count + let sdk_params = SdkCreateMintsParams { + mints: &sdk_mints, + proof, + rent_payment: DEFAULT_RENT_PAYMENT, + write_top_up: DEFAULT_WRITE_TOP_UP, + cpi_context_offset: NUM_LIGHT_PDAS as u8, + output_queue_index: params.create_accounts_proof.output_state_tree_index, + address_tree_index: address_tree_info.address_merkle_tree_pubkey_index, + state_tree_index, + base_leaf_index: 0, // N=1, not used + }; // Build infra accounts + let payer_info = self.payer.to_account_info(); let infra = CreateMintsInfraAccounts { - fee_payer: self.payer.to_account_info(), - compressible_config: self.compressible_config.clone(), - rent_sponsor: self.rent_sponsor.clone(), - cpi_authority: self.cpi_authority.clone(), + fee_payer: &payer_info, + compressible_config: &self.compressible_config, + rent_sponsor: &self.rent_sponsor, + cpi_authority: &self.cpi_authority, }; // Build mint account arrays @@ -249,45 +248,51 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou &[params.token_vault_bump], ]; + let payer_info = self.payer.to_account_info(); + let token_vault_info = self.token_vault.to_account_info(); + let mint_info = self.mint.to_account_info(); + let system_program_info = self.system_program.to_account_info(); CreateTokenAccountCpi { - payer: self.payer.to_account_info(), - account: self.token_vault.to_account_info(), - mint: self.mint.to_account_info(), - owner: *self.vault_owner.key, + payer: &payer_info, + account: &token_vault_info, + mint: &mint_info, + owner: self.vault_owner.key.to_bytes(), } .rent_free( - self.compressible_config.clone(), - self.rent_sponsor.clone(), - self.system_program.to_account_info(), - &crate::ID, + &self.compressible_config, + &self.rent_sponsor, + &system_program_info, + &crate::ID.to_bytes(), ) - .invoke_signed(vault_seeds) - .map_err(|_| LightSdkTypesError::CpiFailed)?; + .invoke_signed(vault_seeds)?; } // ==================================================================== // 7. Create ATA via CreateTokenAtaCpi // ==================================================================== { - let (_, ata_bump) = light_token::instruction::derive_associated_token_account( + let (_, ata_bump) = derive_associated_token_account( self.ata_owner.key, self.mint.key, ); + let payer_info = self.payer.to_account_info(); + let mint_info = self.mint.to_account_info(); + let user_ata_info = self.user_ata.to_account_info(); + let system_program_info = self.system_program.to_account_info(); CreateTokenAtaCpi { - payer: self.payer.to_account_info(), - owner: self.ata_owner.clone(), - mint: self.mint.to_account_info(), - ata: self.user_ata.to_account_info(), + payer: &payer_info, + owner: &self.ata_owner, + mint: &mint_info, + ata: &user_ata_info, bump: ata_bump, } .rent_free( - self.compressible_config.clone(), - self.rent_sponsor.clone(), - self.system_program.to_account_info(), + &self.compressible_config, + &self.rent_sponsor, + &system_program_info, ) - .invoke() - .map_err(|_| LightSdkTypesError::CpiFailed)?; + .invoke()?; } Ok(WITH_CPI_CONTEXT) diff --git a/sdk-tests/manual-test/src/ata/derived.rs b/sdk-tests/manual-test/src/ata/derived.rs index 7bfe357afc..abc48515ae 100644 --- a/sdk-tests/manual-test/src/ata/derived.rs +++ b/sdk-tests/manual-test/src/ata/derived.rs @@ -1,8 +1,10 @@ //! Derived code - what the macro would generate for associated token accounts. use anchor_lang::prelude::*; -use light_account::{LightFinalize, LightPreInit, LightSdkTypesError}; -use light_token::instruction::CreateTokenAtaCpi; +use light_account::{ + CreateTokenAtaCpi, LightFinalize, LightPreInit, LightSdkTypesError, + derive_associated_token_account, +}; use solana_account_info::AccountInfo; use super::accounts::{CreateAtaAccounts, CreateAtaParams}; @@ -19,7 +21,7 @@ impl<'info> LightPreInit, CreateAtaParams> for CreateAtaAccou ) -> std::result::Result { let inner = || -> std::result::Result { // Derive the ATA bump on-chain - let (_, bump) = light_token::instruction::derive_associated_token_account( + let (_, bump) = derive_associated_token_account( self.ata_owner.key, self.mint.key, ); @@ -27,20 +29,23 @@ impl<'info> LightPreInit, CreateAtaParams> for CreateAtaAccou // Create ATA via CPI with idempotent + rent-free mode // NOTE: Unlike token vaults, ATAs use .invoke() not .invoke_signed() // because ATAs are derived from [owner, token_program, mint], not program PDAs + let payer_info = self.payer.to_account_info(); + let user_ata_info = self.user_ata.to_account_info(); + let system_program_info = self.system_program.to_account_info(); CreateTokenAtaCpi { - payer: self.payer.to_account_info(), - owner: self.ata_owner.clone(), - mint: self.mint.clone(), - ata: self.user_ata.to_account_info(), + payer: &payer_info, + owner: &self.ata_owner, + mint: &self.mint, + ata: &user_ata_info, bump, } .idempotent() // Safe: won't fail if ATA already exists .rent_free( - self.compressible_config.clone(), - self.rent_sponsor.clone(), - self.system_program.to_account_info(), + &self.compressible_config, + &self.rent_sponsor, + &system_program_info, ) - .invoke().map_err(|_| LightSdkTypesError::CpiFailed)?; + .invoke()?; // ATAs don't use CPI context, return false Ok(false) diff --git a/sdk-tests/manual-test/src/token_account/derived.rs b/sdk-tests/manual-test/src/token_account/derived.rs index f6e4775a90..ae7d008e1f 100644 --- a/sdk-tests/manual-test/src/token_account/derived.rs +++ b/sdk-tests/manual-test/src/token_account/derived.rs @@ -3,8 +3,7 @@ use anchor_lang::prelude::*; #[cfg(not(target_os = "solana"))] use light_account::Pack; -use light_account::{LightFinalize, LightPreInit, LightSdkTypesError, Unpack}; -use light_token::instruction::CreateTokenAccountCpi; +use light_account::{CreateTokenAccountCpi, LightFinalize, LightPreInit, LightSdkTypesError, Unpack}; use solana_account_info::AccountInfo; use super::accounts::{CreateTokenVaultAccounts, CreateTokenVaultParams, TOKEN_VAULT_SEED}; @@ -28,19 +27,22 @@ impl<'info> LightPreInit, CreateTokenVaultParams> &[TOKEN_VAULT_SEED, mint_key.as_ref(), &[params.vault_bump]]; // Create token account via CPI with rent-free mode + let payer_info = self.payer.to_account_info(); + let token_vault_info = self.token_vault.to_account_info(); + let system_program_info = self.system_program.to_account_info(); CreateTokenAccountCpi { - payer: self.payer.to_account_info(), - account: self.token_vault.to_account_info(), - mint: self.mint.clone(), - owner: *self.vault_owner.key, + payer: &payer_info, + account: &token_vault_info, + mint: &self.mint, + owner: self.vault_owner.key.to_bytes(), } .rent_free( - self.compressible_config.clone(), - self.rent_sponsor.clone(), - self.system_program.to_account_info(), - &crate::ID, + &self.compressible_config, + &self.rent_sponsor, + &system_program_info, + &crate::ID.to_bytes(), ) - .invoke_signed(vault_seeds).map_err(|_| LightSdkTypesError::CpiFailed)?; + .invoke_signed(vault_seeds)?; // Token accounts don't use CPI context, return false Ok(false) diff --git a/sdk-tests/manual-test/src/two_mints/derived.rs b/sdk-tests/manual-test/src/two_mints/derived.rs index c22ed9bc4c..30e0de44c4 100644 --- a/sdk-tests/manual-test/src/two_mints/derived.rs +++ b/sdk-tests/manual-test/src/two_mints/derived.rs @@ -3,15 +3,11 @@ use anchor_lang::prelude::*; use light_account::{ + invoke_create_mints, get_output_queue_next_index, CreateMintsInfraAccounts, + CreateMintsParams as SdkCreateMintsParams, SingleMintParams, + derive_mint_compressed_address, find_mint_address, CpiAccounts, CpiAccountsConfig, LightFinalize, LightPreInit, LightSdkTypesError, - PackedAddressTreeInfoExt, -}; -use light_token::{ - compressible::{invoke_create_mints, CreateMintsInfraAccounts}, - instruction::{ - derive_mint_compressed_address, find_mint_address, - CreateMintsParams as SdkCreateMintsParams, SingleMintParams, - }, + PackedAddressTreeInfoExt, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, }; use solana_account_info::AccountInfo; @@ -73,21 +69,19 @@ impl<'info> LightPreInit, CreateDerivedMintsParams> let mint_signer_1 = self.mint_signer_1.key(); // Derive mint PDAs (light-token derives mint PDA from mint_signer) - let (mint_0_pda, mint_0_bump) = find_mint_address( - &solana_pubkey::Pubkey::new_from_array(mint_signer_0.to_bytes()), - ); - let (mint_1_pda, mint_1_bump) = find_mint_address( - &solana_pubkey::Pubkey::new_from_array(mint_signer_1.to_bytes()), - ); + let (mint_0_pda, mint_0_bump) = + find_mint_address(&mint_signer_0.to_bytes()); + let (mint_1_pda, mint_1_bump) = + find_mint_address(&mint_signer_1.to_bytes()); // Derive compression addresses (from mint_signer + address_tree) let compression_address_0 = derive_mint_compressed_address( - &solana_pubkey::Pubkey::new_from_array(mint_signer_0.to_bytes()), - &solana_pubkey::Pubkey::new_from_array(address_tree_pubkey.to_bytes()), + &mint_signer_0.to_bytes(), + &address_tree_pubkey.to_bytes(), ); let compression_address_1 = derive_mint_compressed_address( - &solana_pubkey::Pubkey::new_from_array(mint_signer_1.to_bytes()), - &solana_pubkey::Pubkey::new_from_array(address_tree_pubkey.to_bytes()), + &mint_signer_1.to_bytes(), + &address_tree_pubkey.to_bytes(), ); // Build mint signer seeds for CPI (mint::seeds + bump) @@ -107,14 +101,12 @@ impl<'info> LightPreInit, CreateDerivedMintsParams> SingleMintParams { decimals: 6, // mint::decimals = 6 address_merkle_tree_root_index: address_tree_info.root_index, - mint_authority: solana_pubkey::Pubkey::new_from_array(authority.to_bytes()), + mint_authority: authority.to_bytes(), compression_address: compression_address_0, mint: mint_0_pda, bump: mint_0_bump, freeze_authority: None, - mint_seed_pubkey: solana_pubkey::Pubkey::new_from_array( - mint_signer_0.to_bytes(), - ), + mint_seed_pubkey: mint_signer_0.to_bytes(), authority_seeds: None, mint_signer_seeds: Some(mint_signer_0_seeds), token_metadata: None, @@ -122,14 +114,12 @@ impl<'info> LightPreInit, CreateDerivedMintsParams> SingleMintParams { decimals: 9, // mint::decimals = 9 address_merkle_tree_root_index: address_tree_info.root_index, - mint_authority: solana_pubkey::Pubkey::new_from_array(authority.to_bytes()), + mint_authority: authority.to_bytes(), compression_address: compression_address_1, mint: mint_1_pda, bump: mint_1_bump, freeze_authority: None, - mint_seed_pubkey: solana_pubkey::Pubkey::new_from_array( - mint_signer_1.to_bytes(), - ), + mint_seed_pubkey: mint_signer_1.to_bytes(), authority_seeds: None, mint_signer_seeds: Some(mint_signer_1_seeds), token_metadata: None, @@ -152,18 +142,31 @@ impl<'info> LightPreInit, CreateDerivedMintsParams> .0 .ok_or(LightSdkTypesError::InvalidInstructionData)?; - let sdk_params = SdkCreateMintsParams::new(&sdk_mints, proof) - .with_output_queue_index(params.create_accounts_proof.output_state_tree_index) - .with_address_tree_index(address_tree_info.address_merkle_tree_pubkey_index) - .with_state_tree_index(state_tree_index) - .with_cpi_context_offset(NUM_LIGHT_PDAS as u8); // Offset by PDA count + // Read base_leaf_index from output queue (required for N > 1) + let output_queue_index = params.create_accounts_proof.output_state_tree_index; + let output_queue = cpi_accounts + .get_tree_account_info(output_queue_index as usize)?; + let base_leaf_index = get_output_queue_next_index(output_queue)?; + + let sdk_params = SdkCreateMintsParams { + mints: &sdk_mints, + proof, + rent_payment: DEFAULT_RENT_PAYMENT, + write_top_up: DEFAULT_WRITE_TOP_UP, + cpi_context_offset: NUM_LIGHT_PDAS as u8, + output_queue_index, + address_tree_index: address_tree_info.address_merkle_tree_pubkey_index, + state_tree_index, + base_leaf_index, + }; // Build infra accounts from Accounts struct + let payer_info = self.payer.to_account_info(); let infra = CreateMintsInfraAccounts { - fee_payer: self.payer.to_account_info(), - compressible_config: self.compressible_config.clone(), - rent_sponsor: self.rent_sponsor.clone(), - cpi_authority: self.cpi_authority.clone(), + fee_payer: &payer_info, + compressible_config: &self.compressible_config, + rent_sponsor: &self.rent_sponsor, + cpi_authority: &self.cpi_authority, }; // Build mint account arrays From 7364b82fbe528c0ac5631beaca614bf332bb37d0 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 2 Feb 2026 17:26:04 +0000 Subject: [PATCH 08/15] csdk tests pass --- Cargo.lock | 78 ++-- Cargo.toml | 3 +- .../src/account_info/account_info_trait.rs | 3 + .../src/account_info/pinocchio.rs | 47 ++- .../account-checks/src/account_info/solana.rs | 56 ++- sdk-libs/account-pinocchio/Cargo.toml | 4 +- sdk-libs/account-pinocchio/src/lib.rs | 2 +- sdk-libs/account/Cargo.toml | 1 + sdk-libs/account/src/lib.rs | 27 ++ .../macros/src/light_pdas/account/derive.rs | 44 +- .../macros/src/light_pdas/account/traits.rs | 6 +- .../macros/src/light_pdas/accounts/builder.rs | 47 ++- .../macros/src/light_pdas/accounts/pda.rs | 45 +- .../macros/src/light_pdas/accounts/token.rs | 68 +-- .../macros/src/light_pdas/accounts/variant.rs | 52 ++- .../macros/src/light_pdas/program/compress.rs | 24 +- .../src/light_pdas/program/decompress.rs | 91 ++-- .../src/light_pdas/program/instructions.rs | 88 ++-- .../macros/src/light_pdas/program/parsing.rs | 12 +- .../src/light_pdas/program/variant_enum.rs | 74 ++-- sdk-libs/sdk-types/Cargo.toml | 3 +- sdk-libs/sdk-types/src/constants.rs | 8 + sdk-libs/sdk-types/src/error.rs | 30 ++ .../src/interface/account/compression_info.rs | 5 + .../sdk-types/src/interface/account/mod.rs | 1 + .../sdk-types/src/interface/account/size.rs | 8 + .../accounts/init_compressed_account.rs | 12 +- .../src/interface/cpi/create_mints.rs | 114 ++--- .../program/decompression/processor.rs | 23 +- .../src/interface/program/variant.rs | 14 +- .../Cargo.toml | 1 + .../src/lib.rs | 3 +- .../csdk-anchor-full-derived-test/Cargo.toml | 22 +- .../src/amm_test/initialize.rs | 127 +++--- .../src/amm_test/states.rs | 2 +- .../src/instruction_accounts.rs | 4 +- .../d10_token_accounts/single_ata.rs | 4 +- .../d10_token_accounts/single_ata_markonly.rs | 3 +- .../d10_token_accounts/single_vault.rs | 3 +- .../d11_zero_copy/mixed_zc_borsh.rs | 2 +- .../instructions/d11_zero_copy/multiple_zc.rs | 2 +- .../instructions/d11_zero_copy/with_ata.rs | 3 +- .../d11_zero_copy/with_ctx_seeds.rs | 2 +- .../d11_zero_copy/with_mint_to.rs | 3 +- .../d11_zero_copy/with_params_seeds.rs | 2 +- .../instructions/d11_zero_copy/with_vault.rs | 3 +- .../src/instructions/d5_markers/all.rs | 3 +- .../instructions/d5_markers/light_token.rs | 2 +- .../instructions/d5_markers/rentfree_bare.rs | 2 +- .../instructions/d6_account_types/account.rs | 2 +- .../src/instructions/d6_account_types/all.rs | 2 +- .../instructions/d6_account_types/boxed.rs | 2 +- .../src/instructions/d7_infra_names/all.rs | 3 +- .../instructions/d7_infra_names/creator.rs | 2 +- .../d7_infra_names/light_token_config.rs | 2 +- .../src/instructions/d7_infra_names/payer.rs | 2 +- .../src/instructions/d8_builder_paths/all.rs | 2 +- .../d8_builder_paths/multi_rentfree.rs | 2 +- .../instructions/d8_builder_paths/pda_only.rs | 2 +- .../src/instructions/d9_seeds/all.rs | 2 +- .../src/instructions/d9_seeds/array_bumps.rs | 2 +- .../instructions/d9_seeds/complex_mixed.rs | 2 +- .../instructions/d9_seeds/const_patterns.rs | 2 +- .../src/instructions/d9_seeds/constant.rs | 2 +- .../src/instructions/d9_seeds/ctx_account.rs | 2 +- .../src/instructions/d9_seeds/edge_cases.rs | 2 +- .../instructions/d9_seeds/external_paths.rs | 12 +- .../instructions/d9_seeds/function_call.rs | 2 +- .../instructions/d9_seeds/instruction_data.rs | 2 +- .../src/instructions/d9_seeds/literal.rs | 2 +- .../instructions/d9_seeds/method_chains.rs | 2 +- .../src/instructions/d9_seeds/mixed.rs | 2 +- .../src/instructions/d9_seeds/nested_seeds.rs | 2 +- .../src/instructions/d9_seeds/param.rs | 2 +- .../src/instructions/d9_seeds/param_bytes.rs | 2 +- .../instructions/d9_seeds/qualified_paths.rs | 2 +- .../csdk-anchor-full-derived-test/src/lib.rs | 199 +++++---- .../src/state/d11_zero_copy/basic.rs | 2 +- .../src/state/d11_zero_copy/with_params.rs | 2 +- .../src/state/d11_zero_copy/with_seeds.rs | 2 +- .../src/state/d1_field_types/all.rs | 2 +- .../src/state/d1_field_types/arrays.rs | 2 +- .../src/state/d1_field_types/multi_pubkey.rs | 2 +- .../src/state/d1_field_types/no_pubkey.rs | 2 +- .../src/state/d1_field_types/non_copy.rs | 2 +- .../state/d1_field_types/option_primitive.rs | 2 +- .../src/state/d1_field_types/option_pubkey.rs | 2 +- .../src/state/d1_field_types/single_pubkey.rs | 2 +- .../src/state/d2_compress_as/absent.rs | 2 +- .../src/state/d2_compress_as/all.rs | 2 +- .../src/state/d2_compress_as/multiple.rs | 2 +- .../src/state/d2_compress_as/option_none.rs | 2 +- .../src/state/d2_compress_as/single.rs | 2 +- .../src/state/d4_composition/all.rs | 2 +- .../src/state/d4_composition/info_last.rs | 2 +- .../src/state/d4_composition/large.rs | 2 +- .../src/state/d4_composition/minimal.rs | 2 +- .../src/state/mod.rs | 6 +- .../amm_observation_state_test.rs | 4 +- .../account_macros/amm_pool_state_test.rs | 4 +- .../account_macros/core_game_session_test.rs | 4 +- .../core_placeholder_record_test.rs | 4 +- .../account_macros/core_user_record_test.rs | 4 +- .../account_macros/d1_all_field_types_test.rs | 6 +- .../account_macros/d1_multi_pubkey_test.rs | 18 +- .../account_macros/d1_option_pubkey_test.rs | 10 +- .../account_macros/d1_single_pubkey_test.rs | 4 +- .../account_macros/d2_all_compress_as_test.rs | 4 +- .../d2_multiple_compress_as_test.rs | 4 +- .../account_macros/d2_no_compress_as_test.rs | 4 +- .../d2_option_none_compress_as_test.rs | 4 +- .../d2_single_compress_as_test.rs | 4 +- .../tests/account_macros/shared.rs | 2 +- .../tests/basic_test.rs | 2 +- .../tests/compressibility_check_test.rs | 3 +- .../tests/failing_tests.rs | 52 +-- .../tests/integration_tests.rs | 8 +- sdk-tests/manual-test-pinocchio/Cargo.toml | 51 +++ sdk-tests/manual-test-pinocchio/keypair.json | 1 + .../src/account_loader/accounts.rs | 93 ++++ .../src/account_loader/derived_accounts.rs | 376 +++++++++++++++++ .../src/account_loader/derived_state.rs | 106 +++++ .../src/account_loader/mod.rs | 24 ++ .../src/account_loader/state.rs | 42 ++ .../manual-test-pinocchio/src/all/accounts.rs | 221 ++++++++++ .../manual-test-pinocchio/src/all/derived.rs | 313 ++++++++++++++ .../src/all/derived_accounts.rs | 398 ++++++++++++++++++ .../manual-test-pinocchio/src/all/mod.rs | 23 + .../manual-test-pinocchio/src/ata/accounts.rs | 50 +++ .../manual-test-pinocchio/src/ata/derived.rs | 66 +++ .../manual-test-pinocchio/src/ata/mod.rs | 6 + .../src/derived_compress.rs | 75 ++++ .../src/derived_decompress.rs | 27 ++ .../src/derived_light_config.rs | 67 +++ .../src/derived_variants.rs | 143 +++++++ sdk-tests/manual-test-pinocchio/src/lib.rs | 292 +++++++++++++ .../manual-test-pinocchio/src/pda/accounts.rs | 91 ++++ .../src/pda/derived_accounts.rs | 378 +++++++++++++++++ .../src/pda/derived_state.rs | 96 +++++ .../manual-test-pinocchio/src/pda/mod.rs | 13 + .../manual-test-pinocchio/src/pda/state.rs | 27 ++ .../src/token_account/accounts.rs | 69 +++ .../src/token_account/derived.rs | 118 ++++++ .../src/token_account/mod.rs | 6 + .../src/two_mints/accounts.rs | 109 +++++ .../src/two_mints/derived.rs | 199 +++++++++ .../src/two_mints/mod.rs | 6 + .../tests/account_loader.rs | 191 +++++++++ sdk-tests/manual-test-pinocchio/tests/all.rs | 220 ++++++++++ sdk-tests/manual-test-pinocchio/tests/ata.rs | 130 ++++++ .../manual-test-pinocchio/tests/shared.rs | 125 ++++++ sdk-tests/manual-test-pinocchio/tests/test.rs | 168 ++++++++ .../tests/token_account.rs | 74 ++++ .../manual-test-pinocchio/tests/two_mints.rs | 161 +++++++ .../manual-test/src/derived_decompress.rs | 4 +- .../Cargo.toml | 0 .../src/instruction_accounts.rs | 0 .../src/lib.rs | 0 .../src/state.rs | 0 .../tests/test.rs | 0 .../single-account-loader-test/Cargo.toml | 13 +- .../single-account-loader-test/src/lib.rs | 4 +- .../single-account-loader-test/src/state.rs | 2 +- sdk-tests/single-ata-test/Cargo.toml | 16 +- sdk-tests/single-ata-test/src/lib.rs | 8 +- sdk-tests/single-mint-test/Cargo.toml | 17 +- sdk-tests/single-mint-test/src/lib.rs | 4 +- sdk-tests/single-pda-test/Cargo.toml | 14 +- .../src/instruction_accounts.rs | 2 +- sdk-tests/single-pda-test/src/lib.rs | 3 +- sdk-tests/single-pda-test/src/state.rs | 2 +- sdk-tests/single-token-test/Cargo.toml | 17 +- sdk-tests/single-token-test/src/lib.rs | 8 +- 173 files changed, 5548 insertions(+), 759 deletions(-) create mode 100644 sdk-libs/sdk-types/src/interface/account/size.rs create mode 100644 sdk-tests/manual-test-pinocchio/Cargo.toml create mode 100644 sdk-tests/manual-test-pinocchio/keypair.json create mode 100644 sdk-tests/manual-test-pinocchio/src/account_loader/accounts.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/account_loader/derived_accounts.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/account_loader/derived_state.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/account_loader/mod.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/account_loader/state.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/all/accounts.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/all/derived.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/all/derived_accounts.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/all/mod.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/ata/accounts.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/ata/derived.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/ata/mod.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/derived_compress.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/derived_decompress.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/derived_light_config.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/derived_variants.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/lib.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/pda/accounts.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/pda/derived_accounts.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/pda/derived_state.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/pda/mod.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/pda/state.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/token_account/accounts.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/token_account/derived.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/token_account/mod.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/two_mints/accounts.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/two_mints/derived.rs create mode 100644 sdk-tests/manual-test-pinocchio/src/two_mints/mod.rs create mode 100644 sdk-tests/manual-test-pinocchio/tests/account_loader.rs create mode 100644 sdk-tests/manual-test-pinocchio/tests/all.rs create mode 100644 sdk-tests/manual-test-pinocchio/tests/ata.rs create mode 100644 sdk-tests/manual-test-pinocchio/tests/shared.rs create mode 100644 sdk-tests/manual-test-pinocchio/tests/test.rs create mode 100644 sdk-tests/manual-test-pinocchio/tests/token_account.rs create mode 100644 sdk-tests/manual-test-pinocchio/tests/two_mints.rs rename sdk-tests/{single-pda-derive-test => pinocchio-derive-test}/Cargo.toml (100%) rename sdk-tests/{single-pda-derive-test => pinocchio-derive-test}/src/instruction_accounts.rs (100%) rename sdk-tests/{single-pda-derive-test => pinocchio-derive-test}/src/lib.rs (100%) rename sdk-tests/{single-pda-derive-test => pinocchio-derive-test}/src/state.rs (100%) rename sdk-tests/{single-pda-derive-test => pinocchio-derive-test}/tests/test.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index f24003a0e4..c7c1e71e0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1667,6 +1667,7 @@ dependencies = [ "ahash", "anchor-lang", "csdk-anchor-full-derived-test", + "light-account", "light-client", "light-compressed-token-sdk", "light-sdk", @@ -3467,6 +3468,7 @@ version = "0.1.0" dependencies = [ "light-account-checks", "light-compressed-account", + "light-compressible", "light-hasher", "light-macros", "light-sdk-macros", @@ -3508,6 +3510,8 @@ dependencies = [ "light-sdk-types", "light-token-interface", "pinocchio", + "solana-instruction", + "solana-pubkey 2.4.0", ] [[package]] @@ -4165,6 +4169,7 @@ dependencies = [ "light-macros", "light-token-interface", "solana-msg 2.2.1", + "solana-program-error 2.2.2", "solana-pubkey 2.4.0", "thiserror 2.0.17", ] @@ -4550,6 +4555,38 @@ dependencies = [ "tokio", ] +[[package]] +name = "manual-test-pinocchio" +version = "0.1.0" +dependencies = [ + "borsh 0.10.4", + "bytemuck", + "light-account-pinocchio", + "light-client", + "light-compressed-account", + "light-compressible", + "light-hasher", + "light-macros", + "light-program-test", + "light-sdk-macros", + "light-test-utils", + "light-token", + "light-token-client", + "light-token-interface", + "light-token-types", + "pinocchio", + "pinocchio-pubkey", + "pinocchio-system", + "solana-instruction", + "solana-keypair", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "solana-sdk", + "solana-signer", + "tokio", +] + [[package]] name = "matchers" version = "0.2.0" @@ -6581,7 +6618,6 @@ dependencies = [ "light-program-test", "light-sdk", "light-sdk-macros", - "light-sdk-types", "light-test-utils", "light-token", "solana-account-info", @@ -6605,7 +6641,6 @@ dependencies = [ "light-account", "light-client", "light-compressed-account", - "light-compressible", "light-hasher", "light-heap", "light-macros", @@ -6616,7 +6651,6 @@ dependencies = [ "light-test-utils", "light-token", "light-token-interface", - "light-token-types", "solana-account-info", "solana-instruction", "solana-keypair", @@ -6639,7 +6673,6 @@ dependencies = [ "light-anchor-spl", "light-client", "light-compressed-account", - "light-compressible", "light-hasher", "light-heap", "light-macros", @@ -6663,39 +6696,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "single-pda-derive-test" -version = "0.1.0" -dependencies = [ - "anchor-lang", - "borsh 0.10.4", - "bytemuck", - "light-account", - "light-anchor-spl", - "light-client", - "light-compressed-account", - "light-compressible", - "light-hasher", - "light-macros", - "light-program-test", - "light-sdk-macros", - "light-sdk-types", - "light-test-utils", - "light-token", - "light-token-interface", - "light-token-types", - "solana-account-info", - "solana-instruction", - "solana-keypair", - "solana-msg 2.2.1", - "solana-program", - "solana-program-error 2.2.2", - "solana-pubkey 2.4.0", - "solana-sdk", - "solana-signer", - "tokio", -] - [[package]] name = "single-pda-test" version = "0.1.0" @@ -6705,17 +6705,14 @@ dependencies = [ "light-account", "light-client", "light-compressed-account", - "light-compressible", "light-hasher", "light-heap", "light-macros", "light-program-test", "light-sdk", "light-sdk-macros", - "light-sdk-types", "light-test-utils", "light-token", - "light-token-types", "solana-account-info", "solana-instruction", "solana-keypair", @@ -6737,7 +6734,6 @@ dependencies = [ "light-account", "light-client", "light-compressed-account", - "light-compressible", "light-hasher", "light-heap", "light-macros", diff --git a/Cargo.toml b/Cargo.toml index 00e97b60be..4f7dbb7d65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,11 +65,12 @@ members = [ "sdk-tests/csdk-anchor-full-derived-test-sdk", "sdk-tests/single-mint-test", "sdk-tests/single-pda-test", - "sdk-tests/single-pda-derive-test", + # "sdk-tests/single-pda-derive-test", "sdk-tests/single-account-loader-test", "sdk-tests/single-ata-test", "sdk-tests/single-token-test", "sdk-tests/manual-test", + "sdk-tests/manual-test-pinocchio", "forester-utils", "forester", "sparse-merkle-tree", diff --git a/program-libs/account-checks/src/account_info/account_info_trait.rs b/program-libs/account-checks/src/account_info/account_info_trait.rs index 632e9667f1..12ce7914ea 100644 --- a/program-libs/account-checks/src/account_info/account_info_trait.rs +++ b/program-libs/account-checks/src/account_info/account_info_trait.rs @@ -28,6 +28,9 @@ pub trait AccountInfoTrait { /// Return raw byte array for maximum compatibility fn key(&self) -> [u8; 32]; + /// Return a reference to the key bytes with the lifetime of the account. + /// Used when building seed ref arrays that borrow from the accounts slice. + fn key_ref(&self) -> &[u8]; /// Return the pubkey in the native format fn pubkey(&self) -> Self::Pubkey; fn is_writable(&self) -> bool; diff --git a/program-libs/account-checks/src/account_info/pinocchio.rs b/program-libs/account-checks/src/account_info/pinocchio.rs index 538b521234..e22feb165f 100644 --- a/program-libs/account-checks/src/account_info/pinocchio.rs +++ b/program-libs/account-checks/src/account_info/pinocchio.rs @@ -63,6 +63,10 @@ impl AccountInfoTrait for pinocchio::account_info::AccountInfo { *self.key() } + fn key_ref(&self) -> &[u8] { + self.key() + } + fn pubkey(&self) -> Self::Pubkey { *self.key() } @@ -250,9 +254,12 @@ impl AccountInfoTrait for pinocchio::account_info::AccountInfo { let pda_seeds_vec: Vec = pda_seeds.iter().map(|s| Seed::from(*s)).collect(); let pda_signer = Signer::from(&pda_seeds_vec[..]); + // Only build payer signer when rent_payer is itself a PDA. + // Passing empty seeds to invoke_signed causes create_program_address(&[], program_id) + // which can fail if the result happens to land on the ed25519 curve. let payer_seeds_vec: Vec = rent_payer_seeds.iter().map(|s| Seed::from(*s)).collect(); - let payer_signer = Signer::from(&payer_seeds_vec[..]); + let has_payer_seeds = !rent_payer_seeds.is_empty(); // Cold path: account already has lamports (e.g., attacker donation). // CreateAccount would fail, so use Assign + Allocate + Transfer. @@ -273,28 +280,47 @@ impl AccountInfoTrait for pinocchio::account_info::AccountInfo { let current_lamports = self.lamports(); if lamports > current_lamports { - pinocchio_system::instructions::Transfer { - from: rent_payer, - to: self, - lamports: lamports - current_lamports, + if has_payer_seeds { + let payer_signer = Signer::from(&payer_seeds_vec[..]); + pinocchio_system::instructions::Transfer { + from: rent_payer, + to: self, + lamports: lamports - current_lamports, + } + .invoke_signed(&[payer_signer]) + .map_err(AccountError::from)?; + } else { + pinocchio_system::instructions::Transfer { + from: rent_payer, + to: self, + lamports: lamports - current_lamports, + } + .invoke_signed(&[]) + .map_err(AccountError::from)?; } - .invoke_signed(&[payer_signer]) - .map_err(AccountError::from)?; } return Ok(()); } // Normal path: CreateAccount - pinocchio_system::instructions::CreateAccount { + let create_account = pinocchio_system::instructions::CreateAccount { from: rent_payer, to: self, lamports, space, owner, + }; + if has_payer_seeds { + let payer_signer = Signer::from(&payer_seeds_vec[..]); + create_account + .invoke_signed(&[payer_signer, pda_signer]) + .map_err(AccountError::from) + } else { + create_account + .invoke_signed(&[pda_signer]) + .map_err(AccountError::from) } - .invoke_signed(&[payer_signer, pda_signer]) - .map_err(AccountError::from) } fn transfer_lamports_cpi( @@ -350,7 +376,6 @@ impl AccountInfoTrait for pinocchio::account_info::AccountInfo { data: instruction_data, }; - // Build account info refs let info_refs: Vec<&pinocchio::account_info::AccountInfo> = account_infos.iter().collect(); diff --git a/program-libs/account-checks/src/account_info/solana.rs b/program-libs/account-checks/src/account_info/solana.rs index 449385ec05..8bc17648fc 100644 --- a/program-libs/account-checks/src/account_info/solana.rs +++ b/program-libs/account-checks/src/account_info/solana.rs @@ -18,6 +18,10 @@ impl AccountInfoTrait for solana_account_info::AccountInfo<'_> { self.key.to_bytes() } + fn key_ref(&self) -> &[u8] { + self.key.as_ref() + } + fn pubkey(&self) -> Self::Pubkey { *self.key } @@ -168,11 +172,22 @@ impl AccountInfoTrait for solana_account_info::AccountInfo<'_> { space, &owner_pubkey, ); - invoke_signed( - &create_ix, - &[rent_payer.clone(), self.clone()], - &[rent_payer_seeds, pda_seeds], - ) + // Only include rent_payer_seeds when the payer is itself a PDA. + // Passing empty seeds to invoke_signed causes create_program_address(&[], program_id) + // which can fail if the result happens to land on the ed25519 curve. + if rent_payer_seeds.is_empty() { + invoke_signed( + &create_ix, + &[rent_payer.clone(), self.clone()], + &[pda_seeds], + ) + } else { + invoke_signed( + &create_ix, + &[rent_payer.clone(), self.clone()], + &[rent_payer_seeds, pda_seeds], + ) + } .map_err(|_| AccountError::InvalidAccount) } @@ -294,15 +309,28 @@ fn create_pda_with_lamports_solana<'a>( account.key, lamports - current_lamports, ); - invoke_signed( - &transfer_ix, - &[ - rent_payer.clone(), - account.clone(), - system_program.clone(), - ], - &[rent_payer_seeds], - ) + // Only include rent_payer_seeds when the payer is itself a PDA. + if rent_payer_seeds.is_empty() { + invoke_signed( + &transfer_ix, + &[ + rent_payer.clone(), + account.clone(), + system_program.clone(), + ], + &[], + ) + } else { + invoke_signed( + &transfer_ix, + &[ + rent_payer.clone(), + account.clone(), + system_program.clone(), + ], + &[rent_payer_seeds], + ) + } .map_err(|_| AccountError::InvalidAccount)?; } diff --git a/sdk-libs/account-pinocchio/Cargo.toml b/sdk-libs/account-pinocchio/Cargo.toml index f21ef36af9..75b3f9f087 100644 --- a/sdk-libs/account-pinocchio/Cargo.toml +++ b/sdk-libs/account-pinocchio/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [features] default = ["alloc"] -std = ["alloc", "light-sdk-types/std", "light-compressed-account/std"] +std = ["alloc", "light-sdk-types/std", "light-compressed-account/std", "light-account-checks/solana", "dep:solana-instruction", "dep:solana-pubkey"] alloc = ["light-sdk-types/alloc", "light-compressed-account/alloc"] token = ["light-sdk-types/token", "dep:light-token-interface"] poseidon = ["light-sdk-types/poseidon", "light-hasher/poseidon"] @@ -23,6 +23,8 @@ light-hasher = { workspace = true, default-features = false } light-compressed-account = { workspace = true, default-features = false } light-token-interface = { workspace = true, optional = true } pinocchio = { workspace = true } +solana-instruction = { workspace = true, optional = true } +solana-pubkey = { workspace = true, optional = true } [lints.rust.unexpected_cfgs] level = "allow" diff --git a/sdk-libs/account-pinocchio/src/lib.rs b/sdk-libs/account-pinocchio/src/lib.rs index 0ba975b587..f04b2c6f83 100644 --- a/sdk-libs/account-pinocchio/src/lib.rs +++ b/sdk-libs/account-pinocchio/src/lib.rs @@ -29,7 +29,7 @@ pub type CpiContextWriteAccounts<'a> = #[cfg(all(not(target_os = "solana"), feature = "std"))] pub type PackedAccounts = light_sdk_types::interface::instruction::PackedAccounts< - light_account_checks::account_info::pinocchio::OwnedAccountMeta, + solana_instruction::AccountMeta, >; // ===== RE-EXPORTED TRAITS (generic over AI, used with explicit AccountInfo in impls) ===== diff --git a/sdk-libs/account/Cargo.toml b/sdk-libs/account/Cargo.toml index be440c367c..a260f78464 100644 --- a/sdk-libs/account/Cargo.toml +++ b/sdk-libs/account/Cargo.toml @@ -22,6 +22,7 @@ light-macros = { workspace = true } light-account-checks = { workspace = true, features = ["solana"] } light-hasher = { workspace = true, default-features = false } light-compressed-account = { workspace = true } +light-compressible = { workspace = true } light-token-interface = { workspace = true, optional = true } solana-account-info = { workspace = true } solana-instruction = { workspace = true } diff --git a/sdk-libs/account/src/lib.rs b/sdk-libs/account/src/lib.rs index e4dab4bef1..b271fa7f2b 100644 --- a/sdk-libs/account/src/lib.rs +++ b/sdk-libs/account/src/lib.rs @@ -123,6 +123,12 @@ pub use light_token_interface::instructions::extensions::TokenMetadataInstructio pub use light_token_interface::instructions::extensions::ExtensionInstructionData as TokenExtensionInstructionData; #[cfg(feature = "token")] pub use light_compressed_account::instruction_data::compressed_proof::CompressedProof; +#[cfg(feature = "token")] +pub use light_token_interface::state::AdditionalMetadata; + +/// Re-export Token state struct for client-side use. +#[cfg(feature = "token")] +pub use light_token_interface::state::Token; /// Token sub-module for paths like `light_account::token::TokenDataWithSeeds`. #[cfg(feature = "token")] @@ -188,10 +194,31 @@ pub use light_sdk_macros::{ LightHasherSha, LightProgram, }; +pub use light_compressible::rent::RentConfig; pub use light_sdk_types::error::LightSdkTypesError; pub use light_sdk_types::instruction::*; +pub use light_sdk_types::interface::account::size::Size; +pub use light_sdk_types::constants::{CPI_AUTHORITY_PDA_SEED, RENT_SPONSOR_SEED}; pub use light_sdk_types::{constants, CpiSigner}; +/// Hasher re-exports for macro-generated code paths like `light_account::hasher::DataHasher`. +pub mod hasher { + pub use light_hasher::errors::HasherError; + pub use light_hasher::{DataHasher, Hasher}; +} + +/// Re-export LIGHT_TOKEN_PROGRAM_ID as Pubkey for Anchor's `#[account(address = ...)]`. +pub const LIGHT_TOKEN_PROGRAM_ID: solana_pubkey::Pubkey = + solana_pubkey::Pubkey::new_from_array(constants::LIGHT_TOKEN_PROGRAM_ID); + +/// Default compressible config PDA for the Light Token Program. +pub const LIGHT_TOKEN_CONFIG: solana_pubkey::Pubkey = + solana_pubkey::Pubkey::new_from_array(constants::LIGHT_TOKEN_CONFIG); + +/// Default rent sponsor PDA for the Light Token Program. +pub const LIGHT_TOKEN_RENT_SPONSOR: solana_pubkey::Pubkey = + solana_pubkey::Pubkey::new_from_array(constants::LIGHT_TOKEN_RENT_SPONSOR); + // ===== UTILITY FUNCTIONS ===== /// Derives the rent sponsor PDA for a given program. diff --git a/sdk-libs/macros/src/light_pdas/account/derive.rs b/sdk-libs/macros/src/light_pdas/account/derive.rs index c4fad94821..35cd07bda5 100644 --- a/sdk-libs/macros/src/light_pdas/account/derive.rs +++ b/sdk-libs/macros/src/light_pdas/account/derive.rs @@ -55,7 +55,7 @@ fn is_zero_copy(attrs: &[syn::Attribute]) -> bool { /// /// ```ignore /// use light_sdk_macros::{LightAccount, LightDiscriminator, LightHasherSha}; -/// use light_sdk::compressible::CompressionInfo; +/// use light_account::CompressionInfo; /// use solana_pubkey::Pubkey; /// /// #[derive(Default, Debug, InitSpace, LightAccount, LightDiscriminator, LightHasherSha)] @@ -269,17 +269,17 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { const INIT_SPACE: usize = #init_space_token; #[inline] - fn compression_info(&self) -> &light_sdk::compressible::CompressionInfo { + fn compression_info(&self) -> &light_account::CompressionInfo { &self.compression_info } #[inline] - fn compression_info_mut(&mut self) -> &mut light_sdk::compressible::CompressionInfo { + fn compression_info_mut(&mut self) -> &mut light_account::CompressionInfo { &mut self.compression_info } fn set_decompressed(&mut self, config: &light_account::LightConfig, current_slot: u64) { - self.compression_info = light_sdk::compressible::CompressionInfo::new_from_config(config, current_slot); + self.compression_info = light_account::CompressionInfo::new_from_config(config, current_slot); #compress_as_assignments } @@ -287,8 +287,8 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { #[inline(never)] fn pack( &self, - accounts: &mut light_sdk::interface::instruction::PackedAccounts, - ) -> std::result::Result { + accounts: &mut light_account::interface::instruction::PackedAccounts, + ) -> std::result::Result { #pack_body } @@ -296,7 +296,7 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { fn unpack( packed: &Self::Packed, accounts: &light_account::packed_accounts::ProgramPackedAccounts, - ) -> std::result::Result { + ) -> std::result::Result { #unpack_body } } @@ -309,8 +309,8 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { fn pack( &self, - remaining_accounts: &mut light_sdk::interface::instruction::PackedAccounts, - ) -> std::result::Result { + remaining_accounts: &mut light_account::interface::instruction::PackedAccounts, + ) -> std::result::Result { ::pack(self, remaining_accounts) } } @@ -322,7 +322,7 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { fn unpack( &self, remaining_accounts: &[AI], - ) -> std::result::Result { + ) -> std::result::Result { // Create a ProgramPackedAccounts wrapper from remaining_accounts let accounts = light_account::packed_accounts::ProgramPackedAccounts { accounts: remaining_accounts @@ -333,11 +333,11 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { // V1 compatibility: HasCompressionInfo trait (wraps non-Option compression_info) impl light_account::HasCompressionInfo for #struct_name { - fn compression_info(&self) -> std::result::Result<&light_account::CompressionInfo, light_sdk_types::error::LightSdkTypesError> { + fn compression_info(&self) -> std::result::Result<&light_account::CompressionInfo, light_account::LightSdkTypesError> { Ok(&self.compression_info) } - fn compression_info_mut(&mut self) -> std::result::Result<&mut light_account::CompressionInfo, light_sdk_types::error::LightSdkTypesError> { + fn compression_info_mut(&mut self) -> std::result::Result<&mut light_account::CompressionInfo, light_account::LightSdkTypesError> { Ok(&mut self.compression_info) } @@ -347,18 +347,18 @@ fn generate_light_account_impl(input: &DeriveInput) -> Result { panic!("compression_info_mut_opt not supported for LightAccount types (use compression_info_mut instead)") } - fn set_compression_info_none(&mut self) -> std::result::Result<(), light_sdk_types::error::LightSdkTypesError> { + fn set_compression_info_none(&mut self) -> std::result::Result<(), light_account::LightSdkTypesError> { // V2 types use non-Option CompressionInfo // Setting to "compressed" state is the equivalent of "None" for V1 - self.compression_info = light_sdk::compressible::CompressionInfo::compressed(); + self.compression_info = light_account::CompressionInfo::compressed(); Ok(()) } } // V1 compatibility: Size trait - impl light_sdk::account::Size for #struct_name { + impl light_account::Size for #struct_name { #[inline] - fn size(&self) -> std::result::Result { + fn size(&self) -> std::result::Result { Ok(::INIT_SPACE) } } @@ -469,7 +469,7 @@ fn generate_pack_body( let field_type = &field.ty; Some(if is_pubkey_type(field_type) { - quote! { #field_name: accounts.insert_or_get_read_only(self.#field_name.to_bytes()) } + quote! { #field_name: accounts.insert_or_get_read_only(AM::pubkey_from_bytes(self.#field_name.to_bytes())) } } else if is_copy_type(field_type) { quote! { #field_name: self.#field_name } } else { @@ -509,7 +509,7 @@ fn generate_unpack_body( // compression_info gets canonical value if field_name == "compression_info" { return Some(quote! { - #field_name: light_sdk::compressible::CompressionInfo::compressed() + #field_name: light_account::CompressionInfo::compressed() }); } @@ -519,7 +519,7 @@ fn generate_unpack_body( #field_name: { let account = accounts .get_u8(packed.#field_name, #error_msg) - .map_err(|_| light_sdk_types::error::LightSdkTypesError::InvalidInstructionData)?; + .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)?; solana_pubkey::Pubkey::from(account.key()) } } @@ -594,7 +594,7 @@ fn generate_compress_as_impl_body( // No overrides - clone and set compression_info to Compressed return quote! { let mut result = self.clone(); - result.compression_info = light_sdk::compressible::CompressionInfo::compressed(); + result.compression_info = light_account::CompressionInfo::compressed(); std::borrow::Cow::Owned(result) }; }; @@ -629,14 +629,14 @@ fn generate_compress_as_impl_body( // No field overrides - clone and set compression_info to Compressed quote! { let mut result = self.clone(); - result.compression_info = light_sdk::compressible::CompressionInfo::compressed(); + result.compression_info = light_account::CompressionInfo::compressed(); std::borrow::Cow::Owned(result) } } else { // Clone, set compression_info to Compressed, and apply overrides quote! { let mut result = self.clone(); - result.compression_info = light_sdk::compressible::CompressionInfo::compressed(); + result.compression_info = light_account::CompressionInfo::compressed(); #(#assignments)* std::borrow::Cow::Owned(result) } diff --git a/sdk-libs/macros/src/light_pdas/account/traits.rs b/sdk-libs/macros/src/light_pdas/account/traits.rs index 48aff1a5ce..4c586221a3 100644 --- a/sdk-libs/macros/src/light_pdas/account/traits.rs +++ b/sdk-libs/macros/src/light_pdas/account/traits.rs @@ -152,14 +152,14 @@ fn generate_compress_as_impl( /// Uses max(INIT_SPACE, serialized_len) to ensure enough space while handling edge cases. fn generate_size_impl(struct_name: &Ident) -> TokenStream { quote! { - impl light_sdk::account::Size for #struct_name { + impl light_account::Size for #struct_name { #[inline] - fn size(&self) -> std::result::Result { + fn size(&self) -> std::result::Result { // Use Anchor's compile-time INIT_SPACE as the baseline. // Fall back to serialized length if it's somehow larger (edge case safety). let init_space = ::INIT_SPACE; let serialized_len = self.try_to_vec() - .map_err(|_| solana_program_error::ProgramError::BorshIoError("serialization failed".to_string()))? + .map_err(|_| light_account::LightSdkTypesError::BorshIoError("serialization failed".to_string()))? .len(); Ok(core::cmp::max(init_space, serialized_len)) } diff --git a/sdk-libs/macros/src/light_pdas/accounts/builder.rs b/sdk-libs/macros/src/light_pdas/accounts/builder.rs index 9253f25d76..5414cb8dbc 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/builder.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/builder.rs @@ -149,7 +149,7 @@ impl LightAccountsBuilder { &mut self, _remaining: &[solana_account_info::AccountInfo<'info>], _params: &(), - ) -> std::result::Result { + ) -> std::result::Result { Ok(false) } } @@ -161,7 +161,7 @@ impl LightAccountsBuilder { _remaining: &[solana_account_info::AccountInfo<'info>], _params: &(), _has_pre_init: bool, - ) -> std::result::Result<(), light_sdk_types::error::LightSdkTypesError> { + ) -> std::result::Result<(), light_account::LightSdkTypesError> { Ok(()) } } @@ -299,14 +299,14 @@ impl LightAccountsBuilder { let compression_config = &self.infra.compression_config; Ok(quote! { - let cpi_accounts = light_sdk::cpi::v2::CpiAccounts::new_with_config( + let cpi_accounts = light_account::CpiAccounts::new_with_config( &self.#fee_payer, _remaining, - light_sdk::cpi::CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER), + light_account::CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER), ); let compression_config_data = light_account::LightConfig::load_checked( &self.#compression_config, - &crate::ID, + &crate::ID.to_bytes(), )?; let mut all_new_address_params = Vec::with_capacity(#rentfree_count as usize); @@ -342,16 +342,16 @@ impl LightAccountsBuilder { let compression_config = &self.infra.compression_config; Ok(quote! { - use light_sdk::cpi::{LightCpiInstruction, InvokeLightSystemProgram}; + use light_account::InvokeLightSystemProgram; - let cpi_accounts = light_sdk::cpi::v2::CpiAccounts::new( + let cpi_accounts = light_account::CpiAccounts::new( &self.#fee_payer, _remaining, crate::LIGHT_CPI_SIGNER, ); let compression_config_data = light_account::LightConfig::load_checked( &self.#compression_config, - &crate::ID, + &crate::ID.to_bytes(), )?; let mut all_new_address_params = Vec::with_capacity(#rentfree_count as usize); @@ -361,13 +361,22 @@ impl LightAccountsBuilder { // Reimburse fee payer for rent paid during PDA creation #rent_reimbursement - light_sdk::cpi::v2::LightSystemProgramCpi::new_cpi( - crate::LIGHT_CPI_SIGNER, - #proof_access.proof.clone(), - ) - .with_new_addresses(&all_new_address_params) - .with_account_infos(&all_compressed_infos) - .invoke(cpi_accounts)?; + let instruction_data = light_compressed_account::instruction_data::with_account_info::InstructionDataInvokeCpiWithAccountInfo { + mode: 1, + bump: crate::LIGHT_CPI_SIGNER.bump, + invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), + compress_or_decompress_lamports: 0, + is_compress: false, + with_cpi_context: false, + with_transaction_hash: false, + cpi_context: light_compressed_account::instruction_data::cpi_context::CompressedCpiContext::default(), + proof: #proof_access.proof.clone().0, + new_address_params: all_new_address_params, + account_infos: all_compressed_infos, + read_only_addresses: vec![], + read_only_accounts: vec![], + }; + instruction_data.invoke(cpi_accounts)?; }) } @@ -383,10 +392,10 @@ impl LightAccountsBuilder { let fee_payer = &self.infra.fee_payer; Ok(quote! { - let cpi_accounts = light_sdk::cpi::v2::CpiAccounts::new_with_config( + let cpi_accounts = light_account::CpiAccounts::new_with_config( &self.#fee_payer, _remaining, - light_sdk::cpi::CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER), + light_account::CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER), ); #mint_invocation @@ -410,7 +419,7 @@ impl LightAccountsBuilder { &mut self, _remaining: &[solana_account_info::AccountInfo<'info>], #params_ident: &#params_type, - ) -> std::result::Result { + ) -> std::result::Result { use anchor_lang::ToAccountInfo; #body } @@ -436,7 +445,7 @@ impl LightAccountsBuilder { _remaining: &[solana_account_info::AccountInfo<'info>], #params_ident: &#params_type, _has_pre_init: bool, - ) -> std::result::Result<(), light_sdk_types::error::LightSdkTypesError> { + ) -> std::result::Result<(), light_account::LightSdkTypesError> { use anchor_lang::ToAccountInfo; #body } diff --git a/sdk-libs/macros/src/light_pdas/accounts/pda.rs b/sdk-libs/macros/src/light_pdas/accounts/pda.rs index d31b51753e..4be9cbec16 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/pda.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/pda.rs @@ -55,7 +55,7 @@ impl<'a> PdaBlockBuilder<'a> { quote! { let #account_info = self.#field_name.to_account_info(); - let #account_key = *#account_info.key; + let #account_key = #account_info.key.to_bytes(); } } @@ -69,13 +69,12 @@ impl<'a> PdaBlockBuilder<'a> { let address_tree_pubkey = &self.idents.address_tree_pubkey; quote! { - let #address_tree_pubkey: solana_pubkey::Pubkey = { - use light_sdk::light_account_checks::AccountInfoTrait; + let #address_tree_pubkey: [u8; 32] = { // Explicit type annotation ensures clear error if wrong type is provided. - let tree_info: &::light_sdk::sdk_types::PackedAddressTreeInfo = &#addr_tree_info; - cpi_accounts - .get_tree_account_info(tree_info.address_merkle_tree_pubkey_index as usize)? - .pubkey() + let tree_info: &light_account::PackedAddressTreeInfo = &#addr_tree_info; + let __tree_account = cpi_accounts + .get_tree_account_info(tree_info.address_merkle_tree_pubkey_index as usize)?; + light_account::AccountInfoTrait::key(__tree_account) }; } } @@ -90,13 +89,14 @@ impl<'a> PdaBlockBuilder<'a> { let account_guard = format_ident!("{}_guard", ident); quote! { { - let current_slot = anchor_lang::solana_program::sysvar::clock::Clock::get()?.slot; + let current_slot = anchor_lang::solana_program::sysvar::clock::Clock::get() + .map_err(|_| light_account::LightSdkTypesError::ConstraintViolation)?.slot; let mut #account_guard = self.#ident.load_init() - .map_err(|_| solana_program_error::ProgramError::InvalidAccountData)?; + .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)?; let #account_data = &mut *#account_guard; // For zero-copy Pod accounts, set compression_info directly #account_data.compression_info = - light_sdk::compressible::CompressionInfo::new_from_config( + light_account::CompressionInfo::new_from_config( &compression_config_data, current_slot, ); @@ -107,7 +107,8 @@ impl<'a> PdaBlockBuilder<'a> { { use light_account::LightAccount; use anchor_lang::AnchorSerialize; - let current_slot = anchor_lang::solana_program::sysvar::clock::Clock::get()?.slot; + let current_slot = anchor_lang::solana_program::sysvar::clock::Clock::get() + .map_err(|_| light_account::LightSdkTypesError::ConstraintViolation)?.slot; // Get account info BEFORE mutable borrow let account_info = self.#ident.to_account_info(); // Scope the mutable borrow @@ -119,9 +120,9 @@ impl<'a> PdaBlockBuilder<'a> { // Now serialize - the mutable borrow above is released let mut data = account_info .try_borrow_mut_data() - .map_err(|_| light_sdk_types::error::LightSdkTypesError::ConstraintViolation)?; + .map_err(|_| light_account::LightSdkTypesError::ConstraintViolation)?; self.#ident.serialize(&mut &mut data[8..]) - .map_err(|_| light_sdk_types::error::LightSdkTypesError::ConstraintViolation)?; + .map_err(|_| light_account::LightSdkTypesError::ConstraintViolation)?; } } } else { @@ -129,7 +130,8 @@ impl<'a> PdaBlockBuilder<'a> { { use light_account::LightAccount; use anchor_lang::AnchorSerialize; - let current_slot = anchor_lang::solana_program::sysvar::clock::Clock::get()?.slot; + let current_slot = anchor_lang::solana_program::sysvar::clock::Clock::get() + .map_err(|_| light_account::LightSdkTypesError::ConstraintViolation)?.slot; // Get account info BEFORE mutable borrow let account_info = self.#ident.to_account_info(); // Scope the mutable borrow @@ -141,9 +143,9 @@ impl<'a> PdaBlockBuilder<'a> { // Now serialize - the mutable borrow above is released let mut data = account_info .try_borrow_mut_data() - .map_err(|_| light_sdk_types::error::LightSdkTypesError::ConstraintViolation)?; + .map_err(|_| light_account::LightSdkTypesError::ConstraintViolation)?; self.#ident.serialize(&mut &mut data[8..]) - .map_err(|_| light_sdk_types::error::LightSdkTypesError::ConstraintViolation)?; + .map_err(|_| light_account::LightSdkTypesError::ConstraintViolation)?; } } } @@ -168,7 +170,7 @@ impl<'a> PdaBlockBuilder<'a> { quote! { { // Explicit type annotation for tree_info - let tree_info: &::light_sdk::sdk_types::PackedAddressTreeInfo = &#addr_tree_info; + let tree_info: &light_account::PackedAddressTreeInfo = &#addr_tree_info; ::light_account::prepare_compressed_account_on_init( &#account_key, @@ -176,7 +178,7 @@ impl<'a> PdaBlockBuilder<'a> { tree_info, #output_tree, #idx, - &crate::ID, + &crate::ID.to_bytes(), &mut all_new_address_params, &mut all_compressed_infos, )?; @@ -248,11 +250,16 @@ pub(super) fn generate_rent_reimbursement_block( let __created_accounts: [solana_account_info::AccountInfo<'info>; #count] = [ #(#account_info_exprs),* ]; + let __rent_sponsor_bump_byte = [compression_config_data.rent_sponsor_bump]; + let __rent_sponsor_seeds: &[&[u8]] = &[ + light_account::RENT_SPONSOR_SEED, + &__rent_sponsor_bump_byte, + ]; ::light_account::reimburse_rent( &__created_accounts, &self.#fee_payer.to_account_info(), &self.#rent_sponsor.to_account_info(), - &crate::ID, + __rent_sponsor_seeds, )?; } } diff --git a/sdk-libs/macros/src/light_pdas/accounts/token.rs b/sdk-libs/macros/src/light_pdas/accounts/token.rs index a8b8e770ef..dc7614f745 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/token.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/token.rs @@ -117,25 +117,24 @@ pub(super) fn generate_token_account_cpi( quote! { &[#(#seed_refs,)* &__bump_slice[..]] } }; - // Get mint and owner from field or derive from context - // mint is used as AccountInfo for CPI - let mint_account_info = field + // Get mint binding from field or default + let mint_binding = field .mint .as_ref() - .map(|m| quote! { self.#m.to_account_info() }) - .unwrap_or_else(|| quote! { self.mint.to_account_info() }); + .map(|m| quote! { let __mint_info = self.#m.to_account_info(); }) + .unwrap_or_else(|| quote! { let __mint_info = self.mint.to_account_info(); }); - // owner is a Pubkey - the owner of the token account + // owner is [u8; 32] - the owner of the token account let owner_expr = field .owner .as_ref() - .map(|o| quote! { *self.#o.to_account_info().key }) - .unwrap_or_else(|| quote! { *self.fee_payer.to_account_info().key }); + .map(|o| quote! { self.#o.to_account_info().key.to_bytes() }) + .unwrap_or_else(|| quote! { self.fee_payer.to_account_info().key.to_bytes() }); Some(quote! { // Create token account: #field_ident { - use light_token::instruction::CreateTokenAccountCpi; + use light_account::CreateTokenAccountCpi; // Bind seeds to local variables to extend temporary lifetimes #(#seed_bindings)* @@ -145,17 +144,24 @@ pub(super) fn generate_token_account_cpi( let __bump_slice: [u8; 1] = [__bump]; let __token_account_seeds: &[&[u8]] = #seeds_array_expr; + // Bind account infos to local variables so we can pass references + let __payer_info = self.#fee_payer.to_account_info(); + let __account_info = self.#field_ident.to_account_info(); + #mint_binding + let __config_info = self.#light_token_config.to_account_info(); + let __sponsor_info = self.#light_token_rent_sponsor.to_account_info(); + CreateTokenAccountCpi { - payer: self.#fee_payer.to_account_info(), - account: self.#field_ident.to_account_info(), - mint: #mint_account_info, + payer: &__payer_info, + account: &__account_info, + mint: &__mint_info, owner: #owner_expr, } .rent_free( - self.#light_token_config.to_account_info(), - self.#light_token_rent_sponsor.to_account_info(), - __system_program.clone(), - &crate::ID, + &__config_info, + &__sponsor_info, + &__system_program, + &crate::ID.to_bytes(), ) .invoke_signed(__token_account_seeds)?; } @@ -187,9 +193,9 @@ pub(super) fn generate_ata_cpi(field: &AtaField, infra: &InfraRefs) -> Option( + &self.#owner.to_account_info().key.to_bytes(), + &self.#mint.to_account_info().key.to_bytes(), ); bump } @@ -199,20 +205,28 @@ pub(super) fn generate_ata_cpi(field: &AtaField, infra: &InfraRefs) -> Option for #variant_name { - const PROGRAM_ID: Pubkey = crate::ID; + const PROGRAM_ID: [u8; 32] = crate::ID.to_bytes(); type Seeds = #seeds_struct_name; type Data = #inner_type; @@ -281,9 +281,9 @@ impl VariantBuilder { // Build ProgramPackedAccounts from the accounts slice let unpack_data = quote! { { - let packed_accounts = light_sdk::light_account_checks::packed_accounts::ProgramPackedAccounts { accounts }; + let packed_accounts = light_account::packed_accounts::ProgramPackedAccounts { accounts }; <#inner_type as light_account::LightAccount>::unpack(&self.data, &packed_accounts) - .map_err(|_| anchor_lang::error::ErrorCode::InvalidProgramId)? + .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)? } }; @@ -298,7 +298,7 @@ impl VariantBuilder { self.seeds.bump } - fn unpack(&self, accounts: &[anchor_lang::prelude::AccountInfo]) -> anchor_lang::Result { + fn unpack(&self, accounts: &[AI]) -> std::result::Result { #(#unpack_seed_stmts)* Ok(#variant_name { @@ -309,21 +309,16 @@ impl VariantBuilder { }) } - fn seed_refs_with_bump<'a>( + fn seed_refs_with_bump<'a, AI: light_account::AccountInfoTrait>( &'a self, - accounts: &'a [anchor_lang::prelude::AccountInfo], + accounts: &'a [AI], bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; #seed_count], solana_program_error::ProgramError> { + ) -> std::result::Result<[&'a [u8]; #seed_count], light_account::LightSdkTypesError> { Ok([#(#packed_seed_refs_items,)* bump_storage]) } - fn into_in_token_data(&self, _tree_info: &light_sdk::instruction::PackedStateTreeInfo, _output_queue_index: u8) -> anchor_lang::Result { - Err(solana_program_error::ProgramError::InvalidAccountData.into()) - } - - fn into_in_tlv(&self) -> anchor_lang::Result>> { - Ok(None) - } + // into_in_token_data and into_in_tlv use default impls from trait + // (return Err/None for PDA variants) } } } @@ -343,21 +338,21 @@ impl VariantBuilder { // Use LightAccount::pack for all accounts (including zero-copy) let pack_data = quote! { <#inner_type as light_account::LightAccount>::pack(&self.data, accounts) - .map_err(|_| solana_program_error::ProgramError::InvalidAccountData)? + .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)? }; quote! { // Pack trait is only available off-chain (client-side packing) #[cfg(not(target_os = "solana"))] - impl light_sdk::Pack for #variant_name { + impl light_account::Pack for #variant_name { type Packed = #packed_variant_name; fn pack( &self, - accounts: &mut light_sdk::instruction::PackedAccounts, - ) -> std::result::Result { + accounts: &mut light_account::interface::instruction::PackedAccounts, + ) -> std::result::Result { use light_account::LightAccountVariantTrait; - let (_, bump) = self.derive_pda(); + let (_, bump) = self.derive_pda::>(); Ok(#packed_variant_name { seeds: #packed_seeds_struct_name { #(#pack_seed_fields,)* @@ -482,7 +477,7 @@ impl VariantBuilder { let field = &sf.field_name; if sf.is_account_seed { let idx_field = format_ident!("{}_idx", field); - quote! { #idx_field: accounts.insert_or_get(self.seeds.#field) } + quote! { #idx_field: accounts.insert_or_get(AM::pubkey_from_bytes(self.seeds.#field.to_bytes())) } } else if sf.has_le_bytes { quote! { #field: self.seeds.#field.to_le_bytes() } } else { @@ -494,7 +489,7 @@ impl VariantBuilder { /// Generate unpack statements to resolve indices to Pubkeys. /// - /// Used in `unpack()` which returns `anchor_lang::Result`. + /// Used in `unpack()` which returns `Result<..., LightSdkTypesError>`. fn generate_unpack_seed_statements(&self, _for_program_error: bool) -> Vec { self.seed_fields .iter() @@ -503,10 +498,12 @@ impl VariantBuilder { let field = &sf.field_name; let idx_field = format_ident!("{}_idx", field); quote! { - let #field = *accounts - .get(self.seeds.#idx_field as usize) - .ok_or(anchor_lang::error::ErrorCode::AccountNotEnoughKeys)? - .key; + let #field = solana_pubkey::Pubkey::new_from_array( + accounts + .get(self.seeds.#idx_field as usize) + .ok_or(light_account::LightSdkTypesError::NotEnoughAccountKeys)? + .key() + ); } }) .collect() @@ -568,9 +565,8 @@ impl VariantBuilder { quote! { accounts .get(self.seeds.#idx_field as usize) - .ok_or(solana_program_error::ProgramError::InvalidAccountData)? - .key - .as_ref() + .ok_or(light_account::LightSdkTypesError::InvalidInstructionData)? + .key_ref() } } ClassifiedSeed::DataRooted { root, expr, .. } => { diff --git a/sdk-libs/macros/src/light_pdas/program/compress.rs b/sdk-libs/macros/src/light_pdas/program/compress.rs index 54a841a9bb..d05f1dfe69 100644 --- a/sdk-libs/macros/src/light_pdas/program/compress.rs +++ b/sdk-libs/macros/src/light_pdas/program/compress.rs @@ -114,7 +114,7 @@ impl CompressBuilder { d if d == #name::LIGHT_DISCRIMINATOR => { let mut reader = &data[8..]; let mut account_data = #name::deserialize(&mut reader) - .map_err(|_| solana_program_error::ProgramError::InvalidAccountData)?; + .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)?; drop(data); light_account::prepare_account_for_compression( account_info, &mut account_data, meta, index, ctx, @@ -127,16 +127,16 @@ impl CompressBuilder { Ok(syn::parse_quote! { fn __compress_dispatch<'info>( account_info: &anchor_lang::prelude::AccountInfo<'info>, - meta: &light_sdk::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, + meta: &light_account::account_meta::CompressedAccountMetaNoLamportsNoAddress, index: usize, ctx: &mut light_account::CompressCtx<'_, 'info>, - ) -> std::result::Result<(), solana_program_error::ProgramError> { - use light_sdk::LightDiscriminator; + ) -> std::result::Result<(), light_account::LightSdkTypesError> { + use light_account::LightDiscriminator; use borsh::BorshDeserialize; let data = account_info.try_borrow_data()?; let discriminator: [u8; 8] = data[..8] .try_into() - .map_err(|_| solana_program_error::ProgramError::InvalidAccountData)?; + .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)?; match discriminator { #(#compress_arms)* _ => Ok(()), @@ -158,9 +158,9 @@ impl CompressBuilder { instruction_data, __compress_dispatch, LIGHT_CPI_SIGNER, - &crate::ID, + &crate::ID.to_bytes(), ) - .map_err(|e: solana_program_error::ProgramError| -> anchor_lang::error::Error { e.into() }) + .map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::from(e))) } }) } @@ -336,7 +336,7 @@ impl CompressBuilder { d if d == #name::LIGHT_DISCRIMINATOR => { let mut reader = &data[8..]; let mut account_data = #name::deserialize(&mut reader) - .map_err(|_| solana_program_error::ProgramError::InvalidAccountData)?; + .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)?; drop(data); light_account::prepare_account_for_compression( account_info, &mut account_data, meta, index, ctx, @@ -350,16 +350,16 @@ impl CompressBuilder { impl #enum_name { pub fn compress_dispatch<'info>( account_info: &anchor_lang::prelude::AccountInfo<'info>, - meta: &light_sdk::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, + meta: &light_account::account_meta::CompressedAccountMetaNoLamportsNoAddress, index: usize, ctx: &mut light_account::CompressCtx<'_, 'info>, - ) -> std::result::Result<(), solana_program_error::ProgramError> { - use light_sdk::LightDiscriminator; + ) -> std::result::Result<(), light_account::LightSdkTypesError> { + use light_account::LightDiscriminator; use borsh::BorshDeserialize; let data = account_info.try_borrow_data()?; let discriminator: [u8; 8] = data[..8] .try_into() - .map_err(|_| solana_program_error::ProgramError::InvalidAccountData)?; + .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)?; match discriminator { #(#compress_arms)* _ => Ok(()), diff --git a/sdk-libs/macros/src/light_pdas/program/decompress.rs b/sdk-libs/macros/src/light_pdas/program/decompress.rs index 6b2d141279..f49916c089 100644 --- a/sdk-libs/macros/src/light_pdas/program/decompress.rs +++ b/sdk-libs/macros/src/light_pdas/program/decompress.rs @@ -30,6 +30,10 @@ pub(super) struct DecompressBuilder { pda_ctx_seeds: Vec, /// PDA seed specifications. pda_seeds: Option>, + /// Whether the program has token accounts (tokens/ATAs/mints). + /// When true, the generated processor calls the full decompress function + /// that handles both PDA and token accounts. + has_tokens: bool, } impl DecompressBuilder { @@ -38,10 +42,16 @@ impl DecompressBuilder { /// # Arguments /// * `pda_ctx_seeds` - PDA context seed information for each variant /// * `pda_seeds` - PDA seed specifications - pub fn new(pda_ctx_seeds: Vec, pda_seeds: Option>) -> Self { + /// * `has_tokens` - Whether the program has token accounts + pub fn new( + pda_ctx_seeds: Vec, + pda_seeds: Option>, + has_tokens: bool, + ) -> Self { Self { pda_ctx_seeds, pda_seeds, + has_tokens, } } @@ -50,22 +60,50 @@ impl DecompressBuilder { // ------------------------------------------------------------------------- /// Generate the processor function for decompress accounts (v2 interface). + /// + /// For programs with token accounts, calls the full processor that handles + /// both PDA and token decompression. For PDA-only programs, calls the + /// simpler PDA-only processor. pub fn generate_processor(&self) -> Result { - Ok(syn::parse_quote! { - #[inline(never)] - pub fn process_decompress_accounts_idempotent<'info>( - remaining_accounts: &[solana_account_info::AccountInfo<'info>], - instruction_data: &[u8], - ) -> Result<()> { - light_account::process_decompress_pda_accounts_idempotent::( - remaining_accounts, - instruction_data, - LIGHT_CPI_SIGNER, - &crate::ID, - ) - .map_err(|e: solana_program_error::ProgramError| -> anchor_lang::error::Error { e.into() }) - } - }) + if self.has_tokens { + Ok(syn::parse_quote! { + #[inline(never)] + pub fn process_decompress_accounts_idempotent<'info>( + remaining_accounts: &[solana_account_info::AccountInfo<'info>], + instruction_data: &[u8], + ) -> Result<()> { + use solana_program::{clock::Clock, sysvar::Sysvar}; + let current_slot = Clock::get()?.slot; + light_account::process_decompress_accounts_idempotent::<_, PackedLightAccountVariant>( + remaining_accounts, + instruction_data, + LIGHT_CPI_SIGNER, + &crate::ID.to_bytes(), + current_slot, + ) + .map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::from(e))) + } + }) + } else { + Ok(syn::parse_quote! { + #[inline(never)] + pub fn process_decompress_accounts_idempotent<'info>( + remaining_accounts: &[solana_account_info::AccountInfo<'info>], + instruction_data: &[u8], + ) -> Result<()> { + use solana_program::{clock::Clock, sysvar::Sysvar}; + let current_slot = Clock::get()?.slot; + light_account::process_decompress_pda_accounts_idempotent::<_, PackedLightAccountVariant>( + remaining_accounts, + instruction_data, + LIGHT_CPI_SIGNER, + &crate::ID.to_bytes(), + current_slot, + ) + .map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::from(e))) + } + }) + } } /// Generate the decompress instruction entrypoint function (v2 interface). @@ -299,10 +337,10 @@ impl DecompressBuilder { impl light_account::PdaSeedDerivation<#ctx_seeds_struct_name, SeedParams> for #inner_type { fn derive_pda_seeds_with_accounts( &self, - program_id: &solana_pubkey::Pubkey, + program_id: &[u8; 32], ctx_seeds: &#ctx_seeds_struct_name, seed_params: &SeedParams, - ) -> std::result::Result<(Vec>, solana_pubkey::Pubkey), solana_program_error::ProgramError> { + ) -> std::result::Result<(Vec>, [u8; 32]), light_account::LightSdkTypesError> { #seed_derivation } } @@ -314,10 +352,10 @@ impl DecompressBuilder { impl light_account::PdaSeedDerivation<#ctx_seeds_struct_name, SeedParams> for #inner_type { fn derive_pda_seeds_with_accounts( &self, - program_id: &solana_pubkey::Pubkey, + program_id: &[u8; 32], ctx_seeds: &#ctx_seeds_struct_name, _seed_params: &SeedParams, - ) -> std::result::Result<(Vec>, solana_pubkey::Pubkey), solana_program_error::ProgramError> { + ) -> std::result::Result<(Vec>, [u8; 32]), light_account::LightSdkTypesError> { #seed_derivation } } @@ -346,9 +384,9 @@ impl DecompressBuilder { pub fn decompress_dispatch<'info>( remaining_accounts: &[solana_account_info::AccountInfo<'info>], instruction_data: &[u8], - cpi_signer: light_sdk_types::CpiSigner, + cpi_signer: light_account::CpiSigner, program_id: &solana_pubkey::Pubkey, - ) -> std::result::Result<(), solana_program_error::ProgramError> { + ) -> std::result::Result<(), light_account::LightSdkTypesError> { light_account::process_decompress_pda_accounts_idempotent::( remaining_accounts, instruction_data, @@ -456,7 +494,7 @@ fn generate_pda_seed_derivation_for_trait_with_ctx_seeds( ); bindings.push(quote! { let #binding_name = seed_params.#field_ident - .ok_or(solana_program_error::ProgramError::InvalidAccountData)?; + .ok_or(light_account::LightSdkTypesError::InvalidInstructionData)?; let #bytes_binding_name = #binding_name.to_le_bytes(); }); seed_refs.push(quote! { #bytes_binding_name.as_ref() }); @@ -464,7 +502,7 @@ fn generate_pda_seed_derivation_for_trait_with_ctx_seeds( // Pubkey field bindings.push(quote! { let #binding_name = seed_params.#field_ident - .ok_or(solana_program_error::ProgramError::InvalidAccountData)?; + .ok_or(light_account::LightSdkTypesError::InvalidInstructionData)?; }); seed_refs.push(quote! { #binding_name.as_ref() }); } @@ -499,7 +537,8 @@ fn generate_pda_seed_derivation_for_trait_with_ctx_seeds( Ok(quote! { #(#bindings)* let seeds: &[&[u8]] = &[#(#seed_refs,)*]; - let (pda, bump) = solana_pubkey::Pubkey::find_program_address(seeds, program_id); + let program_id_pubkey = solana_pubkey::Pubkey::from(*program_id); + let (pda, bump) = solana_pubkey::Pubkey::find_program_address(seeds, &program_id_pubkey); let mut seeds_vec = Vec::with_capacity(seeds.len() + 1); #( seeds_vec.push(seeds[#indices].to_vec()); @@ -510,7 +549,7 @@ fn generate_pda_seed_derivation_for_trait_with_ctx_seeds( bump_vec.push(bump); seeds_vec.push(bump_vec); } - Ok((seeds_vec, pda)) + Ok((seeds_vec, pda.to_bytes())) }) } diff --git a/sdk-libs/macros/src/light_pdas/program/instructions.rs b/sdk-libs/macros/src/light_pdas/program/instructions.rs index 0834e23847..e50f80b872 100644 --- a/sdk-libs/macros/src/light_pdas/program/instructions.rs +++ b/sdk-libs/macros/src/light_pdas/program/instructions.rs @@ -125,55 +125,55 @@ pub(crate) fn generate_light_program_items( } } - impl ::light_sdk::hasher::DataHasher for LightAccountVariant { - fn hash(&self) -> std::result::Result<[u8; 32], ::light_sdk::hasher::HasherError> { + impl light_account::hasher::DataHasher for LightAccountVariant { + fn hash(&self) -> std::result::Result<[u8; 32], light_account::hasher::HasherError> { match self { - Self::Empty => Err(::light_sdk::hasher::HasherError::EmptyInput), + Self::Empty => Err(light_account::hasher::HasherError::EmptyInput), } } } - impl light_sdk::LightDiscriminator for LightAccountVariant { + impl light_account::LightDiscriminator for LightAccountVariant { const LIGHT_DISCRIMINATOR: [u8; 8] = [0; 8]; const LIGHT_DISCRIMINATOR_SLICE: &'static [u8] = &Self::LIGHT_DISCRIMINATOR; } impl light_account::HasCompressionInfo for LightAccountVariant { - fn compression_info(&self) -> std::result::Result<&light_account::CompressionInfo, solana_program_error::ProgramError> { - Err(solana_program_error::ProgramError::InvalidAccountData) + fn compression_info(&self) -> std::result::Result<&light_account::CompressionInfo, light_account::LightSdkTypesError> { + Err(light_account::LightSdkTypesError::InvalidInstructionData) } - fn compression_info_mut(&mut self) -> std::result::Result<&mut light_account::CompressionInfo, solana_program_error::ProgramError> { - Err(solana_program_error::ProgramError::InvalidAccountData) + fn compression_info_mut(&mut self) -> std::result::Result<&mut light_account::CompressionInfo, light_account::LightSdkTypesError> { + Err(light_account::LightSdkTypesError::InvalidInstructionData) } fn compression_info_mut_opt(&mut self) -> &mut Option { panic!("compression_info_mut_opt not supported for mint-only programs") } - fn set_compression_info_none(&mut self) -> std::result::Result<(), solana_program_error::ProgramError> { - Err(solana_program_error::ProgramError::InvalidAccountData) + fn set_compression_info_none(&mut self) -> std::result::Result<(), light_account::LightSdkTypesError> { + Err(light_account::LightSdkTypesError::InvalidInstructionData) } } - impl light_sdk::account::Size for LightAccountVariant { - fn size(&self) -> std::result::Result { - Err(solana_program_error::ProgramError::InvalidAccountData) + impl light_account::Size for LightAccountVariant { + fn size(&self) -> std::result::Result { + Err(light_account::LightSdkTypesError::InvalidInstructionData) } } // Pack trait is only available off-chain (client-side) #[cfg(not(target_os = "solana"))] - impl light_sdk::Pack for LightAccountVariant { + impl light_account::Pack for LightAccountVariant { type Packed = Self; - fn pack(&self, _remaining_accounts: &mut light_sdk::instruction::PackedAccounts) -> std::result::Result { + fn pack(&self, _remaining_accounts: &mut light_account::interface::instruction::PackedAccounts) -> std::result::Result { Ok(Self::Empty) } } - impl light_sdk::Unpack for LightAccountVariant { + impl light_account::Unpack for LightAccountVariant { type Unpacked = Self; - fn unpack(&self, _remaining_accounts: &[solana_account_info::AccountInfo]) -> std::result::Result { + fn unpack(&self, _remaining_accounts: &[AI]) -> std::result::Result { Ok(Self::Empty) } } @@ -181,14 +181,14 @@ pub(crate) fn generate_light_program_items( /// Wrapper for compressed account data (mint-only placeholder). #[derive(Clone, Debug, anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)] pub struct LightAccountData { - pub meta: light_sdk::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, + pub meta: light_account::account_meta::CompressedAccountMetaNoLamportsNoAddress, pub data: LightAccountVariant, } impl Default for LightAccountData { fn default() -> Self { Self { - meta: light_sdk::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress::default(), + meta: light_account::account_meta::CompressedAccountMetaNoLamportsNoAddress::default(), data: LightAccountVariant::default(), } } @@ -328,8 +328,9 @@ pub(crate) fn generate_light_program_items( } } impl light_account::IntoVariant for #seeds_struct_name { - fn into_variant(self, data: &[u8]) -> std::result::Result { + fn into_variant(self, data: &[u8]) -> std::result::Result { LightAccountVariant::#constructor_name(data, self) + .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData) } } }; @@ -366,7 +367,7 @@ pub(crate) fn generate_light_program_items( let error_codes = compress_builder.generate_error_codes()?; // Create DecompressBuilder to generate all decompress-related code - let decompress_builder = DecompressBuilder::new(pda_ctx_seeds.clone(), pda_seeds.clone()); + let decompress_builder = DecompressBuilder::new(pda_ctx_seeds.clone(), pda_seeds.clone(), has_token_seeds_early); // Note: DecompressBuilder validation is optional for now since pda_seeds may be empty for TokenOnly let decompress_accounts = decompress_builder.generate_accounts_struct()?; @@ -503,24 +504,38 @@ pub(crate) fn generate_light_program_items( } }; + let init_config_params_struct: syn::ItemStruct = syn::parse_quote! { + /// Configuration parameters for initializing compression config. + /// Field order matches SDK client's `InitializeCompressionConfigAnchorData`. + #[derive(AnchorSerialize, AnchorDeserialize, Clone)] + pub struct InitConfigParams { + pub write_top_up: u32, + pub rent_sponsor: Pubkey, + pub compression_authority: Pubkey, + pub rent_config: light_account::RentConfig, + pub address_space: Vec, + } + }; + let init_config_instruction: syn::ItemFn = syn::parse_quote! { #[inline(never)] pub fn initialize_compression_config<'info>( ctx: Context<'_, '_, '_, 'info, InitializeCompressionConfig<'info>>, - instruction_data: Vec, + params: InitConfigParams, ) -> Result<()> { - let remaining = [ - ctx.accounts.payer.to_account_info(), - ctx.accounts.config.to_account_info(), - ctx.accounts.program_data.to_account_info(), - ctx.accounts.authority.to_account_info(), - ctx.accounts.system_program.to_account_info(), - ]; - light_account::process_initialize_light_config_checked( - &remaining, - &instruction_data, - &crate::ID, - )?; + light_account::process_initialize_light_config( + &ctx.accounts.config, + &ctx.accounts.authority, + ¶ms.rent_sponsor.to_bytes(), + ¶ms.compression_authority.to_bytes(), + params.rent_config, + params.write_top_up, + params.address_space.iter().map(|p| p.to_bytes()).collect(), + 0, // config_bump + &ctx.accounts.payer, + &ctx.accounts.system_program, + &crate::ID.to_bytes(), + ).map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::from(e)))?; Ok(()) } }; @@ -538,8 +553,8 @@ pub(crate) fn generate_light_program_items( light_account::process_update_light_config( &remaining, &instruction_data, - &crate::ID, - )?; + &crate::ID.to_bytes(), + ).map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::from(e)))?; Ok(()) } }; @@ -582,6 +597,7 @@ pub(crate) fn generate_light_program_items( items.push(quote! { #compress_instruction }); items.push(quote! { #init_config_accounts }); items.push(quote! { #update_config_accounts }); + items.push(quote! { #init_config_params_struct }); items.push(quote! { #init_config_instruction }); items.push(quote! { #update_config_instruction }); diff --git a/sdk-libs/macros/src/light_pdas/program/parsing.rs b/sdk-libs/macros/src/light_pdas/program/parsing.rs index 075bf78667..c740be621e 100644 --- a/sdk-libs/macros/src/light_pdas/program/parsing.rs +++ b/sdk-libs/macros/src/light_pdas/program/parsing.rs @@ -684,9 +684,7 @@ pub fn wrap_function_with_light( // Phase 1: Pre-init (creates mints via CPI context write, registers compressed addresses) use light_account::{LightPreInit, LightFinalize}; let _ = #ctx_name.accounts.light_pre_init(#ctx_name.remaining_accounts, &#params_ident) - .map_err(|e: light_sdk_types::error::LightSdkTypesError| -> solana_program_error::ProgramError { - e.into() - })?; + .map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::from(e)))?; // Execute delegation - this handles its own logic including any finalize #fn_block @@ -700,9 +698,7 @@ pub fn wrap_function_with_light( // Phase 1: Pre-init (creates mints via CPI context write, registers compressed addresses) use light_account::{LightPreInit, LightFinalize}; let __has_pre_init = #ctx_name.accounts.light_pre_init(#ctx_name.remaining_accounts, &#params_ident) - .map_err(|e: light_sdk_types::error::LightSdkTypesError| -> solana_program_error::ProgramError { - e.into() - })?; + .map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::from(e)))?; // Execute the original handler body and capture result let __user_result: anchor_lang::Result<()> = #fn_block; @@ -711,9 +707,7 @@ pub fn wrap_function_with_light( // Phase 2: Finalize (creates token accounts/ATAs via CPI) #ctx_name.accounts.light_finalize(#ctx_name.remaining_accounts, &#params_ident, __has_pre_init) - .map_err(|e: light_sdk_types::error::LightSdkTypesError| -> solana_program_error::ProgramError { - e.into() - })?; + .map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::from(e)))?; Ok(()) } diff --git a/sdk-libs/macros/src/light_pdas/program/variant_enum.rs b/sdk-libs/macros/src/light_pdas/program/variant_enum.rs index 64083cefa1..5094643d40 100644 --- a/sdk-libs/macros/src/light_pdas/program/variant_enum.rs +++ b/sdk-libs/macros/src/light_pdas/program/variant_enum.rs @@ -104,7 +104,7 @@ impl<'a> LightVariantBuilder<'a> { /// Contains PACKED variant data that will be decompressed into PDA accounts. #[derive(Clone, Debug, borsh::BorshSerialize, borsh::BorshDeserialize)] pub struct LightAccountData { - pub meta: light_sdk::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, + pub meta: light_account::account_meta::CompressedAccountMetaNoLamportsNoAddress, pub data: PackedLightAccountVariant, } } @@ -146,7 +146,7 @@ impl<'a> LightVariantBuilder<'a> { .iter() .map(|f| { let idx = format_ident!("{}_idx", f); - quote! { #idx: remaining_accounts.insert_or_get(self.#f) } + quote! { #idx: remaining_accounts.insert_or_get(AM::pubkey_from_bytes(self.#f.to_bytes())) } }) .collect(); @@ -163,10 +163,12 @@ impl<'a> LightVariantBuilder<'a> { .map(|f| { let idx = format_ident!("{}_idx", f); quote! { - let #f = *remaining_accounts - .get(self.#idx as usize) - .ok_or(solana_program_error::ProgramError::InvalidAccountData)? - .key; + let #f = solana_pubkey::Pubkey::new_from_array( + remaining_accounts + .get(self.#idx as usize) + .ok_or(light_account::LightSdkTypesError::InvalidInstructionData)? + .key() + ); } }) .collect(); @@ -198,13 +200,13 @@ impl<'a> LightVariantBuilder<'a> { // Pack trait is only available off-chain (client-side) #[cfg(not(target_os = "solana"))] - impl light_sdk::Pack for #seeds_name { + impl light_account::Pack for #seeds_name { type Packed = #packed_seeds_name; fn pack( &self, - remaining_accounts: &mut light_sdk::instruction::PackedAccounts, - ) -> std::result::Result { + remaining_accounts: &mut light_account::interface::instruction::PackedAccounts, + ) -> std::result::Result { let __seeds: &[&[u8]] = &[#(#bump_seed_refs),*]; let (_, __bump) = solana_pubkey::Pubkey::find_program_address( __seeds, @@ -217,13 +219,13 @@ impl<'a> LightVariantBuilder<'a> { } } - impl light_sdk::Unpack for #packed_seeds_name { + impl light_account::Unpack for #packed_seeds_name { type Unpacked = #seeds_name; fn unpack( &self, - remaining_accounts: &[solana_account_info::AccountInfo], - ) -> std::result::Result { + remaining_accounts: &[AI], + ) -> std::result::Result { #(#unpack_resolve_stmts)* Ok(#seeds_name { #(#unpack_field_assigns,)* @@ -329,11 +331,11 @@ impl<'a> LightVariantBuilder<'a> { &[#(#owner_seed_refs),*], &crate::ID, ); - __owner + __owner.to_bytes() } } else { // No owner_seeds - return default (shouldn't happen for token accounts) - quote! { solana_pubkey::Pubkey::default() } + quote! { [0u8; 32] } }; quote! { @@ -342,7 +344,7 @@ impl<'a> LightVariantBuilder<'a> { { type Packed = #packed_seeds_name; - const PROGRAM_ID: Pubkey = crate::ID; + const PROGRAM_ID: [u8; 32] = crate::ID.to_bytes(); fn seed_vec(&self) -> Vec> { vec![#(#seed_vec_items),*] @@ -356,20 +358,28 @@ impl<'a> LightVariantBuilder<'a> { impl light_account::PackedTokenSeeds<#seed_count> for #packed_seeds_name { + type Unpacked = #seeds_name; + fn bump(&self) -> u8 { self.bump } + fn unpack_seeds( + &self, + accounts: &[AI], + ) -> std::result::Result { + >::unpack(self, accounts) + } - fn seed_refs_with_bump<'a>( + fn seed_refs_with_bump<'a, AI: light_account::AccountInfoTrait>( &'a self, - accounts: &'a [anchor_lang::prelude::AccountInfo], + accounts: &'a [AI], bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; #seed_count], solana_program_error::ProgramError> { + ) -> std::result::Result<[&'a [u8]; #seed_count], light_account::LightSdkTypesError> { Ok([#(#packed_seed_ref_items,)* bump_storage]) } - fn derive_owner(&self) -> solana_pubkey::Pubkey { + fn derive_owner(&self) -> [u8; 32] { #owner_derivation } } @@ -478,7 +488,7 @@ impl<'a> LightVariantBuilder<'a> { quote! { Self::#variant_name { seeds, data } => { let packed_data = #packed_variant_type { seeds: seeds.clone(), data: data.clone() }; - light_account::prepare_account_for_decompression::<#seed_count, #packed_variant_type>( + light_account::prepare_account_for_decompression::<#seed_count, #packed_variant_type, light_account::AccountInfo<'info>>( &packed_data, tree_info, output_queue_index, @@ -503,6 +513,7 @@ impl<'a> LightVariantBuilder<'a> { light_account::token::prepare_token_account_for_decompression::< #seed_count, light_account::token::TokenDataWithPackedSeeds<#packed_seeds_name>, + light_account::AccountInfo<'info>, >( packed_data, tree_info, @@ -519,10 +530,10 @@ impl<'a> LightVariantBuilder<'a> { impl<'info> light_account::DecompressVariant> for PackedLightAccountVariant { fn decompress( &self, - tree_info: &light_sdk::instruction::PackedStateTreeInfo, - pda_account: &anchor_lang::prelude::AccountInfo<'info>, + tree_info: &light_account::PackedStateTreeInfo, + pda_account: &light_account::AccountInfo<'info>, ctx: &mut light_account::DecompressCtx<'_, 'info>, - ) -> std::result::Result<(), solana_program_error::ProgramError> { + ) -> std::result::Result<(), light_account::LightSdkTypesError> { let output_queue_index = ctx.output_queue_index; match self { #(#pda_arms)* @@ -537,7 +548,7 @@ impl<'a> LightVariantBuilder<'a> { // PACK IMPL // ========================================================================= - /// Generate `impl light_sdk::Pack for LightAccountVariant`. + /// Generate `impl light_account::Pack for LightAccountVariant`. fn generate_pack_impl(&self) -> TokenStream { let pda_arms: Vec<_> = self .pda_ctx_seeds @@ -549,7 +560,7 @@ impl<'a> LightVariantBuilder<'a> { quote! { Self::#variant_name { seeds, data } => { let variant = #variant_struct_name { seeds: seeds.clone(), data: data.clone() }; - let packed = light_sdk::Pack::pack(&variant, accounts)?; + let packed = light_account::Pack::pack(&variant, accounts)?; Ok(PackedLightAccountVariant::#variant_name { seeds: packed.seeds, data: packed.data }) } } @@ -563,7 +574,7 @@ impl<'a> LightVariantBuilder<'a> { let variant_name = &spec.variant; quote! { Self::#variant_name(data) => { - let packed = light_sdk::Pack::pack(data, accounts)?; + let packed = light_account::Pack::pack(data, accounts)?; Ok(PackedLightAccountVariant::#variant_name(packed)) } } @@ -573,13 +584,13 @@ impl<'a> LightVariantBuilder<'a> { quote! { // Pack trait is only available off-chain (client-side) #[cfg(not(target_os = "solana"))] - impl light_sdk::Pack for LightAccountVariant { + impl light_account::Pack for LightAccountVariant { type Packed = PackedLightAccountVariant; fn pack( &self, - accounts: &mut light_sdk::instruction::PackedAccounts, - ) -> std::result::Result { + accounts: &mut light_account::interface::instruction::PackedAccounts, + ) -> std::result::Result { match self { #(#pda_arms)* #(#token_arms)* @@ -746,9 +757,8 @@ fn seed_to_packed_ref(seed: &SeedElement) -> TokenStream { return quote! { accounts .get(self.#idx_field as usize) - .ok_or(solana_program_error::ProgramError::InvalidAccountData)? - .key - .as_ref() + .ok_or(light_account::LightSdkTypesError::InvalidInstructionData)? + .key_ref() }; } quote! { { let __seed: &[u8] = (#expr).as_ref(); __seed } } diff --git a/sdk-libs/sdk-types/Cargo.toml b/sdk-libs/sdk-types/Cargo.toml index e0e3059e8b..1cbc0f985e 100644 --- a/sdk-libs/sdk-types/Cargo.toml +++ b/sdk-libs/sdk-types/Cargo.toml @@ -13,7 +13,7 @@ alloc = ["light-compressed-account/alloc"] keccak = ["light-hasher/keccak"] sha256 = ["light-hasher/sha256", "light-compressed-account/sha256"] token = ["dep:light-token-interface"] -anchor = ["anchor-lang", "light-compressed-account/anchor"] +anchor = ["anchor-lang", "light-compressed-account/anchor", "solana-program-error"] idl-build = ["anchor-lang/idl-build", "anchor"] v2 = [] cpi-context = [] @@ -29,6 +29,7 @@ light-compressible = { workspace = true } light-macros = { workspace = true } light-token-interface = { workspace = true, optional = true } solana-msg = { workspace = true, optional = true } +solana-program-error = { workspace = true, optional = true } # External dependencies borsh = { workspace = true } diff --git a/sdk-libs/sdk-types/src/constants.rs b/sdk-libs/sdk-types/src/constants.rs index 2a6f18f7cb..68d5c59c1d 100644 --- a/sdk-libs/sdk-types/src/constants.rs +++ b/sdk-libs/sdk-types/src/constants.rs @@ -33,3 +33,11 @@ pub const CPI_CONTEXT_ACCOUNT_2_DISCRIMINATOR: [u8; 8] = [34, 184, 183, 14, 100, pub const SOL_POOL_PDA: [u8; 32] = pubkey_array!("CHK57ywWSDncAoRu1F8QgwYJeXuAJyyBYT4LixLXvMZ1"); pub const ADDRESS_TREE_V2: [u8; 32] = pubkey_array!("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx"); + +/// Default compressible config PDA for the Light Token Program (V1). +pub const LIGHT_TOKEN_CONFIG: [u8; 32] = + pubkey_array!("ACXg8a7VaqecBWrSbdu73W4Pg9gsqXJ3EXAqkHyhvVXg"); + +/// Default rent sponsor PDA for the Light Token Program (V1). +pub const LIGHT_TOKEN_RENT_SPONSOR: [u8; 32] = + pubkey_array!("r18WwUxfG8kQ69bQPAB2jV6zGNKy3GosFGctjQoV4ti"); diff --git a/sdk-libs/sdk-types/src/error.rs b/sdk-libs/sdk-types/src/error.rs index 44a63107bb..fc2684abff 100644 --- a/sdk-libs/sdk-types/src/error.rs +++ b/sdk-libs/sdk-types/src/error.rs @@ -72,6 +72,36 @@ pub enum LightSdkTypesError { MissingRequiredSignature, } +#[cfg(feature = "anchor")] +impl From for anchor_lang::error::Error { + fn from(e: LightSdkTypesError) -> Self { + anchor_lang::error::Error::from(solana_program_error::ProgramError::Custom(u32::from(e))) + } +} + +#[cfg(feature = "anchor")] +impl From for solana_program_error::ProgramError { + fn from(e: LightSdkTypesError) -> Self { + solana_program_error::ProgramError::Custom(u32::from(e)) + } +} + +#[cfg(feature = "anchor")] +impl From for LightSdkTypesError { + fn from(e: solana_program_error::ProgramError) -> Self { + match e { + solana_program_error::ProgramError::InvalidAccountData => { + LightSdkTypesError::InvalidInstructionData + } + solana_program_error::ProgramError::BorshIoError(_) => LightSdkTypesError::Borsh, + solana_program_error::ProgramError::AccountBorrowFailed => { + LightSdkTypesError::ConstraintViolation + } + _ => LightSdkTypesError::ConstraintViolation, + } + } +} + impl From for u32 { fn from(e: LightSdkTypesError) -> Self { match e { diff --git a/sdk-libs/sdk-types/src/interface/account/compression_info.rs b/sdk-libs/sdk-types/src/interface/account/compression_info.rs index d3d16f48f5..d71d34a85c 100644 --- a/sdk-libs/sdk-types/src/interface/account/compression_info.rs +++ b/sdk-libs/sdk-types/src/interface/account/compression_info.rs @@ -329,6 +329,11 @@ impl Space for CompressionInfo { const INIT_SPACE: usize = core::mem::size_of::(); } +#[cfg(feature = "anchor")] +impl anchor_lang::Space for CompressionInfo { + const INIT_SPACE: usize = core::mem::size_of::(); +} + /// Space required for Option when Some (1 byte discriminator + INIT_SPACE). /// Use this constant in account space calculations. pub const OPTION_COMPRESSION_INFO_SPACE: usize = 1 + CompressionInfo::INIT_SPACE; diff --git a/sdk-libs/sdk-types/src/interface/account/mod.rs b/sdk-libs/sdk-types/src/interface/account/mod.rs index 127e0ce2c2..bf07f3e31f 100644 --- a/sdk-libs/sdk-types/src/interface/account/mod.rs +++ b/sdk-libs/sdk-types/src/interface/account/mod.rs @@ -7,5 +7,6 @@ pub mod compression_info; pub mod light_account; pub mod pack; pub mod pda_seeds; +pub mod size; #[cfg(feature = "token")] pub mod token_seeds; diff --git a/sdk-libs/sdk-types/src/interface/account/size.rs b/sdk-libs/sdk-types/src/interface/account/size.rs new file mode 100644 index 0000000000..9cf8be9e40 --- /dev/null +++ b/sdk-libs/sdk-types/src/interface/account/size.rs @@ -0,0 +1,8 @@ +//! Size trait for compressed accounts. + +use crate::error::LightSdkTypesError; + +/// Trait to get the serialized size of a compressed account. +pub trait Size { + fn size(&self) -> Result; +} diff --git a/sdk-libs/sdk-types/src/interface/accounts/init_compressed_account.rs b/sdk-libs/sdk-types/src/interface/accounts/init_compressed_account.rs index 288212b637..fd00b8e2b8 100644 --- a/sdk-libs/sdk-types/src/interface/accounts/init_compressed_account.rs +++ b/sdk-libs/sdk-types/src/interface/accounts/init_compressed_account.rs @@ -74,16 +74,16 @@ pub fn prepare_compressed_account_on_init( /// Reimburse the fee_payer for rent paid during PDA creation. /// /// During Anchor `init`, the fee_payer pays rent for PDA accounts. -/// This function transfers the total rent amount from the program-owned -/// rent_sponsor PDA back to the fee_payer. +/// This function transfers the total rent amount from the rent_sponsor +/// PDA back to the fee_payer via system program CPI. /// -/// Uses direct lamport manipulation (no CPI) since rent_sponsor is owned -/// by the calling program. +/// The rent_sponsor is a system-owned PDA, so CPI with invoke_signed +/// is required (direct lamport manipulation would fail). pub fn reimburse_rent( created_accounts: &[AI], fee_payer: &AI, rent_sponsor: &AI, - _program_id: &[u8; 32], + rent_sponsor_signer_seeds: &[&[u8]], ) -> Result<(), LightSdkTypesError> { let mut total_rent: u64 = 0; for account in created_accounts { @@ -94,7 +94,7 @@ pub fn reimburse_rent( if total_rent > 0 { rent_sponsor - .transfer_lamports(fee_payer, total_rent) + .transfer_lamports_cpi(fee_payer, total_rent, rent_sponsor_signer_seeds) .map_err(LightSdkTypesError::AccountError)?; } diff --git a/sdk-libs/sdk-types/src/interface/cpi/create_mints.rs b/sdk-libs/sdk-types/src/interface/cpi/create_mints.rs index ae5a68700d..b3965936af 100644 --- a/sdk-libs/sdk-types/src/interface/cpi/create_mints.rs +++ b/sdk-libs/sdk-types/src/interface/cpi/create_mints.rs @@ -207,10 +207,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { .data() .map_err(|_| LightSdkTypesError::Borsh)?; - // Account metas for create_mint + decompress (single mint path) - // Order matches MintActionMetaConfig::to_account_metas with compressible_mint + input_queue - let metas = self.build_mint_action_metas(0, true, true, false); - let account_infos = self.collect_mint_action_infos(0); + let (metas, account_infos) = self.build_mint_action(0, true, true, false); self.invoke_mint_action_raw(&ix_data, &account_infos, &metas, 0) } @@ -364,9 +361,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { .data() .map_err(|_| LightSdkTypesError::Borsh)?; - // Execute uses full account set with cpi_context + input_queue - let metas = self.build_mint_action_metas(last_idx, true, true, true); - let account_infos = self.collect_mint_action_infos(last_idx); + let (metas, account_infos) = self.build_mint_action(last_idx, true, true, true); self.invoke_mint_action_raw(&ix_data, &account_infos, &metas, last_idx) } @@ -400,9 +395,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { .data() .map_err(|_| LightSdkTypesError::Borsh)?; - // Decompress uses state_merkle_tree as tree_pubkey, no cpi_context, with input_queue - let metas = self.build_decompress_metas(index); - let account_infos = self.collect_decompress_infos(index); + let (metas, account_infos) = self.build_decompress_action(index); self.invoke_mint_action_raw(&ix_data, &account_infos, &metas, index) } @@ -440,7 +433,10 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { .map_err(|_| LightSdkTypesError::CpiFailed) } - /// Build account metas for a full mint action instruction. + /// Build matched account metas and infos for a full mint action CPI. + /// + /// Returns `(metas, infos)` in identical order so pinocchio's 1:1 + /// positional CPI requirement is satisfied without runtime reordering. /// /// Order matches `MintActionMetaConfig::to_account_metas`: /// light_system_program, [mint_signer], authority, [compressible_config], @@ -448,14 +444,15 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { /// account_compression_authority, account_compression_program, system_program, /// [cpi_context], output_queue, tree_pubkey, [input_queue] #[inline(never)] - fn build_mint_action_metas( + fn build_mint_action( &self, mint_index: usize, has_input_queue: bool, has_compressible: bool, has_cpi_context: bool, - ) -> Vec { - let mut metas = Vec::with_capacity(16); + ) -> (Vec, Vec) { + let mut metas = Vec::with_capacity(17); + let mut infos = Vec::with_capacity(17); // light_system_program metas.push(CpiMeta { @@ -463,6 +460,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: false, }); + infos.push(self.light_system_program.clone()); // mint_signer (always present for create_mint, must sign) metas.push(CpiMeta { @@ -470,6 +468,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: true, is_writable: false, }); + infos.push(self.mint_seed_accounts[mint_index].clone()); // authority (payer is authority) metas.push(CpiMeta { @@ -477,6 +476,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: true, is_writable: false, }); + infos.push(self.payer.clone()); if has_compressible { // compressible_config @@ -485,6 +485,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: false, }); + infos.push(self.compressible_config.clone()); // mint PDA (writable) metas.push(CpiMeta { @@ -492,6 +493,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: true, }); + infos.push(self.mints[mint_index].clone()); // rent_sponsor (writable) metas.push(CpiMeta { @@ -499,6 +501,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: true, }); + infos.push(self.rent_sponsor.clone()); } // fee_payer (signer, writable) @@ -507,6 +510,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: true, is_writable: true, }); + infos.push(self.payer.clone()); // cpi_authority_pda metas.push(CpiMeta { @@ -514,6 +518,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: false, }); + infos.push(self.cpi_authority_pda.clone()); // registered_program_pda metas.push(CpiMeta { @@ -521,6 +526,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: false, }); + infos.push(self.registered_program_pda.clone()); // account_compression_authority metas.push(CpiMeta { @@ -528,6 +534,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: false, }); + infos.push(self.account_compression_authority.clone()); // account_compression_program metas.push(CpiMeta { @@ -535,6 +542,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: false, }); + infos.push(self.account_compression_program.clone()); // system_program metas.push(CpiMeta { @@ -542,6 +550,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: false, }); + infos.push(self.system_program.clone()); // cpi_context (optional) if has_cpi_context { @@ -550,6 +559,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: true, }); + infos.push(self.cpi_context_account.clone()); } // output_queue (writable) @@ -558,6 +568,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: true, }); + infos.push(self.output_queue.clone()); // tree_pubkey (address_tree for create_mint) metas.push(CpiMeta { @@ -565,6 +576,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: true, }); + infos.push(self.address_tree.clone()); // input_queue (optional, same as output_queue for create_mint) if has_input_queue { @@ -573,22 +585,19 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: true, }); + infos.push(self.output_queue.clone()); } - metas + (metas, infos) } - /// Build account metas for a decompress instruction. + /// Build matched account metas and infos for a decompress CPI. /// /// For prove_by_index, tree_pubkey must be state_merkle_tree for discriminator validation. #[inline(never)] - fn build_decompress_metas(&self, mint_index: usize) -> Vec { - // Decompress uses MintActionMetaConfig::new() (existing mint path) with compressible_mint - // Order: light_system_program, authority, compressible_config, mint, rent_sponsor, - // fee_payer, cpi_authority_pda, registered_program_pda, - // account_compression_authority, account_compression_program, system_program, - // output_queue, tree_pubkey(=state_merkle_tree), input_queue(=output_queue) + fn build_decompress_action(&self, mint_index: usize) -> (Vec, Vec) { let mut metas = Vec::with_capacity(14); + let mut infos = Vec::with_capacity(14); // light_system_program metas.push(CpiMeta { @@ -596,10 +605,9 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: false, }); + infos.push(self.light_system_program.clone()); - // NOTE: No mint_signer for decompress (mint_signer_must_sign = false in MintActionMetaConfig::new) - // But we still have it as non-signing in the original code? Let me check... - // Actually, MintActionMetaConfig::new() sets mint_signer to None. So no mint_signer meta. + // No mint_signer for decompress // authority (payer is authority, signer) metas.push(CpiMeta { @@ -607,6 +615,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: true, is_writable: false, }); + infos.push(self.payer.clone()); // compressible_config metas.push(CpiMeta { @@ -614,6 +623,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: false, }); + infos.push(self.compressible_config.clone()); // mint PDA (writable) metas.push(CpiMeta { @@ -621,6 +631,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: true, }); + infos.push(self.mints[mint_index].clone()); // rent_sponsor (writable) metas.push(CpiMeta { @@ -628,6 +639,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: true, }); + infos.push(self.rent_sponsor.clone()); // fee_payer (signer, writable) metas.push(CpiMeta { @@ -635,6 +647,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: true, is_writable: true, }); + infos.push(self.payer.clone()); // cpi_authority_pda metas.push(CpiMeta { @@ -642,6 +655,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: false, }); + infos.push(self.cpi_authority_pda.clone()); // registered_program_pda metas.push(CpiMeta { @@ -649,6 +663,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: false, }); + infos.push(self.registered_program_pda.clone()); // account_compression_authority metas.push(CpiMeta { @@ -656,6 +671,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: false, }); + infos.push(self.account_compression_authority.clone()); // account_compression_program metas.push(CpiMeta { @@ -663,6 +679,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: false, }); + infos.push(self.account_compression_program.clone()); // system_program metas.push(CpiMeta { @@ -670,6 +687,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: false, }); + infos.push(self.system_program.clone()); // No cpi_context for decompress @@ -679,6 +697,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: true, }); + infos.push(self.output_queue.clone()); // tree_pubkey = state_merkle_tree for prove_by_index discriminator check metas.push(CpiMeta { @@ -686,6 +705,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: true, }); + infos.push(self.state_merkle_tree.clone()); // input_queue = output_queue metas.push(CpiMeta { @@ -693,51 +713,9 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { is_signer: false, is_writable: true, }); - - metas - } - - /// Collect all account infos needed for a full mint action CPI. - #[inline(never)] - fn collect_mint_action_infos(&self, _mint_index: usize) -> Vec { - let mut infos = vec![self.payer.clone()]; - - // System accounts - infos.push(self.light_system_program.clone()); - - // All mint seeds - for mint_seed in self.mint_seed_accounts { - infos.push(mint_seed.clone()); - } - - // More system accounts - infos.push(self.cpi_authority_pda.clone()); - infos.push(self.registered_program_pda.clone()); - infos.push(self.account_compression_authority.clone()); - infos.push(self.account_compression_program.clone()); - infos.push(self.system_program.clone()); - - // CPI context, queues, trees - infos.push(self.cpi_context_account.clone()); infos.push(self.output_queue.clone()); - infos.push(self.state_merkle_tree.clone()); - infos.push(self.address_tree.clone()); - infos.push(self.compressible_config.clone()); - infos.push(self.rent_sponsor.clone()); - // All mint PDAs - for mint in self.mints { - infos.push(mint.clone()); - } - - infos - } - - /// Collect account infos for a decompress CPI. - #[inline(never)] - fn collect_decompress_infos(&self, _mint_index: usize) -> Vec { - // Same set as mint_action_infos - runtime resolves from metas - self.collect_mint_action_infos(_mint_index) + (metas, infos) } } diff --git a/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs b/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs index e35de53153..74a6e93b69 100644 --- a/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs +++ b/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs @@ -332,14 +332,19 @@ where cpi_config, ); - // Token (ctoken) accounts layout: + // Token (ctoken) accounts layout (only required when token accounts are present): // [3] ctoken_rent_sponsor, [6] ctoken_compressible_config - let ctoken_rent_sponsor = remaining_accounts - .get(3) - .ok_or(LightSdkTypesError::NotEnoughAccountKeys)?; - let ctoken_compressible_config = remaining_accounts - .get(6) - .ok_or(LightSdkTypesError::NotEnoughAccountKeys)?; + let (ctoken_rent_sponsor, ctoken_compressible_config) = if has_token_accounts { + let rent_sponsor = remaining_accounts + .get(3) + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)?; + let config = remaining_accounts + .get(6) + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)?; + (Some(rent_sponsor), Some(config)) + } else { + (None, None) + }; // 6. Build context and dispatch (scoped to release borrows before CPI) let (compressed_account_infos, in_token_data, in_tlv, token_seeds) = { @@ -353,8 +358,8 @@ where current_slot, output_queue_index: params.output_queue_index, compressed_account_infos: Vec::new(), - ctoken_rent_sponsor: Some(ctoken_rent_sponsor), - ctoken_compressible_config: Some(ctoken_compressible_config), + ctoken_rent_sponsor, + ctoken_compressible_config, in_token_data: Vec::new(), in_tlv: None, token_seeds: Vec::new(), diff --git a/sdk-libs/sdk-types/src/interface/program/variant.rs b/sdk-libs/sdk-types/src/interface/program/variant.rs index 85712d08e9..a0f0aeaf8e 100644 --- a/sdk-libs/sdk-types/src/interface/program/variant.rs +++ b/sdk-libs/sdk-types/src/interface/program/variant.rs @@ -97,16 +97,18 @@ pub trait PackedLightAccountVariantTrait: ) -> Result<[&'a [u8]; SEED_COUNT], LightSdkTypesError>; /// Extract token data for compressed token CPI. - /// Only meaningful for token account variants; PDA variants should return an error. + /// Only meaningful for token account variants; PDA variants return an error. #[cfg(feature = "token")] fn into_in_token_data( &self, - tree_info: &crate::instruction::PackedStateTreeInfo, - output_queue_index: u8, + _tree_info: &crate::instruction::PackedStateTreeInfo, + _output_queue_index: u8, ) -> Result< light_token_interface::instructions::transfer2::MultiInputTokenDataWithContext, LightSdkTypesError, - >; + > { + Err(LightSdkTypesError::InvalidInstructionData) + } /// Extract TLV extension data for compressed token CPI. /// Only meaningful for token account variants; PDA variants return `None`. @@ -116,7 +118,9 @@ pub trait PackedLightAccountVariantTrait: ) -> Result< Option>, LightSdkTypesError, - >; + > { + Ok(None) + } /// Derive the owner pubkey from constant owner_seeds and program ID. /// Only meaningful for token account variants; PDA variants return default. diff --git a/sdk-tests/csdk-anchor-full-derived-test-sdk/Cargo.toml b/sdk-tests/csdk-anchor-full-derived-test-sdk/Cargo.toml index 25dcb7f470..0b9d952940 100644 --- a/sdk-tests/csdk-anchor-full-derived-test-sdk/Cargo.toml +++ b/sdk-tests/csdk-anchor-full-derived-test-sdk/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" csdk-anchor-full-derived-test = { path = "../csdk-anchor-full-derived-test", features = ["no-entrypoint"] } # SDK trait and types +light-account = { workspace = true, features = ["token", "anchor"] } light-client = { workspace = true, features = ["v2", "anchor"] } light-sdk = { workspace = true, features = ["anchor", "v2"] } light-token = { workspace = true, features = ["anchor"] } diff --git a/sdk-tests/csdk-anchor-full-derived-test-sdk/src/lib.rs b/sdk-tests/csdk-anchor-full-derived-test-sdk/src/lib.rs index 5cbe8e3521..80aaf78a8a 100644 --- a/sdk-tests/csdk-anchor-full-derived-test-sdk/src/lib.rs +++ b/sdk-tests/csdk-anchor-full-derived-test-sdk/src/lib.rs @@ -190,7 +190,8 @@ impl AmmSdk { account: &AccountInterface, is_vault_0: bool, ) -> Result<(), AmmSdkError> { - use light_sdk::interface::token::{Token, TokenDataWithSeeds}; + use light_account::Token; + use light_account::token::TokenDataWithSeeds; let pool_state = self .pool_state_pubkey diff --git a/sdk-tests/csdk-anchor-full-derived-test/Cargo.toml b/sdk-tests/csdk-anchor-full-derived-test/Cargo.toml index 8d58c69d90..7c33174432 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/Cargo.toml +++ b/sdk-tests/csdk-anchor-full-derived-test/Cargo.toml @@ -13,16 +13,17 @@ no-entrypoint = [] no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] -custom-heap = ["light-heap", "light-sdk/custom-heap"] +custom-heap = ["light-heap"] default = [] -idl-build = ["anchor-lang/idl-build", "light-sdk/idl-build", "light-anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build", "light-anchor-spl/idl-build"] test-sbf = [] [dependencies] light-heap = { workspace = true, optional = true } -light-account = { workspace = true } -light-sdk = { workspace = true, features = ["anchor", "v2", "anchor-discriminator", "cpi-context"] } -light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] } +light-account = { workspace = true, features = ["token", "anchor", "sha256"] } +light-token = { workspace = true, features = ["anchor"] } +light-token-interface = { workspace = true } +light-token-types = { workspace = true } light-hasher = { workspace = true, features = ["solana"] } bytemuck = { workspace = true, features = ["derive"] } solana-program = { workspace = true } @@ -34,16 +35,11 @@ light-macros = { workspace = true, features = ["solana"] } light-instruction-decoder = { workspace = true } light-instruction-decoder-derive = { workspace = true } solana-instruction = { workspace = true } -light-sdk-macros = { workspace = true } +light-sdk-macros = { workspace = true, features = ["anchor-discriminator"] } borsh = { workspace = true } light-compressed-account = { workspace = true, features = ["solana"] } anchor-lang = { workspace = true } light-anchor-spl = { workspace = true, features = ["metadata"] } -light-token-interface = { workspace = true, features = ["anchor"] } -light-token = { workspace = true, features = ["anchor"] } -light-compressed-token-sdk = { workspace = true, features = ["anchor"] } -light-token-types = { workspace = true, features = ["anchor"] } -light-compressible = { workspace = true, features = ["anchor"] } [dev-dependencies] csdk-anchor-full-derived-test-sdk = { path = "../csdk-anchor-full-derived-test-sdk" } @@ -51,6 +47,9 @@ light-token-client = { workspace = true } light-program-test = { workspace = true, features = ["devenv"] } light-client = { workspace = true, features = ["v2", "anchor"] } light-test-utils = { workspace = true } +light-compressible = { workspace = true } +light-compressed-token-sdk = { workspace = true } +light-sdk-types = { workspace = true } tokio = { workspace = true } solana-sdk = { workspace = true } solana-logger = { workspace = true } @@ -64,6 +63,7 @@ bincode = "1.3" sha2 = { workspace = true } rand = { workspace = true } light-batched-merkle-tree = { workspace = true } +light-sdk = { workspace = true } [lints.rust.unexpected_cfgs] level = "allow" diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs b/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs index a3317c75b8..eb84f6d801 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs @@ -10,11 +10,11 @@ use anchor_lang::prelude::*; use light_anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; -use light_token::instruction::{ - CreateTokenAccountCpi, CreateTokenAtaCpi, MintToCpi, LIGHT_TOKEN_CONFIG, - LIGHT_TOKEN_RENT_SPONSOR, +use light_account::{ + CreateAccountsProof, CreateTokenAccountCpi, CreateTokenAtaCpi, + LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR, }; +use light_token::instruction::MintToCpi; use super::states::*; @@ -173,60 +173,85 @@ pub fn process_initialize_pool<'info>( params: InitializeParams, ) -> Result<()> { // Create token_0_vault using CreateTokenAccountCpi (mark-only field) - CreateTokenAccountCpi { - payer: ctx.accounts.creator.to_account_info(), - account: ctx.accounts.token_0_vault.to_account_info(), - mint: ctx.accounts.token_0_mint.to_account_info(), - owner: ctx.accounts.authority.key(), + { + let payer_info = ctx.accounts.creator.to_account_info(); + let account_info = ctx.accounts.token_0_vault.to_account_info(); + let mint_info = ctx.accounts.token_0_mint.to_account_info(); + let config_info = ctx.accounts.light_token_config.to_account_info(); + let sponsor_info = ctx.accounts.light_token_rent_sponsor.to_account_info(); + let system_info = ctx.accounts.system_program.to_account_info(); + CreateTokenAccountCpi { + payer: &payer_info, + account: &account_info, + mint: &mint_info, + owner: ctx.accounts.authority.key().to_bytes(), + } + .rent_free( + &config_info, + &sponsor_info, + &system_info, + &crate::ID.to_bytes(), + ) + .invoke_signed(&[ + POOL_VAULT_SEED.as_bytes(), + ctx.accounts.pool_state.to_account_info().key.as_ref(), + ctx.accounts.token_0_mint.to_account_info().key.as_ref(), + &[ctx.bumps.token_0_vault], + ])?; } - .rent_free( - ctx.accounts.light_token_config.to_account_info(), - ctx.accounts.light_token_rent_sponsor.to_account_info(), - ctx.accounts.system_program.to_account_info(), - &crate::ID, - ) - .invoke_signed(&[ - POOL_VAULT_SEED.as_bytes(), - ctx.accounts.pool_state.to_account_info().key.as_ref(), - ctx.accounts.token_0_mint.to_account_info().key.as_ref(), - &[ctx.bumps.token_0_vault], - ])?; // Create token_1_vault using CreateTokenAccountCpi (mark-only field) - CreateTokenAccountCpi { - payer: ctx.accounts.creator.to_account_info(), - account: ctx.accounts.token_1_vault.to_account_info(), - mint: ctx.accounts.token_1_mint.to_account_info(), - owner: ctx.accounts.authority.key(), + { + let payer_info = ctx.accounts.creator.to_account_info(); + let account_info = ctx.accounts.token_1_vault.to_account_info(); + let mint_info = ctx.accounts.token_1_mint.to_account_info(); + let config_info = ctx.accounts.light_token_config.to_account_info(); + let sponsor_info = ctx.accounts.light_token_rent_sponsor.to_account_info(); + let system_info = ctx.accounts.system_program.to_account_info(); + CreateTokenAccountCpi { + payer: &payer_info, + account: &account_info, + mint: &mint_info, + owner: ctx.accounts.authority.key().to_bytes(), + } + .rent_free( + &config_info, + &sponsor_info, + &system_info, + &crate::ID.to_bytes(), + ) + .invoke_signed(&[ + POOL_VAULT_SEED.as_bytes(), + ctx.accounts.pool_state.to_account_info().key.as_ref(), + ctx.accounts.token_1_mint.to_account_info().key.as_ref(), + &[ctx.bumps.token_1_vault], + ])?; } - .rent_free( - ctx.accounts.light_token_config.to_account_info(), - ctx.accounts.light_token_rent_sponsor.to_account_info(), - ctx.accounts.system_program.to_account_info(), - &crate::ID, - ) - .invoke_signed(&[ - POOL_VAULT_SEED.as_bytes(), - ctx.accounts.pool_state.to_account_info().key.as_ref(), - ctx.accounts.token_1_mint.to_account_info().key.as_ref(), - &[ctx.bumps.token_1_vault], - ])?; // Create creator LP token ATA using CreateTokenAtaCpi.rent_free() - CreateTokenAtaCpi { - payer: ctx.accounts.creator.to_account_info(), - owner: ctx.accounts.creator.to_account_info(), - mint: ctx.accounts.lp_mint.to_account_info(), - ata: ctx.accounts.creator_lp_token.to_account_info(), - bump: params.creator_lp_token_bump, + { + let payer_info = ctx.accounts.creator.to_account_info(); + let owner_info = ctx.accounts.creator.to_account_info(); + let mint_info = ctx.accounts.lp_mint.to_account_info(); + let ata_info = ctx.accounts.creator_lp_token.to_account_info(); + let config_info = ctx.accounts.light_token_config.to_account_info(); + let sponsor_info = ctx.accounts.light_token_rent_sponsor.to_account_info(); + let system_info = ctx.accounts.system_program.to_account_info(); + CreateTokenAtaCpi { + payer: &payer_info, + owner: &owner_info, + mint: &mint_info, + ata: &ata_info, + bump: params.creator_lp_token_bump, + } + .idempotent() + .rent_free( + &config_info, + &sponsor_info, + &system_info, + ) + .invoke()?; } - .idempotent() - .rent_free( - ctx.accounts.light_token_config.to_account_info(), - ctx.accounts.light_token_rent_sponsor.to_account_info(), - ctx.accounts.system_program.to_account_info(), - ) - .invoke()?; // Mint LP tokens using MintToCpi let lp_amount = 1000u64; // Placeholder amount diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/states.rs b/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/states.rs index 8a27af823d..6ce6838f84 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/states.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/states.rs @@ -1,7 +1,7 @@ //! AMM state structs adapted from cp-swap-reference. use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; pub const POOL_SEED: &str = "pool"; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instruction_accounts.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instruction_accounts.rs index 3389bf9387..1014544f22 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instruction_accounts.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instruction_accounts.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::*; @@ -310,7 +310,7 @@ pub struct CreateMintWithMetadataParams { pub name: Vec, pub symbol: Vec, pub uri: Vec, - pub additional_metadata: Option>, + pub additional_metadata: Option>, } /// Test instruction with #[light_account(init)] with metadata fields. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata.rs index 9c04cdbf0a..dd6ed1199d 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata.rs @@ -8,9 +8,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; -use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; -use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_account::{CreateAccountsProof, LIGHT_TOKEN_PROGRAM_ID, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; #[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] pub struct D10SingleAtaParams { diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata_markonly.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata_markonly.rs index f825d97a7e..1ca724b643 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata_markonly.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata_markonly.rs @@ -7,8 +7,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; -use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_account::{LIGHT_TOKEN_PROGRAM_ID, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; #[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] pub struct D10SingleAtaMarkonlyParams { diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_vault.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_vault.rs index a717895eac..4b71109c87 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_vault.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_vault.rs @@ -8,8 +8,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; -use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_account::{CreateAccountsProof, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; /// Seed for the vault authority PDA pub const D10_SINGLE_VAULT_AUTH_SEED: &[u8] = b"d10_single_vault_auth"; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/mixed_zc_borsh.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/mixed_zc_borsh.rs index 6c490fe86b..51922c4e7b 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/mixed_zc_borsh.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/mixed_zc_borsh.rs @@ -5,7 +5,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::{ d11_zero_copy::ZcBasicRecord, d1_field_types::single_pubkey::SinglePubkeyRecord, diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/multiple_zc.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/multiple_zc.rs index f1d5bd1d0e..586e26e49f 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/multiple_zc.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/multiple_zc.rs @@ -5,7 +5,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d11_zero_copy::ZcBasicRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ata.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ata.rs index fa6f603bfe..9e22415465 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ata.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ata.rs @@ -5,8 +5,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; -use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_account::{CreateAccountsProof, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use crate::state::d11_zero_copy::ZcBasicRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ctx_seeds.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ctx_seeds.rs index 08ee3ad453..20fe9d4c50 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ctx_seeds.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ctx_seeds.rs @@ -5,7 +5,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d11_zero_copy::ZcWithSeedsRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_mint_to.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_mint_to.rs index 6853f45cb1..85b536ae6e 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_mint_to.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_mint_to.rs @@ -5,8 +5,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; -use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_account::{CreateAccountsProof, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use crate::state::d11_zero_copy::ZcBasicRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_params_seeds.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_params_seeds.rs index e07136bdc8..cdcee60df6 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_params_seeds.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_params_seeds.rs @@ -5,7 +5,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d11_zero_copy::ZcWithParamsRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_vault.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_vault.rs index acf50b231e..37a3b22c72 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_vault.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_vault.rs @@ -5,8 +5,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; -use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_account::{CreateAccountsProof, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use crate::state::d11_zero_copy::ZcBasicRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/all.rs index 6f8508abd1..84d6530df2 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/all.rs @@ -5,8 +5,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; -use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_account::{CreateAccountsProof, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/light_token.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/light_token.rs index bc01742f8a..88b4314fff 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/light_token.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/light_token.rs @@ -6,7 +6,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_account::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; pub const D5_VAULT_AUTH_SEED: &[u8] = b"d5_vault_auth"; pub const D5_VAULT_SEED: &[u8] = b"d5_vault"; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/rentfree_bare.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/rentfree_bare.rs index d2d2daf01a..4da3ef21b8 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/rentfree_bare.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/rentfree_bare.rs @@ -8,7 +8,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/account.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/account.rs index 37281ac3e6..468a5976a6 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/account.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/account.rs @@ -4,7 +4,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/all.rs index b4933a47a2..fdbc67b853 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/all.rs @@ -4,7 +4,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::{ d1_field_types::single_pubkey::SinglePubkeyRecord, diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/boxed.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/boxed.rs index b5dcbc7ae8..0076b8bbe6 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/boxed.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/boxed.rs @@ -5,7 +5,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/all.rs index d4370e7ad2..2d52dc7307 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/all.rs @@ -4,8 +4,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; -use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_account::{CreateAccountsProof, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/creator.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/creator.rs index 5a3e83f1fd..00d3a5192c 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/creator.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/creator.rs @@ -4,7 +4,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/light_token_config.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/light_token_config.rs index 8595a13aff..6bb64cd533 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/light_token_config.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/light_token_config.rs @@ -6,7 +6,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_account::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; pub const D7_LIGHT_TOKEN_AUTH_SEED: &[u8] = b"d7_light_token_auth"; pub const D7_LIGHT_TOKEN_VAULT_SEED: &[u8] = b"d7_light_token_vault"; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/payer.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/payer.rs index aa333c04c5..4fa551e375 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/payer.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/payer.rs @@ -4,7 +4,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/all.rs index 8143ffab42..ebf411c7ba 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/all.rs @@ -4,7 +4,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::{ d1_field_types::single_pubkey::SinglePubkeyRecord, diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/multi_rentfree.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/multi_rentfree.rs index c655d9a7fc..5f55ef1526 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/multi_rentfree.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/multi_rentfree.rs @@ -4,7 +4,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/pda_only.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/pda_only.rs index e86cac31e4..86f37f477c 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/pda_only.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/pda_only.rs @@ -5,7 +5,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/all.rs index 146f0dfdd0..0620929e47 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/all.rs @@ -10,7 +10,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/array_bumps.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/array_bumps.rs index a69704f9b4..967fb82320 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/array_bumps.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/array_bumps.rs @@ -6,7 +6,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/complex_mixed.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/complex_mixed.rs index ce00141206..fbd6a5dba1 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/complex_mixed.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/complex_mixed.rs @@ -7,7 +7,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/const_patterns.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/const_patterns.rs index cd41ba93f1..e130c6d19f 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/const_patterns.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/const_patterns.rs @@ -8,7 +8,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/constant.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/constant.rs index df34deb50f..1c36e41c03 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/constant.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/constant.rs @@ -4,7 +4,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/ctx_account.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/ctx_account.rs index 9cc9e49575..70d03fa28e 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/ctx_account.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/ctx_account.rs @@ -4,7 +4,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/edge_cases.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/edge_cases.rs index 017eb8a49c..0e0c3a3b0c 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/edge_cases.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/edge_cases.rs @@ -10,7 +10,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/external_paths.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/external_paths.rs index 8df2d13438..fa4e0dce19 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/external_paths.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/external_paths.rs @@ -7,7 +7,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; @@ -21,7 +21,7 @@ pub struct D9ExternalSdkTypesParams { pub owner: Pubkey, } -/// Tests external crate path: light_sdk_types::constants::CPI_AUTHORITY_PDA_SEED +/// Tests external crate path: light_account::constants::CPI_AUTHORITY_PDA_SEED #[derive(Accounts, LightAccounts)] #[instruction(params: D9ExternalSdkTypesParams)] pub struct D9ExternalSdkTypes<'info> { @@ -39,7 +39,7 @@ pub struct D9ExternalSdkTypes<'info> { init, payer = fee_payer, space = 8 + SinglePubkeyRecord::INIT_SPACE, - seeds = [b"d9_ext_sdk", light_sdk_types::constants::CPI_AUTHORITY_PDA_SEED, params.owner.as_ref()], + seeds = [b"d9_ext_sdk", light_account::constants::CPI_AUTHORITY_PDA_SEED, params.owner.as_ref()], bump, )] #[light_account(init)] @@ -114,7 +114,7 @@ pub struct D9ExternalMixed<'info> { payer = fee_payer, space = 8 + SinglePubkeyRecord::INIT_SPACE, seeds = [ - light_sdk_types::constants::CPI_AUTHORITY_PDA_SEED, + light_account::constants::CPI_AUTHORITY_PDA_SEED, light_token_types::constants::POOL_SEED, params.owner.as_ref() ], @@ -157,7 +157,7 @@ pub struct D9ExternalWithLocal<'info> { init, payer = fee_payer, space = 8 + SinglePubkeyRecord::INIT_SPACE, - seeds = [D9_EXTERNAL_LOCAL, light_sdk_types::constants::RENT_SPONSOR_SEED, params.owner.as_ref()], + seeds = [D9_EXTERNAL_LOCAL, light_account::constants::RENT_SPONSOR_SEED, params.owner.as_ref()], bump, )] #[light_account(init)] @@ -208,7 +208,7 @@ pub struct D9ExternalBump<'info> { // ============================================================================ /// Re-export from external crate for path testing -pub use light_sdk_types::constants::CPI_AUTHORITY_PDA_SEED as REEXPORTED_SEED; +pub use light_account::CPI_AUTHORITY_PDA_SEED as REEXPORTED_SEED; #[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] pub struct D9ExternalReexportParams { diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/function_call.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/function_call.rs index 3109bca47c..ee52261e3c 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/function_call.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/function_call.rs @@ -4,7 +4,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/instruction_data.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/instruction_data.rs index c35951a909..f56608ab5b 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/instruction_data.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/instruction_data.rs @@ -10,7 +10,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/literal.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/literal.rs index 8977c479ec..c534979870 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/literal.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/literal.rs @@ -4,7 +4,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/method_chains.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/method_chains.rs index f62971d635..bcdb3e83bf 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/method_chains.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/method_chains.rs @@ -9,7 +9,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/mixed.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/mixed.rs index 946f821d85..160f4fb066 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/mixed.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/mixed.rs @@ -4,7 +4,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/nested_seeds.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/nested_seeds.rs index b802e430f7..00de22e5e4 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/nested_seeds.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/nested_seeds.rs @@ -7,7 +7,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param.rs index 5919222fa6..b9786a23b9 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param.rs @@ -4,7 +4,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param_bytes.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param_bytes.rs index 259131d7f2..c4f45eaff9 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param_bytes.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param_bytes.rs @@ -4,7 +4,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/qualified_paths.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/qualified_paths.rs index 9971c330d0..1cbe0bc5e2 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/qualified_paths.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/qualified_paths.rs @@ -8,7 +8,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/lib.rs b/sdk-tests/csdk-anchor-full-derived-test/src/lib.rs index f4014cb8a1..d7619c4e06 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/lib.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/lib.rs @@ -3,9 +3,9 @@ use anchor_lang::prelude::*; use light_instruction_decoder_derive::instruction_decoder; -use light_sdk::{derive_light_cpi_signer, derive_light_rent_sponsor_pda}; +use light_account::{derive_light_cpi_signer, derive_light_rent_sponsor_pda}; use light_sdk_macros::light_program; -use light_sdk_types::CpiSigner; +use light_account::CpiSigner; pub mod amm_test; pub mod d5_markers; @@ -314,9 +314,8 @@ pub mod csdk_anchor_full_derived_test { ctx: Context<'_, '_, '_, 'info, CreatePdasAndMintAuto<'info>>, params: FullAutoWithMintParams, ) -> Result<()> { - use light_token::instruction::{ - CreateTokenAccountCpi, CreateTokenAtaCpi, MintToCpi as CTokenMintToCpi, - }; + use light_account::{CreateTokenAccountCpi, CreateTokenAtaCpi}; + use light_token::instruction::MintToCpi as CTokenMintToCpi; let user_record = &mut ctx.accounts.user_record; user_record.owner = params.owner; @@ -333,38 +332,55 @@ pub mod csdk_anchor_full_derived_test { game_session.score = 0; // vault is mark-only - create manually via CreateTokenAccountCpi - CreateTokenAccountCpi { - payer: ctx.accounts.fee_payer.to_account_info(), - account: ctx.accounts.vault.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), - owner: ctx.accounts.vault_authority.key(), + { + let payer_info = ctx.accounts.fee_payer.to_account_info(); + let account_info = ctx.accounts.vault.to_account_info(); + let mint_info = ctx.accounts.mint.to_account_info(); + let config_info = ctx.accounts.light_token_config.to_account_info(); + let sponsor_info = ctx.accounts.light_token_rent_sponsor.to_account_info(); + let system_info = ctx.accounts.system_program.to_account_info(); + CreateTokenAccountCpi { + payer: &payer_info, + account: &account_info, + mint: &mint_info, + owner: ctx.accounts.vault_authority.key().to_bytes(), + } + .rent_free( + &config_info, + &sponsor_info, + &system_info, + &crate::ID.to_bytes(), + ) + .invoke_signed(&[ + VAULT_SEED, + ctx.accounts.mint.to_account_info().key.as_ref(), + &[ctx.bumps.vault], + ])?; } - .rent_free( - ctx.accounts.light_token_config.to_account_info(), - ctx.accounts.light_token_rent_sponsor.to_account_info(), - ctx.accounts.system_program.to_account_info(), - &crate::ID, - ) - .invoke_signed(&[ - VAULT_SEED, - ctx.accounts.mint.to_account_info().key.as_ref(), - &[ctx.bumps.vault], - ])?; - CreateTokenAtaCpi { - payer: ctx.accounts.fee_payer.to_account_info(), - owner: ctx.accounts.fee_payer.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), - ata: ctx.accounts.user_ata.to_account_info(), - bump: params.user_ata_bump, + { + let payer_info = ctx.accounts.fee_payer.to_account_info(); + let owner_info = ctx.accounts.fee_payer.to_account_info(); + let mint_info = ctx.accounts.mint.to_account_info(); + let ata_info = ctx.accounts.user_ata.to_account_info(); + let config_info = ctx.accounts.light_token_config.to_account_info(); + let sponsor_info = ctx.accounts.light_token_rent_sponsor.to_account_info(); + let system_info = ctx.accounts.system_program.to_account_info(); + CreateTokenAtaCpi { + payer: &payer_info, + owner: &owner_info, + mint: &mint_info, + ata: &ata_info, + bump: params.user_ata_bump, + } + .idempotent() + .rent_free( + &config_info, + &sponsor_info, + &system_info, + ) + .invoke()?; } - .idempotent() - .rent_free( - ctx.accounts.light_token_config.to_account_info(), - ctx.accounts.light_token_rent_sponsor.to_account_info(), - ctx.accounts.system_program.to_account_info(), - ) - .invoke()?; if params.vault_mint_amount > 0 { CTokenMintToCpi { @@ -614,21 +630,22 @@ pub mod csdk_anchor_full_derived_test { ctx: Context<'_, '_, '_, 'info, D7LightTokenConfig<'info>>, params: D7LightTokenConfigParams, ) -> Result<()> { - use light_token::instruction::CreateTokenAccountCpi; + use light_account::CreateTokenAccountCpi; // Token vault is mark-only - create manually via CreateTokenAccountCpi + let __payer = ctx.accounts.fee_payer.to_account_info(); + let __account = ctx.accounts.d7_light_token_vault.to_account_info(); + let __mint = ctx.accounts.mint.to_account_info(); + let __config = ctx.accounts.light_token_config.to_account_info(); + let __sponsor = ctx.accounts.light_token_rent_sponsor.to_account_info(); + let __sys = ctx.accounts.system_program.to_account_info(); CreateTokenAccountCpi { - payer: ctx.accounts.fee_payer.to_account_info(), - account: ctx.accounts.d7_light_token_vault.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), - owner: ctx.accounts.d7_light_token_authority.key(), + payer: &__payer, + account: &__account, + mint: &__mint, + owner: ctx.accounts.d7_light_token_authority.key().to_bytes(), } - .rent_free( - ctx.accounts.light_token_config.to_account_info(), - ctx.accounts.light_token_rent_sponsor.to_account_info(), - ctx.accounts.system_program.to_account_info(), - &crate::ID, - ) + .rent_free(&__config, &__sponsor, &__sys, &crate::ID.to_bytes()) .invoke_signed(&[ D7_LIGHT_TOKEN_VAULT_SEED, ctx.accounts.mint.to_account_info().key.as_ref(), @@ -643,24 +660,25 @@ pub mod csdk_anchor_full_derived_test { ctx: Context<'_, '_, '_, 'info, D7AllNames<'info>>, params: D7AllNamesParams, ) -> Result<()> { - use light_token::instruction::CreateTokenAccountCpi; + use light_account::CreateTokenAccountCpi; // Set up the PDA record ctx.accounts.d7_all_record.owner = params.owner; // Token vault is mark-only - create manually via CreateTokenAccountCpi + let __payer = ctx.accounts.payer.to_account_info(); + let __account = ctx.accounts.d7_all_vault.to_account_info(); + let __mint = ctx.accounts.mint.to_account_info(); + let __config = ctx.accounts.light_token_config.to_account_info(); + let __sponsor = ctx.accounts.light_token_rent_sponsor.to_account_info(); + let __sys = ctx.accounts.system_program.to_account_info(); CreateTokenAccountCpi { - payer: ctx.accounts.payer.to_account_info(), - account: ctx.accounts.d7_all_vault.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), - owner: ctx.accounts.d7_all_authority.key(), + payer: &__payer, + account: &__account, + mint: &__mint, + owner: ctx.accounts.d7_all_authority.key().to_bytes(), } - .rent_free( - ctx.accounts.light_token_config.to_account_info(), - ctx.accounts.light_token_rent_sponsor.to_account_info(), - ctx.accounts.system_program.to_account_info(), - &crate::ID, - ) + .rent_free(&__config, &__sponsor, &__sys, &crate::ID.to_bytes()) .invoke_signed(&[ D7_ALL_VAULT_SEED, ctx.accounts.mint.to_account_info().key.as_ref(), @@ -1336,21 +1354,22 @@ pub mod csdk_anchor_full_derived_test { ctx: Context<'_, '_, '_, 'info, D5LightToken<'info>>, params: D5LightTokenParams, ) -> Result<()> { - use light_token::instruction::CreateTokenAccountCpi; + use light_account::CreateTokenAccountCpi; // Token vault is mark-only - create manually via CreateTokenAccountCpi + let __payer = ctx.accounts.fee_payer.to_account_info(); + let __account = ctx.accounts.d5_token_vault.to_account_info(); + let __mint = ctx.accounts.mint.to_account_info(); + let __config = ctx.accounts.light_token_config.to_account_info(); + let __sponsor = ctx.accounts.light_token_rent_sponsor.to_account_info(); + let __sys = ctx.accounts.system_program.to_account_info(); CreateTokenAccountCpi { - payer: ctx.accounts.fee_payer.to_account_info(), - account: ctx.accounts.d5_token_vault.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), - owner: ctx.accounts.vault_authority.key(), + payer: &__payer, + account: &__account, + mint: &__mint, + owner: ctx.accounts.vault_authority.key().to_bytes(), } - .rent_free( - ctx.accounts.light_token_config.to_account_info(), - ctx.accounts.light_token_rent_sponsor.to_account_info(), - ctx.accounts.system_program.to_account_info(), - &crate::ID, - ) + .rent_free(&__config, &__sponsor, &__sys, &crate::ID.to_bytes()) .invoke_signed(&[ D5_VAULT_SEED, ctx.accounts.mint.to_account_info().key.as_ref(), @@ -1365,24 +1384,25 @@ pub mod csdk_anchor_full_derived_test { ctx: Context<'_, '_, '_, 'info, D5AllMarkers<'info>>, params: D5AllMarkersParams, ) -> Result<()> { - use light_token::instruction::CreateTokenAccountCpi; + use light_account::CreateTokenAccountCpi; // Set up the PDA record ctx.accounts.d5_all_record.owner = params.owner; // Token vault is mark-only - create manually via CreateTokenAccountCpi + let __payer = ctx.accounts.fee_payer.to_account_info(); + let __account = ctx.accounts.d5_all_vault.to_account_info(); + let __mint = ctx.accounts.mint.to_account_info(); + let __config = ctx.accounts.light_token_config.to_account_info(); + let __sponsor = ctx.accounts.light_token_rent_sponsor.to_account_info(); + let __sys = ctx.accounts.system_program.to_account_info(); CreateTokenAccountCpi { - payer: ctx.accounts.fee_payer.to_account_info(), - account: ctx.accounts.d5_all_vault.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), - owner: ctx.accounts.d5_all_authority.key(), + payer: &__payer, + account: &__account, + mint: &__mint, + owner: ctx.accounts.d5_all_authority.key().to_bytes(), } - .rent_free( - ctx.accounts.light_token_config.to_account_info(), - ctx.accounts.light_token_rent_sponsor.to_account_info(), - ctx.accounts.system_program.to_account_info(), - &crate::ID, - ) + .rent_free(&__config, &__sponsor, &__sys, &crate::ID.to_bytes()) .invoke_signed(&[ D5_ALL_VAULT_SEED, ctx.accounts.mint.to_account_info().key.as_ref(), @@ -1433,22 +1453,25 @@ pub mod csdk_anchor_full_derived_test { ctx: Context<'_, '_, '_, 'info, D10SingleAtaMarkonly<'info>>, params: D10SingleAtaMarkonlyParams, ) -> Result<()> { - use light_token::instruction::CreateTokenAtaCpi; + use light_account::CreateTokenAtaCpi; // Mark-only: LightPreInit/LightFinalize are no-ops, we create the ATA manually + let __payer = ctx.accounts.fee_payer.to_account_info(); + let __owner = ctx.accounts.d10_markonly_ata_owner.to_account_info(); + let __mint = ctx.accounts.d10_markonly_ata_mint.to_account_info(); + let __ata = ctx.accounts.d10_markonly_ata.to_account_info(); + let __config = ctx.accounts.light_token_config.to_account_info(); + let __sponsor = ctx.accounts.light_token_rent_sponsor.to_account_info(); + let __sys = ctx.accounts.system_program.to_account_info(); CreateTokenAtaCpi { - payer: ctx.accounts.fee_payer.to_account_info(), - owner: ctx.accounts.d10_markonly_ata_owner.to_account_info(), - mint: ctx.accounts.d10_markonly_ata_mint.to_account_info(), - ata: ctx.accounts.d10_markonly_ata.to_account_info(), + payer: &__payer, + owner: &__owner, + mint: &__mint, + ata: &__ata, bump: params.ata_bump, } .idempotent() - .rent_free( - ctx.accounts.light_token_config.to_account_info(), - ctx.accounts.light_token_rent_sponsor.to_account_info(), - ctx.accounts.system_program.to_account_info(), - ) + .rent_free(&__config, &__sponsor, &__sys) .invoke()?; Ok(()) diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d11_zero_copy/basic.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d11_zero_copy/basic.rs index 7d8d23a339..e7319506c2 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d11_zero_copy/basic.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d11_zero_copy/basic.rs @@ -3,7 +3,7 @@ //! Tests `#[light_account(init, zero_copy)]` with a simple Pod account. use anchor_lang::prelude::*; -use light_sdk::{interface::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// Basic zero-copy record for simple tests (no Pubkey seeds on the struct itself). diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d11_zero_copy/with_params.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d11_zero_copy/with_params.rs index 5e2c4ce710..5b4bfa26be 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d11_zero_copy/with_params.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d11_zero_copy/with_params.rs @@ -3,7 +3,7 @@ //! Tests `#[light_account(init, zero_copy)]` with params-only seeds (not on struct). use anchor_lang::prelude::*; -use light_sdk::{interface::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// Zero-copy record for testing params-only seeds (category_id in seeds but not on struct). diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d11_zero_copy/with_seeds.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d11_zero_copy/with_seeds.rs index 878241dac9..80afdda7b1 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d11_zero_copy/with_seeds.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d11_zero_copy/with_seeds.rs @@ -3,7 +3,7 @@ //! Tests `#[light_account(init, zero_copy)]` with context account seeds. use anchor_lang::prelude::*; -use light_sdk::{interface::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// Zero-copy record with authority field for testing ctx.accounts.* seed packing. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/all.rs index 0a31b380c6..1e0c6f087a 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/all.rs @@ -8,7 +8,7 @@ //! - Option (-> unchanged) use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// Comprehensive struct with all field type variations. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/arrays.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/arrays.rs index 3c081d9ff1..20b3691ab0 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/arrays.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/arrays.rs @@ -3,7 +3,7 @@ //! Exercises the code path for array field handling. use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// A struct with array fields. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/multi_pubkey.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/multi_pubkey.rs index d28b4f5ca1..f917e551c3 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/multi_pubkey.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/multi_pubkey.rs @@ -4,7 +4,7 @@ //! generating a PackedX struct with multiple u8 index fields. use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// A struct with multiple Pubkey fields. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/no_pubkey.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/no_pubkey.rs index 3edfd57fee..40f843c827 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/no_pubkey.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/no_pubkey.rs @@ -4,7 +4,7 @@ //! resulting in Pack/Unpack being a type alias (identity). use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// A struct with only primitive fields - no Pubkey. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/non_copy.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/non_copy.rs index c0b566dfae..ff3760867d 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/non_copy.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/non_copy.rs @@ -4,7 +4,7 @@ //! which triggers the `.clone()` path in pack/unpack generation. use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// A struct with a String field (non-Copy type). diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/option_primitive.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/option_primitive.rs index c083be5e23..b044e048a6 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/option_primitive.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/option_primitive.rs @@ -4,7 +4,7 @@ //! These remain unchanged in the packed struct (not converted to u8 index). use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// A struct with Option fields. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/option_pubkey.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/option_pubkey.rs index e9411786b6..15bfb7874c 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/option_pubkey.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/option_pubkey.rs @@ -4,7 +4,7 @@ //! which generates Option in the packed struct. use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// A struct with Option fields. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/single_pubkey.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/single_pubkey.rs index c3cbb29ae2..06b7c48cd2 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/single_pubkey.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d1_field_types/single_pubkey.rs @@ -4,7 +4,7 @@ //! generating a PackedX struct with a single u8 index field. use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// A struct with exactly one Pubkey field. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/absent.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/absent.rs index cfebaa127e..e3dc09d977 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/absent.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/absent.rs @@ -4,7 +4,7 @@ //! All fields use self.field directly for compression. use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// A struct without any compress_as attribute. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/all.rs index d9ce8eefaf..4eaeaa9875 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/all.rs @@ -6,7 +6,7 @@ //! - Fields without override (use self.field) use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// Comprehensive struct with all compress_as variations. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/multiple.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/multiple.rs index aff8279b6a..58002e3125 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/multiple.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/multiple.rs @@ -3,7 +3,7 @@ //! Exercises the code path where multiple fields have compress_as overrides. use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// A struct with multiple compress_as overrides. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/option_none.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/option_none.rs index 6d4f937f0f..7ac0f4ae45 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/option_none.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/option_none.rs @@ -3,7 +3,7 @@ //! Exercises the code path where Option fields are compressed as None. use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// A struct with compress_as None for Option fields. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/single.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/single.rs index 1ab1515848..4f3d90a922 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/single.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d2_compress_as/single.rs @@ -3,7 +3,7 @@ //! Exercises the code path where one field has a compress_as override. use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// A struct with single compress_as override. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d4_composition/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d4_composition/all.rs index a278d75280..13dc86b3d1 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d4_composition/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d4_composition/all.rs @@ -3,7 +3,7 @@ //! Exercises a large struct with all field type variants from D1. use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// Comprehensive large struct with all field types. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d4_composition/info_last.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d4_composition/info_last.rs index ec85183613..cb5b1f0bf1 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d4_composition/info_last.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d4_composition/info_last.rs @@ -3,7 +3,7 @@ //! Exercises struct validation with compression_info in non-first position. use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// Struct with compression_info as last field. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d4_composition/large.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d4_composition/large.rs index 0b3308e699..9cac58b403 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d4_composition/large.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d4_composition/large.rs @@ -3,7 +3,7 @@ //! Exercises the hash mode selection for large structs (SHA256 path). use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// Large struct with 12+ fields for SHA256 hash mode. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/d4_composition/minimal.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/d4_composition/minimal.rs index 3531b50652..25e9589f9c 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/d4_composition/minimal.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/d4_composition/minimal.rs @@ -3,7 +3,7 @@ //! Exercises the smallest valid struct with compression_info and one field. use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// Smallest valid struct: compression_info + one field. diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/mod.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/mod.rs index fe2bd3eb4b..b54a02b530 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/mod.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/mod.rs @@ -1,11 +1,9 @@ //! State structs for the test program and test cases organized by dimension. use anchor_lang::prelude::*; -use light_sdk::{ - compressible::CompressionInfo, instruction::PackedAddressTreeInfo, LightDiscriminator, -}; +use light_account::{CompressionInfo, PackedAddressTreeInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; -use light_token::ValidityProof; +use light_account::ValidityProof; use light_token_interface::instructions::mint_action::MintWithContext; // Test modules diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_observation_state_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_observation_state_test.rs index 60862565c0..b827a40eab 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_observation_state_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_observation_state_test.rs @@ -279,7 +279,7 @@ fn test_pack_converts_pool_id_to_index() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 1); - assert_eq!(stored_pubkeys[0], pool_id); + assert_eq!(stored_pubkeys[0], pool_id.to_bytes()); } #[test] @@ -495,7 +495,7 @@ fn test_pack_stores_pool_id_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 1, "should have 1 pubkey stored"); assert_eq!( - stored_pubkeys[packed.pool_id as usize], pool_id, + stored_pubkeys[packed.pool_id as usize], pool_id.to_bytes(), "stored pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_pool_state_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_pool_state_test.rs index f8a47832fa..6e36544632 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_pool_state_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_pool_state_test.rs @@ -311,7 +311,7 @@ fn test_pack_converts_all_10_pubkeys_to_indices() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 10); for (i, pubkey) in pubkeys.iter().enumerate() { - assert_eq!(stored_pubkeys[i], *pubkey); + assert_eq!(stored_pubkeys[i], pubkey.to_bytes()); } } @@ -522,7 +522,7 @@ fn test_pack_stores_all_pubkeys_in_packed_accounts() { // Verify each pubkey is stored at its index for (i, expected_pubkey) in pubkeys.iter().enumerate() { assert_eq!( - stored_pubkeys[i], *expected_pubkey, + stored_pubkeys[i], expected_pubkey.to_bytes(), "pubkey at index {} should match", i ); diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_game_session_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_game_session_test.rs index 0d4d4d781e..0aad176f0f 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_game_session_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_game_session_test.rs @@ -442,11 +442,11 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.player as usize], player1, + stored_pubkeys[packed1.player as usize], player1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.player as usize], player2, + stored_pubkeys[packed2.player as usize], player2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_placeholder_record_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_placeholder_record_test.rs index f73166ec02..ef5535db3d 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_placeholder_record_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_placeholder_record_test.rs @@ -355,11 +355,11 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1, + stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2, + stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_user_record_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_user_record_test.rs index 507be688ae..e7fe5fe9c1 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_user_record_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_user_record_test.rs @@ -354,11 +354,11 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1, + stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2, + stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_all_field_types_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_all_field_types_test.rs index f794e963b1..b67ebed5cb 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_all_field_types_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_all_field_types_test.rs @@ -471,9 +471,9 @@ fn test_pack_converts_all_pubkey_types() { // Only direct Pubkey fields are stored in packed_accounts (not Option) let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 3); - assert_eq!(stored_pubkeys[0], owner); - assert_eq!(stored_pubkeys[1], delegate); - assert_eq!(stored_pubkeys[2], authority); + assert_eq!(stored_pubkeys[0], owner.to_bytes()); + assert_eq!(stored_pubkeys[1], delegate.to_bytes()); + assert_eq!(stored_pubkeys[2], authority.to_bytes()); } #[test] diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_multi_pubkey_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_multi_pubkey_test.rs index 8b3bcf7e89..24a5fe9e91 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_multi_pubkey_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_multi_pubkey_test.rs @@ -273,9 +273,9 @@ fn test_pack_converts_all_pubkeys_to_indices() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 3); - assert_eq!(stored_pubkeys[0], owner); - assert_eq!(stored_pubkeys[1], delegate); - assert_eq!(stored_pubkeys[2], authority); + assert_eq!(stored_pubkeys[0], owner.to_bytes()); + assert_eq!(stored_pubkeys[1], delegate.to_bytes()); + assert_eq!(stored_pubkeys[2], authority.to_bytes()); } #[test] @@ -385,27 +385,27 @@ fn test_pack_stores_all_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 6, "should have 6 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1, + stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), "first record owner should match" ); assert_eq!( - stored_pubkeys[packed1.delegate as usize], delegate1, + stored_pubkeys[packed1.delegate as usize], delegate1.to_bytes(), "first record delegate should match" ); assert_eq!( - stored_pubkeys[packed1.authority as usize], authority1, + stored_pubkeys[packed1.authority as usize], authority1.to_bytes(), "first record authority should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2, + stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), "second record owner should match" ); assert_eq!( - stored_pubkeys[packed2.delegate as usize], delegate2, + stored_pubkeys[packed2.delegate as usize], delegate2.to_bytes(), "second record delegate should match" ); assert_eq!( - stored_pubkeys[packed2.authority as usize], authority2, + stored_pubkeys[packed2.authority as usize], authority2.to_bytes(), "second record authority should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_option_pubkey_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_option_pubkey_test.rs index d0922e2ea9..9855bf4cc9 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_option_pubkey_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_option_pubkey_test.rs @@ -268,7 +268,7 @@ fn test_pack_converts_pubkey_to_index() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 1); - assert_eq!(stored_pubkeys[0], owner); + assert_eq!(stored_pubkeys[0], owner.to_bytes()); } #[test] @@ -298,7 +298,7 @@ fn test_pack_preserves_option_pubkey_as_option_pubkey() { // Only the direct Pubkey field (owner) is stored in packed_accounts let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 1); - assert_eq!(stored_pubkeys[0], owner); + assert_eq!(stored_pubkeys[0], owner.to_bytes()); } #[test] @@ -331,7 +331,7 @@ fn test_pack_option_pubkey_none_stays_none() { // Only the direct Pubkey field (owner) is stored in packed_accounts let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 1); - assert_eq!(stored_pubkeys[0], owner); + assert_eq!(stored_pubkeys[0], owner.to_bytes()); } #[test] @@ -361,7 +361,7 @@ fn test_pack_all_option_pubkeys_some() { // Only the direct Pubkey field (owner) is stored in packed_accounts let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 1); - assert_eq!(stored_pubkeys[0], owner); + assert_eq!(stored_pubkeys[0], owner.to_bytes()); } #[test] @@ -386,7 +386,7 @@ fn test_pack_all_option_pubkeys_none() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 1); - assert_eq!(stored_pubkeys[0], owner); + assert_eq!(stored_pubkeys[0], owner.to_bytes()); } #[test] diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_single_pubkey_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_single_pubkey_test.rs index f8666e8c22..402a84eede 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_single_pubkey_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_single_pubkey_test.rs @@ -260,11 +260,11 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1, + stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2, + stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_all_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_all_compress_as_test.rs index 35202490dd..46f182fe83 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_all_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_all_compress_as_test.rs @@ -461,11 +461,11 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1, + stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2, + stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_multiple_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_multiple_compress_as_test.rs index 23287b6e13..57a1e367a0 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_multiple_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_multiple_compress_as_test.rs @@ -385,11 +385,11 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1, + stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2, + stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_no_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_no_compress_as_test.rs index 4bb6b08cc9..27c338bc5b 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_no_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_no_compress_as_test.rs @@ -305,11 +305,11 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1, + stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2, + stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_option_none_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_option_none_compress_as_test.rs index 3a96760a18..449f993f19 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_option_none_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_option_none_compress_as_test.rs @@ -390,11 +390,11 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1, + stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2, + stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_single_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_single_compress_as_test.rs index f757852740..92915b2d6f 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_single_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_single_compress_as_test.rs @@ -307,11 +307,11 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1, + stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2, + stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/shared.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/shared.rs index 7249c896dc..31f3eca146 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/shared.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/shared.rs @@ -6,8 +6,8 @@ use std::borrow::Cow; use light_hasher::{DataHasher, Sha256}; +use light_account::Size; use light_sdk::{ - account::Size, compressible::{CompressAs, CompressedInitSpace, CompressionState, HasCompressionInfo}, LightDiscriminator, }; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs index ba5cc3128c..e678a12709 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs @@ -356,7 +356,7 @@ async fn test_create_pdas_and_mint_auto() { use light_client::interface::{ create_load_instructions, AccountInterface, AccountSpec, ColdContext, PdaSpec, }; - use light_sdk::interface::token::TokenDataWithSeeds; + use light_account::TokenDataWithSeeds; // Fetch unified interfaces (hot/cold transparent) let user_interface = rpc diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/compressibility_check_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/compressibility_check_test.rs index a300d1cc63..bc4cb266c6 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/compressibility_check_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/compressibility_check_test.rs @@ -294,7 +294,8 @@ async fn test_batch_abort_non_compressible() { .value; // Build program account metas (fee_payer, config, rent_sponsor, compression_authority) - let light_config_pda = light_sdk::interface::LightConfig::derive_pda(&program_id, 0).0; + let (config_bytes, _) = light_account::LightConfig::derive_pda_bytes::>(&program_id.to_bytes(), 0); + let light_config_pda = Pubkey::from(config_bytes); let program_metas = vec![ AccountMeta::new(payer.pubkey(), true), AccountMeta::new_readonly(light_config_pda, false), diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/failing_tests.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/failing_tests.rs index 7c281cdc01..adb9112769 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/failing_tests.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/failing_tests.rs @@ -1,14 +1,14 @@ //! Security validation tests for decompression. //! //! Tests verify that invalid decompression attempts are correctly rejected. -//! Error codes reference: -//! - 2: InvalidInstructionData -//! - 3: InvalidAccountData -//! - 8: MissingRequiredSignature -//! - 11: NotEnoughAccountKeys -//! - 14: InvalidSeeds -//! - 17001: LightSdkTypesError::ConstraintViolation -//! - 17004: LightSdkTypesError::InvalidRentSponsor +//! Error codes reference (LightSdkTypesError custom program error codes): +//! - 14017: FewerAccountsThanSystemAccounts +//! - 14035: ConstraintViolation +//! - 14038: InvalidRentSponsor +//! - 14043: InvalidInstructionData +//! - 14044: InvalidSeeds +//! - 14046: NotEnoughAccountKeys +//! - 14047: MissingRequiredSignature mod shared; @@ -167,7 +167,7 @@ impl FailingTestContext { // PDA DECOMPRESSION TESTS // ============================================================================= -/// Test: Wrong rent sponsor should fail with InvalidAccountData (3). +/// Test: Wrong rent sponsor should fail with InvalidRentSponsor (14038). /// Validates rent sponsor PDA derivation check in decompress.rs:160-169. #[tokio::test] async fn test_pda_wrong_rent_sponsor() { @@ -209,8 +209,8 @@ async fn test_pda_wrong_rent_sponsor() { .create_and_send_transaction(&decompress_instructions, &ctx.payer.pubkey(), &[&ctx.payer]) .await; - // Should fail with InvalidRentSponsor (17004) - assert_rpc_error(result, 0, 17004).unwrap(); + // Should fail with InvalidRentSponsor (14038) + assert_rpc_error(result, 0, 14038).unwrap(); } /// Test: Double decompression should be a noop (idempotent). @@ -296,7 +296,7 @@ async fn test_pda_double_decompress_is_noop() { ); } -/// Test: Wrong config PDA should fail with InvalidAccountData (3). +/// Test: Wrong config PDA should fail with ConstraintViolation (14035). /// Validates config check in config.rs:144-153. #[tokio::test] async fn test_pda_wrong_config() { @@ -337,15 +337,15 @@ async fn test_pda_wrong_config() { .await; // Should fail - the config validation will reject the wrong address - // This could be InvalidAccountData (3) since it fails to deserialize - assert_rpc_error(result, 0, 3).unwrap(); + // ConstraintViolation (14035) since deserialization/validation of the config account fails + assert_rpc_error(result, 0, 14035).unwrap(); } // ============================================================================= // COMMON PARAMETER TESTS // ============================================================================= -/// Test: system_accounts_offset out of bounds should fail with InvalidInstructionData (2). +/// Test: system_accounts_offset out of bounds should fail with InvalidInstructionData (14043). /// Validates bounds check in decompress.rs:175-177. #[tokio::test] async fn test_system_accounts_offset_out_of_bounds() { @@ -385,11 +385,11 @@ async fn test_system_accounts_offset_out_of_bounds() { .create_and_send_transaction(&decompress_instructions, &ctx.payer.pubkey(), &[&ctx.payer]) .await; - // Should fail with InvalidInstructionData (2) - assert_rpc_error(result, 0, 2).unwrap(); + // Should fail with InvalidInstructionData (14043) + assert_rpc_error(result, 0, 14043).unwrap(); } -/// Test: token_accounts_offset invalid should fail with NotEnoughAccountKeys (11). +/// Test: token_accounts_offset invalid should fail with InvalidInstructionData (14043). /// Validates bounds check in decompress.rs:178-181. #[tokio::test] async fn test_token_accounts_offset_invalid() { @@ -430,8 +430,8 @@ async fn test_token_accounts_offset_invalid() { .create_and_send_transaction(&decompress_instructions, &ctx.payer.pubkey(), &[&ctx.payer]) .await; - // Should fail with NotEnoughAccountKeys (11) - assert_rpc_error(result, 0, 11).unwrap(); + // Should fail with InvalidInstructionData (14043) - token_accounts_offset exceeds accounts count + assert_rpc_error(result, 0, 14043).unwrap(); } // ============================================================================= @@ -439,7 +439,7 @@ async fn test_token_accounts_offset_invalid() { // ============================================================================= /// Test: Removing required accounts should fail. -/// Error code 11 is NotEnoughAccountKeys. +/// Error code 14017 is FewerAccountsThanSystemAccounts. #[tokio::test] async fn test_missing_system_accounts() { let mut ctx = FailingTestContext::new().await; @@ -477,12 +477,12 @@ async fn test_missing_system_accounts() { .create_and_send_transaction(&decompress_instructions, &ctx.payer.pubkey(), &[&ctx.payer]) .await; - // Should fail with NotEnoughAccountKeys (11) - gracefully handled missing accounts - assert_rpc_error(result, 0, 11).unwrap(); + // Should fail with FewerAccountsThanSystemAccounts (14017) - not enough remaining accounts + assert_rpc_error(result, 0, 14017).unwrap(); } /// Test: Wrong PDA account (mismatch between seeds and account) should fail. -/// When we try to create a PDA at a non-PDA address, we get PrivilegeEscalation (19). +/// When seeds don't match the account, we get InvalidSeeds (14044). #[tokio::test] async fn test_pda_account_mismatch() { let mut ctx = FailingTestContext::new().await; @@ -520,9 +520,9 @@ async fn test_pda_account_mismatch() { .create_and_send_transaction(&decompress_instructions, &ctx.payer.pubkey(), &[&ctx.payer]) .await; - // Should fail with InvalidSeeds (14) - PDA derivation validation catches + // Should fail with InvalidSeeds (14044) - PDA derivation validation catches // the mismatch before attempting CPI - assert_rpc_error(result, 0, 14).unwrap(); + assert_rpc_error(result, 0, 14044).unwrap(); } /// Test: Fee payer not a signer should fail with MissingRequiredSignature (8). diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/integration_tests.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/integration_tests.rs index 0363047387..4f534e669c 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/integration_tests.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/integration_tests.rs @@ -1693,7 +1693,7 @@ async fn test_d5_light_token() { // Full lifecycle: compression + decompression use csdk_anchor_full_derived_test::csdk_anchor_full_derived_test::D5TokenVaultSeeds; - use light_sdk::interface::token::TokenDataWithSeeds; + use light_account::TokenDataWithSeeds; // Warp time to trigger compression ctx.rpc @@ -1797,7 +1797,7 @@ async fn test_d5_all_markers() { use csdk_anchor_full_derived_test::csdk_anchor_full_derived_test::{ D5AllRecordSeeds, D5AllVaultSeeds, }; - use light_sdk::interface::token::TokenDataWithSeeds; + use light_account::TokenDataWithSeeds; // Warp time to trigger compression of BOTH accounts ctx.rpc @@ -1894,7 +1894,7 @@ async fn test_d7_light_token_config() { // Full lifecycle: compression + decompression use csdk_anchor_full_derived_test::csdk_anchor_full_derived_test::D7LightTokenVaultSeeds; - use light_sdk::interface::token::TokenDataWithSeeds; + use light_account::TokenDataWithSeeds; // Warp time to trigger compression ctx.rpc @@ -1999,7 +1999,7 @@ async fn test_d7_all_names() { use csdk_anchor_full_derived_test::csdk_anchor_full_derived_test::{ D7AllRecordSeeds, D7AllVaultSeeds, }; - use light_sdk::interface::token::TokenDataWithSeeds; + use light_account::TokenDataWithSeeds; // Warp time to trigger compression of BOTH accounts ctx.rpc diff --git a/sdk-tests/manual-test-pinocchio/Cargo.toml b/sdk-tests/manual-test-pinocchio/Cargo.toml new file mode 100644 index 0000000000..3be3ef0be8 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "manual-test-pinocchio" +version = "0.1.0" +description = "Manual LightAccount implementation test with pinocchio (no Anchor)" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "manual_test_pinocchio" + +[features] +no-entrypoint = [] +default = [] +test-sbf = [] + +[dependencies] +light-account-pinocchio = { workspace = true, features = ["token", "std"] } +light-macros = { workspace = true, features = ["solana"] } +light-sdk-macros = { workspace = true, features = ["anchor-discriminator"] } +borsh = { workspace = true } +bytemuck = { workspace = true } +light-compressed-account = { workspace = true, features = ["solana"] } +light-compressible = { workspace = true, features = ["pinocchio"] } +light-hasher = { workspace = true, features = ["solana"] } +light-token-types = { workspace = true } +light-token-interface = { workspace = true } +pinocchio = { workspace = true } +pinocchio-pubkey = { workspace = true } +pinocchio-system = { workspace = true } +solana-pubkey = { workspace = true } +solana-instruction = { workspace = true } +solana-msg = { workspace = true } +solana-program-error = { workspace = true } + +[dev-dependencies] +light-program-test = { workspace = true, features = ["devenv"] } +light-client = { workspace = true, features = ["v2", "anchor"] } +light-test-utils = { workspace = true } +light-token = { workspace = true, features = ["anchor"] } +light-token-client = { workspace = true } +tokio = { workspace = true } +solana-sdk = { workspace = true } +solana-keypair = { workspace = true } +solana-signer = { workspace = true } + +[lints.rust.unexpected_cfgs] +level = "allow" +check-cfg = [ + 'cfg(target_os, values("solana"))', + 'cfg(feature, values("frozen-abi", "no-entrypoint"))', +] diff --git a/sdk-tests/manual-test-pinocchio/keypair.json b/sdk-tests/manual-test-pinocchio/keypair.json new file mode 100644 index 0000000000..48a400e300 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/keypair.json @@ -0,0 +1 @@ +[253,60,210,72,31,164,77,145,249,190,2,16,13,127,213,114,240,143,220,32,31,142,54,182,47,172,30,213,57,31,242,178,95,240,102,74,184,144,135,198,93,167,147,174,97,28,27,254,68,20,246,94,255,0,152,161,63,174,49,14,37,212,50,166] \ No newline at end of file diff --git a/sdk-tests/manual-test-pinocchio/src/account_loader/accounts.rs b/sdk-tests/manual-test-pinocchio/src/account_loader/accounts.rs new file mode 100644 index 0000000000..a13d766c1a --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/account_loader/accounts.rs @@ -0,0 +1,93 @@ +//! Accounts module for zero-copy account instruction (pinocchio version). + +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::CreateAccountsProof; +use pinocchio::{ + account_info::AccountInfo, + instruction::{Seed, Signer}, + program_error::ProgramError, + sysvars::Sysvar, +}; + +use super::state::ZeroCopyRecord; + +/// Parameters for creating a zero-copy compressible PDA. +#[derive(BorshSerialize, BorshDeserialize, Clone)] +pub struct CreateZeroCopyParams { + pub create_accounts_proof: CreateAccountsProof, + pub owner: [u8; 32], + pub value: u64, + pub name: String, +} + +/// Accounts struct for creating a zero-copy compressible PDA. +pub struct CreateZeroCopy<'a> { + pub fee_payer: &'a AccountInfo, + pub compression_config: &'a AccountInfo, + pub record: &'a AccountInfo, + pub system_program: &'a AccountInfo, +} + +impl<'a> CreateZeroCopy<'a> { + pub const FIXED_LEN: usize = 4; + + pub fn parse( + accounts: &'a [AccountInfo], + params: &CreateZeroCopyParams, + ) -> Result { + let fee_payer = &accounts[0]; + let compression_config = &accounts[1]; + let record = &accounts[2]; + let system_program = &accounts[3]; + + if !fee_payer.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + // Verify and create the PDA account via system program CPI + let space = 8 + ZeroCopyRecord::INIT_SPACE; + let name_bytes = params.name.as_bytes(); + let seeds: &[&[u8]] = &[b"zero_copy", ¶ms.owner, name_bytes]; + let (expected_pda, bump) = pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if record.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + + let rent = pinocchio::sysvars::rent::Rent::get() + .map_err(|_| ProgramError::UnsupportedSysvar)?; + let lamports = rent.minimum_balance(space); + + let bump_bytes = [bump]; + let seed_array = [ + Seed::from(b"zero_copy" as &[u8]), + Seed::from(params.owner.as_ref()), + Seed::from(name_bytes), + Seed::from(bump_bytes.as_ref()), + ]; + let signer = Signer::from(&seed_array); + pinocchio_system::instructions::CreateAccount { + from: fee_payer, + to: record, + lamports, + space: space as u64, + owner: &crate::ID, + } + .invoke_signed(&[signer])?; + + // Write LIGHT_DISCRIMINATOR to first 8 bytes + { + use light_account_pinocchio::LightDiscriminator; + let mut data = record + .try_borrow_mut_data() + .map_err(|_| ProgramError::AccountBorrowFailed)?; + data[..8].copy_from_slice(&ZeroCopyRecord::LIGHT_DISCRIMINATOR); + } + + Ok(Self { + fee_payer, + compression_config, + record, + system_program, + }) + } +} diff --git a/sdk-tests/manual-test-pinocchio/src/account_loader/derived_accounts.rs b/sdk-tests/manual-test-pinocchio/src/account_loader/derived_accounts.rs new file mode 100644 index 0000000000..c0ab631be8 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/account_loader/derived_accounts.rs @@ -0,0 +1,376 @@ +//! Variant structs and trait implementations for ZeroCopyRecord. +//! +//! This follows the same pattern as MinimalRecord's derived_accounts.rs, +//! adapted for the AccountLoader (zero-copy) access pattern. + +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::{ + light_account_checks::{self, packed_accounts::ProgramPackedAccounts}, + prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, + CpiContextWriteAccounts, InvokeLightSystemProgram, LightAccount, LightAccountVariantTrait, + LightFinalize, LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, + PackedLightAccountVariantTrait, +}; +use light_compressed_account::instruction_data::{ + cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, +}; +use pinocchio::account_info::AccountInfo; + +use super::{ + accounts::{CreateZeroCopy, CreateZeroCopyParams}, + derived_state::PackedZeroCopyRecord, + state::ZeroCopyRecord, +}; + +// ============================================================================ +// Compile-time Size Validation (800-byte limit for compressed accounts) +// ============================================================================ + +const _: () = { + const COMPRESSED_SIZE: usize = 8 + core::mem::size_of::(); + assert!( + COMPRESSED_SIZE <= 800, + "Compressed account 'ZeroCopyRecord' exceeds 800-byte compressible account size limit" + ); +}; + +// ============================================================================ +// Manual LightPreInit Implementation +// ============================================================================ + +impl LightPreInit for CreateZeroCopy<'_> { + fn light_pre_init( + &mut self, + remaining_accounts: &[AccountInfo], + params: &CreateZeroCopyParams, + ) -> std::result::Result { + let inner = || -> std::result::Result { + use light_account_pinocchio::{LightAccount, LightConfig}; + use pinocchio::sysvars::{clock::Clock, Sysvar}; + + // 1. Build CPI accounts (slice remaining_accounts at system_accounts_offset) + let system_accounts_offset = + params.create_accounts_proof.system_accounts_offset as usize; + if remaining_accounts.len() < system_accounts_offset { + return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); + } + let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); + let cpi_accounts = CpiAccounts::new_with_config( + self.fee_payer, + &remaining_accounts[system_accounts_offset..], + config, + ); + + // 2. Get address tree pubkey from packed tree info + let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; + let address_tree_pubkey = address_tree_info + .get_tree_pubkey(&cpi_accounts) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + let output_tree_index = params.create_accounts_proof.output_state_tree_index; + let current_account_index: u8 = 0; + // Is true if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. + const WITH_CPI_CONTEXT: bool = false; + // Is first if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. + let cpi_context = if WITH_CPI_CONTEXT { + CompressedCpiContext::first() + } else { + CompressedCpiContext::default() + }; + const NUM_LIGHT_PDAS: usize = 1; + let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); + let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); + + // 3. Load config and get current slot + let light_config = + LightConfig::load_checked(self.compression_config, &crate::ID) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + let current_slot = Clock::get() + .map_err(|_| LightSdkTypesError::InvalidInstructionData)? + .slot; + + // 4. Prepare compressed account using helper function + // Get the record's key from AccountInfo + let record_key = *self.record.key(); + prepare_compressed_account_on_init( + &record_key, + &address_tree_pubkey, + address_tree_info, + output_tree_index, + current_account_index, + &crate::ID, + &mut new_address_params, + &mut account_infos, + )?; + + // 5. Set compression_info on the zero-copy record + // For pinocchio, access data directly via try_borrow_mut_data() + bytemuck + { + let mut account_data = self + .record + .try_borrow_mut_data() + .map_err(|_| LightSdkTypesError::Borsh)?; + let record_bytes = + &mut account_data[8..8 + core::mem::size_of::()]; + let record: &mut ZeroCopyRecord = bytemuck::from_bytes_mut(record_bytes); + record.set_decompressed(&light_config, current_slot); + } + + // 6. Build instruction data manually (no builder pattern) + let instruction_data = InstructionDataInvokeCpiWithAccountInfo { + mode: 1, // V2 mode + bump: crate::LIGHT_CPI_SIGNER.bump, + invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), + compress_or_decompress_lamports: 0, + is_compress: false, + with_cpi_context: WITH_CPI_CONTEXT, + with_transaction_hash: false, + cpi_context, + proof: params.create_accounts_proof.proof.0, + new_address_params, + account_infos, + read_only_addresses: vec![], + read_only_accounts: vec![], + }; + if !WITH_CPI_CONTEXT { + // 7. Invoke Light System Program CPI + instruction_data.invoke(cpi_accounts)?; + } else { + // For flows that combine light mints with light PDAs, write to CPI context first. + let cpi_context_accounts = CpiContextWriteAccounts { + fee_payer: cpi_accounts.fee_payer(), + authority: cpi_accounts.authority()?, + cpi_context: cpi_accounts.cpi_context()?, + cpi_signer: crate::LIGHT_CPI_SIGNER, + }; + instruction_data + .invoke_write_to_cpi_context_first(cpi_context_accounts)?; + } + + Ok(false) // No mints, so no CPI context write + }; + inner() + } +} + +// ============================================================================ +// Manual LightFinalize Implementation (no-op for PDA-only flow) +// ============================================================================ + +impl LightFinalize for CreateZeroCopy<'_> { + fn light_finalize( + &mut self, + _remaining_accounts: &[AccountInfo], + _params: &CreateZeroCopyParams, + _has_pre_init: bool, + ) -> std::result::Result<(), LightSdkTypesError> { + // No-op for PDA-only flow - compression CPI already executed in light_pre_init + Ok(()) + } +} + +// ============================================================================ +// Seeds Structs +// Extracted from: seeds = [b"zero_copy", params.owner.as_ref()] +// ============================================================================ + +/// Seeds for ZeroCopyRecord PDA. +/// Contains the dynamic seed values (static prefix "zero_copy" is in seed_refs). +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct ZeroCopyRecordSeeds { + pub owner: [u8; 32], + pub name: String, +} + +/// Packed seeds with u8 indices instead of Pubkeys. +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct PackedZeroCopyRecordSeeds { + pub owner_idx: u8, + pub name: String, + pub bump: u8, +} + +// ============================================================================ +// Variant Structs +// ============================================================================ + +/// Full variant combining seeds + data. +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct ZeroCopyRecordVariant { + pub seeds: ZeroCopyRecordSeeds, + pub data: ZeroCopyRecord, +} + +/// Packed variant for efficient serialization. +/// Contains packed seeds and data with u8 indices for Pubkey deduplication. +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct PackedZeroCopyRecordVariant { + pub seeds: PackedZeroCopyRecordSeeds, + pub data: PackedZeroCopyRecord, +} + +// ============================================================================ +// LightAccountVariant Implementation +// ============================================================================ + +impl LightAccountVariantTrait<4> for ZeroCopyRecordVariant { + const PROGRAM_ID: [u8; 32] = crate::ID; + + type Seeds = ZeroCopyRecordSeeds; + type Data = ZeroCopyRecord; + type Packed = PackedZeroCopyRecordVariant; + + fn data(&self) -> &Self::Data { + &self.data + } + + /// Get seed values as owned byte vectors for PDA derivation. + /// Generated from: seeds = [b"zero_copy", params.owner.as_ref(), params.name.as_bytes()] + fn seed_vec(&self) -> Vec> { + vec![ + b"zero_copy".to_vec(), + self.seeds.owner.to_vec(), + self.seeds.name.as_bytes().to_vec(), + ] + } + + /// Get seed references with bump for CPI signing. + /// Generated from: seeds = [b"zero_copy", params.owner.as_ref(), params.name.as_bytes()] + fn seed_refs_with_bump<'a>(&'a self, bump_storage: &'a [u8; 1]) -> [&'a [u8]; 4] { + [ + b"zero_copy", + self.seeds.owner.as_ref(), + self.seeds.name.as_bytes(), + bump_storage, + ] + } +} + +// ============================================================================ +// PackedLightAccountVariant Implementation +// ============================================================================ + +impl PackedLightAccountVariantTrait<4> for PackedZeroCopyRecordVariant { + type Unpacked = ZeroCopyRecordVariant; + + const ACCOUNT_TYPE: light_account_pinocchio::AccountType = + ::ACCOUNT_TYPE; + + fn bump(&self) -> u8 { + self.seeds.bump + } + + fn unpack( + &self, + accounts: &[AI], + ) -> std::result::Result { + let owner = accounts + .get(self.seeds.owner_idx as usize) + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)?; + + // Build ProgramPackedAccounts for LightAccount::unpack + let packed_accounts = ProgramPackedAccounts { accounts }; + let data = ZeroCopyRecord::unpack(&self.data, &packed_accounts) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + + Ok(ZeroCopyRecordVariant { + seeds: ZeroCopyRecordSeeds { + owner: owner.key(), + name: self.seeds.name.clone(), + }, + data, + }) + } + + fn seed_refs_with_bump<'a, AI: light_account_checks::AccountInfoTrait>( + &'a self, + _accounts: &'a [AI], + _bump_storage: &'a [u8; 1], + ) -> std::result::Result<[&'a [u8]; 4], LightSdkTypesError> { + Err(LightSdkTypesError::InvalidSeeds) + } + + fn into_in_token_data( + &self, + _tree_info: &light_account_pinocchio::PackedStateTreeInfo, + _output_queue_index: u8, + ) -> std::result::Result< + light_token_interface::instructions::transfer2::MultiInputTokenDataWithContext, + LightSdkTypesError, + > { + Err(LightSdkTypesError::InvalidInstructionData) + } + + fn into_in_tlv( + &self, + ) -> std::result::Result< + Option>, + LightSdkTypesError, + > { + Ok(None) + } +} + +// ============================================================================ +// IntoVariant Implementation for Seeds (client-side API) +// ============================================================================ + +/// Implement IntoVariant to allow building variant from seeds + compressed data. +/// This enables the high-level `create_load_instructions` API. +#[cfg(not(target_os = "solana"))] +impl light_account_pinocchio::IntoVariant for ZeroCopyRecordSeeds { + fn into_variant( + self, + data: &[u8], + ) -> std::result::Result + { + // For ZeroCopy (Pod) accounts, data is the full Pod bytes including compression_info. + // We deserialize using BorshDeserialize (which ZeroCopyRecord implements). + let record: ZeroCopyRecord = BorshDeserialize::deserialize(&mut &data[..]) + .map_err(|_| LightSdkTypesError::Borsh)?; + + // Verify the owner in data matches the seed + if record.owner != self.owner { + return Err(LightSdkTypesError::InvalidSeeds); + } + + Ok(ZeroCopyRecordVariant { + seeds: self, + data: record, + }) + } +} + +// ============================================================================ +// Pack Implementation for ZeroCopyRecordVariant (client-side API) +// ============================================================================ + +/// Implement Pack trait to allow ZeroCopyRecordVariant to be used with `create_load_instructions`. +/// Transforms the variant into PackedLightAccountVariant for efficient serialization. +#[cfg(not(target_os = "solana"))] +impl light_account_pinocchio::Pack + for ZeroCopyRecordVariant +{ + type Packed = crate::derived_variants::PackedLightAccountVariant; + + fn pack( + &self, + accounts: &mut light_account_pinocchio::PackedAccounts, + ) -> std::result::Result { + use light_account_pinocchio::LightAccountVariantTrait; + let (_, bump) = self.derive_pda::(); + let packed_data = self + .data + .pack(accounts) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + Ok( + crate::derived_variants::PackedLightAccountVariant::ZeroCopyRecord { + seeds: PackedZeroCopyRecordSeeds { + owner_idx: accounts.insert_or_get(solana_pubkey::Pubkey::from(self.seeds.owner)), + name: self.seeds.name.clone(), + bump, + }, + data: packed_data, + }, + ) + } +} diff --git a/sdk-tests/manual-test-pinocchio/src/account_loader/derived_state.rs b/sdk-tests/manual-test-pinocchio/src/account_loader/derived_state.rs new file mode 100644 index 0000000000..dd52f89280 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/account_loader/derived_state.rs @@ -0,0 +1,106 @@ +//! LightAccount implementation for ZeroCopyRecord. +//! +//! This follows the same pattern as MinimalRecord's derived_state.rs, +//! but for a Pod/zero-copy account type. + +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::{ + light_account_checks::{ + packed_accounts::ProgramPackedAccounts, AccountInfoTrait, AccountMetaTrait, + }, + AccountType, CompressionInfo, HasCompressionInfo, LightAccount, LightConfig, + LightSdkTypesError, +}; + +use super::state::ZeroCopyRecord; + +// ============================================================================ +// PackedZeroCopyRecord (compression_info excluded per implementation_details.md) +// ============================================================================ + +/// Packed version of ZeroCopyRecord for efficient transmission. +/// compression_info is excluded - it's cut off during pack. +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct PackedZeroCopyRecord { + /// Index into remaining_accounts instead of full Pubkey + pub owner: u8, + /// Value field (transmitted as-is) + pub value: u64, +} + +// ============================================================================ +// LightAccount Implementation for ZeroCopyRecord +// ============================================================================ + +impl LightAccount for ZeroCopyRecord { + const ACCOUNT_TYPE: AccountType = AccountType::PdaZeroCopy; + + type Packed = PackedZeroCopyRecord; + + // CompressionInfo (24) + owner (32) + value (8) = 64 bytes + const INIT_SPACE: usize = core::mem::size_of::(); + + fn compression_info(&self) -> &CompressionInfo { + &self.compression_info + } + + fn compression_info_mut(&mut self) -> &mut CompressionInfo { + &mut self.compression_info + } + + fn set_decompressed(&mut self, config: &LightConfig, current_slot: u64) { + self.compression_info = CompressionInfo::new_from_config(config, current_slot); + } + + #[cfg(not(target_os = "solana"))] + fn pack( + &self, + accounts: &mut light_account_pinocchio::interface::instruction::PackedAccounts, + ) -> std::result::Result { + // compression_info excluded from packed struct (same as Borsh accounts) + Ok(PackedZeroCopyRecord { + owner: accounts.insert_or_get(AM::pubkey_from_bytes(self.owner)), + value: self.value, + }) + } + + fn unpack( + packed: &Self::Packed, + accounts: &ProgramPackedAccounts, + ) -> std::result::Result { + // Use get_u8 with a descriptive name for better error messages + let owner_account = accounts + .get_u8(packed.owner, "ZeroCopyRecord: owner") + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + + // Set compression_info to compressed() for hash verification at decompress + // (Same pattern as Borsh accounts - canonical compressed state for hashing) + // Note: key() returns [u8; 32] directly, no conversion needed + Ok(ZeroCopyRecord { + compression_info: CompressionInfo::compressed(), + owner: owner_account.key(), + value: packed.value, + }) + } +} + +impl HasCompressionInfo for ZeroCopyRecord { + fn compression_info(&self) -> std::result::Result<&CompressionInfo, LightSdkTypesError> { + Ok(&self.compression_info) + } + + fn compression_info_mut( + &mut self, + ) -> std::result::Result<&mut CompressionInfo, LightSdkTypesError> { + Ok(&mut self.compression_info) + } + + fn compression_info_mut_opt(&mut self) -> &mut Option { + panic!("compression_info_mut_opt not supported for LightAccount types (use compression_info_mut instead)") + } + + fn set_compression_info_none(&mut self) -> std::result::Result<(), LightSdkTypesError> { + self.compression_info = CompressionInfo::compressed(); + Ok(()) + } +} diff --git a/sdk-tests/manual-test-pinocchio/src/account_loader/mod.rs b/sdk-tests/manual-test-pinocchio/src/account_loader/mod.rs new file mode 100644 index 0000000000..cb8b72ba8f --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/account_loader/mod.rs @@ -0,0 +1,24 @@ +//! Zero-copy AccountLoader support for compressible PDAs. +//! +//! This module demonstrates using AccountLoader<'info, T> instead of Account<'info, T> +//! for compressible accounts. Zero-copy accounts use bytemuck Pod/Zeroable traits +//! for direct memory access without deserialization. +//! +//! Key differences from Borsh accounts: +//! - State struct: `#[repr(C)]` + `Pod + Zeroable` instead of `#[account]` +//! - Data access: `ctx.accounts.record.load_mut()?.field` instead of `ctx.accounts.record.field` +//! - On-chain layout: Fixed-size Pod layout vs Borsh serialized +//! - Hashing: Still uses `try_to_vec()` (AnchorSerialize) for consistency + +pub mod accounts; +pub mod derived_accounts; +pub mod derived_state; +pub mod state; + +pub use accounts::*; +pub use derived_accounts::{ + PackedZeroCopyRecordSeeds, PackedZeroCopyRecordVariant, ZeroCopyRecordSeeds, + ZeroCopyRecordVariant, +}; +pub use derived_state::PackedZeroCopyRecord; +pub use state::ZeroCopyRecord; diff --git a/sdk-tests/manual-test-pinocchio/src/account_loader/state.rs b/sdk-tests/manual-test-pinocchio/src/account_loader/state.rs new file mode 100644 index 0000000000..d2df3e13e0 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/account_loader/state.rs @@ -0,0 +1,42 @@ +//! Zero-copy account state for AccountLoader demonstration. + +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::{CompressionInfo, LightDiscriminator, LightHasherSha}; + +/// Zero-copy account for demonstrating AccountLoader integration. +/// +/// Requirements: +/// - `#[repr(C)]` for predictable field layout +/// - `Pod + Zeroable` (bytemuck) for on-chain zero-copy access +/// - `BorshSerialize + BorshDeserialize` for hashing (same as Borsh accounts) +/// - `LightDiscriminator` for dispatch +/// - compression_info field for rent tracking +/// - All fields must be Pod-compatible (no Pubkey, use [u8; 32]) +#[derive( + Default, + Debug, + Copy, + Clone, + BorshSerialize, + BorshDeserialize, + LightDiscriminator, + LightHasherSha, + bytemuck::Pod, + bytemuck::Zeroable, +)] +#[repr(C)] +pub struct ZeroCopyRecord { + /// Compression info for rent tracking (must be first for consistent packing). + /// SDK CompressionInfo is 24 bytes, Pod-compatible. + pub compression_info: CompressionInfo, + /// Owner of the record (use byte array instead of Pubkey for Pod compatibility). + pub owner: [u8; 32], + /// A value field for demonstration. + pub value: u64, +} + +impl ZeroCopyRecord { + /// Space required for this account (excluding discriminator). + /// compression_info (24) + owner (32) + value (8) = 64 bytes + pub const INIT_SPACE: usize = core::mem::size_of::(); +} diff --git a/sdk-tests/manual-test-pinocchio/src/all/accounts.rs b/sdk-tests/manual-test-pinocchio/src/all/accounts.rs new file mode 100644 index 0000000000..3484bb1ee2 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/all/accounts.rs @@ -0,0 +1,221 @@ +//! Accounts module for create_all instruction (pinocchio version). + +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::CreateAccountsProof; +use pinocchio::{ + account_info::AccountInfo, + instruction::{Seed, Signer}, + program_error::ProgramError, + sysvars::Sysvar, +}; + +use crate::{account_loader::ZeroCopyRecord, pda::MinimalRecord}; + +/// Seed constants for ALL module (DIFFERENT from pda/account_loader modules) +pub const ALL_BORSH_SEED: &[u8] = b"all_borsh"; +pub const ALL_ZERO_COPY_SEED: &[u8] = b"all_zero_copy"; +pub const ALL_MINT_SIGNER_SEED: &[u8] = b"all_mint_signer"; +pub const ALL_TOKEN_VAULT_SEED: &[u8] = b"all_vault"; + +/// Parameters for creating all account types in a single instruction. +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] +pub struct CreateAllParams { + /// Proof for creating PDAs and mint addresses (3 addresses: 2 PDAs + 1 Mint). + pub create_accounts_proof: CreateAccountsProof, + /// Bump for the mint signer PDA. + pub mint_signer_bump: u8, + /// Bump for the token vault PDA. + pub token_vault_bump: u8, + /// Owner pubkey (used as seed for both PDAs). + pub owner: [u8; 32], + /// Value for the zero-copy record. + pub value: u64, +} + +/// Accounts struct for creating all account types in a single instruction. +/// +/// CPI context indices: +/// - PDA 0: Borsh PDA (MinimalRecord) - index 0 +/// - PDA 1: ZeroCopy PDA (ZeroCopyRecord) - index 1 +/// - Mint 0: Compressed mint - index 2 (offset by NUM_LIGHT_PDAS=2) +pub struct CreateAllAccounts<'a> { + pub payer: &'a AccountInfo, + pub authority: &'a AccountInfo, + pub compression_config: &'a AccountInfo, + pub borsh_record: &'a AccountInfo, + pub zero_copy_record: &'a AccountInfo, + pub mint_signer: &'a AccountInfo, + pub mint: &'a AccountInfo, + pub token_vault: &'a AccountInfo, + pub vault_owner: &'a AccountInfo, + pub ata_owner: &'a AccountInfo, + pub user_ata: &'a AccountInfo, + pub compressible_config: &'a AccountInfo, + pub rent_sponsor: &'a AccountInfo, + pub light_token_program: &'a AccountInfo, + pub cpi_authority: &'a AccountInfo, + pub system_program: &'a AccountInfo, + /// Slice view for mint_signer accounts (for invoke_create_mints) + pub mint_signers_slice: &'a [AccountInfo], + /// Slice view for mint accounts (for invoke_create_mints) + pub mints_slice: &'a [AccountInfo], +} + +impl<'a> CreateAllAccounts<'a> { + pub const FIXED_LEN: usize = 16; + + pub fn parse( + accounts: &'a [AccountInfo], + params: &CreateAllParams, + ) -> Result { + let payer = &accounts[0]; + let authority = &accounts[1]; + let compression_config = &accounts[2]; + let borsh_record = &accounts[3]; + let zero_copy_record = &accounts[4]; + let mint_signer = &accounts[5]; + let mint = &accounts[6]; + let token_vault = &accounts[7]; + let vault_owner = &accounts[8]; + let ata_owner = &accounts[9]; + let user_ata = &accounts[10]; + let compressible_config = &accounts[11]; + let rent_sponsor = &accounts[12]; + let light_token_program = &accounts[13]; + let cpi_authority = &accounts[14]; + let system_program = &accounts[15]; + + // Validate signers + if !payer.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + if !authority.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + // ==================== Create Borsh PDA ==================== + { + let space = 8 + MinimalRecord::INIT_SPACE; + let seeds: &[&[u8]] = &[ALL_BORSH_SEED, ¶ms.owner]; + let (expected_pda, bump) = + pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if borsh_record.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + + let rent = pinocchio::sysvars::rent::Rent::get() + .map_err(|_| ProgramError::UnsupportedSysvar)?; + let lamports = rent.minimum_balance(space); + + let bump_bytes = [bump]; + let seed_array = [ + Seed::from(ALL_BORSH_SEED), + Seed::from(params.owner.as_ref()), + Seed::from(bump_bytes.as_ref()), + ]; + let signer = Signer::from(&seed_array); + pinocchio_system::instructions::CreateAccount { + from: payer, + to: borsh_record, + lamports, + space: space as u64, + owner: &crate::ID, + } + .invoke_signed(&[signer])?; + + // Write LIGHT_DISCRIMINATOR + use light_account_pinocchio::LightDiscriminator; + let mut data = borsh_record + .try_borrow_mut_data() + .map_err(|_| ProgramError::AccountBorrowFailed)?; + data[..8].copy_from_slice(&MinimalRecord::LIGHT_DISCRIMINATOR); + } + + // ==================== Create Zero-Copy PDA ==================== + { + let space = 8 + ZeroCopyRecord::INIT_SPACE; + let seeds: &[&[u8]] = &[ALL_ZERO_COPY_SEED, ¶ms.owner]; + let (expected_pda, bump) = + pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if zero_copy_record.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + + let rent = pinocchio::sysvars::rent::Rent::get() + .map_err(|_| ProgramError::UnsupportedSysvar)?; + let lamports = rent.minimum_balance(space); + + let bump_bytes = [bump]; + let seed_array = [ + Seed::from(ALL_ZERO_COPY_SEED), + Seed::from(params.owner.as_ref()), + Seed::from(bump_bytes.as_ref()), + ]; + let signer = Signer::from(&seed_array); + pinocchio_system::instructions::CreateAccount { + from: payer, + to: zero_copy_record, + lamports, + space: space as u64, + owner: &crate::ID, + } + .invoke_signed(&[signer])?; + + // Write LIGHT_DISCRIMINATOR + use light_account_pinocchio::LightDiscriminator; + let mut data = zero_copy_record + .try_borrow_mut_data() + .map_err(|_| ProgramError::AccountBorrowFailed)?; + data[..8].copy_from_slice(&ZeroCopyRecord::LIGHT_DISCRIMINATOR); + } + + // ==================== Validate mint_signer PDA ==================== + { + let authority_key = authority.key(); + let seeds: &[&[u8]] = &[ALL_MINT_SIGNER_SEED, authority_key]; + let (expected_pda, expected_bump) = + pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if mint_signer.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + if expected_bump != params.mint_signer_bump { + return Err(ProgramError::InvalidSeeds); + } + } + + // ==================== Validate token_vault PDA ==================== + { + let mint_key = mint.key(); + let seeds: &[&[u8]] = &[ALL_TOKEN_VAULT_SEED, mint_key]; + let (expected_pda, expected_bump) = + pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if token_vault.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + if expected_bump != params.token_vault_bump { + return Err(ProgramError::InvalidSeeds); + } + } + + Ok(Self { + payer, + authority, + compression_config, + borsh_record, + zero_copy_record, + mint_signer, + mint, + token_vault, + vault_owner, + ata_owner, + user_ata, + compressible_config, + rent_sponsor, + light_token_program, + cpi_authority, + system_program, + mint_signers_slice: &accounts[5..6], + mints_slice: &accounts[6..7], + }) + } +} diff --git a/sdk-tests/manual-test-pinocchio/src/all/derived.rs b/sdk-tests/manual-test-pinocchio/src/all/derived.rs new file mode 100644 index 0000000000..ba2d80b8fb --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/all/derived.rs @@ -0,0 +1,313 @@ +//! Derived code for create_all instruction. +//! +//! This implements LightPreInit/LightFinalize for creating all account types: +//! - 2 PDAs (Borsh + ZeroCopy) via `invoke_write_to_cpi_context_first()` +//! - 1 Mint via `invoke_create_mints()` with cpi_context_offset +//! - 1 Token Vault via `CreateTokenAccountCpi` +//! - 1 ATA via `CreateTokenAtaCpi` + +use light_account_pinocchio::{ + prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, + CpiContextWriteAccounts, InvokeLightSystemProgram, LightAccount, LightFinalize, + LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, + invoke_create_mints, CreateMintsInfraAccounts, CreateMintsParams as SdkCreateMintsParams, + SingleMintParams, derive_mint_compressed_address, find_mint_address, + DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, + CreateTokenAccountCpi, CreateTokenAtaCpi, derive_associated_token_account, +}; +use light_compressed_account::instruction_data::{ + cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, +}; +use pinocchio::account_info::AccountInfo; + +use super::accounts::{ + CreateAllAccounts, CreateAllParams, ALL_MINT_SIGNER_SEED, ALL_TOKEN_VAULT_SEED, +}; + +// ============================================================================ +// LightPreInit Implementation - Creates all accounts at START of instruction +// ============================================================================ + +impl LightPreInit for CreateAllAccounts<'_> { + fn light_pre_init( + &mut self, + remaining_accounts: &[AccountInfo], + params: &CreateAllParams, + ) -> std::result::Result { + let inner = || -> std::result::Result { + use light_account_pinocchio::LightConfig; + use pinocchio::sysvars::{clock::Clock, Sysvar}; + + // Constants for this instruction + const NUM_LIGHT_PDAS: usize = 2; + const NUM_LIGHT_MINTS: usize = 1; + const WITH_CPI_CONTEXT: bool = NUM_LIGHT_PDAS > 0 && NUM_LIGHT_MINTS > 0; // true + + // ==================================================================== + // 1. Build CPI accounts with cpi_context config + // ==================================================================== + let system_accounts_offset = + params.create_accounts_proof.system_accounts_offset as usize; + if remaining_accounts.len() < system_accounts_offset { + return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); + } + let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); + let cpi_accounts = CpiAccounts::new_with_config( + self.payer, + &remaining_accounts[system_accounts_offset..], + config, + ); + + // ==================================================================== + // 2. Get address tree info + // ==================================================================== + let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; + let address_tree_pubkey = address_tree_info + .get_tree_pubkey(&cpi_accounts) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + let output_tree_index = params.create_accounts_proof.output_state_tree_index; + + // ==================================================================== + // 3. Load config, get current slot + // ==================================================================== + let light_config = + LightConfig::load_checked(self.compression_config, &crate::ID) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + let current_slot = Clock::get() + .map_err(|_| LightSdkTypesError::InvalidInstructionData)? + .slot; + + // ==================================================================== + // 4. Create PDAs via invoke_write_to_cpi_context_first() + // ==================================================================== + { + // CPI context for PDAs - set to first() since we have mints coming after + let cpi_context = CompressedCpiContext::first(); + let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); + let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); + + // 4a. Prepare Borsh PDA (index 0) + let borsh_record_key = *self.borsh_record.key(); + prepare_compressed_account_on_init( + &borsh_record_key, + &address_tree_pubkey, + address_tree_info, + output_tree_index, + 0, // assigned_account_index = 0 + &crate::ID, + &mut new_address_params, + &mut account_infos, + )?; + // Set compression_info on the Borsh record via mut_from_account_data + { + let mut account_data = self + .borsh_record + .try_borrow_mut_data() + .map_err(|_| LightSdkTypesError::Borsh)?; + let record = crate::pda::MinimalRecord::mut_from_account_data(&mut account_data); + record.set_decompressed(&light_config, current_slot); + } + + // 4b. Prepare ZeroCopy PDA (index 1) + let zero_copy_record_key = *self.zero_copy_record.key(); + prepare_compressed_account_on_init( + &zero_copy_record_key, + &address_tree_pubkey, + address_tree_info, + output_tree_index, + 1, // assigned_account_index = 1 + &crate::ID, + &mut new_address_params, + &mut account_infos, + )?; + { + let mut account_data = self + .zero_copy_record + .try_borrow_mut_data() + .map_err(|_| LightSdkTypesError::Borsh)?; + let record_bytes = &mut account_data + [8..8 + core::mem::size_of::()]; + let record: &mut crate::account_loader::ZeroCopyRecord = + bytemuck::from_bytes_mut(record_bytes); + record.set_decompressed(&light_config, current_slot); + } + + // 4c. Build instruction data and write to CPI context (doesn't execute yet) + let instruction_data = InstructionDataInvokeCpiWithAccountInfo { + mode: 1, // V2 mode + bump: crate::LIGHT_CPI_SIGNER.bump, + invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), + compress_or_decompress_lamports: 0, + is_compress: false, + with_cpi_context: WITH_CPI_CONTEXT, + with_transaction_hash: false, + cpi_context, + proof: params.create_accounts_proof.proof.0, + new_address_params, + account_infos, + read_only_addresses: vec![], + read_only_accounts: vec![], + }; + + // Write to CPI context first (combined execution happens with mints) + let cpi_context_accounts = CpiContextWriteAccounts { + fee_payer: cpi_accounts.fee_payer(), + authority: cpi_accounts.authority()?, + cpi_context: cpi_accounts.cpi_context()?, + cpi_signer: crate::LIGHT_CPI_SIGNER, + }; + instruction_data + .invoke_write_to_cpi_context_first(cpi_context_accounts)?; + } + + // ==================================================================== + // 5. Create Mint via invoke_create_mints() with offset + // ==================================================================== + { + let authority_key = *self.authority.key(); + let mint_signer_key = *self.mint_signer.key(); + + // Derive mint PDA + let (mint_pda, mint_bump) = find_mint_address(&mint_signer_key); + + // Derive compression address + let compression_address = derive_mint_compressed_address( + &mint_signer_key, + &address_tree_pubkey, + ); + + // Build mint signer seeds + let mint_signer_seeds: &[&[u8]] = &[ + ALL_MINT_SIGNER_SEED, + authority_key.as_ref(), + &[params.mint_signer_bump], + ]; + + // Build SingleMintParams + let sdk_mints: [SingleMintParams<'_>; NUM_LIGHT_MINTS] = [SingleMintParams { + decimals: 6, // mint::decimals = 6 + address_merkle_tree_root_index: address_tree_info.root_index, + mint_authority: authority_key, + compression_address, + mint: mint_pda, + bump: mint_bump, + freeze_authority: None, + mint_seed_pubkey: mint_signer_key, + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_seeds), + token_metadata: None, + }]; + + let state_tree_index = params + .create_accounts_proof + .state_tree_index + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + + let proof = params + .create_accounts_proof + .proof + .0 + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + + // Build SDK params with cpi_context_offset + let sdk_params = SdkCreateMintsParams { + mints: &sdk_mints, + proof, + rent_payment: DEFAULT_RENT_PAYMENT, + write_top_up: DEFAULT_WRITE_TOP_UP, + cpi_context_offset: NUM_LIGHT_PDAS as u8, + output_queue_index: params.create_accounts_proof.output_state_tree_index, + address_tree_index: address_tree_info.address_merkle_tree_pubkey_index, + state_tree_index, + base_leaf_index: 0, // N=1, not used + }; + + // Build infra accounts + let infra = CreateMintsInfraAccounts { + fee_payer: self.payer, + compressible_config: self.compressible_config, + rent_sponsor: self.rent_sponsor, + cpi_authority: self.cpi_authority, + }; + + invoke_create_mints( + self.mint_signers_slice, + self.mints_slice, + sdk_params, + infra, + &cpi_accounts, + ) + .map_err(|_| LightSdkTypesError::CpiFailed)?; + } + + // ==================================================================== + // 6. Create Token Vault via CreateTokenAccountCpi + // ==================================================================== + { + let mint_key = *self.mint.key(); + let vault_seeds: &[&[u8]] = &[ + ALL_TOKEN_VAULT_SEED, + mint_key.as_ref(), + &[params.token_vault_bump], + ]; + + CreateTokenAccountCpi { + payer: self.payer, + account: self.token_vault, + mint: self.mint, + owner: *self.vault_owner.key(), + } + .rent_free( + self.compressible_config, + self.rent_sponsor, + self.system_program, + &crate::ID, + ) + .invoke_signed(vault_seeds)?; + } + + // ==================================================================== + // 7. Create ATA via CreateTokenAtaCpi + // ==================================================================== + { + let (_, ata_bump) = derive_associated_token_account( + self.ata_owner.key(), + self.mint.key(), + ); + + CreateTokenAtaCpi { + payer: self.payer, + owner: self.ata_owner, + mint: self.mint, + ata: self.user_ata, + bump: ata_bump, + } + .rent_free( + self.compressible_config, + self.rent_sponsor, + self.system_program, + ) + .invoke()?; + } + + Ok(WITH_CPI_CONTEXT) + }; + inner() + } +} + +// ============================================================================ +// LightFinalize Implementation - No-op for this flow +// ============================================================================ + +impl LightFinalize for CreateAllAccounts<'_> { + fn light_finalize( + &mut self, + _remaining_accounts: &[AccountInfo], + _params: &CreateAllParams, + _has_pre_init: bool, + ) -> std::result::Result<(), LightSdkTypesError> { + // All accounts were created in light_pre_init + Ok(()) + } +} diff --git a/sdk-tests/manual-test-pinocchio/src/all/derived_accounts.rs b/sdk-tests/manual-test-pinocchio/src/all/derived_accounts.rs new file mode 100644 index 0000000000..48e1ceae3d --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/all/derived_accounts.rs @@ -0,0 +1,398 @@ +//! Derived account types for the all module. +//! Uses different seeds than pda/account_loader modules but reuses the data types. + +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::{ + light_account_checks::{self, packed_accounts::ProgramPackedAccounts}, + LightAccount, LightAccountVariantTrait, LightSdkTypesError, PackedLightAccountVariantTrait, +}; +use pinocchio::account_info::AccountInfo; + +use super::accounts::{ALL_BORSH_SEED, ALL_ZERO_COPY_SEED}; +use crate::{ + account_loader::{PackedZeroCopyRecord, ZeroCopyRecord}, + pda::{MinimalRecord, PackedMinimalRecord}, +}; + +// ============================================================================ +// AllBorsh Seeds (different seed prefix from MinimalRecordSeeds) +// ============================================================================ + +/// Seeds for AllBorsh PDA. +/// Contains the dynamic seed values (static prefix "all_borsh" is in seed_refs). +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct AllBorshSeeds { + pub owner: [u8; 32], +} + +/// Packed seeds with u8 indices instead of Pubkeys. +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct PackedAllBorshSeeds { + pub owner_idx: u8, + pub bump: u8, +} + +// ============================================================================ +// AllBorsh Variant (combines AllBorshSeeds + MinimalRecord data) +// ============================================================================ + +/// Full variant combining AllBorsh seeds + MinimalRecord data. +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct AllBorshVariant { + pub seeds: AllBorshSeeds, + pub data: MinimalRecord, +} + +/// Packed variant for efficient serialization. +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct PackedAllBorshVariant { + pub seeds: PackedAllBorshSeeds, + pub data: PackedMinimalRecord, +} + +// ============================================================================ +// LightAccountVariant Implementation for AllBorshVariant +// ============================================================================ + +impl LightAccountVariantTrait<3> for AllBorshVariant { + const PROGRAM_ID: [u8; 32] = crate::ID; + + type Seeds = AllBorshSeeds; + type Data = MinimalRecord; + type Packed = PackedAllBorshVariant; + + fn data(&self) -> &Self::Data { + &self.data + } + + /// Get seed values as owned byte vectors for PDA derivation. + /// Generated from: seeds = [b"all_borsh", params.owner.as_ref()] + fn seed_vec(&self) -> Vec> { + vec![ + ALL_BORSH_SEED.to_vec(), + self.seeds.owner.to_vec(), + ] + } + + /// Get seed references with bump for CPI signing. + /// Generated from: seeds = [b"all_borsh", params.owner.as_ref()] + fn seed_refs_with_bump<'a>(&'a self, bump_storage: &'a [u8; 1]) -> [&'a [u8]; 3] { + [ALL_BORSH_SEED, self.seeds.owner.as_ref(), bump_storage] + } +} + +// ============================================================================ +// PackedLightAccountVariant Implementation for PackedAllBorshVariant +// ============================================================================ + +impl PackedLightAccountVariantTrait<3> for PackedAllBorshVariant { + type Unpacked = AllBorshVariant; + + const ACCOUNT_TYPE: light_account_pinocchio::AccountType = + ::ACCOUNT_TYPE; + + fn bump(&self) -> u8 { + self.seeds.bump + } + + fn unpack( + &self, + accounts: &[AI], + ) -> std::result::Result { + let owner = accounts + .get(self.seeds.owner_idx as usize) + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)?; + + // Build ProgramPackedAccounts for LightAccount::unpack + let packed_accounts = ProgramPackedAccounts { accounts }; + let data = MinimalRecord::unpack(&self.data, &packed_accounts) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + + Ok(AllBorshVariant { + seeds: AllBorshSeeds { + owner: owner.key(), + }, + data, + }) + } + + fn seed_refs_with_bump<'a, AI: light_account_checks::AccountInfoTrait>( + &'a self, + _accounts: &'a [AI], + _bump_storage: &'a [u8; 1], + ) -> std::result::Result<[&'a [u8]; 3], LightSdkTypesError> { + Err(LightSdkTypesError::InvalidSeeds) + } + + fn into_in_token_data( + &self, + _tree_info: &light_account_pinocchio::PackedStateTreeInfo, + _output_queue_index: u8, + ) -> std::result::Result< + light_token_interface::instructions::transfer2::MultiInputTokenDataWithContext, + LightSdkTypesError, + > { + Err(LightSdkTypesError::InvalidInstructionData) + } + + fn into_in_tlv( + &self, + ) -> std::result::Result< + Option>, + LightSdkTypesError, + > { + Ok(None) + } +} + +// ============================================================================ +// AllZeroCopy Seeds (different seed prefix from ZeroCopyRecordSeeds) +// ============================================================================ + +/// Seeds for AllZeroCopy PDA. +/// Contains the dynamic seed values (static prefix "all_zero_copy" is in seed_refs). +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct AllZeroCopySeeds { + pub owner: [u8; 32], +} + +/// Packed seeds with u8 indices instead of Pubkeys. +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct PackedAllZeroCopySeeds { + pub owner_idx: u8, + pub bump: u8, +} + +// ============================================================================ +// AllZeroCopy Variant (combines AllZeroCopySeeds + ZeroCopyRecord data) +// ============================================================================ + +/// Full variant combining AllZeroCopy seeds + ZeroCopyRecord data. +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct AllZeroCopyVariant { + pub seeds: AllZeroCopySeeds, + pub data: ZeroCopyRecord, +} + +/// Packed variant for efficient serialization. +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct PackedAllZeroCopyVariant { + pub seeds: PackedAllZeroCopySeeds, + pub data: PackedZeroCopyRecord, +} + +// ============================================================================ +// LightAccountVariant Implementation for AllZeroCopyVariant +// ============================================================================ + +impl LightAccountVariantTrait<3> for AllZeroCopyVariant { + const PROGRAM_ID: [u8; 32] = crate::ID; + + type Seeds = AllZeroCopySeeds; + type Data = ZeroCopyRecord; + type Packed = PackedAllZeroCopyVariant; + + fn data(&self) -> &Self::Data { + &self.data + } + + /// Get seed values as owned byte vectors for PDA derivation. + /// Generated from: seeds = [b"all_zero_copy", params.owner.as_ref()] + fn seed_vec(&self) -> Vec> { + vec![ + ALL_ZERO_COPY_SEED.to_vec(), + self.seeds.owner.to_vec(), + ] + } + + /// Get seed references with bump for CPI signing. + /// Generated from: seeds = [b"all_zero_copy", params.owner.as_ref()] + fn seed_refs_with_bump<'a>(&'a self, bump_storage: &'a [u8; 1]) -> [&'a [u8]; 3] { + [ALL_ZERO_COPY_SEED, self.seeds.owner.as_ref(), bump_storage] + } +} + +// ============================================================================ +// PackedLightAccountVariant Implementation for PackedAllZeroCopyVariant +// ============================================================================ + +impl PackedLightAccountVariantTrait<3> for PackedAllZeroCopyVariant { + type Unpacked = AllZeroCopyVariant; + + const ACCOUNT_TYPE: light_account_pinocchio::AccountType = + ::ACCOUNT_TYPE; + + fn bump(&self) -> u8 { + self.seeds.bump + } + + fn unpack( + &self, + accounts: &[AI], + ) -> std::result::Result { + let owner = accounts + .get(self.seeds.owner_idx as usize) + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)?; + + // Build ProgramPackedAccounts for LightAccount::unpack + let packed_accounts = ProgramPackedAccounts { accounts }; + let data = ZeroCopyRecord::unpack(&self.data, &packed_accounts) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + + Ok(AllZeroCopyVariant { + seeds: AllZeroCopySeeds { + owner: owner.key(), + }, + data, + }) + } + + fn seed_refs_with_bump<'a, AI: light_account_checks::AccountInfoTrait>( + &'a self, + _accounts: &'a [AI], + _bump_storage: &'a [u8; 1], + ) -> std::result::Result<[&'a [u8]; 3], LightSdkTypesError> { + Err(LightSdkTypesError::InvalidSeeds) + } + + fn into_in_token_data( + &self, + _tree_info: &light_account_pinocchio::PackedStateTreeInfo, + _output_queue_index: u8, + ) -> std::result::Result< + light_token_interface::instructions::transfer2::MultiInputTokenDataWithContext, + LightSdkTypesError, + > { + Err(LightSdkTypesError::InvalidInstructionData) + } + + fn into_in_tlv( + &self, + ) -> std::result::Result< + Option>, + LightSdkTypesError, + > { + Ok(None) + } +} + +// ============================================================================ +// IntoVariant Implementation for AllBorshSeeds (client-side API) +// ============================================================================ + +/// Implement IntoVariant to allow building variant from seeds + compressed data. +/// This enables the high-level `create_load_instructions` API. +#[cfg(not(target_os = "solana"))] +impl light_account_pinocchio::IntoVariant for AllBorshSeeds { + fn into_variant( + self, + data: &[u8], + ) -> std::result::Result { + // Deserialize the compressed data (which includes compression_info) + let record: MinimalRecord = BorshDeserialize::deserialize(&mut &data[..]) + .map_err(|_| LightSdkTypesError::Borsh)?; + + // Verify the owner in data matches the seed + if record.owner != self.owner { + return Err(LightSdkTypesError::InvalidSeeds); + } + + Ok(AllBorshVariant { + seeds: self, + data: record, + }) + } +} + +// ============================================================================ +// Pack Implementation for AllBorshVariant (client-side API) +// ============================================================================ + +/// Implement Pack trait to allow AllBorshVariant to be used with `create_load_instructions`. +/// Transforms the variant into PackedLightAccountVariant for efficient serialization. +#[cfg(not(target_os = "solana"))] +impl light_account_pinocchio::Pack for AllBorshVariant { + type Packed = crate::derived_variants::PackedLightAccountVariant; + + fn pack( + &self, + accounts: &mut light_account_pinocchio::PackedAccounts, + ) -> std::result::Result { + use light_account_pinocchio::LightAccountVariantTrait; + let (_, bump) = self.derive_pda::(); + let packed_data = self + .data + .pack(accounts) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + Ok( + crate::derived_variants::PackedLightAccountVariant::AllBorsh { + seeds: PackedAllBorshSeeds { + owner_idx: accounts.insert_or_get(solana_pubkey::Pubkey::from(self.seeds.owner)), + bump, + }, + data: packed_data, + }, + ) + } +} + +// ============================================================================ +// IntoVariant Implementation for AllZeroCopySeeds (client-side API) +// ============================================================================ + +/// Implement IntoVariant to allow building variant from seeds + compressed data. +/// This enables the high-level `create_load_instructions` API. +#[cfg(not(target_os = "solana"))] +impl light_account_pinocchio::IntoVariant for AllZeroCopySeeds { + fn into_variant( + self, + data: &[u8], + ) -> std::result::Result { + // For ZeroCopy (Pod) accounts, data is the full Pod bytes including compression_info. + // We deserialize using BorshDeserialize (which ZeroCopyRecord implements). + let record: ZeroCopyRecord = BorshDeserialize::deserialize(&mut &data[..]) + .map_err(|_| LightSdkTypesError::Borsh)?; + + // Verify the owner in data matches the seed + if record.owner != self.owner { + return Err(LightSdkTypesError::InvalidSeeds); + } + + Ok(AllZeroCopyVariant { + seeds: self, + data: record, + }) + } +} + +// ============================================================================ +// Pack Implementation for AllZeroCopyVariant (client-side API) +// ============================================================================ + +/// Implement Pack trait to allow AllZeroCopyVariant to be used with `create_load_instructions`. +/// Transforms the variant into PackedLightAccountVariant for efficient serialization. +#[cfg(not(target_os = "solana"))] +impl light_account_pinocchio::Pack for AllZeroCopyVariant { + type Packed = crate::derived_variants::PackedLightAccountVariant; + + fn pack( + &self, + accounts: &mut light_account_pinocchio::PackedAccounts, + ) -> std::result::Result { + use light_account_pinocchio::LightAccountVariantTrait; + let (_, bump) = self.derive_pda::(); + let packed_data = self + .data + .pack(accounts) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + Ok( + crate::derived_variants::PackedLightAccountVariant::AllZeroCopy { + seeds: PackedAllZeroCopySeeds { + owner_idx: accounts.insert_or_get(solana_pubkey::Pubkey::from(self.seeds.owner)), + bump, + }, + data: packed_data, + }, + ) + } +} diff --git a/sdk-tests/manual-test-pinocchio/src/all/mod.rs b/sdk-tests/manual-test-pinocchio/src/all/mod.rs new file mode 100644 index 0000000000..6117f64308 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/all/mod.rs @@ -0,0 +1,23 @@ +//! All account types creation - manual implementation of macro-generated code. +//! +//! This module demonstrates creating ALL account types in a single instruction: +//! - Borsh PDA (MinimalRecord) +//! - ZeroCopy PDA (ZeroCopyRecord) +//! - Compressed Mint +//! - Token Vault +//! - Associated Token Account (ATA) +//! +//! Key pattern: PDAs + Mints require CPI context flow: +//! - PDAs call `invoke_write_to_cpi_context_first()` (writes to CPI context, doesn't execute) +//! - Mints call `invoke_create_mints()` with `.with_cpi_context_offset(NUM_LIGHT_PDAS)` (executes combined CPI) +//! - Token vault and ATA are separate CPIs (don't participate in CPI context) + +pub mod accounts; +mod derived; +pub mod derived_accounts; + +pub use accounts::*; +pub use derived_accounts::{ + AllBorshSeeds, AllBorshVariant, AllZeroCopySeeds, AllZeroCopyVariant, PackedAllBorshSeeds, + PackedAllBorshVariant, PackedAllZeroCopySeeds, PackedAllZeroCopyVariant, +}; diff --git a/sdk-tests/manual-test-pinocchio/src/ata/accounts.rs b/sdk-tests/manual-test-pinocchio/src/ata/accounts.rs new file mode 100644 index 0000000000..8a4ea5665d --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/ata/accounts.rs @@ -0,0 +1,50 @@ +//! Accounts struct for create_ata instruction (pinocchio version). + +use borsh::{BorshDeserialize, BorshSerialize}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError}; + +/// Params for ATA creation (empty - bump is derived automatically). +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Default)] +pub struct CreateAtaParams {} + +/// Accounts struct for creating an Associated Token Account. +pub struct CreateAtaAccounts<'a> { + pub payer: &'a AccountInfo, + pub mint: &'a AccountInfo, + pub ata_owner: &'a AccountInfo, + pub user_ata: &'a AccountInfo, + pub compressible_config: &'a AccountInfo, + pub rent_sponsor: &'a AccountInfo, + pub light_token_program: &'a AccountInfo, + pub system_program: &'a AccountInfo, +} + +impl<'a> CreateAtaAccounts<'a> { + pub const FIXED_LEN: usize = 8; + + pub fn parse(accounts: &'a [AccountInfo]) -> Result { + let payer = &accounts[0]; + let mint = &accounts[1]; + let ata_owner = &accounts[2]; + let user_ata = &accounts[3]; + let compressible_config = &accounts[4]; + let rent_sponsor = &accounts[5]; + let light_token_program = &accounts[6]; + let system_program = &accounts[7]; + + if !payer.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + Ok(Self { + payer, + mint, + ata_owner, + user_ata, + compressible_config, + rent_sponsor, + light_token_program, + system_program, + }) + } +} diff --git a/sdk-tests/manual-test-pinocchio/src/ata/derived.rs b/sdk-tests/manual-test-pinocchio/src/ata/derived.rs new file mode 100644 index 0000000000..576e7c171a --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/ata/derived.rs @@ -0,0 +1,66 @@ +//! Derived code - what the macro would generate for associated token accounts. + +use light_account_pinocchio::{ + CreateTokenAtaCpi, LightFinalize, LightPreInit, LightSdkTypesError, + derive_associated_token_account, +}; +use pinocchio::account_info::AccountInfo; + +use super::accounts::{CreateAtaAccounts, CreateAtaParams}; + +// ============================================================================ +// LightPreInit Implementation - Creates ATA at START of instruction +// ============================================================================ + +impl LightPreInit for CreateAtaAccounts<'_> { + fn light_pre_init( + &mut self, + _remaining_accounts: &[AccountInfo], + _params: &CreateAtaParams, + ) -> std::result::Result { + let inner = || -> std::result::Result { + // Derive the ATA bump on-chain + let (_, bump) = derive_associated_token_account( + self.ata_owner.key(), + self.mint.key(), + ); + + // Create ATA via CPI with idempotent + rent-free mode + // NOTE: Unlike token vaults, ATAs use .invoke() not .invoke_signed() + // because ATAs are derived from [owner, token_program, mint], not program PDAs + CreateTokenAtaCpi { + payer: self.payer, + owner: self.ata_owner, + mint: self.mint, + ata: self.user_ata, + bump, + } + .idempotent() // Safe: won't fail if ATA already exists + .rent_free( + self.compressible_config, + self.rent_sponsor, + self.system_program, + ) + .invoke()?; + + // ATAs don't use CPI context, return false + Ok(false) + }; + inner() + } +} + +// ============================================================================ +// LightFinalize Implementation - No-op for ATA only flow +// ============================================================================ + +impl LightFinalize for CreateAtaAccounts<'_> { + fn light_finalize( + &mut self, + _remaining_accounts: &[AccountInfo], + _params: &CreateAtaParams, + _has_pre_init: bool, + ) -> std::result::Result<(), LightSdkTypesError> { + Ok(()) + } +} diff --git a/sdk-tests/manual-test-pinocchio/src/ata/mod.rs b/sdk-tests/manual-test-pinocchio/src/ata/mod.rs new file mode 100644 index 0000000000..55917fe3bf --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/ata/mod.rs @@ -0,0 +1,6 @@ +//! Associated token account creation - manual implementation of macro-generated code. + +pub mod accounts; +mod derived; + +pub use accounts::*; diff --git a/sdk-tests/manual-test-pinocchio/src/derived_compress.rs b/sdk-tests/manual-test-pinocchio/src/derived_compress.rs new file mode 100644 index 0000000000..d95f06a127 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/derived_compress.rs @@ -0,0 +1,75 @@ +//! Macro-derived compress and close implementation. +//! +//! This module contains the code that would be generated by the `#[light_program]` macro. +//! The dispatch function handles type-specific deserialization and compression. + +use borsh::BorshDeserialize; +use light_account_pinocchio::{ + account_meta::CompressedAccountMetaNoLamportsNoAddress, prepare_account_for_compression, + process_compress_pda_accounts_idempotent, CompressCtx, LightDiscriminator, LightSdkTypesError, +}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError}; + +use crate::{account_loader::ZeroCopyRecord, pda::MinimalRecord}; + +/// MACRO-GENERATED: Discriminator-based dispatch function. +/// +/// For each account type, this function: +/// 1. Reads the discriminator from account data +/// 2. Deserializes the account based on discriminator +/// 3. Calls prepare_account_for_compression with the deserialized data +fn compress_dispatch( + account_info: &AccountInfo, + meta: &CompressedAccountMetaNoLamportsNoAddress, + index: usize, + ctx: &mut CompressCtx<'_>, +) -> std::result::Result<(), LightSdkTypesError> { + let data = account_info + .try_borrow_data() + .map_err(|_| LightSdkTypesError::Borsh)?; + + // Read discriminator from first 8 bytes + let discriminator: [u8; 8] = data[..8] + .try_into() + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + + match discriminator { + d if d == MinimalRecord::LIGHT_DISCRIMINATOR => { + // Borsh path: deserialize using try_from_slice + let mut account_data = + MinimalRecord::try_from_slice(&data[8..]).map_err(|_| LightSdkTypesError::Borsh)?; + drop(data); + + // Call prepare with deserialized data + prepare_account_for_compression(account_info, &mut account_data, meta, index, ctx) + } + d if d == ZeroCopyRecord::LIGHT_DISCRIMINATOR => { + // Pod/Zero-copy path: read using bytemuck + // The data is in fixed Pod layout, so we can directly cast it + let record_bytes = &data[8..8 + core::mem::size_of::()]; + let mut account_data: ZeroCopyRecord = *bytemuck::from_bytes(record_bytes); + drop(data); + + // Same prepare function works - hashing uses try_to_vec() which ZeroCopyRecord supports + // via its BorshSerialize implementation + prepare_account_for_compression(account_info, &mut account_data, meta, index, ctx) + } + // Unknown discriminator - skip (not an error, could be different account type) + _ => Ok(()), + } +} + +/// MACRO-GENERATED: Process handler - just forwards to SDK function with dispatch. +pub fn process_compress_and_close( + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> Result<(), ProgramError> { + process_compress_pda_accounts_idempotent( + accounts, + instruction_data, + compress_dispatch, + crate::LIGHT_CPI_SIGNER, + &crate::ID, + ) + .map_err(|e| ProgramError::Custom(u32::from(e))) +} diff --git a/sdk-tests/manual-test-pinocchio/src/derived_decompress.rs b/sdk-tests/manual-test-pinocchio/src/derived_decompress.rs new file mode 100644 index 0000000000..35b23c0dd0 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/derived_decompress.rs @@ -0,0 +1,27 @@ +//! Macro-derived decompress implementation. +//! +//! This module contains the code that would be generated by the `#[light_program]` macro. +//! With the trait-based dispatch, this module is minimal - just specifies the variant type. + +use light_account_pinocchio::process_decompress_pda_accounts_idempotent; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, sysvars::{clock::Clock, Sysvar}}; + +use crate::derived_variants::PackedLightAccountVariant; + +/// MACRO-GENERATED: Process handler - forwards to SDK function with program's variant type. +pub fn process_decompress_idempotent( + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> Result<(), ProgramError> { + let current_slot = Clock::get() + .map_err(|_| ProgramError::UnsupportedSysvar)? + .slot; + process_decompress_pda_accounts_idempotent::<_, PackedLightAccountVariant>( + accounts, + instruction_data, + crate::LIGHT_CPI_SIGNER, + &crate::ID, + current_slot, + ) + .map_err(|e| ProgramError::Custom(u32::from(e))) +} diff --git a/sdk-tests/manual-test-pinocchio/src/derived_light_config.rs b/sdk-tests/manual-test-pinocchio/src/derived_light_config.rs new file mode 100644 index 0000000000..bf37cff796 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/derived_light_config.rs @@ -0,0 +1,67 @@ +//! Config instructions using SDK functions. + +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::{process_initialize_light_config, process_update_light_config}; +use light_compressible::rent::RentConfig; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError}; + +/// Params order matches SDK's InitializeCompressionConfigAnchorData. +#[derive(BorshSerialize, BorshDeserialize, Clone)] +pub struct InitConfigParams { + pub write_top_up: u32, + pub rent_sponsor: [u8; 32], + pub compression_authority: [u8; 32], + pub rent_config: RentConfig, + pub address_space: Vec<[u8; 32]>, +} + +/// Account order matches SDK's InitializeRentFreeConfig::build(). +/// Order: [payer, config, program_data, authority, system_program] +pub fn process_initialize_config( + accounts: &[AccountInfo], + data: &[u8], +) -> Result<(), ProgramError> { + let params = InitConfigParams::try_from_slice(data) + .map_err(|_| ProgramError::BorshIoError)?; + + if accounts.len() < 5 { + return Err(ProgramError::NotEnoughAccountKeys); + } + + let fee_payer = &accounts[0]; + let config = &accounts[1]; + let _program_data = &accounts[2]; + let authority = &accounts[3]; + let system_program = &accounts[4]; + + process_initialize_light_config( + config, + authority, + ¶ms.rent_sponsor, + ¶ms.compression_authority, + params.rent_config, + params.write_top_up, + params.address_space, + 0, // config_bump + fee_payer, + system_program, + &crate::ID, + ) + .map_err(|e| ProgramError::Custom(u32::from(e))) +} + +pub fn process_update_config( + accounts: &[AccountInfo], + data: &[u8], +) -> Result<(), ProgramError> { + if accounts.len() < 2 { + return Err(ProgramError::NotEnoughAccountKeys); + } + + let authority = &accounts[0]; + let config = &accounts[1]; + + let remaining = [config.clone(), authority.clone()]; + process_update_light_config(&remaining, data, &crate::ID) + .map_err(|e| ProgramError::Custom(u32::from(e))) +} diff --git a/sdk-tests/manual-test-pinocchio/src/derived_variants.rs b/sdk-tests/manual-test-pinocchio/src/derived_variants.rs new file mode 100644 index 0000000000..6aebc1971f --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/derived_variants.rs @@ -0,0 +1,143 @@ +//! Program-wide variant enums for compress/decompress dispatch. +//! +//! This module contains the code that would be generated by the `#[light_program]` macro. + +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::{ + prepare_account_for_decompression, DecompressCtx, DecompressVariant, LightSdkTypesError, + PackedStateTreeInfo, +}; +use pinocchio::account_info::AccountInfo; + +use crate::{ + account_loader::derived_accounts::{ + PackedZeroCopyRecordSeeds, PackedZeroCopyRecordVariant, ZeroCopyRecordSeeds, + }, + all::derived_accounts::{ + AllBorshSeeds, AllZeroCopySeeds, PackedAllBorshSeeds, PackedAllBorshVariant, + PackedAllZeroCopySeeds, PackedAllZeroCopyVariant, + }, + pda::derived_accounts::{ + MinimalRecordSeeds, PackedMinimalRecordSeeds, PackedMinimalRecordVariant, + }, + MinimalRecord, PackedMinimalRecord, PackedZeroCopyRecord, ZeroCopyRecord, +}; + +// ============================================================================ +// Program-wide Variant Enums (generated by #[light_program]) +// ============================================================================ + +/// Unpacked variant enum for all account types in this program. +/// Each variant contains the full seeds + data. +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub enum LightAccountVariant { + MinimalRecord { + seeds: MinimalRecordSeeds, + data: MinimalRecord, + }, + ZeroCopyRecord { + seeds: ZeroCopyRecordSeeds, + data: ZeroCopyRecord, + }, + AllBorsh { + seeds: AllBorshSeeds, + data: MinimalRecord, + }, + AllZeroCopy { + seeds: AllZeroCopySeeds, + data: ZeroCopyRecord, + }, +} + +/// Packed variant enum for efficient serialization. +/// Does NOT wrap CompressedAccountData - that wrapper is added by the client library. +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub enum PackedLightAccountVariant { + MinimalRecord { + seeds: PackedMinimalRecordSeeds, + data: PackedMinimalRecord, + }, + ZeroCopyRecord { + seeds: PackedZeroCopyRecordSeeds, + data: PackedZeroCopyRecord, + }, + AllBorsh { + seeds: PackedAllBorshSeeds, + data: PackedMinimalRecord, + }, + AllZeroCopy { + seeds: PackedAllZeroCopySeeds, + data: PackedZeroCopyRecord, + }, +} + +// ============================================================================ +// DecompressVariant Implementation (MACRO-GENERATED) +// ============================================================================ + +/// Implementation for PackedLightAccountVariant. +/// Implements on the inner variant type to satisfy orphan rules. +impl DecompressVariant for PackedLightAccountVariant { + fn decompress( + &self, + tree_info: &PackedStateTreeInfo, + pda_account: &AccountInfo, + ctx: &mut DecompressCtx<'_>, + ) -> std::result::Result<(), LightSdkTypesError> { + let output_queue_index = ctx.output_queue_index; + match self { + PackedLightAccountVariant::MinimalRecord { seeds, data } => { + let packed_data = PackedMinimalRecordVariant { + seeds: seeds.clone(), + data: data.clone(), + }; + prepare_account_for_decompression::<4, PackedMinimalRecordVariant, AccountInfo>( + &packed_data, + tree_info, + output_queue_index, + pda_account, + ctx, + ) + } + PackedLightAccountVariant::ZeroCopyRecord { seeds, data } => { + let packed_data = PackedZeroCopyRecordVariant { + seeds: seeds.clone(), + data: data.clone(), + }; + prepare_account_for_decompression::<4, PackedZeroCopyRecordVariant, AccountInfo>( + &packed_data, + tree_info, + output_queue_index, + pda_account, + ctx, + ) + } + PackedLightAccountVariant::AllBorsh { seeds, data } => { + let packed_data = PackedAllBorshVariant { + seeds: seeds.clone(), + data: data.clone(), + }; + prepare_account_for_decompression::<3, PackedAllBorshVariant, AccountInfo>( + &packed_data, + tree_info, + output_queue_index, + pda_account, + ctx, + ) + } + PackedLightAccountVariant::AllZeroCopy { seeds, data } => { + let packed_data = PackedAllZeroCopyVariant { + seeds: seeds.clone(), + data: data.clone(), + }; + prepare_account_for_decompression::<3, PackedAllZeroCopyVariant, AccountInfo>( + &packed_data, + tree_info, + output_queue_index, + pda_account, + ctx, + ) + } + } + } +} diff --git a/sdk-tests/manual-test-pinocchio/src/lib.rs b/sdk-tests/manual-test-pinocchio/src/lib.rs new file mode 100644 index 0000000000..a9eae484c7 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/lib.rs @@ -0,0 +1,292 @@ +//! Pinocchio-based test program for compressible PDA creation. +//! +//! This is a pinocchio port of the manual-test program. +//! Same instructions, same behavior, no Anchor dependency. + +#![allow(deprecated)] + +use light_account_pinocchio::{derive_light_cpi_signer, CpiSigner, LightFinalize, LightPreInit}; +use light_macros::pubkey_array; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; + + +pub mod account_loader; +pub mod all; +pub mod ata; +pub mod derived_compress; +pub mod derived_decompress; +pub mod derived_light_config; +pub mod derived_variants; +pub mod pda; +pub mod token_account; +pub mod two_mints; + +// Re-exports for tests and other consumers +pub use account_loader::{ + PackedZeroCopyRecord, PackedZeroCopyRecordSeeds, PackedZeroCopyRecordVariant, ZeroCopyRecord, + ZeroCopyRecordSeeds, ZeroCopyRecordVariant, +}; +pub use all::{ + AllBorshSeeds, AllBorshVariant, AllZeroCopySeeds, AllZeroCopyVariant, PackedAllBorshSeeds, + PackedAllBorshVariant, PackedAllZeroCopySeeds, PackedAllZeroCopyVariant, +}; +pub use ata::accounts::*; +pub use derived_variants::{LightAccountVariant, PackedLightAccountVariant}; +pub use light_account_pinocchio::{ + AccountType, CompressAndCloseParams, DecompressIdempotentParams, DecompressVariant, + LightAccount, +}; +pub use pda::{ + MinimalRecord, MinimalRecordSeeds, MinimalRecordVariant, PackedMinimalRecord, + PackedMinimalRecordSeeds, PackedMinimalRecordVariant, +}; +pub use token_account::accounts::*; +pub use two_mints::accounts::*; + +pub const ID: Pubkey = pubkey_array!("7TWLq8Kmj1Cc3bGaEqsdNKMAiJSA7XN1JeKCN5nQeg2R"); + +pub const LIGHT_CPI_SIGNER: CpiSigner = + derive_light_cpi_signer!("7TWLq8Kmj1Cc3bGaEqsdNKMAiJSA7XN1JeKCN5nQeg2R"); + +// ============================================================================ +// Instruction Discriminators (8-byte, Anchor-compatible via sha256("global:{name}")[..8]) +// ============================================================================ +pub mod discriminators { + pub const CREATE_PDA: [u8; 8] = [220, 10, 244, 120, 183, 4, 64, 232]; + pub const CREATE_ZERO_COPY: [u8; 8] = [172, 231, 175, 212, 64, 240, 20, 209]; + pub const CREATE_DERIVED_MINTS: [u8; 8] = [91, 123, 65, 133, 194, 45, 243, 75]; + pub const CREATE_TOKEN_VAULT: [u8; 8] = [161, 29, 12, 45, 127, 88, 61, 49]; + pub const CREATE_ATA: [u8; 8] = [26, 102, 168, 62, 117, 72, 168, 17]; + pub const CREATE_ALL: [u8; 8] = [149, 49, 144, 45, 208, 155, 177, 43]; + // These match the hardcoded discriminators in light_client::interface::instructions + pub const INITIALIZE_COMPRESSION_CONFIG: [u8; 8] = [133, 228, 12, 169, 56, 76, 222, 61]; + pub const UPDATE_COMPRESSION_CONFIG: [u8; 8] = [135, 215, 243, 81, 163, 146, 33, 70]; + pub const COMPRESS_ACCOUNTS_IDEMPOTENT: [u8; 8] = [70, 236, 171, 120, 164, 93, 113, 181]; + pub const DECOMPRESS_ACCOUNTS_IDEMPOTENT: [u8; 8] = [114, 67, 61, 123, 234, 31, 1, 112]; +} + +// ============================================================================ +// Entrypoint +// ============================================================================ + +/// Strip the 4-byte Vec borsh length prefix from instruction data. +/// +/// The SDK client wraps serialized instruction data in Vec format +/// (4-byte little-endian length prefix + payload) for Anchor compatibility. +/// Anchor strips this automatically via its `Vec` parameter deserialization; +/// pinocchio programs must strip it manually. +#[inline] +fn strip_vec_wrapper(data: &[u8]) -> Result<&[u8], ProgramError> { + if data.len() < 4 { + return Err(ProgramError::InvalidInstructionData); + } + let len = u32::from_le_bytes(data[..4].try_into().unwrap()) as usize; + if data.len() < 4 + len { + return Err(ProgramError::InvalidInstructionData); + } + Ok(&data[4..4 + len]) +} + +pinocchio::entrypoint!(process_instruction); + +pub fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> Result<(), ProgramError> { + if instruction_data.len() < 8 { + return Err(ProgramError::InvalidInstructionData); + } + + let (disc, data) = instruction_data.split_at(8); + let disc: [u8; 8] = disc.try_into().unwrap(); + + match disc { + discriminators::CREATE_PDA => process_create_pda(accounts, data), + discriminators::CREATE_ZERO_COPY => process_create_zero_copy(accounts, data), + discriminators::CREATE_DERIVED_MINTS => process_create_derived_mints(accounts, data), + discriminators::CREATE_TOKEN_VAULT => process_create_token_vault(accounts, data), + discriminators::CREATE_ATA => process_create_ata(accounts, data), + discriminators::CREATE_ALL => process_create_all(accounts, data), + discriminators::INITIALIZE_COMPRESSION_CONFIG => { + derived_light_config::process_initialize_config(accounts, data) + } + discriminators::UPDATE_COMPRESSION_CONFIG => { + derived_light_config::process_update_config(accounts, data) + } + // The SDK client wraps compress/decompress instruction data in Vec format + // (4-byte length prefix) for Anchor compatibility. Anchor strips this + // automatically via its Vec parameter deserialization; pinocchio programs + // must strip it manually. + discriminators::COMPRESS_ACCOUNTS_IDEMPOTENT => { + let inner = strip_vec_wrapper(data)?; + derived_compress::process_compress_and_close(accounts, inner) + } + discriminators::DECOMPRESS_ACCOUNTS_IDEMPOTENT => { + let inner = strip_vec_wrapper(data)?; + derived_decompress::process_decompress_idempotent(accounts, inner) + } + _ => Err(ProgramError::InvalidInstructionData), + } +} + +// ============================================================================ +// Instruction Handlers +// ============================================================================ + +fn process_create_pda(accounts: &[AccountInfo], data: &[u8]) -> Result<(), ProgramError> { + use borsh::BorshDeserialize; + use pda::accounts::{CreatePda, CreatePdaParams}; + + let params = + CreatePdaParams::deserialize(&mut &data[..]).map_err(|_| ProgramError::BorshIoError)?; + + let remaining_start = CreatePda::FIXED_LEN; + let (fixed_accounts, remaining_accounts) = accounts.split_at(remaining_start); + let mut ctx = CreatePda::parse(fixed_accounts, ¶ms)?; + + let has_pre_init = ctx + .light_pre_init(remaining_accounts, ¶ms) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + // Business logic: set account data + { + let mut account_data = ctx.record.try_borrow_mut_data().map_err(|_| ProgramError::AccountBorrowFailed)?; + let record = pda::state::MinimalRecord::mut_from_account_data(&mut account_data); + record.owner = params.owner; + } + + ctx.light_finalize(remaining_accounts, ¶ms, has_pre_init) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + Ok(()) +} + +fn process_create_zero_copy(accounts: &[AccountInfo], data: &[u8]) -> Result<(), ProgramError> { + use account_loader::accounts::{CreateZeroCopy, CreateZeroCopyParams}; + use borsh::BorshDeserialize; + + let params = CreateZeroCopyParams::deserialize(&mut &data[..]) + .map_err(|_| ProgramError::BorshIoError)?; + + let remaining_start = CreateZeroCopy::FIXED_LEN; + let (fixed_accounts, remaining_accounts) = accounts.split_at(remaining_start); + let mut ctx = CreateZeroCopy::parse(fixed_accounts, ¶ms)?; + + let has_pre_init = ctx + .light_pre_init(remaining_accounts, ¶ms) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + // Business logic: set zero-copy account data + { + let mut account_data = ctx.record.try_borrow_mut_data().map_err(|_| ProgramError::AccountBorrowFailed)?; + let record_bytes = &mut account_data[8..8 + core::mem::size_of::()]; + let record: &mut account_loader::ZeroCopyRecord = bytemuck::from_bytes_mut(record_bytes); + record.owner = params.owner; + record.value = params.value; + } + + ctx.light_finalize(remaining_accounts, ¶ms, has_pre_init) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + Ok(()) +} + +fn process_create_derived_mints(accounts: &[AccountInfo], data: &[u8]) -> Result<(), ProgramError> { + use borsh::BorshDeserialize; + use two_mints::accounts::{CreateDerivedMintsAccounts, CreateDerivedMintsParams}; + + let params = CreateDerivedMintsParams::deserialize(&mut &data[..]) + .map_err(|_| ProgramError::BorshIoError)?; + + let remaining_start = CreateDerivedMintsAccounts::FIXED_LEN; + let (fixed_accounts, remaining_accounts) = accounts.split_at(remaining_start); + let mut ctx = CreateDerivedMintsAccounts::parse(fixed_accounts, ¶ms)?; + + let has_pre_init = ctx + .light_pre_init(remaining_accounts, ¶ms) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + ctx.light_finalize(remaining_accounts, ¶ms, has_pre_init) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + Ok(()) +} + +fn process_create_token_vault(accounts: &[AccountInfo], data: &[u8]) -> Result<(), ProgramError> { + use borsh::BorshDeserialize; + use token_account::accounts::{CreateTokenVaultAccounts, CreateTokenVaultParams}; + + let params = CreateTokenVaultParams::deserialize(&mut &data[..]) + .map_err(|_| ProgramError::BorshIoError)?; + + let remaining_start = CreateTokenVaultAccounts::FIXED_LEN; + let (fixed_accounts, remaining_accounts) = accounts.split_at(remaining_start); + let mut ctx = CreateTokenVaultAccounts::parse(fixed_accounts)?; + + let has_pre_init = ctx + .light_pre_init(remaining_accounts, ¶ms) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + ctx.light_finalize(remaining_accounts, ¶ms, has_pre_init) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + Ok(()) +} + +fn process_create_ata(accounts: &[AccountInfo], data: &[u8]) -> Result<(), ProgramError> { + use ata::accounts::{CreateAtaAccounts, CreateAtaParams}; + use borsh::BorshDeserialize; + + let params = + CreateAtaParams::deserialize(&mut &data[..]).map_err(|_| ProgramError::BorshIoError)?; + + let remaining_start = CreateAtaAccounts::FIXED_LEN; + let (fixed_accounts, remaining_accounts) = accounts.split_at(remaining_start); + let mut ctx = CreateAtaAccounts::parse(fixed_accounts)?; + + let has_pre_init = ctx + .light_pre_init(remaining_accounts, ¶ms) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + ctx.light_finalize(remaining_accounts, ¶ms, has_pre_init) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + Ok(()) +} + +fn process_create_all(accounts: &[AccountInfo], data: &[u8]) -> Result<(), ProgramError> { + use all::accounts::{CreateAllAccounts, CreateAllParams}; + use borsh::BorshDeserialize; + + let params = + CreateAllParams::deserialize(&mut &data[..]).map_err(|_| ProgramError::BorshIoError)?; + + let remaining_start = CreateAllAccounts::FIXED_LEN; + let (fixed_accounts, remaining_accounts) = accounts.split_at(remaining_start); + let mut ctx = CreateAllAccounts::parse(fixed_accounts, ¶ms)?; + + let has_pre_init = ctx + .light_pre_init(remaining_accounts, ¶ms) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + // Business logic: set PDA data + { + let mut borsh_data = ctx.borsh_record.try_borrow_mut_data().map_err(|_| ProgramError::AccountBorrowFailed)?; + let borsh_record = pda::state::MinimalRecord::mut_from_account_data(&mut borsh_data); + borsh_record.owner = params.owner; + } + { + let mut zc_data = ctx.zero_copy_record.try_borrow_mut_data().map_err(|_| ProgramError::AccountBorrowFailed)?; + let record_bytes = + &mut zc_data[8..8 + core::mem::size_of::()]; + let record: &mut account_loader::ZeroCopyRecord = bytemuck::from_bytes_mut(record_bytes); + record.owner = params.owner; + record.value = params.value; + } + + ctx.light_finalize(remaining_accounts, ¶ms, has_pre_init) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + Ok(()) +} diff --git a/sdk-tests/manual-test-pinocchio/src/pda/accounts.rs b/sdk-tests/manual-test-pinocchio/src/pda/accounts.rs new file mode 100644 index 0000000000..5aeb474e46 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/pda/accounts.rs @@ -0,0 +1,91 @@ +//! Accounts module for single-pda-test (pinocchio version). + +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::CreateAccountsProof; +use pinocchio::{ + account_info::AccountInfo, + instruction::{Seed, Signer}, + program_error::ProgramError, + sysvars::Sysvar, +}; + +use super::state::MinimalRecord; + +#[derive(BorshSerialize, BorshDeserialize, Clone)] +pub struct CreatePdaParams { + pub create_accounts_proof: CreateAccountsProof, + pub owner: [u8; 32], + pub nonce: u64, +} + +/// Minimal accounts struct for testing single PDA creation. +pub struct CreatePda<'a> { + pub fee_payer: &'a AccountInfo, + pub compression_config: &'a AccountInfo, + pub record: &'a AccountInfo, + pub system_program: &'a AccountInfo, +} + +impl<'a> CreatePda<'a> { + pub const FIXED_LEN: usize = 4; + + pub fn parse( + accounts: &'a [AccountInfo], + params: &CreatePdaParams, + ) -> Result { + let fee_payer = &accounts[0]; + let compression_config = &accounts[1]; + let record = &accounts[2]; + let system_program = &accounts[3]; + + if !fee_payer.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + // Verify and create the PDA account via system program CPI + let space = 8 + MinimalRecord::INIT_SPACE; + let nonce_bytes = params.nonce.to_le_bytes(); + let seeds: &[&[u8]] = &[b"minimal_record", ¶ms.owner, &nonce_bytes]; + let (expected_pda, bump) = pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if record.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + + let rent = pinocchio::sysvars::rent::Rent::get() + .map_err(|_| ProgramError::UnsupportedSysvar)?; + let lamports = rent.minimum_balance(space); + + let bump_bytes = [bump]; + let seed_array = [ + Seed::from(b"minimal_record" as &[u8]), + Seed::from(params.owner.as_ref()), + Seed::from(nonce_bytes.as_ref()), + Seed::from(bump_bytes.as_ref()), + ]; + let signer = Signer::from(&seed_array); + pinocchio_system::instructions::CreateAccount { + from: fee_payer, + to: record, + lamports, + space: space as u64, + owner: &crate::ID, + } + .invoke_signed(&[signer])?; + + // Write LIGHT_DISCRIMINATOR to first 8 bytes + { + use light_account_pinocchio::LightDiscriminator; + let mut data = record + .try_borrow_mut_data() + .map_err(|_| ProgramError::AccountBorrowFailed)?; + data[..8].copy_from_slice(&MinimalRecord::LIGHT_DISCRIMINATOR); + } + + Ok(Self { + fee_payer, + compression_config, + record, + system_program, + }) + } +} diff --git a/sdk-tests/manual-test-pinocchio/src/pda/derived_accounts.rs b/sdk-tests/manual-test-pinocchio/src/pda/derived_accounts.rs new file mode 100644 index 0000000000..bde5d3d692 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/pda/derived_accounts.rs @@ -0,0 +1,378 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::{ + light_account_checks::{self, packed_accounts::ProgramPackedAccounts}, + prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, + CpiContextWriteAccounts, InvokeLightSystemProgram, LightAccount, LightAccountVariantTrait, + LightFinalize, LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, + PackedLightAccountVariantTrait, +}; +use light_compressed_account::instruction_data::{ + cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, +}; +use pinocchio::account_info::AccountInfo; + +use super::{ + accounts::{CreatePda, CreatePdaParams}, + derived_state::PackedMinimalRecord, + state::MinimalRecord, +}; + +// ============================================================================ +// Compile-time Size Validation (800-byte limit for compressed accounts) +// ============================================================================ + +const _: () = { + const COMPRESSED_SIZE: usize = 8 + MinimalRecord::INIT_SPACE; + assert!( + COMPRESSED_SIZE <= 800, + "Compressed account 'MinimalRecord' exceeds 800-byte compressible account size limit" + ); +}; + +// ============================================================================ +// Manual LightPreInit Implementation +// ============================================================================ + +impl LightPreInit for CreatePda<'_> { + fn light_pre_init( + &mut self, + remaining_accounts: &[AccountInfo], + params: &CreatePdaParams, + ) -> std::result::Result { + let inner = || -> std::result::Result { + use light_account_pinocchio::{LightAccount, LightConfig}; + use pinocchio::sysvars::{clock::Clock, Sysvar}; + + // 1. Build CPI accounts (slice remaining_accounts at system_accounts_offset) + let system_accounts_offset = + params.create_accounts_proof.system_accounts_offset as usize; + if remaining_accounts.len() < system_accounts_offset { + return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); + } + let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); + let cpi_accounts = CpiAccounts::new_with_config( + self.fee_payer, + &remaining_accounts[system_accounts_offset..], + config, + ); + + // 2. Get address tree pubkey from packed tree info + let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; + let address_tree_pubkey = address_tree_info + .get_tree_pubkey(&cpi_accounts) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + let output_tree_index = params.create_accounts_proof.output_state_tree_index; + let current_account_index: u8 = 0; + // Is true if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. + const WITH_CPI_CONTEXT: bool = false; + + const NUM_LIGHT_PDAS: usize = 1; + + // 6. Set compression_info from config + let light_config = + LightConfig::load_checked(self.compression_config, &crate::ID) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + let current_slot = Clock::get() + .map_err(|_| LightSdkTypesError::InvalidInstructionData)? + .slot; + // Dynamic derived light pda specific. Only exists if NUM_LIGHT_PDAS > 0 + // ===================================================================== + { + // Is first if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. + let cpi_context = if WITH_CPI_CONTEXT { + CompressedCpiContext::first() + } else { + CompressedCpiContext::default() + }; + let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); + let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); + // 3. Prepare compressed account using helper function + // Dynamic code 0-N variants depending on the accounts struct + // ===================================================================== + let record_key = *self.record.key(); + prepare_compressed_account_on_init( + &record_key, + &address_tree_pubkey, + address_tree_info, + output_tree_index, + current_account_index, + &crate::ID, + &mut new_address_params, + &mut account_infos, + )?; + // Set compression_info on the Borsh record via mut_from_account_data + { + let mut account_data = self + .record + .try_borrow_mut_data() + .map_err(|_| LightSdkTypesError::Borsh)?; + let record = MinimalRecord::mut_from_account_data(&mut account_data); + record.set_decompressed(&light_config, current_slot); + } + // ===================================================================== + + // current_account_index += 1; + // For multiple accounts, repeat the pattern: + // let prepared2 = prepare_compressed_account_on_init(..., current_account_index, ...)?; + // current_account_index += 1; + + // 4. Build instruction data manually (no builder pattern) + let instruction_data = InstructionDataInvokeCpiWithAccountInfo { + mode: 1, // V2 mode + bump: crate::LIGHT_CPI_SIGNER.bump, + invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), + compress_or_decompress_lamports: 0, + is_compress: false, + with_cpi_context: WITH_CPI_CONTEXT, + with_transaction_hash: false, + cpi_context, + proof: params.create_accounts_proof.proof.0, + new_address_params, + account_infos, + read_only_addresses: vec![], + read_only_accounts: vec![], + }; + if !WITH_CPI_CONTEXT { + // 5. Invoke Light System Program CPI + instruction_data + .invoke(cpi_accounts)?; + } else { + // For flows that combine light mints with light PDAs, write to CPI context first. + // The authority and cpi_context accounts must be provided in remaining_accounts. + let cpi_context_accounts = CpiContextWriteAccounts { + fee_payer: cpi_accounts.fee_payer(), + authority: cpi_accounts.authority()?, + cpi_context: cpi_accounts.cpi_context()?, + cpi_signer: crate::LIGHT_CPI_SIGNER, + }; + instruction_data + .invoke_write_to_cpi_context_first(cpi_context_accounts)?; + } + } + // ===================================================================== + Ok(false) // No mints, so no CPI context write + }; + inner() + } +} + +// ============================================================================ +// Manual LightFinalize Implementation (no-op for PDA-only flow) +// ============================================================================ + +impl LightFinalize for CreatePda<'_> { + fn light_finalize( + &mut self, + _remaining_accounts: &[AccountInfo], + _params: &CreatePdaParams, + _has_pre_init: bool, + ) -> std::result::Result<(), LightSdkTypesError> { + // No-op for PDA-only flow - compression CPI already executed in light_pre_init + Ok(()) + } +} + +// ============================================================================ +// Seeds Structs +// Extracted from: seeds = [b"minimal_record", params.owner.as_ref()] +// ============================================================================ + +/// Seeds for MinimalRecord PDA. +/// Contains the dynamic seed values (static prefix "minimal_record" is in seed_refs). +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct MinimalRecordSeeds { + pub owner: [u8; 32], + pub nonce: u64, +} + +/// Packed seeds with u8 indices instead of Pubkeys. +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct PackedMinimalRecordSeeds { + pub owner_idx: u8, + pub nonce_bytes: [u8; 8], + pub bump: u8, +} + +// ============================================================================ +// Variant Structs +// ============================================================================ + +/// Full variant combining seeds + data. +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct MinimalRecordVariant { + pub seeds: MinimalRecordSeeds, + pub data: MinimalRecord, +} + +/// Packed variant for efficient serialization. +/// Contains packed seeds and data with u8 indices for Pubkey deduplication. +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct PackedMinimalRecordVariant { + pub seeds: PackedMinimalRecordSeeds, + pub data: PackedMinimalRecord, +} + +// ============================================================================ +// LightAccountVariant Implementation +// ============================================================================ + +impl LightAccountVariantTrait<4> for MinimalRecordVariant { + const PROGRAM_ID: [u8; 32] = crate::ID; + + type Seeds = MinimalRecordSeeds; + type Data = MinimalRecord; + type Packed = PackedMinimalRecordVariant; + + fn data(&self) -> &Self::Data { + &self.data + } + + /// Get seed values as owned byte vectors for PDA derivation. + /// Generated from: seeds = [b"minimal_record", params.owner.as_ref(), ¶ms.nonce.to_le_bytes()] + fn seed_vec(&self) -> Vec> { + vec![ + b"minimal_record".to_vec(), + self.seeds.owner.to_vec(), + self.seeds.nonce.to_le_bytes().to_vec(), + ] + } + + /// Get seed references with bump for CPI signing. + /// Note: For unpacked variants with computed bytes (like nonce.to_le_bytes()), + /// we cannot return references to temporaries. Use the packed variant instead. + fn seed_refs_with_bump<'a>(&'a self, _bump_storage: &'a [u8; 1]) -> [&'a [u8]; 4] { + // The packed variant stores nonce_bytes as [u8; 8], so it can return references. + // This unpacked variant computes nonce.to_le_bytes() which creates a temporary. + panic!("Use PackedMinimalRecordVariant::seed_refs_with_bump instead") + } +} + +// ============================================================================ +// PackedLightAccountVariant Implementation +// ============================================================================ + +impl PackedLightAccountVariantTrait<4> for PackedMinimalRecordVariant { + type Unpacked = MinimalRecordVariant; + + const ACCOUNT_TYPE: light_account_pinocchio::AccountType = + ::ACCOUNT_TYPE; + + fn bump(&self) -> u8 { + self.seeds.bump + } + + fn unpack( + &self, + accounts: &[AI], + ) -> std::result::Result { + let owner = accounts + .get(self.seeds.owner_idx as usize) + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)?; + + // Build ProgramPackedAccounts for LightAccount::unpack + let packed_accounts = ProgramPackedAccounts { accounts }; + let data = MinimalRecord::unpack(&self.data, &packed_accounts) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + + Ok(MinimalRecordVariant { + seeds: MinimalRecordSeeds { + owner: owner.key(), + nonce: u64::from_le_bytes(self.seeds.nonce_bytes), + }, + data, + }) + } + + fn seed_refs_with_bump<'a, AI: light_account_checks::AccountInfoTrait>( + &'a self, + _accounts: &'a [AI], + _bump_storage: &'a [u8; 1], + ) -> std::result::Result<[&'a [u8]; 4], LightSdkTypesError> { + // PDA variants use seed_vec() in the decompression path, not seed_refs_with_bump. + // Returning a reference to the account key requires a key_ref() method on + // AccountInfoTrait, which is not yet available. Since this method is only + // called for token account variants, PDA variants return an error. + Err(LightSdkTypesError::InvalidSeeds) + } + + fn into_in_token_data( + &self, + _tree_info: &light_account_pinocchio::PackedStateTreeInfo, + _output_queue_index: u8, + ) -> std::result::Result< + light_token_interface::instructions::transfer2::MultiInputTokenDataWithContext, + LightSdkTypesError, + > { + Err(LightSdkTypesError::InvalidInstructionData) + } + + fn into_in_tlv( + &self, + ) -> std::result::Result< + Option>, + LightSdkTypesError, + > { + Ok(None) + } +} + +// ============================================================================ +// IntoVariant Implementation for Seeds (client-side API) +// ============================================================================ + +/// Implement IntoVariant to allow building variant from seeds + compressed data. +/// This enables the high-level `create_load_instructions` API. +#[cfg(not(target_os = "solana"))] +impl light_account_pinocchio::IntoVariant for MinimalRecordSeeds { + fn into_variant( + self, + data: &[u8], + ) -> std::result::Result { + // Deserialize the compressed data (which includes compression_info) + let record: MinimalRecord = BorshDeserialize::deserialize(&mut &data[..]) + .map_err(|_| LightSdkTypesError::Borsh)?; + + // Verify the owner in data matches the seed + if record.owner != self.owner { + return Err(LightSdkTypesError::InvalidSeeds); + } + + Ok(MinimalRecordVariant { + seeds: self, + data: record, + }) + } +} + +// ============================================================================ +// Pack Implementation for MinimalRecordVariant (client-side API) +// ============================================================================ + +/// Implement Pack trait to allow MinimalRecordVariant to be used with `create_load_instructions`. +/// Transforms the variant into PackedLightAccountVariant for efficient serialization. +#[cfg(not(target_os = "solana"))] +impl light_account_pinocchio::Pack for MinimalRecordVariant { + type Packed = crate::derived_variants::PackedLightAccountVariant; + + fn pack( + &self, + accounts: &mut light_account_pinocchio::PackedAccounts, + ) -> std::result::Result { + use light_account_pinocchio::LightAccountVariantTrait; + let (_, bump) = self.derive_pda::(); + let packed_data = self + .data + .pack(accounts) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + Ok( + crate::derived_variants::PackedLightAccountVariant::MinimalRecord { + seeds: PackedMinimalRecordSeeds { + owner_idx: accounts.insert_or_get(solana_pubkey::Pubkey::from(self.seeds.owner)), + nonce_bytes: self.seeds.nonce.to_le_bytes(), + bump, + }, + data: packed_data, + }, + ) + } +} diff --git a/sdk-tests/manual-test-pinocchio/src/pda/derived_state.rs b/sdk-tests/manual-test-pinocchio/src/pda/derived_state.rs new file mode 100644 index 0000000000..a58424d985 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/pda/derived_state.rs @@ -0,0 +1,96 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::{ + light_account_checks::{ + packed_accounts::ProgramPackedAccounts, AccountInfoTrait, AccountMetaTrait, + }, + AccountType, CompressionInfo, HasCompressionInfo, LightAccount, LightConfig, + LightSdkTypesError, +}; + +use super::state::MinimalRecord; + +// ============================================================================ +// PackedMinimalRecord (compression_info excluded per implementation_details.md) +// ============================================================================ + +/// Packed version of MinimalRecord for efficient transmission. +/// compression_info is excluded - it's cut off during pack. +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct PackedMinimalRecord { + /// Index into remaining_accounts instead of full Pubkey + pub owner: u8, +} + +// ============================================================================ +// LightAccount Implementation for MinimalRecord +// ============================================================================ + +impl LightAccount for MinimalRecord { + const ACCOUNT_TYPE: AccountType = AccountType::Pda; + + type Packed = PackedMinimalRecord; + + // CompressionInfo (24) + owner (32) = 56 bytes + const INIT_SPACE: usize = core::mem::size_of::() + 32; + + fn compression_info(&self) -> &CompressionInfo { + &self.compression_info + } + + fn compression_info_mut(&mut self) -> &mut CompressionInfo { + &mut self.compression_info + } + + fn set_decompressed(&mut self, config: &LightConfig, current_slot: u64) { + self.compression_info = CompressionInfo::new_from_config(config, current_slot); + } + + #[cfg(not(target_os = "solana"))] + fn pack( + &self, + accounts: &mut light_account_pinocchio::interface::instruction::PackedAccounts, + ) -> std::result::Result { + // compression_info excluded from packed struct + Ok(PackedMinimalRecord { + owner: accounts.insert_or_get(AM::pubkey_from_bytes(self.owner)), + }) + } + + fn unpack( + packed: &Self::Packed, + accounts: &ProgramPackedAccounts, + ) -> std::result::Result { + // Use get_u8 with a descriptive name for better error messages + let owner_account = accounts + .get_u8(packed.owner, "MinimalRecord: owner") + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + + // Set compression_info to compressed() for hash verification at decompress + // key() returns [u8; 32] directly - no Pubkey::from() needed + Ok(MinimalRecord { + compression_info: CompressionInfo::compressed(), + owner: owner_account.key(), + }) + } +} + +impl HasCompressionInfo for MinimalRecord { + fn compression_info(&self) -> std::result::Result<&CompressionInfo, LightSdkTypesError> { + Ok(&self.compression_info) + } + + fn compression_info_mut( + &mut self, + ) -> std::result::Result<&mut CompressionInfo, LightSdkTypesError> { + Ok(&mut self.compression_info) + } + + fn compression_info_mut_opt(&mut self) -> &mut Option { + panic!("compression_info_mut_opt not supported for LightAccount types (use compression_info_mut instead)") + } + + fn set_compression_info_none(&mut self) -> std::result::Result<(), LightSdkTypesError> { + self.compression_info = CompressionInfo::compressed(); + Ok(()) + } +} diff --git a/sdk-tests/manual-test-pinocchio/src/pda/mod.rs b/sdk-tests/manual-test-pinocchio/src/pda/mod.rs new file mode 100644 index 0000000000..30965d2e2c --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/pda/mod.rs @@ -0,0 +1,13 @@ +//! PDA state and accounts for manual Light Protocol implementation. + +pub mod accounts; +pub mod derived_accounts; +pub mod derived_state; +pub mod state; + +pub use accounts::*; +pub use derived_accounts::{ + MinimalRecordSeeds, MinimalRecordVariant, PackedMinimalRecordSeeds, PackedMinimalRecordVariant, +}; +pub use derived_state::*; +pub use state::*; diff --git a/sdk-tests/manual-test-pinocchio/src/pda/state.rs b/sdk-tests/manual-test-pinocchio/src/pda/state.rs new file mode 100644 index 0000000000..2b88a96fa1 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/pda/state.rs @@ -0,0 +1,27 @@ +//! State module for single-pda-test. + +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::{CompressionInfo, LightDiscriminator, LightHasherSha}; + +/// Minimal record struct for testing PDA creation. +#[derive( + Default, Debug, Clone, BorshSerialize, BorshDeserialize, LightDiscriminator, LightHasherSha, +)] +#[repr(C)] +pub struct MinimalRecord { + pub compression_info: CompressionInfo, + pub owner: [u8; 32], +} + +impl MinimalRecord { + pub const INIT_SPACE: usize = core::mem::size_of::() + 32; + + /// Get a mutable reference to a MinimalRecord from account data (after 8-byte discriminator). + pub fn mut_from_account_data(data: &mut [u8]) -> &mut Self { + let start = 8; // skip discriminator + let end = start + Self::INIT_SPACE; + // Safety: MinimalRecord is just bytes (CompressionInfo is 24 bytes + [u8;32]) + // We need to do manual byte access since it's Borsh-serialized + unsafe { &mut *(data[start..end].as_mut_ptr() as *mut Self) } + } +} diff --git a/sdk-tests/manual-test-pinocchio/src/token_account/accounts.rs b/sdk-tests/manual-test-pinocchio/src/token_account/accounts.rs new file mode 100644 index 0000000000..1822674b88 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/token_account/accounts.rs @@ -0,0 +1,69 @@ +//! Accounts struct for create_token_vault instruction (pinocchio version). + +use borsh::{BorshDeserialize, BorshSerialize}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError}; + +/// Seed constant for token vault PDA +pub const TOKEN_VAULT_SEED: &[u8] = b"vault"; + +/// Minimal params for token vault creation. +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] +pub struct CreateTokenVaultParams { + pub vault_bump: u8, +} + +/// Accounts struct for creating a PDA token vault. +/// +/// The token vault is created via CPI to light-token program, not system program. +/// parse() only validates the PDA derivation. +pub struct CreateTokenVaultAccounts<'a> { + pub payer: &'a AccountInfo, + pub mint: &'a AccountInfo, + pub vault_owner: &'a AccountInfo, + pub token_vault: &'a AccountInfo, + pub compressible_config: &'a AccountInfo, + pub rent_sponsor: &'a AccountInfo, + pub light_token_program: &'a AccountInfo, + pub system_program: &'a AccountInfo, +} + +impl<'a> CreateTokenVaultAccounts<'a> { + pub const FIXED_LEN: usize = 8; + + pub fn parse(accounts: &'a [AccountInfo]) -> Result { + let payer = &accounts[0]; + let mint = &accounts[1]; + let vault_owner = &accounts[2]; + let token_vault = &accounts[3]; + let compressible_config = &accounts[4]; + let rent_sponsor = &accounts[5]; + let light_token_program = &accounts[6]; + let system_program = &accounts[7]; + + if !payer.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + // Validate token_vault PDA + { + let mint_key = mint.key(); + let seeds: &[&[u8]] = &[TOKEN_VAULT_SEED, mint_key]; + let (expected_pda, _bump) = + pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if token_vault.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + } + + Ok(Self { + payer, + mint, + vault_owner, + token_vault, + compressible_config, + rent_sponsor, + light_token_program, + system_program, + }) + } +} diff --git a/sdk-tests/manual-test-pinocchio/src/token_account/derived.rs b/sdk-tests/manual-test-pinocchio/src/token_account/derived.rs new file mode 100644 index 0000000000..5765e66fba --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/token_account/derived.rs @@ -0,0 +1,118 @@ +//! Derived code - what the macro would generate for token accounts. + +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::{ + light_account_checks::{self}, + CreateTokenAccountCpi, LightFinalize, LightPreInit, LightSdkTypesError, Unpack, +}; +#[cfg(not(target_os = "solana"))] +use light_account_pinocchio::Pack; +use pinocchio::account_info::AccountInfo; + +use super::accounts::{CreateTokenVaultAccounts, CreateTokenVaultParams, TOKEN_VAULT_SEED}; + +// ============================================================================ +// LightPreInit Implementation - Creates token account at START of instruction +// ============================================================================ + +impl LightPreInit for CreateTokenVaultAccounts<'_> { + fn light_pre_init( + &mut self, + _remaining_accounts: &[AccountInfo], + params: &CreateTokenVaultParams, + ) -> std::result::Result { + let inner = || -> std::result::Result { + // Build PDA seeds: [TOKEN_VAULT_SEED, mint.key(), &[bump]] + let mint_key = *self.mint.key(); + let vault_seeds: &[&[u8]] = + &[TOKEN_VAULT_SEED, mint_key.as_ref(), &[params.vault_bump]]; + + // Create token account via CPI with rent-free mode + // In pinocchio, accounts are already &AccountInfo, no .to_account_info() needed + CreateTokenAccountCpi { + payer: self.payer, + account: self.token_vault, + mint: self.mint, + owner: *self.vault_owner.key(), + } + .rent_free( + self.compressible_config, + self.rent_sponsor, + self.system_program, + &crate::ID, + ) + .invoke_signed(vault_seeds)?; + + // Token accounts don't use CPI context, return false + Ok(false) + }; + inner() + } +} + +// ============================================================================ +// LightFinalize Implementation - No-op for token account only flow +// ============================================================================ + +impl LightFinalize for CreateTokenVaultAccounts<'_> { + fn light_finalize( + &mut self, + _remaining_accounts: &[AccountInfo], + _params: &CreateTokenVaultParams, + _has_pre_init: bool, + ) -> std::result::Result<(), LightSdkTypesError> { + Ok(()) + } +} + +// ============================================================================ +// Token Vault Seeds (for Pack/Unpack) +// ============================================================================ + +/// Token vault seeds for PDA derivation (client-side). +#[allow(dead_code)] +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct TokenVaultSeeds { + pub mint: [u8; 32], +} + +/// Packed token vault seeds with u8 indices. +#[allow(dead_code)] +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct PackedTokenVaultSeeds { + pub mint_idx: u8, + pub bump: u8, +} + +// ============================================================================ +// Pack/Unpack Implementations +// ============================================================================ + +#[cfg(not(target_os = "solana"))] +impl Pack for TokenVaultSeeds { + type Packed = PackedTokenVaultSeeds; + fn pack( + &self, + remaining_accounts: &mut light_account_pinocchio::PackedAccounts, + ) -> std::result::Result { + Ok(PackedTokenVaultSeeds { + mint_idx: remaining_accounts.insert_or_get(solana_pubkey::Pubkey::from(self.mint)), + bump: 0, + }) + } +} + +impl Unpack for PackedTokenVaultSeeds { + type Unpacked = TokenVaultSeeds; + + fn unpack( + &self, + remaining_accounts: &[AI], + ) -> std::result::Result { + let mint = remaining_accounts + .get(self.mint_idx as usize) + .ok_or(LightSdkTypesError::NotEnoughAccountKeys)? + .key(); + Ok(TokenVaultSeeds { mint }) + } +} diff --git a/sdk-tests/manual-test-pinocchio/src/token_account/mod.rs b/sdk-tests/manual-test-pinocchio/src/token_account/mod.rs new file mode 100644 index 0000000000..28e7ac085e --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/token_account/mod.rs @@ -0,0 +1,6 @@ +//! Token account creation - manual implementation of macro-generated code. + +pub mod accounts; +mod derived; + +pub use accounts::*; diff --git a/sdk-tests/manual-test-pinocchio/src/two_mints/accounts.rs b/sdk-tests/manual-test-pinocchio/src/two_mints/accounts.rs new file mode 100644 index 0000000000..4b4fbe2df8 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/two_mints/accounts.rs @@ -0,0 +1,109 @@ +//! Accounts struct for create_derived_mints instruction (pinocchio version). + +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::CreateAccountsProof; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError}; + +/// Seed constants +pub const MINT_SIGNER_0_SEED: &[u8] = b"mint_signer_0"; +pub const MINT_SIGNER_1_SEED: &[u8] = b"mint_signer_1"; + +/// Minimal params - matches macro pattern. +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] +pub struct CreateDerivedMintsParams { + pub create_accounts_proof: CreateAccountsProof, + pub mint_signer_0_bump: u8, + pub mint_signer_1_bump: u8, +} + +/// Accounts struct - matches macro pattern with mint signers as PDAs. +pub struct CreateDerivedMintsAccounts<'a> { + pub payer: &'a AccountInfo, + pub authority: &'a AccountInfo, + pub mint_signer_0: &'a AccountInfo, + pub mint_signer_1: &'a AccountInfo, + pub mint_0: &'a AccountInfo, + pub mint_1: &'a AccountInfo, + pub compressible_config: &'a AccountInfo, + pub rent_sponsor: &'a AccountInfo, + pub light_token_program: &'a AccountInfo, + pub cpi_authority: &'a AccountInfo, + pub system_program: &'a AccountInfo, + /// Slice view for mint_signer accounts (for invoke_create_mints) + pub mint_signers_slice: &'a [AccountInfo], + /// Slice view for mint accounts (for invoke_create_mints) + pub mints_slice: &'a [AccountInfo], +} + +impl<'a> CreateDerivedMintsAccounts<'a> { + pub const FIXED_LEN: usize = 11; + + pub fn parse( + accounts: &'a [AccountInfo], + params: &CreateDerivedMintsParams, + ) -> Result { + let payer = &accounts[0]; + let authority = &accounts[1]; + let mint_signer_0 = &accounts[2]; + let mint_signer_1 = &accounts[3]; + let mint_0 = &accounts[4]; + let mint_1 = &accounts[5]; + let compressible_config = &accounts[6]; + let rent_sponsor = &accounts[7]; + let light_token_program = &accounts[8]; + let cpi_authority = &accounts[9]; + let system_program = &accounts[10]; + + // Validate signers + if !payer.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + if !authority.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + // Validate mint_signer_0 PDA + { + let authority_key = authority.key(); + let seeds: &[&[u8]] = &[MINT_SIGNER_0_SEED, authority_key]; + let (expected_pda, expected_bump) = + pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if mint_signer_0.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + if expected_bump != params.mint_signer_0_bump { + return Err(ProgramError::InvalidSeeds); + } + } + + // Validate mint_signer_1 PDA + { + let authority_key = authority.key(); + let seeds: &[&[u8]] = &[MINT_SIGNER_1_SEED, authority_key]; + let (expected_pda, expected_bump) = + pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if mint_signer_1.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + if expected_bump != params.mint_signer_1_bump { + return Err(ProgramError::InvalidSeeds); + } + } + + Ok(Self { + payer, + authority, + mint_signer_0, + mint_signer_1, + mint_0, + mint_1, + compressible_config, + rent_sponsor, + light_token_program, + cpi_authority, + system_program, + mint_signers_slice: &accounts[2..4], + mints_slice: &accounts[4..6], + }) + } +} diff --git a/sdk-tests/manual-test-pinocchio/src/two_mints/derived.rs b/sdk-tests/manual-test-pinocchio/src/two_mints/derived.rs new file mode 100644 index 0000000000..5f092e8397 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/two_mints/derived.rs @@ -0,0 +1,199 @@ +//! Derived code - what the macro would generate. +//! Contains LightPreInit/LightFinalize trait implementations. + +use light_account_pinocchio::{ + invoke_create_mints, get_output_queue_next_index, CreateMintsInfraAccounts, + CreateMintsParams as SdkCreateMintsParams, SingleMintParams, + derive_mint_compressed_address, find_mint_address, + CpiAccounts, CpiAccountsConfig, LightFinalize, LightPreInit, LightSdkTypesError, + PackedAddressTreeInfoExt, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, +}; +use pinocchio::account_info::AccountInfo; + +use super::accounts::{ + CreateDerivedMintsAccounts, CreateDerivedMintsParams, MINT_SIGNER_0_SEED, MINT_SIGNER_1_SEED, +}; + +// ============================================================================ +// LightPreInit Implementation - Creates mints at START of instruction +// ============================================================================ + +impl LightPreInit for CreateDerivedMintsAccounts<'_> { + fn light_pre_init( + &mut self, + remaining_accounts: &[AccountInfo], + params: &CreateDerivedMintsParams, + ) -> std::result::Result { + let inner = || -> std::result::Result { + + // ==================================================================== + // STATIC BOILERPLATE (same across all LightPreInit implementations) + // ==================================================================== + + // 1. Build CPI accounts (slice remaining_accounts at system_accounts_offset) + let system_accounts_offset = + params.create_accounts_proof.system_accounts_offset as usize; + if remaining_accounts.len() < system_accounts_offset { + return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); + } + let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); + let cpi_accounts = CpiAccounts::new_with_config( + self.payer, + &remaining_accounts[system_accounts_offset..], + config, + ); + + // 2. Get address tree pubkey from packed tree info + let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; + let address_tree_pubkey = address_tree_info + .get_tree_pubkey(&cpi_accounts) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + + // Constants for this instruction (mirrors macro-generated code) + const NUM_LIGHT_MINTS: usize = 2; + const NUM_LIGHT_PDAS: usize = 0; // Set to actual PDA count when combining PDAs + mints + #[allow(clippy::absurd_extreme_comparisons)] + const WITH_CPI_CONTEXT: bool = NUM_LIGHT_PDAS > 0 && NUM_LIGHT_MINTS > 0; // true if combining mints + PDAs + + // ==================================================================== + // DYNAMIC CODE (specific to this accounts struct) + // ==================================================================== + { + // In pinocchio, .key() returns &[u8; 32], deref to get [u8; 32] + let authority = *self.authority.key(); + + // Get mint signer pubkeys from accounts + let mint_signer_0 = *self.mint_signer_0.key(); + let mint_signer_1 = *self.mint_signer_1.key(); + + // Derive mint PDAs (light-token derives mint PDA from mint_signer) + // In pinocchio, keys are already [u8; 32], no .to_bytes() needed + let (mint_0_pda, mint_0_bump) = find_mint_address(&mint_signer_0); + let (mint_1_pda, mint_1_bump) = find_mint_address(&mint_signer_1); + + // Derive compression addresses (from mint_signer + address_tree) + // address_tree_pubkey is already [u8; 32] from get_tree_pubkey + let compression_address_0 = derive_mint_compressed_address( + &mint_signer_0, + &address_tree_pubkey, + ); + let compression_address_1 = derive_mint_compressed_address( + &mint_signer_1, + &address_tree_pubkey, + ); + + // Build mint signer seeds for CPI (mint::seeds + bump) + let mint_signer_0_seeds: &[&[u8]] = &[ + MINT_SIGNER_0_SEED, + authority.as_ref(), + &[params.mint_signer_0_bump], + ]; + let mint_signer_1_seeds: &[&[u8]] = &[ + MINT_SIGNER_1_SEED, + authority.as_ref(), + &[params.mint_signer_1_bump], + ]; + + // Fixed-size array with values from accounts/attributes + let sdk_mints: [SingleMintParams<'_>; NUM_LIGHT_MINTS] = [ + SingleMintParams { + decimals: 6, // mint::decimals = 6 + address_merkle_tree_root_index: address_tree_info.root_index, + mint_authority: authority, + compression_address: compression_address_0, + mint: mint_0_pda, + bump: mint_0_bump, + freeze_authority: None, + mint_seed_pubkey: mint_signer_0, + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_0_seeds), + token_metadata: None, + }, + SingleMintParams { + decimals: 9, // mint::decimals = 9 + address_merkle_tree_root_index: address_tree_info.root_index, + mint_authority: authority, + compression_address: compression_address_1, + mint: mint_1_pda, + bump: mint_1_bump, + freeze_authority: None, + mint_seed_pubkey: mint_signer_1, + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_1_seeds), + token_metadata: None, + }, + ]; + + // ==================================================================== + // INVOKE invoke_create_mints + // ==================================================================== + + // Get state_tree_index (required for decompress discriminator validation) + let state_tree_index = params + .create_accounts_proof + .state_tree_index + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + + let proof = params + .create_accounts_proof + .proof + .0 + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + + // Read base_leaf_index from output queue (required for N > 1) + let output_queue_index = params.create_accounts_proof.output_state_tree_index; + let output_queue = cpi_accounts + .get_tree_account_info(output_queue_index as usize)?; + let base_leaf_index = get_output_queue_next_index(output_queue)?; + + let sdk_params = SdkCreateMintsParams { + mints: &sdk_mints, + proof, + rent_payment: DEFAULT_RENT_PAYMENT, + write_top_up: DEFAULT_WRITE_TOP_UP, + cpi_context_offset: NUM_LIGHT_PDAS as u8, + output_queue_index, + address_tree_index: address_tree_info.address_merkle_tree_pubkey_index, + state_tree_index, + base_leaf_index, + }; + + // Build infra accounts from Accounts struct + // In pinocchio, accounts are already &AccountInfo, no .to_account_info() needed + let infra = CreateMintsInfraAccounts { + fee_payer: self.payer, + compressible_config: self.compressible_config, + rent_sponsor: self.rent_sponsor, + cpi_authority: self.cpi_authority, + }; + + invoke_create_mints( + self.mint_signers_slice, + self.mints_slice, + sdk_params, + infra, + &cpi_accounts, + ) + .map_err(|_| LightSdkTypesError::CpiFailed)?; + } + Ok(WITH_CPI_CONTEXT) // false = mint-only, no CPI context write + }; + inner() + } +} + +// ============================================================================ +// LightFinalize Implementation - No-op for mint-only flow +// ============================================================================ + +impl LightFinalize for CreateDerivedMintsAccounts<'_> { + fn light_finalize( + &mut self, + _remaining_accounts: &[AccountInfo], + _params: &CreateDerivedMintsParams, + _has_pre_init: bool, + ) -> std::result::Result<(), LightSdkTypesError> { + // No-op for mint-only flow - create_mints already executed in light_pre_init + Ok(()) + } +} diff --git a/sdk-tests/manual-test-pinocchio/src/two_mints/mod.rs b/sdk-tests/manual-test-pinocchio/src/two_mints/mod.rs new file mode 100644 index 0000000000..2876d485c8 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/src/two_mints/mod.rs @@ -0,0 +1,6 @@ +//! Two mints instruction - creates two compressed mints using derived PDAs. + +pub mod accounts; +mod derived; + +pub use accounts::*; diff --git a/sdk-tests/manual-test-pinocchio/tests/account_loader.rs b/sdk-tests/manual-test-pinocchio/tests/account_loader.rs new file mode 100644 index 0000000000..2c39539ced --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/tests/account_loader.rs @@ -0,0 +1,191 @@ +//! Integration test for zero-copy AccountLoader support. +//! +//! Tests the full lifecycle: create -> compress -> decompress +//! for zero-copy accounts (ZeroCopyRecord). + +mod shared; + +use light_account_pinocchio::{CompressionState, IntoVariant, LightDiscriminator}; +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterfaceExt, AccountSpec, + CreateAccountsProofInput, PdaSpec, +}; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::{program_test::TestRpc, Indexer, Rpc}; +use manual_test_pinocchio::{ + account_loader::accounts::CreateZeroCopyParams, ZeroCopyRecord, ZeroCopyRecordSeeds, + ZeroCopyRecordVariant, +}; +use solana_sdk::instruction::{AccountMeta, Instruction}; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +/// Test the full lifecycle for zero-copy accounts: create -> compress -> decompress. +#[tokio::test] +async fn test_zero_copy_create_compress_decompress() { + let program_id = Pubkey::new_from_array(manual_test_pinocchio::ID); + let (mut rpc, payer, config_pda) = shared::setup_test_env().await; + + let owner = Keypair::new().pubkey(); + let value: u64 = 12345; + let name = "my_record".to_string(); + + // Derive PDA for zero-copy record + let (record_pda, _) = Pubkey::find_program_address( + &[b"zero_copy", owner.as_ref(), name.as_bytes()], + &program_id, + ); + + // Get proof for the PDA + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![CreateAccountsProofInput::pda(record_pda)], + ) + .await + .unwrap(); + + let params = CreateZeroCopyParams { + create_accounts_proof: proof_result.create_accounts_proof, + owner: owner.to_bytes(), + value, + name: name.clone(), + }; + + let accounts = vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(config_pda, false), + AccountMeta::new(record_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::ID, false), + ]; + + let ix = Instruction { + program_id, + accounts: [accounts, proof_result.remaining_accounts].concat(), + data: [ + manual_test_pinocchio::discriminators::CREATE_ZERO_COPY.as_slice(), + &borsh::to_vec(¶ms).unwrap(), + ] + .concat(), + }; + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .expect("CreateZeroCopy should succeed"); + + // PHASE 1: Verify account exists on-chain + assert!( + rpc.get_account(record_pda).await.unwrap().is_some(), + "Account should exist on-chain after creation" + ); + + // PHASE 2: Warp time to trigger forester auto-compression + rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); + + // Verify account is closed on-chain (compressed by forester) + let acc = rpc.get_account(record_pda).await.unwrap(); + assert!( + acc.is_none() || acc.unwrap().lamports == 0, + "Account should be closed after compression" + ); + + // PHASE 3: Verify compressed account exists + let address_tree_pubkey = rpc.get_address_tree_v2().tree; + let compressed_address = light_compressed_account::address::derive_address( + &record_pda.to_bytes(), + &address_tree_pubkey.to_bytes(), + &program_id.to_bytes(), + ); + + let compressed_acc = rpc + .get_compressed_account(compressed_address, None) + .await + .unwrap() + .value + .unwrap(); + assert_eq!( + compressed_acc.address.unwrap(), + compressed_address, + "Compressed account address should match" + ); + assert!( + !compressed_acc.data.as_ref().unwrap().data.is_empty(), + "Compressed account should have data" + ); + + // PHASE 4: Decompress account + let account_interface = rpc + .get_account_interface(&record_pda, &program_id) + .await + .expect("failed to get account interface"); + assert!( + account_interface.is_cold(), + "Account should be cold (compressed)" + ); + + // Build variant using IntoVariant - verify seeds match the compressed data + let variant = ZeroCopyRecordSeeds { + owner: owner.to_bytes(), + name: name.clone(), + } + .into_variant(&account_interface.account.data[8..]) + .expect("Seed verification failed"); + + // Verify the data from the compressed account + assert_eq!(variant.data.value, value, "Compressed value should match"); + assert_eq!( + Pubkey::new_from_array(variant.data.owner), + owner, + "Compressed owner should match" + ); + + // Build PdaSpec and create decompress instructions + let spec = PdaSpec::new(account_interface.clone(), variant, program_id); + let specs: Vec> = vec![AccountSpec::Pda(spec)]; + + let decompress_instructions = + create_load_instructions(&specs, payer.pubkey(), config_pda, &rpc) + .await + .expect("create_load_instructions should succeed"); + + rpc.create_and_send_transaction(&decompress_instructions, &payer.pubkey(), &[&payer]) + .await + .expect("Decompression should succeed"); + + // PHASE 5: Verify account is back on-chain with correct data + let record_account = rpc + .get_account(record_pda) + .await + .unwrap() + .expect("Account should exist after decompression"); + + // Verify discriminator is set correctly (first 8 bytes) + let discriminator = &record_account.data[..8]; + assert_eq!( + discriminator, + ZeroCopyRecord::LIGHT_DISCRIMINATOR, + "Discriminator should match ZeroCopyRecord::LIGHT_DISCRIMINATOR after decompression" + ); + + // Verify data is correct (zero-copy uses bytemuck) + let record_bytes = &record_account.data[8..8 + core::mem::size_of::()]; + let record: &ZeroCopyRecord = bytemuck::from_bytes(record_bytes); + + assert_eq!( + Pubkey::new_from_array(record.owner), + owner, + "Record owner should match after decompression" + ); + assert_eq!( + record.value, value, + "Record value should match after decompression" + ); + + // state should be Decompressed after decompression + assert_eq!( + record.compression_info.state, + CompressionState::Decompressed, + "state should be Decompressed after decompression" + ); +} diff --git a/sdk-tests/manual-test-pinocchio/tests/all.rs b/sdk-tests/manual-test-pinocchio/tests/all.rs new file mode 100644 index 0000000000..bc11c24554 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/tests/all.rs @@ -0,0 +1,220 @@ +//! Test create_all instruction - all account types in a single instruction. +//! +//! Creates: +//! - Borsh PDA (MinimalRecord) +//! - ZeroCopy PDA (ZeroCopyRecord) +//! - Compressed Mint +//! - Token Vault +//! - Associated Token Account (ATA) + +mod shared; + +use borsh::BorshDeserialize; +use light_client::interface::{get_create_accounts_proof, CreateAccountsProofInput}; +use light_program_test::Rpc; +use light_token::instruction::{ + config_pda, derive_associated_token_account, find_mint_address, rent_sponsor_pda, + LIGHT_TOKEN_PROGRAM_ID, +}; +use light_token_interface::state::{ + AccountState, BaseMint, Mint, MintMetadata, Token, ACCOUNT_TYPE_MINT, +}; +use manual_test_pinocchio::{ + all::accounts::{ + CreateAllParams, ALL_BORSH_SEED, ALL_MINT_SIGNER_SEED, ALL_TOKEN_VAULT_SEED, + ALL_ZERO_COPY_SEED, + }, + MinimalRecord, ZeroCopyRecord, +}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + signature::{Keypair, Signer}, +}; + +/// Test creating all account types in a single instruction. +#[tokio::test] +async fn test_create_all() { + let (mut rpc, payer, config_pda_addr) = shared::setup_test_env().await; + + let program_id = Pubkey::new_from_array(manual_test_pinocchio::ID); + let authority = Keypair::new(); + let owner = Keypair::new().pubkey(); + let value: u64 = 42; + + // ========== Derive all addresses ========== + + // PDAs (using ALL module-specific seeds) + let (borsh_record_pda, _) = + Pubkey::find_program_address(&[ALL_BORSH_SEED, owner.as_ref()], &program_id); + let (zero_copy_record_pda, _) = + Pubkey::find_program_address(&[ALL_ZERO_COPY_SEED, owner.as_ref()], &program_id); + + // Mint signer and mint + let (mint_signer, mint_signer_bump) = Pubkey::find_program_address( + &[ALL_MINT_SIGNER_SEED, authority.pubkey().as_ref()], + &program_id, + ); + let (mint, _mint_bump) = find_mint_address(&mint_signer); + + // Token vault + let (token_vault, token_vault_bump) = + Pubkey::find_program_address(&[ALL_TOKEN_VAULT_SEED, mint.as_ref()], &program_id); + let vault_owner = Keypair::new(); + + // ATA + let ata_owner = Keypair::new(); + let (user_ata, _) = derive_associated_token_account(&ata_owner.pubkey(), &mint); + + // ========== Get proof for 2 PDAs + 1 Mint ========== + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![ + CreateAccountsProofInput::pda(borsh_record_pda), + CreateAccountsProofInput::pda(zero_copy_record_pda), + CreateAccountsProofInput::mint(mint_signer), + ], + ) + .await + .unwrap(); + + // ========== Build and send instruction ========== + let params = CreateAllParams { + create_accounts_proof: proof_result.create_accounts_proof, + mint_signer_bump, + token_vault_bump, + owner: owner.to_bytes(), + value, + }; + + let accounts = vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(authority.pubkey(), true), + AccountMeta::new_readonly(config_pda_addr, false), + AccountMeta::new(borsh_record_pda, false), + AccountMeta::new(zero_copy_record_pda, false), + AccountMeta::new_readonly(mint_signer, false), + AccountMeta::new(mint, false), + AccountMeta::new(token_vault, false), + AccountMeta::new_readonly(vault_owner.pubkey(), false), + AccountMeta::new_readonly(ata_owner.pubkey(), false), + AccountMeta::new(user_ata, false), + AccountMeta::new_readonly(config_pda(), false), + AccountMeta::new(rent_sponsor_pda(), false), + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + AccountMeta::new_readonly( + Pubkey::new_from_array(light_token_types::CPI_AUTHORITY_PDA), + false, + ), + AccountMeta::new_readonly(solana_sdk::system_program::ID, false), + ]; + + let ix = Instruction { + program_id, + accounts: [accounts, proof_result.remaining_accounts].concat(), + data: [ + manual_test_pinocchio::discriminators::CREATE_ALL.as_slice(), + &borsh::to_vec(¶ms).unwrap(), + ] + .concat(), + }; + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer, &authority]) + .await + .expect("CreateAll should succeed"); + + // ========== Verify all 5 accounts exist with correct data ========== + + // 1. Verify Borsh PDA + let borsh_account = rpc + .get_account(borsh_record_pda) + .await + .unwrap() + .expect("Borsh PDA should exist"); + + let borsh_record = + MinimalRecord::deserialize(&mut &borsh_account.data[8..]).expect("Should deserialize"); + assert_eq!( + borsh_record.owner, + owner.to_bytes(), + "Borsh PDA owner should match" + ); + + // 2. Verify ZeroCopy PDA + let zero_copy_account = rpc + .get_account(zero_copy_record_pda) + .await + .unwrap() + .expect("ZeroCopy PDA should exist"); + + let record_bytes = &zero_copy_account.data[8..8 + core::mem::size_of::()]; + let record: &ZeroCopyRecord = bytemuck::from_bytes(record_bytes); + assert_eq!( + Pubkey::new_from_array(record.owner), + owner, + "ZeroCopy PDA owner should match" + ); + assert_eq!(record.value, value, "ZeroCopy PDA value should match"); + + // 3. Verify Mint + let mint_account = rpc + .get_account(mint) + .await + .unwrap() + .expect("Mint should exist"); + + let mint_data = Mint::deserialize(&mut &mint_account.data[..]).expect("Should deserialize"); + let compression = mint_data.compression; + + let expected_mint = Mint { + base: BaseMint { + mint_authority: Some(authority.pubkey().to_bytes().into()), + supply: 0, + decimals: 6, + is_initialized: true, + freeze_authority: None, + }, + metadata: MintMetadata { + version: 3, + mint_decompressed: true, + mint: mint.to_bytes().into(), + mint_signer: mint_signer.to_bytes(), + bump: _mint_bump, + }, + reserved: [0u8; 16], + account_type: ACCOUNT_TYPE_MINT, + compression, + extensions: None, + }; + + assert_eq!(mint_data, expected_mint, "Mint should match expected"); + + // 4. Verify Token Vault + let vault_account = rpc + .get_account(token_vault) + .await + .unwrap() + .expect("Token vault should exist"); + + let token = + Token::deserialize(&mut &vault_account.data[..]).expect("Should deserialize as Token"); + assert_eq!(token.mint.to_bytes(), mint.to_bytes()); + assert_eq!(token.owner.to_bytes(), vault_owner.pubkey().to_bytes()); + assert_eq!(token.amount, 0); + assert_eq!(token.state, AccountState::Initialized); + + // 5. Verify ATA + let ata_account = rpc + .get_account(user_ata) + .await + .unwrap() + .expect("ATA should exist"); + + let ata_token = + Token::deserialize(&mut &ata_account.data[..]).expect("Should deserialize as Token"); + assert_eq!(ata_token.mint.to_bytes(), mint.to_bytes()); + assert_eq!(ata_token.owner.to_bytes(), ata_owner.pubkey().to_bytes()); + assert_eq!(ata_token.amount, 0); + assert_eq!(ata_token.state, AccountState::Initialized); +} diff --git a/sdk-tests/manual-test-pinocchio/tests/ata.rs b/sdk-tests/manual-test-pinocchio/tests/ata.rs new file mode 100644 index 0000000000..562bca3a61 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/tests/ata.rs @@ -0,0 +1,130 @@ +//! Test ATA pattern - Associated Token Account with rent-free CPI. + +mod shared; + +use borsh::BorshDeserialize; +use light_program_test::Rpc; +use light_token::instruction::{ + config_pda, derive_associated_token_account, rent_sponsor_pda, LIGHT_TOKEN_PROGRAM_ID, +}; +use light_token_interface::state::{AccountState, Token}; +use manual_test_pinocchio::CreateAtaParams; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + signature::{Keypair, Signer}, +}; + +/// Test creating an ATA using CreateTokenAtaCpi. +#[tokio::test] +async fn test_create_ata() { + let (mut rpc, payer, _) = shared::setup_test_env().await; + + // Create a mint to use for the ATA + let mint = shared::create_test_mint(&mut rpc, &payer).await; + + // ATA owner - typically a user wallet + let ata_owner = Keypair::new(); + + // Derive ATA address using light-token's standard derivation + let (user_ata, _) = derive_associated_token_account(&ata_owner.pubkey(), &mint); + + let params = CreateAtaParams::default(); + + let program_id = Pubkey::new_from_array(manual_test_pinocchio::ID); + + let accounts = vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(mint, false), + AccountMeta::new_readonly(ata_owner.pubkey(), false), + AccountMeta::new(user_ata, false), + AccountMeta::new_readonly(config_pda(), false), + AccountMeta::new(rent_sponsor_pda(), false), + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + AccountMeta::new_readonly(solana_sdk::system_program::ID, false), + ]; + + let data = [ + manual_test_pinocchio::discriminators::CREATE_ATA.as_slice(), + &borsh::to_vec(¶ms).unwrap(), + ] + .concat(); + + let ix = Instruction { + program_id, + accounts, + data, + }; + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .expect("CreateAta should succeed"); + + // Verify ATA exists and has correct state + let ata_account = rpc + .get_account(user_ata) + .await + .unwrap() + .expect("ATA should exist"); + + let token = + Token::deserialize(&mut &ata_account.data[..]).expect("Should deserialize as Token"); + + assert_eq!(token.mint.to_bytes(), mint.to_bytes()); + assert_eq!(token.owner.to_bytes(), ata_owner.pubkey().to_bytes()); + assert_eq!(token.amount, 0); + assert_eq!(token.state, AccountState::Initialized); +} + +/// Test idempotent ATA creation - should not fail if ATA already exists. +#[tokio::test] +async fn test_create_ata_idempotent() { + let (mut rpc, payer, _) = shared::setup_test_env().await; + + let mint = shared::create_test_mint(&mut rpc, &payer).await; + let ata_owner = Keypair::new(); + let (user_ata, _) = derive_associated_token_account(&ata_owner.pubkey(), &mint); + + let params = CreateAtaParams::default(); + + let program_id = Pubkey::new_from_array(manual_test_pinocchio::ID); + + let accounts = vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(mint, false), + AccountMeta::new_readonly(ata_owner.pubkey(), false), + AccountMeta::new(user_ata, false), + AccountMeta::new_readonly(config_pda(), false), + AccountMeta::new(rent_sponsor_pda(), false), + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + AccountMeta::new_readonly(solana_sdk::system_program::ID, false), + ]; + + let data = [ + manual_test_pinocchio::discriminators::CREATE_ATA.as_slice(), + &borsh::to_vec(¶ms).unwrap(), + ] + .concat(); + + let ix = Instruction { + program_id, + accounts: accounts.clone(), + data: data.clone(), + }; + + // First creation + rpc.create_and_send_transaction(std::slice::from_ref(&ix), &payer.pubkey(), &[&payer]) + .await + .expect("First CreateAta should succeed"); + + // Second creation (idempotent) - should NOT fail + let ix2 = Instruction { + program_id, + accounts, + data, + }; + + rpc.create_and_send_transaction(&[ix2], &payer.pubkey(), &[&payer]) + .await + .expect("Second CreateAta should succeed (idempotent)"); +} diff --git a/sdk-tests/manual-test-pinocchio/tests/shared.rs b/sdk-tests/manual-test-pinocchio/tests/shared.rs new file mode 100644 index 0000000000..2ca6d48c25 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/tests/shared.rs @@ -0,0 +1,125 @@ +//! Shared test helpers for manual-test-pinocchio integration tests. + +use light_account_pinocchio::derive_rent_sponsor_pda; +use light_client::interface::{ + get_create_accounts_proof, CreateAccountsProofInput, InitializeRentFreeConfig, +}; +use light_program_test::{ + program_test::{setup_mock_program_data, LightProgramTest}, + ProgramTestConfig, Rpc, +}; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +/// Setup test environment with Light Protocol and compression config. +/// Returns (rpc, payer, config_pda). +pub async fn setup_test_env() -> (LightProgramTest, Keypair, Pubkey) { + let program_id = Pubkey::new_from_array(manual_test_pinocchio::ID); + let mut config = ProgramTestConfig::new_v2( + true, + Some(vec![("manual_test_pinocchio", program_id)]), + ); + config = config.with_light_protocol_events(); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); + + // Derive rent sponsor PDA for this program (pinocchio version takes &[u8; 32]) + let (rent_sponsor_bytes, _) = derive_rent_sponsor_pda(&program_id.to_bytes()); + let rent_sponsor = Pubkey::new_from_array(rent_sponsor_bytes); + + let (init_config_ix, config_pda) = InitializeRentFreeConfig::new( + &program_id, + &payer.pubkey(), + &program_data_pda, + rent_sponsor, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .expect("Initialize config should succeed"); + + (rpc, payer, config_pda) +} + +/// Create a test mint using the two_mints instruction and return the mint pubkey. +#[allow(dead_code)] +pub async fn create_test_mint(rpc: &mut LightProgramTest, payer: &Keypair) -> Pubkey { + use manual_test_pinocchio::two_mints::accounts::{ + CreateDerivedMintsParams, MINT_SIGNER_0_SEED, MINT_SIGNER_1_SEED, + }; + use solana_sdk::instruction::{AccountMeta, Instruction}; + + let program_id = Pubkey::new_from_array(manual_test_pinocchio::ID); + let authority = Keypair::new(); + + // Derive mint signer PDAs + let (mint_signer_0, mint_signer_0_bump) = Pubkey::find_program_address( + &[MINT_SIGNER_0_SEED, authority.pubkey().as_ref()], + &program_id, + ); + let (mint_signer_1, mint_signer_1_bump) = Pubkey::find_program_address( + &[MINT_SIGNER_1_SEED, authority.pubkey().as_ref()], + &program_id, + ); + + // Derive mint PDAs + let (mint_0, _) = light_token::instruction::find_mint_address(&mint_signer_0); + let (mint_1, _) = light_token::instruction::find_mint_address(&mint_signer_1); + + // Get proof for the mints + let proof_result = get_create_accounts_proof( + rpc, + &program_id, + vec![ + CreateAccountsProofInput::mint(mint_signer_0), + CreateAccountsProofInput::mint(mint_signer_1), + ], + ) + .await + .unwrap(); + + let params = CreateDerivedMintsParams { + create_accounts_proof: proof_result.create_accounts_proof, + mint_signer_0_bump, + mint_signer_1_bump, + }; + + let accounts = vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(authority.pubkey(), true), + AccountMeta::new_readonly(mint_signer_0, false), + AccountMeta::new_readonly(mint_signer_1, false), + AccountMeta::new(mint_0, false), + AccountMeta::new(mint_1, false), + AccountMeta::new_readonly(light_token::instruction::config_pda(), false), + AccountMeta::new(light_token::instruction::rent_sponsor_pda(), false), + AccountMeta::new_readonly(light_token::instruction::LIGHT_TOKEN_PROGRAM_ID, false), + AccountMeta::new_readonly( + Pubkey::new_from_array(light_token_types::CPI_AUTHORITY_PDA), + false, + ), + AccountMeta::new_readonly(solana_sdk::system_program::ID, false), + ]; + + let ix = Instruction { + program_id, + accounts: [accounts, proof_result.remaining_accounts].concat(), + data: [ + manual_test_pinocchio::discriminators::CREATE_DERIVED_MINTS.as_slice(), + &borsh::to_vec(¶ms).unwrap(), + ] + .concat(), + }; + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[payer, &authority]) + .await + .expect("Create mint should succeed"); + + mint_0 // Return first mint +} diff --git a/sdk-tests/manual-test-pinocchio/tests/test.rs b/sdk-tests/manual-test-pinocchio/tests/test.rs new file mode 100644 index 0000000000..0e2df7cad6 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/tests/test.rs @@ -0,0 +1,168 @@ +//! Integration test for manual Light Protocol implementation. +//! +//! Tests the full lifecycle: create -> compress -> decompress + +mod shared; + +use light_account_pinocchio::{CompressionState, IntoVariant}; +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterfaceExt, AccountSpec, + CreateAccountsProofInput, PdaSpec, +}; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::{program_test::TestRpc, Indexer, Rpc}; +use manual_test_pinocchio::{ + pda::accounts::CreatePdaParams, MinimalRecord, MinimalRecordSeeds, MinimalRecordVariant, +}; +use solana_sdk::instruction::{AccountMeta, Instruction}; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +/// Test the full lifecycle: create -> compress -> decompress. +#[tokio::test] +async fn test_create_compress_decompress() { + let program_id = Pubkey::new_from_array(manual_test_pinocchio::ID); + let (mut rpc, payer, config_pda) = shared::setup_test_env().await; + + let owner = Keypair::new().pubkey(); + let nonce: u64 = 12345; + + // Derive PDA for record + let (record_pda, _) = Pubkey::find_program_address( + &[b"minimal_record", owner.as_ref(), &nonce.to_le_bytes()], + &program_id, + ); + + // Get proof for the PDA + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![CreateAccountsProofInput::pda(record_pda)], + ) + .await + .unwrap(); + + let params = CreatePdaParams { + create_accounts_proof: proof_result.create_accounts_proof, + owner: owner.to_bytes(), + nonce, + }; + + let accounts = vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(config_pda, false), + AccountMeta::new(record_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::ID, false), + ]; + + let ix = Instruction { + program_id, + accounts: [accounts, proof_result.remaining_accounts].concat(), + data: [ + manual_test_pinocchio::discriminators::CREATE_PDA.as_slice(), + &borsh::to_vec(¶ms).unwrap(), + ] + .concat(), + }; + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .expect("CreatePda should succeed"); + + // PHASE 1: Verify account exists on-chain + assert!( + rpc.get_account(record_pda).await.unwrap().is_some(), + "Account should exist on-chain after creation" + ); + + // PHASE 2: Warp time to trigger forester auto-compression + rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); + + // Verify account is closed on-chain (compressed by forester) + let acc = rpc.get_account(record_pda).await.unwrap(); + assert!( + acc.is_none() || acc.unwrap().lamports == 0, + "Account should be closed after compression" + ); + + // PHASE 3: Verify compressed account exists + let address_tree_pubkey = rpc.get_address_tree_v2().tree; + let compressed_address = light_compressed_account::address::derive_address( + &record_pda.to_bytes(), + &address_tree_pubkey.to_bytes(), + &program_id.to_bytes(), + ); + + let compressed_acc = rpc + .get_compressed_account(compressed_address, None) + .await + .unwrap() + .value + .unwrap(); + assert_eq!( + compressed_acc.address.unwrap(), + compressed_address, + "Compressed account address should match" + ); + assert!( + !compressed_acc.data.as_ref().unwrap().data.is_empty(), + "Compressed account should have data" + ); + + // PHASE 4: Decompress account + let account_interface = rpc + .get_account_interface(&record_pda, &program_id) + .await + .expect("failed to get account interface"); + assert!( + account_interface.is_cold(), + "Account should be cold (compressed)" + ); + + // Build variant using IntoVariant - verify seeds match the compressed data + let variant = MinimalRecordSeeds { + owner: owner.to_bytes(), + nonce, + } + .into_variant(&account_interface.account.data[8..]) + .expect("Seed verification failed"); + + // Build PdaSpec and create decompress instructions + let spec = PdaSpec::new(account_interface.clone(), variant, program_id); + let specs: Vec> = vec![AccountSpec::Pda(spec)]; + + let decompress_instructions = + create_load_instructions(&specs, payer.pubkey(), config_pda, &rpc) + .await + .expect("create_load_instructions should succeed"); + + rpc.create_and_send_transaction(&decompress_instructions, &payer.pubkey(), &[&payer]) + .await + .expect("Decompression should succeed"); + + // PHASE 5: Verify account is back on-chain with correct data + let record_account = rpc + .get_account(record_pda) + .await + .unwrap() + .expect("Account should exist after decompression"); + + // Verify data is correct + let record: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &record_account.data[8..]) + .expect("Failed to deserialize MinimalRecord"); + + assert_eq!( + record.owner, + owner.to_bytes(), + "Record owner should match" + ); + + // state should be Decompressed after decompression + assert_eq!( + record.compression_info.state, + CompressionState::Decompressed, + "state should be Decompressed after decompression" + ); +} diff --git a/sdk-tests/manual-test-pinocchio/tests/token_account.rs b/sdk-tests/manual-test-pinocchio/tests/token_account.rs new file mode 100644 index 0000000000..533b1b71d0 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/tests/token_account.rs @@ -0,0 +1,74 @@ +//! Test token vault pattern - PDA token account with rent-free CPI. + +mod shared; + +use borsh::BorshDeserialize; +use light_program_test::Rpc; +use light_token::instruction::{config_pda, rent_sponsor_pda, LIGHT_TOKEN_PROGRAM_ID}; +use light_token_interface::state::{AccountState, Token}; +use manual_test_pinocchio::{CreateTokenVaultParams, TOKEN_VAULT_SEED}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + signature::{Keypair, Signer}, +}; + +/// Test creating a PDA token vault using CreateTokenAccountCpi. +#[tokio::test] +async fn test_create_token_vault() { + let (mut rpc, payer, _) = shared::setup_test_env().await; + + // Create a mint to use for the token vault + let mint = shared::create_test_mint(&mut rpc, &payer).await; + + // Vault owner - can be any pubkey (e.g., a PDA authority) + let vault_owner = Keypair::new(); + + let program_id = Pubkey::new_from_array(manual_test_pinocchio::ID); + + // Derive token vault PDA + let (token_vault, vault_bump) = + Pubkey::find_program_address(&[TOKEN_VAULT_SEED, mint.as_ref()], &program_id); + + let params = CreateTokenVaultParams { vault_bump }; + + let accounts = vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(mint, false), + AccountMeta::new_readonly(vault_owner.pubkey(), false), + AccountMeta::new(token_vault, false), + AccountMeta::new_readonly(config_pda(), false), + AccountMeta::new(rent_sponsor_pda(), false), + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + AccountMeta::new_readonly(solana_sdk::system_program::ID, false), + ]; + + let ix = Instruction { + program_id, + accounts, + data: [ + manual_test_pinocchio::discriminators::CREATE_TOKEN_VAULT.as_slice(), + &borsh::to_vec(¶ms).unwrap(), + ] + .concat(), + }; + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .expect("CreateTokenVault should succeed"); + + // Verify token account exists and has correct state + let vault_account = rpc + .get_account(token_vault) + .await + .unwrap() + .expect("Token vault should exist"); + + let token = + Token::deserialize(&mut &vault_account.data[..]).expect("Should deserialize as Token"); + + assert_eq!(token.mint.to_bytes(), mint.to_bytes()); + assert_eq!(token.owner.to_bytes(), vault_owner.pubkey().to_bytes()); + assert_eq!(token.amount, 0); + assert_eq!(token.state, AccountState::Initialized); +} diff --git a/sdk-tests/manual-test-pinocchio/tests/two_mints.rs b/sdk-tests/manual-test-pinocchio/tests/two_mints.rs new file mode 100644 index 0000000000..3dcd35d291 --- /dev/null +++ b/sdk-tests/manual-test-pinocchio/tests/two_mints.rs @@ -0,0 +1,161 @@ +//! Test derived mint pattern - minimal params, program derives everything. + +mod shared; + +use borsh::BorshDeserialize; +use light_client::interface::{get_create_accounts_proof, CreateAccountsProofInput}; +use light_program_test::Rpc; +use light_token::instruction::{ + config_pda, find_mint_address, rent_sponsor_pda, LIGHT_TOKEN_PROGRAM_ID, +}; +use light_token_interface::state::{BaseMint, Mint, MintMetadata, ACCOUNT_TYPE_MINT}; +use manual_test_pinocchio::two_mints::accounts::{ + CreateDerivedMintsParams, MINT_SIGNER_0_SEED, MINT_SIGNER_1_SEED, +}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + signature::{Keypair, Signer}, +}; + +/// Test creating two compressed mints using derived PDA mint signers. +#[tokio::test] +async fn test_create_derived_mints() { + let (mut rpc, payer, _) = shared::setup_test_env().await; + + let program_id = Pubkey::new_from_array(manual_test_pinocchio::ID); + let authority = Keypair::new(); + + // Derive mint signer PDAs from authority (like macro would) + let (mint_signer_0, mint_signer_0_bump) = Pubkey::find_program_address( + &[MINT_SIGNER_0_SEED, authority.pubkey().as_ref()], + &program_id, + ); + let (mint_signer_1, mint_signer_1_bump) = Pubkey::find_program_address( + &[MINT_SIGNER_1_SEED, authority.pubkey().as_ref()], + &program_id, + ); + + // Derive mint PDAs from mint signers (light-token derives these) + let (mint_0, mint_0_bump) = find_mint_address(&mint_signer_0); + let (mint_1, mint_1_bump) = find_mint_address(&mint_signer_1); + + // Get proof for the mints + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![ + CreateAccountsProofInput::mint(mint_signer_0), + CreateAccountsProofInput::mint(mint_signer_1), + ], + ) + .await + .unwrap(); + + // Minimal params - only proof + bumps + let params = CreateDerivedMintsParams { + create_accounts_proof: proof_result.create_accounts_proof.clone(), + mint_signer_0_bump, + mint_signer_1_bump, + }; + + let accounts = vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(authority.pubkey(), true), + AccountMeta::new_readonly(mint_signer_0, false), + AccountMeta::new_readonly(mint_signer_1, false), + AccountMeta::new(mint_0, false), + AccountMeta::new(mint_1, false), + AccountMeta::new_readonly(config_pda(), false), + AccountMeta::new(rent_sponsor_pda(), false), + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + AccountMeta::new_readonly( + Pubkey::new_from_array(light_token_types::CPI_AUTHORITY_PDA), + false, + ), + AccountMeta::new_readonly(solana_sdk::system_program::ID, false), + ]; + + let ix = Instruction { + program_id, + accounts: [accounts, proof_result.remaining_accounts].concat(), + data: [ + manual_test_pinocchio::discriminators::CREATE_DERIVED_MINTS.as_slice(), + &borsh::to_vec(¶ms).unwrap(), + ] + .concat(), + }; + + // Sign with payer and authority + let signers: Vec<&Keypair> = vec![&payer, &authority]; + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &signers) + .await + .expect("CreateDerivedMints should succeed"); + + // Verify mints exist on-chain + let mint_0_account = rpc + .get_account(mint_0) + .await + .unwrap() + .expect("Mint 0 should exist"); + let mint_1_account = rpc + .get_account(mint_1) + .await + .unwrap() + .expect("Mint 1 should exist"); + + // Deserialize and verify mint 0 + let mint_0_data = Mint::deserialize(&mut &mint_0_account.data[..]).unwrap(); + let compression_0 = mint_0_data.compression; + + let expected_mint_0 = Mint { + base: BaseMint { + mint_authority: Some(authority.pubkey().to_bytes().into()), + supply: 0, + decimals: 6, // mint::decimals = 6 + is_initialized: true, + freeze_authority: None, + }, + metadata: MintMetadata { + version: 3, + mint_decompressed: true, + mint: mint_0.to_bytes().into(), + mint_signer: mint_signer_0.to_bytes(), + bump: mint_0_bump, + }, + reserved: [0u8; 16], + account_type: ACCOUNT_TYPE_MINT, + compression: compression_0, + extensions: None, + }; + + assert_eq!(mint_0_data, expected_mint_0, "Mint 0 should match expected"); + + // Deserialize and verify mint 1 + let mint_1_data = Mint::deserialize(&mut &mint_1_account.data[..]).unwrap(); + let compression_1 = mint_1_data.compression; + + let expected_mint_1 = Mint { + base: BaseMint { + mint_authority: Some(authority.pubkey().to_bytes().into()), + supply: 0, + decimals: 9, // mint::decimals = 9 + is_initialized: true, + freeze_authority: None, + }, + metadata: MintMetadata { + version: 3, + mint_decompressed: true, + mint: mint_1.to_bytes().into(), + mint_signer: mint_signer_1.to_bytes(), + bump: mint_1_bump, + }, + reserved: [0u8; 16], + account_type: ACCOUNT_TYPE_MINT, + compression: compression_1, + extensions: None, + }; + + assert_eq!(mint_1_data, expected_mint_1, "Mint 1 should match expected"); +} diff --git a/sdk-tests/manual-test/src/derived_decompress.rs b/sdk-tests/manual-test/src/derived_decompress.rs index 96f65cf5b0..b945179a45 100644 --- a/sdk-tests/manual-test/src/derived_decompress.rs +++ b/sdk-tests/manual-test/src/derived_decompress.rs @@ -127,5 +127,7 @@ pub fn process_decompress_idempotent<'info>( &crate::ID.to_bytes(), current_slot, ) - .map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::Custom(u32::from(e)))) + .map_err(|e| { + anchor_lang::error::Error::from(solana_program_error::ProgramError::Custom(u32::from(e))) + }) } diff --git a/sdk-tests/single-pda-derive-test/Cargo.toml b/sdk-tests/pinocchio-derive-test/Cargo.toml similarity index 100% rename from sdk-tests/single-pda-derive-test/Cargo.toml rename to sdk-tests/pinocchio-derive-test/Cargo.toml diff --git a/sdk-tests/single-pda-derive-test/src/instruction_accounts.rs b/sdk-tests/pinocchio-derive-test/src/instruction_accounts.rs similarity index 100% rename from sdk-tests/single-pda-derive-test/src/instruction_accounts.rs rename to sdk-tests/pinocchio-derive-test/src/instruction_accounts.rs diff --git a/sdk-tests/single-pda-derive-test/src/lib.rs b/sdk-tests/pinocchio-derive-test/src/lib.rs similarity index 100% rename from sdk-tests/single-pda-derive-test/src/lib.rs rename to sdk-tests/pinocchio-derive-test/src/lib.rs diff --git a/sdk-tests/single-pda-derive-test/src/state.rs b/sdk-tests/pinocchio-derive-test/src/state.rs similarity index 100% rename from sdk-tests/single-pda-derive-test/src/state.rs rename to sdk-tests/pinocchio-derive-test/src/state.rs diff --git a/sdk-tests/single-pda-derive-test/tests/test.rs b/sdk-tests/pinocchio-derive-test/tests/test.rs similarity index 100% rename from sdk-tests/single-pda-derive-test/tests/test.rs rename to sdk-tests/pinocchio-derive-test/tests/test.rs diff --git a/sdk-tests/single-account-loader-test/Cargo.toml b/sdk-tests/single-account-loader-test/Cargo.toml index a116be3b34..04a5c40ba1 100644 --- a/sdk-tests/single-account-loader-test/Cargo.toml +++ b/sdk-tests/single-account-loader-test/Cargo.toml @@ -13,24 +13,20 @@ no-entrypoint = [] no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] -custom-heap = ["light-heap", "light-sdk/custom-heap"] +custom-heap = ["light-heap"] default = [] -idl-build = ["anchor-lang/idl-build", "light-sdk/idl-build"] +idl-build = ["anchor-lang/idl-build"] test-sbf = [] [dependencies] light-heap = { workspace = true, optional = true } -light-account = { workspace = true } -light-sdk = { workspace = true, features = ["anchor", "v2", "anchor-discriminator", "cpi-context"] } -light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] } -light-sdk-macros = { workspace = true } +light-account = { workspace = true, features = ["anchor", "sha256"] } +light-sdk-macros = { workspace = true, features = ["anchor-discriminator"] } light-macros = { workspace = true, features = ["solana"] } borsh = { workspace = true } bytemuck = { workspace = true, features = ["derive"] } -light-compressible = { workspace = true, features = ["anchor"] } light-compressed-account = { workspace = true, features = ["solana"] } light-hasher = { workspace = true, features = ["solana"] } -light-token = { workspace = true, features = ["anchor"] } anchor-lang = { workspace = true } solana-program = { workspace = true } solana-pubkey = { workspace = true } @@ -42,6 +38,7 @@ solana-account-info = { workspace = true } light-program-test = { workspace = true, features = ["devenv"] } light-client = { workspace = true, features = ["v2", "anchor"] } light-test-utils = { workspace = true } +light-sdk = { workspace = true } light-token = { workspace = true } light-compressed-account = { workspace = true, features = ["solana"] } light-compressible = { workspace = true, features = ["anchor"] } diff --git a/sdk-tests/single-account-loader-test/src/lib.rs b/sdk-tests/single-account-loader-test/src/lib.rs index 183011f617..93eaac3f09 100644 --- a/sdk-tests/single-account-loader-test/src/lib.rs +++ b/sdk-tests/single-account-loader-test/src/lib.rs @@ -6,10 +6,8 @@ #![allow(deprecated)] use anchor_lang::prelude::*; -use light_sdk::derive_light_cpi_signer; +use light_account::{derive_light_cpi_signer, CreateAccountsProof, CpiSigner}; use light_sdk_macros::{light_program, LightAccounts}; -use light_sdk_types::interface::CreateAccountsProof; -use light_sdk_types::CpiSigner; pub mod state; diff --git a/sdk-tests/single-account-loader-test/src/state.rs b/sdk-tests/single-account-loader-test/src/state.rs index d568f376a2..5dd7dd0a1e 100644 --- a/sdk-tests/single-account-loader-test/src/state.rs +++ b/sdk-tests/single-account-loader-test/src/state.rs @@ -3,7 +3,7 @@ //! Defines a Pod (zero-copy) account struct for testing AccountLoader with Light Protocol. use anchor_lang::prelude::*; -use light_sdk::{interface::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// A zero-copy account using Pod serialization. diff --git a/sdk-tests/single-ata-test/Cargo.toml b/sdk-tests/single-ata-test/Cargo.toml index ec89b0055f..31e60b49de 100644 --- a/sdk-tests/single-ata-test/Cargo.toml +++ b/sdk-tests/single-ata-test/Cargo.toml @@ -13,24 +13,19 @@ no-entrypoint = [] no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] -custom-heap = ["light-heap", "light-sdk/custom-heap"] +custom-heap = ["light-heap"] default = [] -idl-build = ["anchor-lang/idl-build", "light-sdk/idl-build"] +idl-build = ["anchor-lang/idl-build"] test-sbf = [] [dependencies] light-heap = { workspace = true, optional = true } -light-account = { workspace = true } -light-sdk = { workspace = true, features = ["anchor", "v2", "anchor-discriminator", "cpi-context"] } -light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] } +light-account = { workspace = true, features = ["token", "anchor", "sha256"] } light-macros = { workspace = true, features = ["solana"] } -light-sdk-macros = { workspace = true } +light-sdk-macros = { workspace = true, features = ["anchor-discriminator"] } borsh = { workspace = true } light-compressed-account = { workspace = true, features = ["solana"] } anchor-lang = { workspace = true } -light-token = { workspace = true, features = ["anchor"] } -light-token-types = { workspace = true, features = ["anchor"] } -light-compressible = { workspace = true, features = ["anchor"] } light-hasher = { workspace = true, features = ["solana"] } solana-program = { workspace = true } solana-pubkey = { workspace = true } @@ -43,6 +38,9 @@ light-program-test = { workspace = true, features = ["devenv"] } light-client = { workspace = true, features = ["v2", "anchor"] } light-test-utils = { workspace = true } light-token-interface = { workspace = true } +light-sdk = { workspace = true } +light-sdk-types = { workspace = true, features = ["token"] } +light-token = { workspace = true, features = ["anchor"] } tokio = { workspace = true } solana-sdk = { workspace = true } solana-instruction = { workspace = true } diff --git a/sdk-tests/single-ata-test/src/lib.rs b/sdk-tests/single-ata-test/src/lib.rs index c6f9fa98c1..f2d15dd711 100644 --- a/sdk-tests/single-ata-test/src/lib.rs +++ b/sdk-tests/single-ata-test/src/lib.rs @@ -6,11 +6,11 @@ #![allow(deprecated)] use anchor_lang::prelude::*; -use light_sdk::derive_light_cpi_signer; +use light_account::{ + derive_light_cpi_signer, CreateAccountsProof, CpiSigner, LIGHT_TOKEN_CONFIG, + LIGHT_TOKEN_PROGRAM_ID, LIGHT_TOKEN_RENT_SPONSOR, +}; use light_sdk_macros::{light_program, LightAccounts}; -use light_sdk_types::interface::CreateAccountsProof; -use light_sdk_types::{CpiSigner, LIGHT_TOKEN_PROGRAM_ID}; -use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; declare_id!("AtaT111111111111111111111111111111111111111"); diff --git a/sdk-tests/single-mint-test/Cargo.toml b/sdk-tests/single-mint-test/Cargo.toml index 034b031dee..7cad79adbb 100644 --- a/sdk-tests/single-mint-test/Cargo.toml +++ b/sdk-tests/single-mint-test/Cargo.toml @@ -13,26 +13,21 @@ no-entrypoint = [] no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] -custom-heap = ["light-heap", "light-sdk/custom-heap"] +custom-heap = ["light-heap"] default = [] -idl-build = ["anchor-lang/idl-build", "light-sdk/idl-build", "light-anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build", "light-anchor-spl/idl-build"] test-sbf = [] [dependencies] light-heap = { workspace = true, optional = true } -light-account = { workspace = true } -light-sdk = { workspace = true, features = ["anchor", "v2", "anchor-discriminator", "cpi-context"] } -light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] } +light-account = { workspace = true, features = ["token", "anchor", "sha256"] } light-macros = { workspace = true, features = ["solana"] } -light-sdk-macros = { workspace = true } +light-sdk-macros = { workspace = true, features = ["anchor-discriminator"] } borsh = { workspace = true } light-compressed-account = { workspace = true, features = ["solana"] } light-hasher = { workspace = true, features = ["solana"] } anchor-lang = { workspace = true } light-anchor-spl = { workspace = true, features = ["metadata"] } -light-token = { workspace = true, features = ["anchor"] } -light-token-types = { workspace = true, features = ["anchor"] } -light-compressible = { workspace = true, features = ["anchor"] } solana-program = { workspace = true } solana-pubkey = { workspace = true } solana-msg = { workspace = true } @@ -44,6 +39,10 @@ light-program-test = { workspace = true, features = ["devenv"] } light-client = { workspace = true, features = ["v2", "anchor"] } light-test-utils = { workspace = true } light-token-interface = { workspace = true } +light-sdk = { workspace = true } +light-sdk-types = { workspace = true, features = ["token"] } +light-token = { workspace = true, features = ["anchor"] } +light-token-types = { workspace = true } tokio = { workspace = true } solana-sdk = { workspace = true } solana-instruction = { workspace = true } diff --git a/sdk-tests/single-mint-test/src/lib.rs b/sdk-tests/single-mint-test/src/lib.rs index f0f2e10434..027eb87a18 100644 --- a/sdk-tests/single-mint-test/src/lib.rs +++ b/sdk-tests/single-mint-test/src/lib.rs @@ -6,10 +6,8 @@ #![allow(deprecated)] use anchor_lang::prelude::*; -use light_sdk::derive_light_cpi_signer; +use light_account::{derive_light_cpi_signer, CreateAccountsProof, CpiSigner}; use light_sdk_macros::{light_program, LightAccounts}; -use light_sdk_types::interface::CreateAccountsProof; -use light_sdk_types::CpiSigner; declare_id!("Mint111111111111111111111111111111111111111"); diff --git a/sdk-tests/single-pda-test/Cargo.toml b/sdk-tests/single-pda-test/Cargo.toml index a41814fa09..648e4895a1 100644 --- a/sdk-tests/single-pda-test/Cargo.toml +++ b/sdk-tests/single-pda-test/Cargo.toml @@ -13,25 +13,20 @@ no-entrypoint = [] no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] -custom-heap = ["light-heap", "light-sdk/custom-heap"] +custom-heap = ["light-heap"] default = [] -idl-build = ["anchor-lang/idl-build", "light-sdk/idl-build"] +idl-build = ["anchor-lang/idl-build"] test-sbf = [] [dependencies] light-heap = { workspace = true, optional = true } -light-account = { workspace = true } -light-sdk = { workspace = true, features = ["anchor", "v2", "anchor-discriminator", "cpi-context"] } -light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] } +light-account = { workspace = true, features = ["anchor", "sha256"] } light-macros = { workspace = true, features = ["solana"] } -light-sdk-macros = { workspace = true } +light-sdk-macros = { workspace = true, features = ["anchor-discriminator"] } borsh = { workspace = true } light-compressed-account = { workspace = true, features = ["solana"] } anchor-lang = { workspace = true } -light-compressible = { workspace = true, features = ["anchor"] } light-hasher = { workspace = true, features = ["solana"] } -light-token = { workspace = true, features = ["anchor"] } -light-token-types = { workspace = true, features = ["anchor"] } solana-program = { workspace = true } solana-pubkey = { workspace = true } solana-msg = { workspace = true } @@ -42,6 +37,7 @@ solana-account-info = { workspace = true } light-program-test = { workspace = true, features = ["devenv"] } light-client = { workspace = true, features = ["v2", "anchor"] } light-test-utils = { workspace = true } +light-sdk = { workspace = true } light-token = { workspace = true } tokio = { workspace = true } solana-sdk = { workspace = true } diff --git a/sdk-tests/single-pda-test/src/instruction_accounts.rs b/sdk-tests/single-pda-test/src/instruction_accounts.rs index 2ddbeb7be4..dec5093db8 100644 --- a/sdk-tests/single-pda-test/src/instruction_accounts.rs +++ b/sdk-tests/single-pda-test/src/instruction_accounts.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; +use light_account::CreateAccountsProof; use crate::state::MinimalRecord; diff --git a/sdk-tests/single-pda-test/src/lib.rs b/sdk-tests/single-pda-test/src/lib.rs index 54a2e3618a..ebd4a48b3e 100644 --- a/sdk-tests/single-pda-test/src/lib.rs +++ b/sdk-tests/single-pda-test/src/lib.rs @@ -6,9 +6,8 @@ #![allow(deprecated)] use anchor_lang::prelude::*; -use light_sdk::derive_light_cpi_signer; +use light_account::{derive_light_cpi_signer, CpiSigner}; use light_sdk_macros::light_program; -use light_sdk_types::CpiSigner; pub mod instruction_accounts; pub mod state; diff --git a/sdk-tests/single-pda-test/src/state.rs b/sdk-tests/single-pda-test/src/state.rs index b8d792516d..6605965aad 100644 --- a/sdk-tests/single-pda-test/src/state.rs +++ b/sdk-tests/single-pda-test/src/state.rs @@ -1,7 +1,7 @@ //! State module for single-pda-test. use anchor_lang::prelude::*; -use light_sdk::{compressible::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// Minimal record struct for testing PDA creation. diff --git a/sdk-tests/single-token-test/Cargo.toml b/sdk-tests/single-token-test/Cargo.toml index dc65be90a5..a9b1ad93a4 100644 --- a/sdk-tests/single-token-test/Cargo.toml +++ b/sdk-tests/single-token-test/Cargo.toml @@ -13,24 +13,19 @@ no-entrypoint = [] no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] -custom-heap = ["light-heap", "light-sdk/custom-heap"] +custom-heap = ["light-heap"] default = [] -idl-build = ["anchor-lang/idl-build", "light-sdk/idl-build"] +idl-build = ["anchor-lang/idl-build"] test-sbf = [] [dependencies] light-heap = { workspace = true, optional = true } -light-account = { workspace = true } -light-sdk = { workspace = true, features = ["anchor", "v2", "anchor-discriminator", "cpi-context"] } -light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] } +light-account = { workspace = true, features = ["token", "anchor", "sha256"] } light-macros = { workspace = true, features = ["solana"] } -light-sdk-macros = { workspace = true } +light-sdk-macros = { workspace = true, features = ["anchor-discriminator"] } borsh = { workspace = true } light-compressed-account = { workspace = true, features = ["solana"] } anchor-lang = { workspace = true } -light-token = { workspace = true, features = ["anchor"] } -light-token-types = { workspace = true, features = ["anchor"] } -light-compressible = { workspace = true, features = ["anchor"] } light-hasher = { workspace = true, features = ["solana"] } solana-program = { workspace = true } solana-pubkey = { workspace = true } @@ -43,6 +38,10 @@ light-program-test = { workspace = true, features = ["devenv"] } light-client = { workspace = true, features = ["v2", "anchor"] } light-test-utils = { workspace = true } light-token-interface = { workspace = true } +light-sdk = { workspace = true } +light-sdk-types = { workspace = true, features = ["token"] } +light-token = { workspace = true, features = ["anchor"] } +light-token-types = { workspace = true } tokio = { workspace = true } solana-sdk = { workspace = true } solana-instruction = { workspace = true } diff --git a/sdk-tests/single-token-test/src/lib.rs b/sdk-tests/single-token-test/src/lib.rs index 4c981a10ea..750d275561 100644 --- a/sdk-tests/single-token-test/src/lib.rs +++ b/sdk-tests/single-token-test/src/lib.rs @@ -6,11 +6,11 @@ #![allow(deprecated)] use anchor_lang::prelude::*; -use light_sdk::derive_light_cpi_signer; +use light_account::{ + derive_light_cpi_signer, CreateAccountsProof, CpiSigner, LIGHT_TOKEN_CONFIG, + LIGHT_TOKEN_RENT_SPONSOR, +}; use light_sdk_macros::{light_program, LightAccounts}; -use light_sdk_types::interface::CreateAccountsProof; -use light_sdk_types::CpiSigner; -use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; declare_id!("TknT111111111111111111111111111111111111111"); From a3458fe65e9fed4915e5c4d658cacf942557b2de Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 2 Feb 2026 18:03:27 +0000 Subject: [PATCH 09/15] stash --- Cargo.lock | 34 + Cargo.toml | 2 +- .../src/account_info/pinocchio.rs | 32 +- .../account-checks/src/account_info/solana.rs | 26 +- program-libs/account-checks/src/create_pda.rs | 129 --- program-libs/account-checks/src/lib.rs | 6 +- .../create-address-test-program/src/lib.rs | 4 +- sdk-libs/account-pinocchio/src/lib.rs | 171 ++-- sdk-libs/account/Cargo.toml | 3 +- sdk-libs/account/src/lib.rs | 178 ++-- sdk-libs/client/src/interface/pack.rs | 6 +- .../compressed_token/v2/compress_and_close.rs | 6 +- .../compressed_token/v2/decompress_full.rs | 2 +- .../src/light_pdas/program/decompress.rs | 11 +- .../program/derive_light_program.rs | 67 +- .../src/light_pdas/program/instructions.rs | 18 +- sdk-libs/program-test/src/compressible.rs | 5 +- sdk-libs/sdk-pinocchio/src/error.rs | 3 + sdk-libs/sdk-types/src/error.rs | 13 +- .../src/interface/account/compression_info.rs | 8 +- .../src/interface/account/light_account.rs | 9 +- .../sdk-types/src/interface/account/pack.rs | 5 +- .../src/interface/account/token_seeds.rs | 31 +- .../accounts/init_compressed_account.rs | 3 +- .../sdk-types/src/interface/cpi/account.rs | 11 +- .../src/interface/cpi/create_mints.rs | 3 +- .../interface/cpi/create_token_accounts.rs | 25 +- .../sdk-types/src/interface/cpi/invoke.rs | 8 +- sdk-libs/sdk-types/src/interface/cpi/mod.rs | 4 +- .../src/interface/create_accounts_proof.rs | 3 +- .../src/interface/instruction/mod.rs | 4 +- sdk-libs/sdk-types/src/interface/mod.rs | 85 +- .../interface/program/compression/close.rs | 9 - .../src/interface/program/compression/mod.rs | 1 - .../src/interface/program/compression/pda.rs | 15 +- .../program/compression/processor.rs | 17 +- .../src/interface/program/config/create.rs | 2 +- .../src/interface/program/config/mod.rs | 3 +- .../decompression/create_token_account.rs | 3 +- .../interface/program/decompression/mod.rs | 4 +- .../interface/program/decompression/pda.rs | 34 +- .../program/decompression/processor.rs | 77 +- .../interface/program/decompression/token.rs | 18 +- .../src/interface/program/validation.rs | 3 +- .../src/interface/program/variant.rs | 7 +- sdk-libs/sdk-types/src/lib.rs | 3 +- sdk-libs/sdk/src/cpi/mod.rs | 16 +- sdk-libs/sdk/src/cpi/v1/invoke.rs | 20 +- sdk-libs/sdk/src/cpi/v2/invoke.rs | 6 +- sdk-libs/sdk/src/cpi/v2/mod.rs | 3 +- sdk-libs/sdk/src/error.rs | 18 +- sdk-libs/sdk/src/instruction/mod.rs | 1 - .../src/instruction/packed_accounts_ext.rs | 23 +- sdk-libs/sdk/src/lib.rs | 15 +- .../src/lib.rs | 3 +- .../src/amm_test/initialize.rs | 14 +- .../src/instruction_accounts.rs | 2 +- .../d10_token_accounts/single_ata.rs | 4 +- .../d10_token_accounts/single_ata_markonly.rs | 2 +- .../d10_token_accounts/single_vault.rs | 2 +- .../d11_zero_copy/mixed_zc_borsh.rs | 2 +- .../instructions/d11_zero_copy/multiple_zc.rs | 2 +- .../instructions/d11_zero_copy/with_ata.rs | 2 +- .../d11_zero_copy/with_ctx_seeds.rs | 2 +- .../d11_zero_copy/with_mint_to.rs | 2 +- .../d11_zero_copy/with_params_seeds.rs | 2 +- .../instructions/d11_zero_copy/with_vault.rs | 2 +- .../src/instructions/d5_markers/all.rs | 2 +- .../instructions/d5_markers/light_token.rs | 2 +- .../instructions/d5_markers/rentfree_bare.rs | 2 +- .../instructions/d6_account_types/account.rs | 2 +- .../src/instructions/d6_account_types/all.rs | 2 +- .../instructions/d6_account_types/boxed.rs | 2 +- .../src/instructions/d7_infra_names/all.rs | 2 +- .../instructions/d7_infra_names/creator.rs | 2 +- .../d7_infra_names/light_token_config.rs | 2 +- .../src/instructions/d7_infra_names/payer.rs | 2 +- .../src/instructions/d8_builder_paths/all.rs | 2 +- .../d8_builder_paths/multi_rentfree.rs | 2 +- .../instructions/d8_builder_paths/pda_only.rs | 2 +- .../src/instructions/d9_seeds/all.rs | 2 +- .../src/instructions/d9_seeds/array_bumps.rs | 2 +- .../instructions/d9_seeds/complex_mixed.rs | 2 +- .../instructions/d9_seeds/const_patterns.rs | 2 +- .../src/instructions/d9_seeds/constant.rs | 2 +- .../src/instructions/d9_seeds/ctx_account.rs | 2 +- .../src/instructions/d9_seeds/edge_cases.rs | 2 +- .../instructions/d9_seeds/external_paths.rs | 2 +- .../instructions/d9_seeds/function_call.rs | 2 +- .../instructions/d9_seeds/instruction_data.rs | 2 +- .../src/instructions/d9_seeds/literal.rs | 2 +- .../instructions/d9_seeds/method_chains.rs | 2 +- .../src/instructions/d9_seeds/mixed.rs | 2 +- .../src/instructions/d9_seeds/nested_seeds.rs | 2 +- .../src/instructions/d9_seeds/param.rs | 2 +- .../src/instructions/d9_seeds/param_bytes.rs | 2 +- .../instructions/d9_seeds/qualified_paths.rs | 2 +- .../csdk-anchor-full-derived-test/src/lib.rs | 9 +- .../src/state/mod.rs | 3 +- .../amm_observation_state_test.rs | 3 +- .../account_macros/amm_pool_state_test.rs | 3 +- .../account_macros/core_game_session_test.rs | 6 +- .../core_placeholder_record_test.rs | 6 +- .../account_macros/core_user_record_test.rs | 6 +- .../account_macros/d1_multi_pubkey_test.rs | 18 +- .../account_macros/d1_single_pubkey_test.rs | 6 +- .../account_macros/d2_all_compress_as_test.rs | 6 +- .../d2_multiple_compress_as_test.rs | 6 +- .../account_macros/d2_no_compress_as_test.rs | 6 +- .../d2_option_none_compress_as_test.rs | 6 +- .../d2_single_compress_as_test.rs | 6 +- .../tests/account_macros/shared.rs | 2 +- .../tests/basic_test.rs | 2 +- .../tests/compressibility_check_test.rs | 4 +- .../tests/instruction_decoder_test.rs | 6 +- .../src/account_loader/accounts.rs | 4 +- .../src/account_loader/derived_accounts.rs | 32 +- .../manual-test-pinocchio/src/all/accounts.rs | 6 +- .../manual-test-pinocchio/src/all/derived.rs | 34 +- .../src/all/derived_accounts.rs | 37 +- .../manual-test-pinocchio/src/ata/derived.rs | 9 +- .../src/derived_decompress.rs | 6 +- .../src/derived_light_config.rs | 10 +- sdk-tests/manual-test-pinocchio/src/lib.rs | 24 +- .../manual-test-pinocchio/src/pda/accounts.rs | 4 +- .../src/pda/derived_accounts.rs | 25 +- .../src/token_account/accounts.rs | 3 +- .../src/token_account/derived.rs | 4 +- .../src/two_mints/derived.rs | 26 +- .../tests/account_loader.rs | 2 +- .../manual-test-pinocchio/tests/shared.rs | 6 +- sdk-tests/manual-test-pinocchio/tests/test.rs | 8 +- .../src/account_loader/derived_accounts.rs | 26 +- sdk-tests/manual-test/src/all/derived.rs | 26 +- .../manual-test/src/all/derived_accounts.rs | 6 +- sdk-tests/manual-test/src/ata/derived.rs | 9 +- sdk-tests/manual-test/src/derived_compress.rs | 5 +- .../manual-test/src/derived_light_config.rs | 2 +- .../manual-test/src/pda/derived_accounts.rs | 22 +- .../manual-test/src/token_account/derived.rs | 4 +- .../manual-test/src/two_mints/derived.rs | 20 +- sdk-tests/manual-test/tests/account_loader.rs | 2 +- sdk-tests/manual-test/tests/shared.rs | 2 +- sdk-tests/manual-test/tests/test.rs | 2 +- sdk-tests/pinocchio-derive-test/Cargo.toml | 10 +- .../src/instruction_accounts.rs | 11 +- sdk-tests/pinocchio-derive-test/src/lib.rs | 150 ++- sdk-tests/pinocchio-derive-test/src/state.rs | 2 +- .../pinocchio-derive-test/tests/shared/mod.rs | 118 +++ sdk-tests/pinocchio-derive-test/tests/test.rs | 943 ------------------ .../tests/test_create_all.rs | 208 ++++ .../tests/test_create_ata.rs | 87 ++ .../tests/test_create_mint.rs | 88 ++ .../tests/test_create_pda.rs | 77 ++ .../tests/test_create_token_vault.rs | 91 ++ .../tests/test_create_two_mints.rs | 117 +++ .../tests/test_create_zero_copy_record.rs | 73 ++ .../single-account-loader-test/src/lib.rs | 2 +- sdk-tests/single-ata-test/src/lib.rs | 4 +- sdk-tests/single-mint-test/src/lib.rs | 2 +- .../src/instruction_accounts.rs | 2 +- sdk-tests/single-token-test/src/lib.rs | 2 +- 162 files changed, 1808 insertions(+), 1963 deletions(-) delete mode 100644 program-libs/account-checks/src/create_pda.rs delete mode 100644 sdk-libs/sdk-types/src/interface/program/compression/close.rs create mode 100644 sdk-tests/pinocchio-derive-test/tests/shared/mod.rs delete mode 100644 sdk-tests/pinocchio-derive-test/tests/test.rs create mode 100644 sdk-tests/pinocchio-derive-test/tests/test_create_all.rs create mode 100644 sdk-tests/pinocchio-derive-test/tests/test_create_ata.rs create mode 100644 sdk-tests/pinocchio-derive-test/tests/test_create_mint.rs create mode 100644 sdk-tests/pinocchio-derive-test/tests/test_create_pda.rs create mode 100644 sdk-tests/pinocchio-derive-test/tests/test_create_token_vault.rs create mode 100644 sdk-tests/pinocchio-derive-test/tests/test_create_two_mints.rs create mode 100644 sdk-tests/pinocchio-derive-test/tests/test_create_zero_copy_record.rs diff --git a/Cargo.lock b/Cargo.lock index c7c1e71e0b..cd1a74d598 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3466,6 +3466,7 @@ dependencies = [ name = "light-account" version = "0.1.0" dependencies = [ + "anchor-lang", "light-account-checks", "light-compressed-account", "light-compressible", @@ -5148,6 +5149,39 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b971851087bc3699b001954ad02389d50c41405ece3548cbcafc88b3e20017a" +[[package]] +name = "pinocchio-derive-test" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "borsh 0.10.4", + "bytemuck", + "light-account", + "light-anchor-spl", + "light-client", + "light-compressed-account", + "light-compressible", + "light-hasher", + "light-macros", + "light-program-test", + "light-sdk-macros", + "light-sdk-types", + "light-test-utils", + "light-token", + "light-token-interface", + "light-token-types", + "solana-account-info", + "solana-instruction", + "solana-keypair", + "solana-msg 2.2.1", + "solana-program", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "solana-sdk", + "solana-signer", + "tokio", +] + [[package]] name = "pinocchio-log" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 4f7dbb7d65..f593c04cb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,7 @@ members = [ "sdk-tests/csdk-anchor-full-derived-test-sdk", "sdk-tests/single-mint-test", "sdk-tests/single-pda-test", - # "sdk-tests/single-pda-derive-test", + "sdk-tests/pinocchio-derive-test", "sdk-tests/single-account-loader-test", "sdk-tests/single-ata-test", "sdk-tests/single-token-test", diff --git a/program-libs/account-checks/src/account_info/pinocchio.rs b/program-libs/account-checks/src/account_info/pinocchio.rs index e22feb165f..968b52e2a8 100644 --- a/program-libs/account-checks/src/account_info/pinocchio.rs +++ b/program-libs/account-checks/src/account_info/pinocchio.rs @@ -1,5 +1,4 @@ -use super::account_info_trait::AccountInfoTrait; -use super::account_meta_trait::AccountMetaTrait; +use super::{account_info_trait::AccountInfoTrait, account_meta_trait::AccountMetaTrait}; use crate::error::AccountError; /// Owned account meta for pinocchio. @@ -209,13 +208,11 @@ impl AccountInfoTrait for pinocchio::account_info::AccountInfo { } fn realloc(&self, new_len: usize, _zero_init: bool) -> Result<(), AccountError> { - self.resize(new_len).map_err(|e| AccountError::from(e)) + self.resize(new_len).map_err(AccountError::from) } fn sub_lamports(&self, amount: u64) -> Result<(), AccountError> { - let mut lamports = self - .try_borrow_mut_lamports() - .map_err(AccountError::from)?; + let mut lamports = self.try_borrow_mut_lamports().map_err(AccountError::from)?; *lamports = lamports .checked_sub(amount) .ok_or(AccountError::ArithmeticOverflow)?; @@ -223,9 +220,7 @@ impl AccountInfoTrait for pinocchio::account_info::AccountInfo { } fn add_lamports(&self, amount: u64) -> Result<(), AccountError> { - let mut lamports = self - .try_borrow_mut_lamports() - .map_err(AccountError::from)?; + let mut lamports = self.try_borrow_mut_lamports().map_err(AccountError::from)?; *lamports = lamports .checked_add(amount) .ok_or(AccountError::ArithmeticOverflow)?; @@ -249,6 +244,7 @@ impl AccountInfoTrait for pinocchio::account_info::AccountInfo { ) -> Result<(), AccountError> { extern crate alloc; use alloc::vec::Vec; + use pinocchio::instruction::{Seed, Signer}; let pda_seeds_vec: Vec = pda_seeds.iter().map(|s| Seed::from(*s)).collect(); @@ -257,8 +253,7 @@ impl AccountInfoTrait for pinocchio::account_info::AccountInfo { // Only build payer signer when rent_payer is itself a PDA. // Passing empty seeds to invoke_signed causes create_program_address(&[], program_id) // which can fail if the result happens to land on the ed25519 curve. - let payer_seeds_vec: Vec = - rent_payer_seeds.iter().map(|s| Seed::from(*s)).collect(); + let payer_seeds_vec: Vec = rent_payer_seeds.iter().map(|s| Seed::from(*s)).collect(); let has_payer_seeds = !rent_payer_seeds.is_empty(); // Cold path: account already has lamports (e.g., attacker donation). @@ -268,7 +263,7 @@ impl AccountInfoTrait for pinocchio::account_info::AccountInfo { account: self, owner, } - .invoke_signed(&[pda_signer.clone()]) + .invoke_signed(core::slice::from_ref(&pda_signer)) .map_err(AccountError::from)?; pinocchio_system::instructions::Allocate { @@ -331,6 +326,7 @@ impl AccountInfoTrait for pinocchio::account_info::AccountInfo { ) -> Result<(), AccountError> { extern crate alloc; use alloc::vec::Vec; + use pinocchio::instruction::{Seed, Signer}; let seeds_vec: Vec = signer_seeds.iter().map(|s| Seed::from(*s)).collect(); @@ -354,6 +350,7 @@ impl AccountInfoTrait for pinocchio::account_info::AccountInfo { ) -> Result<(), AccountError> { extern crate alloc; use alloc::vec::Vec; + use pinocchio::instruction::{AccountMeta, Seed, Signer}; // Build owned pubkeys so AccountMeta can borrow them @@ -376,8 +373,7 @@ impl AccountInfoTrait for pinocchio::account_info::AccountInfo { data: instruction_data, }; - let info_refs: Vec<&pinocchio::account_info::AccountInfo> = - account_infos.iter().collect(); + let info_refs: Vec<&pinocchio::account_info::AccountInfo> = account_infos.iter().collect(); // Build signers from seeds let signer_seed_vecs: Vec> = signer_seeds @@ -389,11 +385,7 @@ impl AccountInfoTrait for pinocchio::account_info::AccountInfo { .map(|seeds| Signer::from(&seeds[..])) .collect(); - pinocchio::cpi::invoke_signed_with_bounds::<64>( - &instruction, - &info_refs, - &signers, - ) - .map_err(AccountError::from) + pinocchio::cpi::invoke_signed_with_bounds::<64>(&instruction, &info_refs, &signers) + .map_err(AccountError::from) } } diff --git a/program-libs/account-checks/src/account_info/solana.rs b/program-libs/account-checks/src/account_info/solana.rs index 8bc17648fc..13ebae8e20 100644 --- a/program-libs/account-checks/src/account_info/solana.rs +++ b/program-libs/account-checks/src/account_info/solana.rs @@ -1,5 +1,4 @@ -use super::account_info_trait::AccountInfoTrait; -use super::account_meta_trait::AccountMetaTrait; +use super::{account_info_trait::AccountInfoTrait, account_meta_trait::AccountMetaTrait}; use crate::error::AccountError; /// Implement trait for solana AccountInfo @@ -294,40 +293,29 @@ fn create_pda_with_lamports_solana<'a>( // Assign owner let assign_ix = system_instruction::assign(account.key, owner); - invoke_signed(&assign_ix, &[account.clone()], &[pda_seeds]) + invoke_signed(&assign_ix, std::slice::from_ref(account), &[pda_seeds]) .map_err(|_| AccountError::InvalidAccount)?; // Allocate space let allocate_ix = system_instruction::allocate(account.key, space); - invoke_signed(&allocate_ix, &[account.clone()], &[pda_seeds]) + invoke_signed(&allocate_ix, std::slice::from_ref(account), &[pda_seeds]) .map_err(|_| AccountError::InvalidAccount)?; // Transfer remaining lamports for rent-exemption if needed if lamports > current_lamports { - let transfer_ix = system_instruction::transfer( - rent_payer.key, - account.key, - lamports - current_lamports, - ); + let transfer_ix = + system_instruction::transfer(rent_payer.key, account.key, lamports - current_lamports); // Only include rent_payer_seeds when the payer is itself a PDA. if rent_payer_seeds.is_empty() { invoke_signed( &transfer_ix, - &[ - rent_payer.clone(), - account.clone(), - system_program.clone(), - ], + &[rent_payer.clone(), account.clone(), system_program.clone()], &[], ) } else { invoke_signed( &transfer_ix, - &[ - rent_payer.clone(), - account.clone(), - system_program.clone(), - ], + &[rent_payer.clone(), account.clone(), system_program.clone()], &[rent_payer_seeds], ) } diff --git a/program-libs/account-checks/src/create_pda.rs b/program-libs/account-checks/src/create_pda.rs deleted file mode 100644 index bd0c33d776..0000000000 --- a/program-libs/account-checks/src/create_pda.rs +++ /dev/null @@ -1,129 +0,0 @@ -use crate::error::LightSdkTypesError; -pub mod solana { - - impl AccountInfoTrait for solana_account_info::AccountInfo { - - /// Creates a PDA account, handling the case where the account already has lamports. - /// - /// This function handles the edge case where an attacker might have donated lamports - /// to the PDA address before decompression. In that case, `CreateAccount` would fail, - /// so we fall back to `Assign + Allocate + Transfer`. - /// - /// # Arguments - /// * `rent_sponsor` - Account paying for rent (must be a PDA derived from the calling program) - /// * `rent_sponsor_seeds` - Seeds for the rent sponsor PDA (including bump) for signing - /// * `solana_account` - The PDA account to create - /// * `lamports` - Amount of lamports for rent-exemption - /// * `space` - Size of the account in bytes - /// * `owner` - Program that will own the account - /// * `seeds` - Seeds for the target PDA (including bump) for signing - /// * `system_program` - System program - #[inline(never)] - #[allow(clippy::too_many_arguments)] - pub fn create_pda<'info>( - rent_sponsor: &AccountInfo<'info>, - rent_sponsor_seeds: &[&[u8]], - solana_account: &AccountInfo<'info>, - lamports: u64, - space: u64, - owner: &Pubkey, - seeds: &[&[u8]], - ) -> Result<(), LightSdkTypesError> { - use solana_cpi::invoke_signed; - use solana_pubkey::Pubkey; - use solana_system_interface::instruction as system_instruction; - - use crate::AccountInfoTrait; - - /// Cold path: Account already has lamports (e.g., attacker donation). - /// Uses Assign + Allocate + Transfer instead of CreateAccount which would fail. - #[cold] - #[allow(clippy::too_many_arguments)] - fn create_pda_account_with_lamports<'info>( - rent_sponsor: &AccountInfo<'info>, - rent_sponsor_seeds: &[&[u8]], - solana_account: &AccountInfo<'info>, - lamports: u64, - space: u64, - owner: &Pubkey, - seeds: &[&[u8]], - ) -> Result<(), LightSdkTypesError> { - let current_lamports = solana_account.lamports(); - - // Assign owner - let assign_ix = system_instruction::assign(solana_account.key, owner); - invoke_signed( - &assign_ix, - &[solana_account.clone()], - &[seeds], - ) - .map_err(LightSdkTypesError::ProgramError)?; - - // Allocate space - let allocate_ix = system_instruction::allocate(solana_account.key, space); - invoke_signed( - &allocate_ix, - &[solana_account.clone()], - &[seeds], - ) - .map_err(LightSdkTypesError::ProgramError)?; - - // Transfer remaining lamports for rent-exemption if needed - if lamports > current_lamports { - let transfer_ix = system_instruction::transfer( - rent_sponsor.key, - solana_account.key, - lamports - current_lamports, - ); - // Include rent sponsor seeds so the PDA can sign for the transfer - invoke_signed( - &transfer_ix, - &[ - rent_sponsor.clone(), - solana_account.clone(), - system_program.clone(), - ], - &[rent_sponsor_seeds], - ) - .map_err(LightSdkTypesError::ProgramError)?; - } - - Ok(()) - } - - // Cold path: account already has lamports (e.g., attacker donation) - if solana_account.lamports() > 0 { - return create_pda_account_with_lamports( - rent_sponsor, - rent_sponsor_seeds, - solana_account, - lamports, - space, - owner, - seeds, - ); - } - - // Normal path: CreateAccount - // Include both rent sponsor seeds (payer) and PDA seeds (new account) - let create_account_ix = system_instruction::create_account( - rent_sponsor.key, - solana_account.key, - lamports, - space, - owner, - ); - - invoke_signed( - &create_account_ix, - &[ - rent_sponsor.clone(), - solana_account.clone(), - ], - &[rent_sponsor_seeds, seeds], - ) - .map_err(LightSdkTypesError::ProgramError) - } - } - } -} diff --git a/program-libs/account-checks/src/lib.rs b/program-libs/account-checks/src/lib.rs index 56d4decff1..8bbd085b3c 100644 --- a/program-libs/account-checks/src/lib.rs +++ b/program-libs/account-checks/src/lib.rs @@ -21,8 +21,10 @@ pub mod discriminator; pub mod error; pub mod packed_accounts; -pub use account_info::account_info_trait::{AccountInfoTrait, CpiMeta}; -pub use account_info::account_meta_trait::AccountMetaTrait; +pub use account_info::{ + account_info_trait::{AccountInfoTrait, CpiMeta}, + account_meta_trait::AccountMetaTrait, +}; pub use account_iterator::AccountIterator; pub use close_account::close_account; pub use error::AccountError; diff --git a/program-tests/create-address-test-program/src/lib.rs b/program-tests/create-address-test-program/src/lib.rs index c81602e4b9..1299cf3dbf 100644 --- a/program-tests/create-address-test-program/src/lib.rs +++ b/program-tests/create-address-test-program/src/lib.rs @@ -22,10 +22,10 @@ use light_compressed_account::instruction_data::{ compressed_proof::CompressedProof, data::NewAddressParamsPacked, }; use light_sdk::{ - constants::LIGHT_SYSTEM_PROGRAM_ID, cpi::{ - invoke::invoke_light_system_program, CpiAccountsTrait, + invoke::invoke_light_system_program, v1::lowlevel::{get_account_metas_from_config, CpiInstructionConfig}, + CpiAccountsTrait, }, light_account_checks::CpiMeta, }; diff --git a/sdk-libs/account-pinocchio/src/lib.rs b/sdk-libs/account-pinocchio/src/lib.rs index f04b2c6f83..121aa638db 100644 --- a/sdk-libs/account-pinocchio/src/lib.rs +++ b/sdk-libs/account-pinocchio/src/lib.rs @@ -5,11 +5,9 @@ pub use pinocchio::account_info::AccountInfo; // ===== TYPE ALIASES (structs generic over AI, specialized with pinocchio AccountInfo) ===== // Note: pinocchio's AccountInfo has no lifetime parameter, so aliases have fewer lifetimes. -pub type CpiAccounts<'c> = - light_sdk_types::cpi_accounts::v2::CpiAccounts<'c, AccountInfo>; +pub type CpiAccounts<'c> = light_sdk_types::cpi_accounts::v2::CpiAccounts<'c, AccountInfo>; -pub type CpiAccountsV1<'c> = - light_sdk_types::cpi_accounts::v1::CpiAccounts<'c, AccountInfo>; +pub type CpiAccountsV1<'c> = light_sdk_types::cpi_accounts::v1::CpiAccounts<'c, AccountInfo>; pub type CompressCtx<'a> = light_sdk_types::interface::program::compression::processor::CompressCtx<'a, AccountInfo>; @@ -28,108 +26,105 @@ pub type CpiContextWriteAccounts<'a> = #[cfg(all(not(target_os = "solana"), feature = "std"))] pub type PackedAccounts = - light_sdk_types::interface::instruction::PackedAccounts< - solana_instruction::AccountMeta, - >; + light_sdk_types::interface::instruction::PackedAccounts; // ===== RE-EXPORTED TRAITS (generic over AI, used with explicit AccountInfo in impls) ===== -pub use light_sdk_types::interface::accounts::finalize::{LightFinalize, LightPreInit}; -pub use light_sdk_types::interface::cpi::{ - account::CpiAccountsTrait, invoke::InvokeLightSystemProgram, LightCpi, -}; -pub use light_sdk_types::interface::program::decompression::processor::DecompressVariant; - +pub use light_account_checks::close_account; +#[cfg(feature = "token")] +pub use light_compressed_account::instruction_data::compressed_proof::CompressedProof; // ===== RE-EXPORTED CONCRETE TRAITS (no AI parameter) ===== - pub use light_sdk_types::interface::account::compression_info::{ claim_completed_epoch_rent, CompressAs, CompressedAccountData, CompressedInitSpace, CompressionInfo, CompressionInfoField, CompressionState, HasCompressionInfo, Space, COMPRESSION_INFO_SIZE, OPTION_COMPRESSION_INFO_SPACE, }; -pub use light_sdk_types::interface::account::light_account::{AccountType, LightAccount}; #[cfg(all(not(target_os = "solana"), feature = "std"))] pub use light_sdk_types::interface::account::pack::Pack; -pub use light_sdk_types::interface::account::pack::Unpack; -pub use light_sdk_types::interface::account::pda_seeds::{HasTokenVariant, PdaSeedDerivation}; -pub use light_sdk_types::interface::accounts::init_compressed_account::{ - prepare_compressed_account_on_init, reimburse_rent, -}; -pub use light_sdk_types::interface::create_accounts_proof::CreateAccountsProof; -pub use light_sdk_types::interface::program::variant::{ - IntoVariant, LightAccountVariantTrait, PackedLightAccountVariantTrait, -}; -pub use light_sdk_types::interface::rent; - -// ===== RE-EXPORTED GENERIC FUNCTIONS (AI inferred from call-site args) ===== - -pub use light_sdk_types::interface::cpi::invoke::invoke_light_system_program; -pub use light_sdk_types::interface::cpi::invoke::invoke_write_pdas_to_cpi_context; -pub use light_sdk_types::interface::program::compression::close::close; -pub use light_sdk_types::interface::program::compression::pda::prepare_account_for_compression; -pub use light_sdk_types::interface::program::compression::processor::{ - process_compress_pda_accounts_idempotent, CompressAndCloseParams, -}; -pub use light_sdk_types::interface::program::config::{ - process_initialize_light_config_checked, process_update_light_config, - InitializeLightConfigParams, LightConfig, UpdateLightConfigParams, COMPRESSIBLE_CONFIG_SEED, - MAX_ADDRESS_TREES_PER_SPACE, -}; -pub use light_sdk_types::interface::program::config::create::process_initialize_light_config; -pub use light_sdk_types::interface::program::decompression::pda::prepare_account_for_decompression; -pub use light_sdk_types::interface::program::decompression::processor::{ - process_decompress_pda_accounts_idempotent, DecompressIdempotentParams, -}; -pub use light_sdk_types::interface::program::validation::{ - extract_tail_accounts, is_pda_initialized, should_skip_compression, - split_at_system_accounts_offset, validate_compress_accounts, validate_decompress_accounts, -}; - // ===== TOKEN-GATED RE-EXPORTS ===== - #[cfg(feature = "token")] pub use light_sdk_types::interface::account::token_seeds::{ PackedTokenData, TokenDataWithPackedSeeds, TokenDataWithSeeds, }; -#[cfg(feature = "token")] -pub use light_sdk_types::interface::program::decompression::processor::process_decompress_accounts_idempotent; -#[cfg(feature = "token")] -pub use light_sdk_types::interface::program::decompression::token::prepare_token_account_for_decompression; -#[cfg(feature = "token")] -pub use light_sdk_types::interface::program::variant::{PackedTokenSeeds, UnpackedTokenSeeds}; - // Mint creation CPI types and functions #[cfg(feature = "token")] pub use light_sdk_types::interface::cpi::create_mints::{ - CreateMintsCpi, CreateMintsInfraAccounts, CreateMintsParams, SingleMintParams, - get_output_queue_next_index, invoke_create_mints, - DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, derive_mint_compressed_address as derive_mint_compressed_address_generic, + get_output_queue_next_index, invoke_create_mints, CreateMintsCpi, CreateMintsInfraAccounts, + CreateMintsParams, SingleMintParams, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, }; - // Token account/ATA creation CPI types and functions #[cfg(feature = "token")] pub use light_sdk_types::interface::cpi::create_token_accounts::{ - CreateTokenAccountCpi, CreateTokenAccountRentFreeCpi, - CreateTokenAtaCpi, CreateTokenAtaCpiIdempotent, CreateTokenAtaRentFreeCpi, derive_associated_token_account as derive_associated_token_account_generic, + CreateTokenAccountCpi, CreateTokenAccountRentFreeCpi, CreateTokenAtaCpi, + CreateTokenAtaCpiIdempotent, CreateTokenAtaRentFreeCpi, }; - -// Token-interface re-exports for macro-generated code +// ===== RE-EXPORTED GENERIC FUNCTIONS (AI inferred from call-site args) ===== +pub use light_sdk_types::interface::cpi::invoke::invoke_light_system_program; #[cfg(feature = "token")] -pub use light_token_interface::instructions::extensions::TokenMetadataInstructionData; +pub use light_sdk_types::interface::program::decompression::processor::process_decompress_accounts_idempotent; +#[cfg(feature = "token")] +pub use light_sdk_types::interface::program::decompression::token::prepare_token_account_for_decompression; +#[cfg(feature = "token")] +pub use light_sdk_types::interface::program::variant::{PackedTokenSeeds, UnpackedTokenSeeds}; +pub use light_sdk_types::interface::{ + account::{ + light_account::{AccountType, LightAccount}, + pack::Unpack, + pda_seeds::{HasTokenVariant, PdaSeedDerivation}, + }, + accounts::{ + finalize::{LightFinalize, LightPreInit}, + init_compressed_account::{prepare_compressed_account_on_init, reimburse_rent}, + }, + cpi::{ + account::CpiAccountsTrait, + invoke::{invoke_write_pdas_to_cpi_context, InvokeLightSystemProgram}, + LightCpi, + }, + create_accounts_proof::CreateAccountsProof, + program::{ + compression::{ + pda::prepare_account_for_compression, + processor::{process_compress_pda_accounts_idempotent, CompressAndCloseParams}, + }, + config::{ + create::process_initialize_light_config, process_initialize_light_config_checked, + process_update_light_config, InitializeLightConfigParams, LightConfig, + UpdateLightConfigParams, COMPRESSIBLE_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE, + }, + decompression::{ + pda::prepare_account_for_decompression, + processor::{ + process_decompress_pda_accounts_idempotent, DecompressIdempotentParams, + DecompressVariant, + }, + }, + validation::{ + extract_tail_accounts, is_pda_initialized, should_skip_compression, + split_at_system_accounts_offset, validate_compress_accounts, + validate_decompress_accounts, + }, + variant::{IntoVariant, LightAccountVariantTrait, PackedLightAccountVariantTrait}, + }, + rent, +}; #[cfg(feature = "token")] pub use light_token_interface::instructions::extensions::ExtensionInstructionData as TokenExtensionInstructionData; +// Token-interface re-exports for macro-generated code #[cfg(feature = "token")] -pub use light_compressed_account::instruction_data::compressed_proof::CompressedProof; +pub use light_token_interface::instructions::extensions::TokenMetadataInstructionData; #[cfg(feature = "token")] pub mod token { - pub use light_sdk_types::interface::account::token_seeds::{ - ExtensionInstructionData, MultiInputTokenDataWithContext, PackedTokenData, - TokenDataWithPackedSeeds, TokenDataWithSeeds, + pub use light_sdk_types::interface::{ + account::token_seeds::{ + ExtensionInstructionData, MultiInputTokenDataWithContext, PackedTokenData, + TokenDataWithPackedSeeds, TokenDataWithSeeds, + }, + program::decompression::token::prepare_token_account_for_decompression, }; - pub use light_sdk_types::interface::program::decompression::token::prepare_token_account_for_decompression; } pub mod compression_info { @@ -154,26 +149,29 @@ pub mod account_meta { // ===== ACCOUNT-CHECKS RE-EXPORTS (used by macro-generated code) ===== pub extern crate light_account_checks; -pub use light_account_checks::packed_accounts; -pub use light_account_checks::{AccountInfoTrait, AccountMetaTrait}; -pub use light_account_checks::account_info::pinocchio::OwnedAccountMeta; - // ===== CONVENIENCE RE-EXPORTS ===== - -pub use light_account_checks::discriminator::Discriminator as LightDiscriminator; +pub use light_account_checks::{ + account_info::pinocchio::OwnedAccountMeta, discriminator::Discriminator as LightDiscriminator, + packed_accounts, AccountInfoTrait, AccountMetaTrait, +}; pub use light_compressed_account::instruction_data::compressed_proof::ValidityProof; pub use light_macros::{derive_light_cpi_signer, derive_light_cpi_signer_pda}; pub use light_sdk_macros::{ - CompressAs, Compressible, HasCompressionInfo, - LightAccount, LightDiscriminator, LightHasher, LightHasherSha, - LightProgram, + CompressAs, Compressible, HasCompressionInfo, LightAccount, LightDiscriminator, LightHasher, + LightHasherSha, LightProgram, }; -pub use light_sdk_types::error::LightSdkTypesError; -pub use light_sdk_types::instruction::*; -pub use light_sdk_types::{constants, CpiSigner}; +pub use light_sdk_types::{constants, error::LightSdkTypesError, instruction::*, CpiSigner}; // ===== UTILITY FUNCTIONS ===== +/// Converts a [`LightSdkTypesError`] into a [`pinocchio::program_error::ProgramError`]. +/// +/// Use with `.map_err(light_err)` in pinocchio instruction handlers to disambiguate +/// the multiple `From` implementations on `LightSdkTypesError`. +pub fn light_err(e: LightSdkTypesError) -> pinocchio::program_error::ProgramError { + pinocchio::program_error::ProgramError::Custom(u32::from(e)) +} + /// Derives the rent sponsor PDA for a given program. /// /// Seeds: `["rent_sponsor"]` @@ -206,9 +204,6 @@ pub fn derive_mint_compressed_address( /// /// Returns `([u8; 32], u8)` -- the ATA address and bump seed. #[cfg(feature = "token")] -pub fn derive_associated_token_account( - owner: &[u8; 32], - mint: &[u8; 32], -) -> ([u8; 32], u8) { +pub fn derive_associated_token_account(owner: &[u8; 32], mint: &[u8; 32]) -> ([u8; 32], u8) { derive_associated_token_account_generic::(owner, mint) } diff --git a/sdk-libs/account/Cargo.toml b/sdk-libs/account/Cargo.toml index a260f78464..d6e54d5382 100644 --- a/sdk-libs/account/Cargo.toml +++ b/sdk-libs/account/Cargo.toml @@ -13,7 +13,7 @@ alloc = ["light-sdk-types/alloc", "light-compressed-account/alloc"] token = ["light-sdk-types/token", "dep:light-token-interface"] poseidon = ["light-sdk-types/poseidon", "light-hasher/poseidon"] sha256 = ["light-sdk-types/sha256", "light-hasher/sha256"] -anchor = ["light-sdk-types/anchor"] +anchor = ["light-sdk-types/anchor", "dep:anchor-lang"] [dependencies] light-sdk-types = { workspace = true, features = ["std", "v2", "cpi-context"] } @@ -24,6 +24,7 @@ light-hasher = { workspace = true, default-features = false } light-compressed-account = { workspace = true } light-compressible = { workspace = true } light-token-interface = { workspace = true, optional = true } +anchor-lang = { workspace = true, optional = true } solana-account-info = { workspace = true } solana-instruction = { workspace = true } solana-pubkey = { workspace = true } diff --git a/sdk-libs/account/src/lib.rs b/sdk-libs/account/src/lib.rs index b271fa7f2b..9bef736e59 100644 --- a/sdk-libs/account/src/lib.rs +++ b/sdk-libs/account/src/lib.rs @@ -36,96 +36,93 @@ pub type PackedAccounts = // ===== RE-EXPORTED TRAITS (generic over AI, used with explicit AccountInfo in impls) ===== -pub use light_sdk_types::interface::accounts::finalize::{LightFinalize, LightPreInit}; -pub use light_sdk_types::interface::cpi::{ - account::CpiAccountsTrait, invoke::InvokeLightSystemProgram, LightCpi, -}; -pub use light_sdk_types::interface::program::decompression::processor::DecompressVariant; - +pub use light_account_checks::close_account; +#[cfg(feature = "token")] +pub use light_compressed_account::instruction_data::compressed_proof::CompressedProof; // ===== RE-EXPORTED CONCRETE TRAITS (no AI parameter) ===== - pub use light_sdk_types::interface::account::compression_info::{ claim_completed_epoch_rent, CompressAs, CompressedAccountData, CompressedInitSpace, CompressionInfo, CompressionInfoField, CompressionState, HasCompressionInfo, Space, COMPRESSION_INFO_SIZE, OPTION_COMPRESSION_INFO_SPACE, }; -pub use light_sdk_types::interface::account::light_account::{AccountType, LightAccount}; #[cfg(not(target_os = "solana"))] pub use light_sdk_types::interface::account::pack::Pack; -pub use light_sdk_types::interface::account::pack::Unpack; -pub use light_sdk_types::interface::account::pda_seeds::{HasTokenVariant, PdaSeedDerivation}; -pub use light_sdk_types::interface::accounts::init_compressed_account::{ - prepare_compressed_account_on_init, reimburse_rent, -}; -pub use light_sdk_types::interface::create_accounts_proof::CreateAccountsProof; -pub use light_sdk_types::interface::program::variant::{ - IntoVariant, LightAccountVariantTrait, PackedLightAccountVariantTrait, -}; -pub use light_sdk_types::interface::rent; - -// ===== RE-EXPORTED GENERIC FUNCTIONS (AI inferred from call-site args) ===== - -pub use light_sdk_types::interface::cpi::invoke::invoke_light_system_program; -pub use light_sdk_types::interface::cpi::invoke::invoke_write_pdas_to_cpi_context; -pub use light_sdk_types::interface::program::compression::close::close; -pub use light_sdk_types::interface::program::compression::pda::prepare_account_for_compression; -pub use light_sdk_types::interface::program::compression::processor::{ - process_compress_pda_accounts_idempotent, CompressAndCloseParams, -}; -pub use light_sdk_types::interface::program::config::{ - process_initialize_light_config_checked, process_update_light_config, - InitializeLightConfigParams, LightConfig, UpdateLightConfigParams, COMPRESSIBLE_CONFIG_SEED, - MAX_ADDRESS_TREES_PER_SPACE, -}; -pub use light_sdk_types::interface::program::decompression::pda::prepare_account_for_decompression; -pub use light_sdk_types::interface::program::decompression::processor::{ - process_decompress_pda_accounts_idempotent, DecompressIdempotentParams, -}; -pub use light_sdk_types::interface::program::validation::{ - extract_tail_accounts, is_pda_initialized, should_skip_compression, - split_at_system_accounts_offset, validate_compress_accounts, validate_decompress_accounts, -}; - // ===== TOKEN-GATED RE-EXPORTS ===== - #[cfg(feature = "token")] pub use light_sdk_types::interface::account::token_seeds::{ PackedTokenData, TokenDataWithPackedSeeds, TokenDataWithSeeds, }; -#[cfg(feature = "token")] -pub use light_sdk_types::interface::program::decompression::processor::process_decompress_accounts_idempotent; -#[cfg(feature = "token")] -pub use light_sdk_types::interface::program::decompression::token::prepare_token_account_for_decompression; -#[cfg(feature = "token")] -pub use light_sdk_types::interface::program::variant::{PackedTokenSeeds, UnpackedTokenSeeds}; - // Mint creation CPI types and functions #[cfg(feature = "token")] pub use light_sdk_types::interface::cpi::create_mints::{ - CreateMintsCpi, CreateMintsInfraAccounts, CreateMintsParams, SingleMintParams, - get_output_queue_next_index, invoke_create_mints, - DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, derive_mint_compressed_address as derive_mint_compressed_address_generic, + get_output_queue_next_index, invoke_create_mints, CreateMintsCpi, CreateMintsInfraAccounts, + CreateMintsParams, SingleMintParams, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, }; - // Token account/ATA creation CPI types and functions #[cfg(feature = "token")] pub use light_sdk_types::interface::cpi::create_token_accounts::{ - CreateTokenAccountCpi, CreateTokenAccountRentFreeCpi, - CreateTokenAtaCpi, CreateTokenAtaCpiIdempotent, CreateTokenAtaRentFreeCpi, derive_associated_token_account as derive_associated_token_account_generic, + CreateTokenAccountCpi, CreateTokenAccountRentFreeCpi, CreateTokenAtaCpi, + CreateTokenAtaCpiIdempotent, CreateTokenAtaRentFreeCpi, }; - -// Token-interface re-exports for macro-generated code +// ===== RE-EXPORTED GENERIC FUNCTIONS (AI inferred from call-site args) ===== +pub use light_sdk_types::interface::cpi::invoke::invoke_light_system_program; #[cfg(feature = "token")] -pub use light_token_interface::instructions::extensions::TokenMetadataInstructionData; +pub use light_sdk_types::interface::program::decompression::processor::process_decompress_accounts_idempotent; +#[cfg(feature = "token")] +pub use light_sdk_types::interface::program::decompression::token::prepare_token_account_for_decompression; +#[cfg(feature = "token")] +pub use light_sdk_types::interface::program::variant::{PackedTokenSeeds, UnpackedTokenSeeds}; +pub use light_sdk_types::interface::{ + account::{ + light_account::{AccountType, LightAccount}, + pack::Unpack, + pda_seeds::{HasTokenVariant, PdaSeedDerivation}, + }, + accounts::{ + finalize::{LightFinalize, LightPreInit}, + init_compressed_account::{prepare_compressed_account_on_init, reimburse_rent}, + }, + cpi::{ + account::CpiAccountsTrait, + invoke::{invoke_write_pdas_to_cpi_context, InvokeLightSystemProgram}, + LightCpi, + }, + create_accounts_proof::CreateAccountsProof, + program::{ + compression::{ + pda::prepare_account_for_compression, + processor::{process_compress_pda_accounts_idempotent, CompressAndCloseParams}, + }, + config::{ + process_initialize_light_config_checked, process_update_light_config, + InitializeLightConfigParams, LightConfig, UpdateLightConfigParams, + COMPRESSIBLE_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE, + }, + decompression::{ + pda::prepare_account_for_decompression, + processor::{ + process_decompress_pda_accounts_idempotent, DecompressIdempotentParams, + DecompressVariant, + }, + }, + validation::{ + extract_tail_accounts, is_pda_initialized, should_skip_compression, + split_at_system_accounts_offset, validate_compress_accounts, + validate_decompress_accounts, + }, + variant::{IntoVariant, LightAccountVariantTrait, PackedLightAccountVariantTrait}, + }, + rent, +}; #[cfg(feature = "token")] pub use light_token_interface::instructions::extensions::ExtensionInstructionData as TokenExtensionInstructionData; +// Token-interface re-exports for macro-generated code #[cfg(feature = "token")] -pub use light_compressed_account::instruction_data::compressed_proof::CompressedProof; +pub use light_token_interface::instructions::extensions::TokenMetadataInstructionData; #[cfg(feature = "token")] pub use light_token_interface::state::AdditionalMetadata; - /// Re-export Token state struct for client-side use. #[cfg(feature = "token")] pub use light_token_interface::state::Token; @@ -133,11 +130,13 @@ pub use light_token_interface::state::Token; /// Token sub-module for paths like `light_account::token::TokenDataWithSeeds`. #[cfg(feature = "token")] pub mod token { - pub use light_sdk_types::interface::account::token_seeds::{ - ExtensionInstructionData, MultiInputTokenDataWithContext, PackedTokenData, - TokenDataWithPackedSeeds, TokenDataWithSeeds, + pub use light_sdk_types::interface::{ + account::token_seeds::{ + ExtensionInstructionData, MultiInputTokenDataWithContext, PackedTokenData, + TokenDataWithPackedSeeds, TokenDataWithSeeds, + }, + program::decompression::token::prepare_token_account_for_decompression, }; - pub use light_sdk_types::interface::program::decompression::token::prepare_token_account_for_decompression; } /// Compression info sub-module for paths like `light_account::compression_info::CompressedInitSpace`. @@ -147,9 +146,10 @@ pub mod compression_info { // ===== CPI / SDK-TYPES RE-EXPORTS ===== -pub use light_sdk_types::cpi_accounts::CpiAccountsConfig; -pub use light_sdk_types::cpi_context_write::CpiContextWriteAccounts; -pub use light_sdk_types::interface::program::config::create::process_initialize_light_config; +pub use light_sdk_types::{ + cpi_accounts::CpiAccountsConfig, cpi_context_write::CpiContextWriteAccounts, + interface::program::config::create::process_initialize_light_config, +}; /// Sub-module for generic `PackedAccounts` (not specialized to AccountMeta). #[cfg(not(target_os = "solana"))] @@ -168,13 +168,13 @@ pub mod account_meta { /// Re-export `light_account_checks` so consumers can use `light_account::light_account_checks::*`. pub extern crate light_account_checks; -pub use light_account_checks::packed_accounts; -pub use light_account_checks::{AccountInfoTrait, AccountMetaTrait}; - // ===== CONVENIENCE RE-EXPORTS ===== - -pub use light_account_checks::discriminator::Discriminator as LightDiscriminator; +pub use light_account_checks::{ + discriminator::Discriminator as LightDiscriminator, packed_accounts, AccountInfoTrait, + AccountMetaTrait, +}; pub use light_compressed_account::instruction_data::compressed_proof::ValidityProof; +pub use light_compressible::rent::RentConfig; pub use light_macros::{derive_light_cpi_signer, derive_light_cpi_signer_pda}; pub use light_sdk_macros::{ // Attribute macros @@ -194,17 +194,18 @@ pub use light_sdk_macros::{ LightHasherSha, LightProgram, }; -pub use light_compressible::rent::RentConfig; -pub use light_sdk_types::error::LightSdkTypesError; -pub use light_sdk_types::instruction::*; -pub use light_sdk_types::interface::account::size::Size; -pub use light_sdk_types::constants::{CPI_AUTHORITY_PDA_SEED, RENT_SPONSOR_SEED}; -pub use light_sdk_types::{constants, CpiSigner}; +pub use light_sdk_types::{ + constants, + constants::{CPI_AUTHORITY_PDA_SEED, RENT_SPONSOR_SEED}, + error::LightSdkTypesError, + instruction::*, + interface::account::size::Size, + CpiSigner, +}; /// Hasher re-exports for macro-generated code paths like `light_account::hasher::DataHasher`. pub mod hasher { - pub use light_hasher::errors::HasherError; - pub use light_hasher::{DataHasher, Hasher}; + pub use light_hasher::{errors::HasherError, DataHasher, Hasher}; } /// Re-export LIGHT_TOKEN_PROGRAM_ID as Pubkey for Anchor's `#[account(address = ...)]`. @@ -221,6 +222,15 @@ pub const LIGHT_TOKEN_RENT_SPONSOR: solana_pubkey::Pubkey = // ===== UTILITY FUNCTIONS ===== +/// Converts a [`LightSdkTypesError`] into an [`anchor_lang::error::Error`]. +/// +/// Use with `.map_err(light_err)` in Anchor instruction handlers to disambiguate +/// the multiple `From` implementations on `LightSdkTypesError`. +#[cfg(feature = "anchor")] +pub fn light_err(e: LightSdkTypesError) -> anchor_lang::error::Error { + anchor_lang::error::Error::from(e) +} + /// Derives the rent sponsor PDA for a given program. /// /// Seeds: `["rent_sponsor"]` @@ -255,7 +265,9 @@ pub fn derive_associated_token_account( owner: &solana_pubkey::Pubkey, mint: &solana_pubkey::Pubkey, ) -> (solana_pubkey::Pubkey, u8) { - let (bytes, bump) = - derive_associated_token_account_generic::>(&owner.to_bytes(), &mint.to_bytes()); + let (bytes, bump) = derive_associated_token_account_generic::>( + &owner.to_bytes(), + &mint.to_bytes(), + ); (solana_pubkey::Pubkey::from(bytes), bump) } diff --git a/sdk-libs/client/src/interface/pack.rs b/sdk-libs/client/src/interface/pack.rs index 90216a8921..dd07fbc1bf 100644 --- a/sdk-libs/client/src/interface/pack.rs +++ b/sdk-libs/client/src/interface/pack.rs @@ -1,8 +1,10 @@ //! Helper for packing validity proofs into remaining accounts. -use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; -use light_sdk::PackedAccountsExt; pub use light_sdk::instruction::{PackedAddressTreeInfo, PackedStateTreeInfo}; +use light_sdk::{ + instruction::{PackedAccounts, SystemAccountMetaConfig}, + PackedAccountsExt, +}; use solana_instruction::AccountMeta; use solana_pubkey::Pubkey; use thiserror::Error; diff --git a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs index 20722e625c..3e0314e411 100644 --- a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs +++ b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs @@ -1,12 +1,12 @@ use light_program_profiler::profile; -// PackedAccounts and AccountMetasVec are only available off-chain (client-side) -#[cfg(not(target_os = "solana"))] -use light_sdk_types::error::LightSdkTypesError; #[cfg(not(target_os = "solana"))] use light_sdk::{ instruction::{AccountMetasVec, PackedAccounts, SystemAccountMetaConfig}, PackedAccountsExt, }; +// PackedAccounts and AccountMetasVec are only available off-chain (client-side) +#[cfg(not(target_os = "solana"))] +use light_sdk_types::error::LightSdkTypesError; use light_token_interface::instructions::transfer2::CompressedCpiContext; #[cfg(not(target_os = "solana"))] use light_token_interface::state::Token; diff --git a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs index 5f310f4d97..380b48ed27 100644 --- a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs +++ b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs @@ -2,7 +2,6 @@ use light_compressed_account::compressed_account::PackedMerkleContext; use light_compressed_account::instruction_data::compressed_proof::ValidityProof; use light_program_profiler::profile; -use light_sdk_types::error::LightSdkTypesError; use light_sdk::{instruction::PackedStateTreeInfo, Unpack}; // Pack and PackedAccounts only available off-chain (client-side) #[cfg(not(target_os = "solana"))] @@ -10,6 +9,7 @@ use light_sdk::{ instruction::{AccountMetasVec, PackedAccounts, SystemAccountMetaConfig}, Pack, PackedAccountsExt, }; +use light_sdk_types::error::LightSdkTypesError; use light_token_interface::instructions::{ extensions::ExtensionInstructionData, transfer2::{CompressedCpiContext, MultiInputTokenDataWithContext}, diff --git a/sdk-libs/macros/src/light_pdas/program/decompress.rs b/sdk-libs/macros/src/light_pdas/program/decompress.rs index f49916c089..c00eac0975 100644 --- a/sdk-libs/macros/src/light_pdas/program/decompress.rs +++ b/sdk-libs/macros/src/light_pdas/program/decompress.rs @@ -375,23 +375,22 @@ impl DecompressBuilder { /// /// This wraps the type-parameter-based SDK call, binding `PackedLightAccountVariant` /// as the concrete type. - pub fn generate_enum_decompress_dispatch( - &self, - enum_name: &syn::Ident, - ) -> Result { + pub fn generate_enum_decompress_dispatch(&self, enum_name: &syn::Ident) -> Result { Ok(quote! { impl #enum_name { pub fn decompress_dispatch<'info>( remaining_accounts: &[solana_account_info::AccountInfo<'info>], instruction_data: &[u8], cpi_signer: light_account::CpiSigner, - program_id: &solana_pubkey::Pubkey, + program_id: &[u8; 32], + current_slot: u64, ) -> std::result::Result<(), light_account::LightSdkTypesError> { - light_account::process_decompress_pda_accounts_idempotent::( + light_account::process_decompress_pda_accounts_idempotent::<_, PackedLightAccountVariant>( remaining_accounts, instruction_data, cpi_signer, program_id, + current_slot, ) } } diff --git a/sdk-libs/macros/src/light_pdas/program/derive_light_program.rs b/sdk-libs/macros/src/light_pdas/program/derive_light_program.rs index 37c797a6ab..317cca78f9 100644 --- a/sdk-libs/macros/src/light_pdas/program/derive_light_program.rs +++ b/sdk-libs/macros/src/light_pdas/program/derive_light_program.rs @@ -608,8 +608,11 @@ fn build_intermediate_types( }); // Build ClassifiedSeeds for VariantBuilder - let classified_seeds: Vec = - variant.seeds.iter().map(manual_seed_to_classified).collect(); + let classified_seeds: Vec = variant + .seeds + .iter() + .map(manual_seed_to_classified) + .collect(); // Build ExtractedSeedSpec for VariantBuilder let extracted_spec = ExtractedSeedSpec { @@ -627,8 +630,7 @@ fn build_intermediate_types( // Build TokenSeedSpec for PDA seeds let seed_elements = manual_seeds_to_punctuated(&variant.seeds); - let dummy_eq: syn::Token![=] = - syn::parse_quote!(=); + let dummy_eq: syn::Token![=] = syn::parse_quote!(=); pda_seed_specs.push(TokenSeedSpec { variant: variant.ident.clone(), _eq: dummy_eq, @@ -661,8 +663,7 @@ fn build_intermediate_types( .as_ref() .map(|os| manual_seeds_to_seed_elements_vec(os)); - let dummy_eq: syn::Token![=] = - syn::parse_quote!(=); + let dummy_eq: syn::Token![=] = syn::parse_quote!(=); token_seed_specs.push(TokenSeedSpec { variant: variant.ident.clone(), _eq: dummy_eq, @@ -768,9 +769,10 @@ pub fn derive_light_program_impl(input: DeriveInput) -> Result { #[cfg(test)] mod tests { - use super::*; use quote::format_ident; + use super::*; + fn parse_derive_input(input: &str) -> DeriveInput { syn::parse_str(input).expect("Failed to parse derive input") } @@ -909,11 +911,7 @@ mod tests { let result = parse_enum_variants(&input); assert!(result.is_err()); let err_msg = result.unwrap_err().to_string(); - assert!( - err_msg.contains("pda::seeds"), - "Error: {}", - err_msg - ); + assert!(err_msg.contains("pda::seeds"), "Error: {}", err_msg); } #[test] @@ -1018,6 +1016,7 @@ mod tests { // BUILDER TESTS: verify build_intermediate_types for each configuration // ========================================================================= + #[allow(clippy::type_complexity)] fn parse_and_build( input_str: &str, ) -> ( @@ -1301,11 +1300,7 @@ mod tests { let result = parse_enum_variants(&input); assert!(result.is_err()); let err_msg = result.unwrap_err().to_string(); - assert!( - err_msg.contains("token::seeds"), - "Error: {}", - err_msg - ); + assert!(err_msg.contains("token::seeds"), "Error: {}", err_msg); } /// Token variant without owner_seeds should fail @@ -1324,11 +1319,7 @@ mod tests { let result = parse_enum_variants(&input); assert!(result.is_err()); let err_msg = result.unwrap_err().to_string(); - assert!( - err_msg.contains("token::owner_seeds"), - "Error: {}", - err_msg - ); + assert!(err_msg.contains("token::owner_seeds"), "Error: {}", err_msg); } /// PDA variant with unit type (no tuple field) should fail @@ -1364,11 +1355,7 @@ mod tests { let result = parse_enum_variants(&input); assert!(result.is_err()); let err_msg = result.unwrap_err().to_string(); - assert!( - err_msg.contains("unit variant"), - "Error: {}", - err_msg - ); + assert!(err_msg.contains("unit variant"), "Error: {}", err_msg); } /// ATA variant with fields should fail @@ -1427,11 +1414,7 @@ mod tests { let result = parse_enum_variants(&input); assert!(result.is_err()); let err_msg = result.unwrap_err().to_string(); - assert!( - err_msg.contains("Unknown keyword"), - "Error: {}", - err_msg - ); + assert!(err_msg.contains("Unknown keyword"), "Error: {}", err_msg); } /// Derive on struct (not enum) should fail @@ -1490,11 +1473,7 @@ mod tests { let result = parse_enum_variants(&input); assert!(result.is_err()); let err_msg = result.unwrap_err().to_string(); - assert!( - err_msg.contains("Mixed namespaces"), - "Error: {}", - err_msg - ); + assert!(err_msg.contains("Mixed namespaces"), "Error: {}", err_msg); } /// Unknown namespace should fail @@ -1559,11 +1538,7 @@ mod tests { let result = parse_enum_variants(&input); assert!(result.is_err()); let err_msg = result.unwrap_err().to_string(); - assert!( - err_msg.contains("Duplicate"), - "Error: {}", - err_msg - ); + assert!(err_msg.contains("Duplicate"), "Error: {}", err_msg); } /// Bare seeds keyword (without namespace) should fail @@ -1606,7 +1581,8 @@ mod tests { assert!(result.is_err()); let err_msg = result.unwrap_err().to_string(); assert!( - err_msg.contains("pda") && (err_msg.contains("Unknown keyword") || err_msg.contains("namespaced")), + err_msg.contains("pda") + && (err_msg.contains("Unknown keyword") || err_msg.contains("namespaced")), "Error: {}", err_msg ); @@ -1671,7 +1647,10 @@ mod tests { ); assert_eq!(instr_data.len(), 2, "should extract 2 data fields"); - let names: Vec = instr_data.iter().map(|s| s.field_name.to_string()).collect(); + let names: Vec = instr_data + .iter() + .map(|s| s.field_name.to_string()) + .collect(); assert!(names.contains(&"some_key".to_string())); assert!(names.contains(&"another_key".to_string())); } diff --git a/sdk-libs/macros/src/light_pdas/program/instructions.rs b/sdk-libs/macros/src/light_pdas/program/instructions.rs index e50f80b872..60fee2560d 100644 --- a/sdk-libs/macros/src/light_pdas/program/instructions.rs +++ b/sdk-libs/macros/src/light_pdas/program/instructions.rs @@ -6,11 +6,6 @@ use syn::{Item, ItemMod, Result}; // Re-export types from parsing, compress, and variant_enum for external use pub use super::compress::CompressibleAccountInfo; -pub use super::parsing::{ - extract_ctx_seed_fields, extract_data_seed_fields, InstructionDataSpec, InstructionVariant, - SeedElement, TokenSeedSpec, -}; -pub use super::variant_enum::PdaCtxSeedInfo; use super::{ compress::CompressBuilder, decompress::DecompressBuilder, @@ -20,6 +15,13 @@ use super::{ }, variant_enum::LightVariantBuilder, }; +pub use super::{ + parsing::{ + extract_ctx_seed_fields, extract_data_seed_fields, InstructionDataSpec, InstructionVariant, + SeedElement, TokenSeedSpec, + }, + variant_enum::PdaCtxSeedInfo, +}; use crate::{ light_pdas::shared_utils::{ident_to_type, qualify_type_with_crate}, utils::to_snake_case, @@ -367,7 +369,11 @@ pub(crate) fn generate_light_program_items( let error_codes = compress_builder.generate_error_codes()?; // Create DecompressBuilder to generate all decompress-related code - let decompress_builder = DecompressBuilder::new(pda_ctx_seeds.clone(), pda_seeds.clone(), has_token_seeds_early); + let decompress_builder = DecompressBuilder::new( + pda_ctx_seeds.clone(), + pda_seeds.clone(), + has_token_seeds_early, + ); // Note: DecompressBuilder validation is optional for now since pda_seeds may be empty for TokenOnly let decompress_accounts = decompress_builder.generate_accounts_struct()?; diff --git a/sdk-libs/program-test/src/compressible.rs b/sdk-libs/program-test/src/compressible.rs index ccd25ab032..31c20cfe1d 100644 --- a/sdk-libs/program-test/src/compressible.rs +++ b/sdk-libs/program-test/src/compressible.rs @@ -272,10 +272,7 @@ pub async fn auto_compress_program_pdas( let payer = rpc.get_payer().insecure_clone(); let (config_pda, _) = Pubkey::find_program_address( - &[ - light_sdk::COMPRESSIBLE_CONFIG_SEED, - &0u16.to_le_bytes(), - ], + &[light_sdk::COMPRESSIBLE_CONFIG_SEED, &0u16.to_le_bytes()], &program_id, ); diff --git a/sdk-libs/sdk-pinocchio/src/error.rs b/sdk-libs/sdk-pinocchio/src/error.rs index 19256d249f..0699705e9a 100644 --- a/sdk-libs/sdk-pinocchio/src/error.rs +++ b/sdk-libs/sdk-pinocchio/src/error.rs @@ -166,6 +166,9 @@ impl From for LightSdkError { LightSdkTypesError::CpiFailed => LightSdkError::CpiFailed, LightSdkTypesError::NotEnoughAccountKeys => LightSdkError::NotEnoughAccountKeys, LightSdkTypesError::MissingRequiredSignature => LightSdkError::MissingRequiredSignature, + LightSdkTypesError::ProgramError(code) => { + LightSdkError::ProgramError(ProgramError::Custom(code)) + } } } } diff --git a/sdk-libs/sdk-types/src/error.rs b/sdk-libs/sdk-types/src/error.rs index fc2684abff..23ded4116d 100644 --- a/sdk-libs/sdk-types/src/error.rs +++ b/sdk-libs/sdk-types/src/error.rs @@ -1,11 +1,11 @@ +#[cfg(all(not(feature = "std"), feature = "alloc"))] +use alloc::string::String; + use light_account_checks::error::AccountError; use light_compressed_account::CompressedAccountError; use light_hasher::HasherError; use thiserror::Error; -#[cfg(all(not(feature = "std"), feature = "alloc"))] -use alloc::string::String; - pub type Result = core::result::Result; #[derive(Debug, Error, PartialEq)] @@ -36,7 +36,7 @@ pub enum LightSdkTypesError { InvalidCpiContextAccount, #[error("Invalid sol pool pda account")] InvalidSolPoolPdaAccount, - #[error("CpigAccounts accounts slice starts with an invalid account. It should start with LightSystemProgram SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7.")] + #[error("CpiAccounts accounts slice starts with an invalid account. It should start with LightSystemProgram SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7.")] InvalidCpiAccountsOffset, #[error(transparent)] AccountError(#[from] AccountError), @@ -70,6 +70,8 @@ pub enum LightSdkTypesError { NotEnoughAccountKeys, #[error("Missing required signature")] MissingRequiredSignature, + #[error("Program error: {0}")] + ProgramError(u32), } #[cfg(feature = "anchor")] @@ -97,7 +99,7 @@ impl From for LightSdkTypesError { solana_program_error::ProgramError::AccountBorrowFailed => { LightSdkTypesError::ConstraintViolation } - _ => LightSdkTypesError::ConstraintViolation, + other => LightSdkTypesError::ProgramError(u64::from(other) as u32), } } } @@ -135,6 +137,7 @@ impl From for u32 { LightSdkTypesError::CpiFailed => 14045, LightSdkTypesError::NotEnoughAccountKeys => 14046, LightSdkTypesError::MissingRequiredSignature => 14047, + LightSdkTypesError::ProgramError(code) => code, } } } diff --git a/sdk-libs/sdk-types/src/interface/account/compression_info.rs b/sdk-libs/sdk-types/src/interface/account/compression_info.rs index d71d34a85c..caf30bc6a9 100644 --- a/sdk-libs/sdk-types/src/interface/account/compression_info.rs +++ b/sdk-libs/sdk-types/src/interface/account/compression_info.rs @@ -1,13 +1,13 @@ extern crate alloc; -use alloc::borrow::Cow; -use alloc::string::ToString; +use alloc::{borrow::Cow, string::ToString}; -use crate::instruction::PackedStateTreeInfo; use bytemuck::{Pod, Zeroable}; use light_account_checks::AccountInfoTrait; use light_compressible::rent::RentConfig; -use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; +use crate::{ + error::LightSdkTypesError, instruction::PackedStateTreeInfo, AnchorDeserialize, AnchorSerialize, +}; pub trait HasCompressionInfo { fn compression_info(&self) -> Result<&CompressionInfo, LightSdkTypesError>; diff --git a/sdk-libs/sdk-types/src/interface/account/light_account.rs b/sdk-libs/sdk-types/src/interface/account/light_account.rs index 3cc96ad3c0..80f0b38ebc 100644 --- a/sdk-libs/sdk-types/src/interface/account/light_account.rs +++ b/sdk-libs/sdk-types/src/interface/account/light_account.rs @@ -1,12 +1,15 @@ //! LightAccount trait definition for compressible account data structs. -use light_account_checks::{packed_accounts::ProgramPackedAccounts, AccountInfoTrait}; #[cfg(all(not(target_os = "solana"), feature = "std"))] use light_account_checks::AccountMetaTrait; +use light_account_checks::{packed_accounts::ProgramPackedAccounts, AccountInfoTrait}; use light_hasher::DataHasher; -use crate::interface::{account::compression_info::CompressionInfo, program::config::LightConfig}; -use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; +use crate::{ + error::LightSdkTypesError, + interface::{account::compression_info::CompressionInfo, program::config::LightConfig}, + AnchorDeserialize, AnchorSerialize, +}; pub enum AccountType { Pda, diff --git a/sdk-libs/sdk-types/src/interface/account/pack.rs b/sdk-libs/sdk-types/src/interface/account/pack.rs index c6ec822cb8..8c04c636f1 100644 --- a/sdk-libs/sdk-types/src/interface/account/pack.rs +++ b/sdk-libs/sdk-types/src/interface/account/pack.rs @@ -1,12 +1,11 @@ //! Pack and Unpack traits for converting between full Pubkeys and u8 indices. use light_account_checks::AccountInfoTrait; - -use crate::error::LightSdkTypesError; - #[cfg(all(not(target_os = "solana"), feature = "std"))] use light_account_checks::AccountMetaTrait; +use crate::error::LightSdkTypesError; + /// Replace 32-byte Pubkeys with 1-byte indices to save space. /// If your type has no Pubkeys, just return self. #[cfg(all(not(target_os = "solana"), feature = "std"))] diff --git a/sdk-libs/sdk-types/src/interface/account/token_seeds.rs b/sdk-libs/sdk-types/src/interface/account/token_seeds.rs index 1b57980b3e..93365e99e2 100644 --- a/sdk-libs/sdk-types/src/interface/account/token_seeds.rs +++ b/sdk-libs/sdk-types/src/interface/account/token_seeds.rs @@ -3,10 +3,11 @@ //! Provides `TokenDataWithSeeds`, `PackedTokenData`, and `TokenDataWithPackedSeeds` //! along with Pack/Unpack impls and blanket impls for variant traits. -use alloc::vec; -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; -use crate::instruction::PackedStateTreeInfo; +use light_account_checks::AccountInfoTrait; +#[cfg(all(not(target_os = "solana"), feature = "std"))] +use light_account_checks::AccountMetaTrait; use light_compressed_account::compressed_account::PackedMerkleContext; pub use light_token_interface::{ instructions::{ @@ -19,21 +20,21 @@ pub use light_token_interface::{ }, }; -use light_account_checks::AccountInfoTrait; - use super::pack::Unpack; -use crate::interface::{ - account::light_account::AccountType, - program::variant::{ - LightAccountVariantTrait, PackedLightAccountVariantTrait, PackedTokenSeeds, - UnpackedTokenSeeds, - }, -}; #[cfg(all(not(target_os = "solana"), feature = "std"))] use crate::interface::{account::pack::Pack, instruction::PackedAccounts}; -use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; -#[cfg(all(not(target_os = "solana"), feature = "std"))] -use light_account_checks::AccountMetaTrait; +use crate::{ + error::LightSdkTypesError, + instruction::PackedStateTreeInfo, + interface::{ + account::light_account::AccountType, + program::variant::{ + LightAccountVariantTrait, PackedLightAccountVariantTrait, PackedTokenSeeds, + UnpackedTokenSeeds, + }, + }, + AnchorDeserialize, AnchorSerialize, +}; #[derive(Clone, Debug, AnchorSerialize, AnchorDeserialize)] pub struct TokenDataWithSeeds { diff --git a/sdk-libs/sdk-types/src/interface/accounts/init_compressed_account.rs b/sdk-libs/sdk-types/src/interface/accounts/init_compressed_account.rs index fd00b8e2b8..9e3d3d9a85 100644 --- a/sdk-libs/sdk-types/src/interface/accounts/init_compressed_account.rs +++ b/sdk-libs/sdk-types/src/interface/accounts/init_compressed_account.rs @@ -2,7 +2,6 @@ use alloc::vec::Vec; -use crate::instruction::PackedAddressTreeInfo; use light_account_checks::AccountInfoTrait; use light_compressed_account::{ address::derive_address, @@ -14,7 +13,7 @@ use light_compressed_account::{ use light_compressible::DECOMPRESSED_PDA_DISCRIMINATOR; use light_hasher::{errors::HasherError, sha256::Sha256BE, Hasher}; -use crate::error::LightSdkTypesError; +use crate::{error::LightSdkTypesError, instruction::PackedAddressTreeInfo}; /// Prepare a compressed account for a PDA during initialization. /// diff --git a/sdk-libs/sdk-types/src/interface/cpi/account.rs b/sdk-libs/sdk-types/src/interface/cpi/account.rs index 54992eff3a..01dfb15612 100644 --- a/sdk-libs/sdk-types/src/interface/cpi/account.rs +++ b/sdk-libs/sdk-types/src/interface/cpi/account.rs @@ -1,13 +1,14 @@ //! Generic CPI accounts trait and implementations. -use alloc::vec; -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; -use crate::cpi_accounts::v2::{CompressionCpiAccountIndex, CpiAccounts, PROGRAM_ACCOUNTS_LEN}; -use crate::cpi_context_write::CpiContextWriteAccounts; use light_account_checks::{AccountInfoTrait, CpiMeta}; -use crate::error::LightSdkTypesError; +use crate::{ + cpi_accounts::v2::{CompressionCpiAccountIndex, CpiAccounts, PROGRAM_ACCOUNTS_LEN}, + cpi_context_write::CpiContextWriteAccounts, + error::LightSdkTypesError, +}; /// Trait for types that can provide account infos and metas for Light system program CPI. /// diff --git a/sdk-libs/sdk-types/src/interface/cpi/create_mints.rs b/sdk-libs/sdk-types/src/interface/cpi/create_mints.rs index b3965936af..4f65b1a4a2 100644 --- a/sdk-libs/sdk-types/src/interface/cpi/create_mints.rs +++ b/sdk-libs/sdk-types/src/interface/cpi/create_mints.rs @@ -9,8 +9,7 @@ //! - N=1 (no CPI context offset): Single CPI (create + decompress) //! - N>1 or offset>0: 2N-1 CPIs (N-1 writes + 1 execute with decompress + N-1 decompress) -use alloc::vec; -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use light_account_checks::{AccountInfoTrait, CpiMeta}; use light_compressed_account::instruction_data::{ diff --git a/sdk-libs/sdk-types/src/interface/cpi/create_token_accounts.rs b/sdk-libs/sdk-types/src/interface/cpi/create_token_accounts.rs index 1de5e9ccce..1d12ac1ac6 100644 --- a/sdk-libs/sdk-types/src/interface/cpi/create_token_accounts.rs +++ b/sdk-libs/sdk-types/src/interface/cpi/create_token_accounts.rs @@ -4,8 +4,7 @@ //! `AccountInfoTrait` so they work with both `solana_account_info::AccountInfo` //! and `pinocchio::account_info::AccountInfo`. -use alloc::vec; -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use borsh::BorshSerialize; use light_account_checks::{AccountInfoTrait, CpiMeta}; @@ -122,14 +121,8 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateTokenAccountRentFreeCpi<'a, AI> { /// Invoke CPI for non-program-owned accounts. pub fn invoke(self) -> Result<(), LightSdkTypesError> { let (data, metas, account_infos) = self.build_instruction_inner(None)?; - AI::invoke_cpi( - &LIGHT_TOKEN_PROGRAM_ID, - &data, - &metas, - &account_infos, - &[], - ) - .map_err(|_| LightSdkTypesError::CpiFailed) + AI::invoke_cpi(&LIGHT_TOKEN_PROGRAM_ID, &data, &metas, &account_infos, &[]) + .map_err(|_| LightSdkTypesError::CpiFailed) } /// Invoke CPI with PDA signing for program-owned accounts. @@ -163,6 +156,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateTokenAccountRentFreeCpi<'a, AI> { } /// Build instruction data, account metas, and account infos. + #[allow(clippy::type_complexity)] fn build_instruction_inner( &self, compress_to: Option, @@ -341,14 +335,8 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateTokenAtaRentFreeCpi<'a, AI> { /// Invoke CPI. pub fn invoke(self) -> Result<(), LightSdkTypesError> { let (data, metas, account_infos) = self.build_instruction_inner()?; - AI::invoke_cpi( - &LIGHT_TOKEN_PROGRAM_ID, - &data, - &metas, - &account_infos, - &[], - ) - .map_err(|_| LightSdkTypesError::CpiFailed) + AI::invoke_cpi(&LIGHT_TOKEN_PROGRAM_ID, &data, &metas, &account_infos, &[]) + .map_err(|_| LightSdkTypesError::CpiFailed) } /// Invoke CPI with signer seeds (when caller needs to sign for another account). @@ -365,6 +353,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateTokenAtaRentFreeCpi<'a, AI> { } /// Build instruction data, account metas, and account infos. + #[allow(clippy::type_complexity)] fn build_instruction_inner( &self, ) -> Result<(Vec, Vec, Vec), LightSdkTypesError> { diff --git a/sdk-libs/sdk-types/src/interface/cpi/invoke.rs b/sdk-libs/sdk-types/src/interface/cpi/invoke.rs index 0cb4723178..e1b7bdac16 100644 --- a/sdk-libs/sdk-types/src/interface/cpi/invoke.rs +++ b/sdk-libs/sdk-types/src/interface/cpi/invoke.rs @@ -2,12 +2,14 @@ use alloc::vec; -use crate::constants::{CPI_AUTHORITY_PDA_SEED, LIGHT_SYSTEM_PROGRAM_ID}; use light_account_checks::{AccountInfoTrait, CpiMeta}; pub use light_compressed_account::LightInstructionData; -use crate::error::LightSdkTypesError; -use crate::interface::cpi::{account::CpiAccountsTrait, instruction::LightCpi}; +use crate::{ + constants::{CPI_AUTHORITY_PDA_SEED, LIGHT_SYSTEM_PROGRAM_ID}, + error::LightSdkTypesError, + interface::cpi::{account::CpiAccountsTrait, instruction::LightCpi}, +}; /// Trait for invoking the Light system program via CPI. /// diff --git a/sdk-libs/sdk-types/src/interface/cpi/mod.rs b/sdk-libs/sdk-types/src/interface/cpi/mod.rs index 5ee4f0d626..6b17390817 100644 --- a/sdk-libs/sdk-types/src/interface/cpi/mod.rs +++ b/sdk-libs/sdk-types/src/interface/cpi/mod.rs @@ -14,9 +14,9 @@ pub mod invoke; pub use account::CpiAccountsTrait; pub use instruction::LightCpi; -pub use invoke::{invoke_light_system_program, InvokeLightSystemProgram}; #[cfg(feature = "cpi-context")] pub use invoke::invoke_write_pdas_to_cpi_context; +pub use invoke::{invoke_light_system_program, InvokeLightSystemProgram}; pub use light_compressed_account::instruction_data::traits::LightInstructionData; + pub use crate::{cpi_accounts::CpiAccountsConfig, CpiSigner}; -// TODO: move all of this to light-sdk-types diff --git a/sdk-libs/sdk-types/src/interface/create_accounts_proof.rs b/sdk-libs/sdk-types/src/interface/create_accounts_proof.rs index 2bbbb1959f..be35fadaf8 100644 --- a/sdk-libs/sdk-types/src/interface/create_accounts_proof.rs +++ b/sdk-libs/sdk-types/src/interface/create_accounts_proof.rs @@ -1,8 +1,9 @@ -use crate::{AnchorDeserialize, AnchorSerialize}; use light_compressed_account::instruction_data::{ compressed_proof::ValidityProof, data::PackedAddressTreeInfo, }; +use crate::{AnchorDeserialize, AnchorSerialize}; + /// Proof data for instruction params when creating new compressed accounts. /// Used in the INIT flow - pass directly to instruction data. /// All accounts use the same address tree, so only one `address_tree_info` is needed. diff --git a/sdk-libs/sdk-types/src/interface/instruction/mod.rs b/sdk-libs/sdk-types/src/interface/instruction/mod.rs index e3619684e4..9d0027f3f6 100644 --- a/sdk-libs/sdk-types/src/interface/instruction/mod.rs +++ b/sdk-libs/sdk-types/src/interface/instruction/mod.rs @@ -4,7 +4,7 @@ mod pack_accounts; +pub use pack_accounts::*; + /// Re-exports from light-sdk-types instruction types. pub use crate::instruction::*; -pub use pack_accounts::*; -// TODO: move all of this to light-sdk-types diff --git a/sdk-libs/sdk-types/src/interface/mod.rs b/sdk-libs/sdk-types/src/interface/mod.rs index c53c7e886c..b80dec9f3b 100644 --- a/sdk-libs/sdk-types/src/interface/mod.rs +++ b/sdk-libs/sdk-types/src/interface/mod.rs @@ -13,69 +13,70 @@ pub mod cpi; pub mod instruction; // --- Re-exports from light-compressible --- -pub use light_compressible::rent; - -// --- Re-exports --- -pub use create_accounts_proof::CreateAccountsProof; - // ============================================================================= // FLAT RE-EXPORTS // ============================================================================= // --- account/ --- -pub use account::compression_info::{ - claim_completed_epoch_rent, CompressAs, CompressedAccountData, CompressedInitSpace, - CompressionInfo, CompressionInfoField, CompressionState, HasCompressionInfo, Space, - COMPRESSION_INFO_SIZE, OPTION_COMPRESSION_INFO_SPACE, -}; -pub use account::light_account::{AccountType, LightAccount}; #[cfg(all(not(target_os = "solana"), feature = "std"))] pub use account::pack::Pack; -pub use account::pack::Unpack; -pub use account::pda_seeds::{HasTokenVariant, PdaSeedDerivation}; - +// --- program/ --- +#[cfg(feature = "token")] +pub use account::token_seeds::{PackedTokenData, TokenDataWithPackedSeeds, TokenDataWithSeeds}; +pub use account::{ + compression_info::{ + claim_completed_epoch_rent, CompressAs, CompressedAccountData, CompressedInitSpace, + CompressionInfo, CompressionInfoField, CompressionState, HasCompressionInfo, Space, + COMPRESSION_INFO_SIZE, OPTION_COMPRESSION_INFO_SPACE, + }, + light_account::{AccountType, LightAccount}, + pack::Unpack, + pda_seeds::{HasTokenVariant, PdaSeedDerivation}, +}; // --- accounts/ --- pub use accounts::{ finalize::{LightFinalize, LightPreInit}, init_compressed_account::{prepare_compressed_account_on_init, reimburse_rent}, }; - // --- cpi/ --- pub use cpi::{ account::CpiAccountsTrait, invoke::{invoke_light_system_program, InvokeLightSystemProgram}, LightCpi, }; - -// --- program/ --- -#[cfg(feature = "token")] -pub use account::token_seeds::{PackedTokenData, TokenDataWithPackedSeeds, TokenDataWithSeeds}; -pub use program::compression::close::close; -pub use program::compression::pda::prepare_account_for_compression; -pub use program::compression::processor::{ - process_compress_pda_accounts_idempotent, CompressAndCloseParams, CompressCtx, - CompressDispatchFn, -}; -pub use program::config::{ - process_initialize_light_config_checked, process_update_light_config, - InitializeLightConfigParams, LightConfig, UpdateLightConfigParams, COMPRESSIBLE_CONFIG_SEED, - MAX_ADDRESS_TREES_PER_SPACE, -}; -pub use program::decompression::pda::prepare_account_for_decompression; +// --- Re-exports --- +pub use create_accounts_proof::CreateAccountsProof; +pub use light_compressible::rent; #[cfg(feature = "token")] pub use program::decompression::processor::process_decompress_accounts_idempotent; -pub use program::decompression::processor::{ - process_decompress_pda_accounts_idempotent, DecompressCtx, DecompressIdempotentParams, - DecompressVariant, -}; #[cfg(feature = "token")] pub use program::decompression::token::prepare_token_account_for_decompression; -pub use program::validation::{ - extract_tail_accounts, is_pda_initialized, should_skip_compression, - split_at_system_accounts_offset, validate_compress_accounts, validate_decompress_accounts, - ValidatedPdaContext, -}; -pub use program::variant::IntoVariant; -pub use program::variant::{LightAccountVariantTrait, PackedLightAccountVariantTrait}; #[cfg(feature = "token")] pub use program::variant::{PackedTokenSeeds, UnpackedTokenSeeds}; +pub use program::{ + compression::{ + pda::prepare_account_for_compression, + processor::{ + process_compress_pda_accounts_idempotent, CompressAndCloseParams, CompressCtx, + CompressDispatchFn, + }, + }, + config::{ + process_initialize_light_config_checked, process_update_light_config, + InitializeLightConfigParams, LightConfig, UpdateLightConfigParams, + COMPRESSIBLE_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE, + }, + decompression::{ + pda::prepare_account_for_decompression, + processor::{ + process_decompress_pda_accounts_idempotent, DecompressCtx, DecompressIdempotentParams, + DecompressVariant, + }, + }, + validation::{ + extract_tail_accounts, is_pda_initialized, should_skip_compression, + split_at_system_accounts_offset, validate_compress_accounts, validate_decompress_accounts, + ValidatedPdaContext, + }, + variant::{IntoVariant, LightAccountVariantTrait, PackedLightAccountVariantTrait}, +}; diff --git a/sdk-libs/sdk-types/src/interface/program/compression/close.rs b/sdk-libs/sdk-types/src/interface/program/compression/close.rs deleted file mode 100644 index c15aa191ab..0000000000 --- a/sdk-libs/sdk-types/src/interface/program/compression/close.rs +++ /dev/null @@ -1,9 +0,0 @@ -use light_account_checks::AccountInfoTrait; - -use crate::error::{LightSdkTypesError, Result}; -// TODO: remove and use directly from light-account-checks -/// Close a native Solana account by transferring lamports and clearing data. -pub fn close(info: &AI, sol_destination: &AI) -> Result<()> { - light_account_checks::close_account(info, sol_destination) - .map_err(LightSdkTypesError::AccountError) -} diff --git a/sdk-libs/sdk-types/src/interface/program/compression/mod.rs b/sdk-libs/sdk-types/src/interface/program/compression/mod.rs index 9391ffd875..9dd7eee930 100644 --- a/sdk-libs/sdk-types/src/interface/program/compression/mod.rs +++ b/sdk-libs/sdk-types/src/interface/program/compression/mod.rs @@ -1,5 +1,4 @@ //! Compression functions for PDA accounts. -pub mod close; pub mod pda; pub mod processor; diff --git a/sdk-libs/sdk-types/src/interface/program/compression/pda.rs b/sdk-libs/sdk-types/src/interface/program/compression/pda.rs index 2825963565..c5ab44fce1 100644 --- a/sdk-libs/sdk-types/src/interface/program/compression/pda.rs +++ b/sdk-libs/sdk-types/src/interface/program/compression/pda.rs @@ -3,7 +3,6 @@ //! These functions are generic over account types and can be reused by the macro. //! The compress flow uses a dispatch callback pattern (same as decompress). -use crate::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress; use light_account_checks::AccountInfoTrait; use light_compressed_account::{ address::derive_address, @@ -13,12 +12,16 @@ use light_compressed_account::{ use light_compressible::{rent::AccountRentState, DECOMPRESSED_PDA_DISCRIMINATOR}; use light_hasher::{sha256::Sha256BE, Hasher, Sha256}; -use crate::instruction::account_meta::{CompressedAccountMeta, CompressedAccountMetaTrait}; - -use crate::interface::{ - account::compression_info::HasCompressionInfo, program::compression::processor::CompressCtx, +use crate::{ + error::LightSdkTypesError, + instruction::account_meta::{ + CompressedAccountMeta, CompressedAccountMetaNoLamportsNoAddress, CompressedAccountMetaTrait, + }, + interface::{ + account::compression_info::HasCompressionInfo, program::compression::processor::CompressCtx, + }, + LightDiscriminator, }; -use crate::{error::LightSdkTypesError, LightDiscriminator}; /// Generic prepare_account_for_compression. /// diff --git a/sdk-libs/sdk-types/src/interface/program/compression/processor.rs b/sdk-libs/sdk-types/src/interface/program/compression/processor.rs index ea78d9d287..9b09c358e4 100644 --- a/sdk-libs/sdk-types/src/interface/program/compression/processor.rs +++ b/sdk-libs/sdk-types/src/interface/program/compression/processor.rs @@ -2,21 +2,19 @@ use alloc::vec::Vec; -use crate::{ - cpi_accounts::v2::CpiAccounts, - instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, CpiSigner, -}; use light_account_checks::AccountInfoTrait; use light_compressed_account::instruction_data::{ compressed_proof::ValidityProof, with_account_info::{CompressedAccountInfo, InstructionDataInvokeCpiWithAccountInfo}, }; -use crate::interface::{ - cpi::InvokeLightSystemProgram, - program::{compression::close::close, config::LightConfig}, +use crate::{ + cpi_accounts::v2::CpiAccounts, + error::LightSdkTypesError, + instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, + interface::{cpi::InvokeLightSystemProgram, program::config::LightConfig}, + AnchorDeserialize, AnchorSerialize, CpiSigner, }; -use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; /// Account indices within remaining_accounts for compress instructions. const FEE_PAYER_INDEX: usize = 0; @@ -149,7 +147,8 @@ pub fn process_compress_pda_accounts_idempotent( // 10. Close PDA accounts, transferring lamports to rent_sponsor for pda_index in &pda_indices_to_close { - close(&remaining_accounts[*pda_index], rent_sponsor)?; + light_account_checks::close_account(&remaining_accounts[*pda_index], rent_sponsor) + .map_err(LightSdkTypesError::AccountError)?; } Ok(()) diff --git a/sdk-libs/sdk-types/src/interface/program/config/create.rs b/sdk-libs/sdk-types/src/interface/program/config/create.rs index 7546b18197..a26c797101 100644 --- a/sdk-libs/sdk-types/src/interface/program/config/create.rs +++ b/sdk-libs/sdk-types/src/interface/program/config/create.rs @@ -231,7 +231,7 @@ mod tests { #[test] fn test_upgradeable_loader_state_parsing() { // Build a synthetic ProgramData account matching the manual layout - let mut data = vec![0u8; 45]; + let mut data = [0u8; 45]; // Variant tag = 3 (ProgramData) data[0..4].copy_from_slice(&3u32.to_le_bytes()); diff --git a/sdk-libs/sdk-types/src/interface/program/config/mod.rs b/sdk-libs/sdk-types/src/interface/program/config/mod.rs index f87abb8832..e6cb7f6124 100644 --- a/sdk-libs/sdk-types/src/interface/program/config/mod.rs +++ b/sdk-libs/sdk-types/src/interface/program/config/mod.rs @@ -18,10 +18,11 @@ pub const MAX_ADDRESS_TREES_PER_SPACE: usize = 1; // --- Re-exports --- // Re-export Discriminator trait so users can access LightConfig::LIGHT_DISCRIMINATOR -pub use crate::constants::RENT_SPONSOR_SEED; pub use light_account_checks::discriminator::Discriminator; pub use state::LightConfig; +pub use crate::constants::RENT_SPONSOR_SEED; + // ============================================================================= // Instruction params (serialized by client, deserialized by program) // ============================================================================= diff --git a/sdk-libs/sdk-types/src/interface/program/decompression/create_token_account.rs b/sdk-libs/sdk-types/src/interface/program/decompression/create_token_account.rs index cbf1ff2ee2..46f11f416b 100644 --- a/sdk-libs/sdk-types/src/interface/program/decompression/create_token_account.rs +++ b/sdk-libs/sdk-types/src/interface/program/decompression/create_token_account.rs @@ -2,8 +2,7 @@ //! //! Returns `(instruction_data, account_metas)` tuples for use with `AI::invoke_cpi()`. -use alloc::vec; -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use light_account_checks::CpiMeta; use light_token_interface::{ diff --git a/sdk-libs/sdk-types/src/interface/program/decompression/mod.rs b/sdk-libs/sdk-types/src/interface/program/decompression/mod.rs index f647dc0f56..95e0b35353 100644 --- a/sdk-libs/sdk-types/src/interface/program/decompression/mod.rs +++ b/sdk-libs/sdk-types/src/interface/program/decompression/mod.rs @@ -1,8 +1,8 @@ //! Decompression functions for PDA and token accounts. -pub mod pda; -pub mod processor; #[cfg(feature = "token")] pub mod create_token_account; +pub mod pda; +pub mod processor; #[cfg(feature = "token")] pub mod token; diff --git a/sdk-libs/sdk-types/src/interface/program/decompression/pda.rs b/sdk-libs/sdk-types/src/interface/program/decompression/pda.rs index 8dfc7aa0d4..9ef915ff8f 100644 --- a/sdk-libs/sdk-types/src/interface/program/decompression/pda.rs +++ b/sdk-libs/sdk-types/src/interface/program/decompression/pda.rs @@ -1,6 +1,7 @@ //! Generic prepare_account_for_decompression. -use crate::{constants::RENT_SPONSOR_SEED, instruction::PackedStateTreeInfo}; +use alloc::vec::Vec; + use light_account_checks::AccountInfoTrait; use light_compressed_account::{ address::derive_address, @@ -10,18 +11,19 @@ use light_compressed_account::{ use light_compressible::DECOMPRESSED_PDA_DISCRIMINATOR; use light_hasher::{sha256::Sha256BE, Hasher}; -use alloc::vec::Vec; - -use crate::interface::{ - account::light_account::LightAccount, - program::{ - decompression::processor::DecompressCtx, - variant::{LightAccountVariantTrait, PackedLightAccountVariantTrait}, - }, -}; use crate::{ + constants::RENT_SPONSOR_SEED, error::LightSdkTypesError, - light_account_checks::discriminator::Discriminator as LightDiscriminator, AnchorSerialize, + instruction::PackedStateTreeInfo, + interface::{ + account::light_account::LightAccount, + program::{ + decompression::processor::DecompressCtx, + variant::{LightAccountVariantTrait, PackedLightAccountVariantTrait}, + }, + }, + light_account_checks::discriminator::Discriminator as LightDiscriminator, + AnchorSerialize, }; /// Generic prepare_account_for_decompression. @@ -61,10 +63,7 @@ where <

>::Unpacked as LightAccountVariantTrait>::Data; // 1. Unpack to get seeds (must happen first for PDA validation) - let packed_accounts = ctx - .cpi_accounts - .packed_accounts() - .map_err(LightSdkTypesError::from)?; + let packed_accounts = ctx.cpi_accounts.packed_accounts()?; let unpacked = packed .unpack(packed_accounts) @@ -106,10 +105,7 @@ where let space = discriminator_len + data_len.max( as LightAccount>::INIT_SPACE); let rent_minimum = AI::get_min_rent_balance(space)?; - let system_program = ctx - .cpi_accounts - .system_program() - .map_err(LightSdkTypesError::from)?; + let system_program = ctx.cpi_accounts.system_program()?; // Construct rent sponsor seeds for PDA signing let rent_sponsor_bump_bytes = [ctx.rent_sponsor_bump]; diff --git a/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs b/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs index 74a6e93b69..e12d9dcd35 100644 --- a/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs +++ b/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs @@ -4,18 +4,25 @@ use alloc::vec; use alloc::vec::Vec; -use crate::{cpi_accounts::v2::CpiAccounts, instruction::PackedStateTreeInfo, CpiSigner}; use light_account_checks::AccountInfoTrait; +#[cfg(feature = "token")] +use light_account_checks::CpiMeta; +#[cfg(feature = "token")] +use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; use light_compressed_account::instruction_data::{ compressed_proof::ValidityProof, with_account_info::{CompressedAccountInfo, InstructionDataInvokeCpiWithAccountInfo}, }; - -use crate::interface::{ - account::compression_info::CompressedAccountData, cpi::InvokeLightSystemProgram, - program::config::LightConfig, +#[cfg(feature = "token")] +use light_token_interface::{ + instructions::{ + extensions::ExtensionInstructionData, + transfer2::{ + CompressedTokenInstructionDataTransfer2, Compression, MultiInputTokenDataWithContext, + }, + }, + CPI_AUTHORITY, LIGHT_TOKEN_PROGRAM_ID, TRANSFER2, }; -use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; #[cfg(feature = "token")] use crate::{ @@ -26,19 +33,15 @@ use crate::{ cpi_accounts::CpiAccountsConfig, cpi_context_write::CpiContextWriteAccounts, }; -#[cfg(feature = "token")] -use light_account_checks::CpiMeta; -#[cfg(feature = "token")] -use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; -#[cfg(feature = "token")] -use light_token_interface::{ - instructions::{ - extensions::ExtensionInstructionData, - transfer2::{ - CompressedTokenInstructionDataTransfer2, Compression, MultiInputTokenDataWithContext, - }, +use crate::{ + cpi_accounts::v2::CpiAccounts, + error::LightSdkTypesError, + instruction::PackedStateTreeInfo, + interface::{ + account::compression_info::CompressedAccountData, cpi::InvokeLightSystemProgram, + program::config::LightConfig, }, - CPI_AUTHORITY, LIGHT_TOKEN_PROGRAM_ID, TRANSFER2, + AnchorDeserialize, AnchorSerialize, CpiSigner, }; /// Account indices within remaining_accounts for decompress instructions. @@ -138,11 +141,11 @@ pub struct DecompressCtx<'a, AI: AccountInfoTrait + Clone> { /// Idempotent: if a PDA is already initialized, it is silently skipped. /// /// # Account layout in remaining_accounts: -/// - [0]: fee_payer (Signer, mut) -/// - [1]: config (LightConfig PDA) -/// - [2]: rent_sponsor (mut) -/// - [system_accounts_offset..hot_accounts_start]: Light system + tree accounts -/// - [hot_accounts_start..]: PDA accounts to decompress into +/// - `[0]`: fee_payer (Signer, mut) +/// - `[1]`: config (LightConfig PDA) +/// - `[2]`: rent_sponsor (mut) +/// - `[system_accounts_offset..hot_accounts_start]`: Light system + tree accounts +/// - `[hot_accounts_start..]`: PDA accounts to decompress into #[inline(never)] pub fn process_decompress_pda_accounts_idempotent( remaining_accounts: &[AI], @@ -262,15 +265,15 @@ where /// - Token accounts are decompressed via Transfer2 CPI to the light token program /// /// # Account layout in remaining_accounts: -/// - [0]: fee_payer (Signer, mut) -/// - [1]: config (LightConfig PDA) -/// - [2]: rent_sponsor (mut) -/// - [3]: ctoken_rent_sponsor (mut) -/// - [4]: light_token_program -/// - [5]: cpi_authority -/// - [6]: ctoken_compressible_config -/// - [system_accounts_offset..hot_accounts_start]: Light system + tree accounts -/// - [hot_accounts_start..]: Hot accounts (PDAs then tokens) +/// - `[0]`: fee_payer (Signer, mut) +/// - `[1]`: config (LightConfig PDA) +/// - `[2]`: rent_sponsor (mut) +/// - `[3]`: ctoken_rent_sponsor (mut) +/// - `[4]`: light_token_program +/// - `[5]`: cpi_authority +/// - `[6]`: ctoken_compressible_config +/// - `[system_accounts_offset..hot_accounts_start]`: Light system + tree accounts +/// - `[hot_accounts_start..]`: Hot accounts (PDAs then tokens) #[cfg(feature = "token")] #[inline(never)] pub fn process_decompress_accounts_idempotent( @@ -407,10 +410,8 @@ where cpi_ix_data.invoke::(cpi_accounts.clone())?; } else { // PDAs + tokens: write PDA data to CPI context first, tokens will execute - let authority = cpi_accounts.authority().map_err(LightSdkTypesError::from)?; - let cpi_context_account = cpi_accounts - .cpi_context() - .map_err(LightSdkTypesError::from)?; + let authority = cpi_accounts.authority()?; + let cpi_context_account = cpi_accounts.cpi_context()?; let system_cpi_accounts = CpiContextWriteAccounts { fee_payer: &remaining_accounts[FEE_PAYER_INDEX], authority, @@ -521,9 +522,7 @@ where ]; if cpi_context { - let cpi_ctx = cpi_accounts - .cpi_context() - .map_err(LightSdkTypesError::from)?; + let cpi_ctx = cpi_accounts.cpi_context()?; account_metas.push(CpiMeta { pubkey: cpi_ctx.key(), is_signer: false, diff --git a/sdk-libs/sdk-types/src/interface/program/decompression/token.rs b/sdk-libs/sdk-types/src/interface/program/decompression/token.rs index a54d08e221..37eeb500a2 100644 --- a/sdk-libs/sdk-types/src/interface/program/decompression/token.rs +++ b/sdk-libs/sdk-types/src/interface/program/decompression/token.rs @@ -1,9 +1,7 @@ //! Token account decompression. -use alloc::vec; -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; -use crate::instruction::PackedStateTreeInfo; use light_account_checks::AccountInfoTrait; use light_token_interface::{ instructions::extensions::ExtensionInstructionData, LIGHT_TOKEN_PROGRAM_ID, @@ -12,9 +10,12 @@ use light_token_interface::{ use super::create_token_account::{ build_create_ata_instruction, build_create_token_account_instruction, }; -use crate::error::LightSdkTypesError; -use crate::interface::program::{ - decompression::processor::DecompressCtx, variant::PackedLightAccountVariantTrait, +use crate::{ + error::LightSdkTypesError, + instruction::PackedStateTreeInfo, + interface::program::{ + decompression::processor::DecompressCtx, variant::PackedLightAccountVariantTrait, + }, }; pub fn prepare_token_account_for_decompression( @@ -28,10 +29,7 @@ where AI: AccountInfoTrait + Clone, P: PackedLightAccountVariantTrait, { - let packed_accounts = ctx - .cpi_accounts - .packed_accounts() - .map_err(LightSdkTypesError::from)?; + let packed_accounts = ctx.cpi_accounts.packed_accounts()?; let token_data = packed.into_in_token_data(tree_info, output_queue_index)?; // Get TLV extension early to detect ATA diff --git a/sdk-libs/sdk-types/src/interface/program/validation.rs b/sdk-libs/sdk-types/src/interface/program/validation.rs index 1b669afe7c..f8745b7381 100644 --- a/sdk-libs/sdk-types/src/interface/program/validation.rs +++ b/sdk-libs/sdk-types/src/interface/program/validation.rs @@ -4,8 +4,7 @@ use light_account_checks::{ account_iterator::AccountIterator, checks::check_data_is_zeroed, AccountInfoTrait, }; -use crate::error::LightSdkTypesError; -use crate::interface::program::config::LightConfig; +use crate::{error::LightSdkTypesError, interface::program::config::LightConfig}; /// Validated PDA context after account extraction and config validation. pub struct ValidatedPdaContext { diff --git a/sdk-libs/sdk-types/src/interface/program/variant.rs b/sdk-libs/sdk-types/src/interface/program/variant.rs index a0f0aeaf8e..08e9a577dd 100644 --- a/sdk-libs/sdk-types/src/interface/program/variant.rs +++ b/sdk-libs/sdk-types/src/interface/program/variant.rs @@ -9,8 +9,10 @@ use alloc::vec::Vec; use light_account_checks::AccountInfoTrait; -use crate::interface::account::light_account::AccountType; -use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; +use crate::{ + error::LightSdkTypesError, interface::account::light_account::AccountType, AnchorDeserialize, + AnchorSerialize, +}; // --- Base trait (always available) --- @@ -135,6 +137,7 @@ pub trait PackedLightAccountVariantTrait: #[cfg(feature = "token")] mod token_traits { use alloc::vec::Vec; + use light_account_checks::AccountInfoTrait; use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; diff --git a/sdk-libs/sdk-types/src/lib.rs b/sdk-libs/sdk-types/src/lib.rs index 1bf497be86..89dc7a726b 100644 --- a/sdk-libs/sdk-types/src/lib.rs +++ b/sdk-libs/sdk-types/src/lib.rs @@ -31,8 +31,7 @@ pub use anchor_lang::{AnchorDeserialize, AnchorSerialize}; #[cfg(not(feature = "anchor"))] pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; pub use constants::*; -pub use light_account_checks; -pub use light_account_checks::discriminator::Discriminator as LightDiscriminator; +pub use light_account_checks::{self, discriminator::Discriminator as LightDiscriminator}; pub use light_compressed_account::CpiSigner; #[derive(Clone, Copy, Debug, PartialEq, Eq, AnchorSerialize, AnchorDeserialize)] diff --git a/sdk-libs/sdk/src/cpi/mod.rs b/sdk-libs/sdk/src/cpi/mod.rs index 4fb5dce11e..bb9b055398 100644 --- a/sdk-libs/sdk/src/cpi/mod.rs +++ b/sdk-libs/sdk/src/cpi/mod.rs @@ -36,20 +36,17 @@ //! ``` // Re-export everything from interface's CPI module (LightCpi, InvokeLightSystemProgram, etc.) -pub use light_sdk_types::interface::cpi::*; - use light_compressed_account::instruction_data::{ - compressed_proof::ValidityProof, - cpi_context::CompressedCpiContext, + compressed_proof::ValidityProof, cpi_context::CompressedCpiContext, }; +pub use light_sdk_types::interface::cpi::*; +#[cfg(feature = "poseidon")] +use crate::DataHasher; use crate::{ account::LightAccount, AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError, }; -#[cfg(feature = "poseidon")] -use crate::DataHasher; - /// Trait for Light CPI instruction types including `with_light_account`. /// /// This is the SDK-level trait that provides the full builder API: @@ -108,10 +105,7 @@ pub trait LightCpiInstruction: Sized { /// Macro to delegate base LightCpi methods to the interface trait impl. macro_rules! delegate_light_cpi { ($ty:ty) => { - fn new_cpi( - cpi_signer: crate::cpi::CpiSigner, - proof: ValidityProof, - ) -> Self { + fn new_cpi(cpi_signer: crate::cpi::CpiSigner, proof: ValidityProof) -> Self { <$ty as light_sdk_types::interface::cpi::LightCpi>::new_cpi(cpi_signer, proof) } fn get_mode(&self) -> u8 { diff --git a/sdk-libs/sdk/src/cpi/v1/invoke.rs b/sdk-libs/sdk/src/cpi/v1/invoke.rs index b5d6d3f606..048bbdb817 100644 --- a/sdk-libs/sdk/src/cpi/v1/invoke.rs +++ b/sdk-libs/sdk/src/cpi/v1/invoke.rs @@ -3,20 +3,17 @@ use light_compressed_account::instruction_data::{ }; use light_sdk_types::constants::{CPI_AUTHORITY_PDA_SEED, LIGHT_SYSTEM_PROGRAM_ID}; -#[cfg(feature = "poseidon")] -use crate::{account::poseidon::LightAccount as LightAccountPoseidon, DataHasher}; -use crate::{ - account::LightAccount, - cpi::CpiSigner, - error::LightSdkError, - instruction::account_info::CompressedAccountInfoTrait, - AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError, -}; - use super::{ lowlevel::{get_account_metas_from_config, CpiInstructionConfig}, CpiAccounts, }; +#[cfg(feature = "poseidon")] +use crate::{account::poseidon::LightAccount as LightAccountPoseidon, DataHasher}; +use crate::{ + account::LightAccount, cpi::CpiSigner, error::LightSdkError, + instruction::account_info::CompressedAccountInfoTrait, AnchorDeserialize, AnchorSerialize, + LightDiscriminator, ProgramError, +}; /// Light system program CPI instruction data builder. /// @@ -313,8 +310,7 @@ impl LightSystemProgramCpi { .map_err(ProgramError::from)?; let account_infos = cpi_accounts.to_account_infos(); - let config = - CpiInstructionConfig::try_from(&cpi_accounts).map_err(ProgramError::from)?; + let config = CpiInstructionConfig::try_from(&cpi_accounts).map_err(ProgramError::from)?; let account_metas = get_account_metas_from_config(config); let instruction = solana_instruction::Instruction { diff --git a/sdk-libs/sdk/src/cpi/v2/invoke.rs b/sdk-libs/sdk/src/cpi/v2/invoke.rs index c6cb852e2a..438cb3de7f 100644 --- a/sdk-libs/sdk/src/cpi/v2/invoke.rs +++ b/sdk-libs/sdk/src/cpi/v2/invoke.rs @@ -1,10 +1,8 @@ -use light_compressed_account::instruction_data::with_account_info::InstructionDataInvokeCpiWithAccountInfo; -use light_compressed_account::instruction_data::with_readonly::{ - InAccount, InstructionDataInvokeCpiWithReadOnly, -}; use light_compressed_account::instruction_data::{ compressed_proof::ValidityProof, cpi_context::CompressedCpiContext, + with_account_info::InstructionDataInvokeCpiWithAccountInfo, + with_readonly::{InAccount, InstructionDataInvokeCpiWithReadOnly}, }; #[cfg(feature = "poseidon")] diff --git a/sdk-libs/sdk/src/cpi/v2/mod.rs b/sdk-libs/sdk/src/cpi/v2/mod.rs index b3dcefd820..5ddcf5b6e1 100644 --- a/sdk-libs/sdk/src/cpi/v2/mod.rs +++ b/sdk-libs/sdk/src/cpi/v2/mod.rs @@ -8,8 +8,7 @@ // CpiAccounts from sdk-types (v2) pub use light_sdk_types::cpi_accounts::v2::CpiAccounts as GenericCpiAccounts; -pub type CpiAccounts<'c, 'info> = - GenericCpiAccounts<'c, solana_account_info::AccountInfo<'info>>; +pub type CpiAccounts<'c, 'info> = GenericCpiAccounts<'c, solana_account_info::AccountInfo<'info>>; // Instruction types from light-compressed-account pub use light_compressed_account::instruction_data::{ diff --git a/sdk-libs/sdk/src/error.rs b/sdk-libs/sdk/src/error.rs index 0f44812889..c9b7ec32c2 100644 --- a/sdk-libs/sdk/src/error.rs +++ b/sdk-libs/sdk/src/error.rs @@ -75,7 +75,7 @@ pub enum LightSdkError { InvalidCpiContextAccount, #[error("Invalid SolPool PDA account")] InvalidSolPoolPdaAccount, - #[error("CpigAccounts accounts slice starts with an invalid account. It should start with LightSystemProgram SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7.")] + #[error("CpiAccounts accounts slice starts with an invalid account. It should start with LightSystemProgram SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7.")] InvalidCpiAccountsOffset, #[error("Expected LightAccount to have no data for closure.")] ExpectedNoData, @@ -150,6 +150,7 @@ impl From for LightSdkTypesError { LightSdkError::CompressedAccountError(e) => { LightSdkTypesError::CompressedAccountError(e) } + LightSdkError::ProgramError(e) => LightSdkTypesError::ProgramError(u64::from(e) as u32), _ => LightSdkTypesError::ConstraintViolation, } } @@ -178,15 +179,9 @@ impl From for LightSdkError { LightSdkTypesError::CpiAccountsIndexOutOfBounds(index) => { LightSdkError::CpiAccountsIndexOutOfBounds(index) } - LightSdkTypesError::InvalidSolPoolPdaAccount => { - LightSdkError::InvalidSolPoolPdaAccount - } - LightSdkTypesError::InvalidCpiContextAccount => { - LightSdkError::InvalidCpiContextAccount - } - LightSdkTypesError::InvalidCpiAccountsOffset => { - LightSdkError::InvalidCpiAccountsOffset - } + LightSdkTypesError::InvalidSolPoolPdaAccount => LightSdkError::InvalidSolPoolPdaAccount, + LightSdkTypesError::InvalidCpiContextAccount => LightSdkError::InvalidCpiContextAccount, + LightSdkTypesError::InvalidCpiAccountsOffset => LightSdkError::InvalidCpiAccountsOffset, LightSdkTypesError::AccountError(e) => LightSdkError::AccountError(e), LightSdkTypesError::Hasher(e) => LightSdkError::Hasher(e), LightSdkTypesError::ConstraintViolation => LightSdkError::ConstraintViolation, @@ -206,6 +201,9 @@ impl From for LightSdkError { | LightSdkTypesError::CpiFailed | LightSdkTypesError::NotEnoughAccountKeys | LightSdkTypesError::MissingRequiredSignature => LightSdkError::ConstraintViolation, + LightSdkTypesError::ProgramError(code) => { + LightSdkError::ProgramError(ProgramError::Custom(code)) + } } } } diff --git a/sdk-libs/sdk/src/instruction/mod.rs b/sdk-libs/sdk/src/instruction/mod.rs index 0b3b5eeb36..a179517c91 100644 --- a/sdk-libs/sdk/src/instruction/mod.rs +++ b/sdk-libs/sdk/src/instruction/mod.rs @@ -42,7 +42,6 @@ // Re-export instruction types from sdk-types (available on all targets) pub use light_sdk_types::instruction::*; - // Re-export pack_accounts utilities from interface (off-chain only) #[cfg(not(target_os = "solana"))] pub use light_sdk_types::interface::instruction::*; diff --git a/sdk-libs/sdk/src/instruction/packed_accounts_ext.rs b/sdk-libs/sdk/src/instruction/packed_accounts_ext.rs index cf927854de..76906d012d 100644 --- a/sdk-libs/sdk/src/instruction/packed_accounts_ext.rs +++ b/sdk-libs/sdk/src/instruction/packed_accounts_ext.rs @@ -1,6 +1,7 @@ -use super::PackedAccounts; - -use super::system_accounts::{get_light_system_account_metas, SystemAccountMetaConfig}; +use super::{ + system_accounts::{get_light_system_account_metas, SystemAccountMetaConfig}, + PackedAccounts, +}; /// Extension trait adding Light system account helpers to [`PackedAccounts`]. /// @@ -20,10 +21,7 @@ pub trait PackedAccountsExt { /// /// This adds all the accounts required by the Light system program for v1 operations, /// including the CPI authority, registered programs, account compression program, and Noop program. - fn add_system_accounts( - &mut self, - config: SystemAccountMetaConfig, - ) -> crate::error::Result<()>; + fn add_system_accounts(&mut self, config: SystemAccountMetaConfig) -> crate::error::Result<()>; /// Adds v2 Light system program accounts to the account list. /// @@ -45,10 +43,7 @@ impl PackedAccountsExt for PackedAccounts { Ok(accounts) } - fn add_system_accounts( - &mut self, - config: SystemAccountMetaConfig, - ) -> crate::error::Result<()> { + fn add_system_accounts(&mut self, config: SystemAccountMetaConfig) -> crate::error::Result<()> { self.add_system_accounts_raw(get_light_system_account_metas(config)); Ok(()) } @@ -58,9 +53,9 @@ impl PackedAccountsExt for PackedAccounts { &mut self, config: SystemAccountMetaConfig, ) -> crate::error::Result<()> { - self.add_system_accounts_raw( - super::system_accounts::get_light_system_account_metas_v2(config), - ); + self.add_system_accounts_raw(super::system_accounts::get_light_system_account_metas_v2( + config, + )); Ok(()) } } diff --git a/sdk-libs/sdk/src/lib.rs b/sdk-libs/sdk/src/lib.rs index fee062e5b4..65868a700e 100644 --- a/sdk-libs/sdk/src/lib.rs +++ b/sdk-libs/sdk/src/lib.rs @@ -163,11 +163,11 @@ pub mod proof; pub mod transfer; pub mod utils; -pub use proof::borsh_compat; /// Backward-compat alias for the interface module pub use light_sdk_types::interface as compressible; /// Re-export the interface module pub use light_sdk_types::interface; +pub use proof::borsh_compat; #[cfg(feature = "merkle-tree")] pub mod merkle_tree; @@ -204,20 +204,17 @@ pub mod sdk_types { use anchor_lang::{AnchorDeserialize, AnchorSerialize}; #[cfg(not(feature = "anchor"))] use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; +pub use light_account_checks::{self, discriminator::Discriminator as LightDiscriminator}; // Re-export interface types from light-sdk-types::interface // Pack trait is only available off-chain (client-side) - uses PackedAccounts #[cfg(not(target_os = "solana"))] pub use light_sdk_types::interface::Pack; -pub use light_sdk_types::interface::Unpack; pub use light_sdk_types::interface::{ - process_initialize_light_config_checked, - InitializeLightConfigParams, - process_update_light_config, UpdateLightConfigParams, CompressAs, CompressedAccountData, - CompressedInitSpace, CompressionInfo, - HasCompressionInfo, LightConfig, Space, COMPRESSIBLE_CONFIG_SEED, - MAX_ADDRESS_TREES_PER_SPACE, + process_initialize_light_config_checked, process_update_light_config, CompressAs, + CompressedAccountData, CompressedInitSpace, CompressionInfo, HasCompressionInfo, + InitializeLightConfigParams, LightConfig, Space, Unpack, UpdateLightConfigParams, + COMPRESSIBLE_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE, }; -pub use light_account_checks::{self, discriminator::Discriminator as LightDiscriminator}; // Re-export as extern crate so downstream crates can use `::light_hasher::` paths pub extern crate light_hasher; #[cfg(feature = "poseidon")] diff --git a/sdk-tests/csdk-anchor-full-derived-test-sdk/src/lib.rs b/sdk-tests/csdk-anchor-full-derived-test-sdk/src/lib.rs index 80aaf78a8a..308e4aa52f 100644 --- a/sdk-tests/csdk-anchor-full-derived-test-sdk/src/lib.rs +++ b/sdk-tests/csdk-anchor-full-derived-test-sdk/src/lib.rs @@ -190,8 +190,7 @@ impl AmmSdk { account: &AccountInterface, is_vault_0: bool, ) -> Result<(), AmmSdkError> { - use light_account::Token; - use light_account::token::TokenDataWithSeeds; + use light_account::{token::TokenDataWithSeeds, Token}; let pool_state = self .pool_state_pubkey diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs b/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs index eb84f6d801..9aadf417c0 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs @@ -8,12 +8,12 @@ //! - MintToCpi use anchor_lang::prelude::*; -use light_anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; -use light_sdk_macros::LightAccounts; use light_account::{ - CreateAccountsProof, CreateTokenAccountCpi, CreateTokenAtaCpi, - LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR, + CreateAccountsProof, CreateTokenAccountCpi, CreateTokenAtaCpi, LIGHT_TOKEN_CONFIG, + LIGHT_TOKEN_RENT_SPONSOR, }; +use light_anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; +use light_sdk_macros::LightAccounts; use light_token::instruction::MintToCpi; use super::states::*; @@ -245,11 +245,7 @@ pub fn process_initialize_pool<'info>( bump: params.creator_lp_token_bump, } .idempotent() - .rent_free( - &config_info, - &sponsor_info, - &system_info, - ) + .rent_free(&config_info, &sponsor_info, &system_info) .invoke()?; } diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instruction_accounts.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instruction_accounts.rs index 1014544f22..fc719ad573 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instruction_accounts.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instruction_accounts.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::*; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata.rs index dd6ed1199d..88d8ca4cf4 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata.rs @@ -7,8 +7,10 @@ //! Here the macro should generate CreateTokenAtaCpi call automatically. use anchor_lang::prelude::*; +use light_account::{ + CreateAccountsProof, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_PROGRAM_ID, LIGHT_TOKEN_RENT_SPONSOR, +}; use light_sdk_macros::LightAccounts; -use light_account::{CreateAccountsProof, LIGHT_TOKEN_PROGRAM_ID, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; #[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] pub struct D10SingleAtaParams { diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata_markonly.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata_markonly.rs index 1ca724b643..c5dfcbedfe 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata_markonly.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata_markonly.rs @@ -6,8 +6,8 @@ //! User manually calls CreateTokenAtaCpi in the instruction handler. use anchor_lang::prelude::*; +use light_account::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_PROGRAM_ID, LIGHT_TOKEN_RENT_SPONSOR}; use light_sdk_macros::LightAccounts; -use light_account::{LIGHT_TOKEN_PROGRAM_ID, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; #[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] pub struct D10SingleAtaMarkonlyParams { diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_vault.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_vault.rs index 4b71109c87..c605ed12e2 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_vault.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_vault.rs @@ -7,8 +7,8 @@ //! Here the macro should generate the CreateTokenAccountCpi call automatically. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::{CreateAccountsProof, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_sdk_macros::LightAccounts; /// Seed for the vault authority PDA pub const D10_SINGLE_VAULT_AUTH_SEED: &[u8] = b"d10_single_vault_auth"; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/mixed_zc_borsh.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/mixed_zc_borsh.rs index 51922c4e7b..d1e7b71dce 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/mixed_zc_borsh.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/mixed_zc_borsh.rs @@ -4,8 +4,8 @@ //! Verifies that mixed serialization types work together in the same instruction. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::{ d11_zero_copy::ZcBasicRecord, d1_field_types::single_pubkey::SinglePubkeyRecord, diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/multiple_zc.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/multiple_zc.rs index 586e26e49f..437e9c9b8c 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/multiple_zc.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/multiple_zc.rs @@ -4,8 +4,8 @@ //! Verifies that the macro handles multiple AccountLoader fields correctly. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d11_zero_copy::ZcBasicRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ata.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ata.rs index 9e22415465..ab95a17e39 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ata.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ata.rs @@ -4,8 +4,8 @@ //! Verifies that zero-copy PDAs work alongside associated token account creation macros. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::{CreateAccountsProof, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_sdk_macros::LightAccounts; use crate::state::d11_zero_copy::ZcBasicRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ctx_seeds.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ctx_seeds.rs index 20fe9d4c50..e8031d4723 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ctx_seeds.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_ctx_seeds.rs @@ -4,8 +4,8 @@ //! Verifies that context account seeds work correctly with zero-copy accounts. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d11_zero_copy::ZcWithSeedsRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_mint_to.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_mint_to.rs index 85b536ae6e..07784c8dc0 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_mint_to.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_mint_to.rs @@ -4,8 +4,8 @@ //! Verifies that zero-copy PDAs work alongside token vault creation and MintTo CPI. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::{CreateAccountsProof, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_sdk_macros::LightAccounts; use crate::state::d11_zero_copy::ZcBasicRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_params_seeds.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_params_seeds.rs index cdcee60df6..82957556af 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_params_seeds.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_params_seeds.rs @@ -4,8 +4,8 @@ //! Verifies that seed fields not present on the struct work correctly. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d11_zero_copy::ZcWithParamsRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_vault.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_vault.rs index 37a3b22c72..39d605eb3c 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_vault.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d11_zero_copy/with_vault.rs @@ -4,8 +4,8 @@ //! Verifies that zero-copy PDAs work alongside token account creation macros. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::{CreateAccountsProof, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_sdk_macros::LightAccounts; use crate::state::d11_zero_copy::ZcBasicRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/all.rs index 84d6530df2..6845d8dd3d 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/all.rs @@ -4,8 +4,8 @@ //! Note: #[light_account(init)] is tested separately in amm_test/initialize.rs. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::{CreateAccountsProof, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/light_token.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/light_token.rs index 88b4314fff..3490e15bf1 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/light_token.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/light_token.rs @@ -5,8 +5,8 @@ //! CreateTokenAccountCpi in the instruction handler. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_sdk_macros::LightAccounts; pub const D5_VAULT_AUTH_SEED: &[u8] = b"d5_vault_auth"; pub const D5_VAULT_SEED: &[u8] = b"d5_vault"; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/rentfree_bare.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/rentfree_bare.rs index 4da3ef21b8..145d6ce94d 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/rentfree_bare.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d5_markers/rentfree_bare.rs @@ -7,8 +7,8 @@ //! because the RentFree derive macro generates code that accesses this field. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/account.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/account.rs index 468a5976a6..de19927f8d 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/account.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/account.rs @@ -3,8 +3,8 @@ //! Tests that #[light_account(init)] works with Account<'info, T> directly (not boxed). use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/all.rs index fdbc67b853..647f4ece60 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/all.rs @@ -3,8 +3,8 @@ //! Tests that both account type variants work in the same struct. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::{ d1_field_types::single_pubkey::SinglePubkeyRecord, diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/boxed.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/boxed.rs index 0076b8bbe6..5a904ea9c2 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/boxed.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d6_account_types/boxed.rs @@ -4,8 +4,8 @@ //! This exercises the Box unwrap path in seed_extraction.rs with is_boxed = true. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/all.rs index 2d52dc7307..c02dae94bf 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/all.rs @@ -3,8 +3,8 @@ //! Tests that different naming conventions work together in one struct. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::{CreateAccountsProof, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/creator.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/creator.rs index 00d3a5192c..16d8173982 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/creator.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/creator.rs @@ -3,8 +3,8 @@ //! Tests that #[light_account(init)] works when the payer field is named `creator` instead of `fee_payer`. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/light_token_config.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/light_token_config.rs index 6bb64cd533..d92cf58cee 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/light_token_config.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/light_token_config.rs @@ -5,8 +5,8 @@ //! but the token account is created manually via CreateTokenAccountCpi. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_sdk_macros::LightAccounts; pub const D7_LIGHT_TOKEN_AUTH_SEED: &[u8] = b"d7_light_token_auth"; pub const D7_LIGHT_TOKEN_VAULT_SEED: &[u8] = b"d7_light_token_vault"; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/payer.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/payer.rs index 4fa551e375..65042e546c 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/payer.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d7_infra_names/payer.rs @@ -3,8 +3,8 @@ //! Tests that #[light_account(init)] works when the payer field is named `payer` instead of `fee_payer`. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/all.rs index ebf411c7ba..f26df21efb 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/all.rs @@ -3,8 +3,8 @@ //! Tests the builder path with multiple #[light_account(init)] fields of different state types. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::{ d1_field_types::single_pubkey::SinglePubkeyRecord, diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/multi_rentfree.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/multi_rentfree.rs index 5f55ef1526..34008733a6 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/multi_rentfree.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/multi_rentfree.rs @@ -3,8 +3,8 @@ //! Tests the builder path with multiple #[light_account(init)] PDA accounts of the same type. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/pda_only.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/pda_only.rs index 86f37f477c..ab64992dd3 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/pda_only.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d8_builder_paths/pda_only.rs @@ -4,8 +4,8 @@ //! are marked with #[light_account(init)], without any token accounts. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/all.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/all.rs index 0620929e47..e91ee026d7 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/all.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/all.rs @@ -9,8 +9,8 @@ //! - FunctionCall: max_key(&a, &b) use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/array_bumps.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/array_bumps.rs index 967fb82320..6cf2e1929e 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/array_bumps.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/array_bumps.rs @@ -5,8 +5,8 @@ //! not by including &[bump] in the seeds array. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/complex_mixed.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/complex_mixed.rs index fbd6a5dba1..d458dc61ea 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/complex_mixed.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/complex_mixed.rs @@ -6,8 +6,8 @@ //! - Maximum seed complexity use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/const_patterns.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/const_patterns.rs index e130c6d19f..a1615db850 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/const_patterns.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/const_patterns.rs @@ -7,8 +7,8 @@ //! - Trait associated constants: ::CONSTANT use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/constant.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/constant.rs index 1c36e41c03..1e44690c9a 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/constant.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/constant.rs @@ -3,8 +3,8 @@ //! Tests ClassifiedSeed::Constant with constant identifier seeds. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/ctx_account.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/ctx_account.rs index 70d03fa28e..71742a0d05 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/ctx_account.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/ctx_account.rs @@ -3,8 +3,8 @@ //! Tests ClassifiedSeed::CtxAccount with authority.key() seeds. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/edge_cases.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/edge_cases.rs index 0e0c3a3b0c..c489d6c147 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/edge_cases.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/edge_cases.rs @@ -9,8 +9,8 @@ //! - Many literals in same seeds array use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/external_paths.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/external_paths.rs index fa4e0dce19..d3d3939af5 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/external_paths.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/external_paths.rs @@ -6,8 +6,8 @@ //! - Complex nested external paths use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/function_call.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/function_call.rs index ee52261e3c..0855e0032e 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/function_call.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/function_call.rs @@ -3,8 +3,8 @@ //! Tests ClassifiedSeed::FunctionCall with max_key(&a, &b) seeds. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/instruction_data.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/instruction_data.rs index f56608ab5b..70eb2e61f9 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/instruction_data.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/instruction_data.rs @@ -9,8 +9,8 @@ //! so we test naming variations within the seed expressions, not the param struct name. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/literal.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/literal.rs index c534979870..97cce854d3 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/literal.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/literal.rs @@ -3,8 +3,8 @@ //! Tests ClassifiedSeed::Literal with byte literal seeds. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/method_chains.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/method_chains.rs index bcdb3e83bf..908dd14e50 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/method_chains.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/method_chains.rs @@ -8,8 +8,8 @@ //! - Method chains on qualified paths use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/mixed.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/mixed.rs index 160f4fb066..88ff0940cc 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/mixed.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/mixed.rs @@ -3,8 +3,8 @@ //! Tests multiple seed types combined: literal + ctx_account + param. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/nested_seeds.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/nested_seeds.rs index 00de22e5e4..b7c134dd51 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/nested_seeds.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/nested_seeds.rs @@ -6,8 +6,8 @@ //! - Complex nested struct paths use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param.rs index b9786a23b9..dde7fa3614 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param.rs @@ -3,8 +3,8 @@ //! Tests ClassifiedSeed::DataField with params.owner.as_ref() seeds. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param_bytes.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param_bytes.rs index c4f45eaff9..d8b8b9325a 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param_bytes.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/param_bytes.rs @@ -3,8 +3,8 @@ //! Tests ClassifiedSeed::DataField with params.id.to_le_bytes() conversion. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/qualified_paths.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/qualified_paths.rs index 1cbe0bc5e2..5595e89f5e 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/qualified_paths.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d9_seeds/qualified_paths.rs @@ -7,8 +7,8 @@ //! - Nested module paths use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::d1_field_types::single_pubkey::SinglePubkeyRecord; diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/lib.rs b/sdk-tests/csdk-anchor-full-derived-test/src/lib.rs index d7619c4e06..fa9d004c48 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/lib.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/lib.rs @@ -2,10 +2,9 @@ #![allow(clippy::useless_asref)] // Testing macro handling of .as_ref() patterns use anchor_lang::prelude::*; +use light_account::{derive_light_cpi_signer, derive_light_rent_sponsor_pda, CpiSigner}; use light_instruction_decoder_derive::instruction_decoder; -use light_account::{derive_light_cpi_signer, derive_light_rent_sponsor_pda}; use light_sdk_macros::light_program; -use light_account::CpiSigner; pub mod amm_test; pub mod d5_markers; @@ -374,11 +373,7 @@ pub mod csdk_anchor_full_derived_test { bump: params.user_ata_bump, } .idempotent() - .rent_free( - &config_info, - &sponsor_info, - &system_info, - ) + .rent_free(&config_info, &sponsor_info, &system_info) .invoke()?; } diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/state/mod.rs b/sdk-tests/csdk-anchor-full-derived-test/src/state/mod.rs index b54a02b530..cc182247be 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/state/mod.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/state/mod.rs @@ -1,9 +1,8 @@ //! State structs for the test program and test cases organized by dimension. use anchor_lang::prelude::*; -use light_account::{CompressionInfo, PackedAddressTreeInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator, PackedAddressTreeInfo, ValidityProof}; use light_sdk_macros::LightAccount; -use light_account::ValidityProof; use light_token_interface::instructions::mint_action::MintWithContext; // Test modules diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_observation_state_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_observation_state_test.rs index b827a40eab..9d0ae037aa 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_observation_state_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_observation_state_test.rs @@ -495,7 +495,8 @@ fn test_pack_stores_pool_id_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 1, "should have 1 pubkey stored"); assert_eq!( - stored_pubkeys[packed.pool_id as usize], pool_id.to_bytes(), + stored_pubkeys[packed.pool_id as usize], + pool_id.to_bytes(), "stored pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_pool_state_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_pool_state_test.rs index 6e36544632..57e3cfa8f9 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_pool_state_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_pool_state_test.rs @@ -522,7 +522,8 @@ fn test_pack_stores_all_pubkeys_in_packed_accounts() { // Verify each pubkey is stored at its index for (i, expected_pubkey) in pubkeys.iter().enumerate() { assert_eq!( - stored_pubkeys[i], expected_pubkey.to_bytes(), + stored_pubkeys[i], + expected_pubkey.to_bytes(), "pubkey at index {} should match", i ); diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_game_session_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_game_session_test.rs index 0aad176f0f..6ff79b040f 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_game_session_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_game_session_test.rs @@ -442,11 +442,13 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.player as usize], player1.to_bytes(), + stored_pubkeys[packed1.player as usize], + player1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.player as usize], player2.to_bytes(), + stored_pubkeys[packed2.player as usize], + player2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_placeholder_record_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_placeholder_record_test.rs index ef5535db3d..02d3358ba3 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_placeholder_record_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_placeholder_record_test.rs @@ -355,11 +355,13 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), + stored_pubkeys[packed1.owner as usize], + owner1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), + stored_pubkeys[packed2.owner as usize], + owner2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_user_record_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_user_record_test.rs index e7fe5fe9c1..4c4142dec1 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_user_record_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_user_record_test.rs @@ -354,11 +354,13 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), + stored_pubkeys[packed1.owner as usize], + owner1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), + stored_pubkeys[packed2.owner as usize], + owner2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_multi_pubkey_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_multi_pubkey_test.rs index 24a5fe9e91..2282a33a55 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_multi_pubkey_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_multi_pubkey_test.rs @@ -385,27 +385,33 @@ fn test_pack_stores_all_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 6, "should have 6 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), + stored_pubkeys[packed1.owner as usize], + owner1.to_bytes(), "first record owner should match" ); assert_eq!( - stored_pubkeys[packed1.delegate as usize], delegate1.to_bytes(), + stored_pubkeys[packed1.delegate as usize], + delegate1.to_bytes(), "first record delegate should match" ); assert_eq!( - stored_pubkeys[packed1.authority as usize], authority1.to_bytes(), + stored_pubkeys[packed1.authority as usize], + authority1.to_bytes(), "first record authority should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), + stored_pubkeys[packed2.owner as usize], + owner2.to_bytes(), "second record owner should match" ); assert_eq!( - stored_pubkeys[packed2.delegate as usize], delegate2.to_bytes(), + stored_pubkeys[packed2.delegate as usize], + delegate2.to_bytes(), "second record delegate should match" ); assert_eq!( - stored_pubkeys[packed2.authority as usize], authority2.to_bytes(), + stored_pubkeys[packed2.authority as usize], + authority2.to_bytes(), "second record authority should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_single_pubkey_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_single_pubkey_test.rs index 402a84eede..b9bdb5a386 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_single_pubkey_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_single_pubkey_test.rs @@ -260,11 +260,13 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), + stored_pubkeys[packed1.owner as usize], + owner1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), + stored_pubkeys[packed2.owner as usize], + owner2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_all_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_all_compress_as_test.rs index 46f182fe83..eb67343efc 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_all_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_all_compress_as_test.rs @@ -461,11 +461,13 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), + stored_pubkeys[packed1.owner as usize], + owner1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), + stored_pubkeys[packed2.owner as usize], + owner2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_multiple_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_multiple_compress_as_test.rs index 57a1e367a0..4f37c9f832 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_multiple_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_multiple_compress_as_test.rs @@ -385,11 +385,13 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), + stored_pubkeys[packed1.owner as usize], + owner1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), + stored_pubkeys[packed2.owner as usize], + owner2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_no_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_no_compress_as_test.rs index 27c338bc5b..7a2f81d36b 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_no_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_no_compress_as_test.rs @@ -305,11 +305,13 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), + stored_pubkeys[packed1.owner as usize], + owner1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), + stored_pubkeys[packed2.owner as usize], + owner2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_option_none_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_option_none_compress_as_test.rs index 449f993f19..5b11b4b7c3 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_option_none_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_option_none_compress_as_test.rs @@ -390,11 +390,13 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), + stored_pubkeys[packed1.owner as usize], + owner1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), + stored_pubkeys[packed2.owner as usize], + owner2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_single_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_single_compress_as_test.rs index 92915b2d6f..aa1cf3af37 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_single_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_single_compress_as_test.rs @@ -307,11 +307,13 @@ fn test_pack_stores_pubkeys_in_packed_accounts() { let stored_pubkeys = packed_accounts.packed_pubkeys(); assert_eq!(stored_pubkeys.len(), 2, "should have 2 pubkeys stored"); assert_eq!( - stored_pubkeys[packed1.owner as usize], owner1.to_bytes(), + stored_pubkeys[packed1.owner as usize], + owner1.to_bytes(), "first pubkey should match" ); assert_eq!( - stored_pubkeys[packed2.owner as usize], owner2.to_bytes(), + stored_pubkeys[packed2.owner as usize], + owner2.to_bytes(), "second pubkey should match" ); } diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/shared.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/shared.rs index 31f3eca146..0c868ccad2 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/shared.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/shared.rs @@ -5,8 +5,8 @@ use std::borrow::Cow; -use light_hasher::{DataHasher, Sha256}; use light_account::Size; +use light_hasher::{DataHasher, Sha256}; use light_sdk::{ compressible::{CompressAs, CompressedInitSpace, CompressionState, HasCompressionInfo}, LightDiscriminator, diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs index e678a12709..228994ec57 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs @@ -353,10 +353,10 @@ async fn test_create_pdas_and_mint_auto() { }, GameSession as GameSessionState, UserRecord, }; + use light_account::TokenDataWithSeeds; use light_client::interface::{ create_load_instructions, AccountInterface, AccountSpec, ColdContext, PdaSpec, }; - use light_account::TokenDataWithSeeds; // Fetch unified interfaces (hot/cold transparent) let user_interface = rpc diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/compressibility_check_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/compressibility_check_test.rs index bc4cb266c6..9cfc2f336a 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/compressibility_check_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/compressibility_check_test.rs @@ -294,7 +294,9 @@ async fn test_batch_abort_non_compressible() { .value; // Build program account metas (fee_payer, config, rent_sponsor, compression_authority) - let (config_bytes, _) = light_account::LightConfig::derive_pda_bytes::>(&program_id.to_bytes(), 0); + let (config_bytes, _) = light_account::LightConfig::derive_pda_bytes::< + light_account::AccountInfo<'_>, + >(&program_id.to_bytes(), 0); let light_config_pda = Pubkey::from(config_bytes); let program_metas = vec![ AccountMeta::new(payer.pubkey(), true), diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/instruction_decoder_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/instruction_decoder_test.rs index 0f778056b8..68c1a2dc9b 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/instruction_decoder_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/instruction_decoder_test.rs @@ -186,8 +186,7 @@ fn test_enhanced_decoder_params_decoding() { instruction_accounts::CreateTwoMintsParams, CsdkTestInstructionDecoder, }; use light_compressed_account::instruction_data::compressed_proof::ValidityProof; - use light_sdk_types::instruction::PackedAddressTreeInfo; - use light_sdk_types::interface::CreateAccountsProof; + use light_sdk_types::{instruction::PackedAddressTreeInfo, interface::CreateAccountsProof}; let decoder = CsdkTestInstructionDecoder; @@ -395,8 +394,7 @@ fn test_attribute_macro_decoder_with_instruction_data() { }; use light_compressed_account::instruction_data::compressed_proof::ValidityProof; use light_program_test::logging::InstructionDecoder; - use light_sdk_types::instruction::PackedAddressTreeInfo; - use light_sdk_types::interface::CreateAccountsProof; + use light_sdk_types::{instruction::PackedAddressTreeInfo, interface::CreateAccountsProof}; let decoder = CsdkAnchorFullDerivedTestInstructionDecoder; diff --git a/sdk-tests/manual-test-pinocchio/src/account_loader/accounts.rs b/sdk-tests/manual-test-pinocchio/src/account_loader/accounts.rs index a13d766c1a..2a3db095e9 100644 --- a/sdk-tests/manual-test-pinocchio/src/account_loader/accounts.rs +++ b/sdk-tests/manual-test-pinocchio/src/account_loader/accounts.rs @@ -53,8 +53,8 @@ impl<'a> CreateZeroCopy<'a> { return Err(ProgramError::InvalidSeeds); } - let rent = pinocchio::sysvars::rent::Rent::get() - .map_err(|_| ProgramError::UnsupportedSysvar)?; + let rent = + pinocchio::sysvars::rent::Rent::get().map_err(|_| ProgramError::UnsupportedSysvar)?; let lamports = rent.minimum_balance(space); let bump_bytes = [bump]; diff --git a/sdk-tests/manual-test-pinocchio/src/account_loader/derived_accounts.rs b/sdk-tests/manual-test-pinocchio/src/account_loader/derived_accounts.rs index c0ab631be8..2732bd233e 100644 --- a/sdk-tests/manual-test-pinocchio/src/account_loader/derived_accounts.rs +++ b/sdk-tests/manual-test-pinocchio/src/account_loader/derived_accounts.rs @@ -6,10 +6,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; use light_account_pinocchio::{ light_account_checks::{self, packed_accounts::ProgramPackedAccounts}, - prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, - CpiContextWriteAccounts, InvokeLightSystemProgram, LightAccount, LightAccountVariantTrait, - LightFinalize, LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, - PackedLightAccountVariantTrait, + prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, CpiContextWriteAccounts, + InvokeLightSystemProgram, LightAccount, LightAccountVariantTrait, LightFinalize, LightPreInit, + LightSdkTypesError, PackedAddressTreeInfoExt, PackedLightAccountVariantTrait, }; use light_compressed_account::instruction_data::{ cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, @@ -81,9 +80,8 @@ impl LightPreInit for CreateZeroCopy<'_> { let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); // 3. Load config and get current slot - let light_config = - LightConfig::load_checked(self.compression_config, &crate::ID) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + let light_config = LightConfig::load_checked(self.compression_config, &crate::ID) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; let current_slot = Clock::get() .map_err(|_| LightSdkTypesError::InvalidInstructionData)? .slot; @@ -109,8 +107,7 @@ impl LightPreInit for CreateZeroCopy<'_> { .record .try_borrow_mut_data() .map_err(|_| LightSdkTypesError::Borsh)?; - let record_bytes = - &mut account_data[8..8 + core::mem::size_of::()]; + let record_bytes = &mut account_data[8..8 + core::mem::size_of::()]; let record: &mut ZeroCopyRecord = bytemuck::from_bytes_mut(record_bytes); record.set_decompressed(&light_config, current_slot); } @@ -142,8 +139,7 @@ impl LightPreInit for CreateZeroCopy<'_> { cpi_context: cpi_accounts.cpi_context()?, cpi_signer: crate::LIGHT_CPI_SIGNER, }; - instruction_data - .invoke_write_to_cpi_context_first(cpi_context_accounts)?; + instruction_data.invoke_write_to_cpi_context_first(cpi_context_accounts)?; } Ok(false) // No mints, so no CPI context write @@ -321,12 +317,11 @@ impl light_account_pinocchio::IntoVariant for ZeroCopyRec fn into_variant( self, data: &[u8], - ) -> std::result::Result - { + ) -> std::result::Result { // For ZeroCopy (Pod) accounts, data is the full Pod bytes including compression_info. // We deserialize using BorshDeserialize (which ZeroCopyRecord implements). - let record: ZeroCopyRecord = BorshDeserialize::deserialize(&mut &data[..]) - .map_err(|_| LightSdkTypesError::Borsh)?; + let record: ZeroCopyRecord = + BorshDeserialize::deserialize(&mut &data[..]).map_err(|_| LightSdkTypesError::Borsh)?; // Verify the owner in data matches the seed if record.owner != self.owner { @@ -347,9 +342,7 @@ impl light_account_pinocchio::IntoVariant for ZeroCopyRec /// Implement Pack trait to allow ZeroCopyRecordVariant to be used with `create_load_instructions`. /// Transforms the variant into PackedLightAccountVariant for efficient serialization. #[cfg(not(target_os = "solana"))] -impl light_account_pinocchio::Pack - for ZeroCopyRecordVariant -{ +impl light_account_pinocchio::Pack for ZeroCopyRecordVariant { type Packed = crate::derived_variants::PackedLightAccountVariant; fn pack( @@ -365,7 +358,8 @@ impl light_account_pinocchio::Pack Ok( crate::derived_variants::PackedLightAccountVariant::ZeroCopyRecord { seeds: PackedZeroCopyRecordSeeds { - owner_idx: accounts.insert_or_get(solana_pubkey::Pubkey::from(self.seeds.owner)), + owner_idx: accounts + .insert_or_get(solana_pubkey::Pubkey::from(self.seeds.owner)), name: self.seeds.name.clone(), bump, }, diff --git a/sdk-tests/manual-test-pinocchio/src/all/accounts.rs b/sdk-tests/manual-test-pinocchio/src/all/accounts.rs index 3484bb1ee2..3018df4509 100644 --- a/sdk-tests/manual-test-pinocchio/src/all/accounts.rs +++ b/sdk-tests/manual-test-pinocchio/src/all/accounts.rs @@ -97,8 +97,7 @@ impl<'a> CreateAllAccounts<'a> { { let space = 8 + MinimalRecord::INIT_SPACE; let seeds: &[&[u8]] = &[ALL_BORSH_SEED, ¶ms.owner]; - let (expected_pda, bump) = - pinocchio::pubkey::find_program_address(seeds, &crate::ID); + let (expected_pda, bump) = pinocchio::pubkey::find_program_address(seeds, &crate::ID); if borsh_record.key() != &expected_pda { return Err(ProgramError::InvalidSeeds); } @@ -135,8 +134,7 @@ impl<'a> CreateAllAccounts<'a> { { let space = 8 + ZeroCopyRecord::INIT_SPACE; let seeds: &[&[u8]] = &[ALL_ZERO_COPY_SEED, ¶ms.owner]; - let (expected_pda, bump) = - pinocchio::pubkey::find_program_address(seeds, &crate::ID); + let (expected_pda, bump) = pinocchio::pubkey::find_program_address(seeds, &crate::ID); if zero_copy_record.key() != &expected_pda { return Err(ProgramError::InvalidSeeds); } diff --git a/sdk-tests/manual-test-pinocchio/src/all/derived.rs b/sdk-tests/manual-test-pinocchio/src/all/derived.rs index ba2d80b8fb..3188fb2728 100644 --- a/sdk-tests/manual-test-pinocchio/src/all/derived.rs +++ b/sdk-tests/manual-test-pinocchio/src/all/derived.rs @@ -7,13 +7,12 @@ //! - 1 ATA via `CreateTokenAtaCpi` use light_account_pinocchio::{ - prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, - CpiContextWriteAccounts, InvokeLightSystemProgram, LightAccount, LightFinalize, - LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, - invoke_create_mints, CreateMintsInfraAccounts, CreateMintsParams as SdkCreateMintsParams, - SingleMintParams, derive_mint_compressed_address, find_mint_address, + derive_associated_token_account, derive_mint_compressed_address, find_mint_address, + invoke_create_mints, prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, + CpiContextWriteAccounts, CreateMintsInfraAccounts, CreateMintsParams as SdkCreateMintsParams, + CreateTokenAccountCpi, CreateTokenAtaCpi, InvokeLightSystemProgram, LightAccount, + LightFinalize, LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, SingleMintParams, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, - CreateTokenAccountCpi, CreateTokenAtaCpi, derive_associated_token_account, }; use light_compressed_account::instruction_data::{ cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, @@ -70,9 +69,8 @@ impl LightPreInit for CreateAllAccounts<'_> { // ==================================================================== // 3. Load config, get current slot // ==================================================================== - let light_config = - LightConfig::load_checked(self.compression_config, &crate::ID) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + let light_config = LightConfig::load_checked(self.compression_config, &crate::ID) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; let current_slot = Clock::get() .map_err(|_| LightSdkTypesError::InvalidInstructionData)? .slot; @@ -104,7 +102,8 @@ impl LightPreInit for CreateAllAccounts<'_> { .borsh_record .try_borrow_mut_data() .map_err(|_| LightSdkTypesError::Borsh)?; - let record = crate::pda::MinimalRecord::mut_from_account_data(&mut account_data); + let record = + crate::pda::MinimalRecord::mut_from_account_data(&mut account_data); record.set_decompressed(&light_config, current_slot); } @@ -156,8 +155,7 @@ impl LightPreInit for CreateAllAccounts<'_> { cpi_context: cpi_accounts.cpi_context()?, cpi_signer: crate::LIGHT_CPI_SIGNER, }; - instruction_data - .invoke_write_to_cpi_context_first(cpi_context_accounts)?; + instruction_data.invoke_write_to_cpi_context_first(cpi_context_accounts)?; } // ==================================================================== @@ -171,10 +169,8 @@ impl LightPreInit for CreateAllAccounts<'_> { let (mint_pda, mint_bump) = find_mint_address(&mint_signer_key); // Derive compression address - let compression_address = derive_mint_compressed_address( - &mint_signer_key, - &address_tree_pubkey, - ); + let compression_address = + derive_mint_compressed_address(&mint_signer_key, &address_tree_pubkey); // Build mint signer seeds let mint_signer_seeds: &[&[u8]] = &[ @@ -270,10 +266,8 @@ impl LightPreInit for CreateAllAccounts<'_> { // 7. Create ATA via CreateTokenAtaCpi // ==================================================================== { - let (_, ata_bump) = derive_associated_token_account( - self.ata_owner.key(), - self.mint.key(), - ); + let (_, ata_bump) = + derive_associated_token_account(self.ata_owner.key(), self.mint.key()); CreateTokenAtaCpi { payer: self.payer, diff --git a/sdk-tests/manual-test-pinocchio/src/all/derived_accounts.rs b/sdk-tests/manual-test-pinocchio/src/all/derived_accounts.rs index 48e1ceae3d..fa24058956 100644 --- a/sdk-tests/manual-test-pinocchio/src/all/derived_accounts.rs +++ b/sdk-tests/manual-test-pinocchio/src/all/derived_accounts.rs @@ -68,10 +68,7 @@ impl LightAccountVariantTrait<3> for AllBorshVariant { /// Get seed values as owned byte vectors for PDA derivation. /// Generated from: seeds = [b"all_borsh", params.owner.as_ref()] fn seed_vec(&self) -> Vec> { - vec![ - ALL_BORSH_SEED.to_vec(), - self.seeds.owner.to_vec(), - ] + vec![ALL_BORSH_SEED.to_vec(), self.seeds.owner.to_vec()] } /// Get seed references with bump for CPI signing. @@ -109,9 +106,7 @@ impl PackedLightAccountVariantTrait<3> for PackedAllBorshVariant { .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; Ok(AllBorshVariant { - seeds: AllBorshSeeds { - owner: owner.key(), - }, + seeds: AllBorshSeeds { owner: owner.key() }, data, }) } @@ -199,10 +194,7 @@ impl LightAccountVariantTrait<3> for AllZeroCopyVariant { /// Get seed values as owned byte vectors for PDA derivation. /// Generated from: seeds = [b"all_zero_copy", params.owner.as_ref()] fn seed_vec(&self) -> Vec> { - vec![ - ALL_ZERO_COPY_SEED.to_vec(), - self.seeds.owner.to_vec(), - ] + vec![ALL_ZERO_COPY_SEED.to_vec(), self.seeds.owner.to_vec()] } /// Get seed references with bump for CPI signing. @@ -240,9 +232,7 @@ impl PackedLightAccountVariantTrait<3> for PackedAllZeroCopyVariant { .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; Ok(AllZeroCopyVariant { - seeds: AllZeroCopySeeds { - owner: owner.key(), - }, + seeds: AllZeroCopySeeds { owner: owner.key() }, data, }) } @@ -284,13 +274,10 @@ impl PackedLightAccountVariantTrait<3> for PackedAllZeroCopyVariant { /// This enables the high-level `create_load_instructions` API. #[cfg(not(target_os = "solana"))] impl light_account_pinocchio::IntoVariant for AllBorshSeeds { - fn into_variant( - self, - data: &[u8], - ) -> std::result::Result { + fn into_variant(self, data: &[u8]) -> std::result::Result { // Deserialize the compressed data (which includes compression_info) - let record: MinimalRecord = BorshDeserialize::deserialize(&mut &data[..]) - .map_err(|_| LightSdkTypesError::Borsh)?; + let record: MinimalRecord = + BorshDeserialize::deserialize(&mut &data[..]).map_err(|_| LightSdkTypesError::Borsh)?; // Verify the owner in data matches the seed if record.owner != self.owner { @@ -327,7 +314,8 @@ impl light_account_pinocchio::Pack for AllBorsh Ok( crate::derived_variants::PackedLightAccountVariant::AllBorsh { seeds: PackedAllBorshSeeds { - owner_idx: accounts.insert_or_get(solana_pubkey::Pubkey::from(self.seeds.owner)), + owner_idx: accounts + .insert_or_get(solana_pubkey::Pubkey::from(self.seeds.owner)), bump, }, data: packed_data, @@ -350,8 +338,8 @@ impl light_account_pinocchio::IntoVariant for AllZeroCopySee ) -> std::result::Result { // For ZeroCopy (Pod) accounts, data is the full Pod bytes including compression_info. // We deserialize using BorshDeserialize (which ZeroCopyRecord implements). - let record: ZeroCopyRecord = BorshDeserialize::deserialize(&mut &data[..]) - .map_err(|_| LightSdkTypesError::Borsh)?; + let record: ZeroCopyRecord = + BorshDeserialize::deserialize(&mut &data[..]).map_err(|_| LightSdkTypesError::Borsh)?; // Verify the owner in data matches the seed if record.owner != self.owner { @@ -388,7 +376,8 @@ impl light_account_pinocchio::Pack for AllZeroC Ok( crate::derived_variants::PackedLightAccountVariant::AllZeroCopy { seeds: PackedAllZeroCopySeeds { - owner_idx: accounts.insert_or_get(solana_pubkey::Pubkey::from(self.seeds.owner)), + owner_idx: accounts + .insert_or_get(solana_pubkey::Pubkey::from(self.seeds.owner)), bump, }, data: packed_data, diff --git a/sdk-tests/manual-test-pinocchio/src/ata/derived.rs b/sdk-tests/manual-test-pinocchio/src/ata/derived.rs index 576e7c171a..a11e82cf7e 100644 --- a/sdk-tests/manual-test-pinocchio/src/ata/derived.rs +++ b/sdk-tests/manual-test-pinocchio/src/ata/derived.rs @@ -1,8 +1,8 @@ //! Derived code - what the macro would generate for associated token accounts. use light_account_pinocchio::{ - CreateTokenAtaCpi, LightFinalize, LightPreInit, LightSdkTypesError, - derive_associated_token_account, + derive_associated_token_account, CreateTokenAtaCpi, LightFinalize, LightPreInit, + LightSdkTypesError, }; use pinocchio::account_info::AccountInfo; @@ -20,10 +20,7 @@ impl LightPreInit for CreateAtaAccounts<'_> { ) -> std::result::Result { let inner = || -> std::result::Result { // Derive the ATA bump on-chain - let (_, bump) = derive_associated_token_account( - self.ata_owner.key(), - self.mint.key(), - ); + let (_, bump) = derive_associated_token_account(self.ata_owner.key(), self.mint.key()); // Create ATA via CPI with idempotent + rent-free mode // NOTE: Unlike token vaults, ATAs use .invoke() not .invoke_signed() diff --git a/sdk-tests/manual-test-pinocchio/src/derived_decompress.rs b/sdk-tests/manual-test-pinocchio/src/derived_decompress.rs index 35b23c0dd0..720c464199 100644 --- a/sdk-tests/manual-test-pinocchio/src/derived_decompress.rs +++ b/sdk-tests/manual-test-pinocchio/src/derived_decompress.rs @@ -4,7 +4,11 @@ //! With the trait-based dispatch, this module is minimal - just specifies the variant type. use light_account_pinocchio::process_decompress_pda_accounts_idempotent; -use pinocchio::{account_info::AccountInfo, program_error::ProgramError, sysvars::{clock::Clock, Sysvar}}; +use pinocchio::{ + account_info::AccountInfo, + program_error::ProgramError, + sysvars::{clock::Clock, Sysvar}, +}; use crate::derived_variants::PackedLightAccountVariant; diff --git a/sdk-tests/manual-test-pinocchio/src/derived_light_config.rs b/sdk-tests/manual-test-pinocchio/src/derived_light_config.rs index bf37cff796..def854e2a6 100644 --- a/sdk-tests/manual-test-pinocchio/src/derived_light_config.rs +++ b/sdk-tests/manual-test-pinocchio/src/derived_light_config.rs @@ -21,8 +21,7 @@ pub fn process_initialize_config( accounts: &[AccountInfo], data: &[u8], ) -> Result<(), ProgramError> { - let params = InitConfigParams::try_from_slice(data) - .map_err(|_| ProgramError::BorshIoError)?; + let params = InitConfigParams::try_from_slice(data).map_err(|_| ProgramError::BorshIoError)?; if accounts.len() < 5 { return Err(ProgramError::NotEnoughAccountKeys); @@ -50,10 +49,7 @@ pub fn process_initialize_config( .map_err(|e| ProgramError::Custom(u32::from(e))) } -pub fn process_update_config( - accounts: &[AccountInfo], - data: &[u8], -) -> Result<(), ProgramError> { +pub fn process_update_config(accounts: &[AccountInfo], data: &[u8]) -> Result<(), ProgramError> { if accounts.len() < 2 { return Err(ProgramError::NotEnoughAccountKeys); } @@ -61,7 +57,7 @@ pub fn process_update_config( let authority = &accounts[0]; let config = &accounts[1]; - let remaining = [config.clone(), authority.clone()]; + let remaining = [*config, *authority]; process_update_light_config(&remaining, data, &crate::ID) .map_err(|e| ProgramError::Custom(u32::from(e))) } diff --git a/sdk-tests/manual-test-pinocchio/src/lib.rs b/sdk-tests/manual-test-pinocchio/src/lib.rs index a9eae484c7..b33ef025e4 100644 --- a/sdk-tests/manual-test-pinocchio/src/lib.rs +++ b/sdk-tests/manual-test-pinocchio/src/lib.rs @@ -9,7 +9,6 @@ use light_account_pinocchio::{derive_light_cpi_signer, CpiSigner, LightFinalize, use light_macros::pubkey_array; use pinocchio::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; - pub mod account_loader; pub mod all; pub mod ata; @@ -151,7 +150,10 @@ fn process_create_pda(accounts: &[AccountInfo], data: &[u8]) -> Result<(), Progr // Business logic: set account data { - let mut account_data = ctx.record.try_borrow_mut_data().map_err(|_| ProgramError::AccountBorrowFailed)?; + let mut account_data = ctx + .record + .try_borrow_mut_data() + .map_err(|_| ProgramError::AccountBorrowFailed)?; let record = pda::state::MinimalRecord::mut_from_account_data(&mut account_data); record.owner = params.owner; } @@ -179,8 +181,12 @@ fn process_create_zero_copy(accounts: &[AccountInfo], data: &[u8]) -> Result<(), // Business logic: set zero-copy account data { - let mut account_data = ctx.record.try_borrow_mut_data().map_err(|_| ProgramError::AccountBorrowFailed)?; - let record_bytes = &mut account_data[8..8 + core::mem::size_of::()]; + let mut account_data = ctx + .record + .try_borrow_mut_data() + .map_err(|_| ProgramError::AccountBorrowFailed)?; + let record_bytes = + &mut account_data[8..8 + core::mem::size_of::()]; let record: &mut account_loader::ZeroCopyRecord = bytemuck::from_bytes_mut(record_bytes); record.owner = params.owner; record.value = params.value; @@ -272,12 +278,18 @@ fn process_create_all(accounts: &[AccountInfo], data: &[u8]) -> Result<(), Progr // Business logic: set PDA data { - let mut borsh_data = ctx.borsh_record.try_borrow_mut_data().map_err(|_| ProgramError::AccountBorrowFailed)?; + let mut borsh_data = ctx + .borsh_record + .try_borrow_mut_data() + .map_err(|_| ProgramError::AccountBorrowFailed)?; let borsh_record = pda::state::MinimalRecord::mut_from_account_data(&mut borsh_data); borsh_record.owner = params.owner; } { - let mut zc_data = ctx.zero_copy_record.try_borrow_mut_data().map_err(|_| ProgramError::AccountBorrowFailed)?; + let mut zc_data = ctx + .zero_copy_record + .try_borrow_mut_data() + .map_err(|_| ProgramError::AccountBorrowFailed)?; let record_bytes = &mut zc_data[8..8 + core::mem::size_of::()]; let record: &mut account_loader::ZeroCopyRecord = bytemuck::from_bytes_mut(record_bytes); diff --git a/sdk-tests/manual-test-pinocchio/src/pda/accounts.rs b/sdk-tests/manual-test-pinocchio/src/pda/accounts.rs index 5aeb474e46..6e798e3223 100644 --- a/sdk-tests/manual-test-pinocchio/src/pda/accounts.rs +++ b/sdk-tests/manual-test-pinocchio/src/pda/accounts.rs @@ -51,8 +51,8 @@ impl<'a> CreatePda<'a> { return Err(ProgramError::InvalidSeeds); } - let rent = pinocchio::sysvars::rent::Rent::get() - .map_err(|_| ProgramError::UnsupportedSysvar)?; + let rent = + pinocchio::sysvars::rent::Rent::get().map_err(|_| ProgramError::UnsupportedSysvar)?; let lamports = rent.minimum_balance(space); let bump_bytes = [bump]; diff --git a/sdk-tests/manual-test-pinocchio/src/pda/derived_accounts.rs b/sdk-tests/manual-test-pinocchio/src/pda/derived_accounts.rs index bde5d3d692..f5dbd70139 100644 --- a/sdk-tests/manual-test-pinocchio/src/pda/derived_accounts.rs +++ b/sdk-tests/manual-test-pinocchio/src/pda/derived_accounts.rs @@ -1,10 +1,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; use light_account_pinocchio::{ light_account_checks::{self, packed_accounts::ProgramPackedAccounts}, - prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, - CpiContextWriteAccounts, InvokeLightSystemProgram, LightAccount, LightAccountVariantTrait, - LightFinalize, LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, - PackedLightAccountVariantTrait, + prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, CpiContextWriteAccounts, + InvokeLightSystemProgram, LightAccount, LightAccountVariantTrait, LightFinalize, LightPreInit, + LightSdkTypesError, PackedAddressTreeInfoExt, PackedLightAccountVariantTrait, }; use light_compressed_account::instruction_data::{ cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, @@ -69,9 +68,8 @@ impl LightPreInit for CreatePda<'_> { const NUM_LIGHT_PDAS: usize = 1; // 6. Set compression_info from config - let light_config = - LightConfig::load_checked(self.compression_config, &crate::ID) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + let light_config = LightConfig::load_checked(self.compression_config, &crate::ID) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; let current_slot = Clock::get() .map_err(|_| LightSdkTypesError::InvalidInstructionData)? .slot; @@ -134,8 +132,7 @@ impl LightPreInit for CreatePda<'_> { }; if !WITH_CPI_CONTEXT { // 5. Invoke Light System Program CPI - instruction_data - .invoke(cpi_accounts)?; + instruction_data.invoke(cpi_accounts)?; } else { // For flows that combine light mints with light PDAs, write to CPI context first. // The authority and cpi_context accounts must be provided in remaining_accounts. @@ -145,8 +142,7 @@ impl LightPreInit for CreatePda<'_> { cpi_context: cpi_accounts.cpi_context()?, cpi_signer: crate::LIGHT_CPI_SIGNER, }; - instruction_data - .invoke_write_to_cpi_context_first(cpi_context_accounts)?; + instruction_data.invoke_write_to_cpi_context_first(cpi_context_accounts)?; } } // ===================================================================== @@ -329,8 +325,8 @@ impl light_account_pinocchio::IntoVariant for MinimalRecor data: &[u8], ) -> std::result::Result { // Deserialize the compressed data (which includes compression_info) - let record: MinimalRecord = BorshDeserialize::deserialize(&mut &data[..]) - .map_err(|_| LightSdkTypesError::Borsh)?; + let record: MinimalRecord = + BorshDeserialize::deserialize(&mut &data[..]).map_err(|_| LightSdkTypesError::Borsh)?; // Verify the owner in data matches the seed if record.owner != self.owner { @@ -367,7 +363,8 @@ impl light_account_pinocchio::Pack for MinimalR Ok( crate::derived_variants::PackedLightAccountVariant::MinimalRecord { seeds: PackedMinimalRecordSeeds { - owner_idx: accounts.insert_or_get(solana_pubkey::Pubkey::from(self.seeds.owner)), + owner_idx: accounts + .insert_or_get(solana_pubkey::Pubkey::from(self.seeds.owner)), nonce_bytes: self.seeds.nonce.to_le_bytes(), bump, }, diff --git a/sdk-tests/manual-test-pinocchio/src/token_account/accounts.rs b/sdk-tests/manual-test-pinocchio/src/token_account/accounts.rs index 1822674b88..69bd3406b4 100644 --- a/sdk-tests/manual-test-pinocchio/src/token_account/accounts.rs +++ b/sdk-tests/manual-test-pinocchio/src/token_account/accounts.rs @@ -48,8 +48,7 @@ impl<'a> CreateTokenVaultAccounts<'a> { { let mint_key = mint.key(); let seeds: &[&[u8]] = &[TOKEN_VAULT_SEED, mint_key]; - let (expected_pda, _bump) = - pinocchio::pubkey::find_program_address(seeds, &crate::ID); + let (expected_pda, _bump) = pinocchio::pubkey::find_program_address(seeds, &crate::ID); if token_vault.key() != &expected_pda { return Err(ProgramError::InvalidSeeds); } diff --git a/sdk-tests/manual-test-pinocchio/src/token_account/derived.rs b/sdk-tests/manual-test-pinocchio/src/token_account/derived.rs index 5765e66fba..90efc00eae 100644 --- a/sdk-tests/manual-test-pinocchio/src/token_account/derived.rs +++ b/sdk-tests/manual-test-pinocchio/src/token_account/derived.rs @@ -1,12 +1,12 @@ //! Derived code - what the macro would generate for token accounts. use borsh::{BorshDeserialize, BorshSerialize}; +#[cfg(not(target_os = "solana"))] +use light_account_pinocchio::Pack; use light_account_pinocchio::{ light_account_checks::{self}, CreateTokenAccountCpi, LightFinalize, LightPreInit, LightSdkTypesError, Unpack, }; -#[cfg(not(target_os = "solana"))] -use light_account_pinocchio::Pack; use pinocchio::account_info::AccountInfo; use super::accounts::{CreateTokenVaultAccounts, CreateTokenVaultParams, TOKEN_VAULT_SEED}; diff --git a/sdk-tests/manual-test-pinocchio/src/two_mints/derived.rs b/sdk-tests/manual-test-pinocchio/src/two_mints/derived.rs index 5f092e8397..ff8e833730 100644 --- a/sdk-tests/manual-test-pinocchio/src/two_mints/derived.rs +++ b/sdk-tests/manual-test-pinocchio/src/two_mints/derived.rs @@ -2,11 +2,10 @@ //! Contains LightPreInit/LightFinalize trait implementations. use light_account_pinocchio::{ - invoke_create_mints, get_output_queue_next_index, CreateMintsInfraAccounts, - CreateMintsParams as SdkCreateMintsParams, SingleMintParams, - derive_mint_compressed_address, find_mint_address, - CpiAccounts, CpiAccountsConfig, LightFinalize, LightPreInit, LightSdkTypesError, - PackedAddressTreeInfoExt, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, + derive_mint_compressed_address, find_mint_address, get_output_queue_next_index, + invoke_create_mints, CpiAccounts, CpiAccountsConfig, CreateMintsInfraAccounts, + CreateMintsParams as SdkCreateMintsParams, LightFinalize, LightPreInit, LightSdkTypesError, + PackedAddressTreeInfoExt, SingleMintParams, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, }; use pinocchio::account_info::AccountInfo; @@ -25,7 +24,6 @@ impl LightPreInit for CreateDerivedMintsA params: &CreateDerivedMintsParams, ) -> std::result::Result { let inner = || -> std::result::Result { - // ==================================================================== // STATIC BOILERPLATE (same across all LightPreInit implementations) // ==================================================================== @@ -73,14 +71,10 @@ impl LightPreInit for CreateDerivedMintsA // Derive compression addresses (from mint_signer + address_tree) // address_tree_pubkey is already [u8; 32] from get_tree_pubkey - let compression_address_0 = derive_mint_compressed_address( - &mint_signer_0, - &address_tree_pubkey, - ); - let compression_address_1 = derive_mint_compressed_address( - &mint_signer_1, - &address_tree_pubkey, - ); + let compression_address_0 = + derive_mint_compressed_address(&mint_signer_0, &address_tree_pubkey); + let compression_address_1 = + derive_mint_compressed_address(&mint_signer_1, &address_tree_pubkey); // Build mint signer seeds for CPI (mint::seeds + bump) let mint_signer_0_seeds: &[&[u8]] = &[ @@ -142,8 +136,8 @@ impl LightPreInit for CreateDerivedMintsA // Read base_leaf_index from output queue (required for N > 1) let output_queue_index = params.create_accounts_proof.output_state_tree_index; - let output_queue = cpi_accounts - .get_tree_account_info(output_queue_index as usize)?; + let output_queue = + cpi_accounts.get_tree_account_info(output_queue_index as usize)?; let base_leaf_index = get_output_queue_next_index(output_queue)?; let sdk_params = SdkCreateMintsParams { diff --git a/sdk-tests/manual-test-pinocchio/tests/account_loader.rs b/sdk-tests/manual-test-pinocchio/tests/account_loader.rs index 2c39539ced..cb7f21602a 100644 --- a/sdk-tests/manual-test-pinocchio/tests/account_loader.rs +++ b/sdk-tests/manual-test-pinocchio/tests/account_loader.rs @@ -16,9 +16,9 @@ use manual_test_pinocchio::{ account_loader::accounts::CreateZeroCopyParams, ZeroCopyRecord, ZeroCopyRecordSeeds, ZeroCopyRecordVariant, }; -use solana_sdk::instruction::{AccountMeta, Instruction}; use solana_keypair::Keypair; use solana_pubkey::Pubkey; +use solana_sdk::instruction::{AccountMeta, Instruction}; use solana_signer::Signer; /// Test the full lifecycle for zero-copy accounts: create -> compress -> decompress. diff --git a/sdk-tests/manual-test-pinocchio/tests/shared.rs b/sdk-tests/manual-test-pinocchio/tests/shared.rs index 2ca6d48c25..8709037b54 100644 --- a/sdk-tests/manual-test-pinocchio/tests/shared.rs +++ b/sdk-tests/manual-test-pinocchio/tests/shared.rs @@ -16,10 +16,8 @@ use solana_signer::Signer; /// Returns (rpc, payer, config_pda). pub async fn setup_test_env() -> (LightProgramTest, Keypair, Pubkey) { let program_id = Pubkey::new_from_array(manual_test_pinocchio::ID); - let mut config = ProgramTestConfig::new_v2( - true, - Some(vec![("manual_test_pinocchio", program_id)]), - ); + let mut config = + ProgramTestConfig::new_v2(true, Some(vec![("manual_test_pinocchio", program_id)])); config = config.with_light_protocol_events(); let mut rpc = LightProgramTest::new(config).await.unwrap(); diff --git a/sdk-tests/manual-test-pinocchio/tests/test.rs b/sdk-tests/manual-test-pinocchio/tests/test.rs index 0e2df7cad6..eddc47e290 100644 --- a/sdk-tests/manual-test-pinocchio/tests/test.rs +++ b/sdk-tests/manual-test-pinocchio/tests/test.rs @@ -14,9 +14,9 @@ use light_program_test::{program_test::TestRpc, Indexer, Rpc}; use manual_test_pinocchio::{ pda::accounts::CreatePdaParams, MinimalRecord, MinimalRecordSeeds, MinimalRecordVariant, }; -use solana_sdk::instruction::{AccountMeta, Instruction}; use solana_keypair::Keypair; use solana_pubkey::Pubkey; +use solana_sdk::instruction::{AccountMeta, Instruction}; use solana_signer::Signer; /// Test the full lifecycle: create -> compress -> decompress. @@ -153,11 +153,7 @@ async fn test_create_compress_decompress() { borsh::BorshDeserialize::deserialize(&mut &record_account.data[8..]) .expect("Failed to deserialize MinimalRecord"); - assert_eq!( - record.owner, - owner.to_bytes(), - "Record owner should match" - ); + assert_eq!(record.owner, owner.to_bytes(), "Record owner should match"); // state should be Decompressed after decompression assert_eq!( diff --git a/sdk-tests/manual-test/src/account_loader/derived_accounts.rs b/sdk-tests/manual-test/src/account_loader/derived_accounts.rs index dce41ea610..f3bdc41516 100644 --- a/sdk-tests/manual-test/src/account_loader/derived_accounts.rs +++ b/sdk-tests/manual-test/src/account_loader/derived_accounts.rs @@ -4,15 +4,14 @@ //! adapted for the AccountLoader (zero-copy) access pattern. use anchor_lang::prelude::*; -use light_compressed_account::instruction_data::{ - cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, -}; use light_account::{ light_account_checks::{self, packed_accounts::ProgramPackedAccounts}, - prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, - CpiContextWriteAccounts, InvokeLightSystemProgram, LightAccount, LightAccountVariantTrait, - LightFinalize, LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, - PackedLightAccountVariantTrait, + prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, CpiContextWriteAccounts, + InvokeLightSystemProgram, LightAccount, LightAccountVariantTrait, LightFinalize, LightPreInit, + LightSdkTypesError, PackedAddressTreeInfoExt, PackedLightAccountVariantTrait, +}; +use light_compressed_account::instruction_data::{ + cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, }; use super::{ @@ -138,8 +137,7 @@ impl<'info> LightPreInit, CreateZeroCopyParams> for CreateZer cpi_context: cpi_accounts.cpi_context()?, cpi_signer: crate::LIGHT_CPI_SIGNER, }; - instruction_data - .invoke_write_to_cpi_context_first(cpi_context_accounts)?; + instruction_data.invoke_write_to_cpi_context_first(cpi_context_accounts)?; } Ok(false) // No mints, so no CPI context write @@ -248,8 +246,7 @@ impl LightAccountVariantTrait<4> for ZeroCopyRecordVariant { impl PackedLightAccountVariantTrait<4> for PackedZeroCopyRecordVariant { type Unpacked = ZeroCopyRecordVariant; - const ACCOUNT_TYPE: light_account::AccountType = - ::ACCOUNT_TYPE; + const ACCOUNT_TYPE: light_account::AccountType = ::ACCOUNT_TYPE; fn bump(&self) -> u8 { self.seeds.bump @@ -317,8 +314,7 @@ impl light_account::IntoVariant for ZeroCopyRecordSeeds { fn into_variant( self, data: &[u8], - ) -> std::result::Result - { + ) -> std::result::Result { // For ZeroCopy (Pod) accounts, data is the full Pod bytes including compression_info. // We deserialize using AnchorDeserialize (which ZeroCopyRecord implements). let record: ZeroCopyRecord = AnchorDeserialize::deserialize(&mut &data[..]) @@ -343,9 +339,7 @@ impl light_account::IntoVariant for ZeroCopyRecordSeeds { /// Implement Pack trait to allow ZeroCopyRecordVariant to be used with `create_load_instructions`. /// Transforms the variant into PackedLightAccountVariant for efficient serialization. #[cfg(not(target_os = "solana"))] -impl light_account::Pack - for ZeroCopyRecordVariant -{ +impl light_account::Pack for ZeroCopyRecordVariant { type Packed = crate::derived_variants::PackedLightAccountVariant; fn pack( diff --git a/sdk-tests/manual-test/src/all/derived.rs b/sdk-tests/manual-test/src/all/derived.rs index 66a1ba24dd..97c6d7cf30 100644 --- a/sdk-tests/manual-test/src/all/derived.rs +++ b/sdk-tests/manual-test/src/all/derived.rs @@ -7,17 +7,16 @@ //! - 1 ATA via `CreateTokenAtaCpi` use anchor_lang::prelude::*; -use light_compressed_account::instruction_data::{ - cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, -}; use light_account::{ - prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, - CpiContextWriteAccounts, InvokeLightSystemProgram, LightAccount, LightFinalize, - LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, - invoke_create_mints, CreateMintsInfraAccounts, CreateMintsParams as SdkCreateMintsParams, - SingleMintParams, derive_mint_compressed_address, find_mint_address, + derive_associated_token_account, derive_mint_compressed_address, find_mint_address, + invoke_create_mints, prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, + CpiContextWriteAccounts, CreateMintsInfraAccounts, CreateMintsParams as SdkCreateMintsParams, + CreateTokenAccountCpi, CreateTokenAtaCpi, InvokeLightSystemProgram, LightAccount, + LightFinalize, LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, SingleMintParams, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, - CreateTokenAccountCpi, CreateTokenAtaCpi, derive_associated_token_account, +}; +use light_compressed_account::instruction_data::{ + cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, }; use solana_account_info::AccountInfo; @@ -146,8 +145,7 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou cpi_context: cpi_accounts.cpi_context()?, cpi_signer: crate::LIGHT_CPI_SIGNER, }; - instruction_data - .invoke_write_to_cpi_context_first(cpi_context_accounts)?; + instruction_data.invoke_write_to_cpi_context_first(cpi_context_accounts)?; } // ==================================================================== @@ -271,10 +269,8 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou // 7. Create ATA via CreateTokenAtaCpi // ==================================================================== { - let (_, ata_bump) = derive_associated_token_account( - self.ata_owner.key, - self.mint.key, - ); + let (_, ata_bump) = + derive_associated_token_account(self.ata_owner.key, self.mint.key); let payer_info = self.payer.to_account_info(); let mint_info = self.mint.to_account_info(); diff --git a/sdk-tests/manual-test/src/all/derived_accounts.rs b/sdk-tests/manual-test/src/all/derived_accounts.rs index 2b5a30ba85..704300fb07 100644 --- a/sdk-tests/manual-test/src/all/derived_accounts.rs +++ b/sdk-tests/manual-test/src/all/derived_accounts.rs @@ -87,8 +87,7 @@ impl LightAccountVariantTrait<3> for AllBorshVariant { impl PackedLightAccountVariantTrait<3> for PackedAllBorshVariant { type Unpacked = AllBorshVariant; - const ACCOUNT_TYPE: light_account::AccountType = - ::ACCOUNT_TYPE; + const ACCOUNT_TYPE: light_account::AccountType = ::ACCOUNT_TYPE; fn bump(&self) -> u8 { self.seeds.bump @@ -218,8 +217,7 @@ impl LightAccountVariantTrait<3> for AllZeroCopyVariant { impl PackedLightAccountVariantTrait<3> for PackedAllZeroCopyVariant { type Unpacked = AllZeroCopyVariant; - const ACCOUNT_TYPE: light_account::AccountType = - ::ACCOUNT_TYPE; + const ACCOUNT_TYPE: light_account::AccountType = ::ACCOUNT_TYPE; fn bump(&self) -> u8 { self.seeds.bump diff --git a/sdk-tests/manual-test/src/ata/derived.rs b/sdk-tests/manual-test/src/ata/derived.rs index abc48515ae..62b0183e44 100644 --- a/sdk-tests/manual-test/src/ata/derived.rs +++ b/sdk-tests/manual-test/src/ata/derived.rs @@ -2,8 +2,8 @@ use anchor_lang::prelude::*; use light_account::{ - CreateTokenAtaCpi, LightFinalize, LightPreInit, LightSdkTypesError, - derive_associated_token_account, + derive_associated_token_account, CreateTokenAtaCpi, LightFinalize, LightPreInit, + LightSdkTypesError, }; use solana_account_info::AccountInfo; @@ -21,10 +21,7 @@ impl<'info> LightPreInit, CreateAtaParams> for CreateAtaAccou ) -> std::result::Result { let inner = || -> std::result::Result { // Derive the ATA bump on-chain - let (_, bump) = derive_associated_token_account( - self.ata_owner.key, - self.mint.key, - ); + let (_, bump) = derive_associated_token_account(self.ata_owner.key, self.mint.key); // Create ATA via CPI with idempotent + rent-free mode // NOTE: Unlike token vaults, ATAs use .invoke() not .invoke_signed() diff --git a/sdk-tests/manual-test/src/derived_compress.rs b/sdk-tests/manual-test/src/derived_compress.rs index 495adfa480..07b7fbba42 100644 --- a/sdk-tests/manual-test/src/derived_compress.rs +++ b/sdk-tests/manual-test/src/derived_compress.rs @@ -7,9 +7,8 @@ use std::marker::PhantomData; use anchor_lang::prelude::*; use light_account::{ - account_meta::CompressedAccountMetaNoLamportsNoAddress, - prepare_account_for_compression, process_compress_pda_accounts_idempotent, CompressCtx, - LightDiscriminator, LightSdkTypesError, + account_meta::CompressedAccountMetaNoLamportsNoAddress, prepare_account_for_compression, + process_compress_pda_accounts_idempotent, CompressCtx, LightDiscriminator, LightSdkTypesError, }; use solana_account_info::AccountInfo; diff --git a/sdk-tests/manual-test/src/derived_light_config.rs b/sdk-tests/manual-test/src/derived_light_config.rs index 3181865faa..49157a5037 100644 --- a/sdk-tests/manual-test/src/derived_light_config.rs +++ b/sdk-tests/manual-test/src/derived_light_config.rs @@ -1,8 +1,8 @@ //! Config instructions using SDK functions. use anchor_lang::prelude::*; -use light_compressible::rent::RentConfig; use light_account::process_initialize_light_config; +use light_compressible::rent::RentConfig; use solana_program_error::ProgramError; /// Params order matches SDK's InitializeCompressionConfigAnchorData. diff --git a/sdk-tests/manual-test/src/pda/derived_accounts.rs b/sdk-tests/manual-test/src/pda/derived_accounts.rs index 1049c8a74c..d60736fea6 100644 --- a/sdk-tests/manual-test/src/pda/derived_accounts.rs +++ b/sdk-tests/manual-test/src/pda/derived_accounts.rs @@ -1,13 +1,12 @@ use anchor_lang::prelude::*; -use light_compressed_account::instruction_data::{ - cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, -}; use light_account::{ light_account_checks::{self, packed_accounts::ProgramPackedAccounts}, - prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, - CpiContextWriteAccounts, InvokeLightSystemProgram, LightAccount, LightAccountVariantTrait, - LightFinalize, LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, - PackedLightAccountVariantTrait, + prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, CpiContextWriteAccounts, + InvokeLightSystemProgram, LightAccount, LightAccountVariantTrait, LightFinalize, LightPreInit, + LightSdkTypesError, PackedAddressTreeInfoExt, PackedLightAccountVariantTrait, +}; +use light_compressed_account::instruction_data::{ + cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, }; use super::{ @@ -125,8 +124,7 @@ impl<'info> LightPreInit, CreatePdaParams> for CreatePda<'inf }; if !WITH_CPI_CONTEXT { // 5. Invoke Light System Program CPI - instruction_data - .invoke(cpi_accounts)?; + instruction_data.invoke(cpi_accounts)?; } else { // For flows that combine light mints with light PDAs, write to CPI context first. // The authority and cpi_context accounts must be provided in remaining_accounts. @@ -136,8 +134,7 @@ impl<'info> LightPreInit, CreatePdaParams> for CreatePda<'inf cpi_context: cpi_accounts.cpi_context()?, cpi_signer: crate::LIGHT_CPI_SIGNER, }; - instruction_data - .invoke_write_to_cpi_context_first(cpi_context_accounts)?; + instruction_data.invoke_write_to_cpi_context_first(cpi_context_accounts)?; } } // ===================================================================== @@ -245,8 +242,7 @@ impl LightAccountVariantTrait<4> for MinimalRecordVariant { impl PackedLightAccountVariantTrait<4> for PackedMinimalRecordVariant { type Unpacked = MinimalRecordVariant; - const ACCOUNT_TYPE: light_account::AccountType = - ::ACCOUNT_TYPE; + const ACCOUNT_TYPE: light_account::AccountType = ::ACCOUNT_TYPE; fn bump(&self) -> u8 { self.seeds.bump diff --git a/sdk-tests/manual-test/src/token_account/derived.rs b/sdk-tests/manual-test/src/token_account/derived.rs index ae7d008e1f..7f8804c0f4 100644 --- a/sdk-tests/manual-test/src/token_account/derived.rs +++ b/sdk-tests/manual-test/src/token_account/derived.rs @@ -3,7 +3,9 @@ use anchor_lang::prelude::*; #[cfg(not(target_os = "solana"))] use light_account::Pack; -use light_account::{CreateTokenAccountCpi, LightFinalize, LightPreInit, LightSdkTypesError, Unpack}; +use light_account::{ + CreateTokenAccountCpi, LightFinalize, LightPreInit, LightSdkTypesError, Unpack, +}; use solana_account_info::AccountInfo; use super::accounts::{CreateTokenVaultAccounts, CreateTokenVaultParams, TOKEN_VAULT_SEED}; diff --git a/sdk-tests/manual-test/src/two_mints/derived.rs b/sdk-tests/manual-test/src/two_mints/derived.rs index 30e0de44c4..a2cecdaf0a 100644 --- a/sdk-tests/manual-test/src/two_mints/derived.rs +++ b/sdk-tests/manual-test/src/two_mints/derived.rs @@ -3,11 +3,10 @@ use anchor_lang::prelude::*; use light_account::{ - invoke_create_mints, get_output_queue_next_index, CreateMintsInfraAccounts, - CreateMintsParams as SdkCreateMintsParams, SingleMintParams, - derive_mint_compressed_address, find_mint_address, - CpiAccounts, CpiAccountsConfig, LightFinalize, LightPreInit, LightSdkTypesError, - PackedAddressTreeInfoExt, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, + derive_mint_compressed_address, find_mint_address, get_output_queue_next_index, + invoke_create_mints, CpiAccounts, CpiAccountsConfig, CreateMintsInfraAccounts, + CreateMintsParams as SdkCreateMintsParams, LightFinalize, LightPreInit, LightSdkTypesError, + PackedAddressTreeInfoExt, SingleMintParams, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, }; use solana_account_info::AccountInfo; @@ -28,7 +27,6 @@ impl<'info> LightPreInit, CreateDerivedMintsParams> params: &CreateDerivedMintsParams, ) -> std::result::Result { let inner = || -> std::result::Result { - // ==================================================================== // STATIC BOILERPLATE (same across all LightPreInit implementations) // ==================================================================== @@ -69,10 +67,8 @@ impl<'info> LightPreInit, CreateDerivedMintsParams> let mint_signer_1 = self.mint_signer_1.key(); // Derive mint PDAs (light-token derives mint PDA from mint_signer) - let (mint_0_pda, mint_0_bump) = - find_mint_address(&mint_signer_0.to_bytes()); - let (mint_1_pda, mint_1_bump) = - find_mint_address(&mint_signer_1.to_bytes()); + let (mint_0_pda, mint_0_bump) = find_mint_address(&mint_signer_0.to_bytes()); + let (mint_1_pda, mint_1_bump) = find_mint_address(&mint_signer_1.to_bytes()); // Derive compression addresses (from mint_signer + address_tree) let compression_address_0 = derive_mint_compressed_address( @@ -144,8 +140,8 @@ impl<'info> LightPreInit, CreateDerivedMintsParams> // Read base_leaf_index from output queue (required for N > 1) let output_queue_index = params.create_accounts_proof.output_state_tree_index; - let output_queue = cpi_accounts - .get_tree_account_info(output_queue_index as usize)?; + let output_queue = + cpi_accounts.get_tree_account_info(output_queue_index as usize)?; let base_leaf_index = get_output_queue_next_index(output_queue)?; let sdk_params = SdkCreateMintsParams { diff --git a/sdk-tests/manual-test/tests/account_loader.rs b/sdk-tests/manual-test/tests/account_loader.rs index e96cf6ddcf..e0c68f9745 100644 --- a/sdk-tests/manual-test/tests/account_loader.rs +++ b/sdk-tests/manual-test/tests/account_loader.rs @@ -6,13 +6,13 @@ mod shared; use anchor_lang::{Discriminator, InstructionData, ToAccountMetas}; +use light_account::IntoVariant; use light_client::interface::{ create_load_instructions, get_create_accounts_proof, AccountInterfaceExt, AccountSpec, CreateAccountsProofInput, PdaSpec, }; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Indexer, Rpc}; -use light_account::IntoVariant; use manual_test::{ CreateZeroCopyParams, ZeroCopyRecord, ZeroCopyRecordSeeds, ZeroCopyRecordVariant, }; diff --git a/sdk-tests/manual-test/tests/shared.rs b/sdk-tests/manual-test/tests/shared.rs index b029279189..73930a5892 100644 --- a/sdk-tests/manual-test/tests/shared.rs +++ b/sdk-tests/manual-test/tests/shared.rs @@ -1,6 +1,7 @@ //! Shared test helpers for manual-test integration tests. use anchor_lang::InstructionData; +use light_account::derive_rent_sponsor_pda; use light_client::interface::{ get_create_accounts_proof, CreateAccountsProofInput, InitializeRentFreeConfig, }; @@ -8,7 +9,6 @@ use light_program_test::{ program_test::{setup_mock_program_data, LightProgramTest}, ProgramTestConfig, Rpc, }; -use light_account::derive_rent_sponsor_pda; use solana_keypair::Keypair; use solana_pubkey::Pubkey; use solana_signer::Signer; diff --git a/sdk-tests/manual-test/tests/test.rs b/sdk-tests/manual-test/tests/test.rs index 07e3aa811a..8ddab7283c 100644 --- a/sdk-tests/manual-test/tests/test.rs +++ b/sdk-tests/manual-test/tests/test.rs @@ -5,13 +5,13 @@ mod shared; use anchor_lang::{InstructionData, ToAccountMetas}; +use light_account::IntoVariant; use light_client::interface::{ create_load_instructions, get_create_accounts_proof, AccountInterfaceExt, AccountSpec, CreateAccountsProofInput, PdaSpec, }; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Indexer, Rpc}; -use light_account::IntoVariant; use manual_test::{ pda::{MinimalRecord, MinimalRecordSeeds, MinimalRecordVariant}, CreatePdaParams, diff --git a/sdk-tests/pinocchio-derive-test/Cargo.toml b/sdk-tests/pinocchio-derive-test/Cargo.toml index d91610c5e4..1a4b27f6f4 100644 --- a/sdk-tests/pinocchio-derive-test/Cargo.toml +++ b/sdk-tests/pinocchio-derive-test/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "single-pda-derive-test" +name = "pinocchio-derive-test" version = "0.1.0" description = "Test for #[derive(LightProgram)] macro validation with all variant kinds" edition = "2021" [lib] crate-type = ["cdylib", "lib"] -name = "single_pda_derive_test" +name = "pinocchio_derive_test" [features] no-entrypoint = [] @@ -19,9 +19,8 @@ idl-build = ["anchor-lang/idl-build", "light-anchor-spl/idl-build"] test-sbf = [] [dependencies] -light-account = { workspace = true } -# light-sdk = { workspace = true, features = ["anchor", "v2", "anchor-discriminator", "cpi-context"] } -light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] } +light-account = { workspace = true, features = ["token", "anchor"] } +light-sdk-types = { workspace = true, features = ["anchor", "v2", "cpi-context"] } light-macros = { workspace = true, features = ["solana"] } light-sdk-macros = { workspace = true } bytemuck = { workspace = true, features = ["derive"] } @@ -31,7 +30,6 @@ anchor-lang = { workspace = true } light-anchor-spl = { workspace = true, features = ["metadata"] } light-compressible = { workspace = true, features = ["anchor"] } light-hasher = { workspace = true, features = ["solana"] } -light-token = { workspace = true, features = ["anchor"] } light-token-types = { workspace = true, features = ["anchor"] } solana-program = { workspace = true } solana-pubkey = { workspace = true } diff --git a/sdk-tests/pinocchio-derive-test/src/instruction_accounts.rs b/sdk-tests/pinocchio-derive-test/src/instruction_accounts.rs index 86b4834e8a..cd8b2a90d3 100644 --- a/sdk-tests/pinocchio-derive-test/src/instruction_accounts.rs +++ b/sdk-tests/pinocchio-derive-test/src/instruction_accounts.rs @@ -2,12 +2,13 @@ use anchor_lang::prelude::*; use light_sdk_macros::LightAccounts; -use light_sdk_types::interface::CreateAccountsProof; -use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; -use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_account::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_sdk_types::{interface::CreateAccountsProof, LIGHT_TOKEN_PROGRAM_ID}; -use crate::state::{MinimalRecord, ZeroCopyRecord}; -use crate::{MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, RECORD_SEED, VAULT_AUTH_SEED, VAULT_SEED}; +use crate::{ + state::{MinimalRecord, ZeroCopyRecord}, + MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, RECORD_SEED, VAULT_AUTH_SEED, VAULT_SEED, +}; // ============================================================================= // 1. CreatePda diff --git a/sdk-tests/pinocchio-derive-test/src/lib.rs b/sdk-tests/pinocchio-derive-test/src/lib.rs index 8e40445151..64071f49fb 100644 --- a/sdk-tests/pinocchio-derive-test/src/lib.rs +++ b/sdk-tests/pinocchio-derive-test/src/lib.rs @@ -8,7 +8,7 @@ use std::marker::PhantomData; use anchor_lang::prelude::*; -use light_sdk::derive_light_cpi_signer; +use light_account::derive_light_cpi_signer; use light_sdk_macros::LightProgram; use light_sdk_types::CpiSigner; @@ -45,7 +45,9 @@ pub enum ProgramAccounts { } #[program] -pub mod single_pda_derive_test { +pub mod pinocchio_derive_test { + use light_account::{light_err, LightFinalize, LightPreInit}; + use super::*; // ========================================================================= @@ -56,36 +58,48 @@ pub mod single_pda_derive_test { ctx: Context<'_, '_, '_, 'info, EmptyAccounts<'info>>, instruction_data: Vec, ) -> Result<()> { - light_sdk::interface::program::compression::processor::process_compress_pda_accounts_idempotent( + light_account::process_compress_pda_accounts_idempotent( ctx.remaining_accounts, &instruction_data, ProgramAccounts::compress_dispatch, LIGHT_CPI_SIGNER, &LIGHT_CPI_SIGNER.program_id, - ).map_err(|e| ProgramError::Custom(u64::from(e) as u32)) + ) + .map_err(Into::into) } pub fn decompress_accounts_idempotent<'info>( ctx: Context<'_, '_, '_, 'info, EmptyAccounts<'info>>, instruction_data: Vec, ) -> Result<()> { + use solana_program::{clock::Clock, sysvar::Sysvar}; + let current_slot = Clock::get()?.slot; ProgramAccounts::decompress_dispatch( ctx.remaining_accounts, &instruction_data, LIGHT_CPI_SIGNER, - &crate::ID, + &LIGHT_CPI_SIGNER.program_id, + current_slot, ) .map_err(Into::into) } pub fn initialize_compression_config<'info>( ctx: Context<'_, '_, '_, 'info, EmptyAccounts<'info>>, - instruction_data: Vec, + params: InitConfigParams, ) -> Result<()> { - light_sdk::interface::program::config::process_initialize_light_config_checked( - ctx.remaining_accounts, - &instruction_data, - &crate::ID, + light_account::process_initialize_light_config( + &ctx.remaining_accounts[1], // config + &ctx.remaining_accounts[3], // authority + ¶ms.rent_sponsor.to_bytes(), + ¶ms.compression_authority.to_bytes(), + params.rent_config, + params.write_top_up, + params.address_space.iter().map(|p| p.to_bytes()).collect(), + 0, // config_bump + &ctx.remaining_accounts[0], // payer + &ctx.remaining_accounts[4], // system_program + &LIGHT_CPI_SIGNER.program_id, ) .map_err(Into::into) } @@ -94,13 +108,125 @@ pub mod single_pda_derive_test { ctx: Context<'_, '_, '_, 'info, EmptyAccounts<'info>>, instruction_data: Vec, ) -> Result<()> { - light_sdk::interface::program::config::process_update_light_config( + light_account::process_update_light_config( ctx.remaining_accounts, &instruction_data, - &crate::ID, + &LIGHT_CPI_SIGNER.program_id, ) .map_err(Into::into) } + + // ========================================================================= + // Instruction handlers (manually written for #[derive(LightProgram)]) + // ========================================================================= + + pub fn create_pda<'info>( + ctx: Context<'_, '_, '_, 'info, CreatePda<'info>>, + params: CreatePdaParams, + ) -> Result<()> { + let has_pre_init = ctx + .accounts + .light_pre_init(ctx.remaining_accounts, ¶ms) + .map_err(light_err)?; + ctx.accounts.record.owner = params.owner; + ctx.accounts + .light_finalize(ctx.remaining_accounts, ¶ms, has_pre_init) + .map_err(light_err)?; + Ok(()) + } + + pub fn create_ata<'info>( + ctx: Context<'_, '_, '_, 'info, CreateAta<'info>>, + params: CreateAtaParams, + ) -> Result<()> { + let has_pre_init = ctx + .accounts + .light_pre_init(ctx.remaining_accounts, ¶ms) + .map_err(light_err)?; + ctx.accounts + .light_finalize(ctx.remaining_accounts, ¶ms, has_pre_init) + .map_err(light_err)?; + Ok(()) + } + + pub fn create_token_vault<'info>( + ctx: Context<'_, '_, '_, 'info, CreateTokenVault<'info>>, + params: CreateTokenVaultParams, + ) -> Result<()> { + let has_pre_init = ctx + .accounts + .light_pre_init(ctx.remaining_accounts, ¶ms) + .map_err(light_err)?; + ctx.accounts + .light_finalize(ctx.remaining_accounts, ¶ms, has_pre_init) + .map_err(light_err)?; + Ok(()) + } + + pub fn create_zero_copy_record<'info>( + ctx: Context<'_, '_, '_, 'info, CreateZeroCopyRecord<'info>>, + params: CreateZeroCopyRecordParams, + ) -> Result<()> { + let has_pre_init = ctx + .accounts + .light_pre_init(ctx.remaining_accounts, ¶ms) + .map_err(light_err)?; + { + let mut record = ctx.accounts.record.load_init()?; + record.owner = params.owner; + } + ctx.accounts + .light_finalize(ctx.remaining_accounts, ¶ms, has_pre_init) + .map_err(light_err)?; + Ok(()) + } + + pub fn create_mint<'info>( + ctx: Context<'_, '_, '_, 'info, CreateMint<'info>>, + params: CreateMintParams, + ) -> Result<()> { + let has_pre_init = ctx + .accounts + .light_pre_init(ctx.remaining_accounts, ¶ms) + .map_err(light_err)?; + ctx.accounts + .light_finalize(ctx.remaining_accounts, ¶ms, has_pre_init) + .map_err(light_err)?; + Ok(()) + } + + pub fn create_two_mints<'info>( + ctx: Context<'_, '_, '_, 'info, CreateTwoMints<'info>>, + params: CreateTwoMintsParams, + ) -> Result<()> { + let has_pre_init = ctx + .accounts + .light_pre_init(ctx.remaining_accounts, ¶ms) + .map_err(light_err)?; + ctx.accounts + .light_finalize(ctx.remaining_accounts, ¶ms, has_pre_init) + .map_err(light_err)?; + Ok(()) + } + + pub fn create_all<'info>( + ctx: Context<'_, '_, '_, 'info, CreateAll<'info>>, + params: CreateAllParams, + ) -> Result<()> { + let has_pre_init = ctx + .accounts + .light_pre_init(ctx.remaining_accounts, ¶ms) + .map_err(light_err)?; + ctx.accounts.record.owner = params.owner; + { + let mut record = ctx.accounts.zero_copy_record.load_init()?; + record.owner = params.owner; + } + ctx.accounts + .light_finalize(ctx.remaining_accounts, ¶ms, has_pre_init) + .map_err(light_err)?; + Ok(()) + } } /// Accounts struct for compress instruction. diff --git a/sdk-tests/pinocchio-derive-test/src/state.rs b/sdk-tests/pinocchio-derive-test/src/state.rs index cc61250868..504c25dfe9 100644 --- a/sdk-tests/pinocchio-derive-test/src/state.rs +++ b/sdk-tests/pinocchio-derive-test/src/state.rs @@ -1,7 +1,7 @@ //! State module for single-pda-derive-test. use anchor_lang::prelude::*; -use light_sdk::{interface::CompressionInfo, LightDiscriminator}; +use light_account::{CompressionInfo, LightDiscriminator}; use light_sdk_macros::LightAccount; /// Minimal record struct for testing PDA creation. diff --git a/sdk-tests/pinocchio-derive-test/tests/shared/mod.rs b/sdk-tests/pinocchio-derive-test/tests/shared/mod.rs new file mode 100644 index 0000000000..cb19399778 --- /dev/null +++ b/sdk-tests/pinocchio-derive-test/tests/shared/mod.rs @@ -0,0 +1,118 @@ +use light_account::derive_rent_sponsor_pda; +use light_client::interface::InitializeRentFreeConfig; +use light_program_test::{ + program_test::{setup_mock_program_data, LightProgramTest}, + Indexer, ProgramTestConfig, Rpc, +}; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +/// Shared test environment with initialized compression config. +pub struct TestEnv { + pub rpc: LightProgramTest, + pub payer: Keypair, + pub program_id: Pubkey, + pub config_pda: Pubkey, + pub rent_sponsor: Pubkey, +} + +/// Sets up a test environment with program, config, and rent sponsor initialized. +pub async fn setup_test_env() -> TestEnv { + let program_id = pinocchio_derive_test::ID; + let mut config = + ProgramTestConfig::new_v2(true, Some(vec![("pinocchio_derive_test", program_id)])); + config = config.with_light_protocol_events(); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); + + let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id); + + let (init_config_ix, config_pda) = InitializeRentFreeConfig::new( + &program_id, + &payer.pubkey(), + &program_data_pda, + rent_sponsor, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .expect("Initialize config should succeed"); + + TestEnv { + rpc, + payer, + program_id, + config_pda, + rent_sponsor, + } +} + +/// Creates a compressed mint using the ctoken SDK. +/// Returns (mint_pda, mint_seed_keypair). +pub async fn setup_create_mint( + rpc: &mut (impl Rpc + Indexer), + payer: &Keypair, + mint_authority: Pubkey, + decimals: u8, +) -> (Pubkey, Keypair) { + use light_token::instruction::{CreateMint, CreateMintParams}; + + let mint_seed = Keypair::new(); + let address_tree = rpc.get_address_tree_v2(); + let output_queue = rpc.get_random_state_tree_info().unwrap().queue; + + let compression_address = light_token::instruction::derive_mint_compressed_address( + &mint_seed.pubkey(), + &address_tree.tree, + ); + + let (mint, bump) = light_token::instruction::find_mint_address(&mint_seed.pubkey()); + + let rpc_result = rpc + .get_validity_proof( + vec![], + vec![light_client::indexer::AddressWithTree { + address: compression_address, + tree: address_tree.tree, + }], + None, + ) + .await + .unwrap() + .value; + + let params = CreateMintParams { + decimals, + address_merkle_tree_root_index: rpc_result.addresses[0].root_index, + mint_authority, + proof: rpc_result.proof.0.unwrap(), + compression_address, + mint, + bump, + freeze_authority: None, + extensions: None, + rent_payment: 16, + write_top_up: 766, + }; + + let create_mint_builder = CreateMint::new( + params, + mint_seed.pubkey(), + payer.pubkey(), + address_tree.tree, + output_queue, + ); + let instruction = create_mint_builder.instruction().unwrap(); + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[payer, &mint_seed]) + .await + .unwrap(); + + (mint, mint_seed) +} diff --git a/sdk-tests/pinocchio-derive-test/tests/test.rs b/sdk-tests/pinocchio-derive-test/tests/test.rs deleted file mode 100644 index ffbd5f197c..0000000000 --- a/sdk-tests/pinocchio-derive-test/tests/test.rs +++ /dev/null @@ -1,943 +0,0 @@ -//! Integration tests for #[derive(LightProgram)] with all variant kinds. - -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_client::interface::{ - get_create_accounts_proof, CreateAccountsProofInput, InitializeRentFreeConfig, -}; -use light_program_test::{ - program_test::{setup_mock_program_data, LightProgramTest}, - Indexer, ProgramTestConfig, Rpc, -}; -use light_sdk::utils::derive_rent_sponsor_pda; -use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; -use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; -use solana_instruction::Instruction; -use solana_keypair::Keypair; -use solana_pubkey::Pubkey; -use solana_signer::Signer; - -/// Setup helper: Creates a compressed mint using the ctoken SDK. -/// Returns (mint_pda, mint_seed_keypair) -async fn setup_create_mint( - rpc: &mut (impl Rpc + Indexer), - payer: &Keypair, - mint_authority: Pubkey, - decimals: u8, -) -> (Pubkey, Keypair) { - use light_token::instruction::{CreateMint, CreateMintParams}; - - let mint_seed = Keypair::new(); - let address_tree = rpc.get_address_tree_v2(); - let output_queue = rpc.get_random_state_tree_info().unwrap().queue; - - let compression_address = light_token::instruction::derive_mint_compressed_address( - &mint_seed.pubkey(), - &address_tree.tree, - ); - - let (mint, bump) = light_token::instruction::find_mint_address(&mint_seed.pubkey()); - - let rpc_result = rpc - .get_validity_proof( - vec![], - vec![light_client::indexer::AddressWithTree { - address: compression_address, - tree: address_tree.tree, - }], - None, - ) - .await - .unwrap() - .value; - - let params = CreateMintParams { - decimals, - address_merkle_tree_root_index: rpc_result.addresses[0].root_index, - mint_authority, - proof: rpc_result.proof.0.unwrap(), - compression_address, - mint, - bump, - freeze_authority: None, - extensions: None, - rent_payment: 16, - write_top_up: 766, - }; - - let create_mint_builder = CreateMint::new( - params, - mint_seed.pubkey(), - payer.pubkey(), - address_tree.tree, - output_queue, - ); - let instruction = create_mint_builder.instruction().unwrap(); - - rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[payer, &mint_seed]) - .await - .unwrap(); - - (mint, mint_seed) -} - -// ============================================================================= -// 1. Create PDA -// ============================================================================= - -#[tokio::test] -async fn test_create_single_pda_derive() { - use single_pda_derive_test::CreatePdaParams; - - let program_id = single_pda_derive_test::ID; - let mut config = - ProgramTestConfig::new_v2(true, Some(vec![("single_pda_derive_test", program_id)])); - config = config.with_light_protocol_events(); - - let mut rpc = LightProgramTest::new(config).await.unwrap(); - let payer = rpc.get_payer().insecure_clone(); - - let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); - - let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id); - - let (init_config_ix, config_pda) = InitializeRentFreeConfig::new( - &program_id, - &payer.pubkey(), - &program_data_pda, - rent_sponsor, - payer.pubkey(), - ) - .build(); - - rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) - .await - .expect("Initialize config should succeed"); - - let owner = Keypair::new().pubkey(); - - let (record_pda, _) = - Pubkey::find_program_address(&[b"minimal_record", owner.as_ref()], &program_id); - - let proof_result = get_create_accounts_proof( - &rpc, - &program_id, - vec![CreateAccountsProofInput::pda(record_pda)], - ) - .await - .unwrap(); - - let accounts = single_pda_derive_test::accounts::CreatePda { - fee_payer: payer.pubkey(), - compression_config: config_pda, - pda_rent_sponsor: rent_sponsor, - record: record_pda, - system_program: solana_sdk::system_program::ID, - }; - - let instruction_data = single_pda_derive_test::instruction::CreatePda { - params: CreatePdaParams { - create_accounts_proof: proof_result.create_accounts_proof, - owner, - }, - }; - - let instruction = Instruction { - program_id, - accounts: [ - accounts.to_account_metas(None), - proof_result.remaining_accounts, - ] - .concat(), - data: instruction_data.data(), - }; - - rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) - .await - .expect("CreatePda should succeed"); - - let record_account = rpc - .get_account(record_pda) - .await - .unwrap() - .expect("Record PDA should exist on-chain"); - - use single_pda_derive_test::MinimalRecord; - let record: MinimalRecord = - borsh::BorshDeserialize::deserialize(&mut &record_account.data[8..]) - .expect("Failed to deserialize MinimalRecord"); - - assert_eq!(record.owner, owner, "Record owner should match"); - assert!( - !record.compression_info.is_compressed(), - "Record should be in decompressed state" - ); -} - -// ============================================================================= -// 2. Create ATA -// ============================================================================= - -#[tokio::test] -async fn test_create_ata_derive() { - use single_pda_derive_test::CreateAtaParams; - - let program_id = single_pda_derive_test::ID; - let mut config = - ProgramTestConfig::new_v2(true, Some(vec![("single_pda_derive_test", program_id)])); - config = config.with_light_protocol_events(); - - let mut rpc = LightProgramTest::new(config).await.unwrap(); - let payer = rpc.get_payer().insecure_clone(); - - let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); - - let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id); - - let (init_config_ix, _config_pda) = InitializeRentFreeConfig::new( - &program_id, - &payer.pubkey(), - &program_data_pda, - rent_sponsor, - payer.pubkey(), - ) - .build(); - - rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) - .await - .expect("Initialize config should succeed"); - - // Setup mint first - let (mint, _mint_seed) = setup_create_mint( - &mut rpc, - &payer, - payer.pubkey(), - 9, - ) - .await; - - let ata_owner = payer.pubkey(); - let (ata, ata_bump) = light_token::instruction::derive_token_ata(&ata_owner, &mint); - - // No PDA accounts for ATA-only instruction - let proof_result = get_create_accounts_proof(&rpc, &program_id, vec![]) - .await - .unwrap(); - - let accounts = single_pda_derive_test::accounts::CreateAta { - fee_payer: payer.pubkey(), - ata_mint: mint, - ata_owner, - ata, - light_token_config: LIGHT_TOKEN_CONFIG, - light_token_rent_sponsor: LIGHT_TOKEN_RENT_SPONSOR, - light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), - system_program: solana_sdk::system_program::ID, - }; - - let instruction_data = single_pda_derive_test::instruction::CreateAta { - params: CreateAtaParams { - create_accounts_proof: proof_result.create_accounts_proof, - ata_bump, - }, - }; - - let instruction = Instruction { - program_id, - accounts: [ - accounts.to_account_metas(None), - proof_result.remaining_accounts, - ] - .concat(), - data: instruction_data.data(), - }; - - rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) - .await - .expect("CreateAta should succeed"); - - let ata_account = rpc - .get_account(ata) - .await - .unwrap() - .expect("ATA should exist on-chain"); - - use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; - let token: Token = borsh::BorshDeserialize::deserialize(&mut &ata_account.data[..]) - .expect("Failed to deserialize Token"); - - let expected_token = Token { - mint: mint.to_bytes().into(), - owner: ata_owner.to_bytes().into(), - amount: 0, - delegate: None, - state: AccountState::Initialized, - is_native: None, - delegated_amount: 0, - close_authority: None, - account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT, - extensions: token.extensions.clone(), - }; - - assert_eq!(token, expected_token, "ATA should match expected after creation"); -} - -// ============================================================================= -// 3. Create Token Vault -// ============================================================================= - -#[tokio::test] -async fn test_create_token_vault_derive() { - use single_pda_derive_test::{CreateTokenVaultParams, VAULT_AUTH_SEED, VAULT_SEED}; - - let program_id = single_pda_derive_test::ID; - let mut config = - ProgramTestConfig::new_v2(true, Some(vec![("single_pda_derive_test", program_id)])); - config = config.with_light_protocol_events(); - - let mut rpc = LightProgramTest::new(config).await.unwrap(); - let payer = rpc.get_payer().insecure_clone(); - - let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); - - let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id); - - let (init_config_ix, _config_pda) = InitializeRentFreeConfig::new( - &program_id, - &payer.pubkey(), - &program_data_pda, - rent_sponsor, - payer.pubkey(), - ) - .build(); - - rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) - .await - .expect("Initialize config should succeed"); - - // Setup mint first - let (mint, _mint_seed) = setup_create_mint( - &mut rpc, - &payer, - payer.pubkey(), - 9, - ) - .await; - - let (vault_authority, _auth_bump) = - Pubkey::find_program_address(&[VAULT_AUTH_SEED], &program_id); - let (vault, vault_bump) = - Pubkey::find_program_address(&[VAULT_SEED, mint.as_ref()], &program_id); - - // No PDA accounts for token-only instruction - let proof_result = get_create_accounts_proof(&rpc, &program_id, vec![]) - .await - .unwrap(); - - let accounts = single_pda_derive_test::accounts::CreateTokenVault { - fee_payer: payer.pubkey(), - mint, - vault_authority, - vault, - light_token_config: LIGHT_TOKEN_CONFIG, - light_token_rent_sponsor: LIGHT_TOKEN_RENT_SPONSOR, - light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), - light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), - system_program: solana_sdk::system_program::ID, - }; - - let instruction_data = single_pda_derive_test::instruction::CreateTokenVault { - params: CreateTokenVaultParams { - create_accounts_proof: proof_result.create_accounts_proof, - vault_bump, - }, - }; - - let instruction = Instruction { - program_id, - accounts: [ - accounts.to_account_metas(None), - proof_result.remaining_accounts, - ] - .concat(), - data: instruction_data.data(), - }; - - rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) - .await - .expect("CreateTokenVault should succeed"); - - let vault_account = rpc - .get_account(vault) - .await - .unwrap() - .expect("Token vault should exist on-chain"); - - use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; - let token: Token = borsh::BorshDeserialize::deserialize(&mut &vault_account.data[..]) - .expect("Failed to deserialize Token"); - - let expected_token = Token { - mint: mint.to_bytes().into(), - owner: vault_authority.to_bytes().into(), - amount: 0, - delegate: None, - state: AccountState::Initialized, - is_native: None, - delegated_amount: 0, - close_authority: None, - account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT, - extensions: token.extensions.clone(), - }; - - assert_eq!( - token, expected_token, - "Token vault should match expected after creation" - ); -} - -// ============================================================================= -// 4. Create Zero-Copy Record -// ============================================================================= - -#[tokio::test] -async fn test_create_zero_copy_record_derive() { - use single_pda_derive_test::{CreateZeroCopyRecordParams, RECORD_SEED}; - - let program_id = single_pda_derive_test::ID; - let mut config = - ProgramTestConfig::new_v2(true, Some(vec![("single_pda_derive_test", program_id)])); - config = config.with_light_protocol_events(); - - let mut rpc = LightProgramTest::new(config).await.unwrap(); - let payer = rpc.get_payer().insecure_clone(); - - let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); - - let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id); - - let (init_config_ix, config_pda) = InitializeRentFreeConfig::new( - &program_id, - &payer.pubkey(), - &program_data_pda, - rent_sponsor, - payer.pubkey(), - ) - .build(); - - rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) - .await - .expect("Initialize config should succeed"); - - let owner = Keypair::new().pubkey(); - - let (record_pda, _) = - Pubkey::find_program_address(&[RECORD_SEED, owner.as_ref()], &program_id); - - let proof_result = get_create_accounts_proof( - &rpc, - &program_id, - vec![CreateAccountsProofInput::pda(record_pda)], - ) - .await - .unwrap(); - - let accounts = single_pda_derive_test::accounts::CreateZeroCopyRecord { - fee_payer: payer.pubkey(), - compression_config: config_pda, - pda_rent_sponsor: rent_sponsor, - record: record_pda, - system_program: solana_sdk::system_program::ID, - }; - - let instruction_data = single_pda_derive_test::instruction::CreateZeroCopyRecord { - params: CreateZeroCopyRecordParams { - create_accounts_proof: proof_result.create_accounts_proof, - owner, - }, - }; - - let instruction = Instruction { - program_id, - accounts: [ - accounts.to_account_metas(None), - proof_result.remaining_accounts, - ] - .concat(), - data: instruction_data.data(), - }; - - rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) - .await - .expect("CreateZeroCopyRecord should succeed"); - - let record_account = rpc - .get_account(record_pda) - .await - .unwrap() - .expect("Record PDA should exist on-chain"); - - // Parse zero-copy data using bytemuck - use single_pda_derive_test::ZeroCopyRecord; - let discriminator_len = 8; - let data = &record_account.data[discriminator_len..]; - let record: &ZeroCopyRecord = bytemuck::from_bytes(data); - - assert_eq!(record.owner, owner, "Record owner should match"); - assert_eq!(record.counter, 0, "Record counter should be 0"); -} - -// ============================================================================= -// 5. Create Mint -// ============================================================================= - -#[tokio::test] -async fn test_create_mint_derive() { - use single_pda_derive_test::{CreateMintParams, MINT_SIGNER_SEED_A}; - - let program_id = single_pda_derive_test::ID; - let mut config = - ProgramTestConfig::new_v2(true, Some(vec![("single_pda_derive_test", program_id)])); - config = config.with_light_protocol_events(); - - let mut rpc = LightProgramTest::new(config).await.unwrap(); - let payer = rpc.get_payer().insecure_clone(); - - let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); - - let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id); - - let (init_config_ix, config_pda) = InitializeRentFreeConfig::new( - &program_id, - &payer.pubkey(), - &program_data_pda, - rent_sponsor, - payer.pubkey(), - ) - .build(); - - rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) - .await - .expect("Initialize config should succeed"); - - let authority = Keypair::new(); - - let (mint_signer_pda, mint_signer_bump) = Pubkey::find_program_address( - &[MINT_SIGNER_SEED_A, authority.pubkey().as_ref()], - &program_id, - ); - - let (mint_pda, _) = light_token::instruction::find_mint_address(&mint_signer_pda); - - let proof_result = get_create_accounts_proof( - &rpc, - &program_id, - vec![CreateAccountsProofInput::mint(mint_signer_pda)], - ) - .await - .unwrap(); - - let accounts = single_pda_derive_test::accounts::CreateMint { - fee_payer: payer.pubkey(), - authority: authority.pubkey(), - mint_signer: mint_signer_pda, - mint: mint_pda, - compression_config: config_pda, - light_token_config: LIGHT_TOKEN_CONFIG, - light_token_rent_sponsor: LIGHT_TOKEN_RENT_SPONSOR, - light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), - light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), - system_program: solana_sdk::system_program::ID, - }; - - let instruction_data = single_pda_derive_test::instruction::CreateMint { - params: CreateMintParams { - create_accounts_proof: proof_result.create_accounts_proof, - mint_signer_bump, - }, - }; - - let instruction = Instruction { - program_id, - accounts: [ - accounts.to_account_metas(None), - proof_result.remaining_accounts, - ] - .concat(), - data: instruction_data.data(), - }; - - rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority]) - .await - .expect("CreateMint should succeed"); - - let mint_account = rpc - .get_account(mint_pda) - .await - .unwrap() - .expect("Mint should exist on-chain"); - - use light_token_interface::state::Mint; - let mint: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_account.data[..]) - .expect("Failed to deserialize Mint"); - - assert_eq!(mint.base.decimals, 9, "Mint should have 9 decimals"); - assert_eq!( - mint.base.mint_authority, - Some(payer.pubkey().to_bytes().into()), - "Mint authority should be fee_payer" - ); -} - -// ============================================================================= -// 6. Create Two Mints -// ============================================================================= - -#[tokio::test] -async fn test_create_two_mints_derive() { - use single_pda_derive_test::{CreateTwoMintsParams, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B}; - - let program_id = single_pda_derive_test::ID; - let mut config = - ProgramTestConfig::new_v2(true, Some(vec![("single_pda_derive_test", program_id)])); - config = config.with_light_protocol_events(); - - let mut rpc = LightProgramTest::new(config).await.unwrap(); - let payer = rpc.get_payer().insecure_clone(); - - let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); - - let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id); - - let (init_config_ix, config_pda) = InitializeRentFreeConfig::new( - &program_id, - &payer.pubkey(), - &program_data_pda, - rent_sponsor, - payer.pubkey(), - ) - .build(); - - rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) - .await - .expect("Initialize config should succeed"); - - let authority = Keypair::new(); - - // Derive mint A - let (mint_signer_a, mint_signer_bump_a) = Pubkey::find_program_address( - &[MINT_SIGNER_SEED_A, authority.pubkey().as_ref()], - &program_id, - ); - let (mint_a_pda, _) = light_token::instruction::find_mint_address(&mint_signer_a); - - // Derive mint B - let (mint_signer_b, mint_signer_bump_b) = Pubkey::find_program_address( - &[MINT_SIGNER_SEED_B, authority.pubkey().as_ref()], - &program_id, - ); - let (mint_b_pda, _) = light_token::instruction::find_mint_address(&mint_signer_b); - - let proof_result = get_create_accounts_proof( - &rpc, - &program_id, - vec![ - CreateAccountsProofInput::mint(mint_signer_a), - CreateAccountsProofInput::mint(mint_signer_b), - ], - ) - .await - .unwrap(); - - let accounts = single_pda_derive_test::accounts::CreateTwoMints { - fee_payer: payer.pubkey(), - authority: authority.pubkey(), - mint_signer_a, - mint_a: mint_a_pda, - mint_signer_b, - mint_b: mint_b_pda, - compression_config: config_pda, - light_token_config: LIGHT_TOKEN_CONFIG, - light_token_rent_sponsor: LIGHT_TOKEN_RENT_SPONSOR, - light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), - light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), - system_program: solana_sdk::system_program::ID, - }; - - let instruction_data = single_pda_derive_test::instruction::CreateTwoMints { - params: CreateTwoMintsParams { - create_accounts_proof: proof_result.create_accounts_proof, - mint_signer_bump_a, - mint_signer_bump_b, - }, - }; - - let instruction = Instruction { - program_id, - accounts: [ - accounts.to_account_metas(None), - proof_result.remaining_accounts, - ] - .concat(), - data: instruction_data.data(), - }; - - rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority]) - .await - .expect("CreateTwoMints should succeed"); - - // Verify mint A - let mint_a_account = rpc - .get_account(mint_a_pda) - .await - .unwrap() - .expect("Mint A should exist on-chain"); - - use light_token_interface::state::Mint; - let mint_a: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_a_account.data[..]) - .expect("Failed to deserialize Mint A"); - - assert_eq!(mint_a.base.decimals, 9, "Mint A should have 9 decimals"); - assert_eq!( - mint_a.base.mint_authority, - Some(payer.pubkey().to_bytes().into()), - "Mint A authority should be fee_payer" - ); - - // Verify mint B - let mint_b_account = rpc - .get_account(mint_b_pda) - .await - .unwrap() - .expect("Mint B should exist on-chain"); - - let mint_b: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_b_account.data[..]) - .expect("Failed to deserialize Mint B"); - - assert_eq!(mint_b.base.decimals, 6, "Mint B should have 6 decimals"); - assert_eq!( - mint_b.base.mint_authority, - Some(payer.pubkey().to_bytes().into()), - "Mint B authority should be fee_payer" - ); -} - -// ============================================================================= -// 7. Create All (combined) -// ============================================================================= - -#[tokio::test] -async fn test_create_all_derive() { - use single_pda_derive_test::{ - CreateAllParams, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, RECORD_SEED, VAULT_AUTH_SEED, - VAULT_SEED, - }; - - let program_id = single_pda_derive_test::ID; - let mut config = - ProgramTestConfig::new_v2(true, Some(vec![("single_pda_derive_test", program_id)])); - config = config.with_light_protocol_events(); - - let mut rpc = LightProgramTest::new(config).await.unwrap(); - let payer = rpc.get_payer().insecure_clone(); - - let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); - - let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id); - - let (init_config_ix, config_pda) = InitializeRentFreeConfig::new( - &program_id, - &payer.pubkey(), - &program_data_pda, - rent_sponsor, - payer.pubkey(), - ) - .build(); - - rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) - .await - .expect("Initialize config should succeed"); - - // Setup pre-existing mints for ATA and vault - let (ata_mint, _) = setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; - let (vault_mint, _) = setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; - - let owner = Keypair::new().pubkey(); - let authority = Keypair::new(); - - // PDA - let (record_pda, _) = - Pubkey::find_program_address(&[b"minimal_record", owner.as_ref()], &program_id); - - // Zero-copy - let (zc_record_pda, _) = - Pubkey::find_program_address(&[RECORD_SEED, owner.as_ref()], &program_id); - - // ATA - let ata_owner = payer.pubkey(); - let (ata, ata_bump) = light_token::instruction::derive_token_ata(&ata_owner, &ata_mint); - - // Token vault - let (vault_authority, _) = Pubkey::find_program_address(&[VAULT_AUTH_SEED], &program_id); - let (vault, vault_bump) = - Pubkey::find_program_address(&[VAULT_SEED, vault_mint.as_ref()], &program_id); - - // Mint A - let (mint_signer_a, mint_signer_bump_a) = Pubkey::find_program_address( - &[MINT_SIGNER_SEED_A, authority.pubkey().as_ref()], - &program_id, - ); - let (mint_a_pda, _) = light_token::instruction::find_mint_address(&mint_signer_a); - - // Mint B - let (mint_signer_b, mint_signer_bump_b) = Pubkey::find_program_address( - &[MINT_SIGNER_SEED_B, authority.pubkey().as_ref()], - &program_id, - ); - let (mint_b_pda, _) = light_token::instruction::find_mint_address(&mint_signer_b); - - // Build proof inputs for all accounts - let proof_result = get_create_accounts_proof( - &rpc, - &program_id, - vec![ - CreateAccountsProofInput::pda(record_pda), - CreateAccountsProofInput::pda(zc_record_pda), - CreateAccountsProofInput::mint(mint_signer_a), - CreateAccountsProofInput::mint(mint_signer_b), - ], - ) - .await - .unwrap(); - - let accounts = single_pda_derive_test::accounts::CreateAll { - fee_payer: payer.pubkey(), - compression_config: config_pda, - pda_rent_sponsor: rent_sponsor, - record: record_pda, - zero_copy_record: zc_record_pda, - ata_mint, - ata_owner, - ata, - vault_mint, - vault_authority, - vault, - authority: authority.pubkey(), - mint_signer_a, - mint_a: mint_a_pda, - mint_signer_b, - mint_b: mint_b_pda, - light_token_config: LIGHT_TOKEN_CONFIG, - light_token_rent_sponsor: LIGHT_TOKEN_RENT_SPONSOR, - light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), - light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), - system_program: solana_sdk::system_program::ID, - }; - - let instruction_data = single_pda_derive_test::instruction::CreateAll { - params: CreateAllParams { - create_accounts_proof: proof_result.create_accounts_proof, - owner, - ata_bump, - vault_bump, - mint_signer_bump_a, - mint_signer_bump_b, - }, - }; - - let instruction = Instruction { - program_id, - accounts: [ - accounts.to_account_metas(None), - proof_result.remaining_accounts, - ] - .concat(), - data: instruction_data.data(), - }; - - rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority]) - .await - .expect("CreateAll should succeed"); - - // Verify PDA - let record_account = rpc - .get_account(record_pda) - .await - .unwrap() - .expect("Record PDA should exist"); - use single_pda_derive_test::MinimalRecord; - let record: MinimalRecord = - borsh::BorshDeserialize::deserialize(&mut &record_account.data[8..]) - .expect("Failed to deserialize MinimalRecord"); - assert_eq!(record.owner, owner, "Record owner should match"); - - // Verify zero-copy - let zc_account = rpc - .get_account(zc_record_pda) - .await - .unwrap() - .expect("Zero-copy record should exist"); - use single_pda_derive_test::ZeroCopyRecord; - let zc_record: &ZeroCopyRecord = bytemuck::from_bytes(&zc_account.data[8..]); - assert_eq!(zc_record.owner, owner, "ZC record owner should match"); - assert_eq!(zc_record.counter, 0, "ZC record counter should be 0"); - - // Verify ATA - let ata_account = rpc - .get_account(ata) - .await - .unwrap() - .expect("ATA should exist"); - use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; - let ata_token: Token = borsh::BorshDeserialize::deserialize(&mut &ata_account.data[..]) - .expect("Failed to deserialize ATA Token"); - assert_eq!( - ata_token.mint, - ata_mint.to_bytes().into(), - "ATA mint should match" - ); - assert_eq!( - ata_token.owner, - ata_owner.to_bytes().into(), - "ATA owner should match" - ); - - // Verify vault - let vault_account = rpc - .get_account(vault) - .await - .unwrap() - .expect("Vault should exist"); - let vault_token: Token = borsh::BorshDeserialize::deserialize(&mut &vault_account.data[..]) - .expect("Failed to deserialize Vault Token"); - assert_eq!( - vault_token.mint, - vault_mint.to_bytes().into(), - "Vault mint should match" - ); - assert_eq!( - vault_token.owner, - vault_authority.to_bytes().into(), - "Vault owner should match" - ); - - // Verify mint A - let mint_a_account = rpc - .get_account(mint_a_pda) - .await - .unwrap() - .expect("Mint A should exist"); - use light_token_interface::state::Mint; - let mint_a: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_a_account.data[..]) - .expect("Failed to deserialize Mint A"); - assert_eq!(mint_a.base.decimals, 9, "Mint A should have 9 decimals"); - - // Verify mint B - let mint_b_account = rpc - .get_account(mint_b_pda) - .await - .unwrap() - .expect("Mint B should exist"); - let mint_b: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_b_account.data[..]) - .expect("Failed to deserialize Mint B"); - assert_eq!(mint_b.base.decimals, 6, "Mint B should have 6 decimals"); -} diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_all.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_all.rs new file mode 100644 index 0000000000..1c2d512195 --- /dev/null +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_all.rs @@ -0,0 +1,208 @@ +mod shared; + +use anchor_lang::{InstructionData, ToAccountMetas}; +use light_client::interface::{get_create_accounts_proof, CreateAccountsProofInput}; +use light_program_test::Rpc; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use pinocchio_derive_test::{ + CreateAllParams, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, RECORD_SEED, VAULT_AUTH_SEED, + VAULT_SEED, +}; +use solana_instruction::Instruction; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +#[tokio::test] +async fn test_create_all_derive() { + let env = shared::setup_test_env().await; + let mut rpc = env.rpc; + let payer = env.payer; + let program_id = env.program_id; + + // Setup pre-existing mints for ATA and vault + let (ata_mint, _) = shared::setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; + let (vault_mint, _) = shared::setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; + + let owner = Keypair::new().pubkey(); + let authority = Keypair::new(); + + // PDA + let (record_pda, _) = + Pubkey::find_program_address(&[b"minimal_record", owner.as_ref()], &program_id); + + // Zero-copy + let (zc_record_pda, _) = + Pubkey::find_program_address(&[RECORD_SEED, owner.as_ref()], &program_id); + + // ATA + let ata_owner = payer.pubkey(); + let (ata, ata_bump) = light_token::instruction::derive_token_ata(&ata_owner, &ata_mint); + + // Token vault + let (vault_authority, _) = Pubkey::find_program_address(&[VAULT_AUTH_SEED], &program_id); + let (vault, vault_bump) = + Pubkey::find_program_address(&[VAULT_SEED, vault_mint.as_ref()], &program_id); + + // Mint A + let (mint_signer_a, mint_signer_bump_a) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED_A, authority.pubkey().as_ref()], + &program_id, + ); + let (mint_a_pda, _) = light_token::instruction::find_mint_address(&mint_signer_a); + + // Mint B + let (mint_signer_b, mint_signer_bump_b) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED_B, authority.pubkey().as_ref()], + &program_id, + ); + let (mint_b_pda, _) = light_token::instruction::find_mint_address(&mint_signer_b); + + // Build proof inputs for all accounts + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![ + CreateAccountsProofInput::pda(record_pda), + CreateAccountsProofInput::pda(zc_record_pda), + CreateAccountsProofInput::mint(mint_signer_a), + CreateAccountsProofInput::mint(mint_signer_b), + ], + ) + .await + .unwrap(); + + let accounts = pinocchio_derive_test::accounts::CreateAll { + fee_payer: payer.pubkey(), + compression_config: env.config_pda, + pda_rent_sponsor: env.rent_sponsor, + record: record_pda, + zero_copy_record: zc_record_pda, + ata_mint, + ata_owner, + ata, + vault_mint, + vault_authority, + vault, + authority: authority.pubkey(), + mint_signer_a, + mint_a: mint_a_pda, + mint_signer_b, + mint_b: mint_b_pda, + light_token_config: LIGHT_TOKEN_CONFIG, + light_token_rent_sponsor: LIGHT_TOKEN_RENT_SPONSOR, + light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), + light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = pinocchio_derive_test::instruction::CreateAll { + params: CreateAllParams { + create_accounts_proof: proof_result.create_accounts_proof, + owner, + ata_bump, + vault_bump, + mint_signer_bump_a, + mint_signer_bump_b, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority]) + .await + .expect("CreateAll should succeed"); + + // Verify PDA + let record_account = rpc + .get_account(record_pda) + .await + .unwrap() + .expect("Record PDA should exist"); + use pinocchio_derive_test::MinimalRecord; + let record: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &record_account.data[8..]) + .expect("Failed to deserialize MinimalRecord"); + assert_eq!(record.owner, owner, "Record owner should match"); + + // Verify zero-copy + let zc_account = rpc + .get_account(zc_record_pda) + .await + .unwrap() + .expect("Zero-copy record should exist"); + use pinocchio_derive_test::ZeroCopyRecord; + let zc_record: &ZeroCopyRecord = bytemuck::from_bytes(&zc_account.data[8..]); + assert_eq!(zc_record.owner, owner, "ZC record owner should match"); + assert_eq!(zc_record.counter, 0, "ZC record counter should be 0"); + + // Verify ATA + let ata_account = rpc + .get_account(ata) + .await + .unwrap() + .expect("ATA should exist"); + use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; + let ata_token: Token = borsh::BorshDeserialize::deserialize(&mut &ata_account.data[..]) + .expect("Failed to deserialize ATA Token"); + use light_compressed_account::pubkey::Pubkey as LPubkey; + assert_eq!( + ata_token.mint, + LPubkey::from(ata_mint.to_bytes()), + "ATA mint should match" + ); + assert_eq!( + ata_token.owner, + LPubkey::from(ata_owner.to_bytes()), + "ATA owner should match" + ); + + // Verify vault + let vault_account = rpc + .get_account(vault) + .await + .unwrap() + .expect("Vault should exist"); + let vault_token: Token = borsh::BorshDeserialize::deserialize(&mut &vault_account.data[..]) + .expect("Failed to deserialize Vault Token"); + assert_eq!( + vault_token.mint, + LPubkey::from(vault_mint.to_bytes()), + "Vault mint should match" + ); + assert_eq!( + vault_token.owner, + LPubkey::from(vault_authority.to_bytes()), + "Vault owner should match" + ); + + // Verify mint A + let mint_a_account = rpc + .get_account(mint_a_pda) + .await + .unwrap() + .expect("Mint A should exist"); + use light_token_interface::state::Mint; + let mint_a: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_a_account.data[..]) + .expect("Failed to deserialize Mint A"); + assert_eq!(mint_a.base.decimals, 9, "Mint A should have 9 decimals"); + + // Verify mint B + let mint_b_account = rpc + .get_account(mint_b_pda) + .await + .unwrap() + .expect("Mint B should exist"); + let mint_b: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_b_account.data[..]) + .expect("Failed to deserialize Mint B"); + assert_eq!(mint_b.base.decimals, 6, "Mint B should have 6 decimals"); +} diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_ata.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_ata.rs new file mode 100644 index 0000000000..8078ad1b7b --- /dev/null +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_ata.rs @@ -0,0 +1,87 @@ +mod shared; + +use anchor_lang::{InstructionData, ToAccountMetas}; +use light_client::interface::get_create_accounts_proof; +use light_program_test::Rpc; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use pinocchio_derive_test::CreateAtaParams; +use solana_instruction::Instruction; +use solana_signer::Signer; + +#[tokio::test] +async fn test_create_ata_derive() { + let env = shared::setup_test_env().await; + let mut rpc = env.rpc; + let payer = env.payer; + let program_id = env.program_id; + + let (mint, _mint_seed) = shared::setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; + + let ata_owner = payer.pubkey(); + let (ata, ata_bump) = light_token::instruction::derive_token_ata(&ata_owner, &mint); + + let proof_result = get_create_accounts_proof(&rpc, &program_id, vec![]) + .await + .unwrap(); + + let accounts = pinocchio_derive_test::accounts::CreateAta { + fee_payer: payer.pubkey(), + ata_mint: mint, + ata_owner, + ata, + light_token_config: LIGHT_TOKEN_CONFIG, + light_token_rent_sponsor: LIGHT_TOKEN_RENT_SPONSOR, + light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = pinocchio_derive_test::instruction::CreateAta { + params: CreateAtaParams { + create_accounts_proof: proof_result.create_accounts_proof, + ata_bump, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) + .await + .expect("CreateAta should succeed"); + + let ata_account = rpc + .get_account(ata) + .await + .unwrap() + .expect("ATA should exist on-chain"); + + use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; + let token: Token = borsh::BorshDeserialize::deserialize(&mut &ata_account.data[..]) + .expect("Failed to deserialize Token"); + + let expected_token = Token { + mint: mint.to_bytes().into(), + owner: ata_owner.to_bytes().into(), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT, + extensions: token.extensions.clone(), + }; + + assert_eq!( + token, expected_token, + "ATA should match expected after creation" + ); +} diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_mint.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_mint.rs new file mode 100644 index 0000000000..74ca1d87f3 --- /dev/null +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_mint.rs @@ -0,0 +1,88 @@ +mod shared; + +use anchor_lang::{InstructionData, ToAccountMetas}; +use light_client::interface::{get_create_accounts_proof, CreateAccountsProofInput}; +use light_program_test::Rpc; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use pinocchio_derive_test::{CreateMintParams, MINT_SIGNER_SEED_A}; +use solana_instruction::Instruction; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +#[tokio::test] +async fn test_create_mint_derive() { + let env = shared::setup_test_env().await; + let mut rpc = env.rpc; + let payer = env.payer; + let program_id = env.program_id; + + let authority = Keypair::new(); + + let (mint_signer_pda, mint_signer_bump) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED_A, authority.pubkey().as_ref()], + &program_id, + ); + + let (mint_pda, _) = light_token::instruction::find_mint_address(&mint_signer_pda); + + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![CreateAccountsProofInput::mint(mint_signer_pda)], + ) + .await + .unwrap(); + + let accounts = pinocchio_derive_test::accounts::CreateMint { + fee_payer: payer.pubkey(), + authority: authority.pubkey(), + mint_signer: mint_signer_pda, + mint: mint_pda, + compression_config: env.config_pda, + light_token_config: LIGHT_TOKEN_CONFIG, + light_token_rent_sponsor: LIGHT_TOKEN_RENT_SPONSOR, + light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), + light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = pinocchio_derive_test::instruction::CreateMint { + params: CreateMintParams { + create_accounts_proof: proof_result.create_accounts_proof, + mint_signer_bump, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority]) + .await + .expect("CreateMint should succeed"); + + let mint_account = rpc + .get_account(mint_pda) + .await + .unwrap() + .expect("Mint should exist on-chain"); + + use light_token_interface::state::Mint; + let mint: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_account.data[..]) + .expect("Failed to deserialize Mint"); + + assert_eq!(mint.base.decimals, 9, "Mint should have 9 decimals"); + assert_eq!( + mint.base.mint_authority, + Some(payer.pubkey().to_bytes().into()), + "Mint authority should be fee_payer" + ); +} diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_pda.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_pda.rs new file mode 100644 index 0000000000..e6459fba19 --- /dev/null +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_pda.rs @@ -0,0 +1,77 @@ +mod shared; + +use anchor_lang::{InstructionData, ToAccountMetas}; +use light_client::interface::{get_create_accounts_proof, CreateAccountsProofInput}; +use light_program_test::Rpc; +use pinocchio_derive_test::CreatePdaParams; +use solana_instruction::Instruction; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +#[tokio::test] +async fn test_create_single_pda_derive() { + let env = shared::setup_test_env().await; + let mut rpc = env.rpc; + let payer = env.payer; + let program_id = env.program_id; + + let owner = Keypair::new().pubkey(); + + let (record_pda, _) = + Pubkey::find_program_address(&[b"minimal_record", owner.as_ref()], &program_id); + + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![CreateAccountsProofInput::pda(record_pda)], + ) + .await + .unwrap(); + + let accounts = pinocchio_derive_test::accounts::CreatePda { + fee_payer: payer.pubkey(), + compression_config: env.config_pda, + pda_rent_sponsor: env.rent_sponsor, + record: record_pda, + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = pinocchio_derive_test::instruction::CreatePda { + params: CreatePdaParams { + create_accounts_proof: proof_result.create_accounts_proof, + owner, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) + .await + .expect("CreatePda should succeed"); + + let record_account = rpc + .get_account(record_pda) + .await + .unwrap() + .expect("Record PDA should exist on-chain"); + + use pinocchio_derive_test::MinimalRecord; + let record: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &record_account.data[8..]) + .expect("Failed to deserialize MinimalRecord"); + + assert_eq!(record.owner, owner, "Record owner should match"); + assert!( + !record.compression_info.is_compressed(), + "Record should be in decompressed state" + ); +} diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_token_vault.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_token_vault.rs new file mode 100644 index 0000000000..476dac9ffd --- /dev/null +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_token_vault.rs @@ -0,0 +1,91 @@ +mod shared; + +use anchor_lang::{InstructionData, ToAccountMetas}; +use light_client::interface::get_create_accounts_proof; +use light_program_test::Rpc; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use pinocchio_derive_test::{CreateTokenVaultParams, VAULT_AUTH_SEED, VAULT_SEED}; +use solana_instruction::Instruction; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +#[tokio::test] +async fn test_create_token_vault_derive() { + let env = shared::setup_test_env().await; + let mut rpc = env.rpc; + let payer = env.payer; + let program_id = env.program_id; + + let (mint, _mint_seed) = shared::setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; + + let (vault_authority, _auth_bump) = + Pubkey::find_program_address(&[VAULT_AUTH_SEED], &program_id); + let (vault, vault_bump) = + Pubkey::find_program_address(&[VAULT_SEED, mint.as_ref()], &program_id); + + let proof_result = get_create_accounts_proof(&rpc, &program_id, vec![]) + .await + .unwrap(); + + let accounts = pinocchio_derive_test::accounts::CreateTokenVault { + fee_payer: payer.pubkey(), + mint, + vault_authority, + vault, + light_token_config: LIGHT_TOKEN_CONFIG, + light_token_rent_sponsor: LIGHT_TOKEN_RENT_SPONSOR, + light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), + light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = pinocchio_derive_test::instruction::CreateTokenVault { + params: CreateTokenVaultParams { + create_accounts_proof: proof_result.create_accounts_proof, + vault_bump, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) + .await + .expect("CreateTokenVault should succeed"); + + let vault_account = rpc + .get_account(vault) + .await + .unwrap() + .expect("Token vault should exist on-chain"); + + use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; + let token: Token = borsh::BorshDeserialize::deserialize(&mut &vault_account.data[..]) + .expect("Failed to deserialize Token"); + + let expected_token = Token { + mint: mint.to_bytes().into(), + owner: vault_authority.to_bytes().into(), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT, + extensions: token.extensions.clone(), + }; + + assert_eq!( + token, expected_token, + "Token vault should match expected after creation" + ); +} diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_two_mints.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_two_mints.rs new file mode 100644 index 0000000000..c58d23896b --- /dev/null +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_two_mints.rs @@ -0,0 +1,117 @@ +mod shared; + +use anchor_lang::{InstructionData, ToAccountMetas}; +use light_client::interface::{get_create_accounts_proof, CreateAccountsProofInput}; +use light_program_test::Rpc; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use pinocchio_derive_test::{CreateTwoMintsParams, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B}; +use solana_instruction::Instruction; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +#[tokio::test] +async fn test_create_two_mints_derive() { + let env = shared::setup_test_env().await; + let mut rpc = env.rpc; + let payer = env.payer; + let program_id = env.program_id; + + let authority = Keypair::new(); + + let (mint_signer_a, mint_signer_bump_a) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED_A, authority.pubkey().as_ref()], + &program_id, + ); + let (mint_a_pda, _) = light_token::instruction::find_mint_address(&mint_signer_a); + + let (mint_signer_b, mint_signer_bump_b) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED_B, authority.pubkey().as_ref()], + &program_id, + ); + let (mint_b_pda, _) = light_token::instruction::find_mint_address(&mint_signer_b); + + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![ + CreateAccountsProofInput::mint(mint_signer_a), + CreateAccountsProofInput::mint(mint_signer_b), + ], + ) + .await + .unwrap(); + + let accounts = pinocchio_derive_test::accounts::CreateTwoMints { + fee_payer: payer.pubkey(), + authority: authority.pubkey(), + mint_signer_a, + mint_a: mint_a_pda, + mint_signer_b, + mint_b: mint_b_pda, + compression_config: env.config_pda, + light_token_config: LIGHT_TOKEN_CONFIG, + light_token_rent_sponsor: LIGHT_TOKEN_RENT_SPONSOR, + light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), + light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = pinocchio_derive_test::instruction::CreateTwoMints { + params: CreateTwoMintsParams { + create_accounts_proof: proof_result.create_accounts_proof, + mint_signer_bump_a, + mint_signer_bump_b, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority]) + .await + .expect("CreateTwoMints should succeed"); + + // Verify mint A + let mint_a_account = rpc + .get_account(mint_a_pda) + .await + .unwrap() + .expect("Mint A should exist on-chain"); + + use light_token_interface::state::Mint; + let mint_a: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_a_account.data[..]) + .expect("Failed to deserialize Mint A"); + + assert_eq!(mint_a.base.decimals, 9, "Mint A should have 9 decimals"); + assert_eq!( + mint_a.base.mint_authority, + Some(payer.pubkey().to_bytes().into()), + "Mint A authority should be fee_payer" + ); + + // Verify mint B + let mint_b_account = rpc + .get_account(mint_b_pda) + .await + .unwrap() + .expect("Mint B should exist on-chain"); + + let mint_b: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_b_account.data[..]) + .expect("Failed to deserialize Mint B"); + + assert_eq!(mint_b.base.decimals, 6, "Mint B should have 6 decimals"); + assert_eq!( + mint_b.base.mint_authority, + Some(payer.pubkey().to_bytes().into()), + "Mint B authority should be fee_payer" + ); +} diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_zero_copy_record.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_zero_copy_record.rs new file mode 100644 index 0000000000..fea182c19c --- /dev/null +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_zero_copy_record.rs @@ -0,0 +1,73 @@ +mod shared; + +use anchor_lang::{InstructionData, ToAccountMetas}; +use light_client::interface::{get_create_accounts_proof, CreateAccountsProofInput}; +use light_program_test::Rpc; +use pinocchio_derive_test::{CreateZeroCopyRecordParams, RECORD_SEED}; +use solana_instruction::Instruction; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +#[tokio::test] +async fn test_create_zero_copy_record_derive() { + let env = shared::setup_test_env().await; + let mut rpc = env.rpc; + let payer = env.payer; + let program_id = env.program_id; + + let owner = Keypair::new().pubkey(); + + let (record_pda, _) = Pubkey::find_program_address(&[RECORD_SEED, owner.as_ref()], &program_id); + + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![CreateAccountsProofInput::pda(record_pda)], + ) + .await + .unwrap(); + + let accounts = pinocchio_derive_test::accounts::CreateZeroCopyRecord { + fee_payer: payer.pubkey(), + compression_config: env.config_pda, + pda_rent_sponsor: env.rent_sponsor, + record: record_pda, + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = pinocchio_derive_test::instruction::CreateZeroCopyRecord { + params: CreateZeroCopyRecordParams { + create_accounts_proof: proof_result.create_accounts_proof, + owner, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) + .await + .expect("CreateZeroCopyRecord should succeed"); + + let record_account = rpc + .get_account(record_pda) + .await + .unwrap() + .expect("Record PDA should exist on-chain"); + + use pinocchio_derive_test::ZeroCopyRecord; + let discriminator_len = 8; + let data = &record_account.data[discriminator_len..]; + let record: &ZeroCopyRecord = bytemuck::from_bytes(data); + + assert_eq!(record.owner, owner, "Record owner should match"); + assert_eq!(record.counter, 0, "Record counter should be 0"); +} diff --git a/sdk-tests/single-account-loader-test/src/lib.rs b/sdk-tests/single-account-loader-test/src/lib.rs index 93eaac3f09..f3bed87c04 100644 --- a/sdk-tests/single-account-loader-test/src/lib.rs +++ b/sdk-tests/single-account-loader-test/src/lib.rs @@ -6,7 +6,7 @@ #![allow(deprecated)] use anchor_lang::prelude::*; -use light_account::{derive_light_cpi_signer, CreateAccountsProof, CpiSigner}; +use light_account::{derive_light_cpi_signer, CpiSigner, CreateAccountsProof}; use light_sdk_macros::{light_program, LightAccounts}; pub mod state; diff --git a/sdk-tests/single-ata-test/src/lib.rs b/sdk-tests/single-ata-test/src/lib.rs index f2d15dd711..0c7da63a73 100644 --- a/sdk-tests/single-ata-test/src/lib.rs +++ b/sdk-tests/single-ata-test/src/lib.rs @@ -7,7 +7,7 @@ use anchor_lang::prelude::*; use light_account::{ - derive_light_cpi_signer, CreateAccountsProof, CpiSigner, LIGHT_TOKEN_CONFIG, + derive_light_cpi_signer, CpiSigner, CreateAccountsProof, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_PROGRAM_ID, LIGHT_TOKEN_RENT_SPONSOR, }; use light_sdk_macros::{light_program, LightAccounts}; @@ -49,7 +49,7 @@ pub struct CreateAta<'info> { pub light_token_rent_sponsor: AccountInfo<'info>, /// CHECK: Light Token Program for CPI - #[account(address = LIGHT_TOKEN_PROGRAM_ID.into())] + #[account(address = LIGHT_TOKEN_PROGRAM_ID)] pub light_token_program: AccountInfo<'info>, pub system_program: Program<'info, System>, diff --git a/sdk-tests/single-mint-test/src/lib.rs b/sdk-tests/single-mint-test/src/lib.rs index 027eb87a18..afdeb901e2 100644 --- a/sdk-tests/single-mint-test/src/lib.rs +++ b/sdk-tests/single-mint-test/src/lib.rs @@ -6,7 +6,7 @@ #![allow(deprecated)] use anchor_lang::prelude::*; -use light_account::{derive_light_cpi_signer, CreateAccountsProof, CpiSigner}; +use light_account::{derive_light_cpi_signer, CpiSigner, CreateAccountsProof}; use light_sdk_macros::{light_program, LightAccounts}; declare_id!("Mint111111111111111111111111111111111111111"); diff --git a/sdk-tests/single-pda-test/src/instruction_accounts.rs b/sdk-tests/single-pda-test/src/instruction_accounts.rs index dec5093db8..8e55deef12 100644 --- a/sdk-tests/single-pda-test/src/instruction_accounts.rs +++ b/sdk-tests/single-pda-test/src/instruction_accounts.rs @@ -1,8 +1,8 @@ //! Accounts module for single-pda-test. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::CreateAccountsProof; +use light_sdk_macros::LightAccounts; use crate::state::MinimalRecord; diff --git a/sdk-tests/single-token-test/src/lib.rs b/sdk-tests/single-token-test/src/lib.rs index 750d275561..41c4921e53 100644 --- a/sdk-tests/single-token-test/src/lib.rs +++ b/sdk-tests/single-token-test/src/lib.rs @@ -7,7 +7,7 @@ use anchor_lang::prelude::*; use light_account::{ - derive_light_cpi_signer, CreateAccountsProof, CpiSigner, LIGHT_TOKEN_CONFIG, + derive_light_cpi_signer, CpiSigner, CreateAccountsProof, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR, }; use light_sdk_macros::{light_program, LightAccounts}; From 157d811620719063821cd68261bf3961b0608501 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 2 Feb 2026 19:49:15 +0000 Subject: [PATCH 10/15] cleanup --- Cargo.lock | 6 +- forester/Cargo.toml | 1 + forester/src/compressible/pda/compressor.rs | 16 +- forester/src/compressible/pda/state.rs | 2 +- program-libs/compressible/src/config.rs | 7 +- .../create-address-test-program/src/lib.rs | 66 ++- .../tests/test_cpi_context_event.rs | 2 +- .../src/compressible/create_config.rs | 4 +- sdk-libs/account-pinocchio/src/lib.rs | 2 +- sdk-libs/account/src/lib.rs | 7 +- sdk-libs/client/Cargo.toml | 1 + .../client/src/interface/initialize_config.rs | 2 +- sdk-libs/client/src/interface/instructions.rs | 15 +- .../src/interface/light_program_interface.rs | 2 +- .../client/src/interface/load_accounts.rs | 2 +- sdk-libs/client/src/interface/mod.rs | 2 +- sdk-libs/compressed-token-sdk/Cargo.toml | 1 + .../compressed_token/v2/decompress_full.rs | 11 +- sdk-libs/macros/src/lib.rs | 2 +- sdk-libs/program-test/Cargo.toml | 1 + sdk-libs/program-test/src/compressible.rs | 4 +- .../forester/compress_and_close_forester.rs | 2 +- .../src/program_test/light_program_test.rs | 2 +- sdk-libs/sdk-types/Cargo.toml | 4 +- .../src/interface/cpi/create_mints.rs | 2 +- sdk-libs/sdk-types/src/interface/mod.rs | 4 +- .../src/interface/program/config/create.rs | 8 +- .../src/interface/program/config/mod.rs | 2 +- .../src/interface/program/config/state.rs | 6 +- sdk-libs/sdk/Cargo.toml | 3 - sdk-libs/sdk/src/cpi/account.rs | 88 +++ sdk-libs/sdk/src/cpi/instruction.rs | 104 ++++ sdk-libs/sdk/src/cpi/invoke.rs | 202 +++++++ sdk-libs/sdk/src/cpi/mod.rs | 113 +--- sdk-libs/sdk/src/cpi/v1/invoke.rs | 223 +++++--- sdk-libs/sdk/src/cpi/v2/accounts.rs | 96 ++++ .../sdk/src/cpi/v2/accounts_cpi_context.rs | 19 + sdk-libs/sdk/src/cpi/v2/invoke.rs | 123 ++++- sdk-libs/sdk/src/cpi/v2/mod.rs | 177 +++++- sdk-libs/sdk/src/instruction/pack_accounts.rs | 509 ++++++++++++++++++ sdk-libs/sdk/src/lib.rs | 14 - sdk-libs/token-sdk/Cargo.toml | 1 + sdk-libs/token-sdk/src/anchor.rs | 15 +- .../token-sdk/src/instruction/decompress.rs | 3 +- sdk-libs/token-sdk/src/pack.rs | 3 +- sdk-libs/token-sdk/tests/pack_test.rs | 2 +- .../tests/trait_tests.rs | 6 +- .../d10_token_accounts/single_ata.rs | 2 +- .../d10_token_accounts/single_ata_markonly.rs | 2 +- .../amm_observation_state_test.rs | 6 +- .../account_macros/amm_pool_state_test.rs | 6 +- .../account_macros/core_game_session_test.rs | 6 +- .../core_placeholder_record_test.rs | 6 +- .../account_macros/core_user_record_test.rs | 6 +- .../account_macros/d1_all_field_types_test.rs | 6 +- .../tests/account_macros/d1_array_test.rs | 5 +- .../account_macros/d1_multi_pubkey_test.rs | 8 +- .../tests/account_macros/d1_no_pubkey_test.rs | 5 +- .../tests/account_macros/d1_non_copy_test.rs | 2 +- .../d1_option_primitive_test.rs | 2 +- .../account_macros/d1_option_pubkey_test.rs | 6 +- .../account_macros/d1_single_pubkey_test.rs | 8 +- .../account_macros/d2_all_compress_as_test.rs | 6 +- .../d2_multiple_compress_as_test.rs | 6 +- .../account_macros/d2_no_compress_as_test.rs | 6 +- .../d2_option_none_compress_as_test.rs | 6 +- .../d2_single_compress_as_test.rs | 6 +- .../account_macros/d4_all_composition_test.rs | 6 +- .../tests/account_macros/d4_info_last_test.rs | 6 +- .../tests/account_macros/d4_large_test.rs | 2 +- .../tests/account_macros/d4_minimal_test.rs | 2 +- .../tests/account_macros/shared.rs | 11 +- .../tests/basic_test.rs | 2 +- .../tests/d11_zero_copy_test.rs | 2 +- .../tests/failing_tests.rs | 2 +- .../tests/integration_tests.rs | 4 +- .../tests/shared.rs | 8 +- .../src/instruction_accounts.rs | 2 +- sdk-tests/pinocchio-derive-test/src/lib.rs | 2 +- .../sdk-anchor-test/tests/read_only.rs | 3 +- .../programs/sdk-anchor-test/tests/test.rs | 5 +- sdk-tests/sdk-native-test/tests/test.rs | 3 +- sdk-tests/sdk-pinocchio-v1-test/tests/test.rs | 2 +- sdk-tests/sdk-pinocchio-v2-test/tests/test.rs | 2 +- sdk-tests/sdk-token-test/tests/ctoken_pda.rs | 2 +- sdk-tests/sdk-token-test/tests/pda_ctoken.rs | 2 +- .../tests/test_4_invocations.rs | 2 +- .../sdk-token-test/tests/test_4_transfer2.rs | 2 +- .../tests/test_compress_full_and_close.rs | 2 +- .../sdk-token-test/tests/test_deposit.rs | 5 +- sdk-tests/sdk-v1-native-test/tests/test.rs | 3 +- .../single-account-loader-test/tests/test.rs | 6 +- sdk-tests/single-ata-test/tests/test.rs | 2 +- sdk-tests/single-mint-test/tests/test.rs | 2 +- sdk-tests/single-pda-test/tests/test.rs | 2 +- sdk-tests/single-token-test/tests/test.rs | 2 +- xtask/src/create_compressible_config.rs | 4 +- 97 files changed, 1643 insertions(+), 450 deletions(-) create mode 100644 sdk-libs/sdk/src/cpi/account.rs create mode 100644 sdk-libs/sdk/src/cpi/instruction.rs create mode 100644 sdk-libs/sdk/src/cpi/invoke.rs create mode 100644 sdk-libs/sdk/src/cpi/v2/accounts.rs create mode 100644 sdk-libs/sdk/src/cpi/v2/accounts_cpi_context.rs create mode 100644 sdk-libs/sdk/src/instruction/pack_accounts.rs diff --git a/Cargo.lock b/Cargo.lock index cd1a74d598..eb2b30e271 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2333,6 +2333,7 @@ dependencies = [ "hex", "itertools 0.14.0", "lazy_static", + "light-account", "light-account-checks", "light-batched-merkle-tree", "light-client", @@ -3602,6 +3603,7 @@ dependencies = [ "bs58", "futures", "lazy_static", + "light-account", "light-compressed-account", "light-compressed-token-sdk", "light-compressible", @@ -3716,6 +3718,7 @@ dependencies = [ "anchor-lang", "arrayvec", "borsh 0.10.4", + "light-account", "light-account-checks", "light-compressed-account", "light-program-profiler", @@ -3987,6 +3990,7 @@ dependencies = [ "bs58", "bytemuck", "chrono", + "light-account", "light-account-checks", "light-batched-merkle-tree", "light-client", @@ -4094,7 +4098,6 @@ dependencies = [ "bytemuck", "light-account-checks", "light-compressed-account", - "light-compressible", "light-concurrent-merkle-tree", "light-hasher", "light-heap", @@ -4293,6 +4296,7 @@ dependencies = [ "anchor-lang", "arrayvec", "borsh 0.10.4", + "light-account", "light-account-checks", "light-batched-merkle-tree", "light-compressed-account", diff --git a/forester/Cargo.toml b/forester/Cargo.toml index 5b23dc2855..3b0cc6aef2 100644 --- a/forester/Cargo.toml +++ b/forester/Cargo.toml @@ -26,6 +26,7 @@ forester-utils = { workspace = true } light-client = { workspace = true, features = ["v2"] } light-merkle-tree-metadata = { workspace = true } light-sdk = { workspace = true, features = ["anchor"] } +light-account = { workspace = true } light-program-test = { workspace = true } light-compressible = { workspace = true, default-features = false, features = ["solana"] } light-token-interface = { workspace = true } diff --git a/forester/src/compressible/pda/compressor.rs b/forester/src/compressible/pda/compressor.rs index 188057544a..45c64c7fae 100644 --- a/forester/src/compressible/pda/compressor.rs +++ b/forester/src/compressible/pda/compressor.rs @@ -6,6 +6,7 @@ use std::sync::{ use borsh::BorshDeserialize; use forester_utils::rpc_pool::SolanaRpcPool; use futures::StreamExt; +use light_account::LightConfig; use light_account_checks::discriminator::DISCRIMINATOR_LEN; use light_client::{ indexer::Indexer, @@ -15,7 +16,6 @@ use light_client::{ rpc::Rpc, }; use light_compressed_account::address::derive_address; -use light_sdk::interface::config::LightConfig; use solana_sdk::{ instruction::AccountMeta, pubkey::Pubkey, @@ -80,7 +80,10 @@ impl PdaCompressor { let program_id = &program_config.program_id; // Get the compressible config PDA for this program (config_bump = 0) - let (config_pda, _) = LightConfig::derive_pda(program_id, 0); + let (config_pda, _) = Pubkey::find_program_address( + &[light_account::LIGHT_CONFIG_SEED, &0u16.to_le_bytes()], + program_id, + ); // Fetch the config to get rent_sponsor and address_space let rpc = self.rpc_pool.get_connection().await?; @@ -105,12 +108,13 @@ impl PdaCompressor { ) })?; - let rent_sponsor = config.rent_sponsor; - let compression_authority = config.compression_authority; - let address_tree = *config + let rent_sponsor: Pubkey = config.rent_sponsor.into(); + let compression_authority: Pubkey = config.compression_authority.into(); + let address_tree: Pubkey = (*config .address_space .first() - .ok_or_else(|| anyhow::anyhow!("Config has no address space"))?; + .ok_or_else(|| anyhow::anyhow!("Config has no address space"))?) + .into(); // CompressAccountsIdempotent expects 4 accounts: // 1. fee_payer (signer, writable) diff --git a/forester/src/compressible/pda/state.rs b/forester/src/compressible/pda/state.rs index 6f96bb4f20..af00bc806e 100644 --- a/forester/src/compressible/pda/state.rs +++ b/forester/src/compressible/pda/state.rs @@ -1,9 +1,9 @@ use borsh::BorshDeserialize; use dashmap::DashMap; +use light_account::CompressionInfo; use light_compressible::rent::{ get_last_funded_epoch, get_rent_exemption_lamports, SLOTS_PER_EPOCH, }; -use light_sdk::compressible::compression_info::CompressionInfo; use solana_sdk::pubkey::Pubkey; use tracing::{debug, warn}; diff --git a/program-libs/compressible/src/config.rs b/program-libs/compressible/src/config.rs index f304fd7c7f..2f406d3ddb 100644 --- a/program-libs/compressible/src/config.rs +++ b/program-libs/compressible/src/config.rs @@ -4,7 +4,7 @@ use solana_pubkey::{pubkey, Pubkey}; use crate::{error::CompressibleError, rent::RentConfig, AnchorDeserialize, AnchorSerialize}; -pub const COMPRESSIBLE_CONFIG_SEED: &[u8] = b"compressible_config"; +pub const LIGHT_CONFIG_SEED: &[u8] = b"compressible_config"; #[derive(Debug, PartialEq)] #[repr(u8)] @@ -238,10 +238,7 @@ impl CompressibleConfig { /// Derives the config PDA address with config bump pub fn derive_pda(program_id: &Pubkey, config_bump: u16) -> (Pubkey, u8) { - Pubkey::find_program_address( - &[COMPRESSIBLE_CONFIG_SEED, &config_bump.to_le_bytes()], - program_id, - ) + Pubkey::find_program_address(&[LIGHT_CONFIG_SEED, &config_bump.to_le_bytes()], program_id) } /// Derives the default config PDA address (config_bump = 1) diff --git a/program-tests/create-address-test-program/src/lib.rs b/program-tests/create-address-test-program/src/lib.rs index 1299cf3dbf..8c24f68a18 100644 --- a/program-tests/create-address-test-program/src/lib.rs +++ b/program-tests/create-address-test-program/src/lib.rs @@ -22,12 +22,12 @@ use light_compressed_account::instruction_data::{ compressed_proof::CompressedProof, data::NewAddressParamsPacked, }; use light_sdk::{ + constants::LIGHT_SYSTEM_PROGRAM_ID, cpi::{ invoke::invoke_light_system_program, v1::lowlevel::{get_account_metas_from_config, CpiInstructionConfig}, - CpiAccountsTrait, + v2::lowlevel::to_account_metas, }, - light_account_checks::CpiMeta, }; #[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, PartialEq)] pub struct CpiAccountsConfigLocal { @@ -97,38 +97,38 @@ pub mod system_cpi_test { ) -> Result<()> { let fee_payer = ctx.accounts.signer.to_account_info(); - let (account_infos, cpi_metas) = if v2_ix { + let (account_infos, account_metas) = if v2_ix { let cpi_accounts = CpiAccounts::new_with_config(&fee_payer, ctx.remaining_accounts, config.into()); let account_infos = cpi_accounts.to_account_infos(); - let cpi_metas = if !write_cpi_context { - CpiAccountsTrait::to_account_metas(&cpi_accounts) - .map_err(|_| ErrorCode::AccountNotEnoughKeys)? + let account_metas = if !write_cpi_context { + to_account_metas(&cpi_accounts).map_err(|_| ErrorCode::AccountNotEnoughKeys)? } else { require!( ctx.remaining_accounts.len() >= 3, ErrorCode::AccountNotEnoughKeys ); - vec![ - CpiMeta { - pubkey: cpi_accounts.fee_payer().key.to_bytes(), - is_signer: true, - is_writable: true, - }, - CpiMeta { - pubkey: ctx.remaining_accounts[1].key.to_bytes(), - is_signer: true, - is_writable: false, - }, - CpiMeta { - pubkey: ctx.remaining_accounts[2].key.to_bytes(), - is_signer: false, - is_writable: true, - }, - ] + let mut account_metas = vec![]; + account_metas.push(AccountMeta { + pubkey: *cpi_accounts.fee_payer().key, + is_signer: true, + is_writable: true, + }); + account_metas.push(AccountMeta { + pubkey: *ctx.remaining_accounts[1].key, + is_signer: true, + is_writable: false, + }); + let account = &ctx.remaining_accounts[2]; + account_metas.push(AccountMeta { + pubkey: *account.key, + is_signer: false, + is_writable: true, + }); + account_metas }; - (account_infos, cpi_metas) + (account_infos, account_metas) } else { use light_sdk::cpi::v1::CpiAccounts; let cpi_accounts = @@ -139,19 +139,15 @@ pub mod system_cpi_test { let config = CpiInstructionConfig::try_from(&cpi_accounts) .map_err(|_| ErrorCode::AccountNotEnoughKeys)?; let account_metas = get_account_metas_from_config(config); - let cpi_metas: Vec = account_metas - .into_iter() - .map(|m| CpiMeta { - pubkey: m.pubkey.to_bytes(), - is_signer: m.is_signer, - is_writable: m.is_writable, - }) - .collect(); - (account_infos, cpi_metas) + (account_infos, account_metas) + }; + let instruction = Instruction { + program_id: LIGHT_SYSTEM_PROGRAM_ID.into(), + accounts: account_metas, + data: inputs, }; let cpi_config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); - invoke_light_system_program(&account_infos, &cpi_metas, &inputs, cpi_config.bump()) - .map_err(|_| anchor_lang::error::ErrorCode::AccountNotEnoughKeys)?; + invoke_light_system_program(&account_infos, instruction, cpi_config.bump())?; Ok(()) } diff --git a/program-tests/system-cpi-test/tests/test_cpi_context_event.rs b/program-tests/system-cpi-test/tests/test_cpi_context_event.rs index 5dcbc69457..c9e7d67c45 100644 --- a/program-tests/system-cpi-test/tests/test_cpi_context_event.rs +++ b/program-tests/system-cpi-test/tests/test_cpi_context_event.rs @@ -2,7 +2,7 @@ use anchor_lang::{InstructionData, ToAccountMetas}; use light_program_test::{program_test::LightProgramTest, Indexer, ProgramTestConfig, Rpc}; -use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; +use light_sdk::instruction::{PackedAccounts, PackedAccountsExt, SystemAccountMetaConfig}; use serial_test::serial; use solana_sdk::{ instruction::Instruction, diff --git a/programs/registry/src/compressible/create_config.rs b/programs/registry/src/compressible/create_config.rs index 2da160c9c9..581efa69e8 100644 --- a/programs/registry/src/compressible/create_config.rs +++ b/programs/registry/src/compressible/create_config.rs @@ -1,5 +1,5 @@ use anchor_lang::prelude::*; -use light_compressible::config::{CompressibleConfig, COMPRESSIBLE_CONFIG_SEED}; +use light_compressible::config::{CompressibleConfig, LIGHT_CONFIG_SEED}; /// Context for creating a compressible config #[derive(Accounts)] @@ -20,7 +20,7 @@ pub struct CreateCompressibleConfig<'info> { #[account( init, - seeds = [COMPRESSIBLE_CONFIG_SEED, &config_counter.counter.to_le_bytes()], + seeds = [LIGHT_CONFIG_SEED, &config_counter.counter.to_le_bytes()], bump, space = 8 + std::mem::size_of::(), payer = fee_payer, diff --git a/sdk-libs/account-pinocchio/src/lib.rs b/sdk-libs/account-pinocchio/src/lib.rs index 121aa638db..daf4a9696a 100644 --- a/sdk-libs/account-pinocchio/src/lib.rs +++ b/sdk-libs/account-pinocchio/src/lib.rs @@ -92,7 +92,7 @@ pub use light_sdk_types::interface::{ config::{ create::process_initialize_light_config, process_initialize_light_config_checked, process_update_light_config, InitializeLightConfigParams, LightConfig, - UpdateLightConfigParams, COMPRESSIBLE_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE, + UpdateLightConfigParams, LIGHT_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE, }, decompression::{ pda::prepare_account_for_decompression, diff --git a/sdk-libs/account/src/lib.rs b/sdk-libs/account/src/lib.rs index 9bef736e59..f9544786c5 100644 --- a/sdk-libs/account/src/lib.rs +++ b/sdk-libs/account/src/lib.rs @@ -97,8 +97,8 @@ pub use light_sdk_types::interface::{ }, config::{ process_initialize_light_config_checked, process_update_light_config, - InitializeLightConfigParams, LightConfig, UpdateLightConfigParams, - COMPRESSIBLE_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE, + InitializeLightConfigParams, LightConfig, UpdateLightConfigParams, LIGHT_CONFIG_SEED, + MAX_ADDRESS_TREES_PER_SPACE, }, decompression::{ pda::prepare_account_for_decompression, @@ -125,7 +125,7 @@ pub use light_token_interface::instructions::extensions::TokenMetadataInstructio pub use light_token_interface::state::AdditionalMetadata; /// Re-export Token state struct for client-side use. #[cfg(feature = "token")] -pub use light_token_interface::state::Token; +pub use light_token_interface::state::{AccountState, Token}; /// Token sub-module for paths like `light_account::token::TokenDataWithSeeds`. #[cfg(feature = "token")] @@ -137,6 +137,7 @@ pub mod token { }, program::decompression::token::prepare_token_account_for_decompression, }; + pub use light_token_interface::state::{AccountState, Token}; } /// Compression info sub-module for paths like `light_account::compression_info::CompressedInitSpace`. diff --git a/sdk-libs/client/Cargo.toml b/sdk-libs/client/Cargo.toml index 3ee84cc237..2f1bcfac3e 100644 --- a/sdk-libs/client/Cargo.toml +++ b/sdk-libs/client/Cargo.toml @@ -45,6 +45,7 @@ light-merkle-tree-metadata = { workspace = true, features = ["solana"] } light-concurrent-merkle-tree = { workspace = true } light-indexed-merkle-tree = { workspace = true } light-sdk = { workspace = true, features = ["v2", "cpi-context"] } +light-account = { workspace = true } light-hasher = { workspace = true, features = ["poseidon"] } light-compressed-account = { workspace = true, features = ["solana", "poseidon"] } light-token = { workspace = true, features = ["cpi-context"] } diff --git a/sdk-libs/client/src/interface/initialize_config.rs b/sdk-libs/client/src/interface/initialize_config.rs index 7b038dc4eb..7b5919cdb1 100644 --- a/sdk-libs/client/src/interface/initialize_config.rs +++ b/sdk-libs/client/src/interface/initialize_config.rs @@ -90,7 +90,7 @@ impl InitializeRentFreeConfig { let config_bump_u16 = self.config_bump as u16; let (config_pda, _) = Pubkey::find_program_address( &[ - light_sdk::COMPRESSIBLE_CONFIG_SEED, + light_account::LIGHT_CONFIG_SEED, &config_bump_u16.to_le_bytes(), ], &self.program_id, diff --git a/sdk-libs/client/src/interface/instructions.rs b/sdk-libs/client/src/interface/instructions.rs index 5113534963..8838945187 100644 --- a/sdk-libs/client/src/interface/instructions.rs +++ b/sdk-libs/client/src/interface/instructions.rs @@ -4,13 +4,16 @@ use anchor_lang::{AnchorDeserialize, AnchorSerialize}; #[cfg(not(feature = "anchor"))] use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; +use light_account::{ + CompressedAccountData, InitializeLightConfigParams, Pack, PackedAccounts, + UpdateLightConfigParams, +}; use light_sdk::{ instruction::{ - account_meta::CompressedAccountMetaNoLamportsNoAddress, PackedAccounts, - SystemAccountMetaConfig, ValidityProof, + account_meta::CompressedAccountMetaNoLamportsNoAddress, SystemAccountMetaConfig, + ValidityProof, }, - CompressedAccountData, InitializeLightConfigParams, Pack, PackedAccountsExt, - UpdateLightConfigParams, + PackedAccountsExt, }; use light_token::constants::{ LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_CPI_AUTHORITY, LIGHT_TOKEN_PROGRAM_ID, @@ -104,7 +107,7 @@ pub fn initialize_config( let config_bump_u16 = config_bump as u16; let (config_pda, _) = Pubkey::find_program_address( &[ - light_sdk::COMPRESSIBLE_CONFIG_SEED, + light_account::LIGHT_CONFIG_SEED, &config_bump_u16.to_le_bytes(), ], program_id, @@ -153,7 +156,7 @@ pub fn update_config( new_update_authority: Option, ) -> Instruction { let (config_pda, _) = Pubkey::find_program_address( - &[light_sdk::COMPRESSIBLE_CONFIG_SEED, &0u16.to_le_bytes()], + &[light_account::LIGHT_CONFIG_SEED, &0u16.to_le_bytes()], program_id, ); diff --git a/sdk-libs/client/src/interface/light_program_interface.rs b/sdk-libs/client/src/interface/light_program_interface.rs index 9abe63a033..a1fa25ab0a 100644 --- a/sdk-libs/client/src/interface/light_program_interface.rs +++ b/sdk-libs/client/src/interface/light_program_interface.rs @@ -8,7 +8,7 @@ use std::fmt::Debug; -use light_sdk::interface::Pack; +use light_account::Pack; use light_token::instruction::derive_token_ata; use solana_pubkey::Pubkey; diff --git a/sdk-libs/client/src/interface/load_accounts.rs b/sdk-libs/client/src/interface/load_accounts.rs index 8aad58b249..fcddcaab55 100644 --- a/sdk-libs/client/src/interface/load_accounts.rs +++ b/sdk-libs/client/src/interface/load_accounts.rs @@ -1,5 +1,6 @@ //! Load cold accounts API. +use light_account::{derive_rent_sponsor_pda, Pack, PackedAccounts}; use light_compressed_account::{ compressed_account::PackedMerkleContext, instruction_data::compressed_proof::ValidityProof, }; @@ -9,7 +10,6 @@ use light_compressed_token_sdk::compressed_token::{ }, CTokenAccount2, }; -use light_sdk::{compressible::Pack, instruction::PackedAccounts, utils::derive_rent_sponsor_pda}; use light_token::{ compat::AccountState, instruction::{ diff --git a/sdk-libs/client/src/interface/mod.rs b/sdk-libs/client/src/interface/mod.rs index 51947a6d7b..d3b5bd730c 100644 --- a/sdk-libs/client/src/interface/mod.rs +++ b/sdk-libs/client/src/interface/mod.rs @@ -21,11 +21,11 @@ pub use decompress_mint::{ DecompressMintError, MintInterface, MintState, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, }; pub use initialize_config::InitializeRentFreeConfig; +pub use light_account::LightConfig; pub use light_program_interface::{ all_hot, any_cold, discriminator, matches_discriminator, AccountSpec, AccountToFetch, ColdContext, LightProgramInterface, PdaSpec, }; -pub use light_sdk::LightConfig; pub use light_sdk_types::interface::CreateAccountsProof; pub use light_token::compat::TokenData; pub use load_accounts::{create_load_instructions, LoadAccountsError}; diff --git a/sdk-libs/compressed-token-sdk/Cargo.toml b/sdk-libs/compressed-token-sdk/Cargo.toml index af5bb0d9fd..d34acc0ee4 100644 --- a/sdk-libs/compressed-token-sdk/Cargo.toml +++ b/sdk-libs/compressed-token-sdk/Cargo.toml @@ -32,6 +32,7 @@ light-token-types = { workspace = true } light-compressed-account = { workspace = true, features = ["std", "solana"] } light-token-interface = { workspace = true } light-sdk = { workspace = true, features = ["v2"] } +light-account = { workspace = true } light-sdk-types = { workspace = true, features = ["v2"] } light-account-checks = { workspace = true, features = ["solana"] } light-zero-copy = { workspace = true } diff --git a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs index 380b48ed27..75196f490a 100644 --- a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs +++ b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs @@ -1,13 +1,16 @@ +use light_account::Unpack; +// Pack and PackedAccounts only available off-chain (client-side) +#[cfg(not(target_os = "solana"))] +use light_account::{Pack, PackedAccounts}; #[cfg(not(target_os = "solana"))] use light_compressed_account::compressed_account::PackedMerkleContext; use light_compressed_account::instruction_data::compressed_proof::ValidityProof; use light_program_profiler::profile; -use light_sdk::{instruction::PackedStateTreeInfo, Unpack}; -// Pack and PackedAccounts only available off-chain (client-side) +use light_sdk::instruction::PackedStateTreeInfo; #[cfg(not(target_os = "solana"))] use light_sdk::{ - instruction::{AccountMetasVec, PackedAccounts, SystemAccountMetaConfig}, - Pack, PackedAccountsExt, + instruction::{AccountMetasVec, SystemAccountMetaConfig}, + PackedAccountsExt, }; use light_sdk_types::error::LightSdkTypesError; use light_token_interface::instructions::{ diff --git a/sdk-libs/macros/src/lib.rs b/sdk-libs/macros/src/lib.rs index c26ce1238d..9daa62d9ca 100644 --- a/sdk-libs/macros/src/lib.rs +++ b/sdk-libs/macros/src/lib.rs @@ -310,7 +310,7 @@ pub fn compressible_derive(input: TokenStream) -> TokenStream { /// /// ```ignore /// use light_sdk_macros::{LightAccount, LightDiscriminator, LightHasherSha}; -/// use light_sdk::compressible::CompressionInfo; +/// use light_account::CompressionInfo; /// use solana_pubkey::Pubkey; /// /// #[derive(Default, Debug, InitSpace, LightAccount, LightDiscriminator, LightHasherSha)] diff --git a/sdk-libs/program-test/Cargo.toml b/sdk-libs/program-test/Cargo.toml index 6f1f5524e6..36d357473b 100644 --- a/sdk-libs/program-test/Cargo.toml +++ b/sdk-libs/program-test/Cargo.toml @@ -12,6 +12,7 @@ devenv = ["v2", "light-client/devenv", "light-prover-client/devenv", "dep:accoun [dependencies] light-sdk = { workspace = true, features = ["anchor"] } +light-account = { workspace = true } light-account-checks = { workspace = true } light-indexed-merkle-tree = { workspace = true, features = ["solana"] } light-indexed-array = { workspace = true } diff --git a/sdk-libs/program-test/src/compressible.rs b/sdk-libs/program-test/src/compressible.rs index 31c20cfe1d..00138b5d7b 100644 --- a/sdk-libs/program-test/src/compressible.rs +++ b/sdk-libs/program-test/src/compressible.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use borsh::BorshDeserialize; +use light_account::LightConfig; use light_account_checks::discriminator::DISCRIMINATOR_LEN; use light_client::rpc::{Rpc, RpcError}; use light_compressible::{ @@ -8,7 +9,6 @@ use light_compressible::{ config::CompressibleConfig as CtokenCompressibleConfig, rent::{RentConfig, SLOTS_PER_EPOCH}, }; -use light_sdk::interface::LightConfig; use light_token_interface::{ state::{Mint, Token, ACCOUNT_TYPE_MINT, ACCOUNT_TYPE_TOKEN_ACCOUNT}, LIGHT_TOKEN_PROGRAM_ID, @@ -272,7 +272,7 @@ pub async fn auto_compress_program_pdas( let payer = rpc.get_payer().insecure_clone(); let (config_pda, _) = Pubkey::find_program_address( - &[light_sdk::COMPRESSIBLE_CONFIG_SEED, &0u16.to_le_bytes()], + &[light_account::LIGHT_CONFIG_SEED, &0u16.to_le_bytes()], &program_id, ); diff --git a/sdk-libs/program-test/src/forester/compress_and_close_forester.rs b/sdk-libs/program-test/src/forester/compress_and_close_forester.rs index 1ac543bc3d..15ab7c5889 100644 --- a/sdk-libs/program-test/src/forester/compress_and_close_forester.rs +++ b/sdk-libs/program-test/src/forester/compress_and_close_forester.rs @@ -1,10 +1,10 @@ +use light_account::PackedAccounts; use light_client::{ indexer::Indexer, rpc::{Rpc, RpcError}, }; use light_compressed_token_sdk::compressed_token::CompressAndCloseAccounts as CTokenCompressAndCloseAccounts; use light_compressible::config::CompressibleConfig; -use light_sdk::instruction::PackedAccounts; use solana_sdk::{ pubkey::Pubkey, signature::{Keypair, Signature}, diff --git a/sdk-libs/program-test/src/program_test/light_program_test.rs b/sdk-libs/program-test/src/program_test/light_program_test.rs index 4ddf60d021..1149e8804a 100644 --- a/sdk-libs/program-test/src/program_test/light_program_test.rs +++ b/sdk-libs/program-test/src/program_test/light_program_test.rs @@ -166,7 +166,7 @@ impl LightProgramTest { context.auto_mine_cold_state_programs.push(pid); } // Airdrop to program's rent sponsor PDA for decompression - let (rent_sponsor, _) = light_sdk::utils::derive_rent_sponsor_pda(&pid); + let (rent_sponsor, _) = light_account::derive_rent_sponsor_pda(&pid); context .context .airdrop(&rent_sponsor, 100_000_000_000) diff --git a/sdk-libs/sdk-types/Cargo.toml b/sdk-libs/sdk-types/Cargo.toml index 1cbc0f985e..25bd67ac6e 100644 --- a/sdk-libs/sdk-types/Cargo.toml +++ b/sdk-libs/sdk-types/Cargo.toml @@ -13,8 +13,8 @@ alloc = ["light-compressed-account/alloc"] keccak = ["light-hasher/keccak"] sha256 = ["light-hasher/sha256", "light-compressed-account/sha256"] token = ["dep:light-token-interface"] -anchor = ["anchor-lang", "light-compressed-account/anchor", "solana-program-error"] -idl-build = ["anchor-lang/idl-build", "anchor"] +anchor = ["anchor-lang", "light-compressed-account/anchor", "light-compressible/anchor", "solana-program-error"] +idl-build = ["anchor-lang/idl-build", "light-compressible/idl-build", "anchor"] v2 = [] cpi-context = [] poseidon = ["light-hasher/poseidon", "light-compressed-account/poseidon"] diff --git a/sdk-libs/sdk-types/src/interface/cpi/create_mints.rs b/sdk-libs/sdk-types/src/interface/cpi/create_mints.rs index 4f65b1a4a2..6a6cd38f8c 100644 --- a/sdk-libs/sdk-types/src/interface/cpi/create_mints.rs +++ b/sdk-libs/sdk-types/src/interface/cpi/create_mints.rs @@ -379,7 +379,7 @@ impl<'a, AI: AccountInfoTrait + Clone> CreateMintsCpi<'a, AI> { build_mint_instruction_data(mint_params, &self.mint_seed_accounts[index].key()); let instruction_data = MintActionCompressedInstructionData { - leaf_index: base_leaf_index + index as u32, + leaf_index: base_leaf_index + self.params.cpi_context_offset as u32 + index as u32, prove_by_index: true, root_index: 0, max_top_up: 0, diff --git a/sdk-libs/sdk-types/src/interface/mod.rs b/sdk-libs/sdk-types/src/interface/mod.rs index b80dec9f3b..255e19e680 100644 --- a/sdk-libs/sdk-types/src/interface/mod.rs +++ b/sdk-libs/sdk-types/src/interface/mod.rs @@ -63,8 +63,8 @@ pub use program::{ }, config::{ process_initialize_light_config_checked, process_update_light_config, - InitializeLightConfigParams, LightConfig, UpdateLightConfigParams, - COMPRESSIBLE_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE, + InitializeLightConfigParams, LightConfig, UpdateLightConfigParams, LIGHT_CONFIG_SEED, + MAX_ADDRESS_TREES_PER_SPACE, }, decompression::{ pda::prepare_account_for_decompression, diff --git a/sdk-libs/sdk-types/src/interface/program/config/create.rs b/sdk-libs/sdk-types/src/interface/program/config/create.rs index a26c797101..bdf347e8fe 100644 --- a/sdk-libs/sdk-types/src/interface/program/config/create.rs +++ b/sdk-libs/sdk-types/src/interface/program/config/create.rs @@ -9,7 +9,7 @@ use light_account_checks::{ }; use light_compressible::rent::RentConfig; -use super::{state::LightConfig, validate_address_space_no_duplicates, COMPRESSIBLE_CONFIG_SEED}; +use super::{state::LightConfig, validate_address_space_no_duplicates, LIGHT_CONFIG_SEED}; use crate::{error::LightSdkTypesError, AnchorSerialize}; /// BPFLoaderUpgradeab1e11111111111111111111111 as raw bytes. @@ -85,11 +85,7 @@ pub fn process_initialize_light_config( // Create PDA using AccountInfoTrait let config_bump_bytes = (config_bump as u16).to_le_bytes(); - let seeds: &[&[u8]] = &[ - COMPRESSIBLE_CONFIG_SEED, - config_bump_bytes.as_ref(), - &[bump], - ]; + let seeds: &[&[u8]] = &[LIGHT_CONFIG_SEED, config_bump_bytes.as_ref(), &[bump]]; config_account.create_pda_account( rent_lamports, diff --git a/sdk-libs/sdk-types/src/interface/program/config/mod.rs b/sdk-libs/sdk-types/src/interface/program/config/mod.rs index e6cb7f6124..17fb18f214 100644 --- a/sdk-libs/sdk-types/src/interface/program/config/mod.rs +++ b/sdk-libs/sdk-types/src/interface/program/config/mod.rs @@ -13,7 +13,7 @@ pub mod update; // --- Constants --- -pub const COMPRESSIBLE_CONFIG_SEED: &[u8] = b"compressible_config"; +pub const LIGHT_CONFIG_SEED: &[u8] = b"compressible_config"; pub const MAX_ADDRESS_TREES_PER_SPACE: usize = 1; // --- Re-exports --- diff --git a/sdk-libs/sdk-types/src/interface/program/config/state.rs b/sdk-libs/sdk-types/src/interface/program/config/state.rs index 61d413246d..5a3c5c42ba 100644 --- a/sdk-libs/sdk-types/src/interface/program/config/state.rs +++ b/sdk-libs/sdk-types/src/interface/program/config/state.rs @@ -9,7 +9,7 @@ use light_account_checks::{ }; use light_compressible::rent::RentConfig; -use super::{COMPRESSIBLE_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE}; +use super::{LIGHT_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE}; use crate::{error::LightSdkTypesError, AnchorDeserialize, AnchorSerialize}; /// Global configuration for compressible accounts @@ -83,7 +83,7 @@ impl LightConfig { ) -> ([u8; 32], u8) { let config_bump_u16 = config_bump as u16; AI::find_program_address( - &[COMPRESSIBLE_CONFIG_SEED, &config_bump_u16.to_le_bytes()], + &[LIGHT_CONFIG_SEED, &config_bump_u16.to_le_bytes()], program_id, ) } @@ -148,7 +148,7 @@ impl LightConfig { // CHECK: PDA derivation let (expected_pda, _) = AI::find_program_address( &[ - COMPRESSIBLE_CONFIG_SEED, + LIGHT_CONFIG_SEED, &(config.config_bump as u16).to_le_bytes(), ], program_id, diff --git a/sdk-libs/sdk/Cargo.toml b/sdk-libs/sdk/Cargo.toml index 23d813f6df..afac011961 100644 --- a/sdk-libs/sdk/Cargo.toml +++ b/sdk-libs/sdk/Cargo.toml @@ -16,7 +16,6 @@ idl-build = [ "anchor-lang/idl-build", "light-compressed-account/idl-build", "light-sdk-types/idl-build", - "light-compressible/idl-build", "light-token-interface/idl-build", "anchor", "dep:solana-program" @@ -25,7 +24,6 @@ anchor = [ "anchor-lang", "light-compressed-account/anchor", "light-sdk-types/anchor", - "light-compressible/anchor", "light-token-interface/anchor", ] v2 = ["light-sdk-types/v2"] @@ -72,7 +70,6 @@ light-hasher = { workspace = true, features = ["std"] } light-account-checks = { workspace = true, features = ["solana"] } light-zero-copy = { workspace = true } light-concurrent-merkle-tree = { workspace = true, optional = true } -light-compressible = { workspace = true } light-heap = { workspace = true, optional = true } light-token-interface = { workspace = true } # TODO: make optional diff --git a/sdk-libs/sdk/src/cpi/account.rs b/sdk-libs/sdk/src/cpi/account.rs new file mode 100644 index 0000000000..b535cc2b2f --- /dev/null +++ b/sdk-libs/sdk/src/cpi/account.rs @@ -0,0 +1,88 @@ +#[cfg(all(feature = "v2", feature = "cpi-context"))] +use light_sdk_types::cpi_context_write::CpiContextWriteAccounts; + +#[cfg(all(feature = "v2", feature = "cpi-context"))] +use crate::cpi::v2::get_account_metas_from_config_cpi_context; +use crate::{ + cpi::v1::{ + lowlevel::{get_account_metas_from_config, CpiInstructionConfig}, + CpiAccounts, + }, + AccountInfo, AccountMeta, ProgramError, +}; + +/// Trait for types that can provide account information for CPI calls +pub trait CpiAccountsTrait<'info> { + /// Convert to a vector of AccountInfo references + fn to_account_infos(&self) -> Vec>; + + /// Generate account metas + fn to_account_metas(&self) -> Result, ProgramError>; + + /// Get the mode for the instruction (0 for v1, 1 for v2, None if unknown) + fn get_mode(&self) -> Option; +} + +// Implementation for CpiAccounts +impl<'info> CpiAccountsTrait<'info> for CpiAccounts<'_, 'info> { + fn to_account_infos(&self) -> Vec> { + self.to_account_infos() + } + + fn to_account_metas(&self) -> Result, ProgramError> { + let config = CpiInstructionConfig::try_from(self).map_err(ProgramError::from)?; + Ok(get_account_metas_from_config(config)) + } + + fn get_mode(&self) -> Option { + Some(0) // v1 mode + } +} + +// Implementation for &[AccountInfo] +impl<'info> CpiAccountsTrait<'info> for &[AccountInfo<'info>] { + fn to_account_infos(&self) -> Vec> { + self.to_vec() + } + + fn to_account_metas(&self) -> Result, ProgramError> { + // For raw account info slices, create simple account metas + // preserving the original signer and writable flags + Ok(self + .iter() + .map(|account| AccountMeta { + pubkey: *account.key, + is_signer: account.is_signer, + is_writable: account.is_writable, + }) + .collect()) + } + + fn get_mode(&self) -> Option { + None // Unknown mode for raw slices + } +} + +// Implementation for CpiContextWriteAccounts +#[cfg(all(feature = "v2", feature = "cpi-context"))] +impl<'a, 'info> CpiAccountsTrait<'info> for CpiContextWriteAccounts<'a, AccountInfo<'info>> { + fn to_account_infos(&self) -> Vec> { + vec![ + self.fee_payer.clone(), + self.authority.clone(), + self.cpi_context.clone(), + ] + } + + fn to_account_metas(&self) -> Result, ProgramError> { + // Use the helper function to generate the account metas + let metas = get_account_metas_from_config_cpi_context(self.clone()); + Ok(metas.to_vec()) + } + + fn get_mode(&self) -> Option { + // CPI context write accounts always use v2 mode (1) + // This type requires both the `v2` and `cpi-context` features + Some(1) + } +} diff --git a/sdk-libs/sdk/src/cpi/instruction.rs b/sdk-libs/sdk/src/cpi/instruction.rs new file mode 100644 index 0000000000..d284d747dd --- /dev/null +++ b/sdk-libs/sdk/src/cpi/instruction.rs @@ -0,0 +1,104 @@ +use light_compressed_account::instruction_data::compressed_proof::ValidityProof; + +#[cfg(feature = "poseidon")] +use crate::DataHasher; +use crate::{ + account::LightAccount, AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError, +}; + +/// Trait for Light CPI instruction types +pub trait LightCpiInstruction: Sized { + /// Creates a new CPI instruction builder with a validity proof. + /// + /// # Arguments + /// * `cpi_signer` - The CPI signer containing program ID and bump seed + /// * `proof` - Validity proof for compressed account operations + fn new_cpi(cpi_signer: crate::cpi::CpiSigner, proof: ValidityProof) -> Self; + + /// Adds a compressed account to the instruction (using SHA256 hashing). + /// + /// The account can be an input (for updating/closing), output (for creating/updating), + /// or both. The method automatically handles the conversion based on the account state. + /// + /// # Arguments + /// * `account` - The light account to add to the instruction + /// + /// # Type Parameters + /// * `A` - The compressed account data type + #[must_use = "with_light_account returns a new value"] + fn with_light_account(self, account: LightAccount) -> Result + where + A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default; + + /// Adds a compressed account to the instruction (using Poseidon hashing). + /// + /// Similar to [`with_light_account`](Self::with_light_account), but uses Poseidon hashing + /// instead of SHA256. Use this when your compressed account data implements [`DataHasher`]. + /// + /// # Arguments + /// * `account` - The light account to add to the instruction + /// + /// # Type Parameters + /// * `A` - The compressed account data type that implements DataHasher + #[cfg(feature = "poseidon")] + #[must_use = "with_light_account_poseidon returns a new value"] + fn with_light_account_poseidon( + self, + account: crate::account::poseidon::LightAccount, + ) -> Result + where + A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + DataHasher + Default; + + /// Returns the instruction mode (0 for v1, 1 for v2). + fn get_mode(&self) -> u8; + + /// Returns the CPI signer bump seed. + fn get_bump(&self) -> u8; + + /// Writes instruction to CPI context as the first operation in a batch. + /// + /// # Availability + /// Only available with the `cpi-context` feature enabled. + #[cfg(feature = "cpi-context")] + #[must_use = "write_to_cpi_context_first returns a new value"] + fn write_to_cpi_context_first(self) -> Self; + + /// Writes instruction to CPI context as a subsequent operation in a batch. + /// + /// # Availability + /// Only available with the `cpi-context` feature enabled. + #[cfg(feature = "cpi-context")] + #[must_use = "write_to_cpi_context_set returns a new value"] + fn write_to_cpi_context_set(self) -> Self; + + /// Executes all operations accumulated in CPI context. + /// + /// # Availability + /// Only available with the `cpi-context` feature enabled. + #[cfg(feature = "cpi-context")] + #[must_use = "execute_with_cpi_context returns a new value"] + fn execute_with_cpi_context(self) -> Self; + + /// Returns whether this instruction uses CPI context. + /// + /// # Availability + /// Only available with the `cpi-context` feature enabled. + #[cfg(feature = "cpi-context")] + fn get_with_cpi_context(&self) -> bool; + + /// Returns the CPI context configuration. + /// + /// # Availability + /// Only available with the `cpi-context` feature enabled. + #[cfg(feature = "cpi-context")] + fn get_cpi_context( + &self, + ) -> &light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; + + /// Returns whether this instruction has any read-only accounts. + /// + /// # Availability + /// Only available with the `cpi-context` feature enabled. + #[cfg(feature = "cpi-context")] + fn has_read_only_accounts(&self) -> bool; +} diff --git a/sdk-libs/sdk/src/cpi/invoke.rs b/sdk-libs/sdk/src/cpi/invoke.rs new file mode 100644 index 0000000000..b7fa7c76af --- /dev/null +++ b/sdk-libs/sdk/src/cpi/invoke.rs @@ -0,0 +1,202 @@ +pub use light_compressed_account::LightInstructionData; +use light_sdk_types::constants::{CPI_AUTHORITY_PDA_SEED, LIGHT_SYSTEM_PROGRAM_ID}; + +#[cfg(feature = "cpi-context")] +use solana_instruction::AccountMeta; +use crate::{ + cpi::{account::CpiAccountsTrait, instruction::LightCpiInstruction}, + error::LightSdkError, +}; +use solana_account_info::AccountInfo; +use solana_cpi::invoke_signed; +use solana_instruction::Instruction; +use solana_program_error::ProgramError; + +pub trait InvokeLightSystemProgram { + fn invoke<'info>(self, accounts: impl CpiAccountsTrait<'info>) -> Result<(), ProgramError>; + + #[cfg(feature = "cpi-context")] + fn invoke_write_to_cpi_context_first<'info>( + self, + accounts: impl CpiAccountsTrait<'info>, + ) -> Result<(), ProgramError>; + + #[cfg(feature = "cpi-context")] + fn invoke_write_to_cpi_context_set<'info>( + self, + accounts: impl CpiAccountsTrait<'info>, + ) -> Result<(), ProgramError>; + + #[cfg(feature = "cpi-context")] + fn invoke_execute_cpi_context<'info>( + self, + accounts: impl CpiAccountsTrait<'info>, + ) -> Result<(), ProgramError>; +} + +// Blanket implementation for types that implement both LightInstructionData and LightCpiInstruction +impl InvokeLightSystemProgram for T +where + T: LightInstructionData + LightCpiInstruction, +{ + fn invoke<'info>(self, accounts: impl CpiAccountsTrait<'info>) -> Result<(), ProgramError> { + #[cfg(feature = "cpi-context")] + { + // Check if CPI context operations are being attempted + use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; + if self.get_with_cpi_context() + || *self.get_cpi_context() == CompressedCpiContext::set() + || *self.get_cpi_context() == CompressedCpiContext::first() + { + solana_msg::msg!( + "CPI context operations not supported in invoke(). Use invoke_write_to_cpi_context_first(), invoke_write_to_cpi_context_set(), or invoke_execute_cpi_context() instead" + ); + return Err(ProgramError::InvalidInstructionData); + } + } + + // Validate mode consistency + if let Some(account_mode) = accounts.get_mode() { + if account_mode != self.get_mode() { + solana_msg::msg!( + "Mode mismatch: accounts have mode {} but instruction data has mode {}", + account_mode, + self.get_mode() + ); + return Err(ProgramError::InvalidInstructionData); + } + } + + // Serialize instruction data with discriminator + let data = self + .data() + .map_err(LightSdkError::from) + .map_err(ProgramError::from)?; + + // Get account infos and metas + let account_infos = accounts.to_account_infos(); + let account_metas = accounts.to_account_metas()?; + + let instruction = Instruction { + program_id: LIGHT_SYSTEM_PROGRAM_ID.into(), + accounts: account_metas, + data, + }; + invoke_light_system_program(&account_infos, instruction, self.get_bump()) + } + + #[cfg(feature = "cpi-context")] + fn invoke_write_to_cpi_context_first<'info>( + self, + accounts: impl CpiAccountsTrait<'info>, + ) -> Result<(), ProgramError> { + let instruction_data = self.write_to_cpi_context_first(); + inner_invoke_write_to_cpi_context_typed(instruction_data, accounts) + } + + #[cfg(feature = "cpi-context")] + fn invoke_write_to_cpi_context_set<'info>( + self, + accounts: impl CpiAccountsTrait<'info>, + ) -> Result<(), ProgramError> { + let instruction_data = self.write_to_cpi_context_set(); + inner_invoke_write_to_cpi_context_typed(instruction_data, accounts) + } + + #[cfg(feature = "cpi-context")] + fn invoke_execute_cpi_context<'info>( + self, + accounts: impl CpiAccountsTrait<'info>, + ) -> Result<(), ProgramError> { + let instruction_data = self.execute_with_cpi_context(); + // Serialize instruction data with discriminator + let data = instruction_data + .data() + .map_err(LightSdkError::from) + .map_err(ProgramError::from)?; + + // Get account infos and metas + let account_infos = accounts.to_account_infos(); + let account_metas = accounts.to_account_metas()?; + + let instruction = Instruction { + program_id: LIGHT_SYSTEM_PROGRAM_ID.into(), + accounts: account_metas, + data, + }; + invoke_light_system_program(&account_infos, instruction, instruction_data.get_bump()) + } +} + +// Generic inner helper for write_to_cpi_context operations +#[cfg(feature = "cpi-context")] +#[inline(always)] +fn inner_invoke_write_to_cpi_context_typed<'info, T>( + instruction_data: T, + accounts: impl CpiAccountsTrait<'info>, +) -> Result<(), ProgramError> +where + T: LightInstructionData + LightCpiInstruction, +{ + // Check if read-only accounts are present + if instruction_data.has_read_only_accounts() { + solana_msg::msg!( + "Read-only accounts are not supported in write_to_cpi_context operations. Use invoke_execute_cpi_context() instead." + ); + return Err(LightSdkError::ReadOnlyAccountsNotSupportedInCpiContext.into()); + } + + // Serialize instruction data with discriminator + let data = instruction_data + .data() + .map_err(LightSdkError::from) + .map_err(ProgramError::from)?; + + // Get account infos and metas + let account_infos = accounts.to_account_infos(); + + // Extract account pubkeys from account_infos + // Assuming order: [fee_payer, authority, cpi_context, ...] + if account_infos.len() < 3 { + return Err(ProgramError::NotEnoughAccountKeys); + } + + let instruction = Instruction { + program_id: LIGHT_SYSTEM_PROGRAM_ID.into(), + accounts: vec![ + AccountMeta { + pubkey: *account_infos[0].key, // fee_payer + is_writable: true, + is_signer: true, + }, + AccountMeta { + pubkey: *account_infos[1].key, // authority + is_writable: false, + is_signer: true, + }, + AccountMeta { + pubkey: *account_infos[2].key, // cpi_context + is_writable: true, + is_signer: false, + }, + ], + data, + }; + + invoke_light_system_program(&account_infos, instruction, instruction_data.get_bump()) +} + +/// Low-level function to invoke the Light system program with a PDA signer. +/// +/// **Note**: This is a low-level function. In most cases, you should use the +/// [`InvokeLightSystemProgram`] trait methods instead, which provide a higher-level +/// interface with better type safety and ergonomics. +#[inline(always)] +pub fn invoke_light_system_program( + account_infos: &[AccountInfo], + instruction: Instruction, + bump: u8, +) -> Result<(), ProgramError> { + let signer_seeds = [CPI_AUTHORITY_PDA_SEED, &[bump]]; + invoke_signed(&instruction, account_infos, &[signer_seeds.as_slice()]) +} diff --git a/sdk-libs/sdk/src/cpi/mod.rs b/sdk-libs/sdk/src/cpi/mod.rs index bb9b055398..f3a2d6ddc1 100644 --- a/sdk-libs/sdk/src/cpi/mod.rs +++ b/sdk-libs/sdk/src/cpi/mod.rs @@ -35,107 +35,18 @@ //! .invoke(light_cpi_accounts)?; //! ``` -// Re-export everything from interface's CPI module (LightCpi, InvokeLightSystemProgram, etc.) -use light_compressed_account::instruction_data::{ - compressed_proof::ValidityProof, cpi_context::CompressedCpiContext, -}; -pub use light_sdk_types::interface::cpi::*; - -#[cfg(feature = "poseidon")] -use crate::DataHasher; -use crate::{ - account::LightAccount, AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError, -}; - -/// Trait for Light CPI instruction types including `with_light_account`. -/// -/// This is the SDK-level trait that provides the full builder API: -/// - CPI builder methods (`new_cpi`, `get_mode`, `get_bump`, etc.) -/// - `with_light_account` for adding compressed accounts -/// -/// Internally delegates base methods to [`LightCpi`]. -pub trait LightCpiInstruction: Sized { - /// Creates a new CPI instruction builder with a validity proof. - fn new_cpi(cpi_signer: crate::cpi::CpiSigner, proof: ValidityProof) -> Self; - - /// Returns the instruction mode (0 for v1, 1 for v2). - fn get_mode(&self) -> u8; - - /// Returns the CPI signer bump seed. - fn get_bump(&self) -> u8; - - /// Writes instruction to CPI context as the first operation in a batch. - #[must_use = "write_to_cpi_context_first returns a new value"] - fn write_to_cpi_context_first(self) -> Self; - - /// Writes instruction to CPI context as a subsequent operation in a batch. - #[must_use = "write_to_cpi_context_set returns a new value"] - fn write_to_cpi_context_set(self) -> Self; - - /// Executes all operations accumulated in CPI context. - #[must_use = "execute_with_cpi_context returns a new value"] - fn execute_with_cpi_context(self) -> Self; - - /// Returns whether this instruction uses CPI context. - fn get_with_cpi_context(&self) -> bool; - - /// Returns the CPI context configuration. - fn get_cpi_context(&self) -> &CompressedCpiContext; - - /// Returns whether this instruction has any read-only accounts. - fn has_read_only_accounts(&self) -> bool; - - /// Adds a compressed account to the instruction (SHA256 hashing). - #[must_use = "with_light_account returns a new value"] - fn with_light_account(self, account: LightAccount) -> Result - where - A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default; - - /// Adds a compressed account to the instruction (Poseidon hashing). - #[cfg(feature = "poseidon")] - #[must_use = "with_light_account_poseidon returns a new value"] - fn with_light_account_poseidon( - self, - account: crate::account::poseidon::LightAccount, - ) -> Result - where - A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + DataHasher + Default; -} - -/// Macro to delegate base LightCpi methods to the interface trait impl. -macro_rules! delegate_light_cpi { - ($ty:ty) => { - fn new_cpi(cpi_signer: crate::cpi::CpiSigner, proof: ValidityProof) -> Self { - <$ty as light_sdk_types::interface::cpi::LightCpi>::new_cpi(cpi_signer, proof) - } - fn get_mode(&self) -> u8 { - <$ty as light_sdk_types::interface::cpi::LightCpi>::get_mode(self) - } - fn get_bump(&self) -> u8 { - <$ty as light_sdk_types::interface::cpi::LightCpi>::get_bump(self) - } - fn write_to_cpi_context_first(self) -> Self { - <$ty as light_sdk_types::interface::cpi::LightCpi>::write_to_cpi_context_first(self) - } - fn write_to_cpi_context_set(self) -> Self { - <$ty as light_sdk_types::interface::cpi::LightCpi>::write_to_cpi_context_set(self) - } - fn execute_with_cpi_context(self) -> Self { - <$ty as light_sdk_types::interface::cpi::LightCpi>::execute_with_cpi_context(self) - } - fn get_with_cpi_context(&self) -> bool { - <$ty as light_sdk_types::interface::cpi::LightCpi>::get_with_cpi_context(self) - } - fn get_cpi_context(&self) -> &CompressedCpiContext { - <$ty as light_sdk_types::interface::cpi::LightCpi>::get_cpi_context(self) - } - fn has_read_only_accounts(&self) -> bool { - <$ty as light_sdk_types::interface::cpi::LightCpi>::has_read_only_accounts(self) - } - }; -} - -pub(crate) use delegate_light_cpi; +// Local Solana-specific modules +pub mod account; +pub mod instruction; +pub mod invoke; + +// Re-export local traits at crate::cpi:: level +pub use account::CpiAccountsTrait; +pub use instruction::LightCpiInstruction; +pub use invoke::{invoke_light_system_program, InvokeLightSystemProgram, LightInstructionData}; + +// Re-export non-conflicting items from sdk-types +pub use light_sdk_types::{cpi_accounts::CpiAccountsConfig, CpiSigner}; pub mod v1; #[cfg(feature = "v2")] diff --git a/sdk-libs/sdk/src/cpi/v1/invoke.rs b/sdk-libs/sdk/src/cpi/v1/invoke.rs index 048bbdb817..15a492b6a6 100644 --- a/sdk-libs/sdk/src/cpi/v1/invoke.rs +++ b/sdk-libs/sdk/src/cpi/v1/invoke.rs @@ -1,18 +1,15 @@ use light_compressed_account::instruction_data::{ compressed_proof::ValidityProof, invoke_cpi::InstructionDataInvokeCpi, }; -use light_sdk_types::constants::{CPI_AUTHORITY_PDA_SEED, LIGHT_SYSTEM_PROGRAM_ID}; -use super::{ - lowlevel::{get_account_metas_from_config, CpiInstructionConfig}, - CpiAccounts, -}; #[cfg(feature = "poseidon")] use crate::{account::poseidon::LightAccount as LightAccountPoseidon, DataHasher}; use crate::{ - account::LightAccount, cpi::CpiSigner, error::LightSdkError, - instruction::account_info::CompressedAccountInfoTrait, AnchorDeserialize, AnchorSerialize, - LightDiscriminator, ProgramError, + account::LightAccount, + cpi::{instruction::LightCpiInstruction, invoke::LightInstructionData, CpiSigner}, + error::LightSdkError, + instruction::account_info::CompressedAccountInfoTrait, + AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError, }; /// Light system program CPI instruction data builder. @@ -24,7 +21,7 @@ use crate::{ /// /// ## Common Methods /// -/// - [`with_light_account()`](Self::with_light_account) - Add a compressed account +/// - [`with_light_account()`](Self::with_light_account) - Add a compressed account (handles output hashing, and type conversion to instruction data) /// - [`with_new_addresses()`](Self::with_new_addresses) - Create new compressed account addresses /// - [`compress_lamports()`](Self::compress_lamports) - Compress SOL into compressed accounts /// - [`decompress_lamports()`](Self::decompress_lamports) - Decompress SOL from compressed accounts @@ -42,7 +39,7 @@ use crate::{ /// /// ## Create a compressed account with an address /// ```rust,no_run -/// # use light_sdk::cpi::{v1::LightSystemProgramCpi, CpiSigner}; +/// # use light_sdk::cpi::{v1::LightSystemProgramCpi, InvokeLightSystemProgram, LightCpiInstruction, CpiSigner}; /// # use light_sdk::instruction::ValidityProof; /// # use light_compressed_account::instruction_data::data::NewAddressParamsPacked; /// # use light_sdk::{LightAccount, LightDiscriminator}; @@ -90,7 +87,7 @@ use crate::{ /// ``` /// ## Update a compressed account /// ```rust,no_run -/// # use light_sdk::cpi::{v1::LightSystemProgramCpi, CpiSigner}; +/// # use light_sdk::cpi::{v1::LightSystemProgramCpi, InvokeLightSystemProgram, LightCpiInstruction, CpiSigner}; /// # use light_sdk::instruction::ValidityProof; /// # use light_sdk::{LightAccount, LightDiscriminator}; /// # use light_sdk::instruction::account_meta::CompressedAccountMeta; @@ -143,22 +140,94 @@ pub struct LightSystemProgramCpi { } impl LightSystemProgramCpi { - pub fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self { + #[must_use = "with_new_addresses returns a new value"] + pub fn with_new_addresses( + mut self, + new_address_params: &[light_compressed_account::instruction_data::data::NewAddressParamsPacked], + ) -> Self { + self.instruction_data = self.instruction_data.with_new_addresses(new_address_params); + self + } + + #[must_use = "with_input_compressed_accounts_with_merkle_context returns a new value"] + pub fn with_input_compressed_accounts_with_merkle_context( + mut self, + input_compressed_accounts_with_merkle_context: &[light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext], + ) -> Self { + self.instruction_data = self + .instruction_data + .with_input_compressed_accounts_with_merkle_context( + input_compressed_accounts_with_merkle_context, + ); + self + } + + #[must_use = "with_output_compressed_accounts returns a new value"] + pub fn with_output_compressed_accounts( + mut self, + output_compressed_accounts: &[light_compressed_account::instruction_data::data::OutputCompressedAccountWithPackedContext], + ) -> Self { + self.instruction_data = self + .instruction_data + .with_output_compressed_accounts(output_compressed_accounts); + self + } + + #[must_use = "compress_lamports returns a new value"] + pub fn compress_lamports(mut self, lamports: u64) -> Self { + self.instruction_data = self.instruction_data.compress_lamports(lamports); + self + } + + #[must_use = "decompress_lamports returns a new value"] + pub fn decompress_lamports(mut self, lamports: u64) -> Self { + self.instruction_data = self.instruction_data.decompress_lamports(lamports); + self + } + + #[cfg(feature = "cpi-context")] + #[must_use = "write_to_cpi_context_set returns a new value"] + pub fn write_to_cpi_context_set(mut self) -> Self { + self.instruction_data = self.instruction_data.write_to_cpi_context_set(); + self + } + + #[cfg(feature = "cpi-context")] + #[must_use = "write_to_cpi_context_first returns a new value"] + pub fn write_to_cpi_context_first(mut self) -> Self { + self.instruction_data = self.instruction_data.write_to_cpi_context_first(); + self + } + + #[cfg(feature = "cpi-context")] + #[must_use = "with_cpi_context returns a new value"] + pub fn with_cpi_context( + mut self, + cpi_context: light_compressed_account::instruction_data::cpi_context::CompressedCpiContext, + ) -> Self { + self.instruction_data = self.instruction_data.with_cpi_context(cpi_context); + self + } +} + +impl LightCpiInstruction for LightSystemProgramCpi { + fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self { Self { cpi_signer, instruction_data: InstructionDataInvokeCpi::new(proof.into()), } } - #[must_use = "with_light_account returns a new value"] - pub fn with_light_account(mut self, account: LightAccount) -> Result + fn with_light_account(mut self, account: LightAccount) -> Result where A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default, { use light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext; + // Convert LightAccount to account info let account_info = account.to_account_info()?; + // Handle input accounts - convert to PackedCompressedAccountWithMerkleContext if let Some(input_account) = account_info .input_compressed_account(self.cpi_signer.program_id.into()) .map_err(LightSdkError::from) @@ -168,13 +237,14 @@ impl LightSystemProgramCpi { compressed_account: input_account.compressed_account, merkle_context: input_account.merkle_context, root_index: input_account.root_index, - read_only: false, + read_only: false, // Default to false for v1 }; self.instruction_data .input_compressed_accounts_with_merkle_context .push(packed_input); } + // Handle output accounts if let Some(output_account) = account_info .output_compressed_account(self.cpi_signer.program_id.into()) .map_err(LightSdkError::from) @@ -189,8 +259,7 @@ impl LightSystemProgramCpi { } #[cfg(feature = "poseidon")] - #[must_use = "with_light_account_poseidon returns a new value"] - pub fn with_light_account_poseidon( + fn with_light_account_poseidon( mut self, account: LightAccountPoseidon, ) -> Result @@ -199,8 +268,10 @@ impl LightSystemProgramCpi { { use light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext; + // Convert LightAccount to account info let account_info = account.to_account_info()?; + // Handle input accounts - convert to PackedCompressedAccountWithMerkleContext if let Some(input_account) = account_info .input_compressed_account(self.cpi_signer.program_id.into()) .map_err(LightSdkError::from) @@ -210,13 +281,14 @@ impl LightSystemProgramCpi { compressed_account: input_account.compressed_account, merkle_context: input_account.merkle_context, root_index: input_account.root_index, - read_only: false, + read_only: false, // Default to false for v1 }; self.instruction_data .input_compressed_accounts_with_merkle_context .push(packed_input); } + // Handle output accounts if let Some(output_account) = account_info .output_compressed_account(self.cpi_signer.program_id.into()) .map_err(LightSdkError::from) @@ -230,96 +302,59 @@ impl LightSystemProgramCpi { Ok(self) } - #[must_use = "with_new_addresses returns a new value"] - pub fn with_new_addresses( - mut self, - new_address_params: &[light_compressed_account::instruction_data::data::NewAddressParamsPacked], - ) -> Self { - self.instruction_data = self.instruction_data.with_new_addresses(new_address_params); - self + fn get_mode(&self) -> u8 { + 0 // V1 uses regular mode by default } - #[must_use = "with_input_compressed_accounts_with_merkle_context returns a new value"] - pub fn with_input_compressed_accounts_with_merkle_context( - mut self, - input_compressed_accounts_with_merkle_context: &[light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext], - ) -> Self { - self.instruction_data = self - .instruction_data - .with_input_compressed_accounts_with_merkle_context( - input_compressed_accounts_with_merkle_context, - ); - self + fn get_bump(&self) -> u8 { + self.cpi_signer.bump } - #[must_use = "with_output_compressed_accounts returns a new value"] - pub fn with_output_compressed_accounts( - mut self, - output_compressed_accounts: &[light_compressed_account::instruction_data::data::OutputCompressedAccountWithPackedContext], - ) -> Self { - self.instruction_data = self - .instruction_data - .with_output_compressed_accounts(output_compressed_accounts); - self - } - - #[must_use = "compress_lamports returns a new value"] - pub fn compress_lamports(mut self, lamports: u64) -> Self { - self.instruction_data = self.instruction_data.compress_lamports(lamports); - self - } - - #[must_use = "decompress_lamports returns a new value"] - pub fn decompress_lamports(mut self, lamports: u64) -> Self { - self.instruction_data = self.instruction_data.decompress_lamports(lamports); + #[cfg(feature = "cpi-context")] + fn write_to_cpi_context_first(mut self) -> Self { + self.instruction_data = self.instruction_data.write_to_cpi_context_first(); self } #[cfg(feature = "cpi-context")] - #[must_use = "write_to_cpi_context_set returns a new value"] - pub fn write_to_cpi_context_set(mut self) -> Self { + fn write_to_cpi_context_set(mut self) -> Self { self.instruction_data = self.instruction_data.write_to_cpi_context_set(); self } #[cfg(feature = "cpi-context")] - #[must_use = "write_to_cpi_context_first returns a new value"] - pub fn write_to_cpi_context_first(mut self) -> Self { - self.instruction_data = self.instruction_data.write_to_cpi_context_first(); + fn execute_with_cpi_context(self) -> Self { + // V1 doesn't have a direct execute context, just return self + // The execute happens through the invoke call self } #[cfg(feature = "cpi-context")] - #[must_use = "with_cpi_context returns a new value"] - pub fn with_cpi_context( - mut self, - cpi_context: light_compressed_account::instruction_data::cpi_context::CompressedCpiContext, - ) -> Self { - self.instruction_data = self.instruction_data.with_cpi_context(cpi_context); - self + fn get_with_cpi_context(&self) -> bool { + self.instruction_data.cpi_context.is_some() } - /// Invoke the Light system program via CPI. - pub fn invoke(self, cpi_accounts: CpiAccounts<'_, '_>) -> Result<(), ProgramError> { - use light_compressed_account::instruction_data::traits::LightInstructionData; - - let data = self - .instruction_data - .data() - .map_err(LightSdkError::from) - .map_err(ProgramError::from)?; - - let account_infos = cpi_accounts.to_account_infos(); - let config = CpiInstructionConfig::try_from(&cpi_accounts).map_err(ProgramError::from)?; - let account_metas = get_account_metas_from_config(config); - - let instruction = solana_instruction::Instruction { - program_id: LIGHT_SYSTEM_PROGRAM_ID.into(), - accounts: account_metas, - data, + #[cfg(feature = "cpi-context")] + fn get_cpi_context( + &self, + ) -> &light_compressed_account::instruction_data::cpi_context::CompressedCpiContext { + use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; + // Use a static default with all fields set to false/0 + static DEFAULT: CompressedCpiContext = CompressedCpiContext { + set_context: false, + first_set_context: false, + cpi_context_account_index: 0, }; - let signer_seeds = [CPI_AUTHORITY_PDA_SEED, &[self.cpi_signer.bump]]; - solana_cpi::invoke_signed(&instruction, &account_infos, &[signer_seeds.as_slice()]) + self.instruction_data + .cpi_context + .as_ref() + .unwrap_or(&DEFAULT) + } + + #[cfg(feature = "cpi-context")] + fn has_read_only_accounts(&self) -> bool { + // V1 doesn't support read-only accounts + false } } @@ -329,3 +364,15 @@ impl AnchorSerialize for LightSystemProgramCpi { self.instruction_data.serialize(writer) } } + +impl light_compressed_account::InstructionDiscriminator for LightSystemProgramCpi { + fn discriminator(&self) -> &'static [u8] { + self.instruction_data.discriminator() + } +} + +impl LightInstructionData for LightSystemProgramCpi { + fn data(&self) -> Result, light_compressed_account::CompressedAccountError> { + self.instruction_data.data() + } +} diff --git a/sdk-libs/sdk/src/cpi/v2/accounts.rs b/sdk-libs/sdk/src/cpi/v2/accounts.rs new file mode 100644 index 0000000000..bce47fe5ea --- /dev/null +++ b/sdk-libs/sdk/src/cpi/v2/accounts.rs @@ -0,0 +1,96 @@ +use light_sdk_types::cpi_accounts::v2::{ + CompressionCpiAccountIndex, CpiAccounts as GenericCpiAccounts, PROGRAM_ACCOUNTS_LEN, +}; + +use crate::{error::Result, AccountInfo, AccountMeta}; + +/// Light system program CPI accounts struct. +/// +/// Use with [`LightSystemProgramCpi`](super::LightSystemProgramCpi) to invoke the Light system program. +pub type CpiAccounts<'c, 'info> = GenericCpiAccounts<'c, AccountInfo<'info>>; + +pub fn to_account_metas(cpi_accounts: &CpiAccounts<'_, '_>) -> Result> { + // TODO: do a version with a const array instead of vector. + let mut account_metas = + Vec::with_capacity(1 + cpi_accounts.account_infos().len() - PROGRAM_ACCOUNTS_LEN); + + account_metas.push(AccountMeta { + pubkey: *cpi_accounts.fee_payer().key, + is_signer: true, + is_writable: true, + }); + account_metas.push(AccountMeta { + pubkey: *cpi_accounts.authority()?.key, + is_signer: true, + is_writable: false, + }); + + account_metas.push(AccountMeta { + pubkey: *cpi_accounts.registered_program_pda()?.key, + is_signer: false, + is_writable: false, + }); + account_metas.push(AccountMeta { + pubkey: *cpi_accounts.account_compression_authority()?.key, + is_signer: false, + is_writable: false, + }); + account_metas.push(AccountMeta { + pubkey: *cpi_accounts.account_compression_program()?.key, + is_signer: false, + is_writable: false, + }); + account_metas.push(AccountMeta { + pubkey: *cpi_accounts.system_program()?.key, + is_signer: false, + is_writable: false, + }); + let accounts = cpi_accounts.account_infos(); + let mut index = CompressionCpiAccountIndex::SolPoolPda as usize; + + if cpi_accounts.config().sol_pool_pda { + let account = cpi_accounts.get_account_info(index)?; + account_metas.push(AccountMeta { + pubkey: *account.key, + is_signer: false, + is_writable: true, + }); + index += 1; + } + + if cpi_accounts.config().sol_compression_recipient { + let account = cpi_accounts.get_account_info(index)?; + account_metas.push(AccountMeta { + pubkey: *account.key, + is_signer: false, + is_writable: true, + }); + index += 1; + } + + if cpi_accounts.config().cpi_context { + let account = cpi_accounts.get_account_info(index)?; + account_metas.push(AccountMeta { + pubkey: *account.key, + is_signer: false, + is_writable: true, + }); + index += 1; + } + assert_eq!(cpi_accounts.system_accounts_end_offset(), index); + + let tree_accounts = + accounts + .get(index..) + .ok_or(crate::error::LightSdkError::CpiAccountsIndexOutOfBounds( + index, + ))?; + tree_accounts.iter().for_each(|acc| { + account_metas.push(AccountMeta { + pubkey: *acc.key, + is_signer: acc.is_signer, + is_writable: acc.is_writable, + }); + }); + Ok(account_metas) +} diff --git a/sdk-libs/sdk/src/cpi/v2/accounts_cpi_context.rs b/sdk-libs/sdk/src/cpi/v2/accounts_cpi_context.rs new file mode 100644 index 0000000000..224e11167e --- /dev/null +++ b/sdk-libs/sdk/src/cpi/v2/accounts_cpi_context.rs @@ -0,0 +1,19 @@ +use light_sdk_types::cpi_context_write::CpiContextWriteAccounts; +use solana_account_info::AccountInfo; +use solana_instruction::AccountMeta; + +#[cfg(all(feature = "cpi-context", not(feature = "v2")))] +compile_error!( + "The `cpi-context` feature requires the `v2` feature when using `CpiContextWriteAccounts`. \ + Please enable both features: features = [\"v2\", \"cpi-context\"]" +); + +pub fn get_account_metas_from_config_cpi_context( + config: CpiContextWriteAccounts, +) -> [AccountMeta; 3] { + [ + AccountMeta::new(*config.fee_payer.key, true), + AccountMeta::new_readonly(config.cpi_signer.cpi_signer.into(), true), + AccountMeta::new(*config.cpi_context.key, false), + ] +} diff --git a/sdk-libs/sdk/src/cpi/v2/invoke.rs b/sdk-libs/sdk/src/cpi/v2/invoke.rs index 438cb3de7f..e3841fa694 100644 --- a/sdk-libs/sdk/src/cpi/v2/invoke.rs +++ b/sdk-libs/sdk/src/cpi/v2/invoke.rs @@ -1,22 +1,45 @@ use light_compressed_account::instruction_data::{ - compressed_proof::ValidityProof, - cpi_context::CompressedCpiContext, - with_account_info::InstructionDataInvokeCpiWithAccountInfo, - with_readonly::{InAccount, InstructionDataInvokeCpiWithReadOnly}, + compressed_proof::ValidityProof, with_account_info::InstructionDataInvokeCpiWithAccountInfo, }; +use light_sdk_types::CpiSigner; +#[cfg(feature = "cpi-context")] +use super::lowlevel::CompressedCpiContext; +use super::lowlevel::{to_account_metas, InAccount, InstructionDataInvokeCpiWithReadOnly}; #[cfg(feature = "poseidon")] use crate::{account::poseidon::LightAccount as LightAccountPoseidon, DataHasher}; use crate::{ account::LightAccount, - cpi::{delegate_light_cpi, LightCpiInstruction}, + cpi::{account::CpiAccountsTrait, instruction::LightCpiInstruction, v2::CpiAccounts}, error::LightSdkError, instruction::account_info::CompressedAccountInfoTrait, - AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError, + AccountInfo, AccountMeta, AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError, }; +impl<'info> CpiAccountsTrait<'info> for CpiAccounts<'_, 'info> { + fn to_account_infos(&self) -> Vec> { + self.to_account_infos() + } + + fn to_account_metas(&self) -> Result, ProgramError> { + to_account_metas(self).map_err(ProgramError::from) + } + + fn get_mode(&self) -> Option { + Some(1) // v2 mode + } +} + impl LightCpiInstruction for InstructionDataInvokeCpiWithReadOnly { - delegate_light_cpi!(InstructionDataInvokeCpiWithReadOnly); + fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self { + Self { + bump: cpi_signer.bump, + invoking_program_id: cpi_signer.program_id.into(), + proof: proof.into(), + mode: 1, + ..Default::default() + } + } fn with_light_account(mut self, account: LightAccount) -> Result where @@ -129,10 +152,56 @@ impl LightCpiInstruction for InstructionDataInvokeCpiWithReadOnly { Ok(self) } + + #[cfg(feature = "cpi-context")] + fn write_to_cpi_context_first(self) -> Self { + self.write_to_cpi_context_first() + } + + #[cfg(feature = "cpi-context")] + fn write_to_cpi_context_set(self) -> Self { + self.write_to_cpi_context_set() + } + + #[cfg(feature = "cpi-context")] + fn execute_with_cpi_context(self) -> Self { + self.execute_with_cpi_context() + } + + fn get_mode(&self) -> u8 { + self.mode + } + + #[cfg(feature = "cpi-context")] + fn get_with_cpi_context(&self) -> bool { + self.with_cpi_context + } + + #[cfg(feature = "cpi-context")] + fn get_cpi_context(&self) -> &CompressedCpiContext { + &self.cpi_context + } + + fn get_bump(&self) -> u8 { + self.bump + } + + #[cfg(feature = "cpi-context")] + fn has_read_only_accounts(&self) -> bool { + !self.read_only_accounts.is_empty() + } } impl LightCpiInstruction for InstructionDataInvokeCpiWithAccountInfo { - delegate_light_cpi!(InstructionDataInvokeCpiWithAccountInfo); + fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self { + Self { + bump: cpi_signer.bump, + invoking_program_id: cpi_signer.program_id.into(), + proof: proof.into(), + mode: 1, + ..Default::default() + } + } fn with_light_account(mut self, account: LightAccount) -> Result where @@ -171,4 +240,42 @@ impl LightCpiInstruction for InstructionDataInvokeCpiWithAccountInfo { self.account_infos.push(account_info); Ok(self) } + + #[cfg(feature = "cpi-context")] + fn write_to_cpi_context_first(self) -> Self { + self.write_to_cpi_context_first() + } + + #[cfg(feature = "cpi-context")] + fn write_to_cpi_context_set(self) -> Self { + self.write_to_cpi_context_set() + } + + #[cfg(feature = "cpi-context")] + fn execute_with_cpi_context(self) -> Self { + self.execute_with_cpi_context() + } + + fn get_mode(&self) -> u8 { + self.mode + } + + #[cfg(feature = "cpi-context")] + fn get_with_cpi_context(&self) -> bool { + self.with_cpi_context + } + + #[cfg(feature = "cpi-context")] + fn get_cpi_context(&self) -> &CompressedCpiContext { + &self.cpi_context + } + + fn get_bump(&self) -> u8 { + self.bump + } + + #[cfg(feature = "cpi-context")] + fn has_read_only_accounts(&self) -> bool { + !self.read_only_accounts.is_empty() + } } diff --git a/sdk-libs/sdk/src/cpi/v2/mod.rs b/sdk-libs/sdk/src/cpi/v2/mod.rs index 5ddcf5b6e1..06916706f8 100644 --- a/sdk-libs/sdk/src/cpi/v2/mod.rs +++ b/sdk-libs/sdk/src/cpi/v2/mod.rs @@ -2,20 +2,171 @@ //! //! # Main Types //! -//! - [`InstructionDataInvokeCpiWithReadOnly`] - CPI instruction with read-only account support -//! - [`InstructionDataInvokeCpiWithAccountInfo`] - CPI instruction with account info +//! - [`LightSystemProgramCpi`] - CPI instruction data builder //! - [`CpiAccounts`] - CPI accounts struct +//! +//! +//! # Advanced Usage +//! +//! For maximum flexible light system program CPIs, see the [`lowlevel`] module or use `light-compressed-account` directly. -// CpiAccounts from sdk-types (v2) -pub use light_sdk_types::cpi_accounts::v2::CpiAccounts as GenericCpiAccounts; -pub type CpiAccounts<'c, 'info> = GenericCpiAccounts<'c, solana_account_info::AccountInfo<'info>>; +mod accounts; +#[cfg(feature = "cpi-context")] +mod accounts_cpi_context; +mod invoke; -// Instruction types from light-compressed-account -pub use light_compressed_account::instruction_data::{ - cpi_context::CompressedCpiContext, - with_account_info::InstructionDataInvokeCpiWithAccountInfo, - with_readonly::{InAccount, InstructionDataInvokeCpiWithReadOnly}, -}; +pub use accounts::CpiAccounts; +#[cfg(feature = "cpi-context")] +pub use accounts_cpi_context::*; +/// Light system program CPI instruction data builder. +/// +/// Use this builder to construct instructions for compressed account operations: +/// creating, updating, closing accounts, and compressing/decompressing SOL. +/// +/// # Builder Methods +/// +/// ## Common Methods +/// +/// - [`with_light_account()`](crate::cpi::LightCpiInstruction::with_light_account) - Add a compressed account (handles output hashing, and type conversion to instruction data). +/// - [`with_new_addresses()`](crate::cpi::v2::LightSystemProgramCpi::with_new_addresses) - Create new compressed account addresses. +/// - [`with_read_only_addresses()`](crate::cpi::v2::LightSystemProgramCpi::with_read_only_addresses) - Validate that addresses don't exist without creating them. +/// - [`with_read_only_accounts()`](crate::cpi::v2::LightSystemProgramCpi::with_read_only_accounts) - Validate that compressed account state exists without updating it. +/// - [`compress_lamports()`](crate::cpi::v2::LightSystemProgramCpi::compress_lamports) - Compress SOL into compressed accounts. +/// - [`decompress_lamports()`](crate::cpi::v2::LightSystemProgramCpi::decompress_lamports) - Decompress SOL from compressed accounts. +/// +/// **Note**: An instruction can either compress **or** decompress lamports, not both. +/// ## Advanced Methods +/// +/// For fine-grained control, use these low-level methods instead of [`with_light_account()`](crate::cpi::LightCpiInstruction::with_light_account): +/// +/// - [`with_account_infos()`](crate::cpi::v2::LightSystemProgramCpi::with_account_infos) - Manually specify CompressedAccountInfos. +/// +/// # Examples +/// +/// ## Create a compressed account with an address +/// ```rust,no_run +/// # use light_sdk::cpi::{v2::LightSystemProgramCpi, InvokeLightSystemProgram, LightCpiInstruction, CpiSigner}; +/// # use light_sdk::instruction::ValidityProof; +/// # use light_compressed_account::instruction_data::data::NewAddressParamsAssignedPacked; +/// # use light_sdk::{LightAccount, LightDiscriminator}; +/// # use borsh::{BorshSerialize, BorshDeserialize}; +/// # use solana_pubkey::Pubkey; +/// # use solana_program_error::ProgramError; +/// # +/// # const LIGHT_CPI_SIGNER: CpiSigner = CpiSigner { +/// # program_id: [0; 32], +/// # cpi_signer: [0; 32], +/// # bump: 255, +/// # }; +/// # +/// # #[derive(Clone, Debug, Default, LightDiscriminator, BorshSerialize, BorshDeserialize)] +/// # pub struct MyAccount { +/// # pub value: u64, +/// # } +/// # +/// # fn example() -> Result<(), ProgramError> { +/// # let proof = ValidityProof::default(); +/// # let new_address_params = NewAddressParamsAssignedPacked::default(); +/// # let program_id = Pubkey::new_unique(); +/// # let account = LightAccount::::new_init(&program_id, None, 0); +/// # let key = Pubkey::new_unique(); +/// # let owner = Pubkey::default(); +/// # let mut lamports = 0u64; +/// # let mut data = []; +/// # let fee_payer = &solana_account_info::AccountInfo::new( +/// # &key, +/// # true, +/// # true, +/// # &mut lamports, +/// # &mut data, +/// # &owner, +/// # false, +/// # 0, +/// # ); +/// # let cpi_accounts = light_sdk::cpi::v2::CpiAccounts::new(fee_payer, &[], LIGHT_CPI_SIGNER); +/// LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof) +/// .with_new_addresses(&[new_address_params]) +/// .with_light_account(account)? +/// .invoke(cpi_accounts)?; +/// # Ok(()) +/// # } +/// ``` +/// ## Update a compressed account +/// ```rust,no_run +/// # use light_sdk::cpi::{v2::LightSystemProgramCpi, InvokeLightSystemProgram, LightCpiInstruction, CpiSigner}; +/// # use light_sdk::instruction::ValidityProof; +/// # use light_sdk::{LightAccount, LightDiscriminator}; +/// # use light_sdk::instruction::account_meta::CompressedAccountMeta; +/// # use borsh::{BorshSerialize, BorshDeserialize}; +/// # use solana_pubkey::Pubkey; +/// # use solana_program_error::ProgramError; +/// # +/// # const LIGHT_CPI_SIGNER: CpiSigner = CpiSigner { +/// # program_id: [0; 32], +/// # cpi_signer: [0; 32], +/// # bump: 255, +/// # }; +/// # +/// # #[derive(Clone, Debug, Default, LightDiscriminator, BorshSerialize, BorshDeserialize)] +/// # pub struct MyAccount { +/// # pub value: u64, +/// # } +/// # +/// # fn example() -> Result<(), ProgramError> { +/// # let proof = ValidityProof::default(); +/// # let program_id = Pubkey::new_unique(); +/// # let account_meta = CompressedAccountMeta::default(); +/// # let account_data = MyAccount::default(); +/// # let account = LightAccount::::new_mut(&program_id, &account_meta, account_data)?; +/// # let key = Pubkey::new_unique(); +/// # let owner = Pubkey::default(); +/// # let mut lamports = 0u64; +/// # let mut data = []; +/// # let fee_payer = &solana_account_info::AccountInfo::new( +/// # &key, +/// # true, +/// # true, +/// # &mut lamports, +/// # &mut data, +/// # &owner, +/// # false, +/// # 0, +/// # ); +/// # let cpi_accounts = light_sdk::cpi::v2::CpiAccounts::new(fee_payer, &[], LIGHT_CPI_SIGNER); +/// LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof) +/// .with_light_account(account)? +/// .invoke(cpi_accounts)?; +/// # Ok(()) +/// # } +/// ``` +pub use light_compressed_account::instruction_data::with_account_info::InstructionDataInvokeCpiWithAccountInfo as LightSystemProgramCpi; -// LightCpiInstruction impls for v2 instruction types -mod invoke; +/// Low-level types and functions for flexible Light system program CPIs. +/// +/// # Main Types +/// +/// For most use cases, you only need: +/// - [`LightSystemProgramCpi`] - Main CPI interface +/// - [`CpiAccounts`] - Account management +/// +/// The remaining types in this module are exported for low-level operations and internal use. +pub mod lowlevel { + + /// CPI context for batched compressed account operations. + pub use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; + /// Account information for compressed accounts in CPI operations. + pub use light_compressed_account::instruction_data::with_account_info::CompressedAccountInfo; + /// Input account information for compressed accounts. + pub use light_compressed_account::instruction_data::with_account_info::InAccountInfo; + /// Output account information for compressed accounts. + pub use light_compressed_account::instruction_data::with_account_info::OutAccountInfo; + /// Input compressed account for read-only operations. + pub use light_compressed_account::instruction_data::with_readonly::InAccount; + /// V2 CPI instruction data for read-only compressed account operations. + /// + /// Provides more flexibility for complex operations such as changing the compressed account owner. + /// Most users should use [`crate::cpi::v2::LightSystemProgramCpi`] instead. + pub use light_compressed_account::instruction_data::with_readonly::InstructionDataInvokeCpiWithReadOnly; + + pub use crate::cpi::v2::accounts::to_account_metas; +} diff --git a/sdk-libs/sdk/src/instruction/pack_accounts.rs b/sdk-libs/sdk/src/instruction/pack_accounts.rs new file mode 100644 index 0000000000..c6c5d4663c --- /dev/null +++ b/sdk-libs/sdk/src/instruction/pack_accounts.rs @@ -0,0 +1,509 @@ +//! Utilities for packing accounts into instruction data. +//! +//! [`PackedAccounts`] is a builder for efficiently organizing accounts into the three categories +//! required for compressed account instructions: +//! 1. **Pre-accounts** - Custom accounts needed before system accounts +//! 2. **System accounts** - Static light system program accounts +//! 3. **Packed accounts** - Dynamically packed accounts (Merkle trees, address trees, queues) with automatic deduplication +//! +//! +//! ## System Account Versioning +//! +//! **`add_system_accounts()` is complementary to [`cpi::v1::CpiAccounts`](crate::cpi::v1::CpiAccounts)** +//! **`add_system_accounts_v2()` is complementary to [`cpi::v2::CpiAccounts`](crate::cpi::v2::CpiAccounts)** +//! +//! Always use the matching version - v1 client-side account packing with v1 program-side CPI, +//! and v2 with v2. Mixing versions will cause account layout mismatches. +//! +//! # Example: Creating a compressed PDA +//! +//! ```rust +//! # use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; +//! # use solana_pubkey::Pubkey; +//! # fn example() -> Result<(), Box> { +//! # let program_id = Pubkey::new_unique(); +//! # let payer_pubkey = Pubkey::new_unique(); +//! # let merkle_tree_pubkey = Pubkey::new_unique(); +//! // Initialize with system accounts +//! let system_account_meta_config = SystemAccountMetaConfig::new(program_id); +//! let mut accounts = PackedAccounts::default(); +//! +//! // Add pre-accounts (signers) +//! accounts.add_pre_accounts_signer(payer_pubkey); +//! +//! // Add Light system program accounts (v2) +//! #[cfg(feature = "v2")] +//! accounts.add_system_accounts_v2(system_account_meta_config)?; +//! #[cfg(not(feature = "v2"))] +//! accounts.add_system_accounts(system_account_meta_config)?; +//! +//! // Add Merkle tree accounts (automatically tracked and deduplicated) +//! let output_merkle_tree_index = accounts.insert_or_get(merkle_tree_pubkey); +//! +//! // Convert to final account metas with offsets +//! let (account_metas, system_accounts_offset, tree_accounts_offset) = accounts.to_account_metas(); +//! # assert_eq!(output_merkle_tree_index, 0); +//! # Ok(()) +//! # } +//! ``` +//! +//! # Account Organization +//! +//! The final account layout is: +//! ```text +//! [pre_accounts] [system_accounts] [packed_accounts] +//! ↑ ↑ ↑ +//! Signers, Light system Merkle trees, +//! fee payer program accts address trees +//! ``` +//! +//! # Automatic Deduplication +//! +//! ```rust +//! # use light_sdk::instruction::PackedAccounts; +//! # use solana_pubkey::Pubkey; +//! let mut accounts = PackedAccounts::default(); +//! let tree_pubkey = Pubkey::new_unique(); +//! let other_tree = Pubkey::new_unique(); +//! +//! // First insertion gets index 0 +//! let index1 = accounts.insert_or_get(tree_pubkey); +//! assert_eq!(index1, 0); +//! +//! // Same tree inserted again returns same index (deduplicated) +//! let index2 = accounts.insert_or_get(tree_pubkey); +//! assert_eq!(index2, 0); +//! +//! // Different tree gets next index +//! let index3 = accounts.insert_or_get(other_tree); +//! assert_eq!(index3, 1); +//! ``` +//! +//! # Building Instructions with Anchor Programs +//! +//! When building instructions for Anchor programs, concatenate your custom accounts with the packed accounts: +//! +//! ```rust,ignore +//! # use anchor_lang::InstructionData; +//! # use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; +//! # use solana_instruction::{AccountMeta, Instruction}; +//! +//! // 1. Set up packed accounts +//! let config = SystemAccountMetaConfig::new(program_id); +//! let mut remaining_accounts = PackedAccounts::default(); +//! remaining_accounts.add_system_accounts(config)?; +//! +//! // 2. Pack tree accounts from proof result +//! let packed_tree_info = proof_result.pack_tree_infos(&mut remaining_accounts); +//! let output_tree_index = state_tree_info.pack_output_tree_index(&mut remaining_accounts)?; +//! +//! // 3. Convert to account metas +//! let (remaining_accounts, _, _) = remaining_accounts.to_account_metas(); +//! +//! // 4. Build instruction: custom accounts first, then remaining_accounts +//! let instruction = Instruction { +//! program_id: your_program::ID, +//! accounts: [ +//! vec![AccountMeta::new(payer.pubkey(), true)], // Your program's accounts +//! // Add other custom accounts here if needed +//! remaining_accounts, // Light system accounts + trees +//! ] +//! .concat(), +//! data: your_program::instruction::YourInstruction { +//! proof: proof_result.proof, +//! address_tree_info: packed_tree_info.address_trees[0], +//! output_tree_index, +//! // ... your other fields +//! } +//! .data(), +//! }; +//! ``` + +use std::collections::HashMap; + +use crate::{ + error::LightSdkError, + instruction::system_accounts::{get_light_system_account_metas, SystemAccountMetaConfig}, + AccountMeta, Pubkey, +}; + +/// Builder to collect accounts for compressed account instructions. +/// +/// Manages three categories of accounts: +/// - **Pre-accounts**: Signers and other custom accounts that come before system accounts. +/// - **System accounts**: Light system program accounts (authority, trees, queues). +/// - **Packed accounts**: Dynamically tracked deduplicted accounts. +/// +/// # Example +/// +/// ```rust +/// # use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; +/// # use solana_pubkey::Pubkey; +/// # fn example() -> Result<(), Box> { +/// # let payer_pubkey = Pubkey::new_unique(); +/// # let program_id = Pubkey::new_unique(); +/// # let merkle_tree_pubkey = Pubkey::new_unique(); +/// let mut accounts = PackedAccounts::default(); +/// +/// // Add signer +/// accounts.add_pre_accounts_signer(payer_pubkey); +/// +/// // Add system accounts (use v2 if feature is enabled) +/// let config = SystemAccountMetaConfig::new(program_id); +/// #[cfg(feature = "v2")] +/// accounts.add_system_accounts_v2(config)?; +/// #[cfg(not(feature = "v2"))] +/// accounts.add_system_accounts(config)?; +/// +/// // Add and track tree accounts +/// let tree_index = accounts.insert_or_get(merkle_tree_pubkey); +/// +/// // Get final account metas +/// let (metas, system_offset, tree_offset) = accounts.to_account_metas(); +/// # assert_eq!(tree_index, 0); +/// # Ok(()) +/// # } +/// ``` +#[derive(Default, Debug)] +pub struct PackedAccounts { + /// Accounts that must come before system accounts (e.g., signers, fee payer). + pub pre_accounts: Vec, + /// Light system program accounts (authority, programs, trees, queues). + system_accounts: Vec, + /// Next available index for packed accounts. + next_index: u8, + /// Map of pubkey to (index, AccountMeta) for deduplication and index tracking. + map: HashMap, + /// Field to sanity check + system_accounts_set: bool, +} + +impl PackedAccounts { + pub fn new_with_system_accounts(config: SystemAccountMetaConfig) -> crate::error::Result { + let mut remaining_accounts = PackedAccounts::default(); + remaining_accounts.add_system_accounts(config)?; + Ok(remaining_accounts) + } + + pub fn system_accounts_set(&self) -> bool { + self.system_accounts_set + } + + pub fn add_pre_accounts_signer(&mut self, pubkey: Pubkey) { + self.pre_accounts.push(AccountMeta { + pubkey, + is_signer: true, + is_writable: false, + }); + } + + pub fn add_pre_accounts_signer_mut(&mut self, pubkey: Pubkey) { + self.pre_accounts.push(AccountMeta { + pubkey, + is_signer: true, + is_writable: true, + }); + } + + pub fn add_pre_accounts_meta(&mut self, account_meta: AccountMeta) { + self.pre_accounts.push(account_meta); + } + + pub fn add_pre_accounts_metas(&mut self, account_metas: &[AccountMeta]) { + self.pre_accounts.extend_from_slice(account_metas); + } + + /// Adds v1 Light system program accounts to the account list. + /// + /// **Use with [`cpi::v1::CpiAccounts`](crate::cpi::v1::CpiAccounts) on the program side.** + /// + /// This adds all the accounts required by the Light system program for v1 operations, + /// including the CPI authority, registered programs, account compression program, and Noop program. + /// + /// # Example + /// + /// ```rust + /// # use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; + /// # use solana_pubkey::Pubkey; + /// # fn example() -> Result<(), Box> { + /// # let program_id = Pubkey::new_unique(); + /// let mut accounts = PackedAccounts::default(); + /// let config = SystemAccountMetaConfig::new(program_id); + /// accounts.add_system_accounts(config)?; + /// # Ok(()) + /// # } + /// ``` + pub fn add_system_accounts( + &mut self, + config: SystemAccountMetaConfig, + ) -> crate::error::Result<()> { + self.system_accounts + .extend(get_light_system_account_metas(config)); + // note cpi context account is part of the system accounts + /* if let Some(pubkey) = config.cpi_context { + if self.next_index != 0 { + return Err(crate::error::LightSdkError::CpiContextOrderingViolation); + } + self.insert_or_get(pubkey); + }*/ + Ok(()) + } + + /// Adds v2 Light system program accounts to the account list. + /// + /// **Use with [`cpi::v2::CpiAccounts`](crate::cpi::v2::CpiAccounts) on the program side.** + /// + /// This adds all the accounts required by the Light system program for v2 operations. + /// V2 uses a different account layout optimized for batched state trees. + /// + /// # Example + /// + /// ```rust + /// # #[cfg(feature = "v2")] + /// # { + /// # use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; + /// # use solana_pubkey::Pubkey; + /// # fn example() -> Result<(), Box> { + /// # let program_id = Pubkey::new_unique(); + /// let mut accounts = PackedAccounts::default(); + /// let config = SystemAccountMetaConfig::new(program_id); + /// accounts.add_system_accounts_v2(config)?; + /// # Ok(()) + /// # } + /// # } + /// ``` + #[cfg(feature = "v2")] + pub fn add_system_accounts_v2( + &mut self, + config: SystemAccountMetaConfig, + ) -> crate::error::Result<()> { + self.system_accounts + .extend(crate::instruction::get_light_system_account_metas_v2( + config, + )); + // note cpi context account is part of the system accounts + /* if let Some(pubkey) = config.cpi_context { + if self.next_index != 0 { + return Err(crate::error::LightSdkError::CpiContextOrderingViolation); + } + self.insert_or_get(pubkey); + }*/ + Ok(()) + } + + /// Returns the index of the provided `pubkey` in the collection. + /// + /// If the provided `pubkey` is not a part of the collection, it gets + /// inserted with a `next_index`. + /// + /// If the privided `pubkey` already exists in the collection, its already + /// existing index is returned. + pub fn insert_or_get(&mut self, pubkey: Pubkey) -> u8 { + self.insert_or_get_config(pubkey, false, true) + } + + pub fn insert_or_get_read_only(&mut self, pubkey: Pubkey) -> u8 { + self.insert_or_get_config(pubkey, false, false) + } + + pub fn insert_or_get_config( + &mut self, + pubkey: Pubkey, + is_signer: bool, + is_writable: bool, + ) -> u8 { + match self.map.get_mut(&pubkey) { + Some((index, entry)) => { + if !entry.is_writable { + entry.is_writable = is_writable; + } + if !entry.is_signer { + entry.is_signer = is_signer; + } + *index + } + None => { + let index = self.next_index; + self.next_index += 1; + self.map.insert( + pubkey, + ( + index, + AccountMeta { + pubkey, + is_signer, + is_writable, + }, + ), + ); + index + } + } + } + + fn hash_set_accounts_to_metas(&self) -> Vec { + let mut packed_accounts = self.map.iter().collect::>(); + // hash maps are not sorted so we need to sort manually and collect into a vector again + packed_accounts.sort_by(|a, b| a.1 .0.cmp(&b.1 .0)); + let packed_accounts = packed_accounts + .iter() + .map(|(_, (_, k))| k.clone()) + .collect::>(); + packed_accounts + } + + fn get_offsets(&self) -> (usize, usize) { + let system_accounts_start_offset = self.pre_accounts.len(); + let packed_accounts_start_offset = + system_accounts_start_offset + self.system_accounts.len(); + (system_accounts_start_offset, packed_accounts_start_offset) + } + + /// Converts the collection of accounts to a vector of + /// [`AccountMeta`](solana_instruction::AccountMeta), which can be used + /// as remaining accounts in instructions or CPI calls. + /// + /// # Returns + /// + /// A tuple of `(account_metas, system_accounts_offset, packed_accounts_offset)`: + /// - `account_metas`: All accounts concatenated in order: `[pre_accounts][system_accounts][packed_accounts]` + /// - `system_accounts_offset`: Index where system accounts start (= pre_accounts.len()) + /// - `packed_accounts_offset`: Index where packed accounts start (= pre_accounts.len() + system_accounts.len()) + /// + /// The `system_accounts_offset` can be used to slice the accounts when creating [`CpiAccounts`](crate::cpi::v1::CpiAccounts): + /// ```ignore + /// let accounts_for_cpi = &ctx.remaining_accounts[system_accounts_offset..]; + /// let cpi_accounts = CpiAccounts::new(fee_payer, accounts_for_cpi, cpi_signer)?; + /// ``` + /// + /// The offset can be hardcoded if your program always has the same pre-accounts layout, or passed + /// as a field in your instruction data. + pub fn to_account_metas(&self) -> (Vec, usize, usize) { + let packed_accounts = self.hash_set_accounts_to_metas(); + let (system_accounts_start_offset, packed_accounts_start_offset) = self.get_offsets(); + ( + [ + self.pre_accounts.clone(), + self.system_accounts.clone(), + packed_accounts, + ] + .concat(), + system_accounts_start_offset, + packed_accounts_start_offset, + ) + } + + pub fn packed_pubkeys(&self) -> Vec { + self.hash_set_accounts_to_metas() + .iter() + .map(|meta| meta.pubkey) + .collect() + } + + pub fn add_custom_system_accounts( + &mut self, + accounts: T, + ) -> crate::error::Result<()> { + accounts.get_account_metas_vec(self) + } +} + +pub trait AccountMetasVec { + fn get_account_metas_vec(&self, accounts: &mut PackedAccounts) -> Result<(), LightSdkError>; +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_remaining_accounts() { + let mut remaining_accounts = PackedAccounts::default(); + + let pubkey_1 = Pubkey::new_unique(); + let pubkey_2 = Pubkey::new_unique(); + let pubkey_3 = Pubkey::new_unique(); + let pubkey_4 = Pubkey::new_unique(); + + // Initial insertion. + assert_eq!(remaining_accounts.insert_or_get(pubkey_1), 0); + assert_eq!(remaining_accounts.insert_or_get(pubkey_2), 1); + assert_eq!(remaining_accounts.insert_or_get(pubkey_3), 2); + + assert_eq!( + remaining_accounts.to_account_metas().0.as_slice(), + &[ + AccountMeta { + pubkey: pubkey_1, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: pubkey_2, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: pubkey_3, + is_signer: false, + is_writable: true, + } + ] + ); + + // Insertion of already existing pubkeys. + assert_eq!(remaining_accounts.insert_or_get(pubkey_1), 0); + assert_eq!(remaining_accounts.insert_or_get(pubkey_2), 1); + assert_eq!(remaining_accounts.insert_or_get(pubkey_3), 2); + + assert_eq!( + remaining_accounts.to_account_metas().0.as_slice(), + &[ + AccountMeta { + pubkey: pubkey_1, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: pubkey_2, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: pubkey_3, + is_signer: false, + is_writable: true, + } + ] + ); + + // Again, initial insertion. + assert_eq!(remaining_accounts.insert_or_get(pubkey_4), 3); + + assert_eq!( + remaining_accounts.to_account_metas().0.as_slice(), + &[ + AccountMeta { + pubkey: pubkey_1, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: pubkey_2, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: pubkey_3, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: pubkey_4, + is_signer: false, + is_writable: true, + } + ] + ); + } +} diff --git a/sdk-libs/sdk/src/lib.rs b/sdk-libs/sdk/src/lib.rs index 65868a700e..5f623cb102 100644 --- a/sdk-libs/sdk/src/lib.rs +++ b/sdk-libs/sdk/src/lib.rs @@ -163,10 +163,6 @@ pub mod proof; pub mod transfer; pub mod utils; -/// Backward-compat alias for the interface module -pub use light_sdk_types::interface as compressible; -/// Re-export the interface module -pub use light_sdk_types::interface; pub use proof::borsh_compat; #[cfg(feature = "merkle-tree")] pub mod merkle_tree; @@ -205,16 +201,6 @@ use anchor_lang::{AnchorDeserialize, AnchorSerialize}; #[cfg(not(feature = "anchor"))] use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; pub use light_account_checks::{self, discriminator::Discriminator as LightDiscriminator}; -// Re-export interface types from light-sdk-types::interface -// Pack trait is only available off-chain (client-side) - uses PackedAccounts -#[cfg(not(target_os = "solana"))] -pub use light_sdk_types::interface::Pack; -pub use light_sdk_types::interface::{ - process_initialize_light_config_checked, process_update_light_config, CompressAs, - CompressedAccountData, CompressedInitSpace, CompressionInfo, HasCompressionInfo, - InitializeLightConfigParams, LightConfig, Space, Unpack, UpdateLightConfigParams, - COMPRESSIBLE_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE, -}; // Re-export as extern crate so downstream crates can use `::light_hasher::` paths pub extern crate light_hasher; #[cfg(feature = "poseidon")] diff --git a/sdk-libs/token-sdk/Cargo.toml b/sdk-libs/token-sdk/Cargo.toml index f0a3a1e233..c3887415ed 100644 --- a/sdk-libs/token-sdk/Cargo.toml +++ b/sdk-libs/token-sdk/Cargo.toml @@ -36,6 +36,7 @@ light-compressed-account = { workspace = true, features = ["std", "solana"] } light-compressible = { workspace = true } light-token-interface = { workspace = true } light-sdk = { workspace = true, features = ["v2", "cpi-context"] } +light-account = { workspace = true } light-batched-merkle-tree = { workspace = true } light-macros = { workspace = true } thiserror = { workspace = true } diff --git a/sdk-libs/token-sdk/src/anchor.rs b/sdk-libs/token-sdk/src/anchor.rs index 4bebe8c1ad..74d21ae7a8 100644 --- a/sdk-libs/token-sdk/src/anchor.rs +++ b/sdk-libs/token-sdk/src/anchor.rs @@ -3,6 +3,13 @@ //! Provides a single import point for Anchor programs using Light Protocol. // Re-export Light SDK core types +pub use light_account::{ + CompressAs as CompressAsTrait, CompressedInitSpace, CompressionInfo, + HasCompressionInfo as HasCompressionInfoTrait, LightConfig, LightFinalize, LightPreInit, Space, + Unpack, +}; +#[cfg(not(target_os = "solana"))] +pub use light_account::{Pack, PackedAccounts}; pub use light_sdk::{ account::LightAccount as LightAccountType, address, @@ -10,16 +17,8 @@ pub use light_sdk::{ derive_light_cpi_signer, derive_light_cpi_signer_pda, error::LightSdkError, instruction::ValidityProof, - interface::{ - CompressAs as CompressAsTrait, CompressedInitSpace, CompressionInfo, - HasCompressionInfo as HasCompressionInfoTrait, LightConfig, LightFinalize, LightPreInit, - Space, Unpack, - }, CpiSigner, LightDiscriminator as LightDiscriminatorTrait, }; -// Pack and PackedAccounts only available off-chain (client-side) -#[cfg(not(target_os = "solana"))] -pub use light_sdk::{instruction::PackedAccounts, interface::Pack}; // Re-export Light SDK macros pub use light_sdk_macros::{ // Proc macros diff --git a/sdk-libs/token-sdk/src/instruction/decompress.rs b/sdk-libs/token-sdk/src/instruction/decompress.rs index 7c8b6bff02..163ff6a05f 100644 --- a/sdk-libs/token-sdk/src/instruction/decompress.rs +++ b/sdk-libs/token-sdk/src/instruction/decompress.rs @@ -1,3 +1,4 @@ +use light_account::PackedAccounts; use light_compressed_account::instruction_data::compressed_proof::ValidityProof; use light_compressed_token_sdk::compressed_token::{ decompress_full::pack_for_decompress_full_with_ata, @@ -6,7 +7,7 @@ use light_compressed_token_sdk::compressed_token::{ }, CTokenAccount2, }; -use light_sdk::instruction::{PackedAccounts, PackedStateTreeInfo}; +use light_sdk::instruction::PackedStateTreeInfo; use light_token_interface::{ instructions::extensions::{CompressedOnlyExtensionInstructionData, ExtensionInstructionData}, state::{AccountState, ExtensionStruct, TokenData, TokenDataVersion}, diff --git a/sdk-libs/token-sdk/src/pack.rs b/sdk-libs/token-sdk/src/pack.rs index 025d0353af..7d464835ad 100644 --- a/sdk-libs/token-sdk/src/pack.rs +++ b/sdk-libs/token-sdk/src/pack.rs @@ -1,6 +1,7 @@ //! Pack implementation for TokenData types for c-tokens. use light_compressed_account::compressed_account::CompressedAccountWithMerkleContext; -use light_sdk::{instruction::PackedAccounts, light_hasher::HasherError}; +use light_account::PackedAccounts; +use light_sdk::light_hasher::HasherError; pub use light_token_interface::state::TokenData; use light_token_interface::state::TokenDataVersion; use solana_account_info::AccountInfo; diff --git a/sdk-libs/token-sdk/tests/pack_test.rs b/sdk-libs/token-sdk/tests/pack_test.rs index b2e98e58a5..2f58f39063 100644 --- a/sdk-libs/token-sdk/tests/pack_test.rs +++ b/sdk-libs/token-sdk/tests/pack_test.rs @@ -1,6 +1,6 @@ #![cfg(feature = "compressible")] -use light_sdk::instruction::PackedAccounts; +use light_account::PackedAccounts; use light_token::{ compat::{PackedCompressibleTokenDataWithVariant, TokenData, TokenDataWithVariant}, pack::Pack, diff --git a/sdk-tests/csdk-anchor-full-derived-test-sdk/tests/trait_tests.rs b/sdk-tests/csdk-anchor-full-derived-test-sdk/tests/trait_tests.rs index 3a9c8332e2..4dcb0a8267 100644 --- a/sdk-tests/csdk-anchor-full-derived-test-sdk/tests/trait_tests.rs +++ b/sdk-tests/csdk-anchor-full-derived-test-sdk/tests/trait_tests.rs @@ -550,18 +550,18 @@ fn test_variant_seed_values_distinguish_instances() { use csdk_anchor_full_derived_test::csdk_anchor_full_derived_test::{ Token0VaultSeeds, Token1VaultSeeds, }; - use light_sdk::interface::token::TokenDataWithSeeds; + use light_account::token::TokenDataWithSeeds; let pool_state = Pubkey::new_unique(); let token_0_mint = Pubkey::new_unique(); let token_1_mint = Pubkey::new_unique(); - let default_token = light_sdk::interface::token::Token { + let default_token = light_account::token::Token { mint: Default::default(), owner: Default::default(), amount: 0, delegate: None, - state: light_sdk::interface::token::AccountState::Initialized, + state: light_account::token::AccountState::Initialized, is_native: None, delegated_amount: 0, close_authority: None, diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata.rs index 88d8ca4cf4..64962dc024 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata.rs @@ -45,7 +45,7 @@ pub struct D10SingleAta<'info> { pub light_token_rent_sponsor: AccountInfo<'info>, /// CHECK: Light Token Program for CPI - #[account(address = LIGHT_TOKEN_PROGRAM_ID.into())] + #[account(address = LIGHT_TOKEN_PROGRAM_ID)] pub light_token_program: AccountInfo<'info>, pub system_program: Program<'info, System>, diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata_markonly.rs b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata_markonly.rs index c5dfcbedfe..040d10ccbe 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata_markonly.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/instructions/d10_token_accounts/single_ata_markonly.rs @@ -43,7 +43,7 @@ pub struct D10SingleAtaMarkonly<'info> { pub light_token_rent_sponsor: AccountInfo<'info>, /// CHECK: Light Token Program for CPI - #[account(address = LIGHT_TOKEN_PROGRAM_ID.into())] + #[account(address = LIGHT_TOKEN_PROGRAM_ID)] pub light_token_program: AccountInfo<'info>, pub system_program: Program<'info, System>, diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_observation_state_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_observation_state_test.rs index 9d0ae037aa..ee7562cadd 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_observation_state_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_observation_state_test.rs @@ -11,10 +11,8 @@ use csdk_anchor_full_derived_test::{Observation, ObservationState, PackedObservationState}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::{CompressAs, CompressionInfo, Pack}, - instruction::PackedAccounts, -}; +use light_account::{CompressAs, CompressionInfo, Pack}; +use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; use super::shared::CompressibleTestFactory; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_pool_state_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_pool_state_test.rs index 57e3cfa8f9..c8404c8a39 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_pool_state_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_pool_state_test.rs @@ -11,10 +11,8 @@ use csdk_anchor_full_derived_test::{PackedPoolState, PoolState}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::{CompressAs, CompressionInfo, Pack}, - instruction::PackedAccounts, -}; +use light_account::{CompressAs, CompressionInfo, Pack}; +use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; use super::shared::CompressibleTestFactory; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_game_session_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_game_session_test.rs index 6ff79b040f..c842ebd690 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_game_session_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_game_session_test.rs @@ -11,10 +11,8 @@ use csdk_anchor_full_derived_test::{GameSession, PackedGameSession}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::{CompressAs, CompressionInfo, Pack}, - instruction::PackedAccounts, -}; +use light_account::{CompressAs, CompressionInfo, Pack}; +use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; use super::shared::CompressibleTestFactory; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_placeholder_record_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_placeholder_record_test.rs index 02d3358ba3..b75cc3324c 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_placeholder_record_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_placeholder_record_test.rs @@ -8,10 +8,8 @@ use csdk_anchor_full_derived_test::{PackedPlaceholderRecord, PlaceholderRecord}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::{CompressAs, CompressionInfo, CompressionState, Pack}, - instruction::PackedAccounts, -}; +use light_account::{CompressAs, CompressionInfo, CompressionState, Pack}; +use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; use super::shared::CompressibleTestFactory; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_user_record_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_user_record_test.rs index 4c4142dec1..9069b7bf73 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_user_record_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_user_record_test.rs @@ -7,10 +7,8 @@ use csdk_anchor_full_derived_test::{PackedUserRecord, UserRecord}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::{CompressAs, CompressionInfo, CompressionState, Pack}, - instruction::PackedAccounts, -}; +use light_account::{CompressAs, CompressionInfo, CompressionState, Pack}; +use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; use super::shared::CompressibleTestFactory; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_all_field_types_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_all_field_types_test.rs index b67ebed5cb..1e3540537b 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_all_field_types_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_all_field_types_test.rs @@ -15,10 +15,8 @@ use csdk_anchor_full_derived_test::{AllFieldTypesRecord, PackedAllFieldTypesRecord}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::{CompressAs, CompressionInfo, CompressionState, Pack}, - instruction::PackedAccounts, -}; +use light_account::{CompressAs, CompressionInfo, CompressionState, Pack}; +use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; use super::shared::CompressibleTestFactory; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_array_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_array_test.rs index d97239f8d3..7e90209e1a 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_array_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_array_test.rs @@ -11,10 +11,7 @@ use csdk_anchor_full_derived_test::ArrayRecord; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::CompressionState, - interface::{CompressAs, CompressionInfo}, -}; +use light_account::{CompressAs, CompressionInfo, CompressionState}; use super::shared::CompressibleTestFactory; use crate::generate_trait_tests; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_multi_pubkey_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_multi_pubkey_test.rs index 2282a33a55..0b99f6d252 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_multi_pubkey_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_multi_pubkey_test.rs @@ -7,10 +7,8 @@ use csdk_anchor_full_derived_test::{MultiPubkeyRecord, PackedMultiPubkeyRecord}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::{CompressAs, CompressionInfo, Pack}, - instruction::PackedAccounts, -}; +use light_account::{CompressAs, CompressionInfo, Pack}; +use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; use super::shared::CompressibleTestFactory; @@ -94,7 +92,7 @@ fn test_compress_as_when_compression_info_already_none() { // Should still work and preserve fields assert_eq!( compressed.compression_info.state, - light_sdk::compressible::CompressionState::Compressed + light_account::CompressionState::Compressed ); assert_eq!(compressed.owner, owner); assert_eq!(compressed.delegate, delegate); diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_no_pubkey_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_no_pubkey_test.rs index 2b10e3f05d..75634f3577 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_no_pubkey_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_no_pubkey_test.rs @@ -11,10 +11,7 @@ use csdk_anchor_full_derived_test::NoPubkeyRecord; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::CompressionState, - interface::{CompressAs, CompressionInfo}, -}; +use light_account::{CompressAs, CompressionInfo, CompressionState}; use super::shared::CompressibleTestFactory; use crate::generate_trait_tests; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_non_copy_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_non_copy_test.rs index 7d2c4cf8e1..291a65662b 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_non_copy_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_non_copy_test.rs @@ -10,8 +10,8 @@ //! Therefore, no Pack/Unpack tests are needed. use csdk_anchor_full_derived_test::NonCopyRecord; +use light_account::{CompressAs, CompressionInfo}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::interface::{CompressAs, CompressionInfo}; use super::shared::CompressibleTestFactory; use crate::generate_trait_tests; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_option_primitive_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_option_primitive_test.rs index a12e6dd709..624f724663 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_option_primitive_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_option_primitive_test.rs @@ -10,8 +10,8 @@ //! struct (not converted to Option). Therefore, no Pack/Unpack tests are needed. use csdk_anchor_full_derived_test::OptionPrimitiveRecord; +use light_account::{CompressAs, CompressionInfo}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::interface::{CompressAs, CompressionInfo}; use super::shared::CompressibleTestFactory; use crate::generate_trait_tests; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_option_pubkey_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_option_pubkey_test.rs index 9855bf4cc9..c0f6d8c502 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_option_pubkey_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_option_pubkey_test.rs @@ -11,10 +11,8 @@ use csdk_anchor_full_derived_test::OptionPubkeyRecord; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::{CompressAs, CompressionInfo, Pack}, - instruction::PackedAccounts, -}; +use light_account::{CompressAs, CompressionInfo, Pack}; +use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; use super::shared::CompressibleTestFactory; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_single_pubkey_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_single_pubkey_test.rs index b9bdb5a386..7888e173c2 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_single_pubkey_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_single_pubkey_test.rs @@ -7,10 +7,8 @@ use csdk_anchor_full_derived_test::{PackedSinglePubkeyRecord, SinglePubkeyRecord}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::{CompressAs, CompressionInfo, Pack}, - instruction::PackedAccounts, -}; +use light_account::{CompressAs, CompressionInfo, Pack}; +use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; use super::shared::CompressibleTestFactory; @@ -80,7 +78,7 @@ fn test_compress_as_when_compression_info_already_none() { // Should still work and preserve fields assert_eq!( compressed.compression_info.state, - light_sdk::compressible::CompressionState::Compressed + light_account::CompressionState::Compressed ); assert_eq!(compressed.owner, owner); assert_eq!(compressed.counter, counter); diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_all_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_all_compress_as_test.rs index eb67343efc..cec141c06c 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_all_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_all_compress_as_test.rs @@ -7,10 +7,8 @@ use csdk_anchor_full_derived_test::{AllCompressAsRecord, PackedAllCompressAsRecord}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::{CompressAs, CompressionInfo, Pack}, - instruction::PackedAccounts, -}; +use light_account::{CompressAs, CompressionInfo, Pack}; +use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; use super::shared::CompressibleTestFactory; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_multiple_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_multiple_compress_as_test.rs index 4f37c9f832..9076a915d6 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_multiple_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_multiple_compress_as_test.rs @@ -7,10 +7,8 @@ use csdk_anchor_full_derived_test::{MultipleCompressAsRecord, PackedMultipleCompressAsRecord}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::{CompressAs, CompressionInfo, Pack}, - instruction::PackedAccounts, -}; +use light_account::{CompressAs, CompressionInfo, Pack}; +use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; use super::shared::CompressibleTestFactory; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_no_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_no_compress_as_test.rs index 7a2f81d36b..802d70d585 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_no_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_no_compress_as_test.rs @@ -7,10 +7,8 @@ use csdk_anchor_full_derived_test::{NoCompressAsRecord, PackedNoCompressAsRecord}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::{CompressAs, CompressionInfo, Pack}, - instruction::PackedAccounts, -}; +use light_account::{CompressAs, CompressionInfo, Pack}; +use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; use super::shared::CompressibleTestFactory; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_option_none_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_option_none_compress_as_test.rs index 5b11b4b7c3..1687930de3 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_option_none_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_option_none_compress_as_test.rs @@ -7,10 +7,8 @@ use csdk_anchor_full_derived_test::{OptionNoneCompressAsRecord, PackedOptionNoneCompressAsRecord}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::{CompressAs, CompressionInfo, Pack}, - instruction::PackedAccounts, -}; +use light_account::{CompressAs, CompressionInfo, Pack}; +use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; use super::shared::CompressibleTestFactory; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_single_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_single_compress_as_test.rs index aa1cf3af37..e0683783ff 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_single_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_single_compress_as_test.rs @@ -8,10 +8,8 @@ use csdk_anchor_full_derived_test::{PackedSingleCompressAsRecord, SingleCompressAsRecord}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::{CompressAs, CompressionInfo, Pack}, - instruction::PackedAccounts, -}; +use light_account::{CompressAs, CompressionInfo, Pack}; +use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; use super::shared::CompressibleTestFactory; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_all_composition_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_all_composition_test.rs index d83071f494..2708bbfe53 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_all_composition_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_all_composition_test.rs @@ -11,10 +11,8 @@ use csdk_anchor_full_derived_test::{AllCompositionRecord, PackedAllCompositionRecord}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::{CompressAs, CompressionInfo, Pack}, - instruction::PackedAccounts, -}; +use light_account::{CompressAs, CompressionInfo, Pack}; +use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; use super::shared::CompressibleTestFactory; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_info_last_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_info_last_test.rs index e122ef70c1..eccec16dee 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_info_last_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_info_last_test.rs @@ -10,10 +10,8 @@ use csdk_anchor_full_derived_test::{InfoLastRecord, PackedInfoLastRecord}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::{CompressAs, CompressionInfo, CompressionState, Pack}, - instruction::PackedAccounts, -}; +use light_account::{CompressAs, CompressionInfo, CompressionState, Pack}; +use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; use super::shared::CompressibleTestFactory; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_large_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_large_test.rs index 5f21955680..ca7a10b574 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_large_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_large_test.rs @@ -10,8 +10,8 @@ //! Pack/Unpack traits are NOT generated because there are no Pubkey fields. use csdk_anchor_full_derived_test::LargeRecord; +use light_account::{CompressAs, CompressionInfo}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::interface::{CompressAs, CompressionInfo}; use super::shared::CompressibleTestFactory; use crate::generate_trait_tests; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_minimal_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_minimal_test.rs index 3aea42f537..f37f9c4e95 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_minimal_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_minimal_test.rs @@ -8,8 +8,8 @@ //! MinimalRecord has NO Pubkey fields, so Pack/Unpack traits are NOT generated. use csdk_anchor_full_derived_test::MinimalRecord; +use light_account::{CompressAs, CompressionInfo}; use light_hasher::{DataHasher, Sha256}; -use light_sdk::interface::{CompressAs, CompressionInfo}; use super::shared::CompressibleTestFactory; use crate::generate_trait_tests; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/shared.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/shared.rs index 0c868ccad2..65236f2ac5 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/shared.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/shared.rs @@ -7,10 +7,8 @@ use std::borrow::Cow; use light_account::Size; use light_hasher::{DataHasher, Sha256}; -use light_sdk::{ - compressible::{CompressAs, CompressedInitSpace, CompressionState, HasCompressionInfo}, - LightDiscriminator, -}; +use light_account::{CompressAs, CompressedInitSpace, CompressionState, HasCompressionInfo}; +use light_sdk::LightDiscriminator; // ============================================================================= // Test Factory Trait @@ -125,10 +123,7 @@ pub fn assert_set_compression_info_none_works Self { - use light_sdk::utils::derive_rent_sponsor_pda; + use light_account::derive_rent_sponsor_pda; let program_id = csdk_anchor_full_derived_test::ID; let mut config = ProgramTestConfig::new_v2( diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/shared.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/shared.rs index 69b6c73f1a..83a2fc7356 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/shared.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/shared.rs @@ -1,12 +1,12 @@ #![allow(dead_code)] // Shared test utilities for csdk-anchor-full-derived-test +use light_account::derive_rent_sponsor_pda; use light_client::{indexer::Indexer, interface::InitializeRentFreeConfig, rpc::Rpc}; use light_program_test::{ program_test::{setup_mock_program_data, LightProgramTest}, ProgramTestConfig, }; -use light_sdk::utils::derive_rent_sponsor_pda; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; /// Shared test context for csdk-anchor-full-derived-test @@ -358,9 +358,9 @@ pub async fn setup_create_mint( /// Build expected CompressionInfo, extracting only runtime fields from actual. /// Validates all config-derived fields against expected defaults. pub fn expected_compression_info( - actual: &light_sdk::compressible::CompressionInfo, -) -> light_sdk::compressible::CompressionInfo { - light_sdk::compressible::CompressionInfo { + actual: &light_account::CompressionInfo, +) -> light_account::CompressionInfo { + light_account::CompressionInfo { last_claimed_slot: actual.last_claimed_slot, lamports_per_write: 5000, config_version: 1, diff --git a/sdk-tests/pinocchio-derive-test/src/instruction_accounts.rs b/sdk-tests/pinocchio-derive-test/src/instruction_accounts.rs index cd8b2a90d3..39e415fdc8 100644 --- a/sdk-tests/pinocchio-derive-test/src/instruction_accounts.rs +++ b/sdk-tests/pinocchio-derive-test/src/instruction_accounts.rs @@ -1,8 +1,8 @@ //! Accounts module for single-pda-derive-test. use anchor_lang::prelude::*; -use light_sdk_macros::LightAccounts; use light_account::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_sdk_macros::LightAccounts; use light_sdk_types::{interface::CreateAccountsProof, LIGHT_TOKEN_PROGRAM_ID}; use crate::{ diff --git a/sdk-tests/pinocchio-derive-test/src/lib.rs b/sdk-tests/pinocchio-derive-test/src/lib.rs index 64071f49fb..de847fa24a 100644 --- a/sdk-tests/pinocchio-derive-test/src/lib.rs +++ b/sdk-tests/pinocchio-derive-test/src/lib.rs @@ -96,7 +96,7 @@ pub mod pinocchio_derive_test { params.rent_config, params.write_top_up, params.address_space.iter().map(|p| p.to_bytes()).collect(), - 0, // config_bump + 0, // config_bump &ctx.remaining_accounts[0], // payer &ctx.remaining_accounts[4], // system_program &LIGHT_CPI_SIGNER.program_id, diff --git a/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/read_only.rs b/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/read_only.rs index 154f4e2045..08377f7758 100644 --- a/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/read_only.rs +++ b/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/read_only.rs @@ -8,7 +8,8 @@ use light_program_test::{ use light_sdk::{ address::v1::derive_address, instruction::{ - account_meta::CompressedAccountMetaBurn, PackedAccounts, SystemAccountMetaConfig, + account_meta::CompressedAccountMetaBurn, PackedAccounts, PackedAccountsExt, + SystemAccountMetaConfig, }, }; use light_test_utils::{Rpc, RpcError}; diff --git a/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs b/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs index e19d0742de..e49a8476d6 100644 --- a/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs +++ b/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs @@ -9,7 +9,10 @@ use light_program_test::{ }; use light_sdk::{ address::v1::derive_address, - instruction::{account_meta::CompressedAccountMeta, PackedAccounts, SystemAccountMetaConfig}, + instruction::{ + account_meta::CompressedAccountMeta, PackedAccounts, PackedAccountsExt, + SystemAccountMetaConfig, + }, }; use light_test_utils::{Rpc, RpcError}; use sdk_anchor_test::{MyCompressedAccount, NestedData}; diff --git a/sdk-tests/sdk-native-test/tests/test.rs b/sdk-tests/sdk-native-test/tests/test.rs index f3c5e97d73..fef01c8e62 100644 --- a/sdk-tests/sdk-native-test/tests/test.rs +++ b/sdk-tests/sdk-native-test/tests/test.rs @@ -9,7 +9,8 @@ use light_program_test::{ program_test::LightProgramTest, AddressWithTree, Indexer, ProgramTestConfig, Rpc, RpcError, }; use light_sdk::instruction::{ - account_meta::CompressedAccountMeta, PackedAccounts, SystemAccountMetaConfig, + account_meta::CompressedAccountMeta, PackedAccounts, PackedAccountsExt, + SystemAccountMetaConfig, }; use sdk_native_test::{ create_pda::CreatePdaInstructionData, diff --git a/sdk-tests/sdk-pinocchio-v1-test/tests/test.rs b/sdk-tests/sdk-pinocchio-v1-test/tests/test.rs index 0ae7f5c029..9242ba3f36 100644 --- a/sdk-tests/sdk-pinocchio-v1-test/tests/test.rs +++ b/sdk-tests/sdk-pinocchio-v1-test/tests/test.rs @@ -5,7 +5,7 @@ use light_compressed_account::compressed_account::CompressedAccountWithMerkleCon use light_program_test::{ program_test::LightProgramTest, AddressWithTree, Indexer, ProgramTestConfig, Rpc, RpcError, }; -use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; +use light_sdk::instruction::{PackedAccounts, PackedAccountsExt, SystemAccountMetaConfig}; use light_sdk_pinocchio::instruction::{account_meta::CompressedAccountMeta, PackedStateTreeInfo}; use sdk_pinocchio_v1_test::{ create_pda::CreatePdaInstructionData, diff --git a/sdk-tests/sdk-pinocchio-v2-test/tests/test.rs b/sdk-tests/sdk-pinocchio-v2-test/tests/test.rs index 59a0562c63..5d15f1a53a 100644 --- a/sdk-tests/sdk-pinocchio-v2-test/tests/test.rs +++ b/sdk-tests/sdk-pinocchio-v2-test/tests/test.rs @@ -8,7 +8,7 @@ use light_compressed_account::{ use light_program_test::{ program_test::LightProgramTest, AddressWithTree, Indexer, ProgramTestConfig, Rpc, RpcError, }; -use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; +use light_sdk::instruction::{PackedAccounts, PackedAccountsExt, SystemAccountMetaConfig}; use light_sdk_pinocchio::instruction::{account_meta::CompressedAccountMeta, PackedStateTreeInfo}; use sdk_pinocchio_v2_test::{ create_pda::CreatePdaInstructionData, diff --git a/sdk-tests/sdk-token-test/tests/ctoken_pda.rs b/sdk-tests/sdk-token-test/tests/ctoken_pda.rs index 69a344e04d..9100b6a7f8 100644 --- a/sdk-tests/sdk-token-test/tests/ctoken_pda.rs +++ b/sdk-tests/sdk-token-test/tests/ctoken_pda.rs @@ -5,7 +5,7 @@ use light_compressed_token_sdk::compressed_token::create_compressed_mint::{ derive_mint_compressed_address, find_mint_address, }; use light_program_test::{LightProgramTest, ProgramTestConfig, Rpc, RpcError}; -use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; +use light_sdk::instruction::{PackedAccounts, PackedAccountsExt, SystemAccountMetaConfig}; use light_token_interface::{ instructions::{ extensions::token_metadata::TokenMetadataInstructionData, diff --git a/sdk-tests/sdk-token-test/tests/pda_ctoken.rs b/sdk-tests/sdk-token-test/tests/pda_ctoken.rs index 4c19a08d88..df0d4ec539 100644 --- a/sdk-tests/sdk-token-test/tests/pda_ctoken.rs +++ b/sdk-tests/sdk-token-test/tests/pda_ctoken.rs @@ -8,7 +8,7 @@ use light_compressed_token_sdk::compressed_token::create_compressed_mint::{ derive_mint_compressed_address, find_mint_address, }; use light_program_test::{LightProgramTest, ProgramTestConfig, Rpc, RpcError}; -use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; +use light_sdk::instruction::{PackedAccounts, PackedAccountsExt, SystemAccountMetaConfig}; use light_token::instruction::{ derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, }; diff --git a/sdk-tests/sdk-token-test/tests/test_4_invocations.rs b/sdk-tests/sdk-token-test/tests/test_4_invocations.rs index 9e70170056..1fbeaed92b 100644 --- a/sdk-tests/sdk-token-test/tests/test_4_invocations.rs +++ b/sdk-tests/sdk-token-test/tests/test_4_invocations.rs @@ -9,7 +9,7 @@ use light_compressed_token_sdk::{ use light_program_test::{AddressWithTree, Indexer, LightProgramTest, ProgramTestConfig, Rpc}; use light_sdk::{ address::v1::derive_address, - instruction::{PackedAccounts, SystemAccountMetaConfig}, + instruction::{PackedAccounts, PackedAccountsExt, SystemAccountMetaConfig}, }; use light_test_utils::{ spl::{create_mint_helper, create_token_account, mint_spl_tokens}, diff --git a/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs b/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs index fd978aeba7..84dbcb4351 100644 --- a/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs +++ b/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs @@ -9,7 +9,7 @@ use light_compressed_token_sdk::{ use light_program_test::{AddressWithTree, Indexer, LightProgramTest, ProgramTestConfig, Rpc}; use light_sdk::{ address::v1::derive_address, - instruction::{PackedAccounts, PackedStateTreeInfo, SystemAccountMetaConfig}, + instruction::{PackedAccounts, PackedAccountsExt, PackedStateTreeInfo, SystemAccountMetaConfig}, }; use light_test_utils::RpcError; use light_token::instruction::CreateAssociatedTokenAccount; diff --git a/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs b/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs index badb7dce42..16af41a08a 100644 --- a/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs +++ b/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs @@ -7,7 +7,7 @@ use light_compressed_token_sdk::compressed_token::{ mint_to_compressed::{create_mint_to_compressed_instruction, MintToCompressedInputs}, }; use light_program_test::{Indexer, LightProgramTest, ProgramTestConfig, Rpc}; -use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; +use light_sdk::instruction::{PackedAccounts, PackedAccountsExt, SystemAccountMetaConfig}; use light_test_utils::actions::legacy::instructions::transfer2::create_decompress_instruction; use light_token::instruction::{ config_pda, derive_token_ata, rent_sponsor_pda, CompressibleParams, diff --git a/sdk-tests/sdk-token-test/tests/test_deposit.rs b/sdk-tests/sdk-token-test/tests/test_deposit.rs index 9ebcbd8549..75c0170dcb 100644 --- a/sdk-tests/sdk-token-test/tests/test_deposit.rs +++ b/sdk-tests/sdk-token-test/tests/test_deposit.rs @@ -10,7 +10,10 @@ use light_compressed_token_sdk::{ use light_program_test::{AddressWithTree, Indexer, LightProgramTest, ProgramTestConfig, Rpc}; use light_sdk::{ address::v1::derive_address, - instruction::{account_meta::CompressedAccountMeta, PackedAccounts, SystemAccountMetaConfig}, + instruction::{ + account_meta::CompressedAccountMeta, PackedAccounts, PackedAccountsExt, + SystemAccountMetaConfig, + }, }; use light_test_utils::{ spl::{create_mint_helper, create_token_account, mint_spl_tokens}, diff --git a/sdk-tests/sdk-v1-native-test/tests/test.rs b/sdk-tests/sdk-v1-native-test/tests/test.rs index 16580c9418..86c7381aa4 100644 --- a/sdk-tests/sdk-v1-native-test/tests/test.rs +++ b/sdk-tests/sdk-v1-native-test/tests/test.rs @@ -6,7 +6,8 @@ use light_program_test::{ program_test::LightProgramTest, AddressWithTree, Indexer, ProgramTestConfig, Rpc, RpcError, }; use light_sdk::instruction::{ - account_meta::CompressedAccountMeta, PackedAccounts, SystemAccountMetaConfig, + account_meta::CompressedAccountMeta, PackedAccounts, PackedAccountsExt, + SystemAccountMetaConfig, }; use sdk_native_test::{ create_pda::CreatePdaInstructionData, diff --git a/sdk-tests/single-account-loader-test/tests/test.rs b/sdk-tests/single-account-loader-test/tests/test.rs index bea7332de5..1ff1efcf7c 100644 --- a/sdk-tests/single-account-loader-test/tests/test.rs +++ b/sdk-tests/single-account-loader-test/tests/test.rs @@ -1,6 +1,7 @@ //! Integration test for single AccountLoader (zero-copy) macro validation. use anchor_lang::{InstructionData, ToAccountMetas}; +use light_account::{derive_rent_sponsor_pda, IntoVariant}; use light_client::interface::{ create_load_instructions, get_create_accounts_proof, AccountInterfaceExt, AccountSpec, CreateAccountsProofInput, InitializeRentFreeConfig, PdaSpec, @@ -10,7 +11,6 @@ use light_program_test::{ program_test::{setup_mock_program_data, LightProgramTest, TestRpc}, Indexer, ProgramTestConfig, Rpc, }; -use light_sdk::{interface::IntoVariant, utils::derive_rent_sponsor_pda}; use single_account_loader_test::{ single_account_loader_test::{LightAccountVariant, RecordSeeds}, CreateRecordParams, ZeroCopyRecord, RECORD_SEED, @@ -113,7 +113,7 @@ async fn test_create_zero_copy_record() { assert_eq!(record.counter, 0, "Record counter should be 0"); // Verify compression_info is set (state == Decompressed indicates initialized) - use light_sdk::interface::CompressionState; + use light_account::CompressionState; assert_eq!( record.compression_info.state, CompressionState::Decompressed, @@ -281,7 +281,7 @@ async fn test_zero_copy_record_full_lifecycle() { assert_eq!(record.owner, owner, "Record owner should match"); assert_eq!(record.counter, 0, "Record counter should still be 0"); // state should be Decompressed after decompression - use light_sdk::interface::CompressionState; + use light_account::CompressionState; assert_eq!( record.compression_info.state, CompressionState::Decompressed, diff --git a/sdk-tests/single-ata-test/tests/test.rs b/sdk-tests/single-ata-test/tests/test.rs index 9cfc6d3119..e5dff0f100 100644 --- a/sdk-tests/single-ata-test/tests/test.rs +++ b/sdk-tests/single-ata-test/tests/test.rs @@ -1,12 +1,12 @@ //! Integration test for single ATA macro validation. use anchor_lang::{InstructionData, ToAccountMetas}; +use light_account::derive_rent_sponsor_pda; use light_client::interface::{get_create_accounts_proof, InitializeRentFreeConfig}; use light_program_test::{ program_test::{setup_mock_program_data, LightProgramTest}, Indexer, ProgramTestConfig, Rpc, }; -use light_sdk::utils::derive_rent_sponsor_pda; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use solana_instruction::Instruction; diff --git a/sdk-tests/single-mint-test/tests/test.rs b/sdk-tests/single-mint-test/tests/test.rs index 47a95d42b1..ede75d0504 100644 --- a/sdk-tests/single-mint-test/tests/test.rs +++ b/sdk-tests/single-mint-test/tests/test.rs @@ -1,6 +1,7 @@ //! Integration test for single mint macro validation. use anchor_lang::{InstructionData, ToAccountMetas}; +use light_account::derive_rent_sponsor_pda; use light_client::interface::{ get_create_accounts_proof, CreateAccountsProofInput, InitializeRentFreeConfig, }; @@ -8,7 +9,6 @@ use light_program_test::{ program_test::{setup_mock_program_data, LightProgramTest}, ProgramTestConfig, Rpc, }; -use light_sdk::utils::derive_rent_sponsor_pda; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{find_mint_address, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use solana_instruction::Instruction; diff --git a/sdk-tests/single-pda-test/tests/test.rs b/sdk-tests/single-pda-test/tests/test.rs index 254cef7108..79dc17d0e5 100644 --- a/sdk-tests/single-pda-test/tests/test.rs +++ b/sdk-tests/single-pda-test/tests/test.rs @@ -1,6 +1,7 @@ //! Integration test for single PDA macro validation. use anchor_lang::{InstructionData, ToAccountMetas}; +use light_account::derive_rent_sponsor_pda; use light_client::interface::{ get_create_accounts_proof, CreateAccountsProofInput, InitializeRentFreeConfig, }; @@ -8,7 +9,6 @@ use light_program_test::{ program_test::{setup_mock_program_data, LightProgramTest}, ProgramTestConfig, Rpc, }; -use light_sdk::utils::derive_rent_sponsor_pda; use solana_instruction::Instruction; use solana_keypair::Keypair; use solana_pubkey::Pubkey; diff --git a/sdk-tests/single-token-test/tests/test.rs b/sdk-tests/single-token-test/tests/test.rs index ecb49c02ec..485b4bba7f 100644 --- a/sdk-tests/single-token-test/tests/test.rs +++ b/sdk-tests/single-token-test/tests/test.rs @@ -1,12 +1,12 @@ //! Integration test for single token vault macro validation. use anchor_lang::{InstructionData, ToAccountMetas}; +use light_account::derive_rent_sponsor_pda; use light_client::interface::{get_create_accounts_proof, InitializeRentFreeConfig}; use light_program_test::{ program_test::{setup_mock_program_data, LightProgramTest}, Indexer, ProgramTestConfig, Rpc, }; -use light_sdk::utils::derive_rent_sponsor_pda; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use solana_instruction::Instruction; diff --git a/xtask/src/create_compressible_config.rs b/xtask/src/create_compressible_config.rs index 63783113a1..f4e05b6020 100644 --- a/xtask/src/create_compressible_config.rs +++ b/xtask/src/create_compressible_config.rs @@ -5,7 +5,7 @@ use clap::Parser; use dirs::home_dir; use light_client::rpc::{LightClient, LightClientConfig, Rpc}; use light_compressible::{ - config::COMPRESSIBLE_CONFIG_SEED, + config::LIGHT_CONFIG_SEED, registry_instructions::CreateCompressibleConfig as CreateCompressibleConfigParams, rent::RentConfig, }; @@ -52,7 +52,7 @@ fn get_config_counter_pda() -> (Pubkey, u8) { fn get_compressible_config_pda(version: u16) -> (Pubkey, u8) { Pubkey::find_program_address( - &[COMPRESSIBLE_CONFIG_SEED, &version.to_le_bytes()], + &[LIGHT_CONFIG_SEED, &version.to_le_bytes()], ®ISTRY_PROGRAM_ID, ) } From 902a63ed173e534c4968c11042c038a7890695f4 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 2 Feb 2026 21:28:00 +0000 Subject: [PATCH 11/15] tests work partially --- Cargo.lock | 3 + sdk-libs/account-pinocchio/Cargo.toml | 1 + sdk-libs/account/Cargo.toml | 1 + sdk-tests/pinocchio-derive-test/Cargo.toml | 7 +- sdk-tests/pinocchio-derive-test/src/state.rs | 4 +- .../pinocchio-derive-test/tests/shared/mod.rs | 100 ++++ .../tests/stress_test.rs | 491 ++++++++++++++++++ .../tests/test_create_all.rs | 224 +++++++- .../tests/test_create_ata.rs | 50 +- .../tests/test_create_mint.rs | 64 ++- .../tests/test_create_pda.rs | 64 ++- .../tests/test_create_token_vault.rs | 14 +- .../tests/test_create_two_mints.rs | 101 +++- .../tests/test_create_zero_copy_record.rs | 55 +- 14 files changed, 1142 insertions(+), 37 deletions(-) create mode 100644 sdk-tests/pinocchio-derive-test/tests/stress_test.rs diff --git a/Cargo.lock b/Cargo.lock index eb2b30e271..16ee9426db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5162,6 +5162,7 @@ dependencies = [ "bytemuck", "light-account", "light-anchor-spl", + "light-batched-merkle-tree", "light-client", "light-compressed-account", "light-compressible", @@ -5174,6 +5175,8 @@ dependencies = [ "light-token", "light-token-interface", "light-token-types", + "rand 0.8.5", + "solana-account", "solana-account-info", "solana-instruction", "solana-keypair", diff --git a/sdk-libs/account-pinocchio/Cargo.toml b/sdk-libs/account-pinocchio/Cargo.toml index 75b3f9f087..2ed79fdefa 100644 --- a/sdk-libs/account-pinocchio/Cargo.toml +++ b/sdk-libs/account-pinocchio/Cargo.toml @@ -13,6 +13,7 @@ alloc = ["light-sdk-types/alloc", "light-compressed-account/alloc"] token = ["light-sdk-types/token", "dep:light-token-interface"] poseidon = ["light-sdk-types/poseidon", "light-hasher/poseidon"] sha256 = ["light-sdk-types/sha256", "light-hasher/sha256"] +anchor-discriminator = ["light-sdk-macros/anchor-discriminator"] [dependencies] light-sdk-types = { workspace = true, default-features = false, features = ["alloc", "v2", "cpi-context"] } diff --git a/sdk-libs/account/Cargo.toml b/sdk-libs/account/Cargo.toml index d6e54d5382..81011722d8 100644 --- a/sdk-libs/account/Cargo.toml +++ b/sdk-libs/account/Cargo.toml @@ -14,6 +14,7 @@ token = ["light-sdk-types/token", "dep:light-token-interface"] poseidon = ["light-sdk-types/poseidon", "light-hasher/poseidon"] sha256 = ["light-sdk-types/sha256", "light-hasher/sha256"] anchor = ["light-sdk-types/anchor", "dep:anchor-lang"] +anchor-discriminator = ["light-sdk-macros/anchor-discriminator"] [dependencies] light-sdk-types = { workspace = true, features = ["std", "v2", "cpi-context"] } diff --git a/sdk-tests/pinocchio-derive-test/Cargo.toml b/sdk-tests/pinocchio-derive-test/Cargo.toml index 1a4b27f6f4..143a0d1cce 100644 --- a/sdk-tests/pinocchio-derive-test/Cargo.toml +++ b/sdk-tests/pinocchio-derive-test/Cargo.toml @@ -19,10 +19,10 @@ idl-build = ["anchor-lang/idl-build", "light-anchor-spl/idl-build"] test-sbf = [] [dependencies] -light-account = { workspace = true, features = ["token", "anchor"] } +light-account = { workspace = true, features = ["token", "anchor", "anchor-discriminator"] } light-sdk-types = { workspace = true, features = ["anchor", "v2", "cpi-context"] } light-macros = { workspace = true, features = ["solana"] } -light-sdk-macros = { workspace = true } +light-sdk-macros = { workspace = true, features = ["anchor-discriminator"] } bytemuck = { workspace = true, features = ["derive"] } borsh = { workspace = true } light-compressed-account = { workspace = true, features = ["solana"] } @@ -49,6 +49,9 @@ solana-instruction = { workspace = true } solana-pubkey = { workspace = true } solana-keypair = { workspace = true } solana-signer = { workspace = true } +light-batched-merkle-tree = { workspace = true } +rand = { workspace = true } +solana-account = { workspace = true } [lints.rust.unexpected_cfgs] level = "allow" diff --git a/sdk-tests/pinocchio-derive-test/src/state.rs b/sdk-tests/pinocchio-derive-test/src/state.rs index 504c25dfe9..1ba178a908 100644 --- a/sdk-tests/pinocchio-derive-test/src/state.rs +++ b/sdk-tests/pinocchio-derive-test/src/state.rs @@ -6,7 +6,7 @@ use light_sdk_macros::LightAccount; /// Minimal record struct for testing PDA creation. /// Contains only compression_info and one field. -#[derive(Default, Debug, InitSpace, LightAccount)] +#[derive(Default, Debug, PartialEq, InitSpace, LightAccount)] #[account] pub struct MinimalRecord { pub compression_info: CompressionInfo, @@ -15,7 +15,7 @@ pub struct MinimalRecord { /// A zero-copy account using Pod serialization. /// Used with AccountLoader for efficient on-chain zero-copy access. -#[derive(Default, Debug, LightAccount)] +#[derive(Default, Debug, PartialEq, LightAccount)] #[account(zero_copy)] #[repr(C)] pub struct ZeroCopyRecord { diff --git a/sdk-tests/pinocchio-derive-test/tests/shared/mod.rs b/sdk-tests/pinocchio-derive-test/tests/shared/mod.rs index cb19399778..459d13bdac 100644 --- a/sdk-tests/pinocchio-derive-test/tests/shared/mod.rs +++ b/sdk-tests/pinocchio-derive-test/tests/shared/mod.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use light_account::derive_rent_sponsor_pda; use light_client::interface::InitializeRentFreeConfig; use light_program_test::{ @@ -116,3 +118,101 @@ pub async fn setup_create_mint( (mint, mint_seed) } + +pub async fn assert_onchain_exists(rpc: &mut LightProgramTest, pda: &Pubkey, name: &str) { + assert!( + rpc.get_account(*pda).await.unwrap().is_some(), + "{} account ({}) should exist on-chain", + name, + pda + ); +} + +pub async fn assert_onchain_closed(rpc: &mut LightProgramTest, pda: &Pubkey, name: &str) { + let acc = rpc.get_account(*pda).await.unwrap(); + assert!( + acc.is_none() || acc.unwrap().lamports == 0, + "{} account ({}) should be closed", + name, + pda + ); +} + +pub async fn assert_compressed_token_exists( + rpc: &mut LightProgramTest, + owner: &Pubkey, + expected_amount: u64, + name: &str, +) { + let accs = rpc + .get_compressed_token_accounts_by_owner(owner, None, None) + .await + .unwrap() + .value + .items; + assert!( + !accs.is_empty(), + "{} compressed token account should exist for owner {}", + name, + owner + ); + assert_eq!( + accs[0].token.amount, expected_amount, + "{} token amount mismatch", + name + ); +} + +pub async fn assert_rent_sponsor_paid_for_accounts( + rpc: &mut LightProgramTest, + rent_sponsor: &Pubkey, + rent_sponsor_balance_before: u64, + created_accounts: &[Pubkey], +) { + let rent_sponsor_balance_after = rpc + .get_account(*rent_sponsor) + .await + .expect("get rent sponsor account") + .map(|a| a.lamports) + .unwrap_or(0); + + let mut total_account_lamports = 0u64; + for account in created_accounts { + let account_lamports = rpc + .get_account(*account) + .await + .expect("get created account") + .map(|a| a.lamports) + .unwrap_or(0); + total_account_lamports += account_lamports; + } + + let rent_sponsor_paid = rent_sponsor_balance_before.saturating_sub(rent_sponsor_balance_after); + + assert!( + rent_sponsor_paid >= total_account_lamports, + "Rent sponsor should have paid at least {} lamports for accounts, but only paid {}. \ + Before: {}, After: {}", + total_account_lamports, + rent_sponsor_paid, + rent_sponsor_balance_before, + rent_sponsor_balance_after + ); +} + +pub fn expected_compression_info( + actual: &light_account::CompressionInfo, +) -> light_account::CompressionInfo { + light_account::CompressionInfo { + last_claimed_slot: actual.last_claimed_slot, + lamports_per_write: 5000, + config_version: 1, + state: actual.state, + _padding: 0, + rent_config: light_compressible::rent::RentConfig::default(), + } +} + +pub fn parse_token(data: &[u8]) -> light_token_interface::state::token::Token { + borsh::BorshDeserialize::deserialize(&mut &data[..]).unwrap() +} diff --git a/sdk-tests/pinocchio-derive-test/tests/stress_test.rs b/sdk-tests/pinocchio-derive-test/tests/stress_test.rs new file mode 100644 index 0000000000..e6580d2e17 --- /dev/null +++ b/sdk-tests/pinocchio-derive-test/tests/stress_test.rs @@ -0,0 +1,491 @@ +/// Stress test: 20-iteration compression/decompression cycles for all account types. +/// +/// Tests repeated cycles of: +/// 1. Decompress all accounts +/// 2. Assert cached state matches on-chain state +/// 3. Update cache from on-chain state +/// 4. Compress all accounts (warp forward) +mod shared; + +use anchor_lang::{AnchorDeserialize, InstructionData, ToAccountMetas}; +use light_batched_merkle_tree::{ + initialize_address_tree::InitAddressTreeAccountsInstructionData, + initialize_state_tree::InitStateTreeAccountsInstructionData, +}; +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterface, AccountInterfaceExt, + AccountSpec, ColdContext, CreateAccountsProofInput, PdaSpec, +}; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::{ + program_test::{setup_mock_program_data, LightProgramTest, TestRpc}, + ProgramTestConfig, Rpc, +}; +use light_token_interface::state::{token::Token, Mint}; +use pinocchio_derive_test::{ + CreateAllParams, MinimalRecord, ZeroCopyRecord, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, + RECORD_SEED, VAULT_AUTH_SEED, VAULT_SEED, +}; +use pinocchio_derive_test::{LightAccountVariant, MinimalRecordSeeds, ZeroCopyRecordSeeds}; +use solana_instruction::Instruction; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +/// Stores all derived PDAs +#[allow(dead_code)] +struct TestPdas { + record: Pubkey, + zc_record: Pubkey, + ata: Pubkey, + ata_owner: Pubkey, + ata_mint: Pubkey, + vault: Pubkey, + vault_authority: Pubkey, + vault_mint: Pubkey, + mint_a: Pubkey, + mint_b: Pubkey, +} + +/// Cached state for accounts that go through the compress/decompress cycle. +/// Note: vault stays compressed permanently (standalone token PDA decompression +/// is not supported), so it is excluded from the cycle. +#[derive(Clone)] +struct CachedState { + record: MinimalRecord, + zc_record: ZeroCopyRecord, + ata_token: Token, + owner: Pubkey, +} + +/// Test context +struct StressTestContext { + rpc: LightProgramTest, + payer: Keypair, + config_pda: Pubkey, + program_id: Pubkey, +} + +fn parse_token(data: &[u8]) -> Token { + borsh::BorshDeserialize::deserialize(&mut &data[..]).unwrap() +} + +/// Setup environment with larger queues for stress test +async fn setup() -> (StressTestContext, TestPdas) { + let program_id = pinocchio_derive_test::ID; + let mut config = + ProgramTestConfig::new_v2(true, Some(vec![("pinocchio_derive_test", program_id)])) + .with_light_protocol_events(); + config.v2_state_tree_config = Some(InitStateTreeAccountsInstructionData::e2e_test_default()); + config.v2_address_tree_config = + Some(InitAddressTreeAccountsInstructionData::e2e_test_default()); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); + + let (rent_sponsor, _) = light_account::derive_rent_sponsor_pda(&program_id); + + let (init_config_ix, config_pda) = light_client::interface::InitializeRentFreeConfig::new( + &program_id, + &payer.pubkey(), + &program_data_pda, + rent_sponsor, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .expect("Initialize config should succeed"); + + // Setup pre-existing mints for ATA and vault + let (ata_mint, _) = shared::setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; + let (vault_mint, _) = shared::setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; + + let owner = Keypair::new().pubkey(); + let authority = Keypair::new(); + + // Derive all PDAs + let (record_pda, _) = + Pubkey::find_program_address(&[b"minimal_record", owner.as_ref()], &program_id); + let (zc_record_pda, _) = + Pubkey::find_program_address(&[RECORD_SEED, owner.as_ref()], &program_id); + let ata_owner = payer.pubkey(); + let (ata, ata_bump) = light_token::instruction::derive_token_ata(&ata_owner, &ata_mint); + let (vault_authority, _) = Pubkey::find_program_address(&[VAULT_AUTH_SEED], &program_id); + let (vault, vault_bump) = + Pubkey::find_program_address(&[VAULT_SEED, vault_mint.as_ref()], &program_id); + let (mint_signer_a, mint_signer_bump_a) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED_A, authority.pubkey().as_ref()], + &program_id, + ); + let (mint_a_pda, _) = light_token::instruction::find_mint_address(&mint_signer_a); + let (mint_signer_b, mint_signer_bump_b) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED_B, authority.pubkey().as_ref()], + &program_id, + ); + let (mint_b_pda, _) = light_token::instruction::find_mint_address(&mint_signer_b); + + // Create all accounts in one instruction + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![ + CreateAccountsProofInput::pda(record_pda), + CreateAccountsProofInput::pda(zc_record_pda), + CreateAccountsProofInput::mint(mint_signer_a), + CreateAccountsProofInput::mint(mint_signer_b), + ], + ) + .await + .unwrap(); + + let accounts = pinocchio_derive_test::accounts::CreateAll { + fee_payer: payer.pubkey(), + compression_config: config_pda, + pda_rent_sponsor: rent_sponsor, + record: record_pda, + zero_copy_record: zc_record_pda, + ata_mint, + ata_owner, + ata, + vault_mint, + vault_authority, + vault, + authority: authority.pubkey(), + mint_signer_a, + mint_a: mint_a_pda, + mint_signer_b, + mint_b: mint_b_pda, + light_token_config: light_token::instruction::LIGHT_TOKEN_CONFIG, + light_token_rent_sponsor: light_token::instruction::LIGHT_TOKEN_RENT_SPONSOR, + light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), + light_token_program: light_sdk_types::LIGHT_TOKEN_PROGRAM_ID.into(), + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = pinocchio_derive_test::instruction::CreateAll { + params: CreateAllParams { + create_accounts_proof: proof_result.create_accounts_proof, + owner, + ata_bump, + vault_bump, + mint_signer_bump_a, + mint_signer_bump_b, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority]) + .await + .expect("CreateAll should succeed"); + + let pdas = TestPdas { + record: record_pda, + zc_record: zc_record_pda, + ata, + ata_owner, + ata_mint, + vault, + vault_authority, + vault_mint, + mint_a: mint_a_pda, + mint_b: mint_b_pda, + }; + + let ctx = StressTestContext { + rpc, + payer, + config_pda, + program_id, + }; + + (ctx, pdas) +} + +/// Re-read all on-chain accounts into the cache +async fn refresh_cache( + rpc: &mut LightProgramTest, + pdas: &TestPdas, + owner: Pubkey, +) -> CachedState { + let record_account = rpc.get_account(pdas.record).await.unwrap().unwrap(); + let record: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &record_account.data[8..]).unwrap(); + + let zc_account = rpc.get_account(pdas.zc_record).await.unwrap().unwrap(); + let zc_record: ZeroCopyRecord = *bytemuck::from_bytes(&zc_account.data[8..]); + + let ata_token = parse_token(&rpc.get_account(pdas.ata).await.unwrap().unwrap().data); + + CachedState { + record, + zc_record, + ata_token, + owner, + } +} + +/// Decompress all accounts except vault (vault stays compressed permanently) +async fn decompress_all(ctx: &mut StressTestContext, pdas: &TestPdas, cached: &CachedState) { + // PDA: MinimalRecord + let record_interface = ctx + .rpc + .get_account_interface(&pdas.record, &ctx.program_id) + .await + .expect("failed to get MinimalRecord interface"); + assert!(record_interface.is_cold(), "MinimalRecord should be cold"); + + let record_data = MinimalRecord::deserialize(&mut &record_interface.account.data[8..]) + .expect("Failed to parse MinimalRecord"); + let record_variant = LightAccountVariant::MinimalRecord { + seeds: MinimalRecordSeeds { + owner: cached.owner, + }, + data: record_data, + }; + let record_spec = PdaSpec::new(record_interface, record_variant, ctx.program_id); + + // PDA: ZeroCopyRecord + let zc_interface = ctx + .rpc + .get_account_interface(&pdas.zc_record, &ctx.program_id) + .await + .expect("failed to get ZeroCopyRecord interface"); + assert!(zc_interface.is_cold(), "ZeroCopyRecord should be cold"); + + let zc_data = ZeroCopyRecord::deserialize(&mut &zc_interface.account.data[8..]) + .expect("Failed to parse ZeroCopyRecord"); + let zc_variant = LightAccountVariant::ZeroCopyRecord { + seeds: ZeroCopyRecordSeeds { + owner: cached.owner, + }, + data: zc_data, + }; + let zc_spec = PdaSpec::new(zc_interface, zc_variant, ctx.program_id); + + // ATA + let ata_interface = ctx + .rpc + .get_ata_interface(&pdas.ata_owner, &pdas.ata_mint) + .await + .expect("failed to get ATA interface"); + assert!(ata_interface.is_cold(), "ATA should be cold"); + + // Mint A + let mint_a_iface = ctx + .rpc + .get_mint_interface(&pdas.mint_a) + .await + .expect("failed to get mint A interface"); + assert!(mint_a_iface.is_cold(), "Mint A should be cold"); + let (compressed_a, _) = mint_a_iface + .compressed() + .expect("cold mint A must have compressed data"); + let mint_a_ai = AccountInterface { + key: pdas.mint_a, + account: solana_account::Account { + lamports: 0, + data: vec![], + owner: light_token::instruction::LIGHT_TOKEN_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }, + cold: Some(ColdContext::Account(compressed_a.clone())), + }; + + // Mint B + let mint_b_iface = ctx + .rpc + .get_mint_interface(&pdas.mint_b) + .await + .expect("failed to get mint B interface"); + assert!(mint_b_iface.is_cold(), "Mint B should be cold"); + let (compressed_b, _) = mint_b_iface + .compressed() + .expect("cold mint B must have compressed data"); + let mint_b_ai = AccountInterface { + key: pdas.mint_b, + account: solana_account::Account { + lamports: 0, + data: vec![], + owner: light_token::instruction::LIGHT_TOKEN_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }, + cold: Some(ColdContext::Account(compressed_b.clone())), + }; + + let specs: Vec> = vec![ + AccountSpec::Pda(record_spec), + AccountSpec::Pda(zc_spec), + AccountSpec::Ata(ata_interface), + AccountSpec::Mint(mint_a_ai), + AccountSpec::Mint(mint_b_ai), + ]; + + let decompress_ixs = + create_load_instructions(&specs, ctx.payer.pubkey(), ctx.config_pda, &ctx.rpc) + .await + .expect("create_load_instructions should succeed"); + + ctx.rpc + .create_and_send_transaction(&decompress_ixs, &ctx.payer.pubkey(), &[&ctx.payer]) + .await + .expect("Decompression should succeed"); + + // Verify decompressed accounts exist on-chain (vault stays compressed) + for (pda, name) in [ + (&pdas.record, "MinimalRecord"), + (&pdas.zc_record, "ZeroCopyRecord"), + (&pdas.ata, "ATA"), + (&pdas.mint_a, "MintA"), + (&pdas.mint_b, "MintB"), + ] { + shared::assert_onchain_exists(&mut ctx.rpc, pda, name).await; + } + shared::assert_onchain_closed(&mut ctx.rpc, &pdas.vault, "Vault").await; +} + +/// Compress all accounts by warping forward epochs +async fn compress_all(ctx: &mut StressTestContext, pdas: &TestPdas) { + ctx.rpc + .warp_slot_forward(SLOTS_PER_EPOCH * 100) + .await + .unwrap(); + + for (pda, name) in [ + (&pdas.record, "MinimalRecord"), + (&pdas.zc_record, "ZeroCopyRecord"), + (&pdas.ata, "ATA"), + (&pdas.vault, "Vault"), + (&pdas.mint_a, "MintA"), + (&pdas.mint_b, "MintB"), + ] { + shared::assert_onchain_closed(&mut ctx.rpc, pda, name).await; + } +} + +/// Full-struct assertions for all accounts against cached state +async fn assert_all_state( + rpc: &mut LightProgramTest, + pdas: &TestPdas, + cached: &CachedState, + iteration: usize, +) { + // MinimalRecord + let account = rpc.get_account(pdas.record).await.unwrap().unwrap(); + let actual_record: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &account.data[8..]).unwrap(); + let expected_record = MinimalRecord { + compression_info: shared::expected_compression_info(&actual_record.compression_info), + ..cached.record.clone() + }; + assert_eq!( + actual_record, expected_record, + "MinimalRecord mismatch at iteration {iteration}" + ); + + // ZeroCopyRecord + let account = rpc.get_account(pdas.zc_record).await.unwrap().unwrap(); + let actual_zc: &ZeroCopyRecord = bytemuck::from_bytes(&account.data[8..]); + let expected_zc = ZeroCopyRecord { + compression_info: shared::expected_compression_info(&actual_zc.compression_info), + ..cached.zc_record + }; + assert_eq!( + *actual_zc, expected_zc, + "ZeroCopyRecord mismatch at iteration {iteration}" + ); + + // ATA + let actual_ata = parse_token(&rpc.get_account(pdas.ata).await.unwrap().unwrap().data); + let expected_ata = Token { + extensions: actual_ata.extensions.clone(), + ..cached.ata_token.clone() + }; + assert_eq!( + actual_ata, expected_ata, + "ATA mismatch at iteration {iteration}" + ); + + // Mints + let actual_mint_a: Mint = borsh::BorshDeserialize::deserialize( + &mut &rpc.get_account(pdas.mint_a).await.unwrap().unwrap().data[..], + ) + .unwrap(); + assert_eq!( + actual_mint_a.base.decimals, 9, + "Mint A decimals mismatch at iteration {iteration}" + ); + + let actual_mint_b: Mint = borsh::BorshDeserialize::deserialize( + &mut &rpc.get_account(pdas.mint_b).await.unwrap().unwrap().data[..], + ) + .unwrap(); + assert_eq!( + actual_mint_b.base.decimals, 6, + "Mint B decimals mismatch at iteration {iteration}" + ); +} + +#[tokio::test] +async fn test_stress_20_iterations() { + let (mut ctx, pdas) = setup().await; + + // Verify initial creation + for (pda, name) in [ + (&pdas.record, "MinimalRecord"), + (&pdas.zc_record, "ZeroCopyRecord"), + (&pdas.ata, "ATA"), + (&pdas.vault, "Vault"), + (&pdas.mint_a, "MintA"), + (&pdas.mint_b, "MintB"), + ] { + shared::assert_onchain_exists(&mut ctx.rpc, pda, name).await; + } + + // Cache initial state + let owner = { + let account = ctx.rpc.get_account(pdas.record).await.unwrap().unwrap(); + let record: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &account.data[8..]).unwrap(); + record.owner + }; + let mut cached = refresh_cache(&mut ctx.rpc, &pdas, owner).await; + + // First compression + compress_all(&mut ctx, &pdas).await; + + // Main loop: 20 iterations + for i in 0..20 { + println!("--- Iteration {i} ---"); + + // Decompress all + decompress_all(&mut ctx, &pdas, &cached).await; + + // Assert all cached state + assert_all_state(&mut ctx.rpc, &pdas, &cached, i).await; + + // Update cache after decompression (compression_info changes) + cached = refresh_cache(&mut ctx.rpc, &pdas, owner).await; + + // Compress all + compress_all(&mut ctx, &pdas).await; + + println!(" iteration {i} complete"); + } + + println!("All 20 iterations completed successfully."); +} diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_all.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_all.rs index 1c2d512195..a4f87994e1 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_all.rs +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_all.rs @@ -1,13 +1,19 @@ mod shared; -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_client::interface::{get_create_accounts_proof, CreateAccountsProofInput}; +use anchor_lang::{AnchorDeserialize, InstructionData, ToAccountMetas}; +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterface, AccountInterfaceExt, + AccountSpec, ColdContext, CreateAccountsProofInput, PdaSpec, +}; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::program_test::TestRpc; use light_program_test::Rpc; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; use pinocchio_derive_test::{ - CreateAllParams, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, RECORD_SEED, VAULT_AUTH_SEED, - VAULT_SEED, + CreateAllParams, MinimalRecord, ZeroCopyRecord, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, + RECORD_SEED, VAULT_AUTH_SEED, VAULT_SEED, }; use solana_instruction::Instruction; use solana_keypair::Keypair; @@ -122,39 +128,35 @@ async fn test_create_all_derive() { .await .expect("CreateAll should succeed"); - // Verify PDA + // PHASE 1: Verify all accounts on-chain after creation + use light_compressed_account::pubkey::Pubkey as LPubkey; + let record_account = rpc .get_account(record_pda) .await .unwrap() .expect("Record PDA should exist"); - use pinocchio_derive_test::MinimalRecord; let record: MinimalRecord = borsh::BorshDeserialize::deserialize(&mut &record_account.data[8..]) .expect("Failed to deserialize MinimalRecord"); assert_eq!(record.owner, owner, "Record owner should match"); - // Verify zero-copy let zc_account = rpc .get_account(zc_record_pda) .await .unwrap() .expect("Zero-copy record should exist"); - use pinocchio_derive_test::ZeroCopyRecord; let zc_record: &ZeroCopyRecord = bytemuck::from_bytes(&zc_account.data[8..]); assert_eq!(zc_record.owner, owner, "ZC record owner should match"); assert_eq!(zc_record.counter, 0, "ZC record counter should be 0"); - // Verify ATA let ata_account = rpc .get_account(ata) .await .unwrap() .expect("ATA should exist"); - use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; let ata_token: Token = borsh::BorshDeserialize::deserialize(&mut &ata_account.data[..]) .expect("Failed to deserialize ATA Token"); - use light_compressed_account::pubkey::Pubkey as LPubkey; assert_eq!( ata_token.mint, LPubkey::from(ata_mint.to_bytes()), @@ -166,7 +168,6 @@ async fn test_create_all_derive() { "ATA owner should match" ); - // Verify vault let vault_account = rpc .get_account(vault) .await @@ -185,18 +186,17 @@ async fn test_create_all_derive() { "Vault owner should match" ); - // Verify mint A + use light_token_interface::state::Mint; + let mint_a_account = rpc .get_account(mint_a_pda) .await .unwrap() .expect("Mint A should exist"); - use light_token_interface::state::Mint; let mint_a: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_a_account.data[..]) .expect("Failed to deserialize Mint A"); assert_eq!(mint_a.base.decimals, 9, "Mint A should have 9 decimals"); - // Verify mint B let mint_b_account = rpc .get_account(mint_b_pda) .await @@ -205,4 +205,198 @@ async fn test_create_all_derive() { let mint_b: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_b_account.data[..]) .expect("Failed to deserialize Mint B"); assert_eq!(mint_b.base.decimals, 6, "Mint B should have 6 decimals"); + + // PHASE 2: Warp to trigger auto-compression + rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); + + shared::assert_onchain_closed(&mut rpc, &record_pda, "MinimalRecord").await; + shared::assert_onchain_closed(&mut rpc, &zc_record_pda, "ZeroCopyRecord").await; + shared::assert_onchain_closed(&mut rpc, &ata, "ATA").await; + shared::assert_onchain_closed(&mut rpc, &vault, "Vault").await; + shared::assert_onchain_closed(&mut rpc, &mint_a_pda, "MintA").await; + shared::assert_onchain_closed(&mut rpc, &mint_b_pda, "MintB").await; + + // PHASE 3: Decompress all accounts except vault via create_load_instructions. + // Note: standalone token PDA decompression is not supported; vaults stay compressed. + use pinocchio_derive_test::{ + LightAccountVariant, MinimalRecordSeeds, ZeroCopyRecordSeeds, + }; + + // PDA: MinimalRecord + let record_interface = rpc + .get_account_interface(&record_pda, &program_id) + .await + .expect("failed to get MinimalRecord interface"); + assert!(record_interface.is_cold(), "MinimalRecord should be cold"); + + let record_data = MinimalRecord::deserialize(&mut &record_interface.account.data[8..]) + .expect("Failed to parse MinimalRecord"); + let record_variant = LightAccountVariant::MinimalRecord { + seeds: MinimalRecordSeeds { owner }, + data: record_data, + }; + let record_spec = PdaSpec::new(record_interface, record_variant, program_id); + + // PDA: ZeroCopyRecord + let zc_interface = rpc + .get_account_interface(&zc_record_pda, &program_id) + .await + .expect("failed to get ZeroCopyRecord interface"); + assert!(zc_interface.is_cold(), "ZeroCopyRecord should be cold"); + + let zc_data = ZeroCopyRecord::deserialize(&mut &zc_interface.account.data[8..]) + .expect("Failed to parse ZeroCopyRecord"); + let zc_variant = LightAccountVariant::ZeroCopyRecord { + seeds: ZeroCopyRecordSeeds { owner }, + data: zc_data, + }; + let zc_spec = PdaSpec::new(zc_interface, zc_variant, program_id); + + // ATA + let ata_interface = rpc + .get_ata_interface(&ata_owner, &ata_mint) + .await + .expect("failed to get ATA interface"); + assert!(ata_interface.is_cold(), "ATA should be cold"); + + // Mint A + let mint_a_iface = rpc + .get_mint_interface(&mint_a_pda) + .await + .expect("failed to get mint A interface"); + assert!(mint_a_iface.is_cold(), "Mint A should be cold"); + let (compressed_a, _) = mint_a_iface + .compressed() + .expect("cold mint A must have compressed data"); + let mint_a_ai = AccountInterface { + key: mint_a_pda, + account: solana_account::Account { + lamports: 0, + data: vec![], + owner: light_token::instruction::LIGHT_TOKEN_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }, + cold: Some(ColdContext::Account(compressed_a.clone())), + }; + + // Mint B + let mint_b_iface = rpc + .get_mint_interface(&mint_b_pda) + .await + .expect("failed to get mint B interface"); + assert!(mint_b_iface.is_cold(), "Mint B should be cold"); + let (compressed_b, _) = mint_b_iface + .compressed() + .expect("cold mint B must have compressed data"); + let mint_b_ai = AccountInterface { + key: mint_b_pda, + account: solana_account::Account { + lamports: 0, + data: vec![], + owner: light_token::instruction::LIGHT_TOKEN_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }, + cold: Some(ColdContext::Account(compressed_b.clone())), + }; + + let specs: Vec> = vec![ + AccountSpec::Pda(record_spec), + AccountSpec::Pda(zc_spec), + AccountSpec::Ata(ata_interface), + AccountSpec::Mint(mint_a_ai), + AccountSpec::Mint(mint_b_ai), + ]; + + let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) + .await + .expect("create_load_instructions should succeed"); + + rpc.create_and_send_transaction(&ixs, &payer.pubkey(), &[&payer]) + .await + .expect("Decompression should succeed"); + + // PHASE 4: Assert state preserved after decompression (vault stays compressed) + shared::assert_onchain_exists(&mut rpc, &record_pda, "MinimalRecord").await; + shared::assert_onchain_exists(&mut rpc, &zc_record_pda, "ZeroCopyRecord").await; + shared::assert_onchain_exists(&mut rpc, &ata, "ATA").await; + shared::assert_onchain_closed(&mut rpc, &vault, "Vault").await; + shared::assert_onchain_exists(&mut rpc, &mint_a_pda, "MintA").await; + shared::assert_onchain_exists(&mut rpc, &mint_b_pda, "MintB").await; + + // MinimalRecord + let account = rpc.get_account(record_pda).await.unwrap().unwrap(); + let actual_record: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &account.data[8..]).unwrap(); + let expected_record = MinimalRecord { + compression_info: shared::expected_compression_info(&actual_record.compression_info), + owner, + }; + assert_eq!( + actual_record, expected_record, + "MinimalRecord should match after decompression" + ); + + // ZeroCopyRecord + let account = rpc.get_account(zc_record_pda).await.unwrap().unwrap(); + let actual_zc: &ZeroCopyRecord = bytemuck::from_bytes(&account.data[8..]); + let expected_zc = ZeroCopyRecord { + compression_info: shared::expected_compression_info(&actual_zc.compression_info), + owner, + counter: 0, + }; + assert_eq!( + *actual_zc, expected_zc, + "ZeroCopyRecord should match after decompression" + ); + + // ATA + let actual_ata: Token = + shared::parse_token(&rpc.get_account(ata).await.unwrap().unwrap().data); + let expected_ata = Token { + mint: LPubkey::from(ata_mint.to_bytes()), + owner: LPubkey::from(ata_owner.to_bytes()), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT, + extensions: actual_ata.extensions.clone(), + }; + assert_eq!( + actual_ata, expected_ata, + "ATA should match after decompression" + ); + + // Mints + let actual_ma: Mint = borsh::BorshDeserialize::deserialize( + &mut &rpc.get_account(mint_a_pda).await.unwrap().unwrap().data[..], + ) + .unwrap(); + assert_eq!( + actual_ma.base.decimals, 9, + "Mint A decimals should be preserved" + ); + assert_eq!( + actual_ma.base.mint_authority, + Some(payer.pubkey().to_bytes().into()), + "Mint A authority should be preserved" + ); + + let actual_mb: Mint = borsh::BorshDeserialize::deserialize( + &mut &rpc.get_account(mint_b_pda).await.unwrap().unwrap().data[..], + ) + .unwrap(); + assert_eq!( + actual_mb.base.decimals, 6, + "Mint B decimals should be preserved" + ); + assert_eq!( + actual_mb.base.mint_authority, + Some(payer.pubkey().to_bytes().into()), + "Mint B authority should be preserved" + ); } diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_ata.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_ata.rs index 8078ad1b7b..6a22995ba2 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_ata.rs +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_ata.rs @@ -1,7 +1,11 @@ mod shared; use anchor_lang::{InstructionData, ToAccountMetas}; -use light_client::interface::get_create_accounts_proof; +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterfaceExt, AccountSpec, +}; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::program_test::TestRpc; use light_program_test::Rpc; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; @@ -57,6 +61,7 @@ async fn test_create_ata_derive() { .await .expect("CreateAta should succeed"); + // PHASE 1: Verify on-chain after creation let ata_account = rpc .get_account(ata) .await @@ -84,4 +89,47 @@ async fn test_create_ata_derive() { token, expected_token, "ATA should match expected after creation" ); + + // PHASE 2: Warp to trigger auto-compression + rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); + shared::assert_onchain_closed(&mut rpc, &ata, "ATA").await; + + // PHASE 3: Decompress via create_load_instructions + use pinocchio_derive_test::LightAccountVariant; + + let ata_interface = rpc + .get_ata_interface(&ata_owner, &mint) + .await + .expect("failed to get ATA interface"); + assert!(ata_interface.is_cold(), "ATA should be cold"); + + let specs: Vec> = vec![AccountSpec::Ata(ata_interface)]; + + let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) + .await + .expect("create_load_instructions should succeed"); + + rpc.create_and_send_transaction(&ixs, &payer.pubkey(), &[&payer]) + .await + .expect("Decompression should succeed"); + + // PHASE 4: Assert state preserved after decompression + shared::assert_onchain_exists(&mut rpc, &ata, "ATA").await; + + let actual: Token = shared::parse_token( + &rpc.get_account(ata).await.unwrap().unwrap().data, + ); + let expected = Token { + mint: mint.to_bytes().into(), + owner: ata_owner.to_bytes().into(), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT, + extensions: actual.extensions.clone(), + }; + assert_eq!(actual, expected, "ATA should match after decompression"); } diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_mint.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_mint.rs index 74ca1d87f3..6b1b1b7347 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_mint.rs +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_mint.rs @@ -1,7 +1,12 @@ mod shared; use anchor_lang::{InstructionData, ToAccountMetas}; -use light_client::interface::{get_create_accounts_proof, CreateAccountsProofInput}; +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterface, AccountInterfaceExt, + AccountSpec, ColdContext, CreateAccountsProofInput, +}; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::program_test::TestRpc; use light_program_test::Rpc; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; @@ -69,6 +74,7 @@ async fn test_create_mint_derive() { .await .expect("CreateMint should succeed"); + // PHASE 1: Verify on-chain after creation let mint_account = rpc .get_account(mint_pda) .await @@ -85,4 +91,60 @@ async fn test_create_mint_derive() { Some(payer.pubkey().to_bytes().into()), "Mint authority should be fee_payer" ); + + // PHASE 2: Warp to trigger auto-compression + rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); + shared::assert_onchain_closed(&mut rpc, &mint_pda, "Mint").await; + + // PHASE 3: Decompress via create_load_instructions + use pinocchio_derive_test::LightAccountVariant; + + let mint_interface = rpc + .get_mint_interface(&mint_pda) + .await + .expect("failed to get mint interface"); + assert!(mint_interface.is_cold(), "Mint should be cold"); + + let (compressed, _mint_data) = mint_interface + .compressed() + .expect("cold mint must have compressed data"); + let mint_account_interface = AccountInterface { + key: mint_pda, + account: solana_account::Account { + lamports: 0, + data: vec![], + owner: light_token::instruction::LIGHT_TOKEN_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }, + cold: Some(ColdContext::Account(compressed.clone())), + }; + + let specs: Vec> = + vec![AccountSpec::Mint(mint_account_interface)]; + + let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) + .await + .expect("create_load_instructions should succeed"); + + rpc.create_and_send_transaction(&ixs, &payer.pubkey(), &[&payer]) + .await + .expect("Decompression should succeed"); + + // PHASE 4: Assert state preserved after decompression + shared::assert_onchain_exists(&mut rpc, &mint_pda, "Mint").await; + + let actual: Mint = borsh::BorshDeserialize::deserialize( + &mut &rpc.get_account(mint_pda).await.unwrap().unwrap().data[..], + ) + .unwrap(); + assert_eq!( + actual.base.decimals, 9, + "Mint decimals should be preserved" + ); + assert_eq!( + actual.base.mint_authority, + Some(payer.pubkey().to_bytes().into()), + "Mint authority should be preserved" + ); } diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_pda.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_pda.rs index e6459fba19..7d48dc0e33 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_pda.rs +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_pda.rs @@ -1,7 +1,12 @@ mod shared; -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_client::interface::{get_create_accounts_proof, CreateAccountsProofInput}; +use anchor_lang::{AnchorDeserialize, InstructionData, ToAccountMetas}; +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterfaceExt, AccountSpec, + CreateAccountsProofInput, PdaSpec, +}; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::program_test::TestRpc; use light_program_test::Rpc; use pinocchio_derive_test::CreatePdaParams; use solana_instruction::Instruction; @@ -58,6 +63,7 @@ async fn test_create_single_pda_derive() { .await .expect("CreatePda should succeed"); + // PHASE 1: Verify on-chain after creation let record_account = rpc .get_account(record_pda) .await @@ -69,9 +75,55 @@ async fn test_create_single_pda_derive() { borsh::BorshDeserialize::deserialize(&mut &record_account.data[8..]) .expect("Failed to deserialize MinimalRecord"); - assert_eq!(record.owner, owner, "Record owner should match"); - assert!( - !record.compression_info.is_compressed(), - "Record should be in decompressed state" + let expected = MinimalRecord { + compression_info: shared::expected_compression_info(&record.compression_info), + owner, + }; + assert_eq!(record, expected, "MinimalRecord should match after creation"); + + // PHASE 2: Warp to trigger auto-compression + rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); + shared::assert_onchain_closed(&mut rpc, &record_pda, "MinimalRecord").await; + + // PHASE 3: Decompress via create_load_instructions + use pinocchio_derive_test::{LightAccountVariant, MinimalRecordSeeds}; + + let account_interface = rpc + .get_account_interface(&record_pda, &program_id) + .await + .expect("failed to get MinimalRecord interface"); + assert!(account_interface.is_cold(), "MinimalRecord should be cold"); + + let data = MinimalRecord::deserialize(&mut &account_interface.account.data[8..]) + .expect("Failed to parse MinimalRecord from interface"); + let variant = LightAccountVariant::MinimalRecord { + seeds: MinimalRecordSeeds { owner }, + data, + }; + + let spec = PdaSpec::new(account_interface, variant, program_id); + let specs: Vec> = vec![AccountSpec::Pda(spec)]; + + let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) + .await + .expect("create_load_instructions should succeed"); + + rpc.create_and_send_transaction(&ixs, &payer.pubkey(), &[&payer]) + .await + .expect("Decompression should succeed"); + + // PHASE 4: Assert state preserved after decompression + shared::assert_onchain_exists(&mut rpc, &record_pda, "MinimalRecord").await; + + let account = rpc.get_account(record_pda).await.unwrap().unwrap(); + let actual: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &account.data[8..]).unwrap(); + let expected = MinimalRecord { + compression_info: shared::expected_compression_info(&actual.compression_info), + owner, + }; + assert_eq!( + actual, expected, + "MinimalRecord should match after decompression" ); } diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_token_vault.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_token_vault.rs index 476dac9ffd..0b2037e658 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_token_vault.rs +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_token_vault.rs @@ -2,6 +2,8 @@ mod shared; use anchor_lang::{InstructionData, ToAccountMetas}; use light_client::interface::get_create_accounts_proof; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::program_test::TestRpc; use light_program_test::Rpc; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; @@ -10,6 +12,10 @@ use solana_instruction::Instruction; use solana_pubkey::Pubkey; use solana_signer::Signer; +/// Token vault lifecycle: create -> verify on-chain -> warp -> verify compressed. +/// Note: standalone token PDA decompression is not supported (token_accounts_offset=0); +/// vaults must be decompressed alongside a regular PDA. See csdk d11_zero_copy_test +/// for the reference pattern. #[tokio::test] async fn test_create_token_vault_derive() { let env = shared::setup_test_env().await; @@ -61,13 +67,15 @@ async fn test_create_token_vault_derive() { .await .expect("CreateTokenVault should succeed"); + // PHASE 1: Verify on-chain after creation + use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; + let vault_account = rpc .get_account(vault) .await .unwrap() .expect("Token vault should exist on-chain"); - use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; let token: Token = borsh::BorshDeserialize::deserialize(&mut &vault_account.data[..]) .expect("Failed to deserialize Token"); @@ -88,4 +96,8 @@ async fn test_create_token_vault_derive() { token, expected_token, "Token vault should match expected after creation" ); + + // PHASE 2: Warp to trigger auto-compression + rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); + shared::assert_onchain_closed(&mut rpc, &vault, "Vault").await; } diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_two_mints.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_two_mints.rs index c58d23896b..72d4b8158b 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_two_mints.rs +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_two_mints.rs @@ -1,7 +1,12 @@ mod shared; use anchor_lang::{InstructionData, ToAccountMetas}; -use light_client::interface::{get_create_accounts_proof, CreateAccountsProofInput}; +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterface, AccountInterfaceExt, + AccountSpec, ColdContext, CreateAccountsProofInput, +}; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::program_test::TestRpc; use light_program_test::Rpc; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; @@ -80,17 +85,16 @@ async fn test_create_two_mints_derive() { .await .expect("CreateTwoMints should succeed"); - // Verify mint A + // PHASE 1: Verify on-chain after creation + use light_token_interface::state::Mint; + let mint_a_account = rpc .get_account(mint_a_pda) .await .unwrap() .expect("Mint A should exist on-chain"); - - use light_token_interface::state::Mint; let mint_a: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_a_account.data[..]) .expect("Failed to deserialize Mint A"); - assert_eq!(mint_a.base.decimals, 9, "Mint A should have 9 decimals"); assert_eq!( mint_a.base.mint_authority, @@ -98,20 +102,101 @@ async fn test_create_two_mints_derive() { "Mint A authority should be fee_payer" ); - // Verify mint B let mint_b_account = rpc .get_account(mint_b_pda) .await .unwrap() .expect("Mint B should exist on-chain"); - let mint_b: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_b_account.data[..]) .expect("Failed to deserialize Mint B"); - assert_eq!(mint_b.base.decimals, 6, "Mint B should have 6 decimals"); assert_eq!( mint_b.base.mint_authority, Some(payer.pubkey().to_bytes().into()), "Mint B authority should be fee_payer" ); + + // PHASE 2: Warp to trigger auto-compression + rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); + shared::assert_onchain_closed(&mut rpc, &mint_a_pda, "MintA").await; + shared::assert_onchain_closed(&mut rpc, &mint_b_pda, "MintB").await; + + // PHASE 3: Decompress both mints via create_load_instructions + use pinocchio_derive_test::LightAccountVariant; + + let build_mint_account_interface = |mint_interface: light_client::interface::MintInterface| { + let (compressed, _mint_data) = mint_interface + .compressed() + .expect("cold mint must have compressed data"); + AccountInterface { + key: mint_interface.mint, + account: solana_account::Account { + lamports: 0, + data: vec![], + owner: light_token::instruction::LIGHT_TOKEN_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }, + cold: Some(ColdContext::Account(compressed.clone())), + } + }; + + let mint_a_interface = rpc + .get_mint_interface(&mint_a_pda) + .await + .expect("failed to get mint A interface"); + assert!(mint_a_interface.is_cold(), "Mint A should be cold"); + let mint_a_ai = build_mint_account_interface(mint_a_interface); + + let mint_b_interface = rpc + .get_mint_interface(&mint_b_pda) + .await + .expect("failed to get mint B interface"); + assert!(mint_b_interface.is_cold(), "Mint B should be cold"); + let mint_b_ai = build_mint_account_interface(mint_b_interface); + + let specs: Vec> = vec![ + AccountSpec::Mint(mint_a_ai), + AccountSpec::Mint(mint_b_ai), + ]; + + let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) + .await + .expect("create_load_instructions should succeed"); + + rpc.create_and_send_transaction(&ixs, &payer.pubkey(), &[&payer]) + .await + .expect("Decompression should succeed"); + + // PHASE 4: Assert state preserved after decompression + shared::assert_onchain_exists(&mut rpc, &mint_a_pda, "MintA").await; + shared::assert_onchain_exists(&mut rpc, &mint_b_pda, "MintB").await; + + let actual_a: Mint = borsh::BorshDeserialize::deserialize( + &mut &rpc.get_account(mint_a_pda).await.unwrap().unwrap().data[..], + ) + .unwrap(); + assert_eq!( + actual_a.base.decimals, 9, + "Mint A decimals should be preserved" + ); + assert_eq!( + actual_a.base.mint_authority, + Some(payer.pubkey().to_bytes().into()), + "Mint A authority should be preserved" + ); + + let actual_b: Mint = borsh::BorshDeserialize::deserialize( + &mut &rpc.get_account(mint_b_pda).await.unwrap().unwrap().data[..], + ) + .unwrap(); + assert_eq!( + actual_b.base.decimals, 6, + "Mint B decimals should be preserved" + ); + assert_eq!( + actual_b.base.mint_authority, + Some(payer.pubkey().to_bytes().into()), + "Mint B authority should be preserved" + ); } diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_zero_copy_record.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_zero_copy_record.rs index fea182c19c..5b56f9b20b 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_zero_copy_record.rs +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_zero_copy_record.rs @@ -1,7 +1,12 @@ mod shared; use anchor_lang::{InstructionData, ToAccountMetas}; -use light_client::interface::{get_create_accounts_proof, CreateAccountsProofInput}; +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterfaceExt, AccountSpec, + CreateAccountsProofInput, PdaSpec, +}; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::program_test::TestRpc; use light_program_test::Rpc; use pinocchio_derive_test::{CreateZeroCopyRecordParams, RECORD_SEED}; use solana_instruction::Instruction; @@ -57,6 +62,7 @@ async fn test_create_zero_copy_record_derive() { .await .expect("CreateZeroCopyRecord should succeed"); + // PHASE 1: Verify on-chain after creation let record_account = rpc .get_account(record_pda) .await @@ -70,4 +76,51 @@ async fn test_create_zero_copy_record_derive() { assert_eq!(record.owner, owner, "Record owner should match"); assert_eq!(record.counter, 0, "Record counter should be 0"); + + // PHASE 2: Warp to trigger auto-compression + rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); + shared::assert_onchain_closed(&mut rpc, &record_pda, "ZeroCopyRecord").await; + + // PHASE 3: Decompress via create_load_instructions + use anchor_lang::AnchorDeserialize; + use pinocchio_derive_test::{LightAccountVariant, ZeroCopyRecordSeeds}; + + let account_interface = rpc + .get_account_interface(&record_pda, &program_id) + .await + .expect("failed to get ZeroCopyRecord interface"); + assert!(account_interface.is_cold(), "ZeroCopyRecord should be cold"); + + let zc_data = ZeroCopyRecord::deserialize(&mut &account_interface.account.data[8..]) + .expect("Failed to parse ZeroCopyRecord from interface"); + let variant = LightAccountVariant::ZeroCopyRecord { + seeds: ZeroCopyRecordSeeds { owner }, + data: zc_data, + }; + + let spec = PdaSpec::new(account_interface, variant, program_id); + let specs: Vec> = vec![AccountSpec::Pda(spec)]; + + let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) + .await + .expect("create_load_instructions should succeed"); + + rpc.create_and_send_transaction(&ixs, &payer.pubkey(), &[&payer]) + .await + .expect("Decompression should succeed"); + + // PHASE 4: Assert state preserved after decompression + shared::assert_onchain_exists(&mut rpc, &record_pda, "ZeroCopyRecord").await; + + let account = rpc.get_account(record_pda).await.unwrap().unwrap(); + let actual: &ZeroCopyRecord = bytemuck::from_bytes(&account.data[8..]); + let expected = ZeroCopyRecord { + compression_info: shared::expected_compression_info(&actual.compression_info), + owner, + counter: 0, + }; + assert_eq!( + *actual, expected, + "ZeroCopyRecord should match after decompression" + ); } From 8bbc0f507a88903c2d788cc8debb2a2257520857 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 2 Feb 2026 21:50:27 +0000 Subject: [PATCH 12/15] cleanup packed accounts --- program-libs/compressible/README.md | 1 - .../tests/test_cpi_context_event.rs | 2 +- sdk-libs/client/src/indexer/types.rs | 5 +- sdk-libs/client/src/interface/instructions.rs | 12 +- .../client/src/interface/load_accounts.rs | 3 +- sdk-libs/client/src/interface/pack.rs | 5 +- .../compressed_token/v2/compress_and_close.rs | 13 +- .../compressed_token/v2/decompress_full.rs | 9 +- sdk-libs/sdk/src/cpi/invoke.rs | 10 +- sdk-libs/sdk/src/cpi/mod.rs | 1 - sdk-libs/sdk/src/instruction/mod.rs | 20 +- sdk-libs/sdk/src/instruction/pack_accounts.rs | 509 ------------------ .../sdk/src/instruction/packed_accounts.rs | 82 +++ .../src/instruction/packed_accounts_ext.rs | 61 --- sdk-libs/sdk/src/lib.rs | 2 - .../amm_observation_state_test.rs | 2 +- .../account_macros/amm_pool_state_test.rs | 2 +- .../account_macros/core_game_session_test.rs | 2 +- .../core_placeholder_record_test.rs | 2 +- .../account_macros/core_user_record_test.rs | 2 +- .../account_macros/d1_all_field_types_test.rs | 2 +- .../tests/account_macros/d1_array_test.rs | 2 +- .../account_macros/d1_multi_pubkey_test.rs | 2 +- .../tests/account_macros/d1_no_pubkey_test.rs | 2 +- .../account_macros/d1_option_pubkey_test.rs | 2 +- .../account_macros/d1_single_pubkey_test.rs | 2 +- .../account_macros/d2_all_compress_as_test.rs | 2 +- .../d2_multiple_compress_as_test.rs | 2 +- .../account_macros/d2_no_compress_as_test.rs | 2 +- .../d2_option_none_compress_as_test.rs | 2 +- .../d2_single_compress_as_test.rs | 2 +- .../account_macros/d4_all_composition_test.rs | 2 +- .../tests/account_macros/d4_info_last_test.rs | 2 +- .../tests/account_macros/shared.rs | 3 +- .../tests/stress_test.rs | 12 +- .../tests/test_create_all.rs | 10 +- .../tests/test_create_ata.rs | 7 +- .../tests/test_create_mint.rs | 8 +- .../tests/test_create_pda.rs | 8 +- .../tests/test_create_token_vault.rs | 3 +- .../tests/test_create_two_mints.rs | 9 +- .../tests/test_create_zero_copy_record.rs | 3 +- .../sdk-anchor-test/tests/read_only.rs | 3 +- .../programs/sdk-anchor-test/tests/test.rs | 5 +- sdk-tests/sdk-native-test/tests/test.rs | 3 +- sdk-tests/sdk-pinocchio-v1-test/tests/test.rs | 2 +- sdk-tests/sdk-pinocchio-v2-test/tests/test.rs | 2 +- sdk-tests/sdk-token-test/tests/ctoken_pda.rs | 2 +- sdk-tests/sdk-token-test/tests/pda_ctoken.rs | 2 +- .../tests/test_4_invocations.rs | 2 +- .../sdk-token-test/tests/test_4_transfer2.rs | 2 +- .../tests/test_compress_full_and_close.rs | 2 +- .../sdk-token-test/tests/test_deposit.rs | 5 +- sdk-tests/sdk-v1-native-test/tests/test.rs | 3 +- 54 files changed, 165 insertions(+), 702 deletions(-) delete mode 100644 sdk-libs/sdk/src/instruction/pack_accounts.rs create mode 100644 sdk-libs/sdk/src/instruction/packed_accounts.rs delete mode 100644 sdk-libs/sdk/src/instruction/packed_accounts_ext.rs diff --git a/program-libs/compressible/README.md b/program-libs/compressible/README.md index 9b9a6a7a80..573da139cc 100644 --- a/program-libs/compressible/README.md +++ b/program-libs/compressible/README.md @@ -14,7 +14,6 @@ cold account into hot state in-flight when using the account again. |------|-------------| | [`CompressionInfo`](compression_info::CompressionInfo) | Rent state, authorities, and compression config per account | | [`CompressibleConfig`](config::CompressibleConfig) | Program-level config: rent sponsor, authorities, address space | -| [`CreateAccountsProof`] | Validity proof and tree info for account init | | [`RentConfig`](rent::RentConfig) | Rent function parameters for compression eligibility | | [`compression_info`] | `is_compressible`, `claim`, and top-up logic | | [`registry_instructions`] | Instructions for the compression registry | diff --git a/program-tests/system-cpi-test/tests/test_cpi_context_event.rs b/program-tests/system-cpi-test/tests/test_cpi_context_event.rs index c9e7d67c45..5dcbc69457 100644 --- a/program-tests/system-cpi-test/tests/test_cpi_context_event.rs +++ b/program-tests/system-cpi-test/tests/test_cpi_context_event.rs @@ -2,7 +2,7 @@ use anchor_lang::{InstructionData, ToAccountMetas}; use light_program_test::{program_test::LightProgramTest, Indexer, ProgramTestConfig, Rpc}; -use light_sdk::instruction::{PackedAccounts, PackedAccountsExt, SystemAccountMetaConfig}; +use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; use serial_test::serial; use solana_sdk::{ instruction::Instruction, diff --git a/sdk-libs/client/src/indexer/types.rs b/sdk-libs/client/src/indexer/types.rs index 3f653db274..2cd0f6c8d8 100644 --- a/sdk-libs/client/src/indexer/types.rs +++ b/sdk-libs/client/src/indexer/types.rs @@ -1,4 +1,5 @@ use borsh::BorshDeserialize; +use light_account::PackedAccounts; use light_compressed_account::{ compressed_account::{ CompressedAccount as ProgramCompressedAccount, CompressedAccountData, @@ -8,9 +9,7 @@ use light_compressed_account::{ TreeType, }; use light_indexed_merkle_tree::array::IndexedElement; -use light_sdk::instruction::{ - PackedAccounts, PackedAddressTreeInfo, PackedStateTreeInfo, ValidityProof, -}; +use light_sdk::instruction::{PackedAddressTreeInfo, PackedStateTreeInfo, ValidityProof}; use light_token::compat::{AccountState, TokenData}; use light_token_interface::state::ExtensionStruct; use num_bigint::BigUint; diff --git a/sdk-libs/client/src/interface/instructions.rs b/sdk-libs/client/src/interface/instructions.rs index 8838945187..d74c1f9768 100644 --- a/sdk-libs/client/src/interface/instructions.rs +++ b/sdk-libs/client/src/interface/instructions.rs @@ -5,15 +5,11 @@ use anchor_lang::{AnchorDeserialize, AnchorSerialize}; #[cfg(not(feature = "anchor"))] use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; use light_account::{ - CompressedAccountData, InitializeLightConfigParams, Pack, PackedAccounts, - UpdateLightConfigParams, + CompressedAccountData, InitializeLightConfigParams, Pack, UpdateLightConfigParams, }; -use light_sdk::{ - instruction::{ - account_meta::CompressedAccountMetaNoLamportsNoAddress, SystemAccountMetaConfig, - ValidityProof, - }, - PackedAccountsExt, +use light_sdk::instruction::{ + account_meta::CompressedAccountMetaNoLamportsNoAddress, PackedAccounts, + SystemAccountMetaConfig, ValidityProof, }; use light_token::constants::{ LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_CPI_AUTHORITY, LIGHT_TOKEN_PROGRAM_ID, diff --git a/sdk-libs/client/src/interface/load_accounts.rs b/sdk-libs/client/src/interface/load_accounts.rs index fcddcaab55..49e8bff452 100644 --- a/sdk-libs/client/src/interface/load_accounts.rs +++ b/sdk-libs/client/src/interface/load_accounts.rs @@ -1,6 +1,6 @@ //! Load cold accounts API. -use light_account::{derive_rent_sponsor_pda, Pack, PackedAccounts}; +use light_account::{derive_rent_sponsor_pda, Pack}; use light_compressed_account::{ compressed_account::PackedMerkleContext, instruction_data::compressed_proof::ValidityProof, }; @@ -10,6 +10,7 @@ use light_compressed_token_sdk::compressed_token::{ }, CTokenAccount2, }; +use light_sdk::instruction::PackedAccounts; use light_token::{ compat::AccountState, instruction::{ diff --git a/sdk-libs/client/src/interface/pack.rs b/sdk-libs/client/src/interface/pack.rs index dd07fbc1bf..1247586928 100644 --- a/sdk-libs/client/src/interface/pack.rs +++ b/sdk-libs/client/src/interface/pack.rs @@ -1,10 +1,7 @@ //! Helper for packing validity proofs into remaining accounts. +use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; pub use light_sdk::instruction::{PackedAddressTreeInfo, PackedStateTreeInfo}; -use light_sdk::{ - instruction::{PackedAccounts, SystemAccountMetaConfig}, - PackedAccountsExt, -}; use solana_instruction::AccountMeta; use solana_pubkey::Pubkey; use thiserror::Error; diff --git a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs index 3e0314e411..8978d2703c 100644 --- a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs +++ b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/compress_and_close.rs @@ -1,10 +1,11 @@ +#[cfg(not(target_os = "solana"))] +use light_account::PackedAccounts; use light_program_profiler::profile; +// PackedAccounts and AccountMetasVec are only available off-chain (client-side) #[cfg(not(target_os = "solana"))] -use light_sdk::{ - instruction::{AccountMetasVec, PackedAccounts, SystemAccountMetaConfig}, - PackedAccountsExt, +use light_sdk::instruction::{ + get_light_system_account_metas_v2, AccountMetasVec, SystemAccountMetaConfig, }; -// PackedAccounts and AccountMetasVec are only available off-chain (client-side) #[cfg(not(target_os = "solana"))] use light_sdk_types::error::LightSdkTypesError; use light_token_interface::instructions::transfer2::CompressedCpiContext; @@ -422,9 +423,7 @@ impl AccountMetasVec for CompressAndCloseAccounts { return Err(LightSdkTypesError::InvalidInstructionData); } } - accounts - .add_system_accounts_v2(config) - .map_err(LightSdkTypesError::from)?; + accounts.add_system_accounts_raw(get_light_system_account_metas_v2(config)); } // Add both accounts in one operation for better performance accounts.pre_accounts.extend_from_slice(&[ diff --git a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs index 75196f490a..2cd8830ce8 100644 --- a/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs +++ b/sdk-libs/compressed-token-sdk/src/compressed_token/v2/decompress_full.rs @@ -8,9 +8,8 @@ use light_compressed_account::instruction_data::compressed_proof::ValidityProof; use light_program_profiler::profile; use light_sdk::instruction::PackedStateTreeInfo; #[cfg(not(target_os = "solana"))] -use light_sdk::{ - instruction::{AccountMetasVec, SystemAccountMetaConfig}, - PackedAccountsExt, +use light_sdk::instruction::{ + get_light_system_account_metas_v2, AccountMetasVec, SystemAccountMetaConfig, }; use light_sdk_types::error::LightSdkTypesError; use light_token_interface::instructions::{ @@ -375,9 +374,7 @@ impl AccountMetasVec for DecompressFullAccounts { config }; - accounts - .add_system_accounts_v2(config) - .map_err(LightSdkTypesError::from)?; + accounts.add_system_accounts_raw(get_light_system_account_metas_v2(config)); } // Add both accounts in one operation for better performance accounts.pre_accounts.extend_from_slice(&[ diff --git a/sdk-libs/sdk/src/cpi/invoke.rs b/sdk-libs/sdk/src/cpi/invoke.rs index b7fa7c76af..7a56d08355 100644 --- a/sdk-libs/sdk/src/cpi/invoke.rs +++ b/sdk-libs/sdk/src/cpi/invoke.rs @@ -1,16 +1,16 @@ pub use light_compressed_account::LightInstructionData; use light_sdk_types::constants::{CPI_AUTHORITY_PDA_SEED, LIGHT_SYSTEM_PROGRAM_ID}; - +use solana_account_info::AccountInfo; +use solana_cpi::invoke_signed; #[cfg(feature = "cpi-context")] use solana_instruction::AccountMeta; +use solana_instruction::Instruction; +use solana_program_error::ProgramError; + use crate::{ cpi::{account::CpiAccountsTrait, instruction::LightCpiInstruction}, error::LightSdkError, }; -use solana_account_info::AccountInfo; -use solana_cpi::invoke_signed; -use solana_instruction::Instruction; -use solana_program_error::ProgramError; pub trait InvokeLightSystemProgram { fn invoke<'info>(self, accounts: impl CpiAccountsTrait<'info>) -> Result<(), ProgramError>; diff --git a/sdk-libs/sdk/src/cpi/mod.rs b/sdk-libs/sdk/src/cpi/mod.rs index f3a2d6ddc1..bdaaef1160 100644 --- a/sdk-libs/sdk/src/cpi/mod.rs +++ b/sdk-libs/sdk/src/cpi/mod.rs @@ -44,7 +44,6 @@ pub mod invoke; pub use account::CpiAccountsTrait; pub use instruction::LightCpiInstruction; pub use invoke::{invoke_light_system_program, InvokeLightSystemProgram, LightInstructionData}; - // Re-export non-conflicting items from sdk-types pub use light_sdk_types::{cpi_accounts::CpiAccountsConfig, CpiSigner}; diff --git a/sdk-libs/sdk/src/instruction/mod.rs b/sdk-libs/sdk/src/instruction/mod.rs index a179517c91..5a69959b29 100644 --- a/sdk-libs/sdk/src/instruction/mod.rs +++ b/sdk-libs/sdk/src/instruction/mod.rs @@ -41,20 +41,14 @@ // TODO: link to examples // Re-export instruction types from sdk-types (available on all targets) -pub use light_sdk_types::instruction::*; -// Re-export pack_accounts utilities from interface (off-chain only) -#[cfg(not(target_os = "solana"))] -pub use light_sdk_types::interface::instruction::*; - -// Concrete PackedAccounts type alias for solana AccountMeta (off-chain only) -#[cfg(not(target_os = "solana"))] -pub type PackedAccounts = - light_sdk_types::interface::instruction::PackedAccounts; - // SDK-specific: ValidityProof and CompressedProof pub use light_compressed_account::instruction_data::compressed_proof::{ CompressedProof, ValidityProof, }; +pub use light_sdk_types::instruction::*; +// Re-export pack_accounts utilities from interface (off-chain only) +#[cfg(not(target_os = "solana"))] +pub use light_sdk_types::interface::instruction::*; // SDK-specific: system account helpers (depend on find_cpi_signer_macro!) mod system_accounts; @@ -64,8 +58,8 @@ pub use system_accounts::*; mod tree_info; pub use tree_info::*; -// SDK-specific: PackedAccountsExt extension trait +// Newtype wrapper around generic PackedAccounts with inherent system account methods #[cfg(not(target_os = "solana"))] -mod packed_accounts_ext; +mod packed_accounts; #[cfg(not(target_os = "solana"))] -pub use packed_accounts_ext::*; +pub use packed_accounts::PackedAccounts; diff --git a/sdk-libs/sdk/src/instruction/pack_accounts.rs b/sdk-libs/sdk/src/instruction/pack_accounts.rs deleted file mode 100644 index c6c5d4663c..0000000000 --- a/sdk-libs/sdk/src/instruction/pack_accounts.rs +++ /dev/null @@ -1,509 +0,0 @@ -//! Utilities for packing accounts into instruction data. -//! -//! [`PackedAccounts`] is a builder for efficiently organizing accounts into the three categories -//! required for compressed account instructions: -//! 1. **Pre-accounts** - Custom accounts needed before system accounts -//! 2. **System accounts** - Static light system program accounts -//! 3. **Packed accounts** - Dynamically packed accounts (Merkle trees, address trees, queues) with automatic deduplication -//! -//! -//! ## System Account Versioning -//! -//! **`add_system_accounts()` is complementary to [`cpi::v1::CpiAccounts`](crate::cpi::v1::CpiAccounts)** -//! **`add_system_accounts_v2()` is complementary to [`cpi::v2::CpiAccounts`](crate::cpi::v2::CpiAccounts)** -//! -//! Always use the matching version - v1 client-side account packing with v1 program-side CPI, -//! and v2 with v2. Mixing versions will cause account layout mismatches. -//! -//! # Example: Creating a compressed PDA -//! -//! ```rust -//! # use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; -//! # use solana_pubkey::Pubkey; -//! # fn example() -> Result<(), Box> { -//! # let program_id = Pubkey::new_unique(); -//! # let payer_pubkey = Pubkey::new_unique(); -//! # let merkle_tree_pubkey = Pubkey::new_unique(); -//! // Initialize with system accounts -//! let system_account_meta_config = SystemAccountMetaConfig::new(program_id); -//! let mut accounts = PackedAccounts::default(); -//! -//! // Add pre-accounts (signers) -//! accounts.add_pre_accounts_signer(payer_pubkey); -//! -//! // Add Light system program accounts (v2) -//! #[cfg(feature = "v2")] -//! accounts.add_system_accounts_v2(system_account_meta_config)?; -//! #[cfg(not(feature = "v2"))] -//! accounts.add_system_accounts(system_account_meta_config)?; -//! -//! // Add Merkle tree accounts (automatically tracked and deduplicated) -//! let output_merkle_tree_index = accounts.insert_or_get(merkle_tree_pubkey); -//! -//! // Convert to final account metas with offsets -//! let (account_metas, system_accounts_offset, tree_accounts_offset) = accounts.to_account_metas(); -//! # assert_eq!(output_merkle_tree_index, 0); -//! # Ok(()) -//! # } -//! ``` -//! -//! # Account Organization -//! -//! The final account layout is: -//! ```text -//! [pre_accounts] [system_accounts] [packed_accounts] -//! ↑ ↑ ↑ -//! Signers, Light system Merkle trees, -//! fee payer program accts address trees -//! ``` -//! -//! # Automatic Deduplication -//! -//! ```rust -//! # use light_sdk::instruction::PackedAccounts; -//! # use solana_pubkey::Pubkey; -//! let mut accounts = PackedAccounts::default(); -//! let tree_pubkey = Pubkey::new_unique(); -//! let other_tree = Pubkey::new_unique(); -//! -//! // First insertion gets index 0 -//! let index1 = accounts.insert_or_get(tree_pubkey); -//! assert_eq!(index1, 0); -//! -//! // Same tree inserted again returns same index (deduplicated) -//! let index2 = accounts.insert_or_get(tree_pubkey); -//! assert_eq!(index2, 0); -//! -//! // Different tree gets next index -//! let index3 = accounts.insert_or_get(other_tree); -//! assert_eq!(index3, 1); -//! ``` -//! -//! # Building Instructions with Anchor Programs -//! -//! When building instructions for Anchor programs, concatenate your custom accounts with the packed accounts: -//! -//! ```rust,ignore -//! # use anchor_lang::InstructionData; -//! # use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; -//! # use solana_instruction::{AccountMeta, Instruction}; -//! -//! // 1. Set up packed accounts -//! let config = SystemAccountMetaConfig::new(program_id); -//! let mut remaining_accounts = PackedAccounts::default(); -//! remaining_accounts.add_system_accounts(config)?; -//! -//! // 2. Pack tree accounts from proof result -//! let packed_tree_info = proof_result.pack_tree_infos(&mut remaining_accounts); -//! let output_tree_index = state_tree_info.pack_output_tree_index(&mut remaining_accounts)?; -//! -//! // 3. Convert to account metas -//! let (remaining_accounts, _, _) = remaining_accounts.to_account_metas(); -//! -//! // 4. Build instruction: custom accounts first, then remaining_accounts -//! let instruction = Instruction { -//! program_id: your_program::ID, -//! accounts: [ -//! vec![AccountMeta::new(payer.pubkey(), true)], // Your program's accounts -//! // Add other custom accounts here if needed -//! remaining_accounts, // Light system accounts + trees -//! ] -//! .concat(), -//! data: your_program::instruction::YourInstruction { -//! proof: proof_result.proof, -//! address_tree_info: packed_tree_info.address_trees[0], -//! output_tree_index, -//! // ... your other fields -//! } -//! .data(), -//! }; -//! ``` - -use std::collections::HashMap; - -use crate::{ - error::LightSdkError, - instruction::system_accounts::{get_light_system_account_metas, SystemAccountMetaConfig}, - AccountMeta, Pubkey, -}; - -/// Builder to collect accounts for compressed account instructions. -/// -/// Manages three categories of accounts: -/// - **Pre-accounts**: Signers and other custom accounts that come before system accounts. -/// - **System accounts**: Light system program accounts (authority, trees, queues). -/// - **Packed accounts**: Dynamically tracked deduplicted accounts. -/// -/// # Example -/// -/// ```rust -/// # use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; -/// # use solana_pubkey::Pubkey; -/// # fn example() -> Result<(), Box> { -/// # let payer_pubkey = Pubkey::new_unique(); -/// # let program_id = Pubkey::new_unique(); -/// # let merkle_tree_pubkey = Pubkey::new_unique(); -/// let mut accounts = PackedAccounts::default(); -/// -/// // Add signer -/// accounts.add_pre_accounts_signer(payer_pubkey); -/// -/// // Add system accounts (use v2 if feature is enabled) -/// let config = SystemAccountMetaConfig::new(program_id); -/// #[cfg(feature = "v2")] -/// accounts.add_system_accounts_v2(config)?; -/// #[cfg(not(feature = "v2"))] -/// accounts.add_system_accounts(config)?; -/// -/// // Add and track tree accounts -/// let tree_index = accounts.insert_or_get(merkle_tree_pubkey); -/// -/// // Get final account metas -/// let (metas, system_offset, tree_offset) = accounts.to_account_metas(); -/// # assert_eq!(tree_index, 0); -/// # Ok(()) -/// # } -/// ``` -#[derive(Default, Debug)] -pub struct PackedAccounts { - /// Accounts that must come before system accounts (e.g., signers, fee payer). - pub pre_accounts: Vec, - /// Light system program accounts (authority, programs, trees, queues). - system_accounts: Vec, - /// Next available index for packed accounts. - next_index: u8, - /// Map of pubkey to (index, AccountMeta) for deduplication and index tracking. - map: HashMap, - /// Field to sanity check - system_accounts_set: bool, -} - -impl PackedAccounts { - pub fn new_with_system_accounts(config: SystemAccountMetaConfig) -> crate::error::Result { - let mut remaining_accounts = PackedAccounts::default(); - remaining_accounts.add_system_accounts(config)?; - Ok(remaining_accounts) - } - - pub fn system_accounts_set(&self) -> bool { - self.system_accounts_set - } - - pub fn add_pre_accounts_signer(&mut self, pubkey: Pubkey) { - self.pre_accounts.push(AccountMeta { - pubkey, - is_signer: true, - is_writable: false, - }); - } - - pub fn add_pre_accounts_signer_mut(&mut self, pubkey: Pubkey) { - self.pre_accounts.push(AccountMeta { - pubkey, - is_signer: true, - is_writable: true, - }); - } - - pub fn add_pre_accounts_meta(&mut self, account_meta: AccountMeta) { - self.pre_accounts.push(account_meta); - } - - pub fn add_pre_accounts_metas(&mut self, account_metas: &[AccountMeta]) { - self.pre_accounts.extend_from_slice(account_metas); - } - - /// Adds v1 Light system program accounts to the account list. - /// - /// **Use with [`cpi::v1::CpiAccounts`](crate::cpi::v1::CpiAccounts) on the program side.** - /// - /// This adds all the accounts required by the Light system program for v1 operations, - /// including the CPI authority, registered programs, account compression program, and Noop program. - /// - /// # Example - /// - /// ```rust - /// # use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; - /// # use solana_pubkey::Pubkey; - /// # fn example() -> Result<(), Box> { - /// # let program_id = Pubkey::new_unique(); - /// let mut accounts = PackedAccounts::default(); - /// let config = SystemAccountMetaConfig::new(program_id); - /// accounts.add_system_accounts(config)?; - /// # Ok(()) - /// # } - /// ``` - pub fn add_system_accounts( - &mut self, - config: SystemAccountMetaConfig, - ) -> crate::error::Result<()> { - self.system_accounts - .extend(get_light_system_account_metas(config)); - // note cpi context account is part of the system accounts - /* if let Some(pubkey) = config.cpi_context { - if self.next_index != 0 { - return Err(crate::error::LightSdkError::CpiContextOrderingViolation); - } - self.insert_or_get(pubkey); - }*/ - Ok(()) - } - - /// Adds v2 Light system program accounts to the account list. - /// - /// **Use with [`cpi::v2::CpiAccounts`](crate::cpi::v2::CpiAccounts) on the program side.** - /// - /// This adds all the accounts required by the Light system program for v2 operations. - /// V2 uses a different account layout optimized for batched state trees. - /// - /// # Example - /// - /// ```rust - /// # #[cfg(feature = "v2")] - /// # { - /// # use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; - /// # use solana_pubkey::Pubkey; - /// # fn example() -> Result<(), Box> { - /// # let program_id = Pubkey::new_unique(); - /// let mut accounts = PackedAccounts::default(); - /// let config = SystemAccountMetaConfig::new(program_id); - /// accounts.add_system_accounts_v2(config)?; - /// # Ok(()) - /// # } - /// # } - /// ``` - #[cfg(feature = "v2")] - pub fn add_system_accounts_v2( - &mut self, - config: SystemAccountMetaConfig, - ) -> crate::error::Result<()> { - self.system_accounts - .extend(crate::instruction::get_light_system_account_metas_v2( - config, - )); - // note cpi context account is part of the system accounts - /* if let Some(pubkey) = config.cpi_context { - if self.next_index != 0 { - return Err(crate::error::LightSdkError::CpiContextOrderingViolation); - } - self.insert_or_get(pubkey); - }*/ - Ok(()) - } - - /// Returns the index of the provided `pubkey` in the collection. - /// - /// If the provided `pubkey` is not a part of the collection, it gets - /// inserted with a `next_index`. - /// - /// If the privided `pubkey` already exists in the collection, its already - /// existing index is returned. - pub fn insert_or_get(&mut self, pubkey: Pubkey) -> u8 { - self.insert_or_get_config(pubkey, false, true) - } - - pub fn insert_or_get_read_only(&mut self, pubkey: Pubkey) -> u8 { - self.insert_or_get_config(pubkey, false, false) - } - - pub fn insert_or_get_config( - &mut self, - pubkey: Pubkey, - is_signer: bool, - is_writable: bool, - ) -> u8 { - match self.map.get_mut(&pubkey) { - Some((index, entry)) => { - if !entry.is_writable { - entry.is_writable = is_writable; - } - if !entry.is_signer { - entry.is_signer = is_signer; - } - *index - } - None => { - let index = self.next_index; - self.next_index += 1; - self.map.insert( - pubkey, - ( - index, - AccountMeta { - pubkey, - is_signer, - is_writable, - }, - ), - ); - index - } - } - } - - fn hash_set_accounts_to_metas(&self) -> Vec { - let mut packed_accounts = self.map.iter().collect::>(); - // hash maps are not sorted so we need to sort manually and collect into a vector again - packed_accounts.sort_by(|a, b| a.1 .0.cmp(&b.1 .0)); - let packed_accounts = packed_accounts - .iter() - .map(|(_, (_, k))| k.clone()) - .collect::>(); - packed_accounts - } - - fn get_offsets(&self) -> (usize, usize) { - let system_accounts_start_offset = self.pre_accounts.len(); - let packed_accounts_start_offset = - system_accounts_start_offset + self.system_accounts.len(); - (system_accounts_start_offset, packed_accounts_start_offset) - } - - /// Converts the collection of accounts to a vector of - /// [`AccountMeta`](solana_instruction::AccountMeta), which can be used - /// as remaining accounts in instructions or CPI calls. - /// - /// # Returns - /// - /// A tuple of `(account_metas, system_accounts_offset, packed_accounts_offset)`: - /// - `account_metas`: All accounts concatenated in order: `[pre_accounts][system_accounts][packed_accounts]` - /// - `system_accounts_offset`: Index where system accounts start (= pre_accounts.len()) - /// - `packed_accounts_offset`: Index where packed accounts start (= pre_accounts.len() + system_accounts.len()) - /// - /// The `system_accounts_offset` can be used to slice the accounts when creating [`CpiAccounts`](crate::cpi::v1::CpiAccounts): - /// ```ignore - /// let accounts_for_cpi = &ctx.remaining_accounts[system_accounts_offset..]; - /// let cpi_accounts = CpiAccounts::new(fee_payer, accounts_for_cpi, cpi_signer)?; - /// ``` - /// - /// The offset can be hardcoded if your program always has the same pre-accounts layout, or passed - /// as a field in your instruction data. - pub fn to_account_metas(&self) -> (Vec, usize, usize) { - let packed_accounts = self.hash_set_accounts_to_metas(); - let (system_accounts_start_offset, packed_accounts_start_offset) = self.get_offsets(); - ( - [ - self.pre_accounts.clone(), - self.system_accounts.clone(), - packed_accounts, - ] - .concat(), - system_accounts_start_offset, - packed_accounts_start_offset, - ) - } - - pub fn packed_pubkeys(&self) -> Vec { - self.hash_set_accounts_to_metas() - .iter() - .map(|meta| meta.pubkey) - .collect() - } - - pub fn add_custom_system_accounts( - &mut self, - accounts: T, - ) -> crate::error::Result<()> { - accounts.get_account_metas_vec(self) - } -} - -pub trait AccountMetasVec { - fn get_account_metas_vec(&self, accounts: &mut PackedAccounts) -> Result<(), LightSdkError>; -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_remaining_accounts() { - let mut remaining_accounts = PackedAccounts::default(); - - let pubkey_1 = Pubkey::new_unique(); - let pubkey_2 = Pubkey::new_unique(); - let pubkey_3 = Pubkey::new_unique(); - let pubkey_4 = Pubkey::new_unique(); - - // Initial insertion. - assert_eq!(remaining_accounts.insert_or_get(pubkey_1), 0); - assert_eq!(remaining_accounts.insert_or_get(pubkey_2), 1); - assert_eq!(remaining_accounts.insert_or_get(pubkey_3), 2); - - assert_eq!( - remaining_accounts.to_account_metas().0.as_slice(), - &[ - AccountMeta { - pubkey: pubkey_1, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_2, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_3, - is_signer: false, - is_writable: true, - } - ] - ); - - // Insertion of already existing pubkeys. - assert_eq!(remaining_accounts.insert_or_get(pubkey_1), 0); - assert_eq!(remaining_accounts.insert_or_get(pubkey_2), 1); - assert_eq!(remaining_accounts.insert_or_get(pubkey_3), 2); - - assert_eq!( - remaining_accounts.to_account_metas().0.as_slice(), - &[ - AccountMeta { - pubkey: pubkey_1, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_2, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_3, - is_signer: false, - is_writable: true, - } - ] - ); - - // Again, initial insertion. - assert_eq!(remaining_accounts.insert_or_get(pubkey_4), 3); - - assert_eq!( - remaining_accounts.to_account_metas().0.as_slice(), - &[ - AccountMeta { - pubkey: pubkey_1, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_2, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_3, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: pubkey_4, - is_signer: false, - is_writable: true, - } - ] - ); - } -} diff --git a/sdk-libs/sdk/src/instruction/packed_accounts.rs b/sdk-libs/sdk/src/instruction/packed_accounts.rs new file mode 100644 index 0000000000..e4b75cbfb7 --- /dev/null +++ b/sdk-libs/sdk/src/instruction/packed_accounts.rs @@ -0,0 +1,82 @@ +use std::ops::{Deref, DerefMut}; + +use super::system_accounts::{get_light_system_account_metas, SystemAccountMetaConfig}; + +type Inner = + light_sdk_types::interface::instruction::PackedAccounts; + +/// Packs accounts and creates indices for instruction building (client-side). +/// +/// Wraps the generic `PackedAccounts` from sdk-types with +/// Solana-specific system account helpers as inherent methods. +#[derive(Debug, Default)] +pub struct PackedAccounts(pub Inner); + +impl Deref for PackedAccounts { + type Target = Inner; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for PackedAccounts { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From for PackedAccounts { + fn from(inner: Inner) -> Self { + Self(inner) + } +} + +impl From for Inner { + fn from(wrapper: PackedAccounts) -> Self { + wrapper.0 + } +} + +impl PackedAccounts { + /// Creates a new [`PackedAccounts`] with v1 system accounts pre-configured. + /// + /// **Use with [`cpi::v1::CpiAccounts`](crate::cpi::v1::CpiAccounts) on the program side.** + pub fn new_with_system_accounts(config: SystemAccountMetaConfig) -> crate::error::Result { + let mut accounts = Self::default(); + accounts.add_system_accounts(config)?; + Ok(accounts) + } + + /// Adds v1 Light system program accounts to the account list. + /// + /// **Use with [`cpi::v1::CpiAccounts`](crate::cpi::v1::CpiAccounts) on the program side.** + /// + /// This adds all the accounts required by the Light system program for v1 operations, + /// including the CPI authority, registered programs, account compression program, and Noop program. + pub fn add_system_accounts( + &mut self, + config: SystemAccountMetaConfig, + ) -> crate::error::Result<()> { + self.0 + .add_system_accounts_raw(get_light_system_account_metas(config)); + Ok(()) + } + + /// Adds v2 Light system program accounts to the account list. + /// + /// **Use with [`cpi::v2::CpiAccounts`](crate::cpi::v2::CpiAccounts) on the program side.** + /// + /// This adds all the accounts required by the Light system program for v2 operations. + /// V2 uses a different account layout optimized for batched state trees. + #[cfg(feature = "v2")] + pub fn add_system_accounts_v2( + &mut self, + config: SystemAccountMetaConfig, + ) -> crate::error::Result<()> { + self.0 + .add_system_accounts_raw(super::system_accounts::get_light_system_account_metas_v2( + config, + )); + Ok(()) + } +} diff --git a/sdk-libs/sdk/src/instruction/packed_accounts_ext.rs b/sdk-libs/sdk/src/instruction/packed_accounts_ext.rs deleted file mode 100644 index 76906d012d..0000000000 --- a/sdk-libs/sdk/src/instruction/packed_accounts_ext.rs +++ /dev/null @@ -1,61 +0,0 @@ -use super::{ - system_accounts::{get_light_system_account_metas, SystemAccountMetaConfig}, - PackedAccounts, -}; - -/// Extension trait adding Light system account helpers to [`PackedAccounts`]. -/// -/// These methods depend on [`SystemAccountMetaConfig`] and `find_cpi_signer_macro!` -/// which are SDK-specific (use CPI signer derivation). -pub trait PackedAccountsExt { - /// Creates a new [`PackedAccounts`] with v1 system accounts pre-configured. - /// - /// **Use with [`cpi::v1::CpiAccounts`](crate::cpi::v1::CpiAccounts) on the program side.** - fn new_with_system_accounts(config: SystemAccountMetaConfig) -> crate::error::Result - where - Self: Sized; - - /// Adds v1 Light system program accounts to the account list. - /// - /// **Use with [`cpi::v1::CpiAccounts`](crate::cpi::v1::CpiAccounts) on the program side.** - /// - /// This adds all the accounts required by the Light system program for v1 operations, - /// including the CPI authority, registered programs, account compression program, and Noop program. - fn add_system_accounts(&mut self, config: SystemAccountMetaConfig) -> crate::error::Result<()>; - - /// Adds v2 Light system program accounts to the account list. - /// - /// **Use with [`cpi::v2::CpiAccounts`](crate::cpi::v2::CpiAccounts) on the program side.** - /// - /// This adds all the accounts required by the Light system program for v2 operations. - /// V2 uses a different account layout optimized for batched state trees. - #[cfg(feature = "v2")] - fn add_system_accounts_v2( - &mut self, - config: SystemAccountMetaConfig, - ) -> crate::error::Result<()>; -} - -impl PackedAccountsExt for PackedAccounts { - fn new_with_system_accounts(config: SystemAccountMetaConfig) -> crate::error::Result { - let mut accounts = PackedAccounts::default(); - accounts.add_system_accounts(config)?; - Ok(accounts) - } - - fn add_system_accounts(&mut self, config: SystemAccountMetaConfig) -> crate::error::Result<()> { - self.add_system_accounts_raw(get_light_system_account_metas(config)); - Ok(()) - } - - #[cfg(feature = "v2")] - fn add_system_accounts_v2( - &mut self, - config: SystemAccountMetaConfig, - ) -> crate::error::Result<()> { - self.add_system_accounts_raw(super::system_accounts::get_light_system_account_metas_v2( - config, - )); - Ok(()) - } -} diff --git a/sdk-libs/sdk/src/lib.rs b/sdk-libs/sdk/src/lib.rs index 5f623cb102..854e0c4018 100644 --- a/sdk-libs/sdk/src/lib.rs +++ b/sdk-libs/sdk/src/lib.rs @@ -218,8 +218,6 @@ use solana_pubkey::Pubkey; // Re-export SDK traits pub use crate::cpi::LightCpiInstruction; -#[cfg(not(target_os = "solana"))] -pub use crate::instruction::PackedAccountsExt; pub trait PubkeyTrait { fn to_solana_pubkey(&self) -> Pubkey; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_observation_state_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_observation_state_test.rs index ee7562cadd..dbfa05155c 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_observation_state_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_observation_state_test.rs @@ -10,8 +10,8 @@ //! testing Pack/Unpack behavior with array fields and nested data structures. use csdk_anchor_full_derived_test::{Observation, ObservationState, PackedObservationState}; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, Pack}; +use light_hasher::{DataHasher, Sha256}; use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_pool_state_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_pool_state_test.rs index c8404c8a39..4b8ad2c758 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_pool_state_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/amm_pool_state_test.rs @@ -10,8 +10,8 @@ //! comprehensive Pack/Unpack behavior with multiple pubkey indices. use csdk_anchor_full_derived_test::{PackedPoolState, PoolState}; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, Pack}; +use light_hasher::{DataHasher, Sha256}; use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_game_session_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_game_session_test.rs index c842ebd690..4bd7e13273 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_game_session_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_game_session_test.rs @@ -10,8 +10,8 @@ //! which overrides field values during compression. use csdk_anchor_full_derived_test::{GameSession, PackedGameSession}; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, Pack}; +use light_hasher::{DataHasher, Sha256}; use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_placeholder_record_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_placeholder_record_test.rs index b75cc3324c..90af079cd8 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_placeholder_record_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_placeholder_record_test.rs @@ -7,8 +7,8 @@ //! - CompressiblePack -> Pack + Unpack + PackedPlaceholderRecord use csdk_anchor_full_derived_test::{PackedPlaceholderRecord, PlaceholderRecord}; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, CompressionState, Pack}; +use light_hasher::{DataHasher, Sha256}; use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_user_record_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_user_record_test.rs index 9069b7bf73..5cb0699a55 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_user_record_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/core_user_record_test.rs @@ -6,8 +6,8 @@ //! - Compressible -> HasCompressionInfo + CompressAs + Size + CompressedInitSpace use csdk_anchor_full_derived_test::{PackedUserRecord, UserRecord}; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, CompressionState, Pack}; +use light_hasher::{DataHasher, Sha256}; use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_all_field_types_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_all_field_types_test.rs index 1e3540537b..9819b70507 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_all_field_types_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_all_field_types_test.rs @@ -14,8 +14,8 @@ //! - Regular primitives (counter, flag) -> direct copy use csdk_anchor_full_derived_test::{AllFieldTypesRecord, PackedAllFieldTypesRecord}; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, CompressionState, Pack}; +use light_hasher::{DataHasher, Sha256}; use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_array_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_array_test.rs index 7e90209e1a..a3825b9c06 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_array_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_array_test.rs @@ -10,8 +10,8 @@ //! Therefore, no Pack/Unpack tests are needed. use csdk_anchor_full_derived_test::ArrayRecord; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, CompressionState}; +use light_hasher::{DataHasher, Sha256}; use super::shared::CompressibleTestFactory; use crate::generate_trait_tests; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_multi_pubkey_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_multi_pubkey_test.rs index 0b99f6d252..12e31c740e 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_multi_pubkey_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_multi_pubkey_test.rs @@ -6,8 +6,8 @@ //! - Compressible -> HasCompressionInfo + CompressAs + Size + CompressedInitSpace use csdk_anchor_full_derived_test::{MultiPubkeyRecord, PackedMultiPubkeyRecord}; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, Pack}; +use light_hasher::{DataHasher, Sha256}; use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_no_pubkey_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_no_pubkey_test.rs index 75634f3577..da62b3ea78 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_no_pubkey_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_no_pubkey_test.rs @@ -10,8 +10,8 @@ //! struct is packed as-is without transformation. use csdk_anchor_full_derived_test::NoPubkeyRecord; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, CompressionState}; +use light_hasher::{DataHasher, Sha256}; use super::shared::CompressibleTestFactory; use crate::generate_trait_tests; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_option_pubkey_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_option_pubkey_test.rs index c0f6d8c502..d4d6a9856c 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_option_pubkey_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_option_pubkey_test.rs @@ -10,8 +10,8 @@ //! Option fields remain as Option in the packed struct. use csdk_anchor_full_derived_test::OptionPubkeyRecord; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, Pack}; +use light_hasher::{DataHasher, Sha256}; use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_single_pubkey_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_single_pubkey_test.rs index 7888e173c2..0eb5736452 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_single_pubkey_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d1_single_pubkey_test.rs @@ -6,8 +6,8 @@ //! - Compressible -> HasCompressionInfo + CompressAs + Size + CompressedInitSpace use csdk_anchor_full_derived_test::{PackedSinglePubkeyRecord, SinglePubkeyRecord}; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, Pack}; +use light_hasher::{DataHasher, Sha256}; use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_all_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_all_compress_as_test.rs index cec141c06c..50408387cf 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_all_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_all_compress_as_test.rs @@ -6,8 +6,8 @@ //! - Compressible -> HasCompressionInfo + CompressAs + Size + CompressedInitSpace use csdk_anchor_full_derived_test::{AllCompressAsRecord, PackedAllCompressAsRecord}; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, Pack}; +use light_hasher::{DataHasher, Sha256}; use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_multiple_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_multiple_compress_as_test.rs index 9076a915d6..091e703198 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_multiple_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_multiple_compress_as_test.rs @@ -6,8 +6,8 @@ //! - Compressible -> HasCompressionInfo + CompressAs + Size + CompressedInitSpace use csdk_anchor_full_derived_test::{MultipleCompressAsRecord, PackedMultipleCompressAsRecord}; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, Pack}; +use light_hasher::{DataHasher, Sha256}; use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_no_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_no_compress_as_test.rs index 802d70d585..fe8ed06484 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_no_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_no_compress_as_test.rs @@ -6,8 +6,8 @@ //! - Compressible -> HasCompressionInfo + CompressAs + Size + CompressedInitSpace use csdk_anchor_full_derived_test::{NoCompressAsRecord, PackedNoCompressAsRecord}; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, Pack}; +use light_hasher::{DataHasher, Sha256}; use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_option_none_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_option_none_compress_as_test.rs index 1687930de3..013367c091 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_option_none_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_option_none_compress_as_test.rs @@ -6,8 +6,8 @@ //! - Compressible -> HasCompressionInfo + CompressAs + Size + CompressedInitSpace use csdk_anchor_full_derived_test::{OptionNoneCompressAsRecord, PackedOptionNoneCompressAsRecord}; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, Pack}; +use light_hasher::{DataHasher, Sha256}; use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_single_compress_as_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_single_compress_as_test.rs index e0683783ff..7c5f250044 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_single_compress_as_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d2_single_compress_as_test.rs @@ -7,8 +7,8 @@ //! - CompressiblePack -> Pack + Unpack + PackedSingleCompressAsRecord use csdk_anchor_full_derived_test::{PackedSingleCompressAsRecord, SingleCompressAsRecord}; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, Pack}; +use light_hasher::{DataHasher, Sha256}; use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_all_composition_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_all_composition_test.rs index 2708bbfe53..1100a0f958 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_all_composition_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_all_composition_test.rs @@ -10,8 +10,8 @@ //! This tests full Pack/Unpack behavior with compress_as attribute overrides. use csdk_anchor_full_derived_test::{AllCompositionRecord, PackedAllCompositionRecord}; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, Pack}; +use light_hasher::{DataHasher, Sha256}; use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_info_last_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_info_last_test.rs index eccec16dee..38c81554f2 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_info_last_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/d4_info_last_test.rs @@ -9,8 +9,8 @@ //! compression_info can be placed in non-first position (ordering test). use csdk_anchor_full_derived_test::{InfoLastRecord, PackedInfoLastRecord}; -use light_hasher::{DataHasher, Sha256}; use light_account::{CompressAs, CompressionInfo, CompressionState, Pack}; +use light_hasher::{DataHasher, Sha256}; use light_sdk::instruction::PackedAccounts; use solana_pubkey::Pubkey; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/shared.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/shared.rs index 65236f2ac5..d0c2c59be1 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/shared.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/account_macros/shared.rs @@ -5,9 +5,8 @@ use std::borrow::Cow; -use light_account::Size; +use light_account::{CompressAs, CompressedInitSpace, CompressionState, HasCompressionInfo, Size}; use light_hasher::{DataHasher, Sha256}; -use light_account::{CompressAs, CompressedInitSpace, CompressionState, HasCompressionInfo}; use light_sdk::LightDiscriminator; // ============================================================================= diff --git a/sdk-tests/pinocchio-derive-test/tests/stress_test.rs b/sdk-tests/pinocchio-derive-test/tests/stress_test.rs index e6580d2e17..19ac002937 100644 --- a/sdk-tests/pinocchio-derive-test/tests/stress_test.rs +++ b/sdk-tests/pinocchio-derive-test/tests/stress_test.rs @@ -23,10 +23,10 @@ use light_program_test::{ }; use light_token_interface::state::{token::Token, Mint}; use pinocchio_derive_test::{ - CreateAllParams, MinimalRecord, ZeroCopyRecord, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, - RECORD_SEED, VAULT_AUTH_SEED, VAULT_SEED, + CreateAllParams, LightAccountVariant, MinimalRecord, MinimalRecordSeeds, ZeroCopyRecord, + ZeroCopyRecordSeeds, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, RECORD_SEED, VAULT_AUTH_SEED, + VAULT_SEED, }; -use pinocchio_derive_test::{LightAccountVariant, MinimalRecordSeeds, ZeroCopyRecordSeeds}; use solana_instruction::Instruction; use solana_keypair::Keypair; use solana_pubkey::Pubkey; @@ -215,11 +215,7 @@ async fn setup() -> (StressTestContext, TestPdas) { } /// Re-read all on-chain accounts into the cache -async fn refresh_cache( - rpc: &mut LightProgramTest, - pdas: &TestPdas, - owner: Pubkey, -) -> CachedState { +async fn refresh_cache(rpc: &mut LightProgramTest, pdas: &TestPdas, owner: Pubkey) -> CachedState { let record_account = rpc.get_account(pdas.record).await.unwrap().unwrap(); let record: MinimalRecord = borsh::BorshDeserialize::deserialize(&mut &record_account.data[8..]).unwrap(); diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_all.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_all.rs index a4f87994e1..c6a2f82f1f 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_all.rs +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_all.rs @@ -6,8 +6,7 @@ use light_client::interface::{ AccountSpec, ColdContext, CreateAccountsProofInput, PdaSpec, }; use light_compressible::rent::SLOTS_PER_EPOCH; -use light_program_test::program_test::TestRpc; -use light_program_test::Rpc; +use light_program_test::{program_test::TestRpc, Rpc}; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; @@ -218,9 +217,7 @@ async fn test_create_all_derive() { // PHASE 3: Decompress all accounts except vault via create_load_instructions. // Note: standalone token PDA decompression is not supported; vaults stay compressed. - use pinocchio_derive_test::{ - LightAccountVariant, MinimalRecordSeeds, ZeroCopyRecordSeeds, - }; + use pinocchio_derive_test::{LightAccountVariant, MinimalRecordSeeds, ZeroCopyRecordSeeds}; // PDA: MinimalRecord let record_interface = rpc @@ -352,8 +349,7 @@ async fn test_create_all_derive() { ); // ATA - let actual_ata: Token = - shared::parse_token(&rpc.get_account(ata).await.unwrap().unwrap().data); + let actual_ata: Token = shared::parse_token(&rpc.get_account(ata).await.unwrap().unwrap().data); let expected_ata = Token { mint: LPubkey::from(ata_mint.to_bytes()), owner: LPubkey::from(ata_owner.to_bytes()), diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_ata.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_ata.rs index 6a22995ba2..a5e576ff33 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_ata.rs +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_ata.rs @@ -5,8 +5,7 @@ use light_client::interface::{ create_load_instructions, get_create_accounts_proof, AccountInterfaceExt, AccountSpec, }; use light_compressible::rent::SLOTS_PER_EPOCH; -use light_program_test::program_test::TestRpc; -use light_program_test::Rpc; +use light_program_test::{program_test::TestRpc, Rpc}; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use pinocchio_derive_test::CreateAtaParams; @@ -116,9 +115,7 @@ async fn test_create_ata_derive() { // PHASE 4: Assert state preserved after decompression shared::assert_onchain_exists(&mut rpc, &ata, "ATA").await; - let actual: Token = shared::parse_token( - &rpc.get_account(ata).await.unwrap().unwrap().data, - ); + let actual: Token = shared::parse_token(&rpc.get_account(ata).await.unwrap().unwrap().data); let expected = Token { mint: mint.to_bytes().into(), owner: ata_owner.to_bytes().into(), diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_mint.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_mint.rs index 6b1b1b7347..a3ce0fe0e6 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_mint.rs +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_mint.rs @@ -6,8 +6,7 @@ use light_client::interface::{ AccountSpec, ColdContext, CreateAccountsProofInput, }; use light_compressible::rent::SLOTS_PER_EPOCH; -use light_program_test::program_test::TestRpc; -use light_program_test::Rpc; +use light_program_test::{program_test::TestRpc, Rpc}; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use pinocchio_derive_test::{CreateMintParams, MINT_SIGNER_SEED_A}; @@ -138,10 +137,7 @@ async fn test_create_mint_derive() { &mut &rpc.get_account(mint_pda).await.unwrap().unwrap().data[..], ) .unwrap(); - assert_eq!( - actual.base.decimals, 9, - "Mint decimals should be preserved" - ); + assert_eq!(actual.base.decimals, 9, "Mint decimals should be preserved"); assert_eq!( actual.base.mint_authority, Some(payer.pubkey().to_bytes().into()), diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_pda.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_pda.rs index 7d48dc0e33..d09129d3bd 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_pda.rs +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_pda.rs @@ -6,8 +6,7 @@ use light_client::interface::{ CreateAccountsProofInput, PdaSpec, }; use light_compressible::rent::SLOTS_PER_EPOCH; -use light_program_test::program_test::TestRpc; -use light_program_test::Rpc; +use light_program_test::{program_test::TestRpc, Rpc}; use pinocchio_derive_test::CreatePdaParams; use solana_instruction::Instruction; use solana_keypair::Keypair; @@ -79,7 +78,10 @@ async fn test_create_single_pda_derive() { compression_info: shared::expected_compression_info(&record.compression_info), owner, }; - assert_eq!(record, expected, "MinimalRecord should match after creation"); + assert_eq!( + record, expected, + "MinimalRecord should match after creation" + ); // PHASE 2: Warp to trigger auto-compression rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_token_vault.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_token_vault.rs index 0b2037e658..7616659e4c 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_token_vault.rs +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_token_vault.rs @@ -3,8 +3,7 @@ mod shared; use anchor_lang::{InstructionData, ToAccountMetas}; use light_client::interface::get_create_accounts_proof; use light_compressible::rent::SLOTS_PER_EPOCH; -use light_program_test::program_test::TestRpc; -use light_program_test::Rpc; +use light_program_test::{program_test::TestRpc, Rpc}; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use pinocchio_derive_test::{CreateTokenVaultParams, VAULT_AUTH_SEED, VAULT_SEED}; diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_two_mints.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_two_mints.rs index 72d4b8158b..68fa089eb3 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_two_mints.rs +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_two_mints.rs @@ -6,8 +6,7 @@ use light_client::interface::{ AccountSpec, ColdContext, CreateAccountsProofInput, }; use light_compressible::rent::SLOTS_PER_EPOCH; -use light_program_test::program_test::TestRpc; -use light_program_test::Rpc; +use light_program_test::{program_test::TestRpc, Rpc}; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use pinocchio_derive_test::{CreateTwoMintsParams, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B}; @@ -155,10 +154,8 @@ async fn test_create_two_mints_derive() { assert!(mint_b_interface.is_cold(), "Mint B should be cold"); let mint_b_ai = build_mint_account_interface(mint_b_interface); - let specs: Vec> = vec![ - AccountSpec::Mint(mint_a_ai), - AccountSpec::Mint(mint_b_ai), - ]; + let specs: Vec> = + vec![AccountSpec::Mint(mint_a_ai), AccountSpec::Mint(mint_b_ai)]; let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) .await diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_zero_copy_record.rs b/sdk-tests/pinocchio-derive-test/tests/test_create_zero_copy_record.rs index 5b56f9b20b..15c537728c 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_zero_copy_record.rs +++ b/sdk-tests/pinocchio-derive-test/tests/test_create_zero_copy_record.rs @@ -6,8 +6,7 @@ use light_client::interface::{ CreateAccountsProofInput, PdaSpec, }; use light_compressible::rent::SLOTS_PER_EPOCH; -use light_program_test::program_test::TestRpc; -use light_program_test::Rpc; +use light_program_test::{program_test::TestRpc, Rpc}; use pinocchio_derive_test::{CreateZeroCopyRecordParams, RECORD_SEED}; use solana_instruction::Instruction; use solana_keypair::Keypair; diff --git a/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/read_only.rs b/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/read_only.rs index 08377f7758..154f4e2045 100644 --- a/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/read_only.rs +++ b/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/read_only.rs @@ -8,8 +8,7 @@ use light_program_test::{ use light_sdk::{ address::v1::derive_address, instruction::{ - account_meta::CompressedAccountMetaBurn, PackedAccounts, PackedAccountsExt, - SystemAccountMetaConfig, + account_meta::CompressedAccountMetaBurn, PackedAccounts, SystemAccountMetaConfig, }, }; use light_test_utils::{Rpc, RpcError}; diff --git a/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs b/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs index e49a8476d6..e19d0742de 100644 --- a/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs +++ b/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs @@ -9,10 +9,7 @@ use light_program_test::{ }; use light_sdk::{ address::v1::derive_address, - instruction::{ - account_meta::CompressedAccountMeta, PackedAccounts, PackedAccountsExt, - SystemAccountMetaConfig, - }, + instruction::{account_meta::CompressedAccountMeta, PackedAccounts, SystemAccountMetaConfig}, }; use light_test_utils::{Rpc, RpcError}; use sdk_anchor_test::{MyCompressedAccount, NestedData}; diff --git a/sdk-tests/sdk-native-test/tests/test.rs b/sdk-tests/sdk-native-test/tests/test.rs index fef01c8e62..f3c5e97d73 100644 --- a/sdk-tests/sdk-native-test/tests/test.rs +++ b/sdk-tests/sdk-native-test/tests/test.rs @@ -9,8 +9,7 @@ use light_program_test::{ program_test::LightProgramTest, AddressWithTree, Indexer, ProgramTestConfig, Rpc, RpcError, }; use light_sdk::instruction::{ - account_meta::CompressedAccountMeta, PackedAccounts, PackedAccountsExt, - SystemAccountMetaConfig, + account_meta::CompressedAccountMeta, PackedAccounts, SystemAccountMetaConfig, }; use sdk_native_test::{ create_pda::CreatePdaInstructionData, diff --git a/sdk-tests/sdk-pinocchio-v1-test/tests/test.rs b/sdk-tests/sdk-pinocchio-v1-test/tests/test.rs index 9242ba3f36..0ae7f5c029 100644 --- a/sdk-tests/sdk-pinocchio-v1-test/tests/test.rs +++ b/sdk-tests/sdk-pinocchio-v1-test/tests/test.rs @@ -5,7 +5,7 @@ use light_compressed_account::compressed_account::CompressedAccountWithMerkleCon use light_program_test::{ program_test::LightProgramTest, AddressWithTree, Indexer, ProgramTestConfig, Rpc, RpcError, }; -use light_sdk::instruction::{PackedAccounts, PackedAccountsExt, SystemAccountMetaConfig}; +use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; use light_sdk_pinocchio::instruction::{account_meta::CompressedAccountMeta, PackedStateTreeInfo}; use sdk_pinocchio_v1_test::{ create_pda::CreatePdaInstructionData, diff --git a/sdk-tests/sdk-pinocchio-v2-test/tests/test.rs b/sdk-tests/sdk-pinocchio-v2-test/tests/test.rs index 5d15f1a53a..59a0562c63 100644 --- a/sdk-tests/sdk-pinocchio-v2-test/tests/test.rs +++ b/sdk-tests/sdk-pinocchio-v2-test/tests/test.rs @@ -8,7 +8,7 @@ use light_compressed_account::{ use light_program_test::{ program_test::LightProgramTest, AddressWithTree, Indexer, ProgramTestConfig, Rpc, RpcError, }; -use light_sdk::instruction::{PackedAccounts, PackedAccountsExt, SystemAccountMetaConfig}; +use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; use light_sdk_pinocchio::instruction::{account_meta::CompressedAccountMeta, PackedStateTreeInfo}; use sdk_pinocchio_v2_test::{ create_pda::CreatePdaInstructionData, diff --git a/sdk-tests/sdk-token-test/tests/ctoken_pda.rs b/sdk-tests/sdk-token-test/tests/ctoken_pda.rs index 9100b6a7f8..69a344e04d 100644 --- a/sdk-tests/sdk-token-test/tests/ctoken_pda.rs +++ b/sdk-tests/sdk-token-test/tests/ctoken_pda.rs @@ -5,7 +5,7 @@ use light_compressed_token_sdk::compressed_token::create_compressed_mint::{ derive_mint_compressed_address, find_mint_address, }; use light_program_test::{LightProgramTest, ProgramTestConfig, Rpc, RpcError}; -use light_sdk::instruction::{PackedAccounts, PackedAccountsExt, SystemAccountMetaConfig}; +use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; use light_token_interface::{ instructions::{ extensions::token_metadata::TokenMetadataInstructionData, diff --git a/sdk-tests/sdk-token-test/tests/pda_ctoken.rs b/sdk-tests/sdk-token-test/tests/pda_ctoken.rs index df0d4ec539..4c19a08d88 100644 --- a/sdk-tests/sdk-token-test/tests/pda_ctoken.rs +++ b/sdk-tests/sdk-token-test/tests/pda_ctoken.rs @@ -8,7 +8,7 @@ use light_compressed_token_sdk::compressed_token::create_compressed_mint::{ derive_mint_compressed_address, find_mint_address, }; use light_program_test::{LightProgramTest, ProgramTestConfig, Rpc, RpcError}; -use light_sdk::instruction::{PackedAccounts, PackedAccountsExt, SystemAccountMetaConfig}; +use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; use light_token::instruction::{ derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, }; diff --git a/sdk-tests/sdk-token-test/tests/test_4_invocations.rs b/sdk-tests/sdk-token-test/tests/test_4_invocations.rs index 1fbeaed92b..9e70170056 100644 --- a/sdk-tests/sdk-token-test/tests/test_4_invocations.rs +++ b/sdk-tests/sdk-token-test/tests/test_4_invocations.rs @@ -9,7 +9,7 @@ use light_compressed_token_sdk::{ use light_program_test::{AddressWithTree, Indexer, LightProgramTest, ProgramTestConfig, Rpc}; use light_sdk::{ address::v1::derive_address, - instruction::{PackedAccounts, PackedAccountsExt, SystemAccountMetaConfig}, + instruction::{PackedAccounts, SystemAccountMetaConfig}, }; use light_test_utils::{ spl::{create_mint_helper, create_token_account, mint_spl_tokens}, diff --git a/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs b/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs index 84dbcb4351..fd978aeba7 100644 --- a/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs +++ b/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs @@ -9,7 +9,7 @@ use light_compressed_token_sdk::{ use light_program_test::{AddressWithTree, Indexer, LightProgramTest, ProgramTestConfig, Rpc}; use light_sdk::{ address::v1::derive_address, - instruction::{PackedAccounts, PackedAccountsExt, PackedStateTreeInfo, SystemAccountMetaConfig}, + instruction::{PackedAccounts, PackedStateTreeInfo, SystemAccountMetaConfig}, }; use light_test_utils::RpcError; use light_token::instruction::CreateAssociatedTokenAccount; diff --git a/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs b/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs index 16af41a08a..badb7dce42 100644 --- a/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs +++ b/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs @@ -7,7 +7,7 @@ use light_compressed_token_sdk::compressed_token::{ mint_to_compressed::{create_mint_to_compressed_instruction, MintToCompressedInputs}, }; use light_program_test::{Indexer, LightProgramTest, ProgramTestConfig, Rpc}; -use light_sdk::instruction::{PackedAccounts, PackedAccountsExt, SystemAccountMetaConfig}; +use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; use light_test_utils::actions::legacy::instructions::transfer2::create_decompress_instruction; use light_token::instruction::{ config_pda, derive_token_ata, rent_sponsor_pda, CompressibleParams, diff --git a/sdk-tests/sdk-token-test/tests/test_deposit.rs b/sdk-tests/sdk-token-test/tests/test_deposit.rs index 75c0170dcb..9ebcbd8549 100644 --- a/sdk-tests/sdk-token-test/tests/test_deposit.rs +++ b/sdk-tests/sdk-token-test/tests/test_deposit.rs @@ -10,10 +10,7 @@ use light_compressed_token_sdk::{ use light_program_test::{AddressWithTree, Indexer, LightProgramTest, ProgramTestConfig, Rpc}; use light_sdk::{ address::v1::derive_address, - instruction::{ - account_meta::CompressedAccountMeta, PackedAccounts, PackedAccountsExt, - SystemAccountMetaConfig, - }, + instruction::{account_meta::CompressedAccountMeta, PackedAccounts, SystemAccountMetaConfig}, }; use light_test_utils::{ spl::{create_mint_helper, create_token_account, mint_spl_tokens}, diff --git a/sdk-tests/sdk-v1-native-test/tests/test.rs b/sdk-tests/sdk-v1-native-test/tests/test.rs index 86c7381aa4..16580c9418 100644 --- a/sdk-tests/sdk-v1-native-test/tests/test.rs +++ b/sdk-tests/sdk-v1-native-test/tests/test.rs @@ -6,8 +6,7 @@ use light_program_test::{ program_test::LightProgramTest, AddressWithTree, Indexer, ProgramTestConfig, Rpc, RpcError, }; use light_sdk::instruction::{ - account_meta::CompressedAccountMeta, PackedAccounts, PackedAccountsExt, - SystemAccountMetaConfig, + account_meta::CompressedAccountMeta, PackedAccounts, SystemAccountMetaConfig, }; use sdk_native_test::{ create_pda::CreatePdaInstructionData, From 9876c8558b66753f29c58c0a995a88b2a70d9bda Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 2 Feb 2026 22:15:21 +0000 Subject: [PATCH 13/15] anchor semi manual LightProgram test works --- Cargo.lock | 72 +++++++-------- Cargo.toml | 2 +- .../src/light_pdas/program/decompress.rs | 30 +++++-- .../Cargo.toml | 4 +- .../src/instruction_accounts.rs | 0 .../src/lib.rs | 2 +- .../src/state.rs | 0 .../tests/shared/mod.rs | 4 +- .../tests/stress_test.rs | 68 ++++++++++++--- .../tests/test_create_all.rs | 66 +++++++++++--- .../tests/test_create_ata.rs | 8 +- .../tests/test_create_mint.rs | 8 +- .../tests/test_create_pda.rs | 10 +-- .../tests/test_create_token_vault.rs | 87 ++++++++++++++++--- .../tests/test_create_two_mints.rs | 8 +- .../tests/test_create_zero_copy_record.rs | 10 +-- 16 files changed, 273 insertions(+), 106 deletions(-) rename sdk-tests/{pinocchio-derive-test => anchor-semi-manual-test}/Cargo.toml (96%) rename sdk-tests/{pinocchio-derive-test => anchor-semi-manual-test}/src/instruction_accounts.rs (100%) rename sdk-tests/{pinocchio-derive-test => anchor-semi-manual-test}/src/lib.rs (99%) rename sdk-tests/{pinocchio-derive-test => anchor-semi-manual-test}/src/state.rs (100%) rename sdk-tests/{pinocchio-derive-test => anchor-semi-manual-test}/tests/shared/mod.rs (97%) rename sdk-tests/{pinocchio-derive-test => anchor-semi-manual-test}/tests/stress_test.rs (87%) rename sdk-tests/{pinocchio-derive-test => anchor-semi-manual-test}/tests/test_create_all.rs (85%) rename sdk-tests/{pinocchio-derive-test => anchor-semi-manual-test}/tests/test_create_ata.rs (94%) rename sdk-tests/{pinocchio-derive-test => anchor-semi-manual-test}/tests/test_create_mint.rs (94%) rename sdk-tests/{pinocchio-derive-test => anchor-semi-manual-test}/tests/test_create_pda.rs (92%) rename sdk-tests/{pinocchio-derive-test => anchor-semi-manual-test}/tests/test_create_token_vault.rs (50%) rename sdk-tests/{pinocchio-derive-test => anchor-semi-manual-test}/tests/test_create_two_mints.rs (95%) rename sdk-tests/{pinocchio-derive-test => anchor-semi-manual-test}/tests/test_create_zero_copy_record.rs (91%) diff --git a/Cargo.lock b/Cargo.lock index 16ee9426db..ac2f5d616c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -395,6 +395,42 @@ dependencies = [ "serde", ] +[[package]] +name = "anchor-semi-manual-test" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "borsh 0.10.4", + "bytemuck", + "light-account", + "light-anchor-spl", + "light-batched-merkle-tree", + "light-client", + "light-compressed-account", + "light-compressible", + "light-hasher", + "light-macros", + "light-program-test", + "light-sdk-macros", + "light-sdk-types", + "light-test-utils", + "light-token", + "light-token-interface", + "light-token-types", + "rand 0.8.5", + "solana-account", + "solana-account-info", + "solana-instruction", + "solana-keypair", + "solana-msg 2.2.1", + "solana-program", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "solana-sdk", + "solana-signer", + "tokio", +] + [[package]] name = "anchor-spl" version = "0.31.1" @@ -5153,42 +5189,6 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b971851087bc3699b001954ad02389d50c41405ece3548cbcafc88b3e20017a" -[[package]] -name = "pinocchio-derive-test" -version = "0.1.0" -dependencies = [ - "anchor-lang", - "borsh 0.10.4", - "bytemuck", - "light-account", - "light-anchor-spl", - "light-batched-merkle-tree", - "light-client", - "light-compressed-account", - "light-compressible", - "light-hasher", - "light-macros", - "light-program-test", - "light-sdk-macros", - "light-sdk-types", - "light-test-utils", - "light-token", - "light-token-interface", - "light-token-types", - "rand 0.8.5", - "solana-account", - "solana-account-info", - "solana-instruction", - "solana-keypair", - "solana-msg 2.2.1", - "solana-program", - "solana-program-error 2.2.2", - "solana-pubkey 2.4.0", - "solana-sdk", - "solana-signer", - "tokio", -] - [[package]] name = "pinocchio-log" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index f593c04cb6..20b121391e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,7 @@ members = [ "sdk-tests/csdk-anchor-full-derived-test-sdk", "sdk-tests/single-mint-test", "sdk-tests/single-pda-test", - "sdk-tests/pinocchio-derive-test", + "sdk-tests/anchor-semi-manual-test", "sdk-tests/single-account-loader-test", "sdk-tests/single-ata-test", "sdk-tests/single-token-test", diff --git a/sdk-libs/macros/src/light_pdas/program/decompress.rs b/sdk-libs/macros/src/light_pdas/program/decompress.rs index c00eac0975..405dc65e6e 100644 --- a/sdk-libs/macros/src/light_pdas/program/decompress.rs +++ b/sdk-libs/macros/src/light_pdas/program/decompress.rs @@ -376,6 +376,28 @@ impl DecompressBuilder { /// This wraps the type-parameter-based SDK call, binding `PackedLightAccountVariant` /// as the concrete type. pub fn generate_enum_decompress_dispatch(&self, enum_name: &syn::Ident) -> Result { + let processor_call = if self.has_tokens { + quote! { + light_account::process_decompress_accounts_idempotent::<_, PackedLightAccountVariant>( + remaining_accounts, + instruction_data, + cpi_signer, + program_id, + current_slot, + ) + } + } else { + quote! { + light_account::process_decompress_pda_accounts_idempotent::<_, PackedLightAccountVariant>( + remaining_accounts, + instruction_data, + cpi_signer, + program_id, + current_slot, + ) + } + }; + Ok(quote! { impl #enum_name { pub fn decompress_dispatch<'info>( @@ -385,13 +407,7 @@ impl DecompressBuilder { program_id: &[u8; 32], current_slot: u64, ) -> std::result::Result<(), light_account::LightSdkTypesError> { - light_account::process_decompress_pda_accounts_idempotent::<_, PackedLightAccountVariant>( - remaining_accounts, - instruction_data, - cpi_signer, - program_id, - current_slot, - ) + #processor_call } } }) diff --git a/sdk-tests/pinocchio-derive-test/Cargo.toml b/sdk-tests/anchor-semi-manual-test/Cargo.toml similarity index 96% rename from sdk-tests/pinocchio-derive-test/Cargo.toml rename to sdk-tests/anchor-semi-manual-test/Cargo.toml index 143a0d1cce..41dd5d2a51 100644 --- a/sdk-tests/pinocchio-derive-test/Cargo.toml +++ b/sdk-tests/anchor-semi-manual-test/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "pinocchio-derive-test" +name = "anchor-semi-manual-test" version = "0.1.0" description = "Test for #[derive(LightProgram)] macro validation with all variant kinds" edition = "2021" [lib] crate-type = ["cdylib", "lib"] -name = "pinocchio_derive_test" +name = "anchor_semi_manual_test" [features] no-entrypoint = [] diff --git a/sdk-tests/pinocchio-derive-test/src/instruction_accounts.rs b/sdk-tests/anchor-semi-manual-test/src/instruction_accounts.rs similarity index 100% rename from sdk-tests/pinocchio-derive-test/src/instruction_accounts.rs rename to sdk-tests/anchor-semi-manual-test/src/instruction_accounts.rs diff --git a/sdk-tests/pinocchio-derive-test/src/lib.rs b/sdk-tests/anchor-semi-manual-test/src/lib.rs similarity index 99% rename from sdk-tests/pinocchio-derive-test/src/lib.rs rename to sdk-tests/anchor-semi-manual-test/src/lib.rs index de847fa24a..64367c1cac 100644 --- a/sdk-tests/pinocchio-derive-test/src/lib.rs +++ b/sdk-tests/anchor-semi-manual-test/src/lib.rs @@ -45,7 +45,7 @@ pub enum ProgramAccounts { } #[program] -pub mod pinocchio_derive_test { +pub mod anchor_semi_manual_test { use light_account::{light_err, LightFinalize, LightPreInit}; use super::*; diff --git a/sdk-tests/pinocchio-derive-test/src/state.rs b/sdk-tests/anchor-semi-manual-test/src/state.rs similarity index 100% rename from sdk-tests/pinocchio-derive-test/src/state.rs rename to sdk-tests/anchor-semi-manual-test/src/state.rs diff --git a/sdk-tests/pinocchio-derive-test/tests/shared/mod.rs b/sdk-tests/anchor-semi-manual-test/tests/shared/mod.rs similarity index 97% rename from sdk-tests/pinocchio-derive-test/tests/shared/mod.rs rename to sdk-tests/anchor-semi-manual-test/tests/shared/mod.rs index 459d13bdac..293c26399c 100644 --- a/sdk-tests/pinocchio-derive-test/tests/shared/mod.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/shared/mod.rs @@ -21,9 +21,9 @@ pub struct TestEnv { /// Sets up a test environment with program, config, and rent sponsor initialized. pub async fn setup_test_env() -> TestEnv { - let program_id = pinocchio_derive_test::ID; + let program_id = anchor_semi_manual_test::ID; let mut config = - ProgramTestConfig::new_v2(true, Some(vec![("pinocchio_derive_test", program_id)])); + ProgramTestConfig::new_v2(true, Some(vec![("anchor_semi_manual_test", program_id)])); config = config.with_light_protocol_events(); let mut rpc = LightProgramTest::new(config).await.unwrap(); diff --git a/sdk-tests/pinocchio-derive-test/tests/stress_test.rs b/sdk-tests/anchor-semi-manual-test/tests/stress_test.rs similarity index 87% rename from sdk-tests/pinocchio-derive-test/tests/stress_test.rs rename to sdk-tests/anchor-semi-manual-test/tests/stress_test.rs index 19ac002937..4d3d2972a0 100644 --- a/sdk-tests/pinocchio-derive-test/tests/stress_test.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/stress_test.rs @@ -8,6 +8,11 @@ mod shared; use anchor_lang::{AnchorDeserialize, InstructionData, ToAccountMetas}; +use anchor_semi_manual_test::{ + CreateAllParams, LightAccountVariant, MinimalRecord, MinimalRecordSeeds, VaultSeeds, + ZeroCopyRecord, ZeroCopyRecordSeeds, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, RECORD_SEED, + VAULT_AUTH_SEED, VAULT_SEED, +}; use light_batched_merkle_tree::{ initialize_address_tree::InitAddressTreeAccountsInstructionData, initialize_state_tree::InitStateTreeAccountsInstructionData, @@ -22,11 +27,6 @@ use light_program_test::{ ProgramTestConfig, Rpc, }; use light_token_interface::state::{token::Token, Mint}; -use pinocchio_derive_test::{ - CreateAllParams, LightAccountVariant, MinimalRecord, MinimalRecordSeeds, ZeroCopyRecord, - ZeroCopyRecordSeeds, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, RECORD_SEED, VAULT_AUTH_SEED, - VAULT_SEED, -}; use solana_instruction::Instruction; use solana_keypair::Keypair; use solana_pubkey::Pubkey; @@ -48,13 +48,12 @@ struct TestPdas { } /// Cached state for accounts that go through the compress/decompress cycle. -/// Note: vault stays compressed permanently (standalone token PDA decompression -/// is not supported), so it is excluded from the cycle. #[derive(Clone)] struct CachedState { record: MinimalRecord, zc_record: ZeroCopyRecord, ata_token: Token, + vault_token: Token, owner: Pubkey, } @@ -72,9 +71,9 @@ fn parse_token(data: &[u8]) -> Token { /// Setup environment with larger queues for stress test async fn setup() -> (StressTestContext, TestPdas) { - let program_id = pinocchio_derive_test::ID; + let program_id = anchor_semi_manual_test::ID; let mut config = - ProgramTestConfig::new_v2(true, Some(vec![("pinocchio_derive_test", program_id)])) + ProgramTestConfig::new_v2(true, Some(vec![("anchor_semi_manual_test", program_id)])) .with_light_protocol_events(); config.v2_state_tree_config = Some(InitStateTreeAccountsInstructionData::e2e_test_default()); config.v2_address_tree_config = @@ -142,7 +141,7 @@ async fn setup() -> (StressTestContext, TestPdas) { .await .unwrap(); - let accounts = pinocchio_derive_test::accounts::CreateAll { + let accounts = anchor_semi_manual_test::accounts::CreateAll { fee_payer: payer.pubkey(), compression_config: config_pda, pda_rent_sponsor: rent_sponsor, @@ -166,7 +165,7 @@ async fn setup() -> (StressTestContext, TestPdas) { system_program: solana_sdk::system_program::ID, }; - let instruction_data = pinocchio_derive_test::instruction::CreateAll { + let instruction_data = anchor_semi_manual_test::instruction::CreateAll { params: CreateAllParams { create_accounts_proof: proof_result.create_accounts_proof, owner, @@ -224,16 +223,18 @@ async fn refresh_cache(rpc: &mut LightProgramTest, pdas: &TestPdas, owner: Pubke let zc_record: ZeroCopyRecord = *bytemuck::from_bytes(&zc_account.data[8..]); let ata_token = parse_token(&rpc.get_account(pdas.ata).await.unwrap().unwrap().data); + let vault_token = parse_token(&rpc.get_account(pdas.vault).await.unwrap().unwrap().data); CachedState { record, zc_record, ata_token, + vault_token, owner, } } -/// Decompress all accounts except vault (vault stays compressed permanently) +/// Decompress all accounts async fn decompress_all(ctx: &mut StressTestContext, pdas: &TestPdas, cached: &CachedState) { // PDA: MinimalRecord let record_interface = ctx @@ -279,6 +280,33 @@ async fn decompress_all(ctx: &mut StressTestContext, pdas: &TestPdas, cached: &C .expect("failed to get ATA interface"); assert!(ata_interface.is_cold(), "ATA should be cold"); + // Token PDA: Vault + let vault_iface = ctx + .rpc + .get_token_account_interface(&pdas.vault) + .await + .expect("failed to get vault interface"); + assert!(vault_iface.is_cold(), "Vault should be cold"); + + let vault_token_data: Token = + borsh::BorshDeserialize::deserialize(&mut &vault_iface.account.data[..]) + .expect("Failed to parse vault Token"); + let vault_variant = LightAccountVariant::Vault(light_account::token::TokenDataWithSeeds { + seeds: VaultSeeds { + mint: pdas.vault_mint, + }, + token_data: vault_token_data, + }); + let vault_compressed = vault_iface + .compressed() + .expect("cold vault must have compressed data"); + let vault_interface = AccountInterface { + key: vault_iface.key, + account: vault_iface.account.clone(), + cold: Some(ColdContext::Account(vault_compressed.account.clone())), + }; + let vault_spec = PdaSpec::new(vault_interface, vault_variant, ctx.program_id); + // Mint A let mint_a_iface = ctx .rpc @@ -327,6 +355,7 @@ async fn decompress_all(ctx: &mut StressTestContext, pdas: &TestPdas, cached: &C AccountSpec::Pda(record_spec), AccountSpec::Pda(zc_spec), AccountSpec::Ata(ata_interface), + AccountSpec::Pda(vault_spec), AccountSpec::Mint(mint_a_ai), AccountSpec::Mint(mint_b_ai), ]; @@ -341,17 +370,17 @@ async fn decompress_all(ctx: &mut StressTestContext, pdas: &TestPdas, cached: &C .await .expect("Decompression should succeed"); - // Verify decompressed accounts exist on-chain (vault stays compressed) + // Verify all decompressed accounts exist on-chain for (pda, name) in [ (&pdas.record, "MinimalRecord"), (&pdas.zc_record, "ZeroCopyRecord"), (&pdas.ata, "ATA"), + (&pdas.vault, "Vault"), (&pdas.mint_a, "MintA"), (&pdas.mint_b, "MintB"), ] { shared::assert_onchain_exists(&mut ctx.rpc, pda, name).await; } - shared::assert_onchain_closed(&mut ctx.rpc, &pdas.vault, "Vault").await; } /// Compress all accounts by warping forward epochs @@ -416,6 +445,17 @@ async fn assert_all_state( "ATA mismatch at iteration {iteration}" ); + // Vault + let actual_vault = parse_token(&rpc.get_account(pdas.vault).await.unwrap().unwrap().data); + let expected_vault = Token { + extensions: actual_vault.extensions.clone(), + ..cached.vault_token.clone() + }; + assert_eq!( + actual_vault, expected_vault, + "Vault mismatch at iteration {iteration}" + ); + // Mints let actual_mint_a: Mint = borsh::BorshDeserialize::deserialize( &mut &rpc.get_account(pdas.mint_a).await.unwrap().unwrap().data[..], diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_all.rs b/sdk-tests/anchor-semi-manual-test/tests/test_create_all.rs similarity index 85% rename from sdk-tests/pinocchio-derive-test/tests/test_create_all.rs rename to sdk-tests/anchor-semi-manual-test/tests/test_create_all.rs index c6a2f82f1f..32dcc8b19b 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_all.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/test_create_all.rs @@ -1,6 +1,10 @@ mod shared; use anchor_lang::{AnchorDeserialize, InstructionData, ToAccountMetas}; +use anchor_semi_manual_test::{ + CreateAllParams, MinimalRecord, VaultSeeds, ZeroCopyRecord, MINT_SIGNER_SEED_A, + MINT_SIGNER_SEED_B, RECORD_SEED, VAULT_AUTH_SEED, VAULT_SEED, +}; use light_client::interface::{ create_load_instructions, get_create_accounts_proof, AccountInterface, AccountInterfaceExt, AccountSpec, ColdContext, CreateAccountsProofInput, PdaSpec, @@ -10,10 +14,6 @@ use light_program_test::{program_test::TestRpc, Rpc}; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; -use pinocchio_derive_test::{ - CreateAllParams, MinimalRecord, ZeroCopyRecord, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, - RECORD_SEED, VAULT_AUTH_SEED, VAULT_SEED, -}; use solana_instruction::Instruction; use solana_keypair::Keypair; use solana_pubkey::Pubkey; @@ -78,7 +78,7 @@ async fn test_create_all_derive() { .await .unwrap(); - let accounts = pinocchio_derive_test::accounts::CreateAll { + let accounts = anchor_semi_manual_test::accounts::CreateAll { fee_payer: payer.pubkey(), compression_config: env.config_pda, pda_rent_sponsor: env.rent_sponsor, @@ -102,7 +102,7 @@ async fn test_create_all_derive() { system_program: solana_sdk::system_program::ID, }; - let instruction_data = pinocchio_derive_test::instruction::CreateAll { + let instruction_data = anchor_semi_manual_test::instruction::CreateAll { params: CreateAllParams { create_accounts_proof: proof_result.create_accounts_proof, owner, @@ -215,9 +215,8 @@ async fn test_create_all_derive() { shared::assert_onchain_closed(&mut rpc, &mint_a_pda, "MintA").await; shared::assert_onchain_closed(&mut rpc, &mint_b_pda, "MintB").await; - // PHASE 3: Decompress all accounts except vault via create_load_instructions. - // Note: standalone token PDA decompression is not supported; vaults stay compressed. - use pinocchio_derive_test::{LightAccountVariant, MinimalRecordSeeds, ZeroCopyRecordSeeds}; + // PHASE 3: Decompress all accounts via create_load_instructions. + use anchor_semi_manual_test::{LightAccountVariant, MinimalRecordSeeds, ZeroCopyRecordSeeds}; // PDA: MinimalRecord let record_interface = rpc @@ -298,10 +297,35 @@ async fn test_create_all_derive() { cold: Some(ColdContext::Account(compressed_b.clone())), }; + // Token PDA: Vault + let vault_iface = rpc + .get_token_account_interface(&vault) + .await + .expect("failed to get vault interface"); + assert!(vault_iface.is_cold(), "Vault should be cold"); + + let vault_token_data: Token = + borsh::BorshDeserialize::deserialize(&mut &vault_iface.account.data[..]) + .expect("Failed to parse vault Token"); + let vault_variant = LightAccountVariant::Vault(light_account::token::TokenDataWithSeeds { + seeds: VaultSeeds { mint: vault_mint }, + token_data: vault_token_data, + }); + let vault_compressed = vault_iface + .compressed() + .expect("cold vault must have compressed data"); + let vault_interface = AccountInterface { + key: vault_iface.key, + account: vault_iface.account.clone(), + cold: Some(ColdContext::Account(vault_compressed.account.clone())), + }; + let vault_spec = PdaSpec::new(vault_interface, vault_variant, program_id); + let specs: Vec> = vec![ AccountSpec::Pda(record_spec), AccountSpec::Pda(zc_spec), AccountSpec::Ata(ata_interface), + AccountSpec::Pda(vault_spec), AccountSpec::Mint(mint_a_ai), AccountSpec::Mint(mint_b_ai), ]; @@ -314,11 +338,11 @@ async fn test_create_all_derive() { .await .expect("Decompression should succeed"); - // PHASE 4: Assert state preserved after decompression (vault stays compressed) + // PHASE 4: Assert state preserved after decompression shared::assert_onchain_exists(&mut rpc, &record_pda, "MinimalRecord").await; shared::assert_onchain_exists(&mut rpc, &zc_record_pda, "ZeroCopyRecord").await; shared::assert_onchain_exists(&mut rpc, &ata, "ATA").await; - shared::assert_onchain_closed(&mut rpc, &vault, "Vault").await; + shared::assert_onchain_exists(&mut rpc, &vault, "Vault").await; shared::assert_onchain_exists(&mut rpc, &mint_a_pda, "MintA").await; shared::assert_onchain_exists(&mut rpc, &mint_b_pda, "MintB").await; @@ -367,6 +391,26 @@ async fn test_create_all_derive() { "ATA should match after decompression" ); + // Vault + let actual_vault: Token = + shared::parse_token(&rpc.get_account(vault).await.unwrap().unwrap().data); + let expected_vault = Token { + mint: LPubkey::from(vault_mint.to_bytes()), + owner: LPubkey::from(vault_authority.to_bytes()), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT, + extensions: actual_vault.extensions.clone(), + }; + assert_eq!( + actual_vault, expected_vault, + "Vault should match after decompression" + ); + // Mints let actual_ma: Mint = borsh::BorshDeserialize::deserialize( &mut &rpc.get_account(mint_a_pda).await.unwrap().unwrap().data[..], diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_ata.rs b/sdk-tests/anchor-semi-manual-test/tests/test_create_ata.rs similarity index 94% rename from sdk-tests/pinocchio-derive-test/tests/test_create_ata.rs rename to sdk-tests/anchor-semi-manual-test/tests/test_create_ata.rs index a5e576ff33..3f7ff9883b 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_ata.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/test_create_ata.rs @@ -1,6 +1,7 @@ mod shared; use anchor_lang::{InstructionData, ToAccountMetas}; +use anchor_semi_manual_test::CreateAtaParams; use light_client::interface::{ create_load_instructions, get_create_accounts_proof, AccountInterfaceExt, AccountSpec, }; @@ -8,7 +9,6 @@ use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Rpc}; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; -use pinocchio_derive_test::CreateAtaParams; use solana_instruction::Instruction; use solana_signer::Signer; @@ -28,7 +28,7 @@ async fn test_create_ata_derive() { .await .unwrap(); - let accounts = pinocchio_derive_test::accounts::CreateAta { + let accounts = anchor_semi_manual_test::accounts::CreateAta { fee_payer: payer.pubkey(), ata_mint: mint, ata_owner, @@ -39,7 +39,7 @@ async fn test_create_ata_derive() { system_program: solana_sdk::system_program::ID, }; - let instruction_data = pinocchio_derive_test::instruction::CreateAta { + let instruction_data = anchor_semi_manual_test::instruction::CreateAta { params: CreateAtaParams { create_accounts_proof: proof_result.create_accounts_proof, ata_bump, @@ -94,7 +94,7 @@ async fn test_create_ata_derive() { shared::assert_onchain_closed(&mut rpc, &ata, "ATA").await; // PHASE 3: Decompress via create_load_instructions - use pinocchio_derive_test::LightAccountVariant; + use anchor_semi_manual_test::LightAccountVariant; let ata_interface = rpc .get_ata_interface(&ata_owner, &mint) diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_mint.rs b/sdk-tests/anchor-semi-manual-test/tests/test_create_mint.rs similarity index 94% rename from sdk-tests/pinocchio-derive-test/tests/test_create_mint.rs rename to sdk-tests/anchor-semi-manual-test/tests/test_create_mint.rs index a3ce0fe0e6..8af51ab83e 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_mint.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/test_create_mint.rs @@ -1,6 +1,7 @@ mod shared; use anchor_lang::{InstructionData, ToAccountMetas}; +use anchor_semi_manual_test::{CreateMintParams, MINT_SIGNER_SEED_A}; use light_client::interface::{ create_load_instructions, get_create_accounts_proof, AccountInterface, AccountInterfaceExt, AccountSpec, ColdContext, CreateAccountsProofInput, @@ -9,7 +10,6 @@ use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Rpc}; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; -use pinocchio_derive_test::{CreateMintParams, MINT_SIGNER_SEED_A}; use solana_instruction::Instruction; use solana_keypair::Keypair; use solana_pubkey::Pubkey; @@ -39,7 +39,7 @@ async fn test_create_mint_derive() { .await .unwrap(); - let accounts = pinocchio_derive_test::accounts::CreateMint { + let accounts = anchor_semi_manual_test::accounts::CreateMint { fee_payer: payer.pubkey(), authority: authority.pubkey(), mint_signer: mint_signer_pda, @@ -52,7 +52,7 @@ async fn test_create_mint_derive() { system_program: solana_sdk::system_program::ID, }; - let instruction_data = pinocchio_derive_test::instruction::CreateMint { + let instruction_data = anchor_semi_manual_test::instruction::CreateMint { params: CreateMintParams { create_accounts_proof: proof_result.create_accounts_proof, mint_signer_bump, @@ -96,7 +96,7 @@ async fn test_create_mint_derive() { shared::assert_onchain_closed(&mut rpc, &mint_pda, "Mint").await; // PHASE 3: Decompress via create_load_instructions - use pinocchio_derive_test::LightAccountVariant; + use anchor_semi_manual_test::LightAccountVariant; let mint_interface = rpc .get_mint_interface(&mint_pda) diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_pda.rs b/sdk-tests/anchor-semi-manual-test/tests/test_create_pda.rs similarity index 92% rename from sdk-tests/pinocchio-derive-test/tests/test_create_pda.rs rename to sdk-tests/anchor-semi-manual-test/tests/test_create_pda.rs index d09129d3bd..06b0818f39 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_pda.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/test_create_pda.rs @@ -1,13 +1,13 @@ mod shared; use anchor_lang::{AnchorDeserialize, InstructionData, ToAccountMetas}; +use anchor_semi_manual_test::CreatePdaParams; use light_client::interface::{ create_load_instructions, get_create_accounts_proof, AccountInterfaceExt, AccountSpec, CreateAccountsProofInput, PdaSpec, }; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Rpc}; -use pinocchio_derive_test::CreatePdaParams; use solana_instruction::Instruction; use solana_keypair::Keypair; use solana_pubkey::Pubkey; @@ -33,7 +33,7 @@ async fn test_create_single_pda_derive() { .await .unwrap(); - let accounts = pinocchio_derive_test::accounts::CreatePda { + let accounts = anchor_semi_manual_test::accounts::CreatePda { fee_payer: payer.pubkey(), compression_config: env.config_pda, pda_rent_sponsor: env.rent_sponsor, @@ -41,7 +41,7 @@ async fn test_create_single_pda_derive() { system_program: solana_sdk::system_program::ID, }; - let instruction_data = pinocchio_derive_test::instruction::CreatePda { + let instruction_data = anchor_semi_manual_test::instruction::CreatePda { params: CreatePdaParams { create_accounts_proof: proof_result.create_accounts_proof, owner, @@ -69,7 +69,7 @@ async fn test_create_single_pda_derive() { .unwrap() .expect("Record PDA should exist on-chain"); - use pinocchio_derive_test::MinimalRecord; + use anchor_semi_manual_test::MinimalRecord; let record: MinimalRecord = borsh::BorshDeserialize::deserialize(&mut &record_account.data[8..]) .expect("Failed to deserialize MinimalRecord"); @@ -88,7 +88,7 @@ async fn test_create_single_pda_derive() { shared::assert_onchain_closed(&mut rpc, &record_pda, "MinimalRecord").await; // PHASE 3: Decompress via create_load_instructions - use pinocchio_derive_test::{LightAccountVariant, MinimalRecordSeeds}; + use anchor_semi_manual_test::{LightAccountVariant, MinimalRecordSeeds}; let account_interface = rpc .get_account_interface(&record_pda, &program_id) diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_token_vault.rs b/sdk-tests/anchor-semi-manual-test/tests/test_create_token_vault.rs similarity index 50% rename from sdk-tests/pinocchio-derive-test/tests/test_create_token_vault.rs rename to sdk-tests/anchor-semi-manual-test/tests/test_create_token_vault.rs index 7616659e4c..ba64ff98dd 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_token_vault.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/test_create_token_vault.rs @@ -1,20 +1,23 @@ mod shared; use anchor_lang::{InstructionData, ToAccountMetas}; -use light_client::interface::get_create_accounts_proof; +use anchor_semi_manual_test::{ + CreateTokenVaultParams, LightAccountVariant, VaultSeeds, VAULT_AUTH_SEED, VAULT_SEED, +}; +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterface, AccountInterfaceExt, + AccountSpec, ColdContext, PdaSpec, +}; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Rpc}; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; -use pinocchio_derive_test::{CreateTokenVaultParams, VAULT_AUTH_SEED, VAULT_SEED}; +use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; use solana_instruction::Instruction; use solana_pubkey::Pubkey; use solana_signer::Signer; -/// Token vault lifecycle: create -> verify on-chain -> warp -> verify compressed. -/// Note: standalone token PDA decompression is not supported (token_accounts_offset=0); -/// vaults must be decompressed alongside a regular PDA. See csdk d11_zero_copy_test -/// for the reference pattern. +/// Token vault lifecycle: create -> verify on-chain -> warp -> verify compressed -> decompress -> verify restored. #[tokio::test] async fn test_create_token_vault_derive() { let env = shared::setup_test_env().await; @@ -33,7 +36,7 @@ async fn test_create_token_vault_derive() { .await .unwrap(); - let accounts = pinocchio_derive_test::accounts::CreateTokenVault { + let accounts = anchor_semi_manual_test::accounts::CreateTokenVault { fee_payer: payer.pubkey(), mint, vault_authority, @@ -45,7 +48,7 @@ async fn test_create_token_vault_derive() { system_program: solana_sdk::system_program::ID, }; - let instruction_data = pinocchio_derive_test::instruction::CreateTokenVault { + let instruction_data = anchor_semi_manual_test::instruction::CreateTokenVault { params: CreateTokenVaultParams { create_accounts_proof: proof_result.create_accounts_proof, vault_bump, @@ -67,8 +70,6 @@ async fn test_create_token_vault_derive() { .expect("CreateTokenVault should succeed"); // PHASE 1: Verify on-chain after creation - use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; - let vault_account = rpc .get_account(vault) .await @@ -99,4 +100,70 @@ async fn test_create_token_vault_derive() { // PHASE 2: Warp to trigger auto-compression rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); shared::assert_onchain_closed(&mut rpc, &vault, "Vault").await; + + // PHASE 3: Decompress vault + let vault_iface = rpc + .get_token_account_interface(&vault) + .await + .expect("failed to get vault interface"); + assert!(vault_iface.is_cold(), "Vault should be cold"); + + let token_data: Token = + borsh::BorshDeserialize::deserialize(&mut &vault_iface.account.data[..]) + .expect("Failed to parse vault Token"); + let vault_variant = LightAccountVariant::Vault(light_account::token::TokenDataWithSeeds { + seeds: VaultSeeds { mint }, + token_data, + }); + let vault_compressed = vault_iface + .compressed() + .expect("cold vault must have compressed data"); + let vault_interface = AccountInterface { + key: vault_iface.key, + account: vault_iface.account.clone(), + cold: Some(ColdContext::Account(vault_compressed.account.clone())), + }; + let vault_spec = PdaSpec::new(vault_interface, vault_variant, program_id); + + let specs: Vec> = vec![AccountSpec::Pda(vault_spec)]; + + let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) + .await + .expect("create_load_instructions should succeed"); + + rpc.create_and_send_transaction(&ixs, &payer.pubkey(), &[&payer]) + .await + .expect("Vault decompression should succeed"); + + // PHASE 4: Assert state preserved after decompression + shared::assert_onchain_exists(&mut rpc, &vault, "Vault").await; + + let vault_account = rpc + .get_account(vault) + .await + .unwrap() + .expect("Vault should exist on-chain after decompression"); + + let actual_token: Token = borsh::BorshDeserialize::deserialize(&mut &vault_account.data[..]) + .expect("Failed to deserialize Token after decompression"); + + use light_compressed_account::pubkey::Pubkey as LPubkey; + + let expected_token = Token { + mint: LPubkey::from(mint.to_bytes()), + owner: LPubkey::from(vault_authority.to_bytes()), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT, + extensions: actual_token.extensions.clone(), + }; + + assert_eq!( + actual_token, expected_token, + "Token vault should match expected after decompression" + ); } diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_two_mints.rs b/sdk-tests/anchor-semi-manual-test/tests/test_create_two_mints.rs similarity index 95% rename from sdk-tests/pinocchio-derive-test/tests/test_create_two_mints.rs rename to sdk-tests/anchor-semi-manual-test/tests/test_create_two_mints.rs index 68fa089eb3..4ac2f0dfb3 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_two_mints.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/test_create_two_mints.rs @@ -1,6 +1,7 @@ mod shared; use anchor_lang::{InstructionData, ToAccountMetas}; +use anchor_semi_manual_test::{CreateTwoMintsParams, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B}; use light_client::interface::{ create_load_instructions, get_create_accounts_proof, AccountInterface, AccountInterfaceExt, AccountSpec, ColdContext, CreateAccountsProofInput, @@ -9,7 +10,6 @@ use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Rpc}; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; -use pinocchio_derive_test::{CreateTwoMintsParams, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B}; use solana_instruction::Instruction; use solana_keypair::Keypair; use solana_pubkey::Pubkey; @@ -47,7 +47,7 @@ async fn test_create_two_mints_derive() { .await .unwrap(); - let accounts = pinocchio_derive_test::accounts::CreateTwoMints { + let accounts = anchor_semi_manual_test::accounts::CreateTwoMints { fee_payer: payer.pubkey(), authority: authority.pubkey(), mint_signer_a, @@ -62,7 +62,7 @@ async fn test_create_two_mints_derive() { system_program: solana_sdk::system_program::ID, }; - let instruction_data = pinocchio_derive_test::instruction::CreateTwoMints { + let instruction_data = anchor_semi_manual_test::instruction::CreateTwoMints { params: CreateTwoMintsParams { create_accounts_proof: proof_result.create_accounts_proof, mint_signer_bump_a, @@ -121,7 +121,7 @@ async fn test_create_two_mints_derive() { shared::assert_onchain_closed(&mut rpc, &mint_b_pda, "MintB").await; // PHASE 3: Decompress both mints via create_load_instructions - use pinocchio_derive_test::LightAccountVariant; + use anchor_semi_manual_test::LightAccountVariant; let build_mint_account_interface = |mint_interface: light_client::interface::MintInterface| { let (compressed, _mint_data) = mint_interface diff --git a/sdk-tests/pinocchio-derive-test/tests/test_create_zero_copy_record.rs b/sdk-tests/anchor-semi-manual-test/tests/test_create_zero_copy_record.rs similarity index 91% rename from sdk-tests/pinocchio-derive-test/tests/test_create_zero_copy_record.rs rename to sdk-tests/anchor-semi-manual-test/tests/test_create_zero_copy_record.rs index 15c537728c..0655117ac6 100644 --- a/sdk-tests/pinocchio-derive-test/tests/test_create_zero_copy_record.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/test_create_zero_copy_record.rs @@ -1,13 +1,13 @@ mod shared; use anchor_lang::{InstructionData, ToAccountMetas}; +use anchor_semi_manual_test::{CreateZeroCopyRecordParams, RECORD_SEED}; use light_client::interface::{ create_load_instructions, get_create_accounts_proof, AccountInterfaceExt, AccountSpec, CreateAccountsProofInput, PdaSpec, }; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Rpc}; -use pinocchio_derive_test::{CreateZeroCopyRecordParams, RECORD_SEED}; use solana_instruction::Instruction; use solana_keypair::Keypair; use solana_pubkey::Pubkey; @@ -32,7 +32,7 @@ async fn test_create_zero_copy_record_derive() { .await .unwrap(); - let accounts = pinocchio_derive_test::accounts::CreateZeroCopyRecord { + let accounts = anchor_semi_manual_test::accounts::CreateZeroCopyRecord { fee_payer: payer.pubkey(), compression_config: env.config_pda, pda_rent_sponsor: env.rent_sponsor, @@ -40,7 +40,7 @@ async fn test_create_zero_copy_record_derive() { system_program: solana_sdk::system_program::ID, }; - let instruction_data = pinocchio_derive_test::instruction::CreateZeroCopyRecord { + let instruction_data = anchor_semi_manual_test::instruction::CreateZeroCopyRecord { params: CreateZeroCopyRecordParams { create_accounts_proof: proof_result.create_accounts_proof, owner, @@ -68,7 +68,7 @@ async fn test_create_zero_copy_record_derive() { .unwrap() .expect("Record PDA should exist on-chain"); - use pinocchio_derive_test::ZeroCopyRecord; + use anchor_semi_manual_test::ZeroCopyRecord; let discriminator_len = 8; let data = &record_account.data[discriminator_len..]; let record: &ZeroCopyRecord = bytemuck::from_bytes(data); @@ -82,7 +82,7 @@ async fn test_create_zero_copy_record_derive() { // PHASE 3: Decompress via create_load_instructions use anchor_lang::AnchorDeserialize; - use pinocchio_derive_test::{LightAccountVariant, ZeroCopyRecordSeeds}; + use anchor_semi_manual_test::{LightAccountVariant, ZeroCopyRecordSeeds}; let account_interface = rpc .get_account_interface(&record_pda, &program_id) From d8f36ab65fe07e2ea236c457fcf16846eba271e4 Mon Sep 17 00:00:00 2001 From: ananas Date: Tue, 3 Feb 2026 00:38:34 +0000 Subject: [PATCH 14/15] light program pinocchio works --- Cargo.lock | 36 ++ Cargo.toml | 1 + .../src/account_info/pinocchio.rs | 14 +- sdk-libs/macros/src/lib.rs | 15 + .../macros/src/light_pdas/accounts/builder.rs | 4 +- .../macros/src/light_pdas/accounts/mint.rs | 4 +- .../macros/src/light_pdas/accounts/pda.rs | 2 +- .../macros/src/light_pdas/accounts/token.rs | 6 +- .../macros/src/light_pdas/accounts/variant.rs | 393 +++++++++++++- .../macros/src/light_pdas/program/compress.rs | 115 +++- .../src/light_pdas/program/decompress.rs | 120 ++++- .../program/derive_light_program.rs | 88 +++ .../src/light_pdas/program/instructions.rs | 470 +++++++++++++++- sdk-libs/macros/src/light_pdas/program/mod.rs | 2 +- .../src/light_pdas/program/seed_codegen.rs | 6 +- .../src/light_pdas/program/variant_enum.rs | 510 +++++++++++++++++- .../interface/program/decompression/pda.rs | 2 +- .../program/decompression/processor.rs | 4 +- .../interface/program/decompression/token.rs | 4 +- sdk-libs/sdk/src/cpi/mod.rs | 5 +- sdk-libs/token-sdk/src/anchor.rs | 41 -- sdk-libs/token-sdk/src/lib.rs | 2 - .../manual-test-pinocchio/src/all/derived.rs | 2 +- .../src/two_mints/derived.rs | 2 +- sdk-tests/manual-test/src/all/derived.rs | 2 +- .../manual-test/src/two_mints/derived.rs | 2 +- .../pinocchio-light-program-test/Cargo.toml | 55 ++ .../src/account_loader/accounts.rs | 87 +++ .../src/account_loader/derived_accounts.rs | 109 ++++ .../src/account_loader/mod.rs | 4 + .../src/all/accounts.rs | 193 +++++++ .../src/all/derived.rs | 268 +++++++++ .../src/all/mod.rs | 4 + .../src/ata/accounts.rs | 46 ++ .../src/ata/derived.rs | 48 ++ .../src/ata/mod.rs | 4 + .../src/derived_state.rs | 171 ++++++ .../pinocchio-light-program-test/src/lib.rs | 335 ++++++++++++ .../src/mint/accounts.rs | 73 +++ .../src/mint/derived.rs | 123 +++++ .../src/mint/mod.rs | 4 + .../src/pda/accounts.rs | 88 +++ .../src/pda/derived_accounts.rs | 112 ++++ .../src/pda/mod.rs | 4 + .../pinocchio-light-program-test/src/state.rs | 49 ++ .../src/token_account/accounts.rs | 58 ++ .../src/token_account/derived.rs | 48 ++ .../src/token_account/mod.rs | 4 + .../src/two_mints/accounts.rs | 98 ++++ .../src/two_mints/derived.rs | 150 ++++++ .../src/two_mints/mod.rs | 4 + .../tests/shared/mod.rs | 219 ++++++++ .../tests/stress_test.rs | 480 +++++++++++++++++ .../tests/test_create_all.rs | 400 ++++++++++++++ .../tests/test_create_ata.rs | 124 +++++ .../tests/test_create_mint.rs | 148 +++++ .../tests/test_create_pda.rs | 128 +++++ .../tests/test_create_token_vault.rs | 164 ++++++ .../tests/test_create_two_mints.rs | 205 +++++++ .../tests/test_create_zero_copy_record.rs | 121 +++++ 60 files changed, 5893 insertions(+), 87 deletions(-) delete mode 100644 sdk-libs/token-sdk/src/anchor.rs create mode 100644 sdk-tests/pinocchio-light-program-test/Cargo.toml create mode 100644 sdk-tests/pinocchio-light-program-test/src/account_loader/accounts.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/account_loader/derived_accounts.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/account_loader/mod.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/all/accounts.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/all/derived.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/all/mod.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/ata/accounts.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/ata/derived.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/ata/mod.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/derived_state.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/lib.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/mint/accounts.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/mint/derived.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/mint/mod.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/pda/accounts.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/pda/derived_accounts.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/pda/mod.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/state.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/token_account/accounts.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/token_account/derived.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/token_account/mod.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/two_mints/accounts.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/two_mints/derived.rs create mode 100644 sdk-tests/pinocchio-light-program-test/src/two_mints/mod.rs create mode 100644 sdk-tests/pinocchio-light-program-test/tests/shared/mod.rs create mode 100644 sdk-tests/pinocchio-light-program-test/tests/stress_test.rs create mode 100644 sdk-tests/pinocchio-light-program-test/tests/test_create_all.rs create mode 100644 sdk-tests/pinocchio-light-program-test/tests/test_create_ata.rs create mode 100644 sdk-tests/pinocchio-light-program-test/tests/test_create_mint.rs create mode 100644 sdk-tests/pinocchio-light-program-test/tests/test_create_pda.rs create mode 100644 sdk-tests/pinocchio-light-program-test/tests/test_create_token_vault.rs create mode 100644 sdk-tests/pinocchio-light-program-test/tests/test_create_two_mints.rs create mode 100644 sdk-tests/pinocchio-light-program-test/tests/test_create_zero_copy_record.rs diff --git a/Cargo.lock b/Cargo.lock index ac2f5d616c..b83338754c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5189,6 +5189,42 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b971851087bc3699b001954ad02389d50c41405ece3548cbcafc88b3e20017a" +[[package]] +name = "pinocchio-light-program-test" +version = "0.1.0" +dependencies = [ + "borsh 0.10.4", + "bytemuck", + "light-account", + "light-account-pinocchio", + "light-batched-merkle-tree", + "light-client", + "light-compressed-account", + "light-compressible", + "light-hasher", + "light-macros", + "light-program-test", + "light-sdk-macros", + "light-sdk-types", + "light-test-utils", + "light-token", + "light-token-client", + "light-token-interface", + "light-token-types", + "pinocchio", + "pinocchio-pubkey", + "pinocchio-system", + "solana-account", + "solana-instruction", + "solana-keypair", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "solana-sdk", + "solana-signer", + "tokio", +] + [[package]] name = "pinocchio-log" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 20b121391e..be7ae1ed33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,7 @@ members = [ "sdk-tests/single-token-test", "sdk-tests/manual-test", "sdk-tests/manual-test-pinocchio", + "sdk-tests/pinocchio-light-program-test", "forester-utils", "forester", "sparse-merkle-tree", diff --git a/program-libs/account-checks/src/account_info/pinocchio.rs b/program-libs/account-checks/src/account_info/pinocchio.rs index 968b52e2a8..a7794241dc 100644 --- a/program-libs/account-checks/src/account_info/pinocchio.rs +++ b/program-libs/account-checks/src/account_info/pinocchio.rs @@ -373,7 +373,19 @@ impl AccountInfoTrait for pinocchio::account_info::AccountInfo { data: instruction_data, }; - let info_refs: Vec<&pinocchio::account_info::AccountInfo> = account_infos.iter().collect(); + // Build info_refs by looking up each account_meta's pubkey in account_infos. + // This matches how solana-program's invoke works (lookup by pubkey, not position). + // Pinocchio's invoke_signed_with_bounds zips account_infos with account_metas + // and requires pubkeys to match at each position, so we must reorder. + let mut info_refs: Vec<&pinocchio::account_info::AccountInfo> = + Vec::with_capacity(account_metas.len()); + for meta in account_metas { + let account_info = account_infos + .iter() + .find(|info| info.key() == &meta.pubkey) + .ok_or(AccountError::NotEnoughAccountKeys)?; + info_refs.push(account_info); + } // Build signers from seeds let signer_seed_vecs: Vec> = signer_seeds diff --git a/sdk-libs/macros/src/lib.rs b/sdk-libs/macros/src/lib.rs index 9daa62d9ca..e62700f504 100644 --- a/sdk-libs/macros/src/lib.rs +++ b/sdk-libs/macros/src/lib.rs @@ -246,6 +246,21 @@ pub fn light_program_derive(input: TokenStream) -> TokenStream { into_token_stream(light_pdas::program::derive_light_program_impl(input)) } +/// Pinocchio variant of `#[derive(LightProgram)]`. +/// +/// Generates pinocchio-compatible code instead of Anchor: +/// - `BorshSerialize/BorshDeserialize` instead of `AnchorSerialize/AnchorDeserialize` +/// - `light_account_pinocchio::` paths instead of `light_account::` +/// - Config/compress/decompress as enum associated functions +/// - `[u8; 32]` instead of `Pubkey` in generated params +/// +/// See `#[derive(LightProgram)]` for usage syntax (identical attribute syntax). +#[proc_macro_derive(LightProgramPinocchio, attributes(light_account))] +pub fn light_program_pinocchio_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + into_token_stream(light_pdas::program::derive_light_program_pinocchio_impl(input)) +} + #[proc_macro_attribute] pub fn account(_: TokenStream, input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as ItemStruct); diff --git a/sdk-libs/macros/src/light_pdas/accounts/builder.rs b/sdk-libs/macros/src/light_pdas/accounts/builder.rs index 5414cb8dbc..0e68f22e01 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/builder.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/builder.rs @@ -306,7 +306,7 @@ impl LightAccountsBuilder { ); let compression_config_data = light_account::LightConfig::load_checked( &self.#compression_config, - &crate::ID.to_bytes(), + &crate::LIGHT_CPI_SIGNER.program_id, )?; let mut all_new_address_params = Vec::with_capacity(#rentfree_count as usize); @@ -351,7 +351,7 @@ impl LightAccountsBuilder { ); let compression_config_data = light_account::LightConfig::load_checked( &self.#compression_config, - &crate::ID.to_bytes(), + &crate::LIGHT_CPI_SIGNER.program_id, )?; let mut all_new_address_params = Vec::with_capacity(#rentfree_count as usize); diff --git a/sdk-libs/macros/src/light_pdas/accounts/mint.rs b/sdk-libs/macros/src/light_pdas/accounts/mint.rs index 5e06e4ac5c..ee5b09ca97 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/mint.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/mint.rs @@ -231,7 +231,7 @@ fn generate_mints_invocation(builder: &LightMintsBuilder) -> TokenStream { // Auto-derive bump from mint_seeds quote! { let #mint_signer_bump_ident: u8 = { - let (_, bump) = solana_pubkey::Pubkey::find_program_address(#mint_seeds_ident, &crate::ID); + let (_, bump) = solana_pubkey::Pubkey::find_program_address(#mint_seeds_ident, &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id)); bump }; } @@ -248,7 +248,7 @@ fn generate_mints_invocation(builder: &LightMintsBuilder) -> TokenStream { quote! { let #authority_bump_ident: u8 = { let base_seeds: &[&[u8]] = #seeds; - let (_, bump) = solana_pubkey::Pubkey::find_program_address(base_seeds, &crate::ID); + let (_, bump) = solana_pubkey::Pubkey::find_program_address(base_seeds, &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id)); bump }; } diff --git a/sdk-libs/macros/src/light_pdas/accounts/pda.rs b/sdk-libs/macros/src/light_pdas/accounts/pda.rs index 4be9cbec16..6523bafd5a 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/pda.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/pda.rs @@ -178,7 +178,7 @@ impl<'a> PdaBlockBuilder<'a> { tree_info, #output_tree, #idx, - &crate::ID.to_bytes(), + &crate::LIGHT_CPI_SIGNER.program_id, &mut all_new_address_params, &mut all_compressed_infos, )?; diff --git a/sdk-libs/macros/src/light_pdas/accounts/token.rs b/sdk-libs/macros/src/light_pdas/accounts/token.rs index dc7614f745..15918f317d 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/token.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/token.rs @@ -95,7 +95,7 @@ pub(super) fn generate_token_account_cpi( if token_seeds.is_empty() { quote! { let __bump: u8 = { - let (_, bump) = solana_pubkey::Pubkey::find_program_address(&[], &crate::ID); + let (_, bump) = solana_pubkey::Pubkey::find_program_address(&[], &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id)); bump }; } @@ -103,7 +103,7 @@ pub(super) fn generate_token_account_cpi( quote! { let __bump: u8 = { let seeds: &[&[u8]] = &[#(#seed_refs),*]; - let (_, bump) = solana_pubkey::Pubkey::find_program_address(seeds, &crate::ID); + let (_, bump) = solana_pubkey::Pubkey::find_program_address(seeds, &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id)); bump }; } @@ -161,7 +161,7 @@ pub(super) fn generate_token_account_cpi( &__config_info, &__sponsor_info, &__system_program, - &crate::ID.to_bytes(), + &crate::LIGHT_CPI_SIGNER.program_id, ) .invoke_signed(__token_account_seeds)?; } diff --git a/sdk-libs/macros/src/light_pdas/accounts/variant.rs b/sdk-libs/macros/src/light_pdas/accounts/variant.rs index 18afab2477..3b231a9811 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/variant.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/variant.rs @@ -105,6 +105,397 @@ impl VariantBuilder { } } + /// Generate all variant code for this PDA field (pinocchio version). + /// + /// Same as `build()` but uses: + /// - `BorshSerialize/BorshDeserialize` instead of `AnchorSerialize/AnchorDeserialize` + /// - `light_account_pinocchio::` instead of `light_account::` + /// - `[u8; 32]` instead of `Pubkey` for seed fields + /// - `pinocchio::account_info::AccountInfo` for AccountInfo references + pub fn build_for_pinocchio(&self) -> TokenStream { + let seeds_struct = self.generate_seeds_struct_pinocchio(); + let packed_seeds_struct = self.generate_packed_seeds_struct_pinocchio(); + let variant_struct = self.generate_variant_struct_pinocchio(); + let packed_variant_struct = self.generate_packed_variant_struct_pinocchio(); + let light_account_variant_impl = self.generate_light_account_variant_impl_pinocchio(); + let packed_light_account_variant_impl = + self.generate_packed_light_account_variant_impl_pinocchio(); + let pack_impl = self.generate_pack_impl_pinocchio(); + + quote! { + #seeds_struct + #packed_seeds_struct + #variant_struct + #packed_variant_struct + #light_account_variant_impl + #packed_light_account_variant_impl + #pack_impl + } + } + + // ========================================================================= + // PINOCCHIO GENERATION METHODS + // ========================================================================= + + fn generate_seeds_struct_pinocchio(&self) -> TokenStream { + let struct_name = format_ident!("{}Seeds", self.variant_name); + let fields: Vec<_> = self + .seed_fields + .iter() + .map(|sf| { + let name = &sf.field_name; + let ty = if sf.is_account_seed { + quote! { [u8; 32] } + } else if sf.has_le_bytes { + quote! { u64 } + } else { + quote! { [u8; 32] } + }; + quote! { pub #name: #ty } + }) + .collect(); + + if fields.is_empty() { + quote! { + #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] + pub struct #struct_name; + } + } else { + quote! { + #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] + pub struct #struct_name { + #(#fields,)* + } + } + } + } + + fn generate_packed_seeds_struct_pinocchio(&self) -> TokenStream { + let struct_name = format_ident!("Packed{}Seeds", self.variant_name); + let fields: Vec<_> = self + .seed_fields + .iter() + .map(|sf| { + let name = if sf.is_account_seed { + format_ident!("{}_idx", sf.field_name) + } else { + sf.field_name.clone() + }; + let ty = &sf.packed_field_type; + quote! { pub #name: #ty } + }) + .collect(); + + quote! { + #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] + pub struct #struct_name { + #(#fields,)* + pub bump: u8, + } + } + } + + fn generate_variant_struct_pinocchio(&self) -> TokenStream { + let struct_name = format_ident!("{}Variant", self.variant_name); + let seeds_struct_name = format_ident!("{}Seeds", self.variant_name); + let inner_type = &self.inner_type; + + quote! { + #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] + pub struct #struct_name { + pub seeds: #seeds_struct_name, + pub data: #inner_type, + } + } + } + + fn generate_packed_variant_struct_pinocchio(&self) -> TokenStream { + let struct_name = format_ident!("Packed{}Variant", self.variant_name); + let packed_seeds_struct_name = format_ident!("Packed{}Seeds", self.variant_name); + let inner_type = &self.inner_type; + let data_type = if let Some(packed_type) = make_packed_type(inner_type) { + quote! { #packed_type } + } else { + let type_str = quote!(#inner_type).to_string().replace(' ', ""); + let packed_name = format_ident!("Packed{}", type_str); + quote! { #packed_name } + }; + + quote! { + #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] + pub struct #struct_name { + pub seeds: #packed_seeds_struct_name, + pub data: #data_type, + } + } + } + + fn generate_light_account_variant_impl_pinocchio(&self) -> TokenStream { + let variant_name = format_ident!("{}Variant", self.variant_name); + let seeds_struct_name = format_ident!("{}Seeds", self.variant_name); + let packed_variant_name = format_ident!("Packed{}Variant", self.variant_name); + let inner_type = &self.inner_type; + let seed_count = self.seed_count; + + let seed_vec_items = self.generate_seed_vec_items_pinocchio(); + let seed_refs_items = self.generate_seed_refs_items(); + + quote! { + impl light_account_pinocchio::LightAccountVariantTrait<#seed_count> for #variant_name { + const PROGRAM_ID: [u8; 32] = crate::LIGHT_CPI_SIGNER.program_id; + + type Seeds = #seeds_struct_name; + type Data = #inner_type; + type Packed = #packed_variant_name; + + fn data(&self) -> &Self::Data { + &self.data + } + + fn seed_vec(&self) -> Vec> { + vec![#(#seed_vec_items),*] + } + + fn seed_refs_with_bump<'a>(&'a self, bump_storage: &'a [u8; 1]) -> [&'a [u8]; #seed_count] { + [#(#seed_refs_items,)* bump_storage] + } + } + } + } + + fn generate_packed_light_account_variant_impl_pinocchio(&self) -> TokenStream { + let variant_name = format_ident!("{}Variant", self.variant_name); + let seeds_struct_name = format_ident!("{}Seeds", self.variant_name); + let packed_variant_name = format_ident!("Packed{}Variant", self.variant_name); + let inner_type = &self.inner_type; + let seed_count = self.seed_count; + + let unpack_seed_stmts = self.generate_unpack_seed_statements_pinocchio(); + let unpack_seed_fields = self.generate_unpack_seed_fields_pinocchio(); + let packed_seed_refs_items = self.generate_packed_seed_refs_items_pinocchio(); + + let unpack_data = quote! { + { + let packed_accounts = light_account_pinocchio::light_account_checks::packed_accounts::ProgramPackedAccounts { accounts }; + <#inner_type as light_account_pinocchio::LightAccount>::unpack(&self.data, &packed_accounts) + .map_err(|_| light_account_pinocchio::LightSdkTypesError::InvalidInstructionData)? + } + }; + + quote! { + impl light_account_pinocchio::PackedLightAccountVariantTrait<#seed_count> for #packed_variant_name { + type Unpacked = #variant_name; + + const ACCOUNT_TYPE: light_account_pinocchio::AccountType = + <#inner_type as light_account_pinocchio::LightAccount>::ACCOUNT_TYPE; + + fn bump(&self) -> u8 { + self.seeds.bump + } + + fn unpack(&self, accounts: &[AI]) -> std::result::Result { + #(#unpack_seed_stmts)* + + Ok(#variant_name { + seeds: #seeds_struct_name { + #(#unpack_seed_fields,)* + }, + data: #unpack_data, + }) + } + + fn seed_refs_with_bump<'a, AI: light_account_pinocchio::light_account_checks::AccountInfoTrait>( + &'a self, + accounts: &'a [AI], + bump_storage: &'a [u8; 1], + ) -> std::result::Result<[&'a [u8]; #seed_count], light_account_pinocchio::LightSdkTypesError> { + Ok([#(#packed_seed_refs_items,)* bump_storage]) + } + } + } + } + + fn generate_pack_impl_pinocchio(&self) -> TokenStream { + let variant_name = format_ident!("{}Variant", self.variant_name); + let packed_variant_name = format_ident!("Packed{}Variant", self.variant_name); + let packed_seeds_struct_name = format_ident!("Packed{}Seeds", self.variant_name); + let inner_type = &self.inner_type; + + let pack_seed_fields = self.generate_pack_seed_fields_pinocchio(); + + let pack_data = quote! { + <#inner_type as light_account_pinocchio::LightAccount>::pack(&self.data, accounts) + .map_err(|_| light_account_pinocchio::LightSdkTypesError::InvalidInstructionData)? + }; + + quote! { + #[cfg(not(target_os = "solana"))] + impl light_account_pinocchio::Pack for #variant_name { + type Packed = #packed_variant_name; + + fn pack( + &self, + accounts: &mut light_account_pinocchio::PackedAccounts, + ) -> std::result::Result { + use light_account_pinocchio::LightAccountVariantTrait; + let (_, bump) = self.derive_pda::(); + Ok(#packed_variant_name { + seeds: #packed_seeds_struct_name { + #(#pack_seed_fields,)* + bump, + }, + data: #pack_data, + }) + } + } + } + } + + /// Generate seed_vec items for pinocchio (uses `.to_vec()` on `[u8; 32]` instead of `.to_bytes().to_vec()`). + fn generate_seed_vec_items_pinocchio(&self) -> Vec { + self.seeds + .iter() + .map(|seed| match seed { + ClassifiedSeed::Literal(_) + | ClassifiedSeed::Constant { .. } + | ClassifiedSeed::Passthrough(_) => { + let expr = seed_to_expr(seed, self.module_path.as_deref()); + quote! { (#expr).to_vec() } + } + ClassifiedSeed::CtxRooted { account, .. } => { + quote! { self.seeds.#account.to_vec() } + } + ClassifiedSeed::DataRooted { root, expr, .. } => { + let field = extract_data_field_name(root, expr); + if is_le_bytes_expr(expr) { + quote! { self.seeds.#field.to_le_bytes().to_vec() } + } else { + quote! { self.seeds.#field.to_vec() } + } + } + ClassifiedSeed::FunctionCall { + func_expr, + args, + has_as_ref, + } => { + let rewritten = rewrite_fn_call_for_self(func_expr, args); + if *has_as_ref { + quote! { #rewritten.as_ref().to_vec() } + } else { + quote! { (#rewritten).to_vec() } + } + } + }) + .collect() + } + + fn generate_unpack_seed_statements_pinocchio(&self) -> Vec { + self.seed_fields + .iter() + .filter(|sf| sf.is_account_seed) + .map(|sf| { + let field = &sf.field_name; + let idx_field = format_ident!("{}_idx", field); + quote! { + let #field: [u8; 32] = + accounts + .get(self.seeds.#idx_field as usize) + .ok_or(light_account_pinocchio::LightSdkTypesError::NotEnoughAccountKeys)? + .key(); + } + }) + .collect() + } + + fn generate_unpack_seed_fields_pinocchio(&self) -> Vec { + self.seed_fields + .iter() + .map(|sf| { + let field = &sf.field_name; + if sf.is_account_seed { + quote! { #field } + } else if sf.has_le_bytes { + quote! { #field: u64::from_le_bytes(self.seeds.#field) } + } else { + quote! { #field: self.seeds.#field } + } + }) + .collect() + } + + fn generate_packed_seed_refs_items_pinocchio(&self) -> Vec { + self.seeds + .iter() + .map(|seed| match seed { + ClassifiedSeed::Literal(_) | ClassifiedSeed::Constant { .. } => { + let expr = seed_to_expr(seed, self.module_path.as_deref()); + quote! { #expr } + } + ClassifiedSeed::Passthrough(pass_expr) => { + if expr_contains_call(pass_expr) { + quote! { + { + panic!("seed_refs_with_bump not supported for function call seeds on packed variant."); + #[allow(unreachable_code)] + { bump_storage as &[u8] } + } + } + } else { + let expr = seed_to_expr(seed, self.module_path.as_deref()); + quote! { #expr } + } + } + ClassifiedSeed::CtxRooted { account, .. } => { + let idx_field = format_ident!("{}_idx", account); + quote! { + accounts + .get(self.seeds.#idx_field as usize) + .ok_or(light_account_pinocchio::LightSdkTypesError::InvalidInstructionData)? + .key_ref() + } + } + ClassifiedSeed::DataRooted { root, expr, .. } => { + let field = extract_data_field_name(root, expr); + if is_le_bytes_expr(expr) { + quote! { &self.seeds.#field } + } else { + quote! { self.seeds.#field.as_ref() } + } + } + ClassifiedSeed::FunctionCall { .. } => { + quote! { + { + panic!("seed_refs_with_bump not supported for function call seeds on packed variant."); + #[allow(unreachable_code)] + { bump_storage as &[u8] } + } + } + } + }) + .collect() + } + + fn generate_pack_seed_fields_pinocchio(&self) -> Vec { + self.seed_fields + .iter() + .map(|sf| { + let field = &sf.field_name; + if sf.is_account_seed { + let idx_field = format_ident!("{}_idx", field); + quote! { #idx_field: accounts.insert_or_get(solana_pubkey::Pubkey::from(self.seeds.#field)) } + } else if sf.has_le_bytes { + quote! { #field: self.seeds.#field.to_le_bytes() } + } else { + quote! { #field: self.seeds.#field } + } + }) + .collect() + } + + // ========================================================================= + // ORIGINAL (ANCHOR) GENERATION METHODS + // ========================================================================= + /// Generate the `{Field}Seeds` struct. fn generate_seeds_struct(&self) -> TokenStream { let struct_name = format_ident!("{}Seeds", self.variant_name); @@ -241,7 +632,7 @@ impl VariantBuilder { quote! { impl light_account::LightAccountVariantTrait<#seed_count> for #variant_name { - const PROGRAM_ID: [u8; 32] = crate::ID.to_bytes(); + const PROGRAM_ID: [u8; 32] = crate::LIGHT_CPI_SIGNER.program_id; type Seeds = #seeds_struct_name; type Data = #inner_type; diff --git a/sdk-libs/macros/src/light_pdas/program/compress.rs b/sdk-libs/macros/src/light_pdas/program/compress.rs index d05f1dfe69..936f0ce767 100644 --- a/sdk-libs/macros/src/light_pdas/program/compress.rs +++ b/sdk-libs/macros/src/light_pdas/program/compress.rs @@ -158,7 +158,7 @@ impl CompressBuilder { instruction_data, __compress_dispatch, LIGHT_CPI_SIGNER, - &crate::ID.to_bytes(), + &crate::LIGHT_CPI_SIGNER.program_id, ) .map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::from(e))) } @@ -369,6 +369,119 @@ impl CompressBuilder { }) } + // ------------------------------------------------------------------------- + // Pinocchio Code Generation Methods + // ------------------------------------------------------------------------- + + /// Generate compress dispatch as an associated function on the enum (pinocchio version). + /// + /// Same logic as `generate_enum_dispatch_method()` but with pinocchio types: + /// - `pinocchio::account_info::AccountInfo` instead of `anchor_lang::prelude::AccountInfo` + /// - `light_account_pinocchio::` instead of `light_account::` + pub fn generate_enum_dispatch_method_pinocchio( + &self, + enum_name: &syn::Ident, + ) -> Result { + let compress_arms: Vec<_> = self.accounts.iter().map(|info| { + let name = qualify_type_with_crate(&info.account_type); + + if info.is_zero_copy { + quote! { + d if d == #name::LIGHT_DISCRIMINATOR => { + let pod_bytes = &data[8..8 + core::mem::size_of::<#name>()]; + let mut account_data: #name = *bytemuck::from_bytes(pod_bytes); + drop(data); + light_account_pinocchio::prepare_account_for_compression( + account_info, &mut account_data, meta, index, ctx, + ) + } + } + } else { + quote! { + d if d == #name::LIGHT_DISCRIMINATOR => { + let mut reader = &data[8..]; + let mut account_data = #name::deserialize(&mut reader) + .map_err(|_| light_account_pinocchio::LightSdkTypesError::InvalidInstructionData)?; + drop(data); + light_account_pinocchio::prepare_account_for_compression( + account_info, &mut account_data, meta, index, ctx, + ) + } + } + } + }).collect(); + + Ok(quote! { + impl #enum_name { + pub fn compress_dispatch( + account_info: &pinocchio::account_info::AccountInfo, + meta: &light_account_pinocchio::account_meta::CompressedAccountMetaNoLamportsNoAddress, + index: usize, + ctx: &mut light_account_pinocchio::CompressCtx<'_>, + ) -> std::result::Result<(), light_account_pinocchio::LightSdkTypesError> { + use light_account_pinocchio::LightDiscriminator; + use borsh::BorshDeserialize; + let data = account_info.try_borrow_data() + .map_err(|_| light_account_pinocchio::LightSdkTypesError::Borsh)?; + let discriminator: [u8; 8] = data[..8] + .try_into() + .map_err(|_| light_account_pinocchio::LightSdkTypesError::InvalidInstructionData)?; + match discriminator { + #(#compress_arms)* + _ => Ok(()), + } + } + } + }) + } + + /// Generate `process_compress` as an enum associated function (pinocchio version). + pub fn generate_enum_process_compress_pinocchio( + &self, + enum_name: &syn::Ident, + ) -> Result { + Ok(quote! { + impl #enum_name { + pub fn process_compress( + accounts: &[pinocchio::account_info::AccountInfo], + instruction_data: &[u8], + ) -> std::result::Result<(), pinocchio::program_error::ProgramError> { + light_account_pinocchio::process_compress_pda_accounts_idempotent( + accounts, + instruction_data, + Self::compress_dispatch, + crate::LIGHT_CPI_SIGNER, + &crate::LIGHT_CPI_SIGNER.program_id, + ) + .map_err(|e| pinocchio::program_error::ProgramError::Custom(u32::from(e))) + } + } + }) + } + + /// Generate compile-time size validation for compressed accounts (pinocchio version). + /// Uses INIT_SPACE directly instead of CompressedInitSpace trait. + pub fn generate_size_validation_pinocchio(&self) -> Result { + let size_checks: Vec<_> = self.accounts.iter().map(|info| { + let qualified_type = qualify_type_with_crate(&info.account_type); + + // For pinocchio, all types use INIT_SPACE constant (no CompressedInitSpace trait) + quote! { + const _: () = { + const COMPRESSED_SIZE: usize = 8 + #qualified_type::INIT_SPACE; + assert!( + COMPRESSED_SIZE <= 800, + concat!( + "Compressed account '", stringify!(#qualified_type), "' exceeds 800-byte compressible account size limit" + ) + ); + }; + } + }).collect(); + + Ok(quote! { #(#size_checks)* }) + } + /// Generate compile-time size validation for compressed accounts. pub fn generate_size_validation(&self) -> Result { let size_checks: Vec<_> = self.accounts.iter().map(|info| { diff --git a/sdk-libs/macros/src/light_pdas/program/decompress.rs b/sdk-libs/macros/src/light_pdas/program/decompress.rs index 405dc65e6e..e33c8e573f 100644 --- a/sdk-libs/macros/src/light_pdas/program/decompress.rs +++ b/sdk-libs/macros/src/light_pdas/program/decompress.rs @@ -78,7 +78,7 @@ impl DecompressBuilder { remaining_accounts, instruction_data, LIGHT_CPI_SIGNER, - &crate::ID.to_bytes(), + &crate::LIGHT_CPI_SIGNER.program_id, current_slot, ) .map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::from(e))) @@ -97,7 +97,7 @@ impl DecompressBuilder { remaining_accounts, instruction_data, LIGHT_CPI_SIGNER, - &crate::ID.to_bytes(), + &crate::LIGHT_CPI_SIGNER.program_id, current_slot, ) .map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::from(e))) @@ -254,7 +254,7 @@ impl DecompressBuilder { /// Generate PDA seed provider implementations. /// Returns empty Vec for mint-only or token-only programs that have no PDA seeds. - pub fn generate_seed_provider_impls(&self) -> Result> { + pub fn generate_seed_provider_impls(&self, is_pinocchio: bool) -> Result> { // For mint-only or token-only programs, there are no PDA seeds - return empty Vec let pda_seed_specs = match self.pda_seeds.as_ref() { Some(specs) if !specs.is_empty() => specs, @@ -327,20 +327,26 @@ impl DecompressBuilder { ctx_fields, &ctx_info.state_field_names, params_only_fields, + is_pinocchio, )?; let has_params_only = !params_only_fields.is_empty(); + let account_crate = if is_pinocchio { + quote! { light_account_pinocchio } + } else { + quote! { light_account } + }; let seed_params_impl = if has_params_only { quote! { #ctx_seeds_struct - impl light_account::PdaSeedDerivation<#ctx_seeds_struct_name, SeedParams> for #inner_type { + impl #account_crate::PdaSeedDerivation<#ctx_seeds_struct_name, SeedParams> for #inner_type { fn derive_pda_seeds_with_accounts( &self, program_id: &[u8; 32], ctx_seeds: &#ctx_seeds_struct_name, seed_params: &SeedParams, - ) -> std::result::Result<(Vec>, [u8; 32]), light_account::LightSdkTypesError> { + ) -> std::result::Result<(Vec>, [u8; 32]), #account_crate::LightSdkTypesError> { #seed_derivation } } @@ -349,13 +355,13 @@ impl DecompressBuilder { quote! { #ctx_seeds_struct - impl light_account::PdaSeedDerivation<#ctx_seeds_struct_name, SeedParams> for #inner_type { + impl #account_crate::PdaSeedDerivation<#ctx_seeds_struct_name, SeedParams> for #inner_type { fn derive_pda_seeds_with_accounts( &self, program_id: &[u8; 32], ctx_seeds: &#ctx_seeds_struct_name, _seed_params: &SeedParams, - ) -> std::result::Result<(Vec>, [u8; 32]), light_account::LightSdkTypesError> { + ) -> std::result::Result<(Vec>, [u8; 32]), #account_crate::LightSdkTypesError> { #seed_derivation } } @@ -367,6 +373,96 @@ impl DecompressBuilder { Ok(results) } + // ------------------------------------------------------------------------- + // Pinocchio Code Generation Methods + // ------------------------------------------------------------------------- + + /// Generate decompress dispatch as an associated function on the enum (pinocchio version). + pub fn generate_enum_decompress_dispatch_pinocchio( + &self, + enum_name: &syn::Ident, + ) -> Result { + let processor_call = if self.has_tokens { + quote! { + light_account_pinocchio::process_decompress_accounts_idempotent::<_, PackedLightAccountVariant>( + remaining_accounts, + instruction_data, + cpi_signer, + program_id, + current_slot, + ) + } + } else { + quote! { + light_account_pinocchio::process_decompress_pda_accounts_idempotent::<_, PackedLightAccountVariant>( + remaining_accounts, + instruction_data, + cpi_signer, + program_id, + current_slot, + ) + } + }; + + Ok(quote! { + impl #enum_name { + pub fn decompress_dispatch( + remaining_accounts: &[pinocchio::account_info::AccountInfo], + instruction_data: &[u8], + cpi_signer: light_account_pinocchio::CpiSigner, + program_id: &[u8; 32], + current_slot: u64, + ) -> std::result::Result<(), light_account_pinocchio::LightSdkTypesError> { + #processor_call + } + } + }) + } + + /// Generate `process_decompress` as an enum associated function (pinocchio version). + pub fn generate_enum_process_decompress_pinocchio( + &self, + enum_name: &syn::Ident, + ) -> Result { + let processor_call = if self.has_tokens { + quote! { + light_account_pinocchio::process_decompress_accounts_idempotent::<_, PackedLightAccountVariant>( + accounts, + instruction_data, + crate::LIGHT_CPI_SIGNER, + &crate::LIGHT_CPI_SIGNER.program_id, + current_slot, + ) + } + } else { + quote! { + light_account_pinocchio::process_decompress_pda_accounts_idempotent::<_, PackedLightAccountVariant>( + accounts, + instruction_data, + crate::LIGHT_CPI_SIGNER, + &crate::LIGHT_CPI_SIGNER.program_id, + current_slot, + ) + } + }; + + Ok(quote! { + impl #enum_name { + pub fn process_decompress( + accounts: &[pinocchio::account_info::AccountInfo], + instruction_data: &[u8], + ) -> std::result::Result<(), pinocchio::program_error::ProgramError> { + use pinocchio::sysvars::Sysvar; + let current_slot = pinocchio::sysvars::clock::Clock::get() + .map_err(|_| pinocchio::program_error::ProgramError::UnsupportedSysvar)? + .slot; + #processor_call + .map_err(|e| pinocchio::program_error::ProgramError::Custom(u32::from(e))) + } + } + }) + } + /// Generate decompress dispatch as an associated function on the enum. /// /// When `#[derive(LightProgram)]` is used, the dispatch function is generated @@ -428,7 +524,13 @@ fn generate_pda_seed_derivation_for_trait_with_ctx_seeds( ctx_seed_fields: &[syn::Ident], state_field_names: &std::collections::HashSet, params_only_fields: &[(syn::Ident, syn::Type, bool)], + is_pinocchio: bool, ) -> Result { + let account_crate = if is_pinocchio { + quote! { light_account_pinocchio } + } else { + quote! { light_account } + }; // Build a lookup for params-only field names let params_only_names: std::collections::HashSet = params_only_fields .iter() @@ -509,7 +611,7 @@ fn generate_pda_seed_derivation_for_trait_with_ctx_seeds( ); bindings.push(quote! { let #binding_name = seed_params.#field_ident - .ok_or(light_account::LightSdkTypesError::InvalidInstructionData)?; + .ok_or(#account_crate::LightSdkTypesError::InvalidInstructionData)?; let #bytes_binding_name = #binding_name.to_le_bytes(); }); seed_refs.push(quote! { #bytes_binding_name.as_ref() }); @@ -517,7 +619,7 @@ fn generate_pda_seed_derivation_for_trait_with_ctx_seeds( // Pubkey field bindings.push(quote! { let #binding_name = seed_params.#field_ident - .ok_or(light_account::LightSdkTypesError::InvalidInstructionData)?; + .ok_or(#account_crate::LightSdkTypesError::InvalidInstructionData)?; }); seed_refs.push(quote! { #binding_name.as_ref() }); } diff --git a/sdk-libs/macros/src/light_pdas/program/derive_light_program.rs b/sdk-libs/macros/src/light_pdas/program/derive_light_program.rs index 317cca78f9..5633b92582 100644 --- a/sdk-libs/macros/src/light_pdas/program/derive_light_program.rs +++ b/sdk-libs/macros/src/light_pdas/program/derive_light_program.rs @@ -763,6 +763,94 @@ pub fn derive_light_program_impl(input: DeriveInput) -> Result { Ok(output) } +/// Main entry point for `#[derive(LightProgramPinocchio)]`. +/// +/// Same logic as `derive_light_program_impl()` but generates pinocchio-compatible code: +/// - `BorshSerialize/BorshDeserialize` instead of `AnchorSerialize/AnchorDeserialize` +/// - `light_account_pinocchio::` instead of `light_account::` +/// - No `use anchor_lang::prelude::*;` import +/// - Config/compress/decompress as enum associated functions +pub fn derive_light_program_pinocchio_impl(input: DeriveInput) -> Result { + // 1. Parse the enum variants (reused) + let variants = parse_enum_variants(&input)?; + + if variants.is_empty() { + return Err(syn::Error::new_spanned( + &input, + "#[derive(LightProgramPinocchio)] enum must have at least one variant", + )); + } + + // 2. Parse crate context for struct field lookup + let crate_ctx = CrateContext::parse_from_manifest()?; + + // 3. Build intermediate types (reused) + let ( + compressible_accounts, + pda_seeds, + token_seeds, + instruction_data, + has_mint_fields, + has_ata_fields, + _pda_variant_code, // We'll regenerate with pinocchio derives + ) = build_intermediate_types(&variants, &crate_ctx)?; + + // 3b. Re-generate PDA variant code with pinocchio derives + let pda_variant_code_pinocchio: TokenStream = variants + .iter() + .filter(|v| matches!(v.kind, ManualVariantKind::Pda)) + .map(|variant| { + let spec = manual_variant_to_extracted_spec(variant, &crate_ctx); + VariantBuilder::from_extracted_spec(&spec).build_for_pinocchio() + }) + .collect(); + + // 4. Generate all items using the pinocchio orchestration function + let enum_name = &input.ident; + let items = super::instructions::generate_light_program_pinocchio_items( + compressible_accounts, + pda_seeds, + token_seeds, + instruction_data, + &crate_ctx, + has_mint_fields, + has_ata_fields, + pda_variant_code_pinocchio, + Some(enum_name), + )?; + + // 5. Combine into single TokenStream (NO anchor import) + let mut output = TokenStream::new(); + for item in items { + output.extend(item); + } + + Ok(output) +} + +/// Convert a ParsedManualVariant to ExtractedSeedSpec for VariantBuilder. +fn manual_variant_to_extracted_spec( + variant: &ParsedManualVariant, + _crate_ctx: &CrateContext, +) -> ExtractedSeedSpec { + let seeds: Vec = variant + .seeds + .iter() + .map(|s| manual_seed_to_classified(s)) + .collect(); + + ExtractedSeedSpec { + struct_name: variant.ident.to_string(), + variant_name: variant.ident.clone(), + inner_type: variant.inner_type.clone().unwrap_or_else(|| { + syn::parse_quote!(()) + }), + seeds, + is_zero_copy: variant.is_zero_copy, + module_path: String::new(), + } +} + // ============================================================================= // TESTS // ============================================================================= diff --git a/sdk-libs/macros/src/light_pdas/program/instructions.rs b/sdk-libs/macros/src/light_pdas/program/instructions.rs index 60fee2560d..52a485e8d8 100644 --- a/sdk-libs/macros/src/light_pdas/program/instructions.rs +++ b/sdk-libs/macros/src/light_pdas/program/instructions.rs @@ -377,7 +377,7 @@ pub(crate) fn generate_light_program_items( // Note: DecompressBuilder validation is optional for now since pda_seeds may be empty for TokenOnly let decompress_accounts = decompress_builder.generate_accounts_struct()?; - let pda_seed_provider_impls = decompress_builder.generate_seed_provider_impls()?; + let pda_seed_provider_impls = decompress_builder.generate_seed_provider_impls(false)?; // Generate trait impls and decompress processor/instruction based on program type. // v2 interface: no DecompressContext trait needed - uses DecompressVariant on PackedLightAccountVariant. @@ -540,7 +540,7 @@ pub(crate) fn generate_light_program_items( 0, // config_bump &ctx.accounts.payer, &ctx.accounts.system_program, - &crate::ID.to_bytes(), + &crate::LIGHT_CPI_SIGNER.program_id, ).map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::from(e)))?; Ok(()) } @@ -559,7 +559,7 @@ pub(crate) fn generate_light_program_items( light_account::process_update_light_config( &remaining, &instruction_data, - &crate::ID.to_bytes(), + &crate::LIGHT_CPI_SIGNER.program_id, ).map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::from(e)))?; Ok(()) } @@ -952,3 +952,467 @@ pub fn light_program_impl(_args: TokenStream, mut module: ItemMod) -> Result, + pda_seeds: Option>, + token_seeds: Option>, + instruction_data: Vec, + crate_ctx: &crate::light_pdas::parsing::CrateContext, + has_mint_fields: bool, + has_ata_fields: bool, + pda_variant_code: TokenStream, + enum_name: Option<&syn::Ident>, +) -> Result> { + // Validate token seeds have seeds specified + if let Some(ref token_seed_specs) = token_seeds { + for spec in token_seed_specs { + if spec.seeds.is_empty() { + return Err(super::parsing::macro_error!( + &spec.variant, + "Token account '{}' must have seeds in #[account(seeds = [...])] for PDA signing.", + spec.variant + )); + } + } + } + + // Build PDA context seed info (same logic as Anchor version) + let pda_ctx_seeds: Vec = pda_seeds + .as_ref() + .map(|specs| { + specs + .iter() + .map(|spec| { + let ctx_fields = extract_ctx_seed_fields(&spec.seeds); + let inner_type = spec + .inner_type + .clone() + .unwrap_or_else(|| ident_to_type(&spec.variant)); + + let state_field_names: std::collections::HashSet = crate_ctx + .get_struct_fields(&inner_type) + .map(|fields| fields.into_iter().collect()) + .unwrap_or_default(); + + let params_only_seed_fields = + crate::light_pdas::seeds::get_params_only_seed_fields_from_spec( + spec, + &state_field_names, + ); + + let seed_count = spec.seeds.len() + 1; + + PdaCtxSeedInfo::with_state_fields( + spec.variant.clone(), + inner_type, + ctx_fields, + state_field_names, + params_only_seed_fields, + seed_count, + ) + }) + .collect() + }) + .unwrap_or_default(); + + let has_token_seeds_early = token_seeds.as_ref().map(|t| !t.is_empty()).unwrap_or(false); + + // Generate variant enum and traits using pinocchio builder + let enum_and_traits = if pda_ctx_seeds.is_empty() { + // Minimal placeholder for programs without PDA state accounts + quote! { + #[derive(Clone, Debug, borsh::BorshSerialize, borsh::BorshDeserialize)] + pub enum LightAccountVariant { + Empty, + } + + impl Default for LightAccountVariant { + fn default() -> Self { + Self::Empty + } + } + + impl light_account_pinocchio::hasher::DataHasher for LightAccountVariant { + fn hash(&self) -> std::result::Result<[u8; 32], light_account_pinocchio::hasher::HasherError> { + match self { + Self::Empty => Err(light_account_pinocchio::hasher::HasherError::EmptyInput), + } + } + } + + impl light_account_pinocchio::LightDiscriminator for LightAccountVariant { + const LIGHT_DISCRIMINATOR: [u8; 8] = [0; 8]; + const LIGHT_DISCRIMINATOR_SLICE: &'static [u8] = &Self::LIGHT_DISCRIMINATOR; + } + + impl light_account_pinocchio::HasCompressionInfo for LightAccountVariant { + fn compression_info(&self) -> std::result::Result<&light_account_pinocchio::CompressionInfo, light_account_pinocchio::LightSdkTypesError> { + Err(light_account_pinocchio::LightSdkTypesError::InvalidInstructionData) + } + + fn compression_info_mut(&mut self) -> std::result::Result<&mut light_account_pinocchio::CompressionInfo, light_account_pinocchio::LightSdkTypesError> { + Err(light_account_pinocchio::LightSdkTypesError::InvalidInstructionData) + } + + fn compression_info_mut_opt(&mut self) -> &mut Option { + panic!("compression_info_mut_opt not supported for mint-only programs") + } + + fn set_compression_info_none(&mut self) -> std::result::Result<(), light_account_pinocchio::LightSdkTypesError> { + Err(light_account_pinocchio::LightSdkTypesError::InvalidInstructionData) + } + } + + impl light_account_pinocchio::Size for LightAccountVariant { + fn size(&self) -> std::result::Result { + Err(light_account_pinocchio::LightSdkTypesError::InvalidInstructionData) + } + } + + #[cfg(not(target_os = "solana"))] + impl light_account_pinocchio::Pack for LightAccountVariant { + type Packed = Self; + fn pack(&self, _remaining_accounts: &mut light_account_pinocchio::interface::instruction::PackedAccounts) -> std::result::Result { + Ok(Self::Empty) + } + } + + impl light_account_pinocchio::Unpack for LightAccountVariant { + type Unpacked = Self; + fn unpack(&self, _remaining_accounts: &[AI]) -> std::result::Result { + Ok(Self::Empty) + } + } + + #[derive(Clone, Debug, borsh::BorshSerialize, borsh::BorshDeserialize)] + pub struct LightAccountData { + pub meta: light_account_pinocchio::account_meta::CompressedAccountMetaNoLamportsNoAddress, + pub data: LightAccountVariant, + } + + impl Default for LightAccountData { + fn default() -> Self { + Self { + meta: light_account_pinocchio::account_meta::CompressedAccountMetaNoLamportsNoAddress::default(), + data: LightAccountVariant::default(), + } + } + } + } + } else { + let builder = LightVariantBuilder::new(&pda_ctx_seeds); + let builder = if let Some(ref token_seed_specs) = token_seeds { + if !token_seed_specs.is_empty() { + builder.with_token_seeds(token_seed_specs) + } else { + builder + } + } else { + builder + }; + builder.build_pinocchio()? + }; + + // Collect params-only seed fields for SeedParams struct + let mut all_params_only_fields: std::collections::BTreeMap = + std::collections::BTreeMap::new(); + for ctx_info in &pda_ctx_seeds { + for (field_name, field_type, _) in &ctx_info.params_only_seed_fields { + let field_str = field_name.to_string(); + all_params_only_fields + .entry(field_str) + .or_insert_with(|| field_type.clone()); + } + } + + // SeedParams with Borsh derives instead of Anchor derives + let seed_params_struct = if all_params_only_fields.is_empty() { + quote! { + #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug, Default)] + pub struct SeedParams; + } + } else { + let sorted_fields: Vec<_> = all_params_only_fields.iter().collect(); + let seed_param_fields: Vec<_> = sorted_fields + .iter() + .map(|(name, ty)| { + let field_ident = format_ident!("{}", name); + quote! { pub #field_ident: Option<#ty> } + }) + .collect(); + let seed_param_defaults: Vec<_> = sorted_fields + .iter() + .map(|(name, _)| { + let field_ident = format_ident!("{}", name); + quote! { #field_ident: None } + }) + .collect(); + quote! { + #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] + pub struct SeedParams { + #(#seed_param_fields,)* + } + impl Default for SeedParams { + fn default() -> Self { + Self { + #(#seed_param_defaults,)* + } + } + } + } + }; + + // Seeds constructors with BorshDeserialize and light_account_pinocchio errors + let seeds_structs_and_constructors: Vec = if let Some(ref pda_seed_specs) = + pda_seeds + { + pda_seed_specs + .iter() + .zip(pda_ctx_seeds.iter()) + .map(|(spec, ctx_info)| { + let variant_name = &ctx_info.variant_name; + let inner_type = qualify_type_with_crate(&ctx_info.inner_type); + let seeds_struct_name = format_ident!("{}Seeds", variant_name); + let constructor_name = + format_ident!("{}", to_snake_case(&variant_name.to_string())); + let data_fields = extract_data_seed_fields(&spec.seeds); + + let data_verifications: Vec<_> = data_fields.iter().filter_map(|field| { + let field_str = field.to_string(); + if !ctx_info.state_field_names.contains(&field_str) { + return None; + } + Some(quote! { + if data.#field != seeds.#field { + return std::result::Result::Err( + light_account_pinocchio::LightSdkTypesError::InvalidInstructionData + ); + } + }) + }).collect(); + + // Pinocchio: use BorshDeserialize with light_account_pinocchio errors + let (deserialize_code, variant_data) = ( + quote! { + use borsh::BorshDeserialize; + let data: #inner_type = BorshDeserialize::deserialize(&mut &account_data[..]) + .map_err(|_| light_account_pinocchio::LightSdkTypesError::Borsh)?; + }, + quote! { data }, + ); + + quote! { + impl LightAccountVariant { + pub fn #constructor_name( + account_data: &[u8], + seeds: #seeds_struct_name, + ) -> std::result::Result { + #deserialize_code + + #(#data_verifications)* + + std::result::Result::Ok(Self::#variant_name { + seeds, + data: #variant_data, + }) + } + } + impl light_account_pinocchio::IntoVariant for #seeds_struct_name { + fn into_variant(self, data: &[u8]) -> std::result::Result { + LightAccountVariant::#constructor_name(data, self) + } + } + } + }) + .collect() + } else { + Vec::new() + }; + + let has_pda_seeds = pda_seeds.as_ref().map(|p| !p.is_empty()).unwrap_or(false); + let has_token_seeds = token_seeds.as_ref().map(|t| !t.is_empty()).unwrap_or(false); + + let instruction_variant = match (has_pda_seeds, has_token_seeds, has_mint_fields, has_ata_fields) + { + (true, true, _, _) => InstructionVariant::Mixed, + (true, false, _, _) => InstructionVariant::PdaOnly, + (false, true, _, _) => InstructionVariant::TokenOnly, + (false, false, true, _) => InstructionVariant::MintOnly, + (false, false, false, true) => InstructionVariant::AtaOnly, + (false, false, false, false) => { + return Err(syn::Error::new( + proc_macro2::Span::call_site(), + "No #[light_account(init)], #[light_account(init, mint::...)], #[light_account(init, associated_token::...)], or #[light_account(token::...)] fields found.\n\ + At least one light account field must be provided.", + )) + } + }; + + // Create builders for compress/decompress + let compress_builder = CompressBuilder::new(compressible_accounts.clone(), instruction_variant); + compress_builder.validate()?; + + let size_validation_checks = compress_builder.generate_size_validation_pinocchio()?; + + let decompress_builder = DecompressBuilder::new( + pda_ctx_seeds.clone(), + pda_seeds.clone(), + has_token_seeds_early, + ); + + // PDA seed provider impls (framework-agnostic, reused as-is) + let pda_seed_provider_impls = decompress_builder.generate_seed_provider_impls(true)?; + + // InitConfigParams with [u8; 32] instead of Pubkey + let init_config_params_struct = quote! { + #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone)] + pub struct InitConfigParams { + pub write_top_up: u32, + pub rent_sponsor: [u8; 32], + pub compression_authority: [u8; 32], + pub rent_config: light_compressible::rent::RentConfig, + pub address_space: Vec<[u8; 32]>, + } + }; + + // Client functions (module + pub use - framework-agnostic) + let client_functions = super::seed_codegen::generate_client_seed_functions( + &pda_seeds, + &token_seeds, + &instruction_data, + )?; + + // Collect all generated items + let mut items: Vec = Vec::new(); + + // SeedParams struct + items.push(seed_params_struct); + + // Seeds structs and constructors + for seeds_tokens in seeds_structs_and_constructors.into_iter() { + items.push(seeds_tokens); + } + + // PDA variant structs (already generated with pinocchio derives) + if !pda_variant_code.is_empty() { + items.push(pda_variant_code); + } + + // Size validation + items.push(size_validation_checks); + + // Variant enums and traits + items.push(enum_and_traits); + + // InitConfigParams + items.push(init_config_params_struct); + + // PDA seed provider impls + for pda_impl in pda_seed_provider_impls.into_iter() { + items.push(pda_impl); + } + + // CToken seed provider impls + if let Some(ref seeds) = token_seeds { + if !seeds.is_empty() { + let impl_code = + super::seed_codegen::generate_ctoken_seed_provider_implementation(seeds)?; + items.push(impl_code); + } + } + + // Client functions + items.push(client_functions); + + // Generate enum associated functions for pinocchio + if let Some(enum_name) = enum_name { + // Compress dispatch + process_compress + if compress_builder.has_pdas() { + items.push(compress_builder.generate_enum_dispatch_method_pinocchio(enum_name)?); + items.push(compress_builder.generate_enum_process_compress_pinocchio(enum_name)?); + } + + // Decompress dispatch + process_decompress + if !pda_ctx_seeds.is_empty() { + items.push( + decompress_builder.generate_enum_process_decompress_pinocchio(enum_name)?, + ); + } + + // Config functions as enum methods + items.push(quote! { + impl #enum_name { + pub fn process_initialize_config( + accounts: &[pinocchio::account_info::AccountInfo], + data: &[u8], + ) -> std::result::Result<(), pinocchio::program_error::ProgramError> { + let params = ::try_from_slice(data) + .map_err(|_| pinocchio::program_error::ProgramError::BorshIoError)?; + + if accounts.len() < 5 { + return Err(pinocchio::program_error::ProgramError::NotEnoughAccountKeys); + } + + let fee_payer = &accounts[0]; + let config = &accounts[1]; + let _program_data = &accounts[2]; + let authority = &accounts[3]; + let system_program = &accounts[4]; + + light_account_pinocchio::process_initialize_light_config( + config, + authority, + ¶ms.rent_sponsor, + ¶ms.compression_authority, + params.rent_config, + params.write_top_up, + params.address_space, + 0, // config_bump + fee_payer, + system_program, + &crate::LIGHT_CPI_SIGNER.program_id, + ) + .map_err(|e| pinocchio::program_error::ProgramError::Custom(u32::from(e))) + } + + pub fn process_update_config( + accounts: &[pinocchio::account_info::AccountInfo], + data: &[u8], + ) -> std::result::Result<(), pinocchio::program_error::ProgramError> { + if accounts.len() < 2 { + return Err(pinocchio::program_error::ProgramError::NotEnoughAccountKeys); + } + + let authority = &accounts[0]; + let config = &accounts[1]; + + let remaining = [*config, *authority]; + light_account_pinocchio::process_update_light_config( + &remaining, + data, + &crate::LIGHT_CPI_SIGNER.program_id, + ) + .map_err(|e| pinocchio::program_error::ProgramError::Custom(u32::from(e))) + } + } + }); + } + + Ok(items) +} diff --git a/sdk-libs/macros/src/light_pdas/program/mod.rs b/sdk-libs/macros/src/light_pdas/program/mod.rs index 67cbe616b9..cd6b58306b 100644 --- a/sdk-libs/macros/src/light_pdas/program/mod.rs +++ b/sdk-libs/macros/src/light_pdas/program/mod.rs @@ -18,5 +18,5 @@ pub mod variant_enum; pub(crate) mod parsing; pub(crate) mod visitors; -pub use derive_light_program::derive_light_program_impl; +pub use derive_light_program::{derive_light_program_impl, derive_light_program_pinocchio_impl}; pub use instructions::light_program_impl; diff --git a/sdk-libs/macros/src/light_pdas/program/seed_codegen.rs b/sdk-libs/macros/src/light_pdas/program/seed_codegen.rs index 7960ef3a4d..f40110ba92 100644 --- a/sdk-libs/macros/src/light_pdas/program/seed_codegen.rs +++ b/sdk-libs/macros/src/light_pdas/program/seed_codegen.rs @@ -42,7 +42,7 @@ pub fn generate_client_seed_functions( let (parameters, seed_expressions) = analyze_seed_spec_for_client(spec, instruction_data)?; - let fn_body = generate_seed_derivation_body(&seed_expressions, quote! { &crate::ID }); + let fn_body = generate_seed_derivation_body(&seed_expressions, quote! { &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id) }); let function = quote! { pub fn #function_name(#(#parameters),*) -> (Vec>, solana_pubkey::Pubkey) { #fn_body @@ -62,7 +62,7 @@ pub fn generate_client_seed_functions( let (parameters, seed_expressions) = analyze_seed_spec_for_client(spec, instruction_data)?; - let fn_body = generate_seed_derivation_body(&seed_expressions, quote! { &crate::ID }); + let fn_body = generate_seed_derivation_body(&seed_expressions, quote! { &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id) }); let function = quote! { pub fn #function_name(#(#parameters),*) -> (Vec>, solana_pubkey::Pubkey) { #fn_body @@ -106,7 +106,7 @@ pub fn generate_client_seed_functions( quote! { #(#owner_parameters),* }, generate_seed_derivation_body( &owner_seed_expressions, - quote! { &crate::ID }, + quote! { &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id) }, ), ) }; diff --git a/sdk-libs/macros/src/light_pdas/program/variant_enum.rs b/sdk-libs/macros/src/light_pdas/program/variant_enum.rs index 5094643d40..2a71f9a0c2 100644 --- a/sdk-libs/macros/src/light_pdas/program/variant_enum.rs +++ b/sdk-libs/macros/src/light_pdas/program/variant_enum.rs @@ -97,6 +97,34 @@ impl<'a> LightVariantBuilder<'a> { }) } + /// Generate pinocchio-compatible enum definitions and trait implementations. + /// + /// Same as `build()` but uses: + /// - `BorshSerialize/BorshDeserialize` instead of `AnchorSerialize/AnchorDeserialize` + /// - `light_account_pinocchio::` instead of `light_account::` + /// - `pinocchio::account_info::AccountInfo` instead of anchor's AccountInfo + pub fn build_pinocchio(&self) -> Result { + self.validate()?; + + let token_seeds_structs = self.generate_token_seeds_structs_pinocchio(); + let token_variant_trait_impls = self.generate_token_variant_trait_impls_pinocchio(); + let unpacked_enum = self.generate_unpacked_enum_pinocchio(); + let packed_enum = self.generate_packed_enum_pinocchio(); + let light_account_data_struct = self.generate_light_account_data_struct_pinocchio(); + let decompress_variant_impl = self.generate_decompress_variant_impl_pinocchio(); + let pack_impl = self.generate_pack_impl_pinocchio(); + + Ok(quote! { + #token_seeds_structs + #token_variant_trait_impls + #unpacked_enum + #packed_enum + #light_account_data_struct + #decompress_variant_impl + #pack_impl + }) + } + /// Generate the `LightAccountData` wrapper struct. fn generate_light_account_data_struct(&self) -> TokenStream { quote! { @@ -210,7 +238,7 @@ impl<'a> LightVariantBuilder<'a> { let __seeds: &[&[u8]] = &[#(#bump_seed_refs),*]; let (_, __bump) = solana_pubkey::Pubkey::find_program_address( __seeds, - &crate::ID, + &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id), ); Ok(#packed_seeds_name { #(#pack_stmts,)* @@ -329,7 +357,7 @@ impl<'a> LightVariantBuilder<'a> { quote! { let (__owner, _) = solana_pubkey::Pubkey::find_program_address( &[#(#owner_seed_refs),*], - &crate::ID, + &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id), ); __owner.to_bytes() } @@ -344,7 +372,7 @@ impl<'a> LightVariantBuilder<'a> { { type Packed = #packed_seeds_name; - const PROGRAM_ID: [u8; 32] = crate::ID.to_bytes(); + const PROGRAM_ID: [u8; 32] = crate::LIGHT_CPI_SIGNER.program_id; fn seed_vec(&self) -> Vec> { vec![#(#seed_vec_items),*] @@ -599,6 +627,470 @@ impl<'a> LightVariantBuilder<'a> { } } } + + // ========================================================================= + // PINOCCHIO GENERATION METHODS + // ========================================================================= + + /// Generate token seeds structs (pinocchio version, uses BorshSerialize/BorshDeserialize). + fn generate_token_seeds_structs_pinocchio(&self) -> TokenStream { + let structs: Vec<_> = self + .token_seeds + .iter() + .map(|spec| { + let variant_name = &spec.variant; + let seeds_name = format_ident!("{}Seeds", variant_name); + let packed_seeds_name = format_ident!("Packed{}Seeds", variant_name); + let ctx_fields = extract_ctx_fields_from_token_spec(spec); + + let unpacked_fields: Vec<_> = ctx_fields + .iter() + .map(|f| quote! { pub #f: [u8; 32] }) + .collect(); + + let packed_fields: Vec<_> = ctx_fields + .iter() + .map(|f| { + let idx = format_ident!("{}_idx", f); + quote! { pub #idx: u8 } + }) + .collect(); + + let pack_stmts: Vec<_> = ctx_fields + .iter() + .map(|f| { + let idx = format_ident!("{}_idx", f); + quote! { #idx: remaining_accounts.insert_or_get(solana_pubkey::Pubkey::from(self.#f)) } + }) + .collect(); + + let bump_seed_refs: Vec<_> = spec + .seeds + .iter() + .map(seed_to_unpacked_ref) + .collect(); + + let unpack_resolve_stmts: Vec<_> = ctx_fields + .iter() + .map(|f| { + let idx = format_ident!("{}_idx", f); + quote! { + let #f: [u8; 32] = + remaining_accounts + .get(self.#idx as usize) + .ok_or(light_account_pinocchio::LightSdkTypesError::InvalidInstructionData)? + .key(); + } + }) + .collect(); + + let unpack_field_assigns: Vec<_> = ctx_fields.iter().map(|f| quote! { #f }).collect(); + + let seeds_struct = if unpacked_fields.is_empty() { + quote! { + #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] + pub struct #seeds_name; + } + } else { + quote! { + #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] + pub struct #seeds_name { + #(#unpacked_fields,)* + } + } + }; + + quote! { + #seeds_struct + + #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] + pub struct #packed_seeds_name { + #(#packed_fields,)* + pub bump: u8, + } + + #[cfg(not(target_os = "solana"))] + impl light_account_pinocchio::Pack for #seeds_name { + type Packed = #packed_seeds_name; + + fn pack( + &self, + remaining_accounts: &mut light_account_pinocchio::PackedAccounts, + ) -> std::result::Result { + let __seeds: &[&[u8]] = &[#(#bump_seed_refs),*]; + let (_, __bump) = solana_pubkey::Pubkey::find_program_address( + __seeds, + &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id), + ); + Ok(#packed_seeds_name { + #(#pack_stmts,)* + bump: __bump, + }) + } + } + + impl light_account_pinocchio::Unpack for #packed_seeds_name { + type Unpacked = #seeds_name; + + fn unpack( + &self, + remaining_accounts: &[AI], + ) -> std::result::Result { + #(#unpack_resolve_stmts)* + Ok(#seeds_name { + #(#unpack_field_assigns,)* + }) + } + } + } + }) + .collect(); + + quote! { #(#structs)* } + } + + /// Generate token variant trait impls (pinocchio version). + fn generate_token_variant_trait_impls_pinocchio(&self) -> TokenStream { + let impls: Vec<_> = self + .token_seeds + .iter() + .map(|spec| { + let seeds_name = format_ident!("{}Seeds", spec.variant); + let packed_seeds_name = format_ident!("Packed{}Seeds", spec.variant); + let seed_count = spec.seeds.len() + 1; + + let unpacked_seed_ref_items: Vec<_> = spec + .seeds + .iter() + .map(seed_to_unpacked_ref) + .collect(); + + let seed_vec_items: Vec<_> = spec + .seeds + .iter() + .map(|seed| { + match seed { + SeedElement::Literal(lit) => { + let value = lit.value(); + quote! { #value.as_bytes().to_vec() } + } + SeedElement::Expression(expr) => { + if let Some(field_name) = extract_ctx_field_from_expr(expr) { + quote! { self.#field_name.as_ref().to_vec() } + } else { + if let syn::Expr::Lit(lit_expr) = &**expr { + if let syn::Lit::ByteStr(byte_str) = &lit_expr.lit { + let bytes = byte_str.value(); + return quote! { vec![#(#bytes),*] }; + } + } + if let syn::Expr::Path(path_expr) = &**expr { + if path_expr.qself.is_none() { + if let Some(last_seg) = path_expr.path.segments.last() { + if crate::light_pdas::shared_utils::is_constant_identifier(&last_seg.ident.to_string()) { + let path = &path_expr.path; + return quote! { { let __seed: &[u8] = #path.as_ref(); __seed.to_vec() } }; + } + } + } + } + quote! { { let __seed: &[u8] = (#expr).as_ref(); __seed.to_vec() } } + } + } + } + }) + .collect(); + + let pinocchio_crate = quote! { light_account_pinocchio }; + let packed_seed_ref_items: Vec<_> = spec + .seeds + .iter() + .map(|s| seed_to_packed_ref_with_crate(s, &pinocchio_crate)) + .collect(); + + let owner_derivation = if let Some(owner_seeds) = &spec.owner_seeds { + let owner_seed_refs: Vec<_> = owner_seeds + .iter() + .map(|seed| { + match seed { + SeedElement::Literal(lit) => { + let value = lit.value(); + quote! { #value.as_bytes() } + } + SeedElement::Expression(expr) => { + quote! { { let __seed: &[u8] = (#expr).as_ref(); __seed } } + } + } + }) + .collect(); + quote! { + let (__owner, _) = solana_pubkey::Pubkey::find_program_address( + &[#(#owner_seed_refs),*], + &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id), + ); + __owner.to_bytes() + } + } else { + quote! { [0u8; 32] } + }; + + quote! { + impl light_account_pinocchio::UnpackedTokenSeeds<#seed_count> + for #seeds_name + { + type Packed = #packed_seeds_name; + + const PROGRAM_ID: [u8; 32] = crate::LIGHT_CPI_SIGNER.program_id; + + fn seed_vec(&self) -> Vec> { + vec![#(#seed_vec_items),*] + } + + fn seed_refs_with_bump<'a>(&'a self, bump_storage: &'a [u8; 1]) -> [&'a [u8]; #seed_count] { + [#(#unpacked_seed_ref_items,)* bump_storage] + } + } + + impl light_account_pinocchio::PackedTokenSeeds<#seed_count> + for #packed_seeds_name + { + type Unpacked = #seeds_name; + + fn bump(&self) -> u8 { + self.bump + } + + fn unpack_seeds( + &self, + accounts: &[AI], + ) -> std::result::Result { + >::unpack(self, accounts) + } + + fn seed_refs_with_bump<'a, AI: light_account_pinocchio::light_account_checks::AccountInfoTrait>( + &'a self, + accounts: &'a [AI], + bump_storage: &'a [u8; 1], + ) -> std::result::Result<[&'a [u8]; #seed_count], light_account_pinocchio::LightSdkTypesError> { + Ok([#(#packed_seed_ref_items,)* bump_storage]) + } + + fn derive_owner(&self) -> [u8; 32] { + #owner_derivation + } + } + } + }) + .collect(); + + quote! { #(#impls)* } + } + + /// Generate unpacked enum (pinocchio version). + fn generate_unpacked_enum_pinocchio(&self) -> TokenStream { + let pda_variants: Vec<_> = self + .pda_ctx_seeds + .iter() + .map(|info| { + let variant_name = &info.variant_name; + let seeds_type = format_ident!("{}Seeds", variant_name); + let inner_type = qualify_type_with_crate(&info.inner_type); + quote! { #variant_name { seeds: #seeds_type, data: #inner_type } } + }) + .collect(); + + let token_variants: Vec<_> = self + .token_seeds + .iter() + .map(|spec| { + let variant_name = &spec.variant; + let seeds_name = format_ident!("{}Seeds", variant_name); + quote! { + #variant_name(light_account_pinocchio::token::TokenDataWithSeeds<#seeds_name>) + } + }) + .collect(); + + quote! { + #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] + pub enum LightAccountVariant { + #(#pda_variants,)* + #(#token_variants,)* + } + } + } + + /// Generate packed enum (pinocchio version). + fn generate_packed_enum_pinocchio(&self) -> TokenStream { + let pda_variants: Vec<_> = + self.pda_ctx_seeds + .iter() + .map(|info| { + let variant_name = &info.variant_name; + let packed_seeds_type = format_ident!("Packed{}Seeds", variant_name); + let inner_type = &info.inner_type; + let packed_data_type = + crate::light_pdas::shared_utils::make_packed_type(inner_type) + .unwrap_or_else(|| { + let type_str = quote!(#inner_type).to_string().replace(' ', ""); + let packed_name = format_ident!("Packed{}", type_str); + syn::parse_quote!(#packed_name) + }); + quote! { #variant_name { seeds: #packed_seeds_type, data: #packed_data_type } } + }) + .collect(); + + let token_variants: Vec<_> = self + .token_seeds + .iter() + .map(|spec| { + let variant_name = &spec.variant; + let packed_seeds_name = format_ident!("Packed{}Seeds", variant_name); + quote! { + #variant_name(light_account_pinocchio::token::TokenDataWithPackedSeeds<#packed_seeds_name>) + } + }) + .collect(); + + quote! { + #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] + pub enum PackedLightAccountVariant { + #(#pda_variants,)* + #(#token_variants,)* + } + } + } + + /// Generate LightAccountData struct (pinocchio version). + fn generate_light_account_data_struct_pinocchio(&self) -> TokenStream { + quote! { + #[derive(Clone, Debug, borsh::BorshSerialize, borsh::BorshDeserialize)] + pub struct LightAccountData { + pub meta: light_account_pinocchio::account_meta::CompressedAccountMetaNoLamportsNoAddress, + pub data: PackedLightAccountVariant, + } + } + } + + /// Generate DecompressVariant impl (pinocchio version). + fn generate_decompress_variant_impl_pinocchio(&self) -> TokenStream { + let pda_arms: Vec<_> = self + .pda_ctx_seeds + .iter() + .map(|info| { + let variant_name = &info.variant_name; + let packed_variant_type = format_ident!("Packed{}Variant", variant_name); + let seed_count = info.seed_count; + + quote! { + Self::#variant_name { seeds, data } => { + let packed_data = #packed_variant_type { seeds: seeds.clone(), data: data.clone() }; + light_account_pinocchio::prepare_account_for_decompression::<#seed_count, #packed_variant_type, pinocchio::account_info::AccountInfo>( + &packed_data, + tree_info, + output_queue_index, + pda_account, + ctx, + ) + } + } + }) + .collect(); + + let token_arms: Vec<_> = self + .token_seeds + .iter() + .map(|spec| { + let variant_name = &spec.variant; + let packed_seeds_name = format_ident!("Packed{}Seeds", variant_name); + let seed_count = spec.seeds.len() + 1; + + quote! { + Self::#variant_name(packed_data) => { + light_account_pinocchio::token::prepare_token_account_for_decompression::< + #seed_count, + light_account_pinocchio::token::TokenDataWithPackedSeeds<#packed_seeds_name>, + pinocchio::account_info::AccountInfo, + >( + packed_data, + tree_info, + output_queue_index, + pda_account, + ctx, + ) + } + } + }) + .collect(); + + quote! { + impl light_account_pinocchio::DecompressVariant for PackedLightAccountVariant { + fn decompress( + &self, + tree_info: &light_account_pinocchio::PackedStateTreeInfo, + pda_account: &pinocchio::account_info::AccountInfo, + ctx: &mut light_account_pinocchio::DecompressCtx<'_>, + ) -> std::result::Result<(), light_account_pinocchio::LightSdkTypesError> { + let output_queue_index = ctx.output_queue_index; + match self { + #(#pda_arms)* + #(#token_arms)* + } + } + } + } + } + + /// Generate Pack impl (pinocchio version). + fn generate_pack_impl_pinocchio(&self) -> TokenStream { + let pda_arms: Vec<_> = self + .pda_ctx_seeds + .iter() + .map(|info| { + let variant_name = &info.variant_name; + let variant_struct_name = format_ident!("{}Variant", variant_name); + + quote! { + Self::#variant_name { seeds, data } => { + let variant = #variant_struct_name { seeds: seeds.clone(), data: data.clone() }; + let packed = light_account_pinocchio::Pack::pack(&variant, accounts)?; + Ok(PackedLightAccountVariant::#variant_name { seeds: packed.seeds, data: packed.data }) + } + } + }) + .collect(); + + let token_arms: Vec<_> = self + .token_seeds + .iter() + .map(|spec| { + let variant_name = &spec.variant; + quote! { + Self::#variant_name(data) => { + let packed = light_account_pinocchio::Pack::pack(data, accounts)?; + Ok(PackedLightAccountVariant::#variant_name(packed)) + } + } + }) + .collect(); + + quote! { + #[cfg(not(target_os = "solana"))] + impl light_account_pinocchio::Pack for LightAccountVariant { + type Packed = PackedLightAccountVariant; + + fn pack( + &self, + accounts: &mut light_account_pinocchio::PackedAccounts, + ) -> std::result::Result { + match self { + #(#pda_arms)* + #(#token_arms)* + } + } + } + } + } } // ============================================================================= @@ -727,7 +1219,9 @@ fn seed_to_unpacked_ref(seed: &SeedElement) -> TokenStream { } /// Generate a seed ref expression for the PACKED variant (uses `accounts[idx].key.as_ref()`). -fn seed_to_packed_ref(seed: &SeedElement) -> TokenStream { +/// +/// `account_crate` selects the error path: `light_account` for Anchor, `light_account_pinocchio` for pinocchio. +fn seed_to_packed_ref_with_crate(seed: &SeedElement, account_crate: &TokenStream) -> TokenStream { match seed { SeedElement::Literal(lit) => { let value = lit.value(); @@ -757,7 +1251,7 @@ fn seed_to_packed_ref(seed: &SeedElement) -> TokenStream { return quote! { accounts .get(self.#idx_field as usize) - .ok_or(light_account::LightSdkTypesError::InvalidInstructionData)? + .ok_or(#account_crate::LightSdkTypesError::InvalidInstructionData)? .key_ref() }; } @@ -765,3 +1259,9 @@ fn seed_to_packed_ref(seed: &SeedElement) -> TokenStream { } } } + +/// Anchor-compatible wrapper. +fn seed_to_packed_ref(seed: &SeedElement) -> TokenStream { + let crate_path = quote! { light_account }; + seed_to_packed_ref_with_crate(seed, &crate_path) +} diff --git a/sdk-libs/sdk-types/src/interface/program/decompression/pda.rs b/sdk-libs/sdk-types/src/interface/program/decompression/pda.rs index 9ef915ff8f..2cbf6ba77a 100644 --- a/sdk-libs/sdk-types/src/interface/program/decompression/pda.rs +++ b/sdk-libs/sdk-types/src/interface/program/decompression/pda.rs @@ -121,7 +121,7 @@ where rent_sponsor_seeds, system_program, ) - .map_err(|_| LightSdkTypesError::CpiFailed)?; + .map_err(|e| LightSdkTypesError::ProgramError(e.into()))?; // 7. Write discriminator + data to PDA let mut pda_data = pda_account diff --git a/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs b/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs index e12d9dcd35..bf80ce1807 100644 --- a/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs +++ b/sdk-libs/sdk-types/src/interface/program/decompression/processor.rs @@ -561,7 +561,7 @@ where remaining_accounts, &[], ) - .map_err(|_| LightSdkTypesError::CpiFailed)?; + .map_err(|e| LightSdkTypesError::ProgramError(e.into()))?; } else { // At least one regular token account - use invoke_signed with PDA seeds let signer_seed_refs: Vec<&[u8]> = token_seeds.iter().map(|s| s.as_slice()).collect(); @@ -572,7 +572,7 @@ where remaining_accounts, &[signer_seed_refs.as_slice()], ) - .map_err(|_| LightSdkTypesError::CpiFailed)?; + .map_err(|e| LightSdkTypesError::ProgramError(e.into()))?; } } diff --git a/sdk-libs/sdk-types/src/interface/program/decompression/token.rs b/sdk-libs/sdk-types/src/interface/program/decompression/token.rs index 37eeb500a2..613feed142 100644 --- a/sdk-libs/sdk-types/src/interface/program/decompression/token.rs +++ b/sdk-libs/sdk-types/src/interface/program/decompression/token.rs @@ -108,7 +108,7 @@ where ctx.remaining_accounts, &[], ) - .map_err(|_| LightSdkTypesError::CpiFailed)?; + .map_err(|e| LightSdkTypesError::ProgramError(e.into()))?; } // Don't extend token_seeds for ATAs (invoke, not invoke_signed) } else { @@ -147,7 +147,7 @@ where ctx.remaining_accounts, &[signer_seeds.as_slice()], ) - .map_err(|_| LightSdkTypesError::CpiFailed)?; + .map_err(|e| LightSdkTypesError::ProgramError(e.into()))?; // Push seeds for the Transfer2 CPI (needed for invoke_signed) ctx.token_seeds.extend(seeds.iter().map(|s| s.to_vec())); diff --git a/sdk-libs/sdk/src/cpi/mod.rs b/sdk-libs/sdk/src/cpi/mod.rs index bdaaef1160..9c6379fa81 100644 --- a/sdk-libs/sdk/src/cpi/mod.rs +++ b/sdk-libs/sdk/src/cpi/mod.rs @@ -35,9 +35,8 @@ //! .invoke(light_cpi_accounts)?; //! ``` -// Local Solana-specific modules -pub mod account; -pub mod instruction; +mod account; +mod instruction; pub mod invoke; // Re-export local traits at crate::cpi:: level diff --git a/sdk-libs/token-sdk/src/anchor.rs b/sdk-libs/token-sdk/src/anchor.rs deleted file mode 100644 index 74d21ae7a8..0000000000 --- a/sdk-libs/token-sdk/src/anchor.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! Anchor integration module for Light Protocol compressed tokens. -//! -//! Provides a single import point for Anchor programs using Light Protocol. - -// Re-export Light SDK core types -pub use light_account::{ - CompressAs as CompressAsTrait, CompressedInitSpace, CompressionInfo, - HasCompressionInfo as HasCompressionInfoTrait, LightConfig, LightFinalize, LightPreInit, Space, - Unpack, -}; -#[cfg(not(target_os = "solana"))] -pub use light_account::{Pack, PackedAccounts}; -pub use light_sdk::{ - account::LightAccount as LightAccountType, - address, - cpi::{v2::CpiAccounts, InvokeLightSystemProgram, LightCpiInstruction}, - derive_light_cpi_signer, derive_light_cpi_signer_pda, - error::LightSdkError, - instruction::ValidityProof, - CpiSigner, LightDiscriminator as LightDiscriminatorTrait, -}; -// Re-export Light SDK macros -pub use light_sdk_macros::{ - // Proc macros - derive_light_rent_sponsor, - derive_light_rent_sponsor_pda, - // Attribute macros - light_program, - // Derive macros - CompressAs, - Compressible, - HasCompressionInfo, - LightAccount, - LightAccounts, - LightDiscriminator, - LightHasher, - LightHasherSha, -}; - -// Re-export token SDK types -pub use crate::{instruction::*, CompressedProof, ValidityProof as ValidityProofAlias}; diff --git a/sdk-libs/token-sdk/src/lib.rs b/sdk-libs/token-sdk/src/lib.rs index 2feafee8f1..217ecd57eb 100644 --- a/sdk-libs/token-sdk/src/lib.rs +++ b/sdk-libs/token-sdk/src/lib.rs @@ -70,8 +70,6 @@ //! # Disclaimer //! This library is not audited and in a beta state. Use at your own risk and expect breaking changes. -#[cfg(feature = "anchor")] -pub mod anchor; pub mod compressible; pub mod constants; pub mod error; diff --git a/sdk-tests/manual-test-pinocchio/src/all/derived.rs b/sdk-tests/manual-test-pinocchio/src/all/derived.rs index 3188fb2728..bad9eeae49 100644 --- a/sdk-tests/manual-test-pinocchio/src/all/derived.rs +++ b/sdk-tests/manual-test-pinocchio/src/all/derived.rs @@ -233,7 +233,7 @@ impl LightPreInit for CreateAllAccounts<'_> { infra, &cpi_accounts, ) - .map_err(|_| LightSdkTypesError::CpiFailed)?; + .map_err(|e| LightSdkTypesError::ProgramError(e.into()))?; } // ==================================================================== diff --git a/sdk-tests/manual-test-pinocchio/src/two_mints/derived.rs b/sdk-tests/manual-test-pinocchio/src/two_mints/derived.rs index ff8e833730..895ac1aed6 100644 --- a/sdk-tests/manual-test-pinocchio/src/two_mints/derived.rs +++ b/sdk-tests/manual-test-pinocchio/src/two_mints/derived.rs @@ -168,7 +168,7 @@ impl LightPreInit for CreateDerivedMintsA infra, &cpi_accounts, ) - .map_err(|_| LightSdkTypesError::CpiFailed)?; + .map_err(|e| LightSdkTypesError::ProgramError(e.into()))?; } Ok(WITH_CPI_CONTEXT) // false = mint-only, no CPI context write }; diff --git a/sdk-tests/manual-test/src/all/derived.rs b/sdk-tests/manual-test/src/all/derived.rs index 97c6d7cf30..b3f055363a 100644 --- a/sdk-tests/manual-test/src/all/derived.rs +++ b/sdk-tests/manual-test/src/all/derived.rs @@ -232,7 +232,7 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou infra, &cpi_accounts, ) - .map_err(|_| LightSdkTypesError::CpiFailed)?; + .map_err(|e| LightSdkTypesError::ProgramError(e.into()))?; } // ==================================================================== diff --git a/sdk-tests/manual-test/src/two_mints/derived.rs b/sdk-tests/manual-test/src/two_mints/derived.rs index a2cecdaf0a..b32373551f 100644 --- a/sdk-tests/manual-test/src/two_mints/derived.rs +++ b/sdk-tests/manual-test/src/two_mints/derived.rs @@ -179,7 +179,7 @@ impl<'info> LightPreInit, CreateDerivedMintsParams> infra, &cpi_accounts, ) - .map_err(|_| LightSdkTypesError::CpiFailed)?; + .map_err(|e| LightSdkTypesError::ProgramError(e.into()))?; } Ok(WITH_CPI_CONTEXT) // false = mint-only, no CPI context write }; diff --git a/sdk-tests/pinocchio-light-program-test/Cargo.toml b/sdk-tests/pinocchio-light-program-test/Cargo.toml new file mode 100644 index 0000000000..ad33239596 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "pinocchio-light-program-test" +version = "0.1.0" +description = "Test for #[derive(LightProgramPinocchio)] macro validation with pinocchio" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "pinocchio_light_program_test" + +[features] +no-entrypoint = [] +default = [] +test-sbf = [] + +[dependencies] +light-account-pinocchio = { workspace = true, features = ["token", "std"] } +light-macros = { workspace = true, features = ["solana"] } +light-sdk-macros = { workspace = true, features = ["anchor-discriminator"] } +light-sdk-types = { workspace = true, features = ["v2", "cpi-context"] } +borsh = { workspace = true } +bytemuck = { workspace = true, features = ["derive"] } +light-compressed-account = { workspace = true, features = ["solana"] } +light-compressible = { workspace = true, features = ["pinocchio"] } +light-hasher = { workspace = true, features = ["solana"] } +light-token-types = { workspace = true } +light-token-interface = { workspace = true } +pinocchio = { workspace = true } +pinocchio-pubkey = { workspace = true } +pinocchio-system = { workspace = true } +solana-pubkey = { workspace = true } +solana-instruction = { workspace = true } +solana-msg = { workspace = true } +solana-program-error = { workspace = true } + +[dev-dependencies] +light-program-test = { workspace = true, features = ["devenv"] } +light-client = { workspace = true, features = ["v2", "anchor"] } +light-test-utils = { workspace = true } +light-token = { workspace = true, features = ["anchor"] } +light-token-client = { workspace = true } +light-account = { workspace = true } +light-batched-merkle-tree = { workspace = true } +tokio = { workspace = true } +solana-sdk = { workspace = true } +solana-account = { workspace = true } +solana-keypair = { workspace = true } +solana-signer = { workspace = true } + +[lints.rust.unexpected_cfgs] +level = "allow" +check-cfg = [ + 'cfg(target_os, values("solana"))', + 'cfg(feature, values("frozen-abi", "no-entrypoint"))', +] diff --git a/sdk-tests/pinocchio-light-program-test/src/account_loader/accounts.rs b/sdk-tests/pinocchio-light-program-test/src/account_loader/accounts.rs new file mode 100644 index 0000000000..ed27ddfb2d --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/account_loader/accounts.rs @@ -0,0 +1,87 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::CreateAccountsProof; +use pinocchio::{ + account_info::AccountInfo, + instruction::{Seed, Signer}, + program_error::ProgramError, + sysvars::Sysvar, +}; + +use crate::state::ZeroCopyRecord; + +#[derive(BorshSerialize, BorshDeserialize, Clone)] +pub struct CreateZeroCopyRecordParams { + pub create_accounts_proof: CreateAccountsProof, + pub owner: [u8; 32], +} + +pub struct CreateZeroCopyRecord<'a> { + pub fee_payer: &'a AccountInfo, + pub compression_config: &'a AccountInfo, + pub pda_rent_sponsor: &'a AccountInfo, + pub record: &'a AccountInfo, + pub system_program: &'a AccountInfo, +} + +impl<'a> CreateZeroCopyRecord<'a> { + pub const FIXED_LEN: usize = 5; + + pub fn parse( + accounts: &'a [AccountInfo], + params: &CreateZeroCopyRecordParams, + ) -> Result { + let fee_payer = &accounts[0]; + let compression_config = &accounts[1]; + let pda_rent_sponsor = &accounts[2]; + let record = &accounts[3]; + let system_program = &accounts[4]; + + if !fee_payer.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + let space = 8 + ZeroCopyRecord::INIT_SPACE; + let seeds: &[&[u8]] = &[crate::RECORD_SEED, ¶ms.owner]; + let (expected_pda, bump) = pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if record.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + + let rent = pinocchio::sysvars::rent::Rent::get() + .map_err(|_| ProgramError::UnsupportedSysvar)?; + let lamports = rent.minimum_balance(space); + + let bump_bytes = [bump]; + let seed_array = [ + Seed::from(crate::RECORD_SEED), + Seed::from(params.owner.as_ref()), + Seed::from(bump_bytes.as_ref()), + ]; + let signer = Signer::from(&seed_array); + pinocchio_system::instructions::CreateAccount { + from: fee_payer, + to: record, + lamports, + space: space as u64, + owner: &crate::ID, + } + .invoke_signed(&[signer])?; + + // Write LIGHT_DISCRIMINATOR + { + use light_account_pinocchio::LightDiscriminator; + let mut data = record + .try_borrow_mut_data() + .map_err(|_| ProgramError::AccountBorrowFailed)?; + data[..8].copy_from_slice(&ZeroCopyRecord::LIGHT_DISCRIMINATOR); + } + + Ok(Self { + fee_payer, + compression_config, + pda_rent_sponsor, + record, + system_program, + }) + } +} diff --git a/sdk-tests/pinocchio-light-program-test/src/account_loader/derived_accounts.rs b/sdk-tests/pinocchio-light-program-test/src/account_loader/derived_accounts.rs new file mode 100644 index 0000000000..06815f7033 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/account_loader/derived_accounts.rs @@ -0,0 +1,109 @@ +use light_account_pinocchio::{ + prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, LightFinalize, + LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, +}; +use light_compressed_account::instruction_data::{ + cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, +}; +use pinocchio::account_info::AccountInfo; + +use super::accounts::{CreateZeroCopyRecord, CreateZeroCopyRecordParams}; +use crate::state::ZeroCopyRecord; + +impl LightPreInit for CreateZeroCopyRecord<'_> { + fn light_pre_init( + &mut self, + remaining_accounts: &[AccountInfo], + params: &CreateZeroCopyRecordParams, + ) -> std::result::Result { + let inner = || -> std::result::Result { + use light_account_pinocchio::{InvokeLightSystemProgram, LightAccount, LightConfig}; + use pinocchio::sysvars::{clock::Clock, Sysvar}; + + let system_accounts_offset = + params.create_accounts_proof.system_accounts_offset as usize; + if remaining_accounts.len() < system_accounts_offset { + return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); + } + let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); + let cpi_accounts = CpiAccounts::new_with_config( + self.fee_payer, + &remaining_accounts[system_accounts_offset..], + config, + ); + + let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; + let address_tree_pubkey = address_tree_info + .get_tree_pubkey(&cpi_accounts) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + let output_tree_index = params.create_accounts_proof.output_state_tree_index; + let current_account_index: u8 = 0; + const WITH_CPI_CONTEXT: bool = false; + let cpi_context = CompressedCpiContext::default(); + const NUM_LIGHT_PDAS: usize = 1; + let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); + let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); + + let light_config = LightConfig::load_checked(self.compression_config, &crate::ID) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + let current_slot = Clock::get() + .map_err(|_| LightSdkTypesError::InvalidInstructionData)? + .slot; + + let record_key = *self.record.key(); + prepare_compressed_account_on_init( + &record_key, + &address_tree_pubkey, + address_tree_info, + output_tree_index, + current_account_index, + &crate::ID, + &mut new_address_params, + &mut account_infos, + )?; + + // Set compression_info on the zero-copy record via bytemuck + { + let mut account_data = self + .record + .try_borrow_mut_data() + .map_err(|_| LightSdkTypesError::Borsh)?; + let record_bytes = + &mut account_data[8..8 + core::mem::size_of::()]; + let record: &mut ZeroCopyRecord = bytemuck::from_bytes_mut(record_bytes); + record.set_decompressed(&light_config, current_slot); + } + + let instruction_data = InstructionDataInvokeCpiWithAccountInfo { + mode: 1, + bump: crate::LIGHT_CPI_SIGNER.bump, + invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), + compress_or_decompress_lamports: 0, + is_compress: false, + with_cpi_context: WITH_CPI_CONTEXT, + with_transaction_hash: false, + cpi_context, + proof: params.create_accounts_proof.proof.0, + new_address_params, + account_infos, + read_only_addresses: vec![], + read_only_accounts: vec![], + }; + + instruction_data.invoke(cpi_accounts)?; + Ok(false) + }; + inner() + } +} + +impl LightFinalize for CreateZeroCopyRecord<'_> { + fn light_finalize( + &mut self, + _remaining_accounts: &[AccountInfo], + _params: &CreateZeroCopyRecordParams, + _has_pre_init: bool, + ) -> std::result::Result<(), LightSdkTypesError> { + Ok(()) + } +} diff --git a/sdk-tests/pinocchio-light-program-test/src/account_loader/mod.rs b/sdk-tests/pinocchio-light-program-test/src/account_loader/mod.rs new file mode 100644 index 0000000000..bfb5a447e8 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/account_loader/mod.rs @@ -0,0 +1,4 @@ +pub mod accounts; +mod derived_accounts; + +pub use accounts::*; diff --git a/sdk-tests/pinocchio-light-program-test/src/all/accounts.rs b/sdk-tests/pinocchio-light-program-test/src/all/accounts.rs new file mode 100644 index 0000000000..ed578fc123 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/all/accounts.rs @@ -0,0 +1,193 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::CreateAccountsProof; +use pinocchio::{ + account_info::AccountInfo, + instruction::{Seed, Signer}, + program_error::ProgramError, + sysvars::Sysvar, +}; + +use crate::state::{MinimalRecord, ZeroCopyRecord}; + +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] +pub struct CreateAllParams { + pub create_accounts_proof: CreateAccountsProof, + pub owner: [u8; 32], + pub mint_signer_bump: u8, + pub token_vault_bump: u8, +} + +pub struct CreateAllAccounts<'a> { + pub payer: &'a AccountInfo, + pub authority: &'a AccountInfo, + pub compression_config: &'a AccountInfo, + pub borsh_record: &'a AccountInfo, + pub zero_copy_record: &'a AccountInfo, + pub mint_signer: &'a AccountInfo, + pub mint: &'a AccountInfo, + pub token_vault: &'a AccountInfo, + pub vault_owner: &'a AccountInfo, + pub ata_owner: &'a AccountInfo, + pub user_ata: &'a AccountInfo, + pub compressible_config: &'a AccountInfo, + pub rent_sponsor: &'a AccountInfo, + pub light_token_program: &'a AccountInfo, + pub cpi_authority: &'a AccountInfo, + pub system_program: &'a AccountInfo, + pub mint_signers_slice: &'a [AccountInfo], + pub mints_slice: &'a [AccountInfo], +} + +impl<'a> CreateAllAccounts<'a> { + pub const FIXED_LEN: usize = 16; + + pub fn parse( + accounts: &'a [AccountInfo], + params: &CreateAllParams, + ) -> Result { + let payer = &accounts[0]; + let authority = &accounts[1]; + let compression_config = &accounts[2]; + let borsh_record = &accounts[3]; + let zero_copy_record = &accounts[4]; + let mint_signer = &accounts[5]; + let mint = &accounts[6]; + let token_vault = &accounts[7]; + let vault_owner = &accounts[8]; + let ata_owner = &accounts[9]; + let user_ata = &accounts[10]; + let compressible_config = &accounts[11]; + let rent_sponsor = &accounts[12]; + let light_token_program = &accounts[13]; + let cpi_authority = &accounts[14]; + let system_program = &accounts[15]; + + if !payer.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + if !authority.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + // Create Borsh PDA + { + let space = 8 + MinimalRecord::INIT_SPACE; + let seeds: &[&[u8]] = &[b"minimal_record", ¶ms.owner]; + let (expected_pda, bump) = pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if borsh_record.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + + let rent = pinocchio::sysvars::rent::Rent::get() + .map_err(|_| ProgramError::UnsupportedSysvar)?; + let lamports = rent.minimum_balance(space); + + let bump_bytes = [bump]; + let seed_array = [ + Seed::from(b"minimal_record" as &[u8]), + Seed::from(params.owner.as_ref()), + Seed::from(bump_bytes.as_ref()), + ]; + let signer = Signer::from(&seed_array); + pinocchio_system::instructions::CreateAccount { + from: payer, + to: borsh_record, + lamports, + space: space as u64, + owner: &crate::ID, + } + .invoke_signed(&[signer])?; + + use light_account_pinocchio::LightDiscriminator; + let mut data = borsh_record + .try_borrow_mut_data() + .map_err(|_| ProgramError::AccountBorrowFailed)?; + data[..8].copy_from_slice(&MinimalRecord::LIGHT_DISCRIMINATOR); + } + + // Create ZeroCopy PDA + { + let space = 8 + ZeroCopyRecord::INIT_SPACE; + let seeds: &[&[u8]] = &[crate::RECORD_SEED, ¶ms.owner]; + let (expected_pda, bump) = pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if zero_copy_record.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + + let rent = pinocchio::sysvars::rent::Rent::get() + .map_err(|_| ProgramError::UnsupportedSysvar)?; + let lamports = rent.minimum_balance(space); + + let bump_bytes = [bump]; + let seed_array = [ + Seed::from(crate::RECORD_SEED), + Seed::from(params.owner.as_ref()), + Seed::from(bump_bytes.as_ref()), + ]; + let signer = Signer::from(&seed_array); + pinocchio_system::instructions::CreateAccount { + from: payer, + to: zero_copy_record, + lamports, + space: space as u64, + owner: &crate::ID, + } + .invoke_signed(&[signer])?; + + use light_account_pinocchio::LightDiscriminator; + let mut data = zero_copy_record + .try_borrow_mut_data() + .map_err(|_| ProgramError::AccountBorrowFailed)?; + data[..8].copy_from_slice(&ZeroCopyRecord::LIGHT_DISCRIMINATOR); + } + + // Validate mint_signer PDA + { + let authority_key = authority.key(); + let seeds: &[&[u8]] = &[crate::MINT_SIGNER_SEED_A, authority_key]; + let (expected_pda, expected_bump) = + pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if mint_signer.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + if expected_bump != params.mint_signer_bump { + return Err(ProgramError::InvalidSeeds); + } + } + + // Validate token_vault PDA + { + let mint_key = mint.key(); + let seeds: &[&[u8]] = &[crate::VAULT_SEED, mint_key]; + let (expected_pda, expected_bump) = + pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if token_vault.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + if expected_bump != params.token_vault_bump { + return Err(ProgramError::InvalidSeeds); + } + } + + Ok(Self { + payer, + authority, + compression_config, + borsh_record, + zero_copy_record, + mint_signer, + mint, + token_vault, + vault_owner, + ata_owner, + user_ata, + compressible_config, + rent_sponsor, + light_token_program, + cpi_authority, + system_program, + mint_signers_slice: &accounts[5..6], + mints_slice: &accounts[6..7], + }) + } +} diff --git a/sdk-tests/pinocchio-light-program-test/src/all/derived.rs b/sdk-tests/pinocchio-light-program-test/src/all/derived.rs new file mode 100644 index 0000000000..dcd6885a26 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/all/derived.rs @@ -0,0 +1,268 @@ +use borsh::BorshDeserialize; +use light_account_pinocchio::{ + derive_associated_token_account, derive_mint_compressed_address, find_mint_address, + invoke_create_mints, prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, + CpiContextWriteAccounts, CreateMintsInfraAccounts, CreateMintsParams as SdkCreateMintsParams, + CreateTokenAccountCpi, CreateTokenAtaCpi, InvokeLightSystemProgram, LightAccount, + LightFinalize, LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, SingleMintParams, + DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, +}; +use light_compressed_account::instruction_data::{ + cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, +}; +use pinocchio::account_info::AccountInfo; + +use super::accounts::{CreateAllAccounts, CreateAllParams}; + +impl LightPreInit for CreateAllAccounts<'_> { + fn light_pre_init( + &mut self, + remaining_accounts: &[AccountInfo], + params: &CreateAllParams, + ) -> std::result::Result { + let inner = || -> std::result::Result { + use light_account_pinocchio::LightConfig; + use pinocchio::sysvars::{clock::Clock, Sysvar}; + + const NUM_LIGHT_PDAS: usize = 2; + const NUM_LIGHT_MINTS: usize = 1; + const WITH_CPI_CONTEXT: bool = true; + + // 1. Build CPI accounts + let system_accounts_offset = + params.create_accounts_proof.system_accounts_offset as usize; + if remaining_accounts.len() < system_accounts_offset { + return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); + } + let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); + let cpi_accounts = CpiAccounts::new_with_config( + self.payer, + &remaining_accounts[system_accounts_offset..], + config, + ); + + // 2. Address tree info + let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; + let address_tree_pubkey = address_tree_info + .get_tree_pubkey(&cpi_accounts) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + let output_tree_index = params.create_accounts_proof.output_state_tree_index; + + // 3. Load config, get slot + let light_config = LightConfig::load_checked(self.compression_config, &crate::ID) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + let current_slot = Clock::get() + .map_err(|_| LightSdkTypesError::InvalidInstructionData)? + .slot; + + // 4. Create PDAs via invoke_write_to_cpi_context_first + { + let cpi_context = CompressedCpiContext::first(); + let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); + let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); + + // 4a. Borsh PDA (index 0) + let borsh_record_key = *self.borsh_record.key(); + prepare_compressed_account_on_init( + &borsh_record_key, + &address_tree_pubkey, + address_tree_info, + output_tree_index, + 0, + &crate::ID, + &mut new_address_params, + &mut account_infos, + )?; + { + let mut account_data = self + .borsh_record + .try_borrow_mut_data() + .map_err(|_| LightSdkTypesError::Borsh)?; + let mut record = + crate::state::MinimalRecord::try_from_slice(&account_data[8..]) + .map_err(|_| LightSdkTypesError::Borsh)?; + record.set_decompressed(&light_config, current_slot); + let serialized = + borsh::to_vec(&record).map_err(|_| LightSdkTypesError::Borsh)?; + account_data[8..8 + serialized.len()].copy_from_slice(&serialized); + } + + // 4b. ZeroCopy PDA (index 1) + let zero_copy_record_key = *self.zero_copy_record.key(); + prepare_compressed_account_on_init( + &zero_copy_record_key, + &address_tree_pubkey, + address_tree_info, + output_tree_index, + 1, + &crate::ID, + &mut new_address_params, + &mut account_infos, + )?; + { + let mut account_data = self + .zero_copy_record + .try_borrow_mut_data() + .map_err(|_| LightSdkTypesError::Borsh)?; + let record_bytes = &mut account_data + [8..8 + core::mem::size_of::()]; + let record: &mut crate::state::ZeroCopyRecord = + bytemuck::from_bytes_mut(record_bytes); + record.set_decompressed(&light_config, current_slot); + } + + // 4c. Write to CPI context + let instruction_data = InstructionDataInvokeCpiWithAccountInfo { + mode: 1, + bump: crate::LIGHT_CPI_SIGNER.bump, + invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), + compress_or_decompress_lamports: 0, + is_compress: false, + with_cpi_context: WITH_CPI_CONTEXT, + with_transaction_hash: false, + cpi_context, + proof: params.create_accounts_proof.proof.0, + new_address_params, + account_infos, + read_only_addresses: vec![], + read_only_accounts: vec![], + }; + + let cpi_context_accounts = CpiContextWriteAccounts { + fee_payer: cpi_accounts.fee_payer(), + authority: cpi_accounts.authority()?, + cpi_context: cpi_accounts.cpi_context()?, + cpi_signer: crate::LIGHT_CPI_SIGNER, + }; + instruction_data.invoke_write_to_cpi_context_first(cpi_context_accounts)?; + } + + // 5. Create Mint + { + let authority_key = *self.authority.key(); + let mint_signer_key = *self.mint_signer.key(); + + let (mint_pda, mint_bump) = find_mint_address(&mint_signer_key); + let compression_address = + derive_mint_compressed_address(&mint_signer_key, &address_tree_pubkey); + + let mint_signer_seeds: &[&[u8]] = &[ + crate::MINT_SIGNER_SEED_A, + authority_key.as_ref(), + &[params.mint_signer_bump], + ]; + + let sdk_mints: [SingleMintParams<'_>; NUM_LIGHT_MINTS] = [SingleMintParams { + decimals: 9, + address_merkle_tree_root_index: address_tree_info.root_index, + mint_authority: authority_key, + compression_address, + mint: mint_pda, + bump: mint_bump, + freeze_authority: None, + mint_seed_pubkey: mint_signer_key, + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_seeds), + token_metadata: None, + }]; + + let state_tree_index = params + .create_accounts_proof + .state_tree_index + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + + let proof = params + .create_accounts_proof + .proof + .0 + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + + let sdk_params = SdkCreateMintsParams { + mints: &sdk_mints, + proof, + rent_payment: DEFAULT_RENT_PAYMENT, + write_top_up: DEFAULT_WRITE_TOP_UP, + cpi_context_offset: NUM_LIGHT_PDAS as u8, + output_queue_index: params.create_accounts_proof.output_state_tree_index, + address_tree_index: address_tree_info.address_merkle_tree_pubkey_index, + state_tree_index, + base_leaf_index: 0, + }; + + let infra = CreateMintsInfraAccounts { + fee_payer: self.payer, + compressible_config: self.compressible_config, + rent_sponsor: self.rent_sponsor, + cpi_authority: self.cpi_authority, + }; + + invoke_create_mints( + self.mint_signers_slice, + self.mints_slice, + sdk_params, + infra, + &cpi_accounts, + ) + .map_err(|e| LightSdkTypesError::ProgramError(e.into()))?; + } + + // 6. Create Token Vault + { + let mint_key = *self.mint.key(); + let vault_seeds: &[&[u8]] = &[ + crate::VAULT_SEED, + mint_key.as_ref(), + &[params.token_vault_bump], + ]; + + CreateTokenAccountCpi { + payer: self.payer, + account: self.token_vault, + mint: self.mint, + owner: *self.vault_owner.key(), + } + .rent_free( + self.compressible_config, + self.rent_sponsor, + self.system_program, + &crate::ID, + ) + .invoke_signed(vault_seeds)?; + } + + // 7. Create ATA + { + let (_, ata_bump) = + derive_associated_token_account(self.ata_owner.key(), self.mint.key()); + + CreateTokenAtaCpi { + payer: self.payer, + owner: self.ata_owner, + mint: self.mint, + ata: self.user_ata, + bump: ata_bump, + } + .rent_free( + self.compressible_config, + self.rent_sponsor, + self.system_program, + ) + .invoke()?; + } + + Ok(WITH_CPI_CONTEXT) + }; + inner() + } +} + +impl LightFinalize for CreateAllAccounts<'_> { + fn light_finalize( + &mut self, + _remaining_accounts: &[AccountInfo], + _params: &CreateAllParams, + _has_pre_init: bool, + ) -> std::result::Result<(), LightSdkTypesError> { + Ok(()) + } +} diff --git a/sdk-tests/pinocchio-light-program-test/src/all/mod.rs b/sdk-tests/pinocchio-light-program-test/src/all/mod.rs new file mode 100644 index 0000000000..5e2a6edaeb --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/all/mod.rs @@ -0,0 +1,4 @@ +pub mod accounts; +mod derived; + +pub use accounts::*; diff --git a/sdk-tests/pinocchio-light-program-test/src/ata/accounts.rs b/sdk-tests/pinocchio-light-program-test/src/ata/accounts.rs new file mode 100644 index 0000000000..6f51536470 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/ata/accounts.rs @@ -0,0 +1,46 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError}; + +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Default)] +pub struct CreateAtaParams {} + +pub struct CreateAtaAccounts<'a> { + pub payer: &'a AccountInfo, + pub mint: &'a AccountInfo, + pub ata_owner: &'a AccountInfo, + pub user_ata: &'a AccountInfo, + pub compressible_config: &'a AccountInfo, + pub rent_sponsor: &'a AccountInfo, + pub light_token_program: &'a AccountInfo, + pub system_program: &'a AccountInfo, +} + +impl<'a> CreateAtaAccounts<'a> { + pub const FIXED_LEN: usize = 8; + + pub fn parse(accounts: &'a [AccountInfo]) -> Result { + let payer = &accounts[0]; + let mint = &accounts[1]; + let ata_owner = &accounts[2]; + let user_ata = &accounts[3]; + let compressible_config = &accounts[4]; + let rent_sponsor = &accounts[5]; + let light_token_program = &accounts[6]; + let system_program = &accounts[7]; + + if !payer.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + Ok(Self { + payer, + mint, + ata_owner, + user_ata, + compressible_config, + rent_sponsor, + light_token_program, + system_program, + }) + } +} diff --git a/sdk-tests/pinocchio-light-program-test/src/ata/derived.rs b/sdk-tests/pinocchio-light-program-test/src/ata/derived.rs new file mode 100644 index 0000000000..4102e65dc6 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/ata/derived.rs @@ -0,0 +1,48 @@ +use light_account_pinocchio::{ + derive_associated_token_account, CreateTokenAtaCpi, LightFinalize, LightPreInit, + LightSdkTypesError, +}; +use pinocchio::account_info::AccountInfo; + +use super::accounts::{CreateAtaAccounts, CreateAtaParams}; + +impl LightPreInit for CreateAtaAccounts<'_> { + fn light_pre_init( + &mut self, + _remaining_accounts: &[AccountInfo], + _params: &CreateAtaParams, + ) -> std::result::Result { + let inner = || -> std::result::Result { + let (_, bump) = derive_associated_token_account(self.ata_owner.key(), self.mint.key()); + + CreateTokenAtaCpi { + payer: self.payer, + owner: self.ata_owner, + mint: self.mint, + ata: self.user_ata, + bump, + } + .idempotent() + .rent_free( + self.compressible_config, + self.rent_sponsor, + self.system_program, + ) + .invoke()?; + + Ok(false) + }; + inner() + } +} + +impl LightFinalize for CreateAtaAccounts<'_> { + fn light_finalize( + &mut self, + _remaining_accounts: &[AccountInfo], + _params: &CreateAtaParams, + _has_pre_init: bool, + ) -> std::result::Result<(), LightSdkTypesError> { + Ok(()) + } +} diff --git a/sdk-tests/pinocchio-light-program-test/src/ata/mod.rs b/sdk-tests/pinocchio-light-program-test/src/ata/mod.rs new file mode 100644 index 0000000000..5e2a6edaeb --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/ata/mod.rs @@ -0,0 +1,4 @@ +pub mod accounts; +mod derived; + +pub use accounts::*; diff --git a/sdk-tests/pinocchio-light-program-test/src/derived_state.rs b/sdk-tests/pinocchio-light-program-test/src/derived_state.rs new file mode 100644 index 0000000000..871e372f6b --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/derived_state.rs @@ -0,0 +1,171 @@ +//! LightAccount implementations for state types. +//! +//! Provides PackedXxx types and LightAccount/HasCompressionInfo trait impls. + +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::{ + light_account_checks::{ + packed_accounts::ProgramPackedAccounts, AccountInfoTrait, AccountMetaTrait, + }, + AccountType, CompressionInfo, HasCompressionInfo, LightAccount, LightConfig, + LightSdkTypesError, +}; + +use crate::state::{MinimalRecord, ZeroCopyRecord}; + +// ============================================================================ +// PackedMinimalRecord +// ============================================================================ + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct PackedMinimalRecord { + pub owner: u8, +} + +// ============================================================================ +// LightAccount for MinimalRecord +// ============================================================================ + +impl LightAccount for MinimalRecord { + const ACCOUNT_TYPE: AccountType = AccountType::Pda; + + type Packed = PackedMinimalRecord; + + const INIT_SPACE: usize = core::mem::size_of::() + 32; + + fn compression_info(&self) -> &CompressionInfo { + &self.compression_info + } + + fn compression_info_mut(&mut self) -> &mut CompressionInfo { + &mut self.compression_info + } + + fn set_decompressed(&mut self, config: &LightConfig, current_slot: u64) { + self.compression_info = CompressionInfo::new_from_config(config, current_slot); + } + + #[cfg(not(target_os = "solana"))] + fn pack( + &self, + accounts: &mut light_account_pinocchio::interface::instruction::PackedAccounts, + ) -> std::result::Result { + Ok(PackedMinimalRecord { + owner: accounts.insert_or_get(AM::pubkey_from_bytes(self.owner)), + }) + } + + fn unpack( + packed: &Self::Packed, + accounts: &ProgramPackedAccounts, + ) -> std::result::Result { + let owner_account = accounts + .get_u8(packed.owner, "MinimalRecord: owner") + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + Ok(MinimalRecord { + compression_info: CompressionInfo::compressed(), + owner: owner_account.key(), + }) + } +} + +impl HasCompressionInfo for MinimalRecord { + fn compression_info(&self) -> std::result::Result<&CompressionInfo, LightSdkTypesError> { + Ok(&self.compression_info) + } + + fn compression_info_mut( + &mut self, + ) -> std::result::Result<&mut CompressionInfo, LightSdkTypesError> { + Ok(&mut self.compression_info) + } + + fn compression_info_mut_opt(&mut self) -> &mut Option { + panic!("compression_info_mut_opt not supported for LightAccount types") + } + + fn set_compression_info_none(&mut self) -> std::result::Result<(), LightSdkTypesError> { + self.compression_info = CompressionInfo::compressed(); + Ok(()) + } +} + +// ============================================================================ +// PackedZeroCopyRecord +// ============================================================================ + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct PackedZeroCopyRecord { + pub owner: u8, + pub counter: u64, +} + +// ============================================================================ +// LightAccount for ZeroCopyRecord +// ============================================================================ + +impl LightAccount for ZeroCopyRecord { + const ACCOUNT_TYPE: AccountType = AccountType::PdaZeroCopy; + + type Packed = PackedZeroCopyRecord; + + const INIT_SPACE: usize = core::mem::size_of::(); + + fn compression_info(&self) -> &CompressionInfo { + &self.compression_info + } + + fn compression_info_mut(&mut self) -> &mut CompressionInfo { + &mut self.compression_info + } + + fn set_decompressed(&mut self, config: &LightConfig, current_slot: u64) { + self.compression_info = CompressionInfo::new_from_config(config, current_slot); + } + + #[cfg(not(target_os = "solana"))] + fn pack( + &self, + accounts: &mut light_account_pinocchio::interface::instruction::PackedAccounts, + ) -> std::result::Result { + Ok(PackedZeroCopyRecord { + owner: accounts.insert_or_get(AM::pubkey_from_bytes(self.owner)), + counter: self.counter, + }) + } + + fn unpack( + packed: &Self::Packed, + accounts: &ProgramPackedAccounts, + ) -> std::result::Result { + let owner_account = accounts + .get_u8(packed.owner, "ZeroCopyRecord: owner") + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + Ok(ZeroCopyRecord { + compression_info: CompressionInfo::compressed(), + owner: owner_account.key(), + counter: packed.counter, + }) + } +} + +impl HasCompressionInfo for ZeroCopyRecord { + fn compression_info(&self) -> std::result::Result<&CompressionInfo, LightSdkTypesError> { + Ok(&self.compression_info) + } + + fn compression_info_mut( + &mut self, + ) -> std::result::Result<&mut CompressionInfo, LightSdkTypesError> { + Ok(&mut self.compression_info) + } + + fn compression_info_mut_opt(&mut self) -> &mut Option { + panic!("compression_info_mut_opt not supported for LightAccount types") + } + + fn set_compression_info_none(&mut self) -> std::result::Result<(), LightSdkTypesError> { + self.compression_info = CompressionInfo::compressed(); + Ok(()) + } +} diff --git a/sdk-tests/pinocchio-light-program-test/src/lib.rs b/sdk-tests/pinocchio-light-program-test/src/lib.rs new file mode 100644 index 0000000000..9ec797b8dd --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/lib.rs @@ -0,0 +1,335 @@ +//! Test program for #[derive(LightProgramPinocchio)] macro validation. +//! +//! Uses #[derive(LightProgramPinocchio)] to generate compress/decompress dispatch, +//! config handlers, and variant types. No Anchor dependency. + +#![allow(deprecated)] + +use light_account_pinocchio::{derive_light_cpi_signer, CpiSigner, LightFinalize, LightPreInit}; +use light_macros::pubkey_array; +use light_sdk_macros::LightProgramPinocchio; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; + +pub mod account_loader; +pub mod all; +pub mod ata; +pub mod derived_state; +pub mod mint; +pub mod pda; +pub mod state; +pub mod token_account; +pub mod two_mints; + +pub use derived_state::*; +pub use state::*; + +pub const ID: Pubkey = pubkey_array!("DrvPda11111111111111111111111111111111111111"); + +pub const LIGHT_CPI_SIGNER: CpiSigner = + derive_light_cpi_signer!("DrvPda11111111111111111111111111111111111111"); + +pub const VAULT_AUTH_SEED: &[u8] = b"vault_auth"; +pub const VAULT_SEED: &[u8] = b"vault"; +pub const RECORD_SEED: &[u8] = b"zero_copy_record"; +pub const MINT_SIGNER_SEED_A: &[u8] = b"mint_signer_a"; +pub const MINT_SIGNER_SEED_B: &[u8] = b"mint_signer_b"; + +/// This generates: variant enums, compress/decompress dispatch, config handlers, +/// per-variant Seeds/Variant/Packed types, LightAccountVariantTrait impls, +/// size validation, seed providers, and client functions. +#[derive(LightProgramPinocchio)] +pub enum ProgramAccounts { + #[light_account(pda::seeds = [b"minimal_record", ctx.owner])] + MinimalRecord(MinimalRecord), + + #[light_account(associated_token)] + Ata, + + #[light_account(token::seeds = [VAULT_SEED, ctx.mint], token::owner_seeds = [VAULT_AUTH_SEED])] + Vault, + + #[light_account(pda::seeds = [RECORD_SEED, ctx.owner], pda::zero_copy)] + ZeroCopyRecord(ZeroCopyRecord), +} + +// ============================================================================ +// Instruction Discriminators (Anchor-compatible: sha256("global:{name}")[..8]) +// ============================================================================ +pub mod discriminators { + pub const CREATE_PDA: [u8; 8] = [220, 10, 244, 120, 183, 4, 64, 232]; + pub const CREATE_ATA: [u8; 8] = [26, 102, 168, 62, 117, 72, 168, 17]; + pub const CREATE_TOKEN_VAULT: [u8; 8] = [161, 29, 12, 45, 127, 88, 61, 49]; + pub const CREATE_ZERO_COPY_RECORD: [u8; 8] = [6, 252, 72, 240, 45, 91, 28, 6]; + pub const CREATE_MINT: [u8; 8] = [69, 44, 215, 132, 253, 214, 41, 45]; + pub const CREATE_TWO_MINTS: [u8; 8] = [222, 41, 188, 84, 174, 115, 236, 105]; + pub const CREATE_ALL: [u8; 8] = [149, 49, 144, 45, 208, 155, 177, 43]; + // SDK-standard discriminators (must match light-client) + pub const INITIALIZE_COMPRESSION_CONFIG: [u8; 8] = [133, 228, 12, 169, 56, 76, 222, 61]; + pub const UPDATE_COMPRESSION_CONFIG: [u8; 8] = [135, 215, 243, 81, 163, 146, 33, 70]; + pub const COMPRESS_ACCOUNTS_IDEMPOTENT: [u8; 8] = [70, 236, 171, 120, 164, 93, 113, 181]; + pub const DECOMPRESS_ACCOUNTS_IDEMPOTENT: [u8; 8] = [114, 67, 61, 123, 234, 31, 1, 112]; +} + +// ============================================================================ +// Entrypoint +// ============================================================================ + +/// Strip the 4-byte Vec borsh length prefix from instruction data. +/// +/// The SDK client wraps serialized instruction data in Vec format +/// (4-byte little-endian length prefix + payload) for Anchor compatibility. +/// Pinocchio programs must strip it manually. +#[inline] +fn strip_vec_wrapper(data: &[u8]) -> Result<&[u8], ProgramError> { + if data.len() < 4 { + return Err(ProgramError::InvalidInstructionData); + } + let len = u32::from_le_bytes(data[..4].try_into().unwrap()) as usize; + if data.len() < 4 + len { + return Err(ProgramError::InvalidInstructionData); + } + Ok(&data[4..4 + len]) +} + +pinocchio::entrypoint!(process_instruction); + +pub fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> Result<(), ProgramError> { + if instruction_data.len() < 8 { + return Err(ProgramError::InvalidInstructionData); + } + + let (disc, data) = instruction_data.split_at(8); + let disc: [u8; 8] = disc.try_into().unwrap(); + + match disc { + discriminators::CREATE_PDA => process_create_pda(accounts, data), + discriminators::CREATE_ATA => process_create_ata(accounts, data), + discriminators::CREATE_TOKEN_VAULT => process_create_token_vault(accounts, data), + discriminators::CREATE_ZERO_COPY_RECORD => process_create_zero_copy_record(accounts, data), + discriminators::CREATE_MINT => process_create_mint(accounts, data), + discriminators::CREATE_TWO_MINTS => process_create_two_mints(accounts, data), + discriminators::CREATE_ALL => process_create_all(accounts, data), + discriminators::INITIALIZE_COMPRESSION_CONFIG => { + ProgramAccounts::process_initialize_config(accounts, data) + } + discriminators::UPDATE_COMPRESSION_CONFIG => { + ProgramAccounts::process_update_config(accounts, data) + } + discriminators::COMPRESS_ACCOUNTS_IDEMPOTENT => { + let inner = strip_vec_wrapper(data)?; + ProgramAccounts::process_compress(accounts, inner) + } + discriminators::DECOMPRESS_ACCOUNTS_IDEMPOTENT => { + let inner = strip_vec_wrapper(data)?; + ProgramAccounts::process_decompress(accounts, inner) + } + _ => Err(ProgramError::InvalidInstructionData), + } +} + +// ============================================================================ +// Instruction Handlers +// ============================================================================ + +fn process_create_pda(accounts: &[AccountInfo], data: &[u8]) -> Result<(), ProgramError> { + use borsh::BorshDeserialize; + use pda::accounts::{CreatePda, CreatePdaParams}; + + let params = + CreatePdaParams::deserialize(&mut &data[..]).map_err(|_| ProgramError::BorshIoError)?; + + let remaining_start = CreatePda::FIXED_LEN; + let (fixed_accounts, remaining_accounts) = accounts.split_at(remaining_start); + let mut ctx = CreatePda::parse(fixed_accounts, ¶ms)?; + + let has_pre_init = ctx + .light_pre_init(remaining_accounts, ¶ms) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + // Business logic: set account data + { + let mut account_data = ctx + .record + .try_borrow_mut_data() + .map_err(|_| ProgramError::AccountBorrowFailed)?; + let mut record = state::MinimalRecord::try_from_slice(&account_data[8..]) + .map_err(|_| ProgramError::BorshIoError)?; + record.owner = params.owner; + let serialized = borsh::to_vec(&record).map_err(|_| ProgramError::BorshIoError)?; + account_data[8..8 + serialized.len()].copy_from_slice(&serialized); + } + + ctx.light_finalize(remaining_accounts, ¶ms, has_pre_init) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + Ok(()) +} + +fn process_create_ata(accounts: &[AccountInfo], data: &[u8]) -> Result<(), ProgramError> { + use ata::accounts::{CreateAtaAccounts, CreateAtaParams}; + use borsh::BorshDeserialize; + + let params = + CreateAtaParams::deserialize(&mut &data[..]).map_err(|_| ProgramError::BorshIoError)?; + + let remaining_start = CreateAtaAccounts::FIXED_LEN; + let (fixed_accounts, remaining_accounts) = accounts.split_at(remaining_start); + let mut ctx = CreateAtaAccounts::parse(fixed_accounts)?; + + let has_pre_init = ctx + .light_pre_init(remaining_accounts, ¶ms) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + ctx.light_finalize(remaining_accounts, ¶ms, has_pre_init) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + Ok(()) +} + +fn process_create_token_vault(accounts: &[AccountInfo], data: &[u8]) -> Result<(), ProgramError> { + use borsh::BorshDeserialize; + use token_account::accounts::{CreateTokenVaultAccounts, CreateTokenVaultParams}; + + let params = CreateTokenVaultParams::deserialize(&mut &data[..]) + .map_err(|_| ProgramError::BorshIoError)?; + + let remaining_start = CreateTokenVaultAccounts::FIXED_LEN; + let (fixed_accounts, remaining_accounts) = accounts.split_at(remaining_start); + let mut ctx = CreateTokenVaultAccounts::parse(fixed_accounts)?; + + let has_pre_init = ctx + .light_pre_init(remaining_accounts, ¶ms) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + ctx.light_finalize(remaining_accounts, ¶ms, has_pre_init) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + Ok(()) +} + +fn process_create_zero_copy_record( + accounts: &[AccountInfo], + data: &[u8], +) -> Result<(), ProgramError> { + use account_loader::accounts::{CreateZeroCopyRecord, CreateZeroCopyRecordParams}; + use borsh::BorshDeserialize; + + let params = CreateZeroCopyRecordParams::deserialize(&mut &data[..]) + .map_err(|_| ProgramError::BorshIoError)?; + + let remaining_start = CreateZeroCopyRecord::FIXED_LEN; + let (fixed_accounts, remaining_accounts) = accounts.split_at(remaining_start); + let mut ctx = CreateZeroCopyRecord::parse(fixed_accounts, ¶ms)?; + + let has_pre_init = ctx + .light_pre_init(remaining_accounts, ¶ms) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + // Business logic: set zero-copy account data + { + let mut account_data = ctx + .record + .try_borrow_mut_data() + .map_err(|_| ProgramError::AccountBorrowFailed)?; + let record_bytes = + &mut account_data[8..8 + core::mem::size_of::()]; + let record: &mut state::ZeroCopyRecord = bytemuck::from_bytes_mut(record_bytes); + record.owner = params.owner; + } + + ctx.light_finalize(remaining_accounts, ¶ms, has_pre_init) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + Ok(()) +} + +fn process_create_mint(accounts: &[AccountInfo], data: &[u8]) -> Result<(), ProgramError> { + use borsh::BorshDeserialize; + use mint::accounts::{CreateMintAccounts, CreateMintParams}; + + let params = + CreateMintParams::deserialize(&mut &data[..]).map_err(|_| ProgramError::BorshIoError)?; + + let remaining_start = CreateMintAccounts::FIXED_LEN; + let (fixed_accounts, remaining_accounts) = accounts.split_at(remaining_start); + let mut ctx = CreateMintAccounts::parse(fixed_accounts, ¶ms)?; + + let has_pre_init = ctx + .light_pre_init(remaining_accounts, ¶ms) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + ctx.light_finalize(remaining_accounts, ¶ms, has_pre_init) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + Ok(()) +} + +fn process_create_two_mints(accounts: &[AccountInfo], data: &[u8]) -> Result<(), ProgramError> { + use borsh::BorshDeserialize; + use two_mints::accounts::{CreateTwoMintsAccounts, CreateTwoMintsParams}; + + let params = CreateTwoMintsParams::deserialize(&mut &data[..]) + .map_err(|_| ProgramError::BorshIoError)?; + + let remaining_start = CreateTwoMintsAccounts::FIXED_LEN; + let (fixed_accounts, remaining_accounts) = accounts.split_at(remaining_start); + let mut ctx = CreateTwoMintsAccounts::parse(fixed_accounts, ¶ms)?; + + let has_pre_init = ctx + .light_pre_init(remaining_accounts, ¶ms) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + ctx.light_finalize(remaining_accounts, ¶ms, has_pre_init) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + Ok(()) +} + +fn process_create_all(accounts: &[AccountInfo], data: &[u8]) -> Result<(), ProgramError> { + use all::accounts::{CreateAllAccounts, CreateAllParams}; + use borsh::BorshDeserialize; + + let params = + CreateAllParams::deserialize(&mut &data[..]).map_err(|_| ProgramError::BorshIoError)?; + + let remaining_start = CreateAllAccounts::FIXED_LEN; + let (fixed_accounts, remaining_accounts) = accounts.split_at(remaining_start); + let mut ctx = CreateAllAccounts::parse(fixed_accounts, ¶ms)?; + + let has_pre_init = ctx + .light_pre_init(remaining_accounts, ¶ms) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + // Business logic: set PDA data + { + let mut borsh_data = ctx + .borsh_record + .try_borrow_mut_data() + .map_err(|_| ProgramError::AccountBorrowFailed)?; + let mut borsh_record = state::MinimalRecord::try_from_slice(&borsh_data[8..]) + .map_err(|_| ProgramError::BorshIoError)?; + borsh_record.owner = params.owner; + let serialized = + borsh::to_vec(&borsh_record).map_err(|_| ProgramError::BorshIoError)?; + borsh_data[8..8 + serialized.len()].copy_from_slice(&serialized); + } + { + let mut zc_data = ctx + .zero_copy_record + .try_borrow_mut_data() + .map_err(|_| ProgramError::AccountBorrowFailed)?; + let record_bytes = + &mut zc_data[8..8 + core::mem::size_of::()]; + let record: &mut state::ZeroCopyRecord = bytemuck::from_bytes_mut(record_bytes); + record.owner = params.owner; + } + + ctx.light_finalize(remaining_accounts, ¶ms, has_pre_init) + .map_err(|e| ProgramError::Custom(u32::from(e)))?; + + Ok(()) +} diff --git a/sdk-tests/pinocchio-light-program-test/src/mint/accounts.rs b/sdk-tests/pinocchio-light-program-test/src/mint/accounts.rs new file mode 100644 index 0000000000..8f524b0a55 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/mint/accounts.rs @@ -0,0 +1,73 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::CreateAccountsProof; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError}; + +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] +pub struct CreateMintParams { + pub create_accounts_proof: CreateAccountsProof, + pub mint_signer_bump: u8, +} + +pub struct CreateMintAccounts<'a> { + pub payer: &'a AccountInfo, + pub authority: &'a AccountInfo, + pub mint_signer: &'a AccountInfo, + pub mint: &'a AccountInfo, + pub compressible_config: &'a AccountInfo, + pub rent_sponsor: &'a AccountInfo, + pub light_token_program: &'a AccountInfo, + pub cpi_authority: &'a AccountInfo, + pub system_program: &'a AccountInfo, +} + +impl<'a> CreateMintAccounts<'a> { + pub const FIXED_LEN: usize = 9; + + pub fn parse( + accounts: &'a [AccountInfo], + params: &CreateMintParams, + ) -> Result { + let payer = &accounts[0]; + let authority = &accounts[1]; + let mint_signer = &accounts[2]; + let mint = &accounts[3]; + let compressible_config = &accounts[4]; + let rent_sponsor = &accounts[5]; + let light_token_program = &accounts[6]; + let cpi_authority = &accounts[7]; + let system_program = &accounts[8]; + + if !payer.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + if !authority.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + // Validate mint_signer PDA + { + let authority_key = authority.key(); + let seeds: &[&[u8]] = &[crate::MINT_SIGNER_SEED_A, authority_key]; + let (expected_pda, expected_bump) = + pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if mint_signer.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + if expected_bump != params.mint_signer_bump { + return Err(ProgramError::InvalidSeeds); + } + } + + Ok(Self { + payer, + authority, + mint_signer, + mint, + compressible_config, + rent_sponsor, + light_token_program, + cpi_authority, + system_program, + }) + } +} diff --git a/sdk-tests/pinocchio-light-program-test/src/mint/derived.rs b/sdk-tests/pinocchio-light-program-test/src/mint/derived.rs new file mode 100644 index 0000000000..e56e3cf250 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/mint/derived.rs @@ -0,0 +1,123 @@ +use light_account_pinocchio::{ + derive_mint_compressed_address, find_mint_address, get_output_queue_next_index, + invoke_create_mints, CpiAccounts, CpiAccountsConfig, CreateMintsInfraAccounts, + CreateMintsParams as SdkCreateMintsParams, LightFinalize, LightPreInit, LightSdkTypesError, + PackedAddressTreeInfoExt, SingleMintParams, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, +}; +use pinocchio::account_info::AccountInfo; + +use super::accounts::{CreateMintAccounts, CreateMintParams}; + +impl LightPreInit for CreateMintAccounts<'_> { + fn light_pre_init( + &mut self, + remaining_accounts: &[AccountInfo], + params: &CreateMintParams, + ) -> std::result::Result { + let inner = || -> std::result::Result { + let system_accounts_offset = + params.create_accounts_proof.system_accounts_offset as usize; + if remaining_accounts.len() < system_accounts_offset { + return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); + } + let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); + let cpi_accounts = CpiAccounts::new_with_config( + self.payer, + &remaining_accounts[system_accounts_offset..], + config, + ); + + let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; + let address_tree_pubkey = address_tree_info + .get_tree_pubkey(&cpi_accounts) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + + const NUM_LIGHT_MINTS: usize = 1; + const NUM_LIGHT_PDAS: usize = 0; + #[allow(clippy::absurd_extreme_comparisons)] + const WITH_CPI_CONTEXT: bool = NUM_LIGHT_PDAS > 0 && NUM_LIGHT_MINTS > 0; + + let authority = *self.authority.key(); + let mint_signer_key = *self.mint_signer.key(); + + let (mint_pda, mint_bump) = find_mint_address(&mint_signer_key); + let compression_address = + derive_mint_compressed_address(&mint_signer_key, &address_tree_pubkey); + + let mint_signer_seeds: &[&[u8]] = &[ + crate::MINT_SIGNER_SEED_A, + authority.as_ref(), + &[params.mint_signer_bump], + ]; + + let sdk_mints: [SingleMintParams<'_>; NUM_LIGHT_MINTS] = [SingleMintParams { + decimals: 9, + address_merkle_tree_root_index: address_tree_info.root_index, + mint_authority: authority, + compression_address, + mint: mint_pda, + bump: mint_bump, + freeze_authority: None, + mint_seed_pubkey: mint_signer_key, + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_seeds), + token_metadata: None, + }]; + + let state_tree_index = params + .create_accounts_proof + .state_tree_index + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + + let proof = params + .create_accounts_proof + .proof + .0 + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + + let output_queue_index = params.create_accounts_proof.output_state_tree_index; + let output_queue = cpi_accounts.get_tree_account_info(output_queue_index as usize)?; + let base_leaf_index = get_output_queue_next_index(output_queue)?; + + let sdk_params = SdkCreateMintsParams { + mints: &sdk_mints, + proof, + rent_payment: DEFAULT_RENT_PAYMENT, + write_top_up: DEFAULT_WRITE_TOP_UP, + cpi_context_offset: NUM_LIGHT_PDAS as u8, + output_queue_index, + address_tree_index: address_tree_info.address_merkle_tree_pubkey_index, + state_tree_index, + base_leaf_index, + }; + + // Use self.mint_signer and self.mint as slices + let mint_signers = core::slice::from_ref(self.mint_signer); + let mints = core::slice::from_ref(self.mint); + + let infra = CreateMintsInfraAccounts { + fee_payer: self.payer, + compressible_config: self.compressible_config, + rent_sponsor: self.rent_sponsor, + cpi_authority: self.cpi_authority, + }; + + invoke_create_mints(mint_signers, mints, sdk_params, infra, &cpi_accounts) + .map_err(|e| LightSdkTypesError::ProgramError(e.into()))?; + + Ok(WITH_CPI_CONTEXT) + }; + inner() + } +} + +impl LightFinalize for CreateMintAccounts<'_> { + fn light_finalize( + &mut self, + _remaining_accounts: &[AccountInfo], + _params: &CreateMintParams, + _has_pre_init: bool, + ) -> std::result::Result<(), LightSdkTypesError> { + Ok(()) + } +} diff --git a/sdk-tests/pinocchio-light-program-test/src/mint/mod.rs b/sdk-tests/pinocchio-light-program-test/src/mint/mod.rs new file mode 100644 index 0000000000..5e2a6edaeb --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/mint/mod.rs @@ -0,0 +1,4 @@ +pub mod accounts; +mod derived; + +pub use accounts::*; diff --git a/sdk-tests/pinocchio-light-program-test/src/pda/accounts.rs b/sdk-tests/pinocchio-light-program-test/src/pda/accounts.rs new file mode 100644 index 0000000000..d9aed1a0f4 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/pda/accounts.rs @@ -0,0 +1,88 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::CreateAccountsProof; +use pinocchio::{ + account_info::AccountInfo, + instruction::{Seed, Signer}, + program_error::ProgramError, + sysvars::Sysvar, +}; + +use crate::state::MinimalRecord; + +#[derive(BorshSerialize, BorshDeserialize, Clone)] +pub struct CreatePdaParams { + pub create_accounts_proof: CreateAccountsProof, + pub owner: [u8; 32], +} + +pub struct CreatePda<'a> { + pub fee_payer: &'a AccountInfo, + pub compression_config: &'a AccountInfo, + pub pda_rent_sponsor: &'a AccountInfo, + pub record: &'a AccountInfo, + pub system_program: &'a AccountInfo, +} + +impl<'a> CreatePda<'a> { + pub const FIXED_LEN: usize = 5; + + pub fn parse( + accounts: &'a [AccountInfo], + params: &CreatePdaParams, + ) -> Result { + let fee_payer = &accounts[0]; + let compression_config = &accounts[1]; + let pda_rent_sponsor = &accounts[2]; + let record = &accounts[3]; + let system_program = &accounts[4]; + + if !fee_payer.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + // Derive PDA and create account + let space = 8 + MinimalRecord::INIT_SPACE; + let seeds: &[&[u8]] = &[b"minimal_record", ¶ms.owner]; + let (expected_pda, bump) = pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if record.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + + let rent = pinocchio::sysvars::rent::Rent::get() + .map_err(|_| ProgramError::UnsupportedSysvar)?; + let lamports = rent.minimum_balance(space); + + let bump_bytes = [bump]; + let seed_array = [ + Seed::from(b"minimal_record" as &[u8]), + Seed::from(params.owner.as_ref()), + Seed::from(bump_bytes.as_ref()), + ]; + let signer = Signer::from(&seed_array); + pinocchio_system::instructions::CreateAccount { + from: fee_payer, + to: record, + lamports, + space: space as u64, + owner: &crate::ID, + } + .invoke_signed(&[signer])?; + + // Write LIGHT_DISCRIMINATOR to first 8 bytes + { + use light_account_pinocchio::LightDiscriminator; + let mut data = record + .try_borrow_mut_data() + .map_err(|_| ProgramError::AccountBorrowFailed)?; + data[..8].copy_from_slice(&MinimalRecord::LIGHT_DISCRIMINATOR); + } + + Ok(Self { + fee_payer, + compression_config, + pda_rent_sponsor, + record, + system_program, + }) + } +} diff --git a/sdk-tests/pinocchio-light-program-test/src/pda/derived_accounts.rs b/sdk-tests/pinocchio-light-program-test/src/pda/derived_accounts.rs new file mode 100644 index 0000000000..6e83c558a5 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/pda/derived_accounts.rs @@ -0,0 +1,112 @@ +use borsh::BorshDeserialize; +use light_account_pinocchio::{ + prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, LightFinalize, + LightPreInit, LightSdkTypesError, PackedAddressTreeInfoExt, +}; +use light_compressed_account::instruction_data::{ + cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, +}; +use pinocchio::account_info::AccountInfo; + +use super::accounts::{CreatePda, CreatePdaParams}; + +impl LightPreInit for CreatePda<'_> { + fn light_pre_init( + &mut self, + remaining_accounts: &[AccountInfo], + params: &CreatePdaParams, + ) -> std::result::Result { + let inner = || -> std::result::Result { + use light_account_pinocchio::{InvokeLightSystemProgram, LightAccount, LightConfig}; + use pinocchio::sysvars::{clock::Clock, Sysvar}; + + let system_accounts_offset = + params.create_accounts_proof.system_accounts_offset as usize; + if remaining_accounts.len() < system_accounts_offset { + return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); + } + let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); + let cpi_accounts = CpiAccounts::new_with_config( + self.fee_payer, + &remaining_accounts[system_accounts_offset..], + config, + ); + + let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; + let address_tree_pubkey = address_tree_info + .get_tree_pubkey(&cpi_accounts) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + let output_tree_index = params.create_accounts_proof.output_state_tree_index; + let current_account_index: u8 = 0; + const WITH_CPI_CONTEXT: bool = false; + let cpi_context = CompressedCpiContext::default(); + const NUM_LIGHT_PDAS: usize = 1; + let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); + let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); + + let light_config = LightConfig::load_checked(self.compression_config, &crate::ID) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + let current_slot = Clock::get() + .map_err(|_| LightSdkTypesError::InvalidInstructionData)? + .slot; + + let record_key = *self.record.key(); + prepare_compressed_account_on_init( + &record_key, + &address_tree_pubkey, + address_tree_info, + output_tree_index, + current_account_index, + &crate::ID, + &mut new_address_params, + &mut account_infos, + )?; + + // Set compression_info on the record via borsh deserialize/serialize + { + let mut account_data = self + .record + .try_borrow_mut_data() + .map_err(|_| LightSdkTypesError::Borsh)?; + let mut record = + crate::state::MinimalRecord::try_from_slice(&account_data[8..]) + .map_err(|_| LightSdkTypesError::Borsh)?; + record.set_decompressed(&light_config, current_slot); + let serialized = borsh::to_vec(&record) + .map_err(|_| LightSdkTypesError::Borsh)?; + account_data[8..8 + serialized.len()].copy_from_slice(&serialized); + } + + let instruction_data = InstructionDataInvokeCpiWithAccountInfo { + mode: 1, + bump: crate::LIGHT_CPI_SIGNER.bump, + invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), + compress_or_decompress_lamports: 0, + is_compress: false, + with_cpi_context: WITH_CPI_CONTEXT, + with_transaction_hash: false, + cpi_context, + proof: params.create_accounts_proof.proof.0, + new_address_params, + account_infos, + read_only_addresses: vec![], + read_only_accounts: vec![], + }; + + instruction_data.invoke(cpi_accounts)?; + Ok(false) + }; + inner() + } +} + +impl LightFinalize for CreatePda<'_> { + fn light_finalize( + &mut self, + _remaining_accounts: &[AccountInfo], + _params: &CreatePdaParams, + _has_pre_init: bool, + ) -> std::result::Result<(), LightSdkTypesError> { + Ok(()) + } +} diff --git a/sdk-tests/pinocchio-light-program-test/src/pda/mod.rs b/sdk-tests/pinocchio-light-program-test/src/pda/mod.rs new file mode 100644 index 0000000000..bfb5a447e8 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/pda/mod.rs @@ -0,0 +1,4 @@ +pub mod accounts; +mod derived_accounts; + +pub use accounts::*; diff --git a/sdk-tests/pinocchio-light-program-test/src/state.rs b/sdk-tests/pinocchio-light-program-test/src/state.rs new file mode 100644 index 0000000000..dd8d155366 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/state.rs @@ -0,0 +1,49 @@ +//! State module for pinocchio-light-program-test. +//! +//! Pinocchio-compatible account types using BorshSerialize/BorshDeserialize +//! instead of Anchor's #[account] macro. + +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::{CompressionInfo, LightDiscriminator, LightHasherSha}; + +/// Minimal record struct for testing PDA creation. +/// Contains compression_info and one field. +#[derive( + Default, Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize, LightDiscriminator, + LightHasherSha, +)] +#[repr(C)] +pub struct MinimalRecord { + pub compression_info: CompressionInfo, + pub owner: [u8; 32], +} + +impl MinimalRecord { + pub const INIT_SPACE: usize = core::mem::size_of::() + 32; +} + +/// A zero-copy account using Pod serialization. +/// Used for efficient on-chain zero-copy access. +#[derive( + Default, + Debug, + Copy, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + LightDiscriminator, + LightHasherSha, + bytemuck::Pod, + bytemuck::Zeroable, +)] +#[repr(C)] +pub struct ZeroCopyRecord { + pub compression_info: CompressionInfo, + pub owner: [u8; 32], + pub counter: u64, +} + +impl ZeroCopyRecord { + pub const INIT_SPACE: usize = core::mem::size_of::(); +} diff --git a/sdk-tests/pinocchio-light-program-test/src/token_account/accounts.rs b/sdk-tests/pinocchio-light-program-test/src/token_account/accounts.rs new file mode 100644 index 0000000000..5ef226de06 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/token_account/accounts.rs @@ -0,0 +1,58 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError}; + +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] +pub struct CreateTokenVaultParams { + pub vault_bump: u8, +} + +pub struct CreateTokenVaultAccounts<'a> { + pub payer: &'a AccountInfo, + pub mint: &'a AccountInfo, + pub vault_owner: &'a AccountInfo, + pub token_vault: &'a AccountInfo, + pub compressible_config: &'a AccountInfo, + pub rent_sponsor: &'a AccountInfo, + pub light_token_program: &'a AccountInfo, + pub system_program: &'a AccountInfo, +} + +impl<'a> CreateTokenVaultAccounts<'a> { + pub const FIXED_LEN: usize = 8; + + pub fn parse(accounts: &'a [AccountInfo]) -> Result { + let payer = &accounts[0]; + let mint = &accounts[1]; + let vault_owner = &accounts[2]; + let token_vault = &accounts[3]; + let compressible_config = &accounts[4]; + let rent_sponsor = &accounts[5]; + let light_token_program = &accounts[6]; + let system_program = &accounts[7]; + + if !payer.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + // Validate token_vault PDA + { + let mint_key = mint.key(); + let seeds: &[&[u8]] = &[crate::VAULT_SEED, mint_key]; + let (expected_pda, _bump) = pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if token_vault.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + } + + Ok(Self { + payer, + mint, + vault_owner, + token_vault, + compressible_config, + rent_sponsor, + light_token_program, + system_program, + }) + } +} diff --git a/sdk-tests/pinocchio-light-program-test/src/token_account/derived.rs b/sdk-tests/pinocchio-light-program-test/src/token_account/derived.rs new file mode 100644 index 0000000000..0cb3791d09 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/token_account/derived.rs @@ -0,0 +1,48 @@ +use light_account_pinocchio::{ + CreateTokenAccountCpi, LightFinalize, LightPreInit, LightSdkTypesError, +}; +use pinocchio::account_info::AccountInfo; + +use super::accounts::{CreateTokenVaultAccounts, CreateTokenVaultParams}; + +impl LightPreInit for CreateTokenVaultAccounts<'_> { + fn light_pre_init( + &mut self, + _remaining_accounts: &[AccountInfo], + params: &CreateTokenVaultParams, + ) -> std::result::Result { + let inner = || -> std::result::Result { + let mint_key = *self.mint.key(); + let vault_seeds: &[&[u8]] = + &[crate::VAULT_SEED, mint_key.as_ref(), &[params.vault_bump]]; + + CreateTokenAccountCpi { + payer: self.payer, + account: self.token_vault, + mint: self.mint, + owner: *self.vault_owner.key(), + } + .rent_free( + self.compressible_config, + self.rent_sponsor, + self.system_program, + &crate::ID, + ) + .invoke_signed(vault_seeds)?; + + Ok(false) + }; + inner() + } +} + +impl LightFinalize for CreateTokenVaultAccounts<'_> { + fn light_finalize( + &mut self, + _remaining_accounts: &[AccountInfo], + _params: &CreateTokenVaultParams, + _has_pre_init: bool, + ) -> std::result::Result<(), LightSdkTypesError> { + Ok(()) + } +} diff --git a/sdk-tests/pinocchio-light-program-test/src/token_account/mod.rs b/sdk-tests/pinocchio-light-program-test/src/token_account/mod.rs new file mode 100644 index 0000000000..5e2a6edaeb --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/token_account/mod.rs @@ -0,0 +1,4 @@ +pub mod accounts; +mod derived; + +pub use accounts::*; diff --git a/sdk-tests/pinocchio-light-program-test/src/two_mints/accounts.rs b/sdk-tests/pinocchio-light-program-test/src/two_mints/accounts.rs new file mode 100644 index 0000000000..c2b925bc26 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/two_mints/accounts.rs @@ -0,0 +1,98 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use light_account_pinocchio::CreateAccountsProof; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError}; + +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] +pub struct CreateTwoMintsParams { + pub create_accounts_proof: CreateAccountsProof, + pub mint_signer_bump_a: u8, + pub mint_signer_bump_b: u8, +} + +pub struct CreateTwoMintsAccounts<'a> { + pub payer: &'a AccountInfo, + pub authority: &'a AccountInfo, + pub mint_signer_a: &'a AccountInfo, + pub mint_signer_b: &'a AccountInfo, + pub mint_a: &'a AccountInfo, + pub mint_b: &'a AccountInfo, + pub compressible_config: &'a AccountInfo, + pub rent_sponsor: &'a AccountInfo, + pub light_token_program: &'a AccountInfo, + pub cpi_authority: &'a AccountInfo, + pub system_program: &'a AccountInfo, + pub mint_signers_slice: &'a [AccountInfo], + pub mints_slice: &'a [AccountInfo], +} + +impl<'a> CreateTwoMintsAccounts<'a> { + pub const FIXED_LEN: usize = 11; + + pub fn parse( + accounts: &'a [AccountInfo], + params: &CreateTwoMintsParams, + ) -> Result { + let payer = &accounts[0]; + let authority = &accounts[1]; + let mint_signer_a = &accounts[2]; + let mint_signer_b = &accounts[3]; + let mint_a = &accounts[4]; + let mint_b = &accounts[5]; + let compressible_config = &accounts[6]; + let rent_sponsor = &accounts[7]; + let light_token_program = &accounts[8]; + let cpi_authority = &accounts[9]; + let system_program = &accounts[10]; + + if !payer.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + if !authority.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + // Validate mint_signer_a PDA + { + let authority_key = authority.key(); + let seeds: &[&[u8]] = &[crate::MINT_SIGNER_SEED_A, authority_key]; + let (expected_pda, expected_bump) = + pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if mint_signer_a.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + if expected_bump != params.mint_signer_bump_a { + return Err(ProgramError::InvalidSeeds); + } + } + + // Validate mint_signer_b PDA + { + let authority_key = authority.key(); + let seeds: &[&[u8]] = &[crate::MINT_SIGNER_SEED_B, authority_key]; + let (expected_pda, expected_bump) = + pinocchio::pubkey::find_program_address(seeds, &crate::ID); + if mint_signer_b.key() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + if expected_bump != params.mint_signer_bump_b { + return Err(ProgramError::InvalidSeeds); + } + } + + Ok(Self { + payer, + authority, + mint_signer_a, + mint_signer_b, + mint_a, + mint_b, + compressible_config, + rent_sponsor, + light_token_program, + cpi_authority, + system_program, + mint_signers_slice: &accounts[2..4], + mints_slice: &accounts[4..6], + }) + } +} diff --git a/sdk-tests/pinocchio-light-program-test/src/two_mints/derived.rs b/sdk-tests/pinocchio-light-program-test/src/two_mints/derived.rs new file mode 100644 index 0000000000..7115ccb82d --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/two_mints/derived.rs @@ -0,0 +1,150 @@ +use light_account_pinocchio::{ + derive_mint_compressed_address, find_mint_address, get_output_queue_next_index, + invoke_create_mints, CpiAccounts, CpiAccountsConfig, CreateMintsInfraAccounts, + CreateMintsParams as SdkCreateMintsParams, LightFinalize, LightPreInit, LightSdkTypesError, + PackedAddressTreeInfoExt, SingleMintParams, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, +}; +use pinocchio::account_info::AccountInfo; + +use super::accounts::{CreateTwoMintsAccounts, CreateTwoMintsParams}; + +impl LightPreInit for CreateTwoMintsAccounts<'_> { + fn light_pre_init( + &mut self, + remaining_accounts: &[AccountInfo], + params: &CreateTwoMintsParams, + ) -> std::result::Result { + let inner = || -> std::result::Result { + let system_accounts_offset = + params.create_accounts_proof.system_accounts_offset as usize; + if remaining_accounts.len() < system_accounts_offset { + return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); + } + let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); + let cpi_accounts = CpiAccounts::new_with_config( + self.payer, + &remaining_accounts[system_accounts_offset..], + config, + ); + + let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; + let address_tree_pubkey = address_tree_info + .get_tree_pubkey(&cpi_accounts) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + + const NUM_LIGHT_MINTS: usize = 2; + const NUM_LIGHT_PDAS: usize = 0; + #[allow(clippy::absurd_extreme_comparisons)] + const WITH_CPI_CONTEXT: bool = NUM_LIGHT_PDAS > 0 && NUM_LIGHT_MINTS > 0; + + let authority = *self.authority.key(); + let mint_signer_a_key = *self.mint_signer_a.key(); + let mint_signer_b_key = *self.mint_signer_b.key(); + + let (mint_a_pda, mint_a_bump) = find_mint_address(&mint_signer_a_key); + let (mint_b_pda, mint_b_bump) = find_mint_address(&mint_signer_b_key); + + let compression_address_a = + derive_mint_compressed_address(&mint_signer_a_key, &address_tree_pubkey); + let compression_address_b = + derive_mint_compressed_address(&mint_signer_b_key, &address_tree_pubkey); + + let mint_signer_a_seeds: &[&[u8]] = &[ + crate::MINT_SIGNER_SEED_A, + authority.as_ref(), + &[params.mint_signer_bump_a], + ]; + let mint_signer_b_seeds: &[&[u8]] = &[ + crate::MINT_SIGNER_SEED_B, + authority.as_ref(), + &[params.mint_signer_bump_b], + ]; + + let sdk_mints: [SingleMintParams<'_>; NUM_LIGHT_MINTS] = [ + SingleMintParams { + decimals: 9, + address_merkle_tree_root_index: address_tree_info.root_index, + mint_authority: authority, + compression_address: compression_address_a, + mint: mint_a_pda, + bump: mint_a_bump, + freeze_authority: None, + mint_seed_pubkey: mint_signer_a_key, + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_a_seeds), + token_metadata: None, + }, + SingleMintParams { + decimals: 6, + address_merkle_tree_root_index: address_tree_info.root_index, + mint_authority: authority, + compression_address: compression_address_b, + mint: mint_b_pda, + bump: mint_b_bump, + freeze_authority: None, + mint_seed_pubkey: mint_signer_b_key, + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_b_seeds), + token_metadata: None, + }, + ]; + + let state_tree_index = params + .create_accounts_proof + .state_tree_index + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + + let proof = params + .create_accounts_proof + .proof + .0 + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + + let output_queue_index = params.create_accounts_proof.output_state_tree_index; + let output_queue = cpi_accounts.get_tree_account_info(output_queue_index as usize)?; + let base_leaf_index = get_output_queue_next_index(output_queue)?; + + let sdk_params = SdkCreateMintsParams { + mints: &sdk_mints, + proof, + rent_payment: DEFAULT_RENT_PAYMENT, + write_top_up: DEFAULT_WRITE_TOP_UP, + cpi_context_offset: NUM_LIGHT_PDAS as u8, + output_queue_index, + address_tree_index: address_tree_info.address_merkle_tree_pubkey_index, + state_tree_index, + base_leaf_index, + }; + + let infra = CreateMintsInfraAccounts { + fee_payer: self.payer, + compressible_config: self.compressible_config, + rent_sponsor: self.rent_sponsor, + cpi_authority: self.cpi_authority, + }; + + invoke_create_mints( + self.mint_signers_slice, + self.mints_slice, + sdk_params, + infra, + &cpi_accounts, + ) + .map_err(|e| LightSdkTypesError::ProgramError(e.into()))?; + + Ok(WITH_CPI_CONTEXT) + }; + inner() + } +} + +impl LightFinalize for CreateTwoMintsAccounts<'_> { + fn light_finalize( + &mut self, + _remaining_accounts: &[AccountInfo], + _params: &CreateTwoMintsParams, + _has_pre_init: bool, + ) -> std::result::Result<(), LightSdkTypesError> { + Ok(()) + } +} diff --git a/sdk-tests/pinocchio-light-program-test/src/two_mints/mod.rs b/sdk-tests/pinocchio-light-program-test/src/two_mints/mod.rs new file mode 100644 index 0000000000..5e2a6edaeb --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/src/two_mints/mod.rs @@ -0,0 +1,4 @@ +pub mod accounts; +mod derived; + +pub use accounts::*; diff --git a/sdk-tests/pinocchio-light-program-test/tests/shared/mod.rs b/sdk-tests/pinocchio-light-program-test/tests/shared/mod.rs new file mode 100644 index 0000000000..e58f09d0fb --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/tests/shared/mod.rs @@ -0,0 +1,219 @@ +#![allow(dead_code)] + +use light_account::derive_rent_sponsor_pda; +use light_client::interface::InitializeRentFreeConfig; +use light_program_test::{ + program_test::{setup_mock_program_data, LightProgramTest}, + Indexer, ProgramTestConfig, Rpc, +}; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +/// Shared test environment with initialized compression config. +pub struct TestEnv { + pub rpc: LightProgramTest, + pub payer: Keypair, + pub program_id: Pubkey, + pub config_pda: Pubkey, + pub rent_sponsor: Pubkey, +} + +/// Sets up a test environment with program, config, and rent sponsor initialized. +pub async fn setup_test_env() -> TestEnv { + let program_id = Pubkey::new_from_array(pinocchio_light_program_test::ID); + let mut config = + ProgramTestConfig::new_v2(true, Some(vec![("pinocchio_light_program_test", program_id)])); + config = config.with_light_protocol_events(); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); + + let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id); + + let (init_config_ix, config_pda) = InitializeRentFreeConfig::new( + &program_id, + &payer.pubkey(), + &program_data_pda, + rent_sponsor, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .expect("Initialize config should succeed"); + + TestEnv { + rpc, + payer, + program_id, + config_pda, + rent_sponsor, + } +} + +/// Creates a compressed mint using the ctoken SDK. +/// Returns (mint_pda, mint_seed_keypair). +pub async fn setup_create_mint( + rpc: &mut (impl Rpc + Indexer), + payer: &Keypair, + mint_authority: Pubkey, + decimals: u8, +) -> (Pubkey, Keypair) { + use light_token::instruction::{CreateMint, CreateMintParams}; + + let mint_seed = Keypair::new(); + let address_tree = rpc.get_address_tree_v2(); + let output_queue = rpc.get_random_state_tree_info().unwrap().queue; + + let compression_address = light_token::instruction::derive_mint_compressed_address( + &mint_seed.pubkey(), + &address_tree.tree, + ); + + let (mint, bump) = light_token::instruction::find_mint_address(&mint_seed.pubkey()); + + let rpc_result = rpc + .get_validity_proof( + vec![], + vec![light_client::indexer::AddressWithTree { + address: compression_address, + tree: address_tree.tree, + }], + None, + ) + .await + .unwrap() + .value; + + let params = CreateMintParams { + decimals, + address_merkle_tree_root_index: rpc_result.addresses[0].root_index, + mint_authority, + proof: rpc_result.proof.0.unwrap(), + compression_address, + mint, + bump, + freeze_authority: None, + extensions: None, + rent_payment: 16, + write_top_up: 766, + }; + + let create_mint_builder = CreateMint::new( + params, + mint_seed.pubkey(), + payer.pubkey(), + address_tree.tree, + output_queue, + ); + let instruction = create_mint_builder.instruction().unwrap(); + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[payer, &mint_seed]) + .await + .unwrap(); + + (mint, mint_seed) +} + +pub async fn assert_onchain_exists(rpc: &mut LightProgramTest, pda: &Pubkey, name: &str) { + assert!( + rpc.get_account(*pda).await.unwrap().is_some(), + "{} account ({}) should exist on-chain", + name, + pda + ); +} + +pub async fn assert_onchain_closed(rpc: &mut LightProgramTest, pda: &Pubkey, name: &str) { + let acc = rpc.get_account(*pda).await.unwrap(); + assert!( + acc.is_none() || acc.unwrap().lamports == 0, + "{} account ({}) should be closed", + name, + pda + ); +} + +pub async fn assert_compressed_token_exists( + rpc: &mut LightProgramTest, + owner: &Pubkey, + expected_amount: u64, + name: &str, +) { + let accs = rpc + .get_compressed_token_accounts_by_owner(owner, None, None) + .await + .unwrap() + .value + .items; + assert!( + !accs.is_empty(), + "{} compressed token account should exist for owner {}", + name, + owner + ); + assert_eq!( + accs[0].token.amount, expected_amount, + "{} token amount mismatch", + name + ); +} + +pub async fn assert_rent_sponsor_paid_for_accounts( + rpc: &mut LightProgramTest, + rent_sponsor: &Pubkey, + rent_sponsor_balance_before: u64, + created_accounts: &[Pubkey], +) { + let rent_sponsor_balance_after = rpc + .get_account(*rent_sponsor) + .await + .expect("get rent sponsor account") + .map(|a| a.lamports) + .unwrap_or(0); + + let mut total_account_lamports = 0u64; + for account in created_accounts { + let account_lamports = rpc + .get_account(*account) + .await + .expect("get created account") + .map(|a| a.lamports) + .unwrap_or(0); + total_account_lamports += account_lamports; + } + + let rent_sponsor_paid = rent_sponsor_balance_before.saturating_sub(rent_sponsor_balance_after); + + assert!( + rent_sponsor_paid >= total_account_lamports, + "Rent sponsor should have paid at least {} lamports for accounts, but only paid {}. \ + Before: {}, After: {}", + total_account_lamports, + rent_sponsor_paid, + rent_sponsor_balance_before, + rent_sponsor_balance_after + ); +} + +pub fn expected_compression_info( + actual: &light_account::CompressionInfo, +) -> light_account::CompressionInfo { + *actual +} + +pub fn parse_token(data: &[u8]) -> light_token_interface::state::token::Token { + borsh::BorshDeserialize::deserialize(&mut &data[..]).unwrap() +} + +/// Build instruction data: discriminator + borsh-serialized params. +pub fn build_instruction_data(disc: &[u8; 8], params: &T) -> Vec { + let mut data = Vec::new(); + data.extend_from_slice(disc); + borsh::BorshSerialize::serialize(params, &mut data).unwrap(); + data +} diff --git a/sdk-tests/pinocchio-light-program-test/tests/stress_test.rs b/sdk-tests/pinocchio-light-program-test/tests/stress_test.rs new file mode 100644 index 0000000000..108a1a1a92 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/tests/stress_test.rs @@ -0,0 +1,480 @@ +/// Stress test: 20-iteration compression/decompression cycles for all account types. +/// +/// Tests repeated cycles of: +/// 1. Decompress all accounts +/// 2. Assert cached state matches on-chain state +/// 3. Update cache from on-chain state +/// 4. Compress all accounts (warp forward) +mod shared; + +use light_account_pinocchio::token::TokenDataWithSeeds; +use light_batched_merkle_tree::{ + initialize_address_tree::InitAddressTreeAccountsInstructionData, + initialize_state_tree::InitStateTreeAccountsInstructionData, +}; +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterface, AccountInterfaceExt, + AccountSpec, ColdContext, CreateAccountsProofInput, PdaSpec, +}; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::{ + program_test::{setup_mock_program_data, LightProgramTest, TestRpc}, + ProgramTestConfig, Rpc, +}; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_token_interface::state::{token::Token, Mint}; +use pinocchio_light_program_test::{ + all::accounts::CreateAllParams, discriminators, LightAccountVariant, MinimalRecord, + MinimalRecordSeeds, VaultSeeds, ZeroCopyRecord, ZeroCopyRecordSeeds, MINT_SIGNER_SEED_A, + RECORD_SEED, VAULT_AUTH_SEED, VAULT_SEED, +}; +use solana_instruction::{AccountMeta, Instruction}; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +/// Stores all derived PDAs +#[allow(dead_code)] +struct TestPdas { + record: Pubkey, + zc_record: Pubkey, + ata: Pubkey, + ata_owner: Pubkey, + vault: Pubkey, + vault_owner: Pubkey, + mint: Pubkey, +} + +/// Cached state for accounts that go through the compress/decompress cycle. +#[derive(Clone)] +struct CachedState { + record: MinimalRecord, + zc_record: ZeroCopyRecord, + ata_token: Token, + vault_token: Token, + owner: [u8; 32], +} + +/// Test context +struct StressTestContext { + rpc: LightProgramTest, + payer: Keypair, + config_pda: Pubkey, + program_id: Pubkey, +} + +fn parse_token(data: &[u8]) -> Token { + borsh::BorshDeserialize::deserialize(&mut &data[..]).unwrap() +} + +/// Setup environment with larger queues for stress test +async fn setup() -> (StressTestContext, TestPdas) { + let program_id = Pubkey::new_from_array(pinocchio_light_program_test::ID); + let mut config = ProgramTestConfig::new_v2( + true, + Some(vec![("pinocchio_light_program_test", program_id)]), + ) + .with_light_protocol_events(); + config.v2_state_tree_config = Some(InitStateTreeAccountsInstructionData::e2e_test_default()); + config.v2_address_tree_config = + Some(InitAddressTreeAccountsInstructionData::e2e_test_default()); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); + + let (rent_sponsor, _) = light_account::derive_rent_sponsor_pda(&program_id); + + let (init_config_ix, config_pda) = light_client::interface::InitializeRentFreeConfig::new( + &program_id, + &payer.pubkey(), + &program_data_pda, + rent_sponsor, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .expect("Initialize config should succeed"); + + let owner = Keypair::new().pubkey(); + let authority = Keypair::new(); + + // Derive all PDAs + let (record_pda, _) = + Pubkey::find_program_address(&[b"minimal_record", owner.as_ref()], &program_id); + let (zc_record_pda, _) = + Pubkey::find_program_address(&[RECORD_SEED, owner.as_ref()], &program_id); + + // Mint signer PDA + let (mint_signer, mint_signer_bump) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED_A, authority.pubkey().as_ref()], + &program_id, + ); + let (mint_pda, _) = light_token::instruction::find_mint_address(&mint_signer); + + // Token vault PDA (uses the mint we're creating) + let (vault_owner, _) = Pubkey::find_program_address(&[VAULT_AUTH_SEED], &program_id); + let (vault, vault_bump) = + Pubkey::find_program_address(&[VAULT_SEED, mint_pda.as_ref()], &program_id); + + // ATA (uses the mint we're creating) + let ata_owner = payer.pubkey(); + let (ata, _) = light_token::instruction::derive_token_ata(&ata_owner, &mint_pda); + + // Create all accounts in one instruction + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![ + CreateAccountsProofInput::pda(record_pda), + CreateAccountsProofInput::pda(zc_record_pda), + CreateAccountsProofInput::mint(mint_signer), + ], + ) + .await + .unwrap(); + + let params = CreateAllParams { + create_accounts_proof: proof_result.create_accounts_proof, + owner: owner.to_bytes(), + mint_signer_bump, + token_vault_bump: vault_bump, + }; + + // Account order per all/accounts.rs + let accounts = vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(authority.pubkey(), true), + AccountMeta::new_readonly(config_pda, false), + AccountMeta::new(record_pda, false), + AccountMeta::new(zc_record_pda, false), + AccountMeta::new_readonly(mint_signer, false), + AccountMeta::new(mint_pda, false), + AccountMeta::new(vault, false), + AccountMeta::new_readonly(vault_owner, false), + AccountMeta::new_readonly(ata_owner, false), + AccountMeta::new(ata, false), + AccountMeta::new_readonly(LIGHT_TOKEN_CONFIG, false), + AccountMeta::new(LIGHT_TOKEN_RENT_SPONSOR, false), + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID.into(), false), + AccountMeta::new_readonly(light_token_types::CPI_AUTHORITY_PDA.into(), false), + AccountMeta::new_readonly(solana_sdk::system_program::ID, false), + ]; + + let instruction = Instruction { + program_id, + accounts: [accounts, proof_result.remaining_accounts].concat(), + data: shared::build_instruction_data(&discriminators::CREATE_ALL, ¶ms), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority]) + .await + .expect("CreateAll should succeed"); + + let pdas = TestPdas { + record: record_pda, + zc_record: zc_record_pda, + ata, + ata_owner, + vault, + vault_owner, + mint: mint_pda, + }; + + let ctx = StressTestContext { + rpc, + payer, + config_pda, + program_id, + }; + + (ctx, pdas) +} + +/// Re-read all on-chain accounts into the cache +async fn refresh_cache( + rpc: &mut LightProgramTest, + pdas: &TestPdas, + owner: [u8; 32], +) -> CachedState { + let record_account = rpc.get_account(pdas.record).await.unwrap().unwrap(); + let record: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &record_account.data[8..]).unwrap(); + + let zc_account = rpc.get_account(pdas.zc_record).await.unwrap().unwrap(); + let zc_record: ZeroCopyRecord = *bytemuck::from_bytes(&zc_account.data[8..]); + + let ata_token = parse_token(&rpc.get_account(pdas.ata).await.unwrap().unwrap().data); + let vault_token = parse_token(&rpc.get_account(pdas.vault).await.unwrap().unwrap().data); + + CachedState { + record, + zc_record, + ata_token, + vault_token, + owner, + } +} + +/// Decompress all accounts +async fn decompress_all(ctx: &mut StressTestContext, pdas: &TestPdas, cached: &CachedState) { + // PDA: MinimalRecord + let record_interface = ctx + .rpc + .get_account_interface(&pdas.record, &ctx.program_id) + .await + .expect("failed to get MinimalRecord interface"); + assert!(record_interface.is_cold(), "MinimalRecord should be cold"); + + let record_data: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &record_interface.account.data[8..]) + .expect("Failed to parse MinimalRecord"); + let record_variant = LightAccountVariant::MinimalRecord { + seeds: MinimalRecordSeeds { + owner: cached.owner, + }, + data: record_data, + }; + let record_spec = PdaSpec::new(record_interface, record_variant, ctx.program_id); + + // PDA: ZeroCopyRecord + let zc_interface = ctx + .rpc + .get_account_interface(&pdas.zc_record, &ctx.program_id) + .await + .expect("failed to get ZeroCopyRecord interface"); + assert!(zc_interface.is_cold(), "ZeroCopyRecord should be cold"); + + let zc_data: ZeroCopyRecord = + borsh::BorshDeserialize::deserialize(&mut &zc_interface.account.data[8..]) + .expect("Failed to parse ZeroCopyRecord"); + let zc_variant = LightAccountVariant::ZeroCopyRecord { + seeds: ZeroCopyRecordSeeds { + owner: cached.owner, + }, + data: zc_data, + }; + let zc_spec = PdaSpec::new(zc_interface, zc_variant, ctx.program_id); + + // ATA + let ata_interface = ctx + .rpc + .get_ata_interface(&pdas.ata_owner, &pdas.mint) + .await + .expect("failed to get ATA interface"); + assert!(ata_interface.is_cold(), "ATA should be cold"); + + // Token PDA: Vault + let vault_iface = ctx + .rpc + .get_token_account_interface(&pdas.vault) + .await + .expect("failed to get vault interface"); + assert!(vault_iface.is_cold(), "Vault should be cold"); + + let vault_token_data: Token = + borsh::BorshDeserialize::deserialize(&mut &vault_iface.account.data[..]) + .expect("Failed to parse vault Token"); + let vault_variant = LightAccountVariant::Vault(TokenDataWithSeeds { + seeds: VaultSeeds { + mint: pdas.mint.to_bytes(), + }, + token_data: vault_token_data, + }); + let vault_compressed = vault_iface + .compressed() + .expect("cold vault must have compressed data"); + let vault_interface = AccountInterface { + key: vault_iface.key, + account: vault_iface.account.clone(), + cold: Some(ColdContext::Account(vault_compressed.account.clone())), + }; + let vault_spec = PdaSpec::new(vault_interface, vault_variant, ctx.program_id); + + // Mint + let mint_iface = ctx + .rpc + .get_mint_interface(&pdas.mint) + .await + .expect("failed to get mint interface"); + assert!(mint_iface.is_cold(), "Mint should be cold"); + let (compressed_mint, _) = mint_iface + .compressed() + .expect("cold mint must have compressed data"); + let mint_ai = AccountInterface { + key: pdas.mint, + account: solana_account::Account { + lamports: 0, + data: vec![], + owner: light_token::instruction::LIGHT_TOKEN_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }, + cold: Some(ColdContext::Account(compressed_mint.clone())), + }; + + let specs: Vec> = vec![ + AccountSpec::Pda(record_spec), + AccountSpec::Pda(zc_spec), + AccountSpec::Ata(ata_interface), + AccountSpec::Pda(vault_spec), + AccountSpec::Mint(mint_ai), + ]; + + let decompress_ixs = + create_load_instructions(&specs, ctx.payer.pubkey(), ctx.config_pda, &ctx.rpc) + .await + .expect("create_load_instructions should succeed"); + + ctx.rpc + .create_and_send_transaction(&decompress_ixs, &ctx.payer.pubkey(), &[&ctx.payer]) + .await + .expect("Decompression should succeed"); + + // Verify all decompressed accounts exist on-chain + for (pda, name) in [ + (&pdas.record, "MinimalRecord"), + (&pdas.zc_record, "ZeroCopyRecord"), + (&pdas.ata, "ATA"), + (&pdas.vault, "Vault"), + (&pdas.mint, "Mint"), + ] { + shared::assert_onchain_exists(&mut ctx.rpc, pda, name).await; + } +} + +/// Compress all accounts by warping forward epochs +async fn compress_all(ctx: &mut StressTestContext, pdas: &TestPdas) { + ctx.rpc + .warp_slot_forward(SLOTS_PER_EPOCH * 100) + .await + .unwrap(); + + for (pda, name) in [ + (&pdas.record, "MinimalRecord"), + (&pdas.zc_record, "ZeroCopyRecord"), + (&pdas.ata, "ATA"), + (&pdas.vault, "Vault"), + (&pdas.mint, "Mint"), + ] { + shared::assert_onchain_closed(&mut ctx.rpc, pda, name).await; + } +} + +/// Full-struct assertions for all accounts against cached state +async fn assert_all_state( + rpc: &mut LightProgramTest, + pdas: &TestPdas, + cached: &CachedState, + iteration: usize, +) { + // MinimalRecord + let account = rpc.get_account(pdas.record).await.unwrap().unwrap(); + let actual_record: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &account.data[8..]).unwrap(); + let expected_record = MinimalRecord { + compression_info: shared::expected_compression_info(&actual_record.compression_info), + ..cached.record.clone() + }; + assert_eq!( + actual_record, expected_record, + "MinimalRecord mismatch at iteration {iteration}" + ); + + // ZeroCopyRecord + let account = rpc.get_account(pdas.zc_record).await.unwrap().unwrap(); + let actual_zc: &ZeroCopyRecord = bytemuck::from_bytes(&account.data[8..]); + let expected_zc = ZeroCopyRecord { + compression_info: shared::expected_compression_info(&actual_zc.compression_info), + ..cached.zc_record + }; + assert_eq!( + *actual_zc, expected_zc, + "ZeroCopyRecord mismatch at iteration {iteration}" + ); + + // ATA + let actual_ata = parse_token(&rpc.get_account(pdas.ata).await.unwrap().unwrap().data); + let expected_ata = Token { + extensions: actual_ata.extensions.clone(), + ..cached.ata_token.clone() + }; + assert_eq!( + actual_ata, expected_ata, + "ATA mismatch at iteration {iteration}" + ); + + // Vault + let actual_vault = parse_token(&rpc.get_account(pdas.vault).await.unwrap().unwrap().data); + let expected_vault = Token { + extensions: actual_vault.extensions.clone(), + ..cached.vault_token.clone() + }; + assert_eq!( + actual_vault, expected_vault, + "Vault mismatch at iteration {iteration}" + ); + + // Mint + let actual_mint: Mint = borsh::BorshDeserialize::deserialize( + &mut &rpc.get_account(pdas.mint).await.unwrap().unwrap().data[..], + ) + .unwrap(); + assert_eq!( + actual_mint.base.decimals, 9, + "Mint decimals mismatch at iteration {iteration}" + ); +} + +#[tokio::test] +async fn test_stress_20_iterations() { + let (mut ctx, pdas) = setup().await; + + // Verify initial creation + for (pda, name) in [ + (&pdas.record, "MinimalRecord"), + (&pdas.zc_record, "ZeroCopyRecord"), + (&pdas.ata, "ATA"), + (&pdas.vault, "Vault"), + (&pdas.mint, "Mint"), + ] { + shared::assert_onchain_exists(&mut ctx.rpc, pda, name).await; + } + + // Cache initial state + let owner = { + let account = ctx.rpc.get_account(pdas.record).await.unwrap().unwrap(); + let record: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &account.data[8..]).unwrap(); + record.owner + }; + let mut cached = refresh_cache(&mut ctx.rpc, &pdas, owner).await; + + // First compression + compress_all(&mut ctx, &pdas).await; + + // Main loop: 20 iterations + for i in 0..20 { + println!("--- Iteration {i} ---"); + + // Decompress all + decompress_all(&mut ctx, &pdas, &cached).await; + + // Assert all cached state + assert_all_state(&mut ctx.rpc, &pdas, &cached, i).await; + + // Update cache after decompression (compression_info changes) + cached = refresh_cache(&mut ctx.rpc, &pdas, owner).await; + + // Compress all + compress_all(&mut ctx, &pdas).await; + + println!(" iteration {i} complete"); + } + + println!("All 20 iterations completed successfully."); +} diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_all.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_all.rs new file mode 100644 index 0000000000..e8faeb4a29 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_all.rs @@ -0,0 +1,400 @@ +mod shared; + +use light_account_pinocchio::token::TokenDataWithSeeds; +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterface, AccountInterfaceExt, + AccountSpec, ColdContext, CreateAccountsProofInput, PdaSpec, +}; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::{program_test::TestRpc, Rpc}; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; +use pinocchio_light_program_test::{ + all::accounts::CreateAllParams, + discriminators, LightAccountVariant, MinimalRecord, MinimalRecordSeeds, VaultSeeds, + ZeroCopyRecord, ZeroCopyRecordSeeds, MINT_SIGNER_SEED_A, RECORD_SEED, VAULT_AUTH_SEED, + VAULT_SEED, +}; +use solana_instruction::{AccountMeta, Instruction}; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +#[tokio::test] +async fn test_create_all_derive() { + let env = shared::setup_test_env().await; + let mut rpc = env.rpc; + let payer = env.payer; + let program_id = env.program_id; + + let owner = Keypair::new().pubkey(); + let authority = Keypair::new(); + + // PDA: MinimalRecord + let (record_pda, _) = + Pubkey::find_program_address(&[b"minimal_record", owner.as_ref()], &program_id); + + // PDA: ZeroCopyRecord + let (zc_record_pda, _) = + Pubkey::find_program_address(&[RECORD_SEED, owner.as_ref()], &program_id); + + // Mint signer PDA + let (mint_signer, mint_signer_bump) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED_A, authority.pubkey().as_ref()], + &program_id, + ); + let (mint_pda, _) = light_token::instruction::find_mint_address(&mint_signer); + + // Token vault PDA (uses the mint we're creating) + let (vault_owner, _) = Pubkey::find_program_address(&[VAULT_AUTH_SEED], &program_id); + let (vault, vault_bump) = + Pubkey::find_program_address(&[VAULT_SEED, mint_pda.as_ref()], &program_id); + + // ATA (uses the mint we're creating) + let ata_owner = payer.pubkey(); + let (ata, _) = light_token::instruction::derive_token_ata(&ata_owner, &mint_pda); + + // Build proof inputs for PDA accounts and the mint + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![ + CreateAccountsProofInput::pda(record_pda), + CreateAccountsProofInput::pda(zc_record_pda), + CreateAccountsProofInput::mint(mint_signer), + ], + ) + .await + .unwrap(); + + let params = CreateAllParams { + create_accounts_proof: proof_result.create_accounts_proof, + owner: owner.to_bytes(), + mint_signer_bump, + token_vault_bump: vault_bump, + }; + + // Account order per all/accounts.rs: + // [0] payer (signer, writable) + // [1] authority (signer) + // [2] compression_config + // [3] borsh_record (writable) + // [4] zero_copy_record (writable) + // [5] mint_signer + // [6] mint (writable) + // [7] token_vault (writable) + // [8] vault_owner + // [9] ata_owner + // [10] user_ata (writable) + // [11] compressible_config (LIGHT_TOKEN_CONFIG) + // [12] rent_sponsor (LIGHT_TOKEN_RENT_SPONSOR, writable) + // [13] light_token_program + // [14] cpi_authority + // [15] system_program + let accounts = vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(authority.pubkey(), true), + AccountMeta::new_readonly(env.config_pda, false), + AccountMeta::new(record_pda, false), + AccountMeta::new(zc_record_pda, false), + AccountMeta::new_readonly(mint_signer, false), + AccountMeta::new(mint_pda, false), + AccountMeta::new(vault, false), + AccountMeta::new_readonly(vault_owner, false), + AccountMeta::new_readonly(ata_owner, false), + AccountMeta::new(ata, false), + AccountMeta::new_readonly(LIGHT_TOKEN_CONFIG, false), + AccountMeta::new(LIGHT_TOKEN_RENT_SPONSOR, false), + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID.into(), false), + AccountMeta::new_readonly(light_token_types::CPI_AUTHORITY_PDA.into(), false), + AccountMeta::new_readonly(solana_sdk::system_program::ID, false), + ]; + + let instruction = Instruction { + program_id, + accounts: [accounts, proof_result.remaining_accounts].concat(), + data: shared::build_instruction_data(&discriminators::CREATE_ALL, ¶ms), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority]) + .await + .expect("CreateAll should succeed"); + + // PHASE 1: Verify all accounts on-chain after creation + use light_compressed_account::pubkey::Pubkey as LPubkey; + + let record_account = rpc + .get_account(record_pda) + .await + .unwrap() + .expect("Record PDA should exist"); + let record: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &record_account.data[8..]) + .expect("Failed to deserialize MinimalRecord"); + assert_eq!(record.owner, owner.to_bytes(), "Record owner should match"); + + let zc_account = rpc + .get_account(zc_record_pda) + .await + .unwrap() + .expect("Zero-copy record should exist"); + let zc_record: &ZeroCopyRecord = bytemuck::from_bytes(&zc_account.data[8..]); + assert_eq!( + zc_record.owner, + owner.to_bytes(), + "ZC record owner should match" + ); + assert_eq!(zc_record.counter, 0, "ZC record counter should be 0"); + + let ata_account = rpc + .get_account(ata) + .await + .unwrap() + .expect("ATA should exist"); + let ata_token: Token = borsh::BorshDeserialize::deserialize(&mut &ata_account.data[..]) + .expect("Failed to deserialize ATA Token"); + assert_eq!( + ata_token.mint, + LPubkey::from(mint_pda.to_bytes()), + "ATA mint should match" + ); + assert_eq!( + ata_token.owner, + LPubkey::from(ata_owner.to_bytes()), + "ATA owner should match" + ); + + let vault_account = rpc + .get_account(vault) + .await + .unwrap() + .expect("Vault should exist"); + let vault_token: Token = borsh::BorshDeserialize::deserialize(&mut &vault_account.data[..]) + .expect("Failed to deserialize Vault Token"); + assert_eq!( + vault_token.mint, + LPubkey::from(mint_pda.to_bytes()), + "Vault mint should match" + ); + assert_eq!( + vault_token.owner, + LPubkey::from(vault_owner.to_bytes()), + "Vault owner should match" + ); + + use light_token_interface::state::Mint; + + let mint_account = rpc + .get_account(mint_pda) + .await + .unwrap() + .expect("Mint should exist"); + let mint: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_account.data[..]) + .expect("Failed to deserialize Mint"); + assert_eq!(mint.base.decimals, 9, "Mint should have 9 decimals"); + + // PHASE 2: Warp to trigger auto-compression + rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); + + shared::assert_onchain_closed(&mut rpc, &record_pda, "MinimalRecord").await; + shared::assert_onchain_closed(&mut rpc, &zc_record_pda, "ZeroCopyRecord").await; + shared::assert_onchain_closed(&mut rpc, &ata, "ATA").await; + shared::assert_onchain_closed(&mut rpc, &vault, "Vault").await; + shared::assert_onchain_closed(&mut rpc, &mint_pda, "Mint").await; + + // PHASE 3: Decompress all accounts via create_load_instructions + + // PDA: MinimalRecord + let record_interface = rpc + .get_account_interface(&record_pda, &program_id) + .await + .expect("failed to get MinimalRecord interface"); + assert!(record_interface.is_cold(), "MinimalRecord should be cold"); + + let record_data: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &record_interface.account.data[8..]) + .expect("Failed to parse MinimalRecord"); + let record_variant = LightAccountVariant::MinimalRecord { + seeds: MinimalRecordSeeds { + owner: owner.to_bytes(), + }, + data: record_data, + }; + let record_spec = PdaSpec::new(record_interface, record_variant, program_id); + + // PDA: ZeroCopyRecord + let zc_interface = rpc + .get_account_interface(&zc_record_pda, &program_id) + .await + .expect("failed to get ZeroCopyRecord interface"); + assert!(zc_interface.is_cold(), "ZeroCopyRecord should be cold"); + + let zc_data: ZeroCopyRecord = + borsh::BorshDeserialize::deserialize(&mut &zc_interface.account.data[8..]) + .expect("Failed to parse ZeroCopyRecord"); + let zc_variant = LightAccountVariant::ZeroCopyRecord { + seeds: ZeroCopyRecordSeeds { + owner: owner.to_bytes(), + }, + data: zc_data, + }; + let zc_spec = PdaSpec::new(zc_interface, zc_variant, program_id); + + // ATA + let ata_interface = rpc + .get_ata_interface(&ata_owner, &mint_pda) + .await + .expect("failed to get ATA interface"); + assert!(ata_interface.is_cold(), "ATA should be cold"); + + // Token PDA: Vault + let vault_iface = rpc + .get_token_account_interface(&vault) + .await + .expect("failed to get vault interface"); + assert!(vault_iface.is_cold(), "Vault should be cold"); + + let vault_token_data: Token = + borsh::BorshDeserialize::deserialize(&mut &vault_iface.account.data[..]) + .expect("Failed to parse vault Token"); + let vault_variant = LightAccountVariant::Vault(TokenDataWithSeeds { + seeds: VaultSeeds { + mint: mint_pda.to_bytes(), + }, + token_data: vault_token_data, + }); + let vault_compressed = vault_iface + .compressed() + .expect("cold vault must have compressed data"); + let vault_interface = AccountInterface { + key: vault_iface.key, + account: vault_iface.account.clone(), + cold: Some(ColdContext::Account(vault_compressed.account.clone())), + }; + let vault_spec = PdaSpec::new(vault_interface, vault_variant, program_id); + + // Mint + let mint_iface = rpc + .get_mint_interface(&mint_pda) + .await + .expect("failed to get mint interface"); + assert!(mint_iface.is_cold(), "Mint should be cold"); + let (compressed_mint, _) = mint_iface + .compressed() + .expect("cold mint must have compressed data"); + let mint_ai = AccountInterface { + key: mint_pda, + account: solana_account::Account { + lamports: 0, + data: vec![], + owner: light_token::instruction::LIGHT_TOKEN_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }, + cold: Some(ColdContext::Account(compressed_mint.clone())), + }; + + let specs: Vec> = vec![ + AccountSpec::Pda(record_spec), + AccountSpec::Pda(zc_spec), + AccountSpec::Ata(ata_interface), + AccountSpec::Pda(vault_spec), + AccountSpec::Mint(mint_ai), + ]; + + let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) + .await + .expect("create_load_instructions should succeed"); + + rpc.create_and_send_transaction(&ixs, &payer.pubkey(), &[&payer]) + .await + .expect("Decompression should succeed"); + + // PHASE 4: Assert state preserved after decompression + shared::assert_onchain_exists(&mut rpc, &record_pda, "MinimalRecord").await; + shared::assert_onchain_exists(&mut rpc, &zc_record_pda, "ZeroCopyRecord").await; + shared::assert_onchain_exists(&mut rpc, &ata, "ATA").await; + shared::assert_onchain_exists(&mut rpc, &vault, "Vault").await; + shared::assert_onchain_exists(&mut rpc, &mint_pda, "Mint").await; + + // MinimalRecord + let account = rpc.get_account(record_pda).await.unwrap().unwrap(); + let actual_record: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &account.data[8..]).unwrap(); + let expected_record = MinimalRecord { + compression_info: shared::expected_compression_info(&actual_record.compression_info), + owner: owner.to_bytes(), + }; + assert_eq!( + actual_record, expected_record, + "MinimalRecord should match after decompression" + ); + + // ZeroCopyRecord + let account = rpc.get_account(zc_record_pda).await.unwrap().unwrap(); + let actual_zc: &ZeroCopyRecord = bytemuck::from_bytes(&account.data[8..]); + let expected_zc = ZeroCopyRecord { + compression_info: shared::expected_compression_info(&actual_zc.compression_info), + owner: owner.to_bytes(), + counter: 0, + }; + assert_eq!( + *actual_zc, expected_zc, + "ZeroCopyRecord should match after decompression" + ); + + // ATA + let actual_ata: Token = shared::parse_token(&rpc.get_account(ata).await.unwrap().unwrap().data); + let expected_ata = Token { + mint: LPubkey::from(mint_pda.to_bytes()), + owner: LPubkey::from(ata_owner.to_bytes()), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT, + extensions: actual_ata.extensions.clone(), + }; + assert_eq!( + actual_ata, expected_ata, + "ATA should match after decompression" + ); + + // Vault + let actual_vault: Token = + shared::parse_token(&rpc.get_account(vault).await.unwrap().unwrap().data); + let expected_vault = Token { + mint: LPubkey::from(mint_pda.to_bytes()), + owner: LPubkey::from(vault_owner.to_bytes()), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT, + extensions: actual_vault.extensions.clone(), + }; + assert_eq!( + actual_vault, expected_vault, + "Vault should match after decompression" + ); + + // Mint + let actual_mint: Mint = borsh::BorshDeserialize::deserialize( + &mut &rpc.get_account(mint_pda).await.unwrap().unwrap().data[..], + ) + .unwrap(); + assert_eq!( + actual_mint.base.decimals, 9, + "Mint decimals should be preserved" + ); + assert_eq!( + actual_mint.base.mint_authority, + Some(authority.pubkey().to_bytes().into()), + "Mint authority should be preserved" + ); +} diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_ata.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_ata.rs new file mode 100644 index 0000000000..495999f63d --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_ata.rs @@ -0,0 +1,124 @@ +mod shared; + +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterfaceExt, AccountSpec, +}; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::{program_test::TestRpc, Rpc}; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use pinocchio_light_program_test::{ + discriminators, + ata::accounts::CreateAtaParams, + LightAccountVariant, +}; +use solana_instruction::{AccountMeta, Instruction}; +use solana_signer::Signer; + +#[tokio::test] +async fn test_create_ata_derive() { + let env = shared::setup_test_env().await; + let mut rpc = env.rpc; + let payer = env.payer; + let program_id = env.program_id; + + let (mint, _mint_seed) = shared::setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; + + let ata_owner = payer.pubkey(); + let (ata, _ata_bump) = light_token::instruction::derive_token_ata(&ata_owner, &mint); + + let proof_result = get_create_accounts_proof(&rpc, &program_id, vec![]) + .await + .unwrap(); + + let params = CreateAtaParams {}; + + let accounts = vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(mint, false), + AccountMeta::new_readonly(ata_owner, false), + AccountMeta::new(ata, false), + AccountMeta::new_readonly(LIGHT_TOKEN_CONFIG, false), + AccountMeta::new(LIGHT_TOKEN_RENT_SPONSOR, false), + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID.into(), false), + AccountMeta::new_readonly(solana_sdk::system_program::ID, false), + ]; + + let instruction = Instruction { + program_id, + accounts: [accounts, proof_result.remaining_accounts].concat(), + data: shared::build_instruction_data(&discriminators::CREATE_ATA, ¶ms), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) + .await + .expect("CreateAta should succeed"); + + // PHASE 1: Verify on-chain after creation + let ata_account = rpc + .get_account(ata) + .await + .unwrap() + .expect("ATA should exist on-chain"); + + use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; + let token: Token = borsh::BorshDeserialize::deserialize(&mut &ata_account.data[..]) + .expect("Failed to deserialize Token"); + + let expected_token = Token { + mint: mint.to_bytes().into(), + owner: ata_owner.to_bytes().into(), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT, + extensions: token.extensions.clone(), + }; + + assert_eq!( + token, expected_token, + "ATA should match expected after creation" + ); + + // PHASE 2: Warp to trigger auto-compression + rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); + shared::assert_onchain_closed(&mut rpc, &ata, "ATA").await; + + // PHASE 3: Decompress via create_load_instructions + let ata_interface = rpc + .get_ata_interface(&ata_owner, &mint) + .await + .expect("failed to get ATA interface"); + assert!(ata_interface.is_cold(), "ATA should be cold"); + + let specs: Vec> = vec![AccountSpec::Ata(ata_interface)]; + + let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) + .await + .expect("create_load_instructions should succeed"); + + rpc.create_and_send_transaction(&ixs, &payer.pubkey(), &[&payer]) + .await + .expect("Decompression should succeed"); + + // PHASE 4: Assert state preserved after decompression + shared::assert_onchain_exists(&mut rpc, &ata, "ATA").await; + + let actual: Token = shared::parse_token(&rpc.get_account(ata).await.unwrap().unwrap().data); + let expected = Token { + mint: mint.to_bytes().into(), + owner: ata_owner.to_bytes().into(), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT, + extensions: actual.extensions.clone(), + }; + assert_eq!(actual, expected, "ATA should match after decompression"); +} diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_mint.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_mint.rs new file mode 100644 index 0000000000..30bd074149 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_mint.rs @@ -0,0 +1,148 @@ +mod shared; + +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterface, AccountInterfaceExt, + AccountSpec, ColdContext, CreateAccountsProofInput, +}; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::{program_test::TestRpc, Rpc}; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use pinocchio_light_program_test::{ + discriminators, mint::accounts::CreateMintParams, LightAccountVariant, MINT_SIGNER_SEED_A, +}; +use solana_instruction::{AccountMeta, Instruction}; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +#[tokio::test] +async fn test_create_mint_derive() { + let env = shared::setup_test_env().await; + let mut rpc = env.rpc; + let payer = env.payer; + let program_id = env.program_id; + + let authority = Keypair::new(); + + let (mint_signer_pda, mint_signer_bump) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED_A, authority.pubkey().as_ref()], + &program_id, + ); + + let (mint_pda, _) = light_token::instruction::find_mint_address(&mint_signer_pda); + + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![CreateAccountsProofInput::mint(mint_signer_pda)], + ) + .await + .unwrap(); + + let params = CreateMintParams { + create_accounts_proof: proof_result.create_accounts_proof, + mint_signer_bump, + }; + + // Account order per mint/accounts.rs: + // [0] payer (signer, writable) + // [1] authority (signer) + // [2] mint_signer + // [3] mint (writable) + // [4] compressible_config (LIGHT_TOKEN_CONFIG) + // [5] rent_sponsor (LIGHT_TOKEN_RENT_SPONSOR, writable) + // [6] light_token_program + // [7] cpi_authority + // [8] system_program + let accounts = vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(authority.pubkey(), true), + AccountMeta::new_readonly(mint_signer_pda, false), + AccountMeta::new(mint_pda, false), + AccountMeta::new_readonly(LIGHT_TOKEN_CONFIG, false), + AccountMeta::new(LIGHT_TOKEN_RENT_SPONSOR, false), + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID.into(), false), + AccountMeta::new_readonly(light_token_types::CPI_AUTHORITY_PDA.into(), false), + AccountMeta::new_readonly(solana_sdk::system_program::ID, false), + ]; + + let instruction = Instruction { + program_id, + accounts: [accounts, proof_result.remaining_accounts].concat(), + data: shared::build_instruction_data(&discriminators::CREATE_MINT, ¶ms), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority]) + .await + .expect("CreateMint should succeed"); + + // PHASE 1: Verify on-chain after creation + let mint_account = rpc + .get_account(mint_pda) + .await + .unwrap() + .expect("Mint should exist on-chain"); + + use light_token_interface::state::Mint; + let mint: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_account.data[..]) + .expect("Failed to deserialize Mint"); + + assert_eq!(mint.base.decimals, 9, "Mint should have 9 decimals"); + assert_eq!( + mint.base.mint_authority, + Some(authority.pubkey().to_bytes().into()), + "Mint authority should be authority" + ); + + // PHASE 2: Warp to trigger auto-compression + rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); + shared::assert_onchain_closed(&mut rpc, &mint_pda, "Mint").await; + + // PHASE 3: Decompress via create_load_instructions + let mint_interface = rpc + .get_mint_interface(&mint_pda) + .await + .expect("failed to get mint interface"); + assert!(mint_interface.is_cold(), "Mint should be cold"); + + let (compressed, _mint_data) = mint_interface + .compressed() + .expect("cold mint must have compressed data"); + let mint_account_interface = AccountInterface { + key: mint_pda, + account: solana_account::Account { + lamports: 0, + data: vec![], + owner: light_token::instruction::LIGHT_TOKEN_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }, + cold: Some(ColdContext::Account(compressed.clone())), + }; + + let specs: Vec> = + vec![AccountSpec::Mint(mint_account_interface)]; + + let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) + .await + .expect("create_load_instructions should succeed"); + + rpc.create_and_send_transaction(&ixs, &payer.pubkey(), &[&payer]) + .await + .expect("Decompression should succeed"); + + // PHASE 4: Assert state preserved after decompression + shared::assert_onchain_exists(&mut rpc, &mint_pda, "Mint").await; + + let actual: Mint = borsh::BorshDeserialize::deserialize( + &mut &rpc.get_account(mint_pda).await.unwrap().unwrap().data[..], + ) + .unwrap(); + assert_eq!(actual.base.decimals, 9, "Mint decimals should be preserved"); + assert_eq!( + actual.base.mint_authority, + Some(authority.pubkey().to_bytes().into()), + "Mint authority should be preserved" + ); +} diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_pda.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_pda.rs new file mode 100644 index 0000000000..d2c0d334dc --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_pda.rs @@ -0,0 +1,128 @@ +mod shared; + +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterfaceExt, AccountSpec, + CreateAccountsProofInput, PdaSpec, +}; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::{program_test::TestRpc, Rpc}; +use pinocchio_light_program_test::{ + discriminators, + pda::accounts::CreatePdaParams, + LightAccountVariant, MinimalRecord, MinimalRecordSeeds, +}; +use solana_instruction::{AccountMeta, Instruction}; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +#[tokio::test] +async fn test_create_single_pda_derive() { + let env = shared::setup_test_env().await; + let mut rpc = env.rpc; + let payer = env.payer; + let program_id = env.program_id; + + let owner = Keypair::new().pubkey(); + + let (record_pda, _) = + Pubkey::find_program_address(&[b"minimal_record", owner.as_ref()], &program_id); + + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![CreateAccountsProofInput::pda(record_pda)], + ) + .await + .unwrap(); + + let params = CreatePdaParams { + create_accounts_proof: proof_result.create_accounts_proof, + owner: owner.to_bytes(), + }; + + let accounts = vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(env.config_pda, false), + AccountMeta::new(env.rent_sponsor, false), + AccountMeta::new(record_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::ID, false), + ]; + + let instruction = Instruction { + program_id, + accounts: [accounts, proof_result.remaining_accounts].concat(), + data: shared::build_instruction_data(&discriminators::CREATE_PDA, ¶ms), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) + .await + .expect("CreatePda should succeed"); + + // PHASE 1: Verify on-chain after creation + let record_account = rpc + .get_account(record_pda) + .await + .unwrap() + .expect("Record PDA should exist on-chain"); + + let record: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &record_account.data[8..]) + .expect("Failed to deserialize MinimalRecord"); + + let expected = MinimalRecord { + compression_info: shared::expected_compression_info(&record.compression_info), + owner: owner.to_bytes(), + }; + assert_eq!( + record, expected, + "MinimalRecord should match after creation" + ); + + // PHASE 2: Warp to trigger auto-compression + rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); + shared::assert_onchain_closed(&mut rpc, &record_pda, "MinimalRecord").await; + + // PHASE 3: Decompress via create_load_instructions + let account_interface = rpc + .get_account_interface(&record_pda, &program_id) + .await + .expect("failed to get MinimalRecord interface"); + assert!(account_interface.is_cold(), "MinimalRecord should be cold"); + + let data: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &account_interface.account.data[8..]) + .expect("Failed to parse MinimalRecord from interface"); + let variant = LightAccountVariant::MinimalRecord { + seeds: MinimalRecordSeeds { + owner: owner.to_bytes(), + }, + data, + }; + + let spec = PdaSpec::new(account_interface, variant, program_id); + let specs: Vec> = vec![AccountSpec::Pda(spec)]; + + let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) + .await + .expect("create_load_instructions should succeed"); + + rpc.create_and_send_transaction(&ixs, &payer.pubkey(), &[&payer]) + .await + .expect("Decompression should succeed"); + + // PHASE 4: Assert state preserved after decompression + shared::assert_onchain_exists(&mut rpc, &record_pda, "MinimalRecord").await; + + let account = rpc.get_account(record_pda).await.unwrap().unwrap(); + let actual: MinimalRecord = + borsh::BorshDeserialize::deserialize(&mut &account.data[8..]).unwrap(); + let expected = MinimalRecord { + compression_info: shared::expected_compression_info(&actual.compression_info), + owner: owner.to_bytes(), + }; + assert_eq!( + actual, expected, + "MinimalRecord should match after decompression" + ); +} diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_token_vault.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_token_vault.rs new file mode 100644 index 0000000000..ffe6f089cc --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_token_vault.rs @@ -0,0 +1,164 @@ +mod shared; + +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterface, AccountInterfaceExt, + AccountSpec, ColdContext, PdaSpec, +}; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::{program_test::TestRpc, Rpc}; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; +use pinocchio_light_program_test::{ + discriminators, + token_account::accounts::CreateTokenVaultParams, + LightAccountVariant, VaultSeeds, VAULT_AUTH_SEED, VAULT_SEED, +}; +use solana_instruction::{AccountMeta, Instruction}; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +#[tokio::test] +async fn test_create_token_vault_derive() { + let env = shared::setup_test_env().await; + let mut rpc = env.rpc; + let payer = env.payer; + let program_id = env.program_id; + + let (mint, _mint_seed) = shared::setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; + + let (vault_authority, _auth_bump) = + Pubkey::find_program_address(&[VAULT_AUTH_SEED], &program_id); + let (vault, vault_bump) = + Pubkey::find_program_address(&[VAULT_SEED, mint.as_ref()], &program_id); + + let proof_result = get_create_accounts_proof(&rpc, &program_id, vec![]) + .await + .unwrap(); + + let params = CreateTokenVaultParams { vault_bump }; + + let accounts = vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(mint, false), + AccountMeta::new_readonly(vault_authority, false), + AccountMeta::new(vault, false), + AccountMeta::new_readonly(LIGHT_TOKEN_CONFIG, false), + AccountMeta::new(LIGHT_TOKEN_RENT_SPONSOR, false), + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID.into(), false), + AccountMeta::new_readonly(solana_sdk::system_program::ID, false), + ]; + + let instruction = Instruction { + program_id, + accounts: [accounts, proof_result.remaining_accounts].concat(), + data: shared::build_instruction_data(&discriminators::CREATE_TOKEN_VAULT, ¶ms), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) + .await + .expect("CreateTokenVault should succeed"); + + // PHASE 1: Verify on-chain after creation + let vault_account = rpc + .get_account(vault) + .await + .unwrap() + .expect("Token vault should exist on-chain"); + + let token: Token = borsh::BorshDeserialize::deserialize(&mut &vault_account.data[..]) + .expect("Failed to deserialize Token"); + + let expected_token = Token { + mint: mint.to_bytes().into(), + owner: vault_authority.to_bytes().into(), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT, + extensions: token.extensions.clone(), + }; + + assert_eq!( + token, expected_token, + "Token vault should match expected after creation" + ); + + // PHASE 2: Warp to trigger auto-compression + rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); + shared::assert_onchain_closed(&mut rpc, &vault, "Vault").await; + + // PHASE 3: Decompress vault + let vault_iface = rpc + .get_token_account_interface(&vault) + .await + .expect("failed to get vault interface"); + assert!(vault_iface.is_cold(), "Vault should be cold"); + + let token_data: Token = + borsh::BorshDeserialize::deserialize(&mut &vault_iface.account.data[..]) + .expect("Failed to parse vault Token"); + let vault_variant = LightAccountVariant::Vault( + light_account_pinocchio::token::TokenDataWithSeeds { + seeds: VaultSeeds { + mint: mint.to_bytes(), + }, + token_data, + }, + ); + let vault_compressed = vault_iface + .compressed() + .expect("cold vault must have compressed data"); + let vault_interface = AccountInterface { + key: vault_iface.key, + account: vault_iface.account.clone(), + cold: Some(ColdContext::Account(vault_compressed.account.clone())), + }; + let vault_spec = PdaSpec::new(vault_interface, vault_variant, program_id); + + let specs: Vec> = vec![AccountSpec::Pda(vault_spec)]; + + let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) + .await + .expect("create_load_instructions should succeed"); + + rpc.create_and_send_transaction(&ixs, &payer.pubkey(), &[&payer]) + .await + .expect("Vault decompression should succeed"); + + // PHASE 4: Assert state preserved after decompression + shared::assert_onchain_exists(&mut rpc, &vault, "Vault").await; + + let vault_account = rpc + .get_account(vault) + .await + .unwrap() + .expect("Vault should exist on-chain after decompression"); + + let actual_token: Token = + borsh::BorshDeserialize::deserialize(&mut &vault_account.data[..]) + .expect("Failed to deserialize Token after decompression"); + + use light_compressed_account::pubkey::Pubkey as LPubkey; + + let expected_token = Token { + mint: LPubkey::from(mint.to_bytes()), + owner: LPubkey::from(vault_authority.to_bytes()), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT, + extensions: actual_token.extensions.clone(), + }; + + assert_eq!( + actual_token, expected_token, + "Token vault should match expected after decompression" + ); +} diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_two_mints.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_two_mints.rs new file mode 100644 index 0000000000..d5567a8de1 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_two_mints.rs @@ -0,0 +1,205 @@ +mod shared; + +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterface, AccountInterfaceExt, + AccountSpec, ColdContext, CreateAccountsProofInput, +}; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::{program_test::TestRpc, Rpc}; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; +use pinocchio_light_program_test::{ + discriminators, + two_mints::accounts::CreateTwoMintsParams, + LightAccountVariant, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, +}; +use solana_instruction::{AccountMeta, Instruction}; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +#[tokio::test] +async fn test_create_two_mints_derive() { + let env = shared::setup_test_env().await; + let mut rpc = env.rpc; + let payer = env.payer; + let program_id = env.program_id; + + let authority = Keypair::new(); + + let (mint_signer_a, mint_signer_bump_a) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED_A, authority.pubkey().as_ref()], + &program_id, + ); + let (mint_a_pda, _) = light_token::instruction::find_mint_address(&mint_signer_a); + + let (mint_signer_b, mint_signer_bump_b) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED_B, authority.pubkey().as_ref()], + &program_id, + ); + let (mint_b_pda, _) = light_token::instruction::find_mint_address(&mint_signer_b); + + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![ + CreateAccountsProofInput::mint(mint_signer_a), + CreateAccountsProofInput::mint(mint_signer_b), + ], + ) + .await + .unwrap(); + + let params = CreateTwoMintsParams { + create_accounts_proof: proof_result.create_accounts_proof, + mint_signer_bump_a, + mint_signer_bump_b, + }; + + // Account order per two_mints/accounts.rs: + // [0] payer (signer, writable) + // [1] authority (signer) + // [2] mint_signer_a + // [3] mint_signer_b + // [4] mint_a (writable) + // [5] mint_b (writable) + // [6] compressible_config (LIGHT_TOKEN_CONFIG) + // [7] rent_sponsor (LIGHT_TOKEN_RENT_SPONSOR, writable) + // [8] light_token_program + // [9] cpi_authority + // [10] system_program + let accounts = vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(authority.pubkey(), true), + AccountMeta::new_readonly(mint_signer_a, false), + AccountMeta::new_readonly(mint_signer_b, false), + AccountMeta::new(mint_a_pda, false), + AccountMeta::new(mint_b_pda, false), + AccountMeta::new_readonly(LIGHT_TOKEN_CONFIG, false), + AccountMeta::new(LIGHT_TOKEN_RENT_SPONSOR, false), + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID.into(), false), + AccountMeta::new_readonly(light_token_types::CPI_AUTHORITY_PDA.into(), false), + AccountMeta::new_readonly(solana_sdk::system_program::ID, false), + ]; + + let instruction = Instruction { + program_id, + accounts: [accounts, proof_result.remaining_accounts].concat(), + data: shared::build_instruction_data(&discriminators::CREATE_TWO_MINTS, ¶ms), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority]) + .await + .expect("CreateTwoMints should succeed"); + + // PHASE 1: Verify on-chain after creation + use light_token_interface::state::Mint; + + let mint_a_account = rpc + .get_account(mint_a_pda) + .await + .unwrap() + .expect("Mint A should exist on-chain"); + let mint_a: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_a_account.data[..]) + .expect("Failed to deserialize Mint A"); + assert_eq!(mint_a.base.decimals, 9, "Mint A should have 9 decimals"); + assert_eq!( + mint_a.base.mint_authority, + Some(authority.pubkey().to_bytes().into()), + "Mint A authority should be authority" + ); + + let mint_b_account = rpc + .get_account(mint_b_pda) + .await + .unwrap() + .expect("Mint B should exist on-chain"); + let mint_b: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_b_account.data[..]) + .expect("Failed to deserialize Mint B"); + assert_eq!(mint_b.base.decimals, 6, "Mint B should have 6 decimals"); + assert_eq!( + mint_b.base.mint_authority, + Some(authority.pubkey().to_bytes().into()), + "Mint B authority should be authority" + ); + + // PHASE 2: Warp to trigger auto-compression + rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); + shared::assert_onchain_closed(&mut rpc, &mint_a_pda, "MintA").await; + shared::assert_onchain_closed(&mut rpc, &mint_b_pda, "MintB").await; + + // PHASE 3: Decompress both mints via create_load_instructions + let build_mint_account_interface = |mint_interface: light_client::interface::MintInterface| { + let (compressed, _mint_data) = mint_interface + .compressed() + .expect("cold mint must have compressed data"); + AccountInterface { + key: mint_interface.mint, + account: solana_account::Account { + lamports: 0, + data: vec![], + owner: light_token::instruction::LIGHT_TOKEN_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }, + cold: Some(ColdContext::Account(compressed.clone())), + } + }; + + let mint_a_interface = rpc + .get_mint_interface(&mint_a_pda) + .await + .expect("failed to get mint A interface"); + assert!(mint_a_interface.is_cold(), "Mint A should be cold"); + let mint_a_ai = build_mint_account_interface(mint_a_interface); + + let mint_b_interface = rpc + .get_mint_interface(&mint_b_pda) + .await + .expect("failed to get mint B interface"); + assert!(mint_b_interface.is_cold(), "Mint B should be cold"); + let mint_b_ai = build_mint_account_interface(mint_b_interface); + + let specs: Vec> = + vec![AccountSpec::Mint(mint_a_ai), AccountSpec::Mint(mint_b_ai)]; + + let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) + .await + .expect("create_load_instructions should succeed"); + + rpc.create_and_send_transaction(&ixs, &payer.pubkey(), &[&payer]) + .await + .expect("Decompression should succeed"); + + // PHASE 4: Assert state preserved after decompression + shared::assert_onchain_exists(&mut rpc, &mint_a_pda, "MintA").await; + shared::assert_onchain_exists(&mut rpc, &mint_b_pda, "MintB").await; + + let actual_a: Mint = borsh::BorshDeserialize::deserialize( + &mut &rpc.get_account(mint_a_pda).await.unwrap().unwrap().data[..], + ) + .unwrap(); + assert_eq!( + actual_a.base.decimals, 9, + "Mint A decimals should be preserved" + ); + assert_eq!( + actual_a.base.mint_authority, + Some(authority.pubkey().to_bytes().into()), + "Mint A authority should be preserved" + ); + + let actual_b: Mint = borsh::BorshDeserialize::deserialize( + &mut &rpc.get_account(mint_b_pda).await.unwrap().unwrap().data[..], + ) + .unwrap(); + assert_eq!( + actual_b.base.decimals, 6, + "Mint B decimals should be preserved" + ); + assert_eq!( + actual_b.base.mint_authority, + Some(authority.pubkey().to_bytes().into()), + "Mint B authority should be preserved" + ); +} diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_zero_copy_record.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_zero_copy_record.rs new file mode 100644 index 0000000000..cbe1b9eb57 --- /dev/null +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_zero_copy_record.rs @@ -0,0 +1,121 @@ +mod shared; + +use light_client::interface::{ + create_load_instructions, get_create_accounts_proof, AccountInterfaceExt, AccountSpec, + CreateAccountsProofInput, PdaSpec, +}; +use light_compressible::rent::SLOTS_PER_EPOCH; +use light_program_test::{program_test::TestRpc, Rpc}; +use pinocchio_light_program_test::{ + account_loader::accounts::CreateZeroCopyRecordParams, + discriminators, + LightAccountVariant, ZeroCopyRecord, ZeroCopyRecordSeeds, RECORD_SEED, +}; +use solana_instruction::{AccountMeta, Instruction}; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +#[tokio::test] +async fn test_create_zero_copy_record_derive() { + let env = shared::setup_test_env().await; + let mut rpc = env.rpc; + let payer = env.payer; + let program_id = env.program_id; + + let owner = Keypair::new().pubkey(); + + let (record_pda, _) = Pubkey::find_program_address(&[RECORD_SEED, owner.as_ref()], &program_id); + + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![CreateAccountsProofInput::pda(record_pda)], + ) + .await + .unwrap(); + + let params = CreateZeroCopyRecordParams { + create_accounts_proof: proof_result.create_accounts_proof, + owner: owner.to_bytes(), + }; + + let accounts = vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(env.config_pda, false), + AccountMeta::new(env.rent_sponsor, false), + AccountMeta::new(record_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::ID, false), + ]; + + let instruction = Instruction { + program_id, + accounts: [accounts, proof_result.remaining_accounts].concat(), + data: shared::build_instruction_data(&discriminators::CREATE_ZERO_COPY_RECORD, ¶ms), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) + .await + .expect("CreateZeroCopyRecord should succeed"); + + // PHASE 1: Verify on-chain after creation + let record_account = rpc + .get_account(record_pda) + .await + .unwrap() + .expect("Record PDA should exist on-chain"); + + let discriminator_len = 8; + let data = &record_account.data[discriminator_len..]; + let record: &ZeroCopyRecord = bytemuck::from_bytes(data); + + assert_eq!(record.owner, owner.to_bytes(), "Record owner should match"); + assert_eq!(record.counter, 0, "Record counter should be 0"); + + // PHASE 2: Warp to trigger auto-compression + rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); + shared::assert_onchain_closed(&mut rpc, &record_pda, "ZeroCopyRecord").await; + + // PHASE 3: Decompress via create_load_instructions + let account_interface = rpc + .get_account_interface(&record_pda, &program_id) + .await + .expect("failed to get ZeroCopyRecord interface"); + assert!(account_interface.is_cold(), "ZeroCopyRecord should be cold"); + + let zc_data: ZeroCopyRecord = + borsh::BorshDeserialize::deserialize(&mut &account_interface.account.data[8..]) + .expect("Failed to parse ZeroCopyRecord from interface"); + let variant = LightAccountVariant::ZeroCopyRecord { + seeds: ZeroCopyRecordSeeds { + owner: owner.to_bytes(), + }, + data: zc_data, + }; + + let spec = PdaSpec::new(account_interface, variant, program_id); + let specs: Vec> = vec![AccountSpec::Pda(spec)]; + + let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) + .await + .expect("create_load_instructions should succeed"); + + rpc.create_and_send_transaction(&ixs, &payer.pubkey(), &[&payer]) + .await + .expect("Decompression should succeed"); + + // PHASE 4: Assert state preserved after decompression + shared::assert_onchain_exists(&mut rpc, &record_pda, "ZeroCopyRecord").await; + + let account = rpc.get_account(record_pda).await.unwrap().unwrap(); + let actual: &ZeroCopyRecord = bytemuck::from_bytes(&account.data[8..]); + let expected = ZeroCopyRecord { + compression_info: shared::expected_compression_info(&actual.compression_info), + owner: owner.to_bytes(), + counter: 0, + }; + assert_eq!( + *actual, expected, + "ZeroCopyRecord should match after decompression" + ); +} From 317683d75f2c6642e4820259fe77224084aedb88 Mon Sep 17 00:00:00 2001 From: ananas Date: Tue, 3 Feb 2026 00:55:46 +0000 Subject: [PATCH 15/15] format --- sdk-libs/account-pinocchio/src/lib.rs | 4 +- sdk-libs/account/src/lib.rs | 4 +- sdk-libs/macros/src/lib.rs | 4 +- .../src/light_pdas/program/decompress.rs | 42 ------------------- .../program/derive_light_program.rs | 9 ++-- .../src/light_pdas/program/instructions.rs | 4 +- .../src/light_pdas/program/seed_codegen.rs | 10 ++++- .../src/interface/account/light_account.rs | 2 +- .../sdk-types/src/interface/account/pack.rs | 2 +- .../src/interface/account/token_seeds.rs | 4 +- .../src/interface/instruction/mod.rs | 10 ----- sdk-libs/sdk-types/src/interface/mod.rs | 4 -- sdk-libs/sdk-types/src/lib.rs | 4 +- .../instruction => }/pack_accounts.rs | 0 sdk-libs/sdk/src/instruction/mod.rs | 4 +- .../sdk/src/instruction/packed_accounts.rs | 3 +- .../src/account_loader/accounts.rs | 4 +- .../src/account_loader/derived_accounts.rs | 3 +- .../pinocchio-light-program-test/src/lib.rs | 9 ++-- .../src/pda/accounts.rs | 4 +- .../src/pda/derived_accounts.rs | 8 ++-- .../pinocchio-light-program-test/src/state.rs | 8 +++- .../tests/shared/mod.rs | 6 ++- .../tests/test_create_all.rs | 7 ++-- .../tests/test_create_ata.rs | 4 +- .../tests/test_create_pda.rs | 5 +-- .../tests/test_create_token_vault.rs | 17 ++++---- .../tests/test_create_two_mints.rs | 5 +-- .../tests/test_create_zero_copy_record.rs | 5 +-- 29 files changed, 71 insertions(+), 124 deletions(-) delete mode 100644 sdk-libs/sdk-types/src/interface/instruction/mod.rs rename sdk-libs/sdk-types/src/{interface/instruction => }/pack_accounts.rs (100%) diff --git a/sdk-libs/account-pinocchio/src/lib.rs b/sdk-libs/account-pinocchio/src/lib.rs index daf4a9696a..8229624563 100644 --- a/sdk-libs/account-pinocchio/src/lib.rs +++ b/sdk-libs/account-pinocchio/src/lib.rs @@ -26,7 +26,7 @@ pub type CpiContextWriteAccounts<'a> = #[cfg(all(not(target_os = "solana"), feature = "std"))] pub type PackedAccounts = - light_sdk_types::interface::instruction::PackedAccounts; + light_sdk_types::pack_accounts::PackedAccounts; // ===== RE-EXPORTED TRAITS (generic over AI, used with explicit AccountInfo in impls) ===== @@ -138,7 +138,7 @@ pub use light_sdk_types::cpi_accounts::CpiAccountsConfig; #[cfg(all(not(target_os = "solana"), feature = "std"))] pub mod interface { pub mod instruction { - pub use light_sdk_types::interface::instruction::PackedAccounts; + pub use light_sdk_types::pack_accounts::PackedAccounts; } } diff --git a/sdk-libs/account/src/lib.rs b/sdk-libs/account/src/lib.rs index f9544786c5..bfb415b741 100644 --- a/sdk-libs/account/src/lib.rs +++ b/sdk-libs/account/src/lib.rs @@ -32,7 +32,7 @@ pub type ValidatedPdaContext<'info> = #[cfg(not(target_os = "solana"))] pub type PackedAccounts = - light_sdk_types::interface::instruction::PackedAccounts; + light_sdk_types::pack_accounts::PackedAccounts; // ===== RE-EXPORTED TRAITS (generic over AI, used with explicit AccountInfo in impls) ===== @@ -156,7 +156,7 @@ pub use light_sdk_types::{ #[cfg(not(target_os = "solana"))] pub mod interface { pub mod instruction { - pub use light_sdk_types::interface::instruction::PackedAccounts; + pub use light_sdk_types::pack_accounts::PackedAccounts; } } diff --git a/sdk-libs/macros/src/lib.rs b/sdk-libs/macros/src/lib.rs index e62700f504..f233707ace 100644 --- a/sdk-libs/macros/src/lib.rs +++ b/sdk-libs/macros/src/lib.rs @@ -258,7 +258,9 @@ pub fn light_program_derive(input: TokenStream) -> TokenStream { #[proc_macro_derive(LightProgramPinocchio, attributes(light_account))] pub fn light_program_pinocchio_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - into_token_stream(light_pdas::program::derive_light_program_pinocchio_impl(input)) + into_token_stream(light_pdas::program::derive_light_program_pinocchio_impl( + input, + )) } #[proc_macro_attribute] diff --git a/sdk-libs/macros/src/light_pdas/program/decompress.rs b/sdk-libs/macros/src/light_pdas/program/decompress.rs index e33c8e573f..6ca1aadad3 100644 --- a/sdk-libs/macros/src/light_pdas/program/decompress.rs +++ b/sdk-libs/macros/src/light_pdas/program/decompress.rs @@ -377,48 +377,6 @@ impl DecompressBuilder { // Pinocchio Code Generation Methods // ------------------------------------------------------------------------- - /// Generate decompress dispatch as an associated function on the enum (pinocchio version). - pub fn generate_enum_decompress_dispatch_pinocchio( - &self, - enum_name: &syn::Ident, - ) -> Result { - let processor_call = if self.has_tokens { - quote! { - light_account_pinocchio::process_decompress_accounts_idempotent::<_, PackedLightAccountVariant>( - remaining_accounts, - instruction_data, - cpi_signer, - program_id, - current_slot, - ) - } - } else { - quote! { - light_account_pinocchio::process_decompress_pda_accounts_idempotent::<_, PackedLightAccountVariant>( - remaining_accounts, - instruction_data, - cpi_signer, - program_id, - current_slot, - ) - } - }; - - Ok(quote! { - impl #enum_name { - pub fn decompress_dispatch( - remaining_accounts: &[pinocchio::account_info::AccountInfo], - instruction_data: &[u8], - cpi_signer: light_account_pinocchio::CpiSigner, - program_id: &[u8; 32], - current_slot: u64, - ) -> std::result::Result<(), light_account_pinocchio::LightSdkTypesError> { - #processor_call - } - } - }) - } - /// Generate `process_decompress` as an enum associated function (pinocchio version). pub fn generate_enum_process_decompress_pinocchio( &self, diff --git a/sdk-libs/macros/src/light_pdas/program/derive_light_program.rs b/sdk-libs/macros/src/light_pdas/program/derive_light_program.rs index 5633b92582..3d4f3fe89c 100644 --- a/sdk-libs/macros/src/light_pdas/program/derive_light_program.rs +++ b/sdk-libs/macros/src/light_pdas/program/derive_light_program.rs @@ -836,15 +836,16 @@ fn manual_variant_to_extracted_spec( let seeds: Vec = variant .seeds .iter() - .map(|s| manual_seed_to_classified(s)) + .map(manual_seed_to_classified) .collect(); ExtractedSeedSpec { struct_name: variant.ident.to_string(), variant_name: variant.ident.clone(), - inner_type: variant.inner_type.clone().unwrap_or_else(|| { - syn::parse_quote!(()) - }), + inner_type: variant + .inner_type + .clone() + .unwrap_or_else(|| syn::parse_quote!(())), seeds, is_zero_copy: variant.is_zero_copy, module_path: String::new(), diff --git a/sdk-libs/macros/src/light_pdas/program/instructions.rs b/sdk-libs/macros/src/light_pdas/program/instructions.rs index 52a485e8d8..300d2b42fb 100644 --- a/sdk-libs/macros/src/light_pdas/program/instructions.rs +++ b/sdk-libs/macros/src/light_pdas/program/instructions.rs @@ -1350,9 +1350,7 @@ pub(crate) fn generate_light_program_pinocchio_items( // Decompress dispatch + process_decompress if !pda_ctx_seeds.is_empty() { - items.push( - decompress_builder.generate_enum_process_decompress_pinocchio(enum_name)?, - ); + items.push(decompress_builder.generate_enum_process_decompress_pinocchio(enum_name)?); } // Config functions as enum methods diff --git a/sdk-libs/macros/src/light_pdas/program/seed_codegen.rs b/sdk-libs/macros/src/light_pdas/program/seed_codegen.rs index f40110ba92..3a205812b1 100644 --- a/sdk-libs/macros/src/light_pdas/program/seed_codegen.rs +++ b/sdk-libs/macros/src/light_pdas/program/seed_codegen.rs @@ -42,7 +42,10 @@ pub fn generate_client_seed_functions( let (parameters, seed_expressions) = analyze_seed_spec_for_client(spec, instruction_data)?; - let fn_body = generate_seed_derivation_body(&seed_expressions, quote! { &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id) }); + let fn_body = generate_seed_derivation_body( + &seed_expressions, + quote! { &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id) }, + ); let function = quote! { pub fn #function_name(#(#parameters),*) -> (Vec>, solana_pubkey::Pubkey) { #fn_body @@ -62,7 +65,10 @@ pub fn generate_client_seed_functions( let (parameters, seed_expressions) = analyze_seed_spec_for_client(spec, instruction_data)?; - let fn_body = generate_seed_derivation_body(&seed_expressions, quote! { &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id) }); + let fn_body = generate_seed_derivation_body( + &seed_expressions, + quote! { &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id) }, + ); let function = quote! { pub fn #function_name(#(#parameters),*) -> (Vec>, solana_pubkey::Pubkey) { #fn_body diff --git a/sdk-libs/sdk-types/src/interface/account/light_account.rs b/sdk-libs/sdk-types/src/interface/account/light_account.rs index 80f0b38ebc..5f32da2471 100644 --- a/sdk-libs/sdk-types/src/interface/account/light_account.rs +++ b/sdk-libs/sdk-types/src/interface/account/light_account.rs @@ -53,7 +53,7 @@ pub trait LightAccount: #[cfg(all(not(target_os = "solana"), feature = "std"))] fn pack( &self, - accounts: &mut crate::interface::instruction::PackedAccounts, + accounts: &mut crate::pack_accounts::PackedAccounts, ) -> Result; /// Convert from packed form (indices -> Pubkeys). diff --git a/sdk-libs/sdk-types/src/interface/account/pack.rs b/sdk-libs/sdk-types/src/interface/account/pack.rs index 8c04c636f1..c7a656ace4 100644 --- a/sdk-libs/sdk-types/src/interface/account/pack.rs +++ b/sdk-libs/sdk-types/src/interface/account/pack.rs @@ -14,7 +14,7 @@ pub trait Pack { fn pack( &self, - remaining_accounts: &mut crate::interface::instruction::PackedAccounts, + remaining_accounts: &mut crate::pack_accounts::PackedAccounts, ) -> Result; } diff --git a/sdk-libs/sdk-types/src/interface/account/token_seeds.rs b/sdk-libs/sdk-types/src/interface/account/token_seeds.rs index 93365e99e2..f22657590a 100644 --- a/sdk-libs/sdk-types/src/interface/account/token_seeds.rs +++ b/sdk-libs/sdk-types/src/interface/account/token_seeds.rs @@ -22,7 +22,9 @@ pub use light_token_interface::{ use super::pack::Unpack; #[cfg(all(not(target_os = "solana"), feature = "std"))] -use crate::interface::{account::pack::Pack, instruction::PackedAccounts}; +use crate::interface::account::pack::Pack; +#[cfg(all(not(target_os = "solana"), feature = "std"))] +use crate::pack_accounts::PackedAccounts; use crate::{ error::LightSdkTypesError, instruction::PackedStateTreeInfo, diff --git a/sdk-libs/sdk-types/src/interface/instruction/mod.rs b/sdk-libs/sdk-types/src/interface/instruction/mod.rs deleted file mode 100644 index 9d0027f3f6..0000000000 --- a/sdk-libs/sdk-types/src/interface/instruction/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Client-side instruction building utilities. -//! -//! Only available off-chain (`#[cfg(not(target_os = "solana"))]`). - -mod pack_accounts; - -pub use pack_accounts::*; - -/// Re-exports from light-sdk-types instruction types. -pub use crate::instruction::*; diff --git a/sdk-libs/sdk-types/src/interface/mod.rs b/sdk-libs/sdk-types/src/interface/mod.rs index 255e19e680..16e2982e52 100644 --- a/sdk-libs/sdk-types/src/interface/mod.rs +++ b/sdk-libs/sdk-types/src/interface/mod.rs @@ -8,10 +8,6 @@ pub mod program; // LightCpi trait + CPI builder (no runtime dep) pub mod cpi; -// Client-side instruction building (not available on Solana BPF, requires std for HashMap) -#[cfg(all(not(target_os = "solana"), feature = "std"))] -pub mod instruction; - // --- Re-exports from light-compressible --- // ============================================================================= // FLAT RE-EXPORTS diff --git a/sdk-libs/sdk-types/src/lib.rs b/sdk-libs/sdk-types/src/lib.rs index 89dc7a726b..6f9be9cba6 100644 --- a/sdk-libs/sdk-types/src/lib.rs +++ b/sdk-libs/sdk-types/src/lib.rs @@ -21,8 +21,10 @@ pub mod cpi_accounts; pub mod cpi_context_write; pub mod error; pub mod instruction; +#[cfg(feature = "std")] +pub mod pack_accounts; -#[cfg(feature = "alloc")] +#[cfg(all(feature = "alloc", feature = "v2"))] pub mod interface; // Re-exports diff --git a/sdk-libs/sdk-types/src/interface/instruction/pack_accounts.rs b/sdk-libs/sdk-types/src/pack_accounts.rs similarity index 100% rename from sdk-libs/sdk-types/src/interface/instruction/pack_accounts.rs rename to sdk-libs/sdk-types/src/pack_accounts.rs diff --git a/sdk-libs/sdk/src/instruction/mod.rs b/sdk-libs/sdk/src/instruction/mod.rs index 5a69959b29..95d0ce67d0 100644 --- a/sdk-libs/sdk/src/instruction/mod.rs +++ b/sdk-libs/sdk/src/instruction/mod.rs @@ -46,9 +46,9 @@ pub use light_compressed_account::instruction_data::compressed_proof::{ CompressedProof, ValidityProof, }; pub use light_sdk_types::instruction::*; -// Re-export pack_accounts utilities from interface (off-chain only) +// Re-export pack_accounts utilities (off-chain only, requires std for HashMap) #[cfg(not(target_os = "solana"))] -pub use light_sdk_types::interface::instruction::*; +pub use light_sdk_types::pack_accounts::*; // SDK-specific: system account helpers (depend on find_cpi_signer_macro!) mod system_accounts; diff --git a/sdk-libs/sdk/src/instruction/packed_accounts.rs b/sdk-libs/sdk/src/instruction/packed_accounts.rs index e4b75cbfb7..f1a09537b8 100644 --- a/sdk-libs/sdk/src/instruction/packed_accounts.rs +++ b/sdk-libs/sdk/src/instruction/packed_accounts.rs @@ -2,8 +2,7 @@ use std::ops::{Deref, DerefMut}; use super::system_accounts::{get_light_system_account_metas, SystemAccountMetaConfig}; -type Inner = - light_sdk_types::interface::instruction::PackedAccounts; +type Inner = light_sdk_types::pack_accounts::PackedAccounts; /// Packs accounts and creates indices for instruction building (client-side). /// diff --git a/sdk-tests/pinocchio-light-program-test/src/account_loader/accounts.rs b/sdk-tests/pinocchio-light-program-test/src/account_loader/accounts.rs index ed27ddfb2d..3863acac55 100644 --- a/sdk-tests/pinocchio-light-program-test/src/account_loader/accounts.rs +++ b/sdk-tests/pinocchio-light-program-test/src/account_loader/accounts.rs @@ -47,8 +47,8 @@ impl<'a> CreateZeroCopyRecord<'a> { return Err(ProgramError::InvalidSeeds); } - let rent = pinocchio::sysvars::rent::Rent::get() - .map_err(|_| ProgramError::UnsupportedSysvar)?; + let rent = + pinocchio::sysvars::rent::Rent::get().map_err(|_| ProgramError::UnsupportedSysvar)?; let lamports = rent.minimum_balance(space); let bump_bytes = [bump]; diff --git a/sdk-tests/pinocchio-light-program-test/src/account_loader/derived_accounts.rs b/sdk-tests/pinocchio-light-program-test/src/account_loader/derived_accounts.rs index 06815f7033..0036b7be45 100644 --- a/sdk-tests/pinocchio-light-program-test/src/account_loader/derived_accounts.rs +++ b/sdk-tests/pinocchio-light-program-test/src/account_loader/derived_accounts.rs @@ -68,8 +68,7 @@ impl LightPreInit for CreateZeroCopyRec .record .try_borrow_mut_data() .map_err(|_| LightSdkTypesError::Borsh)?; - let record_bytes = - &mut account_data[8..8 + core::mem::size_of::()]; + let record_bytes = &mut account_data[8..8 + core::mem::size_of::()]; let record: &mut ZeroCopyRecord = bytemuck::from_bytes_mut(record_bytes); record.set_decompressed(&light_config, current_slot); } diff --git a/sdk-tests/pinocchio-light-program-test/src/lib.rs b/sdk-tests/pinocchio-light-program-test/src/lib.rs index 9ec797b8dd..7528c51f43 100644 --- a/sdk-tests/pinocchio-light-program-test/src/lib.rs +++ b/sdk-tests/pinocchio-light-program-test/src/lib.rs @@ -235,8 +235,7 @@ fn process_create_zero_copy_record( .record .try_borrow_mut_data() .map_err(|_| ProgramError::AccountBorrowFailed)?; - let record_bytes = - &mut account_data[8..8 + core::mem::size_of::()]; + let record_bytes = &mut account_data[8..8 + core::mem::size_of::()]; let record: &mut state::ZeroCopyRecord = bytemuck::from_bytes_mut(record_bytes); record.owner = params.owner; } @@ -313,8 +312,7 @@ fn process_create_all(accounts: &[AccountInfo], data: &[u8]) -> Result<(), Progr let mut borsh_record = state::MinimalRecord::try_from_slice(&borsh_data[8..]) .map_err(|_| ProgramError::BorshIoError)?; borsh_record.owner = params.owner; - let serialized = - borsh::to_vec(&borsh_record).map_err(|_| ProgramError::BorshIoError)?; + let serialized = borsh::to_vec(&borsh_record).map_err(|_| ProgramError::BorshIoError)?; borsh_data[8..8 + serialized.len()].copy_from_slice(&serialized); } { @@ -322,8 +320,7 @@ fn process_create_all(accounts: &[AccountInfo], data: &[u8]) -> Result<(), Progr .zero_copy_record .try_borrow_mut_data() .map_err(|_| ProgramError::AccountBorrowFailed)?; - let record_bytes = - &mut zc_data[8..8 + core::mem::size_of::()]; + let record_bytes = &mut zc_data[8..8 + core::mem::size_of::()]; let record: &mut state::ZeroCopyRecord = bytemuck::from_bytes_mut(record_bytes); record.owner = params.owner; } diff --git a/sdk-tests/pinocchio-light-program-test/src/pda/accounts.rs b/sdk-tests/pinocchio-light-program-test/src/pda/accounts.rs index d9aed1a0f4..f9ee6f5ca9 100644 --- a/sdk-tests/pinocchio-light-program-test/src/pda/accounts.rs +++ b/sdk-tests/pinocchio-light-program-test/src/pda/accounts.rs @@ -48,8 +48,8 @@ impl<'a> CreatePda<'a> { return Err(ProgramError::InvalidSeeds); } - let rent = pinocchio::sysvars::rent::Rent::get() - .map_err(|_| ProgramError::UnsupportedSysvar)?; + let rent = + pinocchio::sysvars::rent::Rent::get().map_err(|_| ProgramError::UnsupportedSysvar)?; let lamports = rent.minimum_balance(space); let bump_bytes = [bump]; diff --git a/sdk-tests/pinocchio-light-program-test/src/pda/derived_accounts.rs b/sdk-tests/pinocchio-light-program-test/src/pda/derived_accounts.rs index 6e83c558a5..33022fe7f8 100644 --- a/sdk-tests/pinocchio-light-program-test/src/pda/derived_accounts.rs +++ b/sdk-tests/pinocchio-light-program-test/src/pda/derived_accounts.rs @@ -68,12 +68,10 @@ impl LightPreInit for CreatePda<'_> { .record .try_borrow_mut_data() .map_err(|_| LightSdkTypesError::Borsh)?; - let mut record = - crate::state::MinimalRecord::try_from_slice(&account_data[8..]) - .map_err(|_| LightSdkTypesError::Borsh)?; - record.set_decompressed(&light_config, current_slot); - let serialized = borsh::to_vec(&record) + let mut record = crate::state::MinimalRecord::try_from_slice(&account_data[8..]) .map_err(|_| LightSdkTypesError::Borsh)?; + record.set_decompressed(&light_config, current_slot); + let serialized = borsh::to_vec(&record).map_err(|_| LightSdkTypesError::Borsh)?; account_data[8..8 + serialized.len()].copy_from_slice(&serialized); } diff --git a/sdk-tests/pinocchio-light-program-test/src/state.rs b/sdk-tests/pinocchio-light-program-test/src/state.rs index dd8d155366..ca3b4ce107 100644 --- a/sdk-tests/pinocchio-light-program-test/src/state.rs +++ b/sdk-tests/pinocchio-light-program-test/src/state.rs @@ -9,7 +9,13 @@ use light_account_pinocchio::{CompressionInfo, LightDiscriminator, LightHasherSh /// Minimal record struct for testing PDA creation. /// Contains compression_info and one field. #[derive( - Default, Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize, LightDiscriminator, + Default, + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + LightDiscriminator, LightHasherSha, )] #[repr(C)] diff --git a/sdk-tests/pinocchio-light-program-test/tests/shared/mod.rs b/sdk-tests/pinocchio-light-program-test/tests/shared/mod.rs index e58f09d0fb..c855d4289a 100644 --- a/sdk-tests/pinocchio-light-program-test/tests/shared/mod.rs +++ b/sdk-tests/pinocchio-light-program-test/tests/shared/mod.rs @@ -22,8 +22,10 @@ pub struct TestEnv { /// Sets up a test environment with program, config, and rent sponsor initialized. pub async fn setup_test_env() -> TestEnv { let program_id = Pubkey::new_from_array(pinocchio_light_program_test::ID); - let mut config = - ProgramTestConfig::new_v2(true, Some(vec![("pinocchio_light_program_test", program_id)])); + let mut config = ProgramTestConfig::new_v2( + true, + Some(vec![("pinocchio_light_program_test", program_id)]), + ); config = config.with_light_protocol_events(); let mut rpc = LightProgramTest::new(config).await.unwrap(); diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_all.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_all.rs index e8faeb4a29..4b505dfe15 100644 --- a/sdk-tests/pinocchio-light-program-test/tests/test_create_all.rs +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_all.rs @@ -11,10 +11,9 @@ use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; use pinocchio_light_program_test::{ - all::accounts::CreateAllParams, - discriminators, LightAccountVariant, MinimalRecord, MinimalRecordSeeds, VaultSeeds, - ZeroCopyRecord, ZeroCopyRecordSeeds, MINT_SIGNER_SEED_A, RECORD_SEED, VAULT_AUTH_SEED, - VAULT_SEED, + all::accounts::CreateAllParams, discriminators, LightAccountVariant, MinimalRecord, + MinimalRecordSeeds, VaultSeeds, ZeroCopyRecord, ZeroCopyRecordSeeds, MINT_SIGNER_SEED_A, + RECORD_SEED, VAULT_AUTH_SEED, VAULT_SEED, }; use solana_instruction::{AccountMeta, Instruction}; use solana_keypair::Keypair; diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_ata.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_ata.rs index 495999f63d..73630b6a3f 100644 --- a/sdk-tests/pinocchio-light-program-test/tests/test_create_ata.rs +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_ata.rs @@ -8,9 +8,7 @@ use light_program_test::{program_test::TestRpc, Rpc}; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use pinocchio_light_program_test::{ - discriminators, - ata::accounts::CreateAtaParams, - LightAccountVariant, + ata::accounts::CreateAtaParams, discriminators, LightAccountVariant, }; use solana_instruction::{AccountMeta, Instruction}; use solana_signer::Signer; diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_pda.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_pda.rs index d2c0d334dc..ac8af4db94 100644 --- a/sdk-tests/pinocchio-light-program-test/tests/test_create_pda.rs +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_pda.rs @@ -7,9 +7,8 @@ use light_client::interface::{ use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Rpc}; use pinocchio_light_program_test::{ - discriminators, - pda::accounts::CreatePdaParams, - LightAccountVariant, MinimalRecord, MinimalRecordSeeds, + discriminators, pda::accounts::CreatePdaParams, LightAccountVariant, MinimalRecord, + MinimalRecordSeeds, }; use solana_instruction::{AccountMeta, Instruction}; use solana_keypair::Keypair; diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_token_vault.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_token_vault.rs index ffe6f089cc..777bfa4bb4 100644 --- a/sdk-tests/pinocchio-light-program-test/tests/test_create_token_vault.rs +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_token_vault.rs @@ -10,9 +10,8 @@ use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use light_token_interface::state::token::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; use pinocchio_light_program_test::{ - discriminators, - token_account::accounts::CreateTokenVaultParams, - LightAccountVariant, VaultSeeds, VAULT_AUTH_SEED, VAULT_SEED, + discriminators, token_account::accounts::CreateTokenVaultParams, LightAccountVariant, + VaultSeeds, VAULT_AUTH_SEED, VAULT_SEED, }; use solana_instruction::{AccountMeta, Instruction}; use solana_pubkey::Pubkey; @@ -101,14 +100,13 @@ async fn test_create_token_vault_derive() { let token_data: Token = borsh::BorshDeserialize::deserialize(&mut &vault_iface.account.data[..]) .expect("Failed to parse vault Token"); - let vault_variant = LightAccountVariant::Vault( - light_account_pinocchio::token::TokenDataWithSeeds { + let vault_variant = + LightAccountVariant::Vault(light_account_pinocchio::token::TokenDataWithSeeds { seeds: VaultSeeds { mint: mint.to_bytes(), }, token_data, - }, - ); + }); let vault_compressed = vault_iface .compressed() .expect("cold vault must have compressed data"); @@ -138,9 +136,8 @@ async fn test_create_token_vault_derive() { .unwrap() .expect("Vault should exist on-chain after decompression"); - let actual_token: Token = - borsh::BorshDeserialize::deserialize(&mut &vault_account.data[..]) - .expect("Failed to deserialize Token after decompression"); + let actual_token: Token = borsh::BorshDeserialize::deserialize(&mut &vault_account.data[..]) + .expect("Failed to deserialize Token after decompression"); use light_compressed_account::pubkey::Pubkey as LPubkey; diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_two_mints.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_two_mints.rs index d5567a8de1..ef04c1eed6 100644 --- a/sdk-tests/pinocchio-light-program-test/tests/test_create_two_mints.rs +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_two_mints.rs @@ -9,9 +9,8 @@ use light_program_test::{program_test::TestRpc, Rpc}; use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; use light_token::instruction::{LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR}; use pinocchio_light_program_test::{ - discriminators, - two_mints::accounts::CreateTwoMintsParams, - LightAccountVariant, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, + discriminators, two_mints::accounts::CreateTwoMintsParams, LightAccountVariant, + MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B, }; use solana_instruction::{AccountMeta, Instruction}; use solana_keypair::Keypair; diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_zero_copy_record.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_zero_copy_record.rs index cbe1b9eb57..955c1bb0c0 100644 --- a/sdk-tests/pinocchio-light-program-test/tests/test_create_zero_copy_record.rs +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_zero_copy_record.rs @@ -7,9 +7,8 @@ use light_client::interface::{ use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Rpc}; use pinocchio_light_program_test::{ - account_loader::accounts::CreateZeroCopyRecordParams, - discriminators, - LightAccountVariant, ZeroCopyRecord, ZeroCopyRecordSeeds, RECORD_SEED, + account_loader::accounts::CreateZeroCopyRecordParams, discriminators, LightAccountVariant, + ZeroCopyRecord, ZeroCopyRecordSeeds, RECORD_SEED, }; use solana_instruction::{AccountMeta, Instruction}; use solana_keypair::Keypair;

>::Unpacked as LightAccountVariantTrait>::Data; + let discriminator_len = 8; + let space = discriminator_len + data_len.max( as LightAccount>::INIT_SPACE); + let rent_minimum = ctx.rent.minimum_balance(space); + + let system_program = ctx + .cpi_accounts + .system_program() + .map_err(|_| ProgramError::InvalidAccountData)?; + + // Construct rent sponsor seeds for PDA signing + let rent_sponsor_bump_bytes = [ctx.rent_sponsor_bump]; + let rent_sponsor_seeds: &[&[u8]] = &[RENT_SPONSOR_SEED, &rent_sponsor_bump_bytes]; + + create_pda_account( + ctx.rent_sponsor, + rent_sponsor_seeds, + pda_account, + rent_minimum, + space as u64, + ctx.program_id, + &seed_slices, + system_program, + )?; + + // 7. Write discriminator + data to PDA + let mut pda_data = pda_account.try_borrow_mut_data()?; + pda_data[..8] + .copy_from_slice(& as LightDiscriminator>::LIGHT_DISCRIMINATOR); + + // 8. Set decompressed state and serialize + let mut decompressed = account_data; + decompressed.set_decompressed(ctx.light_config, ctx.current_slot); + let writer = &mut &mut pda_data[8..]; + decompressed + .serialize(writer) + .map_err(|_| ProgramError::InvalidAccountData)?; + + // 9. Derive compressed address from PDA key (saves instruction data size) + let address = derive_address( + &pda_account.key.to_bytes(), + &ctx.light_config.address_space[0].to_bytes(), + &ctx.program_id.to_bytes(), + ); + + // 10. Build CompressedAccountInfo for CPI + let input = InAccountInfo { + data_hash: input_data_hash, + lamports: 0, + merkle_context: PackedMerkleContext { + merkle_tree_pubkey_index: tree_info.merkle_tree_pubkey_index, + queue_pubkey_index: tree_info.queue_pubkey_index, + leaf_index: tree_info.leaf_index, + prove_by_index: tree_info.prove_by_index, + }, + root_index: tree_info.root_index, + discriminator: as LightDiscriminator>::LIGHT_DISCRIMINATOR, + }; + + // Output is a DECOMPRESSED_PDA placeholder (same as init creates). + // This allows CompressAccountsIdempotent to re-compress the account + // in a future cycle by finding and nullifying this placeholder. + let pda_pubkey_bytes = pda_account.key.to_bytes(); + let output_data_hash = + Sha256BE::hash(&pda_pubkey_bytes).map_err(|_| ProgramError::Custom(101))?; + let output = OutAccountInfo { + lamports: 0, + output_merkle_tree_index: output_queue_index, + discriminator: DECOMPRESSED_PDA_DISCRIMINATOR, + data: pda_pubkey_bytes.to_vec(), + data_hash: output_data_hash, + }; + + // 11. Push to ctx's internal vec + ctx.compressed_account_infos.push(CompressedAccountInfo { + address: Some(address), + input: Some(input), + output: Some(output), + }); + + Ok(()) +} diff --git a/sdk-libs/sdk-interface/src/.backup/program/decompression/processor.rs b/sdk-libs/sdk-interface/src/.backup/program/decompression/processor.rs new file mode 100644 index 0000000000..85ff61b499 --- /dev/null +++ b/sdk-libs/sdk-interface/src/.backup/program/decompression/processor.rs @@ -0,0 +1,400 @@ +//! SDK generic decompression functions. +//! +//! These functions are generic over account types and can be reused by the macro. +//! The decompress flow creates PDAs from compressed state (needs validity proof, packed data, seeds). + +use anchor_lang::{ + prelude::*, + solana_program::{clock::Clock, program::invoke_signed, rent::Rent, sysvar::Sysvar}, +}; +use light_compressed_account::instruction_data::{ + cpi_context::CompressedCpiContext, + compressed_proof::ValidityProof, + with_account_info::CompressedAccountInfo, +}; +use light_sdk_types::cpi_context_write::CpiContextWriteAccounts; +use light_sdk_types::{ + cpi_accounts::CpiAccountsConfig, instruction::PackedStateTreeInfo, CpiSigner, + ACCOUNT_COMPRESSION_AUTHORITY_PDA, ACCOUNT_COMPRESSION_PROGRAM_ID, LIGHT_SYSTEM_PROGRAM_ID, + REGISTERED_PROGRAM_PDA, +}; +use light_token_interface::{ + instructions::{ + extensions::ExtensionInstructionData, + transfer2::{ + CompressedTokenInstructionDataTransfer2, Compression, MultiInputTokenDataWithContext, + }, + }, + CPI_AUTHORITY, LIGHT_TOKEN_PROGRAM_ID, TRANSFER2, +}; +use solana_instruction::Instruction; +use solana_program_error::ProgramError; + +use crate::{ + account::compression_info::CompressedAccountData, + cpi::{v2::CpiAccounts, InvokeLightSystemProgram}, + program::config::LightConfig, +}; + +// ============================================================================ +// DecompressVariant Trait (implemented by program's PackedProgramAccountVariant) +// ============================================================================ + +/// Trait for packed program account variants that support decompression. +/// +/// This trait is implemented by the program's `PackedProgramAccountVariant` enum +/// to handle type-specific dispatch during decompression. +/// +/// MACRO-GENERATED: The implementation contains a match statement routing each +/// enum variant to the appropriate `prepare_account_for_decompression` call. +pub trait DecompressVariant<'info>: AnchorSerialize + AnchorDeserialize + Clone { + /// Decompress this variant into a PDA account. + /// + /// The implementation should match on the enum variant and call + /// `prepare_account_for_decompression::(packed, pda_account, ctx)`. + fn decompress( + &self, + meta: &PackedStateTreeInfo, + pda_account: &AccountInfo<'info>, + ctx: &mut DecompressCtx<'_, 'info>, + ) -> std::result::Result<(), ProgramError>; +} + +// ============================================================================ +// Parameters and Context +// ============================================================================ + +/// Parameters for decompress_idempotent instruction. +/// Generic over the variant type - each program defines its own `PackedProgramAccountVariant`. +/// +/// Field order matches `LoadAccountsData` from light-client for compatibility. +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct DecompressIdempotentParams +where + V: AnchorSerialize + AnchorDeserialize + Clone, +{ + /// Offset into remaining_accounts where Light system accounts begin + pub system_accounts_offset: u8, + /// All account variants less than offset are pda acccounts. + /// 255 if no token accounts + pub token_accounts_offset: u8, + /// Packed index of the output queue in remaining_accounts. + pub output_queue_index: u8, + /// Validity proof for compressed account verification + pub proof: ValidityProof, + /// Accounts to decompress - wrapped in CompressedAccountData for metadata + pub accounts: Vec>, +} + +/// Context struct holding all data needed for decompression. +/// Contains internal vec for collecting CompressedAccountInfo results. +pub struct DecompressCtx<'a, 'info> { + pub program_id: &'a Pubkey, + pub cpi_accounts: &'a CpiAccounts<'a, 'info>, + pub remaining_accounts: &'a [AccountInfo<'info>], + pub rent_sponsor: &'a AccountInfo<'info>, + /// Rent sponsor PDA bump for signing + pub rent_sponsor_bump: u8, + pub light_config: &'a LightConfig, + /// Token (ctoken) rent sponsor for creating token accounts + pub ctoken_rent_sponsor: &'a AccountInfo<'info>, + /// Token (ctoken) compressible config for creating token accounts + pub ctoken_compressible_config: &'a AccountInfo<'info>, + pub rent: &'a Rent, + pub current_slot: u64, + /// Packed index of the output queue in remaining_accounts. + pub output_queue_index: u8, + /// Internal vec - dispatch functions push results here + pub compressed_account_infos: Vec, + pub in_token_data: Vec, + pub in_tlv: Option>>, + pub token_seeds: Vec>, +} + +// ============================================================================ +// Processor Function +// ============================================================================ + +/// Remaining accounts layout: +/// [0]: fee_payer (Signer, mut) +/// [1]: config (LightConfig PDA) +/// [2]: rent_sponsor (mut) +/// [system_accounts_offset..]: Light system accounts for CPI +/// [remaining_accounts.len() - num_pda_accounts..]: PDA accounts to decompress +/// +/// Runtime processor - handles all the plumbing, dispatches via DecompressVariant trait. +/// +/// **Takes raw instruction data** and deserializes internally - minimizes macro code. +/// **Uses only remaining_accounts** - no Context struct needed. +/// **Generic over V** - the program's `PackedProgramAccountVariant` enum. +pub fn process_decompress_pda_accounts_idempotent<'info, V>( + remaining_accounts: &[AccountInfo<'info>], + instruction_data: &[u8], + cpi_signer: CpiSigner, + program_id: &Pubkey, +) -> std::result::Result<(), ProgramError> +where + V: DecompressVariant<'info>, +{ + // Deserialize params internally + let params = DecompressIdempotentParams::::try_from_slice(instruction_data) + .map_err(|_| ProgramError::InvalidInstructionData)?; + + // Extract and validate accounts using shared validation + let validated_ctx = + crate::program::validation::validate_decompress_accounts(remaining_accounts, program_id)?; + let fee_payer = &validated_ctx.fee_payer; + let rent_sponsor = &validated_ctx.rent_sponsor; + let rent_sponsor_bump = validated_ctx.rent_sponsor_bump; + let light_config = validated_ctx.light_config; + + let rent = Rent::get()?; + let current_slot = Clock::get()?.slot; + + let system_accounts_offset_usize = params.system_accounts_offset as usize; + if system_accounts_offset_usize > remaining_accounts.len() { + return Err(ProgramError::InvalidInstructionData); + } + let (pda_accounts, token_accounts) = params + .accounts + .split_at_checked(params.token_accounts_offset as usize) + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + // PDA and token account infos are at the tail of remaining_accounts. + let num_hot_accounts = params.accounts.len(); + let hot_accounts_start = remaining_accounts + .len() + .checked_sub(num_hot_accounts) + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let hot_account_infos = &remaining_accounts[hot_accounts_start..]; + let (pda_account_infos, token_account_infos) = hot_account_infos + .split_at_checked(params.token_accounts_offset as usize) + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + let has_pda_accounts = !pda_accounts.is_empty(); + let has_token_accounts = !token_accounts.is_empty(); + let cpi_context = has_pda_accounts && has_token_accounts; + let config = CpiAccountsConfig { + sol_compression_recipient: false, + sol_pool_pda: false, + cpi_context, + cpi_signer, + }; + let cpi_accounts = CpiAccounts::new_with_config( + fee_payer, + &remaining_accounts[system_accounts_offset_usize..], + config, + ); + + // Token (ctoken) accounts layout in remaining_accounts: + // [0]fee_payer, [1]pda_config, [2]pda_rent_sponsor, [3]ctoken_rent_sponsor, + // [4]light_token_program, [5]cpi_authority, [6]ctoken_compressible_config + let ctoken_rent_sponsor = remaining_accounts + .get(3) + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let ctoken_compressible_config = remaining_accounts + .get(6) + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + // Build context struct with all needed data (includes internal vec) + let mut decompress_ctx = DecompressCtx { + program_id, + cpi_accounts: &cpi_accounts, + remaining_accounts, + rent_sponsor, + rent_sponsor_bump, + light_config: &light_config, + ctoken_rent_sponsor, + ctoken_compressible_config, + rent: &rent, + current_slot, + output_queue_index: params.output_queue_index, + compressed_account_infos: Vec::new(), + in_token_data: Vec::new(), + in_tlv: None, + token_seeds: Vec::new(), + }; + + // Process each account using trait dispatch on inner variant + for (pda_account, pda_account_info) in pda_accounts.iter().zip(pda_account_infos) { + pda_account.data.decompress( + &pda_account.tree_info, + pda_account_info, + &mut decompress_ctx, + )?; + } + // Process token accounts + for (token_account, token_account_info) in token_accounts.iter().zip(token_account_infos) { + token_account.data.decompress( + &token_account.tree_info, + token_account_info, + &mut decompress_ctx, + )?; + } + + if has_pda_accounts { + // CPI to Light System Program with proof + let pda_only = !cpi_context; + + if pda_only { + // Manual construction to avoid extra allocations + let instruction_data = light_compressed_account::instruction_data::with_account_info::InstructionDataInvokeCpiWithAccountInfo { + mode: 1, + bump: cpi_signer.bump, + invoking_program_id: cpi_signer.program_id.into(), + compress_or_decompress_lamports: 0, + is_compress: false, + with_cpi_context: false, + with_transaction_hash: false, + cpi_context: CompressedCpiContext::default(), + proof: params.proof.0, + new_address_params: Vec::new(), + account_infos: decompress_ctx.compressed_account_infos, + read_only_addresses: Vec::new(), + read_only_accounts: Vec::new(), + }; + instruction_data.invoke(cpi_accounts.clone())?; + } else { + { + // PDAs + tokens - write to CPI context first, tokens will execute + let authority = cpi_accounts + .authority() + .map_err(|_| ProgramError::MissingRequiredSignature)?; + let cpi_context_account = cpi_accounts + .cpi_context() + .map_err(|_| ProgramError::MissingRequiredSignature)?; + let system_cpi_accounts = CpiContextWriteAccounts { + fee_payer, + authority, + cpi_context: cpi_context_account, + cpi_signer, + }; + + // Manual construction to avoid extra allocations + let instruction_data = light_compressed_account::instruction_data::with_account_info::InstructionDataInvokeCpiWithAccountInfo { + mode: 1, + bump: cpi_signer.bump, + invoking_program_id: cpi_signer.program_id.into(), + compress_or_decompress_lamports: 0, + is_compress: false, + with_cpi_context: true, + with_transaction_hash: false, + cpi_context: CompressedCpiContext::first(), + proof: None, + new_address_params: Vec::new(), + account_infos: decompress_ctx.compressed_account_infos, + read_only_addresses: Vec::new(), + read_only_accounts: Vec::new(), + }; + instruction_data.invoke_write_to_cpi_context_first(system_cpi_accounts)?; + } + } + } + + if has_token_accounts { + let mut compressions = Vec::new(); + // Assumes is compressed to pubkey. + decompress_ctx + .in_token_data + .iter() + .for_each(|a| compressions.push(Compression::decompress(a.amount, a.mint, a.owner))); + let mut cpi = CompressedTokenInstructionDataTransfer2 { + with_transaction_hash: false, + in_token_data: decompress_ctx.in_token_data.clone(), + in_tlv: decompress_ctx.in_tlv.clone(), + with_lamports_change_account_merkle_tree_index: false, + lamports_change_account_merkle_tree_index: 0, + lamports_change_account_owner_index: 0, + output_queue: 0, + max_top_up: 0, + cpi_context: None, + compressions: Some(compressions), + proof: params.proof.0, + out_token_data: Vec::new(), + in_lamports: None, + out_lamports: None, + out_tlv: None, + }; + if has_pda_accounts { + cpi.cpi_context = Some( + light_token_interface::instructions::transfer2::CompressedCpiContext { + set_context: false, + first_set_context: false, + }, + ) + } + + // Build Transfer2 account_metas in the order the handler expects: + // [0] light_system_program (readonly) + // [1] fee_payer (signer, writable) + // [2] cpi_authority_pda (readonly) + // [3] registered_program_pda (readonly) + // [4] account_compression_authority (readonly) + // [5] account_compression_program (readonly) + // [6] system_program (readonly) + // [7] cpi_context (optional, writable) + // [N+] packed_accounts + let mut account_metas = vec![ + AccountMeta::new_readonly(Pubkey::new_from_array(LIGHT_SYSTEM_PROGRAM_ID), false), + AccountMeta::new(*fee_payer.key, true), + AccountMeta::new_readonly(Pubkey::new_from_array(CPI_AUTHORITY), false), + AccountMeta::new_readonly(Pubkey::new_from_array(REGISTERED_PROGRAM_PDA), false), + AccountMeta::new_readonly( + Pubkey::new_from_array(ACCOUNT_COMPRESSION_AUTHORITY_PDA), + false, + ), + AccountMeta::new_readonly( + Pubkey::new_from_array(ACCOUNT_COMPRESSION_PROGRAM_ID), + false, + ), + AccountMeta::new_readonly(Pubkey::default(), false), + ]; + if cpi_context { + let cpi_ctx = cpi_accounts + .cpi_context() + .map_err(|_| ProgramError::NotEnoughAccountKeys)?; + account_metas.push(AccountMeta::new(*cpi_ctx.key, false)); + } + let transfer2_packed_start = account_metas.len(); + let packed_accounts_offset = + system_accounts_offset_usize + cpi_accounts.system_accounts_end_offset(); + for account in &remaining_accounts[packed_accounts_offset..] { + account_metas.push(AccountMeta { + pubkey: *account.key, + is_signer: account.is_signer, + is_writable: account.is_writable, + }); + } + cpi.in_token_data.iter().for_each(|data| { + account_metas[data.owner as usize + transfer2_packed_start].is_signer = true; + }); + let mut instruction_data = vec![TRANSFER2]; + cpi.serialize(&mut instruction_data).unwrap(); + let instruction = Instruction { + program_id: LIGHT_TOKEN_PROGRAM_ID.into(), + accounts: account_metas, + data: instruction_data, + }; + // For ATAs, no PDA signing is needed (wallet owner signed at transaction level). + // For regular token accounts, use invoke_signed with PDA seeds. + if decompress_ctx.token_seeds.is_empty() { + // All tokens are ATAs - use regular invoke (no PDA signing needed) + anchor_lang::solana_program::program::invoke(&instruction, remaining_accounts)?; + } else { + // At least one regular token account - use invoke_signed with PDA seeds + let signer_seed_refs: Vec<&[u8]> = decompress_ctx + .token_seeds + .iter() + .map(|s| s.as_slice()) + .collect(); + + invoke_signed( + &instruction, + remaining_accounts, + &[signer_seed_refs.as_slice()], + )?; + } + } + + Ok(()) +} diff --git a/sdk-libs/sdk-interface/src/.backup/program/decompression/token.rs b/sdk-libs/sdk-interface/src/.backup/program/decompression/token.rs new file mode 100644 index 0000000000..014046af96 --- /dev/null +++ b/sdk-libs/sdk-interface/src/.backup/program/decompression/token.rs @@ -0,0 +1,149 @@ +//! Token account decompression. + +use light_sdk_types::instruction::PackedStateTreeInfo; +use light_token_interface::instructions::extensions::ExtensionInstructionData; +use solana_account_info::AccountInfo; +use solana_program_error::ProgramError; + +use super::create_token_account::{ + build_create_ata_instruction, build_create_token_account_instruction, +}; +use crate::program::{ + decompression::processor::DecompressCtx, + variant::PackedLightAccountVariantTrait, +}; + +pub fn prepare_token_account_for_decompression<'info, const SEED_COUNT: usize, P>( + packed: &P, + tree_info: &PackedStateTreeInfo, + output_queue_index: u8, + token_account_info: &AccountInfo<'info>, + ctx: &mut DecompressCtx<'_, 'info>, +) -> std::result::Result<(), ProgramError> +where + P: PackedLightAccountVariantTrait, +{ + let packed_accounts = ctx + .cpi_accounts + .packed_accounts() + .map_err(|_| ProgramError::NotEnoughAccountKeys)?; + let token_data = packed.into_in_token_data(tree_info, output_queue_index)?; + + // Get TLV extension early to detect ATA + let in_tlv: Option> = packed.into_in_tlv()?; + + // Extract ATA info from TLV if present + let ata_info = in_tlv.as_ref().and_then(|exts| { + exts.iter().find_map(|ext| { + if let ExtensionInstructionData::CompressedOnly(co) = ext { + if co.is_ata { + Some((co.bump, co.owner_index)) + } else { + None + } + } else { + None + } + }) + }); + + // Resolve mint pubkey from packed index + let mint_pubkey = packed_accounts + .get(token_data.mint as usize) + .ok_or(ProgramError::InvalidAccountData)? + .key; + + let fee_payer = ctx.cpi_accounts.fee_payer(); + + // Helper to check if token account is already initialized + // State byte at offset 108: 0=Uninitialized, 1=Initialized, 2=Frozen + const STATE_OFFSET: usize = 108; + let is_already_initialized = !token_account_info.data_is_empty() + && token_account_info.data_len() > STATE_OFFSET + && token_account_info.try_borrow_data()?[STATE_OFFSET] != 0; + + if let Some((ata_bump, wallet_owner_index)) = ata_info { + // ATA path: use invoke() without signer seeds + // Resolve wallet owner pubkey from packed index + let wallet_owner_pubkey = packed_accounts + .get(wallet_owner_index as usize) + .ok_or(ProgramError::InvalidAccountData)? + .key; + + // Idempotency check: only create ATA if it doesn't exist + // For ATAs, we still continue with decompression even if account exists + if token_account_info.data_is_empty() { + let instruction = build_create_ata_instruction( + wallet_owner_pubkey, + mint_pubkey, + fee_payer.key, + token_account_info.key, + ata_bump, + ctx.ctoken_compressible_config.key, + ctx.ctoken_rent_sponsor.key, + ctx.light_config.write_top_up, + )?; + + // Invoke WITHOUT signer seeds - ATA is derived from light token program, not our program + anchor_lang::solana_program::program::invoke(&instruction, ctx.remaining_accounts)?; + } + + // Don't extend token_seeds for ATAs (invoke, not invoke_signed) + } else { + // Regular token vault path: use invoke_signed with PDA seeds + // For regular vaults, if already initialized, skip BOTH creation AND decompression (full idempotency) + if is_already_initialized { + solana_msg::msg!("Token vault is already decompressed, skipping"); + return Ok(()); + } + + let bump = &[packed.bump()]; + let seeds = packed + .seed_refs_with_bump(packed_accounts, bump) + .map_err(|_| ProgramError::InvalidSeeds)?; + + // Derive owner pubkey from constant owner_seeds + let owner = packed.derive_owner(); + + let signer_seeds: Vec<&[u8]> = seeds.iter().copied().collect(); + + let instruction = build_create_token_account_instruction( + token_account_info.key, + mint_pubkey, + &owner, + fee_payer.key, + ctx.ctoken_compressible_config.key, + ctx.ctoken_rent_sponsor.key, + ctx.light_config.write_top_up, + &signer_seeds, + ctx.program_id, + )?; + + // Invoke with PDA seeds + anchor_lang::solana_program::program::invoke_signed( + &instruction, + ctx.remaining_accounts, + &[signer_seeds.as_slice()], + )?; + + // Push seeds for the Transfer2 CPI (needed for invoke_signed) + ctx.token_seeds.extend(seeds.iter().map(|s| s.to_vec())); + } + + // Push token data for the Transfer2 CPI (common for both ATA and regular paths) + ctx.in_token_data.push(token_data); + + // Push TLV data + if let Some(ctx_in_tlv) = ctx.in_tlv.as_mut() { + ctx_in_tlv.push(in_tlv.unwrap_or_default()); + } else if let Some(in_tlv) = in_tlv { + let mut ctx_in_tlv = vec![]; + for _ in 0..ctx.in_token_data.len() - 1 { + ctx_in_tlv.push(vec![]); + } + ctx_in_tlv.push(in_tlv); + ctx.in_tlv = Some(ctx_in_tlv); + } + + Ok(()) +} diff --git a/sdk-libs/sdk-interface/src/.backup/program/variant.rs b/sdk-libs/sdk-interface/src/.backup/program/variant.rs new file mode 100644 index 0000000000..9b3ab25e37 --- /dev/null +++ b/sdk-libs/sdk-interface/src/.backup/program/variant.rs @@ -0,0 +1,187 @@ +//! Traits for decompression variant construction and manual Light Protocol implementation. +//! +//! This module contains traits for typed compressed account handling: +//! - Base traits (`IntoVariant`) - always available +//! - Variant traits (`LightAccountVariantTrait`, `PackedLightAccountVariantTrait`) - anchor-gated +//! - Token seed traits (`UnpackedTokenSeeds`, `PackedTokenSeeds`) - anchor-gated + +// --- Base traits (always available) --- + +#[cfg(feature = "anchor")] +use anchor_lang::error::Error; +#[cfg(not(feature = "anchor"))] +use solana_program_error::ProgramError as Error; + +/// Trait for seeds that can construct a compressed account variant. +/// +/// Implemented by generated `XxxSeeds` structs (e.g., `UserRecordSeeds`). +/// The macro generates impls that deserialize account data and verify seeds match. +/// +/// # Example (generated code) +/// ```ignore +/// impl IntoVariant for UserRecordSeeds { +/// fn into_variant(self, data: &[u8]) -> Result { +/// RentFreeAccountVariant::user_record(data, self) +/// } +/// } +/// ``` +pub trait IntoVariant { + /// Construct variant from compressed account data bytes and these seeds. + /// + /// # Arguments + /// * `data` - Raw compressed account data bytes + /// + /// # Returns + /// The constructed variant on success, or an error if: + /// - Deserialization fails + /// - Seed verification fails (data.* seeds don't match account data) + fn into_variant(self, data: &[u8]) -> Result; +} + +// --- Anchor-gated variant traits --- + +#[cfg(feature = "anchor")] +mod anchor_traits { + use anchor_lang::prelude::*; + use light_sdk_types::instruction::PackedStateTreeInfo; + use light_token_interface::instructions::{ + extensions::ExtensionInstructionData, transfer2::MultiInputTokenDataWithContext, + }; + use solana_program_error::ProgramError; + + use crate::account::light_account::AccountType; + + /// Trait for unpacked compressed account variants with seeds. + /// + /// Implementations are generated by the `#[light_program]` macro for each + /// account type marked with `#[light_account(init)]`. + /// + /// # Type Parameters + /// * `SEED_COUNT` - Number of seeds including bump for CPI signing + /// * `Seeds` - The seeds struct type (e.g., `UserRecordSeeds`) + /// * `Data` - The account data type (e.g., `UserRecord`) + /// * `Packed` - The packed variant type for serialization + pub trait LightAccountVariantTrait: + Sized + Clone + AnchorSerialize + AnchorDeserialize + { + /// The program ID that owns accounts of this variant type. + const PROGRAM_ID: Pubkey; + + /// The seeds struct type containing seed values. + type Seeds; + + /// The account data type. + type Data; + + /// The packed variant type for efficient serialization. + type Packed: PackedLightAccountVariantTrait; + + /// Get a reference to the account data. + fn data(&self) -> &Self::Data; + + /// Get seed values as owned byte vectors for PDA derivation. + fn seed_vec(&self) -> Vec>; + + /// Get seed references with bump for CPI signing. + /// Returns a fixed-size array that can be passed to invoke_signed. + fn seed_refs_with_bump<'a>(&'a self, bump_storage: &'a [u8; 1]) -> [&'a [u8]; SEED_COUNT]; + + /// Derive the PDA address and bump seed using PROGRAM_ID. + fn derive_pda(&self) -> (Pubkey, u8) { + let seeds = self.seed_vec(); + let seed_slices: Vec<&[u8]> = seeds.iter().map(|s| s.as_slice()).collect(); + Pubkey::find_program_address(&seed_slices, &Self::PROGRAM_ID) + } + } + + /// Trait for packed compressed account variants. + /// + /// Packed variants use u8 indices instead of 32-byte Pubkeys for efficient + /// serialization. They can be unpacked back to full variants using account info. + #[allow(clippy::wrong_self_convention)] + pub trait PackedLightAccountVariantTrait: + Sized + Clone + AnchorSerialize + AnchorDeserialize + { + /// The unpacked variant type with full Pubkey values. + type Unpacked: LightAccountVariantTrait; + + /// The account type (Pda, Token, Ata, etc.) for dispatch. + const ACCOUNT_TYPE: AccountType; + + /// Get the PDA bump seed. + fn bump(&self) -> u8; + + /// Unpack this variant by resolving u8 indices to Pubkeys. + fn unpack(&self, accounts: &[AccountInfo]) -> Result; + + /// Get seed references with bump for CPI signing. + /// Resolves u8 indices to pubkey refs from accounts slice. + fn seed_refs_with_bump<'a>( + &'a self, + accounts: &'a [AccountInfo], + bump_storage: &'a [u8; 1], + ) -> std::result::Result<[&'a [u8]; SEED_COUNT], ProgramError>; + + /// Extract token data for compressed token CPI. + /// + /// Returns the packed token data needed for the token transfer instruction. + /// Only meaningful for token account variants; PDA variants should not override. + fn into_in_token_data( + &self, + tree_info: &PackedStateTreeInfo, + output_queue_index: u8, + ) -> Result; + + /// Extract TLV extension data for compressed token CPI. + /// + /// Returns extension instruction data if the token account has extensions. + /// Only meaningful for token account variants; PDA variants return `None`. + fn into_in_tlv(&self) -> Result>>; + + /// Derive the owner pubkey from constant owner_seeds and program ID. + /// Only meaningful for token account variants; PDA variants return default. + fn derive_owner(&self) -> Pubkey { + Pubkey::default() + } + } + + /// Trait for unpacked token seed structs. + /// + /// Generated by the `#[light_program]` macro on per-variant seed structs + /// (e.g., `TokenVaultSeeds`). Provides seed-specific behavior for the blanket + /// `LightAccountVariantTrait` impl on `TokenDataWithSeeds`. + pub trait UnpackedTokenSeeds: + Clone + std::fmt::Debug + AnchorSerialize + AnchorDeserialize + { + /// The packed seeds type. + type Packed: PackedTokenSeeds; + + const PROGRAM_ID: Pubkey; + fn seed_vec(&self) -> Vec>; + fn seed_refs_with_bump<'a>(&'a self, bump_storage: &'a [u8; 1]) -> [&'a [u8]; N]; + } + + /// Trait for packed token seed structs. + /// + /// Generated by the `#[light_program]` macro on per-variant packed seed structs + /// (e.g., `PackedTokenVaultSeeds`). Provides seed-specific behavior for the blanket + /// `PackedLightAccountVariantTrait` impl on `TokenDataWithPackedSeeds`. + pub trait PackedTokenSeeds: + crate::account::pack::Unpack + Clone + std::fmt::Debug + AnchorSerialize + AnchorDeserialize + { + fn bump(&self) -> u8; + fn seed_refs_with_bump<'a>( + &'a self, + accounts: &'a [AccountInfo], + bump_storage: &'a [u8; 1], + ) -> std::result::Result<[&'a [u8]; N], ProgramError>; + + /// Derive the owner pubkey from constant owner_seeds and program ID. + fn derive_owner(&self) -> Pubkey; + } +} + +#[cfg(feature = "anchor")] +pub use anchor_traits::{ + LightAccountVariantTrait, PackedLightAccountVariantTrait, PackedTokenSeeds, UnpackedTokenSeeds, +}; diff --git a/sdk-libs/sdk-interface/src/account/compression_info.rs b/sdk-libs/sdk-interface/src/account/compression_info.rs index 724d00e917..915d53f739 100644 --- a/sdk-libs/sdk-interface/src/account/compression_info.rs +++ b/sdk-libs/sdk-interface/src/account/compression_info.rs @@ -2,26 +2,17 @@ extern crate alloc; use alloc::borrow::Cow; use bytemuck::{Pod, Zeroable}; +use light_account_checks::AccountInfoTrait; use light_compressible::rent::RentConfig; use light_sdk_types::instruction::PackedStateTreeInfo; -use solana_account_info::AccountInfo; -use solana_program_error::ProgramError; -use super::pack::Unpack; -use crate::{AnchorDeserialize, AnchorSerialize}; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, AnchorSerialize, AnchorDeserialize)] -#[repr(u8)] -pub enum AccountState { - Initialized, - Frozen, -} +use crate::{error::LightPdaError, AnchorDeserialize, AnchorSerialize}; pub trait HasCompressionInfo { - fn compression_info(&self) -> Result<&CompressionInfo, ProgramError>; - fn compression_info_mut(&mut self) -> Result<&mut CompressionInfo, ProgramError>; + fn compression_info(&self) -> Result<&CompressionInfo, LightPdaError>; + fn compression_info_mut(&mut self) -> Result<&mut CompressionInfo, LightPdaError>; fn compression_info_mut_opt(&mut self) -> &mut Option; - fn set_compression_info_none(&mut self) -> Result<(), ProgramError>; + fn set_compression_info_none(&mut self) -> Result<(), LightPdaError>; } /// Simple field accessor trait for types with a `compression_info: Option` field. @@ -49,7 +40,7 @@ pub trait CompressionInfoField { fn write_decompressed_info_to_slice( data: &mut [u8], current_slot: u64, - ) -> Result<(), ProgramError> { + ) -> Result<(), LightPdaError> { use crate::AnchorSerialize; let info = CompressionInfo { @@ -71,7 +62,7 @@ pub trait CompressionInfoField { }; if data.len() < offset + option_size { - return Err(ProgramError::AccountDataTooSmall); + return Err(LightPdaError::AccountDataTooSmall); } let target = &mut data[offset..offset + option_size]; @@ -79,30 +70,30 @@ pub trait CompressionInfoField { target[0] = 1; // Write CompressionInfo info.serialize(&mut &mut target[1..]) - .map_err(|_| ProgramError::BorshIoError("compression_info serialize failed".into()))?; + .map_err(|e| LightPdaError::BorshIo(e.to_string()))?; Ok(()) } } impl HasCompressionInfo for T { - fn compression_info(&self) -> Result<&CompressionInfo, ProgramError> { + fn compression_info(&self) -> Result<&CompressionInfo, LightPdaError> { self.compression_info_field() .as_ref() - .ok_or(crate::error::LightPdaError::MissingCompressionInfo.into()) + .ok_or(LightPdaError::MissingCompressionInfo) } - fn compression_info_mut(&mut self) -> Result<&mut CompressionInfo, ProgramError> { + fn compression_info_mut(&mut self) -> Result<&mut CompressionInfo, LightPdaError> { self.compression_info_field_mut() .as_mut() - .ok_or(crate::error::LightPdaError::MissingCompressionInfo.into()) + .ok_or(LightPdaError::MissingCompressionInfo) } fn compression_info_mut_opt(&mut self) -> &mut Option { self.compression_info_field_mut() } - fn set_compression_info_none(&mut self) -> Result<(), ProgramError> { + fn set_compression_info_none(&mut self) -> Result<(), LightPdaError> { *self.compression_info_field_mut() = None; Ok(()) } @@ -291,27 +282,21 @@ impl CompressionInfo { /// Top up rent on write if needed and transfer lamports from payer to account. /// This is the standard pattern for all write operations on compressible PDAs. + /// Generic over AccountInfoTrait to work with both solana and pinocchio. /// /// # Arguments /// * `account_info` - The PDA account to top up /// * `payer_info` - The payer account (will be debited) - /// * `system_program_info` - The System Program account for CPI - /// - /// # Returns - /// * `Ok(())` if top-up succeeded or was not needed - /// * `Err(ProgramError)` if transfer failed - pub fn top_up_rent<'a>( + pub fn top_up_rent( &self, - account_info: &AccountInfo<'a>, - payer_info: &AccountInfo<'a>, - system_program_info: &AccountInfo<'a>, - ) -> Result<(), ProgramError> { - use solana_sysvar::{rent::Rent, Sysvar}; - + account_info: &AI, + payer_info: &AI, + ) -> Result<(), LightPdaError> { let bytes = account_info.data_len() as u64; let current_lamports = account_info.lamports(); - let current_slot = solana_sysvar::clock::Clock::get()?.slot; - let rent_exemption_lamports = Rent::get()?.minimum_balance(bytes as usize); + let current_slot = AI::get_current_slot().map_err(LightPdaError::AccountCheck)?; + let rent_exemption_lamports = + AI::get_min_rent_balance(bytes as usize).map_err(LightPdaError::AccountCheck)?; let top_up = self.calculate_top_up_lamports( bytes, @@ -321,8 +306,10 @@ impl CompressionInfo { ); if top_up > 0 { - // Use System Program CPI to transfer lamports - transfer_lamports_cpi(payer_info, account_info, system_program_info, top_up)?; + // Use System Program CPI to transfer lamports (payer is a signer, pass empty seeds) + payer_info + .transfer_lamports_cpi(account_info, top_up, &[]) + .map_err(LightPdaError::AccountCheck)?; } Ok(()) @@ -338,11 +325,6 @@ impl Space for CompressionInfo { const INIT_SPACE: usize = core::mem::size_of::(); } -#[cfg(feature = "anchor")] -impl anchor_lang::Space for CompressionInfo { - const INIT_SPACE: usize = ::INIT_SPACE; -} - /// Space required for Option when Some (1 byte discriminator + INIT_SPACE). /// Use this constant in account space calculations. pub const OPTION_COMPRESSION_INFO_SPACE: usize = 1 + CompressionInfo::INIT_SPACE; @@ -358,33 +340,25 @@ pub struct CompressedAccountData { pub data: T, } -impl Unpack for CompressedAccountData> { - type Unpacked = Vec; - - fn unpack(&self, _remaining_accounts: &[AccountInfo]) -> Result { - unimplemented!() - } -} - /// Claim completed-epoch rent to the provided rent sponsor and update last_claimed_slot. /// Returns Some(claimed) if any lamports were claimed; None if account is compressible or nothing to claim. -pub fn claim_completed_epoch_rent<'info, A>( - account_info: &AccountInfo<'info>, +/// Generic over AccountInfoTrait to work with both solana and pinocchio. +pub fn claim_completed_epoch_rent( + account_info: &AI, account_data: &mut A, - rent_sponsor: &AccountInfo<'info>, -) -> Result, ProgramError> + rent_sponsor: &AI, +) -> Result, LightPdaError> where + AI: AccountInfoTrait, A: HasCompressionInfo, { use light_compressible::rent::{AccountRentState, SLOTS_PER_EPOCH}; - use solana_sysvar::{rent::Rent, Sysvar}; - let current_slot = solana_sysvar::clock::Clock::get()?.slot; + let current_slot = AI::get_current_slot().map_err(LightPdaError::AccountCheck)?; let bytes = account_info.data_len() as u64; let current_lamports = account_info.lamports(); - let rent_exemption_lamports = Rent::get() - .map_err(|_| ProgramError::Custom(0))? - .minimum_balance(bytes as usize); + let rent_exemption_lamports = + AI::get_min_rent_balance(bytes as usize).map_err(LightPdaError::AccountCheck)?; let ci = account_data.compression_info_mut()?; let state = AccountRentState { @@ -413,54 +387,13 @@ where .saturating_add(completed_epochs * SLOTS_PER_EPOCH), ); - // Transfer lamports to rent sponsor - { - let mut src = account_info - .try_borrow_mut_lamports() - .map_err(|_| ProgramError::Custom(0))?; - let mut dst = rent_sponsor - .try_borrow_mut_lamports() - .map_err(|_| ProgramError::Custom(0))?; - let new_src = src - .checked_sub(amount) - .ok_or(ProgramError::InsufficientFunds)?; - let new_dst = dst.checked_add(amount).ok_or(ProgramError::Custom(0))?; - **src = new_src; - **dst = new_dst; - } + // Transfer lamports to rent sponsor (direct lamport manipulation, no CPI needed + // since the program owns the account) + account_info + .transfer_lamports(rent_sponsor, amount) + .map_err(LightPdaError::AccountCheck)?; return Ok(Some(amount)); } } Ok(Some(0)) } - -/// Transfer lamports from one account to another using System Program CPI. -/// This is required when transferring from accounts owned by the System Program. -fn transfer_lamports_cpi<'a>( - from: &AccountInfo<'a>, - to: &AccountInfo<'a>, - system_program: &AccountInfo<'a>, - lamports: u64, -) -> Result<(), ProgramError> { - use solana_cpi::invoke; - use solana_instruction::{AccountMeta, Instruction}; - use solana_pubkey::Pubkey; - - // System Program Transfer instruction discriminator: 2 (u32 little-endian) - let mut instruction_data = vec![2, 0, 0, 0]; - instruction_data.extend_from_slice(&lamports.to_le_bytes()); - - let transfer_instruction = Instruction { - program_id: Pubkey::default(), // System Program ID - accounts: vec![ - AccountMeta::new(*from.key, true), - AccountMeta::new(*to.key, false), - ], - data: instruction_data, - }; - - invoke( - &transfer_instruction, - &[from.clone(), to.clone(), system_program.clone()], - ) -} diff --git a/sdk-libs/sdk-interface/src/account/light_account.rs b/sdk-libs/sdk-interface/src/account/light_account.rs index 923c11878f..13e5f56b70 100644 --- a/sdk-libs/sdk-interface/src/account/light_account.rs +++ b/sdk-libs/sdk-interface/src/account/light_account.rs @@ -1,14 +1,13 @@ //! LightAccount trait definition for compressible account data structs. -use anchor_lang::prelude::*; +use light_account_checks::{ + packed_accounts::ProgramPackedAccounts, AccountInfoTrait, AccountMetaTrait, +}; use light_hasher::DataHasher; -use solana_program_error::ProgramError; use crate::{ - account::compression_info::CompressionInfo, - instruction::PackedAccounts, - program::config::LightConfig, - light_account_checks::{packed_accounts::ProgramPackedAccounts, AccountInfoTrait}, + account::compression_info::CompressionInfo, error::LightPdaError, + program::config::LightConfig, AnchorDeserialize, AnchorSerialize, }; pub enum AccountType { @@ -48,15 +47,18 @@ pub trait LightAccount: /// Set compression info to decompressed state (used at decompression) fn set_decompressed(&mut self, config: &LightConfig, current_slot: u64); - /// Convert to packed form (Pubkeys -> indices) - fn pack( + /// Convert to packed form (Pubkeys -> indices). + /// Generic over AccountMetaTrait for runtime-agnostic packing. + #[cfg(not(target_os = "solana"))] + fn pack( &self, - accounts: &mut PackedAccounts, - ) -> std::result::Result; + accounts: &mut crate::instruction::PackedAccounts, + ) -> Result; - /// Convert from packed form (indices -> Pubkeys) - fn unpack( + /// Convert from packed form (indices -> Pubkeys). + /// Generic over AccountInfoTrait for runtime-agnostic unpacking. + fn unpack( packed: &Self::Packed, - accounts: &ProgramPackedAccounts, - ) -> std::result::Result; + accounts: &ProgramPackedAccounts, + ) -> Result; } diff --git a/sdk-libs/sdk-interface/src/account/mod.rs b/sdk-libs/sdk-interface/src/account/mod.rs index 0d37bc0fe0..127e0ce2c2 100644 --- a/sdk-libs/sdk-interface/src/account/mod.rs +++ b/sdk-libs/sdk-interface/src/account/mod.rs @@ -4,11 +4,8 @@ //! including compression info, decompression, and closing. pub mod compression_info; +pub mod light_account; pub mod pack; pub mod pda_seeds; - -#[cfg(feature = "anchor")] -pub mod light_account; - -#[cfg(feature = "anchor")] +#[cfg(feature = "token")] pub mod token_seeds; diff --git a/sdk-libs/sdk-interface/src/account/pack.rs b/sdk-libs/sdk-interface/src/account/pack.rs index 976e4a1d88..5e0e50fb58 100644 --- a/sdk-libs/sdk-interface/src/account/pack.rs +++ b/sdk-libs/sdk-interface/src/account/pack.rs @@ -1,24 +1,26 @@ //! Pack and Unpack traits for converting between full Pubkeys and u8 indices. -use solana_account_info::AccountInfo; -use solana_program_error::ProgramError; +use light_account_checks::AccountInfoTrait; + +use crate::error::LightPdaError; #[cfg(not(target_os = "solana"))] -use crate::instruction::PackedAccounts; -#[cfg(not(target_os = "solana"))] -use crate::AnchorSerialize; +use light_account_checks::AccountMetaTrait; /// Replace 32-byte Pubkeys with 1-byte indices to save space. /// If your type has no Pubkeys, just return self. #[cfg(not(target_os = "solana"))] -pub trait Pack { - type Packed: AnchorSerialize + Clone + std::fmt::Debug; +pub trait Pack { + type Packed: crate::AnchorSerialize + Clone + core::fmt::Debug; - fn pack(&self, remaining_accounts: &mut PackedAccounts) -> Result; + fn pack( + &self, + remaining_accounts: &mut crate::instruction::PackedAccounts, + ) -> Result; } -pub trait Unpack { +pub trait Unpack { type Unpacked; - fn unpack(&self, remaining_accounts: &[AccountInfo]) -> Result; + fn unpack(&self, remaining_accounts: &[AI]) -> Result; } diff --git a/sdk-libs/sdk-interface/src/account/pda_seeds.rs b/sdk-libs/sdk-interface/src/account/pda_seeds.rs index fc088eb498..a22d85a78c 100644 --- a/sdk-libs/sdk-interface/src/account/pda_seeds.rs +++ b/sdk-libs/sdk-interface/src/account/pda_seeds.rs @@ -1,7 +1,6 @@ -// --- cpi-context-gated traits (from decompress_runtime.rs) --- +//! PDA seed derivation traits. -use solana_program_error::ProgramError; -use solana_pubkey::Pubkey; +use crate::error::LightPdaError; /// Trait for account variants that can be checked for token or PDA type. pub trait HasTokenVariant { @@ -13,8 +12,8 @@ pub trait HasTokenVariant { pub trait PdaSeedDerivation { fn derive_pda_seeds_with_accounts( &self, - program_id: &Pubkey, + program_id: &[u8; 32], accounts: &A, seed_params: &S, - ) -> Result<(Vec>, Pubkey), ProgramError>; + ) -> Result<(Vec>, [u8; 32]), LightPdaError>; } diff --git a/sdk-libs/sdk-interface/src/account/token_seeds.rs b/sdk-libs/sdk-interface/src/account/token_seeds.rs index c6cb257279..2a7b98f527 100644 --- a/sdk-libs/sdk-interface/src/account/token_seeds.rs +++ b/sdk-libs/sdk-interface/src/account/token_seeds.rs @@ -1,3 +1,8 @@ +//! Token seed types for packed/unpacked token account variants. +//! +//! Provides `TokenDataWithSeeds`, `PackedTokenData`, and `TokenDataWithPackedSeeds` +//! along with Pack/Unpack impls and blanket impls for variant traits. + use light_compressed_account::compressed_account::PackedMerkleContext; use light_sdk_types::instruction::PackedStateTreeInfo; pub use light_token_interface::{ @@ -10,20 +15,21 @@ pub use light_token_interface::{ AccountState, Token, TokenDataVersion, }, }; -use solana_account_info::AccountInfo; -use solana_program_error::ProgramError; -use solana_pubkey::Pubkey; + +use light_account_checks::AccountInfoTrait; use super::pack::Unpack; -// Pack trait and PackedAccounts only available off-chain (client-side packing) +#[cfg(not(target_os = "solana"))] +use light_account_checks::AccountMetaTrait; #[cfg(not(target_os = "solana"))] use crate::{account::pack::Pack, instruction::PackedAccounts}; use crate::{ + account::light_account::AccountType, + error::LightPdaError, program::variant::{ LightAccountVariantTrait, PackedLightAccountVariantTrait, PackedTokenSeeds, UnpackedTokenSeeds, }, - account::light_account::AccountType, AnchorDeserialize, AnchorSerialize, }; @@ -32,12 +38,13 @@ pub struct TokenDataWithSeeds { pub seeds: S, pub token_data: Token, } + #[repr(C)] #[derive(Debug, Copy, Clone, Default, PartialEq, AnchorSerialize, AnchorDeserialize)] pub struct PackedTokenData { pub owner: u8, pub amount: u64, - pub has_delegate: bool, // Optional delegate is set + pub has_delegate: bool, pub delegate: u8, pub mint: u8, pub version: u8, @@ -45,26 +52,91 @@ pub struct PackedTokenData { #[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone)] pub struct TokenDataWithPackedSeeds< - S: Unpack + AnchorSerialize + AnchorDeserialize + Clone + std::fmt::Debug, + S: AnchorSerialize + AnchorDeserialize + Clone + core::fmt::Debug, > { pub seeds: S, pub token_data: PackedTokenData, pub extension: Option, } +// ============================================================================= +// Helper: unpack token data from packed indices +// ============================================================================= + +fn unpack_token_data_from_packed( + packed: &PackedTokenData, + extension: &Option, + accounts: &[AI], +) -> Result { + let owner_key = accounts + .get(packed.owner as usize) + .ok_or(LightPdaError::InvalidInstructionData)? + .key(); + let mint_key = accounts + .get(packed.mint as usize) + .ok_or(LightPdaError::InvalidInstructionData)? + .key(); + let delegate = if packed.has_delegate { + let delegate_key = accounts + .get(packed.delegate as usize) + .ok_or(LightPdaError::InvalidInstructionData)? + .key(); + Some(light_compressed_account::Pubkey::from(delegate_key)) + } else { + None + }; + + let extensions = extension.map(|ext| { + vec![ExtensionStruct::CompressedOnly(CompressedOnlyExtension { + delegated_amount: ext.delegated_amount, + withheld_transfer_fee: ext.withheld_transfer_fee, + is_ata: ext.is_ata as u8, + })] + }); + + let state = extension.map_or(AccountState::Initialized, |ext| { + if ext.is_frozen { + AccountState::Frozen + } else { + AccountState::Initialized + } + }); + + let delegated_amount = extension.map_or(0, |ext| ext.delegated_amount); + + Ok(Token { + mint: light_compressed_account::Pubkey::from(mint_key), + owner: light_compressed_account::Pubkey::from(owner_key), + amount: packed.amount, + delegate, + state, + is_native: None, + delegated_amount, + close_authority: None, + account_type: TokenDataVersion::ShaFlat as u8, + extensions, + }) +} + +// ============================================================================= +// Pack impl (client-side only) +// ============================================================================= + #[cfg(not(target_os = "solana"))] -impl Pack for TokenDataWithSeeds +impl Pack for TokenDataWithSeeds where - S: Pack, - S::Packed: Unpack + AnchorDeserialize + AnchorSerialize + Clone + std::fmt::Debug, + S: Pack, + S::Packed: AnchorDeserialize + AnchorSerialize + Clone + core::fmt::Debug, { type Packed = TokenDataWithPackedSeeds; - fn pack(&self, remaining_accounts: &mut PackedAccounts) -> Result { + fn pack( + &self, + remaining_accounts: &mut PackedAccounts, + ) -> Result { let seeds = self.seeds.pack(remaining_accounts)?; - let owner_index = remaining_accounts - .insert_or_get(Pubkey::new_from_array(self.token_data.owner.to_bytes())); + let owner_index = remaining_accounts.insert_or_get(self.token_data.owner.to_bytes()); let token_data = PackedTokenData { owner: owner_index, @@ -73,14 +145,12 @@ where delegate: self .token_data .delegate - .map(|d| remaining_accounts.insert_or_get(Pubkey::new_from_array(d.to_bytes()))) + .map(|d| remaining_accounts.insert_or_get(d.to_bytes())) .unwrap_or(0), - mint: remaining_accounts - .insert_or_get(Pubkey::new_from_array(self.token_data.mint.to_bytes())), + mint: remaining_accounts.insert_or_get(self.token_data.mint.to_bytes()), version: TokenDataVersion::ShaFlat as u8, }; - // Extract CompressedOnly extension from Token state if present. let extension = self.token_data.extensions.as_ref().and_then(|exts| { exts.iter().find_map(|ext| { if let ExtensionStruct::CompressedOnly(co) = ext { @@ -107,67 +177,20 @@ where } } -impl Unpack for TokenDataWithPackedSeeds +// ============================================================================= +// Unpack impl +// ============================================================================= + +impl Unpack for TokenDataWithPackedSeeds where - S: Unpack + AnchorSerialize + AnchorDeserialize + Clone + std::fmt::Debug, + S: Unpack + AnchorSerialize + AnchorDeserialize + Clone + core::fmt::Debug, { - type Unpacked = TokenDataWithSeeds; + type Unpacked = TokenDataWithSeeds<>::Unpacked>; - fn unpack(&self, remaining_accounts: &[AccountInfo]) -> Result { + fn unpack(&self, remaining_accounts: &[AI]) -> Result { let seeds = self.seeds.unpack(remaining_accounts)?; - - let owner_key = remaining_accounts - .get(self.token_data.owner as usize) - .ok_or(ProgramError::InvalidAccountData)? - .key; - let mint_key = remaining_accounts - .get(self.token_data.mint as usize) - .ok_or(ProgramError::InvalidAccountData)? - .key; - let delegate = if self.token_data.has_delegate { - let delegate_key = remaining_accounts - .get(self.token_data.delegate as usize) - .ok_or(ProgramError::InvalidAccountData)? - .key; - Some(light_compressed_account::Pubkey::from( - delegate_key.to_bytes(), - )) - } else { - None - }; - - // Reconstruct extensions from instruction extension data. - let extensions = self.extension.map(|ext| { - vec![ExtensionStruct::CompressedOnly(CompressedOnlyExtension { - delegated_amount: ext.delegated_amount, - withheld_transfer_fee: ext.withheld_transfer_fee, - is_ata: ext.is_ata as u8, - })] - }); - - let state = self.extension.map_or(AccountState::Initialized, |ext| { - if ext.is_frozen { - AccountState::Frozen - } else { - AccountState::Initialized - } - }); - - let delegated_amount = self.extension.map_or(0, |ext| ext.delegated_amount); - - let token_data = Token { - mint: light_compressed_account::Pubkey::from(mint_key.to_bytes()), - owner: light_compressed_account::Pubkey::from(owner_key.to_bytes()), - amount: self.token_data.amount, - delegate, - state, - is_native: None, - delegated_amount, - close_authority: None, - account_type: TokenDataVersion::ShaFlat as u8, - extensions, - }; - + let token_data = + unpack_token_data_from_packed(&self.token_data, &self.extension, remaining_accounts)?; Ok(TokenDataWithSeeds { seeds, token_data }) } } @@ -181,9 +204,9 @@ where impl LightAccountVariantTrait for TokenDataWithSeeds where S: UnpackedTokenSeeds, - S::Packed: PackedTokenSeeds + Unpack, + S::Packed: PackedTokenSeeds, { - const PROGRAM_ID: Pubkey = S::PROGRAM_ID; + const PROGRAM_ID: [u8; 32] = S::PROGRAM_ID; type Seeds = S; type Data = Token; type Packed = TokenDataWithPackedSeeds; @@ -203,7 +226,7 @@ where impl PackedLightAccountVariantTrait for TokenDataWithPackedSeeds where - S: PackedTokenSeeds, + S: PackedTokenSeeds + AnchorSerialize + AnchorDeserialize + Clone + core::fmt::Debug, S::Unpacked: UnpackedTokenSeeds, { type Unpacked = TokenDataWithSeeds; @@ -214,15 +237,21 @@ where self.seeds.bump() } - fn unpack(&self, accounts: &[AccountInfo]) -> anchor_lang::Result { - ::unpack(self, accounts).map_err(anchor_lang::error::Error::from) + fn unpack( + &self, + accounts: &[AI], + ) -> Result { + let seeds = self.seeds.unpack_seeds::(accounts)?; + let token_data = + unpack_token_data_from_packed(&self.token_data, &self.extension, accounts)?; + Ok(TokenDataWithSeeds { seeds, token_data }) } - fn seed_refs_with_bump<'a>( + fn seed_refs_with_bump<'a, AI: AccountInfoTrait>( &'a self, - accounts: &'a [AccountInfo], + accounts: &'a [AI], bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; N], ProgramError> { + ) -> Result<[&'a [u8]; N], LightPdaError> { self.seeds.seed_refs_with_bump(accounts, bump_storage) } @@ -230,7 +259,7 @@ where &self, tree_info: &PackedStateTreeInfo, output_queue_index: u8, - ) -> anchor_lang::Result { + ) -> Result { Ok(MultiInputTokenDataWithContext { amount: self.token_data.amount, mint: self.token_data.mint, @@ -248,14 +277,14 @@ where }) } - fn into_in_tlv(&self) -> anchor_lang::Result>> { + fn into_in_tlv(&self) -> Result>, LightPdaError> { Ok(self .extension .as_ref() .map(|ext| vec![ExtensionInstructionData::CompressedOnly(*ext)])) } - fn derive_owner(&self) -> Pubkey { + fn derive_owner(&self) -> [u8; 32] { self.seeds.derive_owner() } } diff --git a/sdk-libs/sdk-interface/src/accounts/finalize.rs b/sdk-libs/sdk-interface/src/accounts/finalize.rs index 685d6ba8ae..a795da87d1 100644 --- a/sdk-libs/sdk-interface/src/accounts/finalize.rs +++ b/sdk-libs/sdk-interface/src/accounts/finalize.rs @@ -9,7 +9,7 @@ //! This two-phase design allows mints to be created BEFORE the instruction body runs, //! so they can be used during the instruction (e.g., for vault creation, minting tokens). -use solana_account_info::AccountInfo; +use light_account_checks::AccountInfoTrait; /// Trait for pre-initialization operations (mint creation). /// @@ -21,13 +21,13 @@ use solana_account_info::AccountInfo; /// mints and PDAs. /// /// # Type Parameters -/// * `'info` - The account info lifetime +/// * `AI` - AccountInfoTrait implementation (solana or pinocchio) /// * `P` - The instruction params type (from `#[instruction(params: P)]`) -pub trait LightPreInit<'info, P> { +pub trait LightPreInit { /// Execute pre-initialization operations (mint creation). fn light_pre_init( &mut self, - remaining_accounts: &[AccountInfo<'info>], + remaining_accounts: &[AI], params: &P, ) -> Result; } @@ -35,13 +35,13 @@ pub trait LightPreInit<'info, P> { /// Trait for finalizing compression operations on accounts. /// /// # Type Parameters -/// * `'info` - The account info lifetime +/// * `AI` - AccountInfoTrait implementation (solana or pinocchio) /// * `P` - The instruction params type (from `#[instruction(params: P)]`) -pub trait LightFinalize<'info, P> { +pub trait LightFinalize { /// Execute compression finalization. fn light_finalize( &mut self, - remaining_accounts: &[AccountInfo<'info>], + remaining_accounts: &[AI], params: &P, has_pre_init: bool, ) -> Result<(), crate::error::LightPdaError>; diff --git a/sdk-libs/sdk-interface/src/accounts/init_compressed_account.rs b/sdk-libs/sdk-interface/src/accounts/init_compressed_account.rs index c1c4908b8a..a1428df368 100644 --- a/sdk-libs/sdk-interface/src/accounts/init_compressed_account.rs +++ b/sdk-libs/sdk-interface/src/accounts/init_compressed_account.rs @@ -1,5 +1,6 @@ //! Helper functions for preparing compressed accounts on init. +use light_account_checks::AccountInfoTrait; use light_compressed_account::{ address::derive_address, instruction_data::{ @@ -9,15 +10,9 @@ use light_compressed_account::{ }; use light_compressible::DECOMPRESSED_PDA_DISCRIMINATOR; use light_hasher::{errors::HasherError, sha256::Sha256BE, Hasher}; -use light_sdk_types::constants::RENT_SPONSOR_SEED; use light_sdk_types::instruction::PackedAddressTreeInfo; -use solana_account_info::AccountInfo; -use solana_program_error::ProgramError; -use solana_pubkey::Pubkey; -use solana_sysvar::{rent::Rent, Sysvar}; use crate::error::LightPdaError; -use light_account_checks::checks::check_mut; /// Prepare a compressed account for a PDA during initialization. /// @@ -26,47 +21,28 @@ use light_account_checks::checks::check_mut; /// 2. Creating NewAddressParamsAssignedPacked for the address tree /// 3. Building CompressedAccountInfo with hashed PDA pubkey data /// -/// Uses: -/// - Discriminator: `[255, 255, 255, 255, 255, 255, 255, 0]` - marks this as a -/// rent-free PDA placeholder (distinct from actual account data discriminators) -/// - Data: PDA pubkey bytes (32 bytes) - allows lookup/verification of the -/// compressed account by its on-chain PDA address -/// -/// # Arguments -/// * `pda_pubkey` - The PDA's pubkey (used as address seed and data) -/// * `address_tree_pubkey` - The address Merkle tree pubkey -/// * `address_tree_info` - Packed address tree info from CreateAccountsProof -/// * `output_tree_index` - Output state tree index -/// * `assigned_account_index` - Index in the accounts array (for assigned_account_index) -/// * `program_id` - The program ID (owner of the compressed account) -/// * `new_address_params` - Vector to push new address params into -/// * `account_infos` - Vector to push compressed account info into +/// Uses `[u8; 32]` for all pubkey parameters - framework-agnostic. #[inline(never)] #[allow(clippy::too_many_arguments)] pub fn prepare_compressed_account_on_init( - pda_pubkey: &Pubkey, - address_tree_pubkey: &Pubkey, + pda_pubkey: &[u8; 32], + address_tree_pubkey: &[u8; 32], address_tree_info: &PackedAddressTreeInfo, output_tree_index: u8, assigned_account_index: u8, - program_id: &Pubkey, + program_id: &[u8; 32], new_address_params: &mut Vec, account_infos: &mut Vec, ) -> Result<(), HasherError> { // Data is always the PDA pubkey bytes - let data = pda_pubkey.to_bytes().to_vec(); + let data = pda_pubkey.to_vec(); // Derive compressed address from PDA pubkey seed - let address_seed = pda_pubkey.to_bytes(); - let address = derive_address( - &address_seed, - &address_tree_pubkey.to_bytes(), - &program_id.to_bytes(), - ); + let address = derive_address(pda_pubkey, address_tree_pubkey, program_id); // Create and push new address params new_address_params.push(NewAddressParamsAssignedPacked { - seed: address_seed, + seed: *pda_pubkey, address_merkle_tree_account_index: address_tree_info.address_merkle_tree_pubkey_index, address_queue_account_index: address_tree_info.address_queue_pubkey_index, address_merkle_tree_root_index: address_tree_info.root_index, @@ -93,134 +69,32 @@ pub fn prepare_compressed_account_on_init( Ok(()) } -/// Safe variant that validates PDA derivation before preparing compressed account. +/// Reimburse the fee_payer for rent paid during PDA creation. /// -/// # Arguments -/// * `pda_pubkey` - The PDA's pubkey (used as address seed and data) -/// * `pda_seeds` - Seeds used to derive the PDA (without bump) -/// * `pda_bump` - The bump seed for the PDA -/// * `address_tree_pubkey` - The address Merkle tree pubkey -/// * `address_tree_info` - Packed address tree info from CreateAccountsProof -/// * `output_tree_index` - Output state tree index -/// * `assigned_account_index` - Index in the accounts array -/// * `program_id` - The program ID (owner of the compressed account) -/// * `new_address_params` - Vector to push new address params into -/// * `account_infos` - Vector to push compressed account info into -#[inline(never)] -#[allow(clippy::too_many_arguments)] -pub fn prepare_compressed_account_on_init_checked( - pda_pubkey: &Pubkey, - pda_seeds: &[&[u8]], - pda_bump: u8, - address_tree_pubkey: &Pubkey, - address_tree_info: &PackedAddressTreeInfo, - output_tree_index: u8, - assigned_account_index: u8, - program_id: &Pubkey, - new_address_params: &mut Vec, - account_infos: &mut Vec, -) -> Result<(), ProgramError> { - // Validate PDA derivation - let bump_slice = [pda_bump]; - let seeds_with_bump: Vec<&[u8]> = pda_seeds - .iter() - .copied() - .chain(std::iter::once(bump_slice.as_slice())) - .collect(); - - let expected_pda = Pubkey::create_program_address(&seeds_with_bump, program_id) - .map_err(|_| ProgramError::InvalidSeeds)?; - - if pda_pubkey != &expected_pda { - solana_msg::msg!( - "PDA key mismatch: expected {:?}, got {:?}", - expected_pda, - pda_pubkey - ); - return Err(ProgramError::InvalidSeeds); - } - - prepare_compressed_account_on_init( - pda_pubkey, - address_tree_pubkey, - address_tree_info, - output_tree_index, - assigned_account_index, - program_id, - new_address_params, - account_infos, - ) - .map_err(|e| LightPdaError::from(e).into()) -} - -/// Reimburse the fee payer for rent paid during PDA initialization. -/// -/// When using Anchor's `#[account(init)]` with `#[light_account(init)]`, the fee_payer -/// pays for rent-exemption. Since these become rent-free compressed accounts, this function -/// transfers the total rent amount back to the fee_payer from the program's rent sponsor PDA. +/// During Anchor `init`, the fee_payer pays rent for PDA accounts. +/// This function transfers the total rent amount from the program-owned +/// rent_sponsor PDA back to the fee_payer. /// -/// # Arguments -/// * `created_accounts` - Slice of AccountInfo for the PDAs that were created -/// * `fee_payer` - The account that paid for rent (will receive reimbursement) -/// * `rent_sponsor` - The program's rent sponsor PDA (must be mutable, pays reimbursement) -/// * `program_id` - The program ID (for deriving rent sponsor PDA bump) -/// -/// # Seeds -/// The rent sponsor PDA is derived using: `[RENT_SPONSOR_SEED]` -pub fn reimburse_rent<'info>( - created_accounts: &[AccountInfo<'info>], - fee_payer: &AccountInfo<'info>, - rent_sponsor: &AccountInfo<'info>, - program_id: &Pubkey, -) -> Result<(), ProgramError> { - if created_accounts.is_empty() { - return Ok(()); - } - - // Calculate total rent-exemption for all created accounts - let rent = Rent::get()?; - let total_lamports: u64 = created_accounts - .iter() - .map(|acc| rent.minimum_balance(acc.data_len())) - .sum(); - - if total_lamports == 0 { - return Ok(()); +/// Uses direct lamport manipulation (no CPI) since rent_sponsor is owned +/// by the calling program. +pub fn reimburse_rent( + created_accounts: &[AI], + fee_payer: &AI, + rent_sponsor: &AI, + _program_id: &[u8; 32], +) -> Result<(), LightPdaError> { + let mut total_rent: u64 = 0; + for account in created_accounts { + total_rent = total_rent + .checked_add(account.lamports()) + .ok_or(LightPdaError::ConstraintViolation)?; } - // Derive rent sponsor bump - let (expected_rent_sponsor, rent_sponsor_bump) = - Pubkey::find_program_address(&[RENT_SPONSOR_SEED], program_id); - - // Verify the rent sponsor account matches expected PDA - if rent_sponsor.key != &expected_rent_sponsor { - solana_msg::msg!( - "rent_sponsor mismatch: expected {:?}, got {:?}", - expected_rent_sponsor, - rent_sponsor.key - ); - return Err(LightPdaError::InvalidRentSponsor.into()); + if total_rent > 0 { + rent_sponsor + .transfer_lamports(fee_payer, total_rent) + .map_err(LightPdaError::AccountCheck)?; } - // Validate accounts are writable for transfer - check_mut(rent_sponsor).map_err(ProgramError::from)?; - check_mut(fee_payer).map_err(ProgramError::from)?; - - // Transfer from rent sponsor to fee payer - let transfer_ix = solana_system_interface::instruction::transfer( - rent_sponsor.key, - fee_payer.key, - total_lamports, - ); - - let bump_bytes = [rent_sponsor_bump]; - let rent_sponsor_seeds: &[&[u8]] = &[RENT_SPONSOR_SEED, &bump_bytes]; - - solana_cpi::invoke_signed( - &transfer_ix, - &[rent_sponsor.clone(), fee_payer.clone()], - &[rent_sponsor_seeds], - )?; - Ok(()) } diff --git a/sdk-libs/sdk-interface/src/accounts/mod.rs b/sdk-libs/sdk-interface/src/accounts/mod.rs index c9a07a4127..3e313e9503 100644 --- a/sdk-libs/sdk-interface/src/accounts/mod.rs +++ b/sdk-libs/sdk-interface/src/accounts/mod.rs @@ -3,6 +3,5 @@ //! This module contains traits and functions for context struct handling, //! validation, and initialization at the accounts struct level. -pub mod create_pda; pub mod finalize; pub mod init_compressed_account; diff --git a/sdk-libs/sdk-interface/src/cpi/account.rs b/sdk-libs/sdk-interface/src/cpi/account.rs index 00c425fba8..a356fcd640 100644 --- a/sdk-libs/sdk-interface/src/cpi/account.rs +++ b/sdk-libs/sdk-interface/src/cpi/account.rs @@ -1,85 +1,150 @@ -use light_sdk_types::cpi_context_write::CpiContextWriteAccounts; - -use crate::cpi::v2::get_account_metas_from_config_cpi_context; -use crate::cpi::v1::{ - lowlevel::{get_account_metas_from_config, CpiInstructionConfig}, - CpiAccounts, -}; -use solana_account_info::AccountInfo; -use solana_instruction::AccountMeta; -use solana_program_error::ProgramError; +//! Generic CPI accounts trait and implementations. -/// Trait for types that can provide account information for CPI calls -pub trait CpiAccountsTrait<'info> { - /// Convert to a vector of AccountInfo references - fn to_account_infos(&self) -> Vec>; +use light_account_checks::{AccountInfoTrait, CpiMeta}; +use light_sdk_types::cpi_accounts::v2::{CompressionCpiAccountIndex, CpiAccounts, PROGRAM_ACCOUNTS_LEN}; +use light_sdk_types::cpi_context_write::CpiContextWriteAccounts; - /// Generate account metas - fn to_account_metas(&self) -> Result, ProgramError>; +use crate::error::LightPdaError; - /// Get the mode for the instruction (0 for v1, 1 for v2, None if unknown) +/// Trait for types that can provide account infos and metas for Light system program CPI. +/// +/// Generic over `AI: AccountInfoTrait` to work with both solana and pinocchio backends. +pub trait CpiAccountsTrait { + fn to_account_infos(&self) -> Vec; + fn to_account_metas(&self) -> Result, LightPdaError>; fn get_mode(&self) -> Option; } -// Implementation for CpiAccounts -impl<'info> CpiAccountsTrait<'info> for CpiAccounts<'_, 'info> { - fn to_account_infos(&self) -> Vec> { - self.to_account_infos() +/// Build `CpiMeta` vec from `CpiAccounts` (v2 mode=1). +impl<'a, AI: AccountInfoTrait + Clone> CpiAccountsTrait for CpiAccounts<'a, AI> { + fn to_account_infos(&self) -> Vec { + CpiAccounts::to_account_infos(self) } - fn to_account_metas(&self) -> Result, ProgramError> { - let config = CpiInstructionConfig::try_from(self).map_err(ProgramError::from)?; - Ok(get_account_metas_from_config(config)) + fn to_account_metas(&self) -> Result, LightPdaError> { + to_cpi_metas(self) } fn get_mode(&self) -> Option { - Some(0) // v1 mode + Some(1) // v2 mode } } -// Implementation for &[AccountInfo] -impl<'info> CpiAccountsTrait<'info> for &[AccountInfo<'info>] { - fn to_account_infos(&self) -> Vec> { - self.to_vec() +/// Build `CpiMeta` vec from `CpiContextWriteAccounts` (3-account CPI context write). +impl<'a, AI: AccountInfoTrait + Clone> CpiAccountsTrait for CpiContextWriteAccounts<'a, AI> { + fn to_account_infos(&self) -> Vec { + self.to_account_infos().to_vec() } - fn to_account_metas(&self) -> Result, ProgramError> { - // For raw account info slices, create simple account metas - // preserving the original signer and writable flags - Ok(self - .iter() - .map(|account| AccountMeta { - pubkey: *account.key, - is_signer: account.is_signer, - is_writable: account.is_writable, - }) - .collect()) + fn to_account_metas(&self) -> Result, LightPdaError> { + let infos = self.to_account_info_refs(); + Ok(vec![ + CpiMeta { + pubkey: infos[0].key(), + is_signer: true, + is_writable: true, + }, + CpiMeta { + pubkey: infos[1].key(), + is_signer: true, + is_writable: false, + }, + CpiMeta { + pubkey: infos[2].key(), + is_signer: false, + is_writable: true, + }, + ]) } fn get_mode(&self) -> Option { - None // Unknown mode for raw slices + Some(1) // v2 mode } } -// Implementation for CpiContextWriteAccounts -impl<'a, 'info> CpiAccountsTrait<'info> for CpiContextWriteAccounts<'a, AccountInfo<'info>> { - fn to_account_infos(&self) -> Vec> { - vec![ - self.fee_payer.clone(), - self.authority.clone(), - self.cpi_context.clone(), - ] +/// Convert `CpiAccounts` to a vec of `CpiMeta`, preserving the account layout +/// expected by the Light system program. +fn to_cpi_metas( + cpi_accounts: &CpiAccounts<'_, AI>, +) -> Result, LightPdaError> { + let mut metas = + Vec::with_capacity(1 + cpi_accounts.account_infos().len() - PROGRAM_ACCOUNTS_LEN); + + metas.push(CpiMeta { + pubkey: cpi_accounts.fee_payer().key(), + is_signer: true, + is_writable: true, + }); + metas.push(CpiMeta { + pubkey: cpi_accounts.authority()?.key(), + is_signer: true, + is_writable: false, + }); + metas.push(CpiMeta { + pubkey: cpi_accounts.registered_program_pda()?.key(), + is_signer: false, + is_writable: false, + }); + metas.push(CpiMeta { + pubkey: cpi_accounts.account_compression_authority()?.key(), + is_signer: false, + is_writable: false, + }); + metas.push(CpiMeta { + pubkey: cpi_accounts.account_compression_program()?.key(), + is_signer: false, + is_writable: false, + }); + metas.push(CpiMeta { + pubkey: cpi_accounts.system_program()?.key(), + is_signer: false, + is_writable: false, + }); + + let accounts = cpi_accounts.account_infos(); + let mut index = CompressionCpiAccountIndex::SolPoolPda as usize; + + if cpi_accounts.config().sol_pool_pda { + let account = cpi_accounts.get_account_info(index)?; + metas.push(CpiMeta { + pubkey: account.key(), + is_signer: false, + is_writable: true, + }); + index += 1; } - fn to_account_metas(&self) -> Result, ProgramError> { - // Use the helper function to generate the account metas - let metas = get_account_metas_from_config_cpi_context(self.clone()); - Ok(metas.to_vec()) + if cpi_accounts.config().sol_compression_recipient { + let account = cpi_accounts.get_account_info(index)?; + metas.push(CpiMeta { + pubkey: account.key(), + is_signer: false, + is_writable: true, + }); + index += 1; } - fn get_mode(&self) -> Option { - // CPI context write accounts always use v2 mode (1) - // This type requires both the `v2` and `cpi-context` features - Some(1) + if cpi_accounts.config().cpi_context { + let account = cpi_accounts.get_account_info(index)?; + metas.push(CpiMeta { + pubkey: account.key(), + is_signer: false, + is_writable: true, + }); + index += 1; } + assert_eq!(cpi_accounts.system_accounts_end_offset(), index); + + let tree_accounts = + accounts + .get(index..) + .ok_or(LightPdaError::CpiAccountsIndexOutOfBounds(index))?; + tree_accounts.iter().for_each(|acc| { + metas.push(CpiMeta { + pubkey: acc.key(), + is_signer: acc.is_signer(), + is_writable: acc.is_writable(), + }); + }); + Ok(metas) } diff --git a/sdk-libs/sdk-interface/src/cpi/impls.rs b/sdk-libs/sdk-interface/src/cpi/impls.rs new file mode 100644 index 0000000000..a76e746219 --- /dev/null +++ b/sdk-libs/sdk-interface/src/cpi/impls.rs @@ -0,0 +1,88 @@ +//! LightCpi trait implementations for v2 instruction data types. + +use light_compressed_account::{ + instruction_data::{ + compressed_proof::ValidityProof, + with_account_info::InstructionDataInvokeCpiWithAccountInfo, + with_readonly::InstructionDataInvokeCpiWithReadOnly, + }, + CpiSigner, +}; + +use super::instruction::LightCpi; + +impl LightCpi for InstructionDataInvokeCpiWithReadOnly { + fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self { + Self { + bump: cpi_signer.bump, + invoking_program_id: cpi_signer.program_id.into(), + proof: proof.into(), + mode: 1, + ..Default::default() + } + } + fn write_to_cpi_context_first(self) -> Self { + self.write_to_cpi_context_first() + } + fn write_to_cpi_context_set(self) -> Self { + self.write_to_cpi_context_set() + } + fn execute_with_cpi_context(self) -> Self { + self.execute_with_cpi_context() + } + fn get_mode(&self) -> u8 { + self.mode + } + fn get_with_cpi_context(&self) -> bool { + self.with_cpi_context + } + fn get_cpi_context( + &self, + ) -> &light_compressed_account::instruction_data::cpi_context::CompressedCpiContext { + &self.cpi_context + } + fn get_bump(&self) -> u8 { + self.bump + } + fn has_read_only_accounts(&self) -> bool { + !self.read_only_accounts.is_empty() + } +} + +impl LightCpi for InstructionDataInvokeCpiWithAccountInfo { + fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self { + Self { + bump: cpi_signer.bump, + invoking_program_id: cpi_signer.program_id.into(), + proof: proof.into(), + mode: 1, + ..Default::default() + } + } + fn write_to_cpi_context_first(self) -> Self { + self.write_to_cpi_context_first() + } + fn write_to_cpi_context_set(self) -> Self { + self.write_to_cpi_context_set() + } + fn execute_with_cpi_context(self) -> Self { + self.execute_with_cpi_context() + } + fn get_mode(&self) -> u8 { + self.mode + } + fn get_with_cpi_context(&self) -> bool { + self.with_cpi_context + } + fn get_cpi_context( + &self, + ) -> &light_compressed_account::instruction_data::cpi_context::CompressedCpiContext { + &self.cpi_context + } + fn get_bump(&self) -> u8 { + self.bump + } + fn has_read_only_accounts(&self) -> bool { + !self.read_only_accounts.is_empty() + } +} diff --git a/sdk-libs/sdk-interface/src/cpi/instruction.rs b/sdk-libs/sdk-interface/src/cpi/instruction.rs index 6965da52ff..a49cd44acf 100644 --- a/sdk-libs/sdk-interface/src/cpi/instruction.rs +++ b/sdk-libs/sdk-interface/src/cpi/instruction.rs @@ -1,12 +1,13 @@ use light_compressed_account::instruction_data::compressed_proof::ValidityProof; -/// Trait for Light CPI instruction types. +/// Base trait for Light CPI instruction types. /// /// This is the framework-agnostic version that provides CPI builder methods -/// without referencing light-sdk-specific types like `LightAccount`. -/// The `with_light_account` and `with_light_account_poseidon` methods are -/// provided by light-sdk, which depends on this crate. -pub trait LightCpiInstruction: Sized { +/// without referencing SDK-specific types like `LightAccount`. +/// +/// Each SDK (`light-sdk`, `light-sdk-pinocchio`) defines its own +/// `LightCpiInstruction` trait that includes `with_light_account`. +pub trait LightCpi: Sized { /// Creates a new CPI instruction builder with a validity proof. /// /// # Arguments diff --git a/sdk-libs/sdk-interface/src/cpi/invoke.rs b/sdk-libs/sdk-interface/src/cpi/invoke.rs index 768d6ccfed..1c270ea7d5 100644 --- a/sdk-libs/sdk-interface/src/cpi/invoke.rs +++ b/sdk-libs/sdk-interface/src/cpi/invoke.rs @@ -1,187 +1,170 @@ +//! Generic Light system program invocation. + pub use light_compressed_account::LightInstructionData; +use light_account_checks::{AccountInfoTrait, CpiMeta}; use light_sdk_types::constants::{CPI_AUTHORITY_PDA_SEED, LIGHT_SYSTEM_PROGRAM_ID}; -use solana_instruction::AccountMeta; -use solana_account_info::AccountInfo; -use solana_instruction::Instruction; -use solana_program_error::ProgramError; use crate::{ - cpi::{account::CpiAccountsTrait, instruction::LightCpiInstruction}, + cpi::{account::CpiAccountsTrait, instruction::LightCpi}, error::LightPdaError, }; -use solana_cpi::invoke_signed; +/// Trait for invoking the Light system program via CPI. +/// +/// Provides `invoke`, `invoke_write_to_cpi_context_first`, +/// `invoke_write_to_cpi_context_set`, and `invoke_execute_cpi_context` methods. +/// +/// Blanket-implemented for all types implementing `LightInstructionData + LightCpi`. pub trait InvokeLightSystemProgram { - fn invoke<'info>(self, accounts: impl CpiAccountsTrait<'info>) -> Result<(), ProgramError>; - fn invoke_write_to_cpi_context_first<'info>( + fn invoke( + self, + accounts: impl CpiAccountsTrait, + ) -> Result<(), LightPdaError>; + fn invoke_write_to_cpi_context_first( self, - accounts: impl CpiAccountsTrait<'info>, - ) -> Result<(), ProgramError>; - fn invoke_write_to_cpi_context_set<'info>( + accounts: impl CpiAccountsTrait, + ) -> Result<(), LightPdaError>; + fn invoke_write_to_cpi_context_set( self, - accounts: impl CpiAccountsTrait<'info>, - ) -> Result<(), ProgramError>; - fn invoke_execute_cpi_context<'info>( + accounts: impl CpiAccountsTrait, + ) -> Result<(), LightPdaError>; + fn invoke_execute_cpi_context( self, - accounts: impl CpiAccountsTrait<'info>, - ) -> Result<(), ProgramError>; + accounts: impl CpiAccountsTrait, + ) -> Result<(), LightPdaError>; } -// Blanket implementation for types that implement both LightInstructionData and LightCpiInstruction impl InvokeLightSystemProgram for T where - T: LightInstructionData + LightCpiInstruction, + T: LightInstructionData + LightCpi, { - fn invoke<'info>(self, accounts: impl CpiAccountsTrait<'info>) -> Result<(), ProgramError> { + fn invoke( + self, + accounts: impl CpiAccountsTrait, + ) -> Result<(), LightPdaError> { + // Check if CPI context operations are being attempted { - // Check if CPI context operations are being attempted use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; if self.get_with_cpi_context() || *self.get_cpi_context() == CompressedCpiContext::set() || *self.get_cpi_context() == CompressedCpiContext::first() { - solana_msg::msg!( - "CPI context operations not supported in invoke(). Use invoke_write_to_cpi_context_first(), invoke_write_to_cpi_context_set(), or invoke_execute_cpi_context() instead" - ); - return Err(ProgramError::InvalidInstructionData); + return Err(LightPdaError::InvalidInstructionData); } } // Validate mode consistency if let Some(account_mode) = accounts.get_mode() { if account_mode != self.get_mode() { - solana_msg::msg!( - "Mode mismatch: accounts have mode {} but instruction data has mode {}", - account_mode, - self.get_mode() - ); - return Err(ProgramError::InvalidInstructionData); + return Err(LightPdaError::InvalidInstructionData); } } - // Serialize instruction data with discriminator - let data = self - .data() - .map_err(LightPdaError::from) - .map_err(ProgramError::from)?; - - // Get account infos and metas + let data = self.data().map_err(LightPdaError::from)?; let account_infos = accounts.to_account_infos(); let account_metas = accounts.to_account_metas()?; - let instruction = Instruction { - program_id: LIGHT_SYSTEM_PROGRAM_ID.into(), - accounts: account_metas, - data, - }; - invoke_light_system_program(&account_infos, instruction, self.get_bump()) + invoke_light_system_program::(&account_infos, &account_metas, &data, self.get_bump()) } - fn invoke_write_to_cpi_context_first<'info>( + + fn invoke_write_to_cpi_context_first( self, - accounts: impl CpiAccountsTrait<'info>, - ) -> Result<(), ProgramError> { + accounts: impl CpiAccountsTrait, + ) -> Result<(), LightPdaError> { let instruction_data = self.write_to_cpi_context_first(); inner_invoke_write_to_cpi_context_typed(instruction_data, accounts) } - fn invoke_write_to_cpi_context_set<'info>( + + fn invoke_write_to_cpi_context_set( self, - accounts: impl CpiAccountsTrait<'info>, - ) -> Result<(), ProgramError> { + accounts: impl CpiAccountsTrait, + ) -> Result<(), LightPdaError> { let instruction_data = self.write_to_cpi_context_set(); inner_invoke_write_to_cpi_context_typed(instruction_data, accounts) } - fn invoke_execute_cpi_context<'info>( + + fn invoke_execute_cpi_context( self, - accounts: impl CpiAccountsTrait<'info>, - ) -> Result<(), ProgramError> { + accounts: impl CpiAccountsTrait, + ) -> Result<(), LightPdaError> { let instruction_data = self.execute_with_cpi_context(); - // Serialize instruction data with discriminator - let data = instruction_data - .data() - .map_err(LightPdaError::from) - .map_err(ProgramError::from)?; - // Get account infos and metas + let data = instruction_data.data().map_err(LightPdaError::from)?; let account_infos = accounts.to_account_infos(); let account_metas = accounts.to_account_metas()?; - let instruction = Instruction { - program_id: LIGHT_SYSTEM_PROGRAM_ID.into(), - accounts: account_metas, - data, - }; - invoke_light_system_program(&account_infos, instruction, instruction_data.get_bump()) + invoke_light_system_program::( + &account_infos, + &account_metas, + &data, + instruction_data.get_bump(), + ) } } -// Generic inner helper for write_to_cpi_context operations -#[inline(always)] -fn inner_invoke_write_to_cpi_context_typed<'info, T>( +/// Inner helper for write_to_cpi_context operations. +fn inner_invoke_write_to_cpi_context_typed( instruction_data: T, - accounts: impl CpiAccountsTrait<'info>, -) -> Result<(), ProgramError> + accounts: impl CpiAccountsTrait, +) -> Result<(), LightPdaError> where - T: LightInstructionData + LightCpiInstruction, + AI: AccountInfoTrait + Clone, + T: LightInstructionData + LightCpi, { - // Check if read-only accounts are present if instruction_data.has_read_only_accounts() { - solana_msg::msg!( - "Read-only accounts are not supported in write_to_cpi_context operations. Use invoke_execute_cpi_context() instead." - ); - return Err(LightPdaError::ReadOnlyAccountsNotSupportedInCpiContext.into()); + return Err(LightPdaError::ReadOnlyAccountsNotSupportedInCpiContext); } - // Serialize instruction data with discriminator - let data = instruction_data - .data() - .map_err(LightPdaError::from) - .map_err(ProgramError::from)?; - - // Get account infos and metas + let data = instruction_data.data().map_err(LightPdaError::from)?; let account_infos = accounts.to_account_infos(); - // Extract account pubkeys from account_infos - // Assuming order: [fee_payer, authority, cpi_context, ...] if account_infos.len() < 3 { - return Err(ProgramError::NotEnoughAccountKeys); + return Err(LightPdaError::NotEnoughAccountKeys); } - let instruction = Instruction { - program_id: LIGHT_SYSTEM_PROGRAM_ID.into(), - accounts: vec![ - AccountMeta { - pubkey: *account_infos[0].key, // fee_payer - is_writable: true, - is_signer: true, - }, - AccountMeta { - pubkey: *account_infos[1].key, // authority - is_writable: false, - is_signer: true, - }, - AccountMeta { - pubkey: *account_infos[2].key, // cpi_context - is_writable: true, - is_signer: false, - }, - ], - data, - }; - - invoke_light_system_program(&account_infos, instruction, instruction_data.get_bump()) + let account_metas = vec![ + CpiMeta { + pubkey: account_infos[0].key(), + is_signer: true, + is_writable: true, + }, + CpiMeta { + pubkey: account_infos[1].key(), + is_signer: true, + is_writable: false, + }, + CpiMeta { + pubkey: account_infos[2].key(), + is_signer: false, + is_writable: true, + }, + ]; + + invoke_light_system_program::( + &account_infos, + &account_metas, + &data, + instruction_data.get_bump(), + ) } /// Low-level function to invoke the Light system program with a PDA signer. /// -/// **Note**: This is a low-level function. In most cases, you should use the -/// [`InvokeLightSystemProgram`] trait methods instead, which provide a higher-level -/// interface with better type safety and ergonomics. +/// Uses `AI::invoke_cpi()` to be generic over the runtime backend. #[inline(always)] -pub fn invoke_light_system_program( - account_infos: &[AccountInfo], - instruction: Instruction, +pub fn invoke_light_system_program( + account_infos: &[AI], + account_metas: &[CpiMeta], + data: &[u8], bump: u8, -) -> Result<(), ProgramError> { - let signer_seeds = [CPI_AUTHORITY_PDA_SEED, &[bump]]; - invoke_signed(&instruction, account_infos, &[signer_seeds.as_slice()]) +) -> Result<(), LightPdaError> { + let signer_seeds: &[&[u8]] = &[CPI_AUTHORITY_PDA_SEED, &[bump]]; + AI::invoke_cpi( + &LIGHT_SYSTEM_PROGRAM_ID, + data, + account_metas, + account_infos, + &[signer_seeds], + ) + .map_err(|_| LightPdaError::CpiFailed) } diff --git a/sdk-libs/sdk-interface/src/cpi/mod.rs b/sdk-libs/sdk-interface/src/cpi/mod.rs index ff7301083e..2377bd8480 100644 --- a/sdk-libs/sdk-interface/src/cpi/mod.rs +++ b/sdk-libs/sdk-interface/src/cpi/mod.rs @@ -1,18 +1,16 @@ +//! Generic CPI module for Light system program invocation. //! -//! -//! To create, update, or close compressed accounts, -//! programs need to invoke the light system program via cross program invocation (cpi). +//! Uses v2 `CpiAccounts<'a, T: AccountInfoTrait>` from light-sdk-types. +//! All CPI calls go through `AI::invoke_cpi()` for framework independence. -mod account; +pub mod account; +pub mod impls; mod instruction; pub mod invoke; -pub mod v1; -pub mod v2; - -pub use account::*; -pub use instruction::*; -pub use invoke::InvokeLightSystemProgram; +pub use account::CpiAccountsTrait; +pub use instruction::LightCpi; +pub use invoke::{invoke_light_system_program, InvokeLightSystemProgram}; pub use light_compressed_account::instruction_data::traits::LightInstructionData; -/// Contains program id, derived cpi signer, and bump, pub use light_sdk_types::{cpi_accounts::CpiAccountsConfig, CpiSigner}; +// TODO: move all of this to light-sdk-types diff --git a/sdk-libs/sdk-interface/src/error.rs b/sdk-libs/sdk-interface/src/error.rs index 4e694789bc..ad6b3066fe 100644 --- a/sdk-libs/sdk-interface/src/error.rs +++ b/sdk-libs/sdk-interface/src/error.rs @@ -18,9 +18,6 @@ pub enum LightPdaError { MissingCompressionInfo, #[error("Rent sponsor account does not match the expected PDA from config")] InvalidRentSponsor, - #[cfg(feature = "solana")] - #[error("Program error: {0}")] - ProgramError(#[from] solana_program_error::ProgramError), #[error("Borsh IO error: {0}")] BorshIo(String), #[error("CPI accounts index out of bounds: {0}")] @@ -29,17 +26,22 @@ pub enum LightPdaError { ReadOnlyAccountsNotSupportedInCpiContext, #[error("Compressed account error: {0}")] CompressedAccountError(#[from] CompressedAccountError), + #[error("Account data too small")] + AccountDataTooSmall, + #[error("Invalid instruction data")] + InvalidInstructionData, + #[error("Invalid seeds")] + InvalidSeeds, + #[error("CPI invocation failed")] + CpiFailed, + #[error("Not enough account keys")] + NotEnoughAccountKeys, + #[error("Missing required signature")] + MissingRequiredSignature, } pub type Result = core::result::Result; -#[cfg(feature = "solana")] -impl From for solana_program_error::ProgramError { - fn from(e: LightPdaError) -> Self { - solana_program_error::ProgramError::Custom(u32::from(e)) - } -} - impl From for LightPdaError { fn from(e: LightSdkTypesError) -> Self { match e { @@ -62,26 +64,16 @@ impl From for u32 { LightPdaError::Hasher(e) => e.into(), LightPdaError::MissingCompressionInfo => 17003, LightPdaError::InvalidRentSponsor => 17004, - #[cfg(feature = "solana")] - LightPdaError::ProgramError(e) => u64::from(e) as u32, LightPdaError::BorshIo(_) => 17005, LightPdaError::CpiAccountsIndexOutOfBounds(_) => 17006, LightPdaError::ReadOnlyAccountsNotSupportedInCpiContext => 17007, LightPdaError::CompressedAccountError(e) => e.into(), + LightPdaError::AccountDataTooSmall => 17008, + LightPdaError::InvalidInstructionData => 17009, + LightPdaError::InvalidSeeds => 17010, + LightPdaError::CpiFailed => 17011, + LightPdaError::NotEnoughAccountKeys => 17012, + LightPdaError::MissingRequiredSignature => 17013, } } } - -#[cfg(feature = "solana")] -impl From for LightPdaError { - fn from(_e: core::cell::BorrowError) -> Self { - LightPdaError::AccountCheck(AccountError::BorrowAccountDataFailed) - } -} - -#[cfg(feature = "solana")] -impl From for LightPdaError { - fn from(_e: core::cell::BorrowMutError) -> Self { - LightPdaError::AccountCheck(AccountError::BorrowAccountDataFailed) - } -} diff --git a/sdk-libs/sdk-interface/src/instruction/mod.rs b/sdk-libs/sdk-interface/src/instruction/mod.rs index f23027164e..49b45eefaa 100644 --- a/sdk-libs/sdk-interface/src/instruction/mod.rs +++ b/sdk-libs/sdk-interface/src/instruction/mod.rs @@ -1,34 +1,10 @@ -// Only available off-chain (client-side) - contains sorting code that exceeds BPF stack limits -#[cfg(not(target_os = "solana"))] -mod pack_accounts; - -// Stub type for on-chain compilation - allows trait signatures to compile -// The actual pack methods are never called on-chain -#[cfg(target_os = "solana")] -mod pack_accounts_stub { - use solana_pubkey::Pubkey; - - /// Stub type for on-chain compilation. The actual implementation with sorting - /// is only available off-chain. This allows trait signatures that reference - /// PackedAccounts to compile on Solana. - pub struct PackedAccounts { - _phantom: core::marker::PhantomData<()>, - } +//! Client-side instruction building utilities. +//! +//! Only available off-chain (`#[cfg(not(target_os = "solana"))]`). - impl PackedAccounts { - pub fn insert_or_get(&mut self, _pubkey: Pubkey) -> u8 { - panic!("PackedAccounts::insert_or_get is not available on-chain") - } - - pub fn insert_or_get_read_only(&mut self, _pubkey: Pubkey) -> u8 { - panic!("PackedAccounts::insert_or_get_read_only is not available on-chain") - } - } -} +mod pack_accounts; /// Re-exports from light-sdk-types instruction types. pub use light_sdk_types::instruction::*; -#[cfg(not(target_os = "solana"))] pub use pack_accounts::*; -#[cfg(target_os = "solana")] -pub use pack_accounts_stub::PackedAccounts; +// TODO: move all of this to light-sdk-types diff --git a/sdk-libs/sdk-interface/src/instruction/pack_accounts.rs b/sdk-libs/sdk-interface/src/instruction/pack_accounts.rs index 3564fc4f71..381d70a67b 100644 --- a/sdk-libs/sdk-interface/src/instruction/pack_accounts.rs +++ b/sdk-libs/sdk-interface/src/instruction/pack_accounts.rs @@ -8,60 +8,66 @@ use std::collections::HashMap; -use solana_instruction::AccountMeta; -use solana_program_error::ProgramError; -use solana_pubkey::Pubkey; +use light_account_checks::AccountMetaTrait; + +use crate::error::LightPdaError; /// Builder to collect accounts for compressed account instructions. /// +/// Generic over `AM: AccountMetaTrait` to work with both solana and pinocchio account metas. +/// /// Manages three categories of accounts: /// - **Pre-accounts**: Signers and other custom accounts that come before system accounts. /// - **System accounts**: Light system program accounts (authority, trees, queues). /// - **Packed accounts**: Dynamically tracked deduplicated accounts. -#[derive(Default, Debug)] -pub struct PackedAccounts { +#[derive(Debug)] +pub struct PackedAccounts { /// Accounts that must come before system accounts (e.g., signers, fee payer). - pub pre_accounts: Vec, + pub pre_accounts: Vec, /// Light system program accounts (authority, programs, trees, queues). - system_accounts: Vec, + system_accounts: Vec, /// Next available index for packed accounts. next_index: u8, - /// Map of pubkey to (index, AccountMeta) for deduplication and index tracking. - map: HashMap, + /// Map of pubkey bytes to (index, AccountMeta) for deduplication and index tracking. + map: HashMap<[u8; 32], (u8, AM)>, /// Field to sanity check system_accounts_set: bool, } -impl PackedAccounts { +impl Default for PackedAccounts { + fn default() -> Self { + Self { + pre_accounts: Vec::new(), + system_accounts: Vec::new(), + next_index: 0, + map: HashMap::new(), + system_accounts_set: false, + } + } +} + +impl PackedAccounts { pub fn system_accounts_set(&self) -> bool { self.system_accounts_set } - pub fn add_pre_accounts_signer(&mut self, pubkey: Pubkey) { - self.pre_accounts.push(AccountMeta { - pubkey, - is_signer: true, - is_writable: false, - }); + pub fn add_pre_accounts_signer(&mut self, pubkey: [u8; 32]) { + self.pre_accounts.push(AM::new(pubkey, true, false)); } - pub fn add_pre_accounts_signer_mut(&mut self, pubkey: Pubkey) { - self.pre_accounts.push(AccountMeta { - pubkey, - is_signer: true, - is_writable: true, - }); + pub fn add_pre_accounts_signer_mut(&mut self, pubkey: [u8; 32]) { + self.pre_accounts.push(AM::new(pubkey, true, true)); } - pub fn add_pre_accounts_meta(&mut self, account_meta: AccountMeta) { + pub fn add_pre_accounts_meta(&mut self, account_meta: AM) { self.pre_accounts.push(account_meta); } - pub fn add_pre_accounts_metas(&mut self, account_metas: &[AccountMeta]) { + pub fn add_pre_accounts_metas(&mut self, account_metas: &[AM]) { self.pre_accounts.extend_from_slice(account_metas); } - pub fn add_system_accounts_raw(&mut self, system_accounts: Vec) { + pub fn add_system_accounts_raw(&mut self, system_accounts: Vec) { self.system_accounts.extend(system_accounts); self.system_accounts_set = true; } @@ -73,58 +79,48 @@ impl PackedAccounts { /// /// If the provided `pubkey` already exists in the collection, its already /// existing index is returned. - pub fn insert_or_get(&mut self, pubkey: Pubkey) -> u8 { + pub fn insert_or_get(&mut self, pubkey: [u8; 32]) -> u8 { self.insert_or_get_config(pubkey, false, true) } - pub fn insert_or_get_read_only(&mut self, pubkey: Pubkey) -> u8 { + pub fn insert_or_get_read_only(&mut self, pubkey: [u8; 32]) -> u8 { self.insert_or_get_config(pubkey, false, false) } pub fn insert_or_get_config( &mut self, - pubkey: Pubkey, + pubkey: [u8; 32], is_signer: bool, is_writable: bool, ) -> u8 { match self.map.get_mut(&pubkey) { Some((index, entry)) => { - if !entry.is_writable { - entry.is_writable = is_writable; + if !entry.is_writable() { + entry.set_is_writable(is_writable); } - if !entry.is_signer { - entry.is_signer = is_signer; + if !entry.is_signer() { + entry.set_is_signer(is_signer); } *index } None => { let index = self.next_index; self.next_index += 1; - self.map.insert( - pubkey, - ( - index, - AccountMeta { - pubkey, - is_signer, - is_writable, - }, - ), - ); + self.map + .insert(pubkey, (index, AM::new(pubkey, is_signer, is_writable))); index } } } - fn hash_set_accounts_to_metas(&self) -> Vec { + fn hash_set_accounts_to_metas(&self) -> Vec { let mut packed_accounts = self.map.iter().collect::>(); // hash maps are not sorted so we need to sort manually and collect into a vector again packed_accounts.sort_by(|a, b| a.1 .0.cmp(&b.1 .0)); - let packed_accounts = packed_accounts + packed_accounts .iter() .map(|(_, (_, k))| k.clone()) - .collect::>(); - packed_accounts + .collect::>() } fn get_offsets(&self) -> (usize, usize) { @@ -134,9 +130,8 @@ impl PackedAccounts { (system_accounts_start_offset, packed_accounts_start_offset) } - /// Converts the collection of accounts to a vector of - /// [`AccountMeta`](solana_instruction::AccountMeta), which can be used - /// as remaining accounts in instructions or CPI calls. + /// Converts the collection of accounts to a vector of account metas, + /// which can be used as remaining accounts in instructions or CPI calls. /// /// # Returns /// @@ -144,7 +139,7 @@ impl PackedAccounts { /// - `account_metas`: All accounts concatenated in order: `[pre_accounts][system_accounts][packed_accounts]` /// - `system_accounts_offset`: Index where system accounts start (= pre_accounts.len()) /// - `packed_accounts_offset`: Index where packed accounts start (= pre_accounts.len() + system_accounts.len()) - pub fn to_account_metas(&self) -> (Vec, usize, usize) { + pub fn to_account_metas(&self) -> (Vec, usize, usize) { let packed_accounts = self.hash_set_accounts_to_metas(); let (system_accounts_start_offset, packed_accounts_start_offset) = self.get_offsets(); ( @@ -159,21 +154,21 @@ impl PackedAccounts { ) } - pub fn packed_pubkeys(&self) -> Vec { + pub fn packed_pubkeys(&self) -> Vec<[u8; 32]> { self.hash_set_accounts_to_metas() .iter() - .map(|meta| meta.pubkey) + .map(|meta| meta.pubkey_bytes()) .collect() } - pub fn add_custom_system_accounts( + pub fn add_custom_system_accounts>( &mut self, accounts: T, - ) -> Result<(), ProgramError> { + ) -> Result<(), LightPdaError> { accounts.get_account_metas_vec(self) } } -pub trait AccountMetasVec { - fn get_account_metas_vec(&self, accounts: &mut PackedAccounts) -> Result<(), ProgramError>; +pub trait AccountMetasVec { + fn get_account_metas_vec(&self, accounts: &mut PackedAccounts) -> Result<(), LightPdaError>; } diff --git a/sdk-libs/sdk-interface/src/lib.rs b/sdk-libs/sdk-interface/src/lib.rs index 3c65584510..2416796bb7 100644 --- a/sdk-libs/sdk-interface/src/lib.rs +++ b/sdk-libs/sdk-interface/src/lib.rs @@ -1,150 +1,87 @@ //! Framework-agnostic interface for Light Protocol compressible accounts. -//! -//! This crate provides the interface for compressible accounts, organized by -//! macro hierarchy: -//! -//! - `program/` - #[light_program] level (instruction processors) -//! - `accounts/` - #[derive(LightAccounts)] level (context structs, validation) -//! - `account/` - #[derive(LightAccount)] level (single account operations) -//! -//! # Features -//! - `solana` (default) - Enables Solana runtime support -//! - `pinocchio` - Enables Pinocchio runtime support -//! - `anchor` - Enables Anchor framework support -//! - `v2` - Enables v2 Light system program instructions -//! - `cpi-context` - Enables CPI context operations +#![cfg_attr(not(feature = "std"), no_std)] -// --- Conditional serialization trait aliases --- -#[cfg(feature = "anchor")] -pub use anchor_lang::{AnchorDeserialize, AnchorSerialize}; -#[cfg(not(feature = "anchor"))] pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; pub mod error; -// --- Subdirectory modules --- pub mod account; pub mod accounts; pub mod program; -// --- CPI module (solana-only for now) --- -#[cfg(feature = "solana")] +// LightCpi trait + CPI builder (no runtime dep) pub mod cpi; -// --- Instruction module --- -#[cfg(feature = "solana")] +// Client-side instruction building (not available on Solana BPF) +#[cfg(not(target_os = "solana"))] pub mod instruction; // --- Re-exports from light-account-checks --- pub use light_account_checks::{self, discriminator::Discriminator as LightDiscriminator}; -// ============================================================================= -// BACKWARD COMPATIBILITY: Submodule path preservation -// ============================================================================= - -/// Re-export config module for backward compatibility. -pub mod config { - pub use super::program::config::*; -} - -/// Re-export validation module for backward compatibility. -pub mod validation { - pub use super::program::validation::*; -} - -/// Re-export token module for backward compatibility. -#[cfg(feature = "anchor")] -pub mod token { - pub use super::{ - account::token_seeds::*, - program::decompression::token::prepare_token_account_for_decompression, - }; -} - -/// Re-export compression_info module for backward compatibility. -pub mod compression_info { - pub use super::account::compression_info::*; -} - -/// Re-export close module for backward compatibility. -pub mod close { - pub use super::program::compression::close::*; -} - -/// Re-export finalize module for backward compatibility. -pub mod finalize { - pub use super::accounts::finalize::*; -} - -/// Re-export traits module for backward compatibility. -pub mod traits { - #[cfg(feature = "anchor")] - pub use super::account::light_account::{AccountType, LightAccount}; - pub use super::program::variant::IntoVariant; - #[cfg(feature = "anchor")] - pub use super::program::variant::{ - LightAccountVariantTrait, PackedLightAccountVariantTrait, PackedTokenSeeds, - UnpackedTokenSeeds, - }; -} +// --- Re-exports from external crates --- +pub use light_compressible::{rent, CreateAccountsProof}; // ============================================================================= // FLAT RE-EXPORTS // ============================================================================= -// --- Re-exports from account/ --- -#[cfg(feature = "anchor")] +// --- account/ --- +pub use account::compression_info::{ + claim_completed_epoch_rent, CompressAs, CompressedAccountData, CompressedInitSpace, + CompressionInfo, CompressionInfoField, CompressionState, HasCompressionInfo, Space, + COMPRESSION_INFO_SIZE, OPTION_COMPRESSION_INFO_SPACE, +}; pub use account::light_account::{AccountType, LightAccount}; -#[cfg(all(not(target_os = "solana"), feature = "solana"))] +#[cfg(not(target_os = "solana"))] pub use account::pack::Pack; -pub use account::{ - compression_info::{ - claim_completed_epoch_rent, CompressAs, CompressedAccountData, CompressedInitSpace, - CompressionInfo, CompressionInfoField, CompressionState, HasCompressionInfo, Space, - COMPRESSION_INFO_SIZE, OPTION_COMPRESSION_INFO_SPACE, - }, - pack::Unpack, -}; +pub use account::pack::Unpack; pub use account::pda_seeds::{HasTokenVariant, PdaSeedDerivation}; -// --- Re-exports from accounts/ --- -pub use accounts::create_pda::create_pda_account; + +// --- accounts/ --- pub use accounts::{ finalize::{LightFinalize, LightPreInit}, - init_compressed_account::{ - prepare_compressed_account_on_init, prepare_compressed_account_on_init_checked, - reimburse_rent, - }, + init_compressed_account::{prepare_compressed_account_on_init, reimburse_rent}, }; -// --- Re-exports from external crates --- -pub use light_compressible::{rent, CreateAccountsProof}; + +// --- cpi/ --- +pub use cpi::{ + account::CpiAccountsTrait, + invoke::{invoke_light_system_program, InvokeLightSystemProgram}, + LightCpi, +}; + +// --- program/ --- pub use program::compression::close::close; -#[cfg(feature = "anchor")] pub use program::compression::pda::prepare_account_for_compression; -#[cfg(feature = "anchor")] -pub use program::compression::processor::process_compress_pda_accounts_idempotent; -#[cfg(feature = "anchor")] -pub use program::compression::processor::{CompressAndCloseParams, CompressCtx, CompressDispatchFn}; -#[cfg(feature = "anchor")] +pub use program::compression::processor::{ + process_compress_pda_accounts_idempotent, CompressAndCloseParams, CompressCtx, + CompressDispatchFn, +}; +pub use program::config::{ + process_initialize_light_config_checked, process_update_light_config, + InitializeLightConfigParams, LightConfig, UpdateLightConfigParams, + COMPRESSIBLE_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE, +}; +pub use program::validation::{ + extract_tail_accounts, is_pda_initialized, should_skip_compression, + split_at_system_accounts_offset, validate_compress_accounts, validate_decompress_accounts, + ValidatedPdaContext, +}; pub use program::decompression::pda::prepare_account_for_decompression; -#[cfg(feature = "anchor")] pub use program::decompression::processor::{ process_decompress_pda_accounts_idempotent, DecompressCtx, DecompressIdempotentParams, DecompressVariant, }; -#[cfg(feature = "anchor")] -pub use program::variant::{ - LightAccountVariantTrait, PackedLightAccountVariantTrait, PackedTokenSeeds, UnpackedTokenSeeds, -}; -pub use program::{ - config::{ - process_initialize_light_config, process_initialize_light_config_checked, - process_update_light_config, LightConfig, COMPRESSIBLE_CONFIG_SEED, - MAX_ADDRESS_TREES_PER_SPACE, - }, - validation::{ - extract_tail_accounts, is_pda_initialized, should_skip_compression, - split_at_system_accounts_offset, validate_compress_accounts, validate_decompress_accounts, - ValidatedPdaContext, - }, - variant::IntoVariant, +pub use program::variant::IntoVariant; +pub use program::variant::{LightAccountVariantTrait, PackedLightAccountVariantTrait}; +#[cfg(feature = "token")] +pub use program::variant::{PackedTokenSeeds, UnpackedTokenSeeds}; +#[cfg(feature = "token")] +pub use program::decompression::processor::process_decompress_accounts_idempotent; +#[cfg(feature = "token")] +pub use program::decompression::token::prepare_token_account_for_decompression; +#[cfg(feature = "token")] +pub use account::token_seeds::{ + PackedTokenData, TokenDataWithPackedSeeds, TokenDataWithSeeds, }; diff --git a/sdk-libs/sdk-interface/src/program/compression/close.rs b/sdk-libs/sdk-interface/src/program/compression/close.rs index b2b1f68956..e46ff3b5c0 100644 --- a/sdk-libs/sdk-interface/src/program/compression/close.rs +++ b/sdk-libs/sdk-interface/src/program/compression/close.rs @@ -1,45 +1,8 @@ -use solana_account_info::AccountInfo; +use light_account_checks::AccountInfoTrait; use crate::error::{LightPdaError, Result}; - -// close native solana account -pub fn close<'info>( - info: &mut AccountInfo<'info>, - sol_destination: &AccountInfo<'info>, -) -> Result<()> { - let system_program_id = solana_pubkey::pubkey!("11111111111111111111111111111111"); - - if info.key == sol_destination.key { - info.assign(&system_program_id); - info.resize(0) - .map_err(|_| LightPdaError::ConstraintViolation)?; - return Ok(()); - } - - let lamports_to_transfer = info.lamports(); - - let new_destination_lamports = sol_destination - .lamports() - .checked_add(lamports_to_transfer) - .ok_or(LightPdaError::ConstraintViolation)?; - - { - let mut destination_lamports = sol_destination - .try_borrow_mut_lamports() - .map_err(|_| LightPdaError::ConstraintViolation)?; - **destination_lamports = new_destination_lamports; - } - - { - let mut source_lamports = info - .try_borrow_mut_lamports() - .map_err(|_| LightPdaError::ConstraintViolation)?; - **source_lamports = 0; - } - - info.assign(&system_program_id); - info.resize(0) - .map_err(|_| LightPdaError::ConstraintViolation)?; - - Ok(()) +// TODO: remove and use directly from light-account-checks +/// Close a native Solana account by transferring lamports and clearing data. +pub fn close(info: &AI, sol_destination: &AI) -> Result<()> { + light_account_checks::close_account(info, sol_destination).map_err(LightPdaError::AccountCheck) } diff --git a/sdk-libs/sdk-interface/src/program/compression/mod.rs b/sdk-libs/sdk-interface/src/program/compression/mod.rs index 46572ac8b4..9391ffd875 100644 --- a/sdk-libs/sdk-interface/src/program/compression/mod.rs +++ b/sdk-libs/sdk-interface/src/program/compression/mod.rs @@ -1,9 +1,5 @@ //! Compression functions for PDA accounts. pub mod close; - -#[cfg(feature = "anchor")] -pub mod processor; - -#[cfg(feature = "anchor")] pub mod pda; +pub mod processor; diff --git a/sdk-libs/sdk-interface/src/program/compression/pda.rs b/sdk-libs/sdk-interface/src/program/compression/pda.rs index 2066d056a5..052a52649e 100644 --- a/sdk-libs/sdk-interface/src/program/compression/pda.rs +++ b/sdk-libs/sdk-interface/src/program/compression/pda.rs @@ -3,10 +3,7 @@ //! These functions are generic over account types and can be reused by the macro. //! The compress flow uses a dispatch callback pattern (same as decompress). -use anchor_lang::{ - prelude::*, - solana_program::{clock::Clock, rent::Rent, sysvar::Sysvar}, -}; +use light_account_checks::AccountInfoTrait; use light_compressed_account::{ address::derive_address, compressed_account::PackedMerkleContext, @@ -15,13 +12,13 @@ use light_compressed_account::{ use light_compressible::{rent::AccountRentState, DECOMPRESSED_PDA_DISCRIMINATOR}; use light_hasher::{sha256::Sha256BE, Hasher, Sha256}; use light_sdk_types::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress; -use solana_program_error::ProgramError; use light_sdk_types::instruction::account_meta::{CompressedAccountMeta, CompressedAccountMetaTrait}; use crate::{ + account::compression_info::HasCompressionInfo, + error::LightPdaError, program::compression::processor::CompressCtx, - account::light_account::LightAccount, LightDiscriminator, }; @@ -40,21 +37,23 @@ use crate::{ /// * `compressed_account_meta` - Compressed account metadata /// * `pda_index` - Index of the PDA in the accounts array (for tracking closes) /// * `ctx` - Mutable context ref - pushes results here -pub fn prepare_account_for_compression<'info, A>( - account_info: &AccountInfo<'info>, +pub fn prepare_account_for_compression( + account_info: &AI, account_data: &mut A, compressed_account_meta: &CompressedAccountMetaNoLamportsNoAddress, pda_index: usize, - ctx: &mut CompressCtx<'_, 'info>, -) -> std::result::Result<(), ProgramError> + ctx: &mut CompressCtx<'_, AI>, +) -> Result<(), LightPdaError> where - A: LightAccount + LightDiscriminator + Clone + AnchorSerialize, + AI: AccountInfoTrait, + A: HasCompressionInfo + LightDiscriminator + Clone + borsh::BorshSerialize, { // v2 address derive using PDA as seed + let account_key = account_info.key(); let derived_c_pda = derive_address( - &account_info.key.to_bytes(), - &ctx.light_config.address_space[0].to_bytes(), - &ctx.program_id.to_bytes(), + &account_key, + &ctx.light_config.address_space[0], + ctx.program_id, ); let meta_with_address = CompressedAccountMeta { @@ -63,14 +62,13 @@ where output_state_tree_index: compressed_account_meta.output_state_tree_index, }; - let current_slot = Clock::get()?.slot; + let current_slot = AI::get_current_slot().map_err(LightPdaError::AccountCheck)?; let bytes = account_info.data_len() as u64; let current_lamports = account_info.lamports(); - let rent_exemption_lamports = Rent::get() - .map_err(|_| ProgramError::Custom(0))? - .minimum_balance(bytes as usize); + let rent_exemption_lamports = + AI::get_min_rent_balance(bytes as usize).map_err(LightPdaError::AccountCheck)?; - let ci = account_data.compression_info(); + let ci = account_data.compression_info()?; let last_claimed_slot = ci.last_claimed_slot(); let rent_cfg = ci.rent_config; @@ -86,45 +84,41 @@ where .is_compressible(&rent_cfg, rent_exemption_lamports) .is_none() { - solana_msg::msg!("pda not yet compressible, skipping batch"); ctx.has_non_compressible = true; return Ok(()); } - // Mark as compressed using LightAccount trait - account_data.compression_info_mut().set_compressed(); + // Mark as compressed + account_data.compression_info_mut()?.set_compressed(); // Serialize updated account data back (includes 8-byte discriminator) { let mut data = account_info .try_borrow_mut_data() - .map_err(|_| ProgramError::Custom(2))?; + .map_err(LightPdaError::AccountCheck)?; // Write discriminator first data[..8].copy_from_slice(&A::LIGHT_DISCRIMINATOR); // Write serialized account data after discriminator let writer = &mut &mut data[8..]; account_data .serialize(writer) - .map_err(|_| ProgramError::Custom(3))?; + .map_err(|_| LightPdaError::Borsh)?; } // Create compressed account with canonical compressed CompressionInfo for hashing let mut compressed_data = account_data.clone(); - *compressed_data.compression_info_mut() = + *compressed_data.compression_info_mut()? = crate::account::compression_info::CompressionInfo::compressed(); // Hash the data (discriminator NOT included per protocol convention) - let data_bytes = compressed_data - .try_to_vec() - .map_err(|_| ProgramError::Custom(4))?; - let mut output_data_hash = Sha256::hash(&data_bytes).map_err(|_| ProgramError::Custom(5))?; + let data_bytes = borsh::to_vec(&compressed_data).map_err(|_| LightPdaError::Borsh)?; + let mut output_data_hash = Sha256::hash(&data_bytes).map_err(LightPdaError::Hasher)?; output_data_hash[0] = 0; // Zero first byte per protocol convention // Build input account info (placeholder compressed account from init) // The init created a placeholder with DECOMPRESSED_PDA_DISCRIMINATOR and PDA pubkey as data let tree_info = compressed_account_meta.tree_info; - let input_data_hash = - Sha256BE::hash(&account_info.key.to_bytes()).map_err(|_| ProgramError::Custom(6))?; + let input_data_hash = Sha256BE::hash(&account_key).map_err(LightPdaError::Hasher)?; let input_account_info = InAccountInfo { data_hash: input_data_hash, lamports: 0, diff --git a/sdk-libs/sdk-interface/src/program/compression/processor.rs b/sdk-libs/sdk-interface/src/program/compression/processor.rs index e7500a2456..6204d9bdee 100644 --- a/sdk-libs/sdk-interface/src/program/compression/processor.rs +++ b/sdk-libs/sdk-interface/src/program/compression/processor.rs @@ -1,25 +1,27 @@ //! Compression instruction processor. +use light_account_checks::AccountInfoTrait; use light_compressed_account::instruction_data::{ compressed_proof::ValidityProof, - with_account_info::CompressedAccountInfo, + with_account_info::{CompressedAccountInfo, InstructionDataInvokeCpiWithAccountInfo}, }; use light_sdk_types::{ - instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, CpiSigner, + cpi_accounts::v2::CpiAccounts, instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, + CpiSigner, }; -use solana_account_info::AccountInfo; -use solana_program_error::ProgramError; -use solana_pubkey::Pubkey; use crate::{ - cpi::{ - v2::{CpiAccounts, LightSystemProgramCpi}, - InvokeLightSystemProgram, LightCpiInstruction, - }, - program::config::LightConfig, + cpi::InvokeLightSystemProgram, + error::LightPdaError, + program::{compression::close::close, config::LightConfig}, AnchorDeserialize, AnchorSerialize, }; +/// Account indices within remaining_accounts for compress instructions. +const FEE_PAYER_INDEX: usize = 0; +const CONFIG_INDEX: usize = 1; +const RENT_SPONSOR_INDEX: usize = 2; + /// Parameters for compress_and_close instruction. /// Matches SDK's SaveAccountsData field order for compatibility. #[derive(AnchorSerialize, AnchorDeserialize, Clone)] @@ -33,12 +35,11 @@ pub struct CompressAndCloseParams { } /// Context struct holding all data needed for compression. -/// Contains internal vec for collecting CompressedAccountInfo results. -pub struct CompressCtx<'a, 'info> { - pub program_id: &'a Pubkey, - pub cpi_accounts: &'a CpiAccounts<'a, 'info>, - pub remaining_accounts: &'a [AccountInfo<'info>], - pub rent_sponsor: &'a AccountInfo<'info>, +/// Generic over AccountInfoTrait to work with both solana and pinocchio. +pub struct CompressCtx<'a, AI: AccountInfoTrait> { + pub program_id: &'a [u8; 32], + pub remaining_accounts: &'a [AI], + pub rent_sponsor: &'a AI, pub light_config: &'a LightConfig, /// Internal vec - dispatch functions push results here pub compressed_account_infos: Vec, @@ -51,110 +52,103 @@ pub struct CompressCtx<'a, 'info> { /// Callback type for discriminator-based dispatch. /// MACRO-GENERATED: Just a match statement routing to prepare_account_for_compression. -/// Takes &mut CompressCtx and pushes CompressedAccountInfo into ctx.compressed_account_infos. -/// -/// The dispatch function is responsible for: -/// 1. Reading the discriminator from the account data -/// 2. Deserializing the account based on discriminator -/// 3. Calling prepare_account_for_compression with the deserialized data -pub type CompressDispatchFn<'info> = fn( - account_info: &AccountInfo<'info>, +pub type CompressDispatchFn = fn( + account_info: &AI, compressed_account_meta: &CompressedAccountMetaNoLamportsNoAddress, index: usize, - ctx: &mut CompressCtx<'_, 'info>, -) -> std::result::Result<(), ProgramError>; - -/// Remaining accounts layout: -/// [0]: fee_payer (Signer, mut) -/// [1]: config (LightConfig PDA) -/// [2]: rent_sponsor (mut) -/// [3]: compression_authority (Signer) -/// [system_accounts_offset..]: Light system accounts for CPI -/// [remaining_accounts.len() - num_pda_accounts..]: PDA accounts to compress + ctx: &mut CompressCtx<'_, AI>, +) -> Result<(), LightPdaError>; + +/// Process compress-and-close for PDA accounts (idempotent). /// -/// Runtime processor - handles all the plumbing, delegates dispatch to callback. +/// Iterates over PDA accounts, dispatches each for compression via `dispatch_fn`, +/// then invokes the Light system program CPI to commit compressed state, +/// and closes the PDA accounts (transferring lamports to rent_sponsor). /// -/// **Takes raw instruction data** and deserializes internally - minimizes macro code. -/// **Uses only remaining_accounts** - no Context struct needed. -pub fn process_compress_pda_accounts_idempotent<'info>( - remaining_accounts: &[AccountInfo<'info>], +/// Idempotent: if any account is not yet compressible (rent function check fails), +/// the entire batch is silently skipped. +#[inline(never)] +pub fn process_compress_pda_accounts_idempotent( + remaining_accounts: &[AI], instruction_data: &[u8], - dispatch_fn: CompressDispatchFn<'info>, + dispatch_fn: CompressDispatchFn, cpi_signer: CpiSigner, - program_id: &Pubkey, -) -> std::result::Result<(), ProgramError> { - // Deserialize params internally - let params = CompressAndCloseParams::try_from_slice(instruction_data).map_err(|e| { - solana_msg::msg!("compress: params deser failed: {:?}", e); - ProgramError::InvalidInstructionData - })?; - - // Extract and validate accounts using shared validation - let validated_ctx = - crate::program::validation::validate_compress_accounts(remaining_accounts, program_id)?; - let fee_payer = &validated_ctx.fee_payer; - let rent_sponsor = &validated_ctx.rent_sponsor; - let light_config = validated_ctx.light_config; - - let (_, system_accounts) = crate::program::validation::split_at_system_accounts_offset( - remaining_accounts, - params.system_accounts_offset, - )?; - - let cpi_accounts = CpiAccounts::new(fee_payer, system_accounts, cpi_signer); - - // Build context struct with all needed data (includes internal vec) - let mut compress_ctx = CompressCtx { - program_id, - cpi_accounts: &cpi_accounts, - remaining_accounts, - rent_sponsor, - light_config: &light_config, - compressed_account_infos: Vec::with_capacity(params.compressed_accounts.len()), - pda_indices_to_close: Vec::with_capacity(params.compressed_accounts.len()), - has_non_compressible: false, - }; + program_id: &[u8; 32], +) -> Result<(), LightPdaError> { + // 1. Deserialize params + let params = CompressAndCloseParams::try_from_slice(instruction_data) + .map_err(|_| LightPdaError::Borsh)?; - // PDA accounts at end of remaining_accounts - let pda_accounts = crate::program::validation::extract_tail_accounts( - remaining_accounts, - params.compressed_accounts.len(), - )?; + let system_accounts_offset = params.system_accounts_offset as usize; + let num_pdas = params.compressed_accounts.len(); - for (i, account_data) in params.compressed_accounts.iter().enumerate() { - let pda_account = &pda_accounts[i]; + if num_pdas == 0 { + return Err(LightPdaError::InvalidInstructionData); + } - // Skip empty accounts or accounts not owned by this program - if crate::program::validation::should_skip_compression(pda_account, program_id) { - continue; + // 2. Load and validate config + let config = LightConfig::load_checked(&remaining_accounts[CONFIG_INDEX], program_id)?; + + // 3. Validate rent_sponsor + let rent_sponsor = &remaining_accounts[RENT_SPONSOR_INDEX]; + config.validate_rent_sponsor_account::(rent_sponsor)?; + + // 4. PDA accounts are at the tail of remaining_accounts + let pda_start = remaining_accounts + .len() + .checked_sub(num_pdas) + .ok_or(LightPdaError::NotEnoughAccountKeys)?; + + // 5. Run dispatch for each PDA + let (compressed_account_infos, pda_indices_to_close, has_non_compressible) = { + let mut ctx = CompressCtx { + program_id, + remaining_accounts, + rent_sponsor, + light_config: &config, + compressed_account_infos: Vec::with_capacity(num_pdas), + pda_indices_to_close: Vec::with_capacity(num_pdas), + has_non_compressible: false, + }; + + for (i, meta) in params.compressed_accounts.iter().enumerate() { + let pda_index = pda_start + i; + dispatch_fn(&remaining_accounts[pda_index], meta, pda_index, &mut ctx)?; } - // Delegate to dispatch callback (macro-generated match) - dispatch_fn(pda_account, account_data, i, &mut compress_ctx)?; - } + ( + ctx.compressed_account_infos, + ctx.pda_indices_to_close, + ctx.has_non_compressible, + ) + }; - // If any account is not yet compressible, skip the entire batch. - // The proof covers all accounts so we cannot partially compress. - if compress_ctx.has_non_compressible { + // 6. Idempotent: if any account is not yet compressible, skip entire batch + if has_non_compressible { return Ok(()); } - // CPI to Light System Program - if !compress_ctx.compressed_account_infos.is_empty() { - LightSystemProgramCpi::new_cpi(cpi_signer, params.proof) - .with_account_infos(&compress_ctx.compressed_account_infos) - .invoke(cpi_accounts.clone()) - .map_err(|e| { - solana_msg::msg!("compress: CPI failed: {:?}", e); - ProgramError::Custom(200) - })?; - - // Close the PDA accounts - for idx in compress_ctx.pda_indices_to_close { - let mut info = pda_accounts[idx].clone(); - crate::program::compression::close::close(&mut info, rent_sponsor) - .map_err(ProgramError::from)?; - } + // 7. Build CPI instruction data + let mut cpi_ix_data = InstructionDataInvokeCpiWithAccountInfo::new( + program_id.into(), + cpi_signer.bump, + params.proof.into(), + ); + cpi_ix_data.account_infos = compressed_account_infos; + + // 8. Build CpiAccounts from system accounts slice (excluding PDA accounts at tail) + let cpi_accounts = CpiAccounts::new( + &remaining_accounts[FEE_PAYER_INDEX], + &remaining_accounts[system_accounts_offset..pda_start], + cpi_signer, + ); + + // 9. Invoke Light system program CPI + cpi_ix_data.invoke::(cpi_accounts)?; + + // 10. Close PDA accounts, transferring lamports to rent_sponsor + for pda_index in &pda_indices_to_close { + close(&remaining_accounts[*pda_index], rent_sponsor)?; } Ok(()) diff --git a/sdk-libs/sdk-interface/src/program/config/create.rs b/sdk-libs/sdk-interface/src/program/config/create.rs index c86c3a9997..dd9a989895 100644 --- a/sdk-libs/sdk-interface/src/program/config/create.rs +++ b/sdk-libs/sdk-interface/src/program/config/create.rs @@ -1,149 +1,108 @@ -//! Config initialization instructions. +//! Config initialization instructions (generic over AccountInfoTrait). use light_account_checks::{ checks::check_signer, discriminator::{Discriminator, DISCRIMINATOR_LEN}, + AccountInfoTrait, }; use light_compressible::rent::RentConfig; -use solana_account_info::AccountInfo; -use solana_cpi::invoke_signed; -use solana_loader_v3_interface::state::UpgradeableLoaderState; -use solana_msg::msg; -use solana_program_error::ProgramError; -use solana_pubkey::Pubkey; -use solana_system_interface::instruction as system_instruction; -use solana_sysvar::{rent::Rent, Sysvar}; use super::{state::LightConfig, validate_address_space_no_duplicates, COMPRESSIBLE_CONFIG_SEED}; use crate::{error::LightPdaError, AnchorSerialize}; -const BPF_LOADER_UPGRADEABLE_ID: Pubkey = - Pubkey::from_str_const("BPFLoaderUpgradeab1e11111111111111111111111"); +/// BPFLoaderUpgradeab1e11111111111111111111111 as raw bytes. +const BPF_LOADER_UPGRADEABLE_ID: [u8; 32] = [ + 2, 168, 246, 145, 78, 136, 161, 110, 57, 90, 225, 40, 148, 143, 144, 16, 207, 227, 47, 228, + 248, 212, 16, 185, 221, 165, 30, 160, 42, 103, 43, 122, +]; -/// Creates a new compressible config PDA -/// -/// # Security - Solana Best Practice -/// This function follows the standard Solana pattern where only the program's -/// upgrade authority can create the initial config. This prevents unauthorized -/// parties from hijacking the config system. -/// -/// # Arguments -/// * `config_account` - The config PDA account to initialize -/// * `update_authority` - Authority that can update the config after creation -/// * `rent_sponsor` - Account that receives rent from compressed PDAs -/// * `compression_authority` - Authority that can compress/close PDAs -/// * `rent_config` - Rent function parameters -/// * `write_top_up` - Lamports to top up on each write -/// * `address_space` - Address space for compressed accounts (currently 1 address_tree allowed) -/// * `config_bump` - Config bump seed (must be 0 for now) -/// * `payer` - Account paying for the PDA creation -/// * `system_program` - System program -/// * `program_id` - The program that owns the config +/// UpgradeableLoaderState::ProgramData layout (manual parsing, no bincode dep): +/// - bytes 0..4: variant tag (u32 LE, must be 3 for ProgramData) +/// - bytes 4..12: slot (u64 LE) +/// - byte 12: Option discriminant (0=None, 1=Some) +/// - bytes 13..45: authority pubkey (32 bytes, only valid when discriminant=1) +const PROGRAM_DATA_VARIANT_TAG: u32 = 3; +const PROGRAM_DATA_MIN_LEN: usize = 45; + +/// Creates a new compressible config PDA. /// /// # Required Validation (must be done by caller) -/// The caller MUST validate that the signer is the program's upgrade authority -/// by checking against the program data account. This cannot be done in the SDK -/// due to dependency constraints. -/// -/// # Returns -/// * `Ok(())` if config was created successfully -/// * `Err(ProgramError)` if there was an error +/// The caller MUST validate that the signer is the program's upgrade authority. +/// Use `process_initialize_light_config_checked` for the version that does this. #[allow(clippy::too_many_arguments)] -pub fn process_initialize_light_config<'info>( - config_account: &AccountInfo<'info>, - update_authority: &AccountInfo<'info>, - rent_sponsor: &Pubkey, - compression_authority: &Pubkey, +pub fn process_initialize_light_config( + config_account: &AI, + update_authority: &AI, + rent_sponsor: &[u8; 32], + compression_authority: &[u8; 32], rent_config: RentConfig, write_top_up: u32, - address_space: Vec, + address_space: Vec<[u8; 32]>, config_bump: u8, - payer: &AccountInfo<'info>, - system_program: &AccountInfo<'info>, - program_id: &Pubkey, -) -> Result<(), ProgramError> { - // CHECK: only 1 address_space + payer: &AI, + system_program: &AI, + program_id: &[u8; 32], +) -> Result<(), LightPdaError> { + // CHECK: config_bump must be 0 if config_bump != 0 { - msg!("Config bump must be 0 for now, found: {}", config_bump); - return Err(LightPdaError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation); } // CHECK: not already initialized - if config_account.data_len() > 0 { - msg!("Config account already initialized"); - return Err(LightPdaError::ConstraintViolation.into()); + if !config_account.data_is_empty() { + return Err(LightPdaError::ConstraintViolation); } - // CHECK: only 1 address_space + // CHECK: exactly 1 address space if address_space.len() != 1 { - msg!( - "Address space must contain exactly 1 pubkey, found: {}", - address_space.len() - ); - return Err(LightPdaError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation); } // CHECK: unique pubkeys in address_space validate_address_space_no_duplicates(&address_space)?; // CHECK: signer - check_signer(update_authority).inspect_err(|_| { - msg!("Update authority must be signer for initial config creation"); - })?; - - // CHECK: pda derivation - let (derived_pda, bump) = LightConfig::derive_pda(program_id, config_bump); - if derived_pda != *config_account.key { - msg!("Invalid config PDA"); - return Err(LightPdaError::ConstraintViolation.into()); + check_signer(update_authority).map_err(LightPdaError::AccountCheck)?; + + // CHECK: PDA derivation + let (derived_pda, bump) = LightConfig::derive_pda_bytes::(program_id, config_bump); + if derived_pda != config_account.key() { + return Err(LightPdaError::ConstraintViolation); } // Derive rent_sponsor_bump for storage let (derived_rent_sponsor, rent_sponsor_bump) = - LightConfig::derive_rent_sponsor_pda(program_id); + LightConfig::derive_rent_sponsor_pda_bytes::(program_id); if *rent_sponsor != derived_rent_sponsor { - msg!( - "rent_sponsor must be derived PDA: expected {:?}, got {:?}", - derived_rent_sponsor, - rent_sponsor - ); - return Err(LightPdaError::InvalidRentSponsor.into()); + return Err(LightPdaError::InvalidRentSponsor); } - let rent = Rent::get().map_err(LightPdaError::from)?; let account_size = LightConfig::size_for_address_space(address_space.len()); - let rent_lamports = rent.minimum_balance(account_size); + let rent_lamports = + AI::get_min_rent_balance(account_size).map_err(LightPdaError::AccountCheck)?; - // Use u16 to_le_bytes to match derive_pda (2 bytes instead of 1) + // Create PDA using AccountInfoTrait let config_bump_bytes = (config_bump as u16).to_le_bytes(); - let seeds = &[ + let seeds: &[&[u8]] = &[ COMPRESSIBLE_CONFIG_SEED, config_bump_bytes.as_ref(), &[bump], ]; - let create_account_ix = system_instruction::create_account( - payer.key, - config_account.key, + + config_account.create_pda_account( rent_lamports, account_size as u64, program_id, - ); - - invoke_signed( - &create_account_ix, - &[ - payer.clone(), - config_account.clone(), - system_program.clone(), - ], - &[seeds], - ) - .map_err(LightPdaError::from)?; + seeds, + payer, + &[], + system_program, + )?; let config = LightConfig { version: 1, write_top_up, - update_authority: *update_authority.key, + update_authority: update_authority.key(), rent_sponsor: *rent_sponsor, compression_authority: *compression_authority, rent_config, @@ -155,9 +114,9 @@ pub fn process_initialize_light_config<'info>( let mut data = config_account .try_borrow_mut_data() - .map_err(LightPdaError::from)?; + .map_err(LightPdaError::AccountCheck)?; - // Write discriminator first (using trait constant) + // Write discriminator first data[..DISCRIMINATOR_LEN].copy_from_slice(&LightConfig::LIGHT_DISCRIMINATOR); // Serialize config data after discriminator @@ -168,127 +127,86 @@ pub fn process_initialize_light_config<'info>( Ok(()) } -/// Checks that the signer is the program's upgrade authority -/// -/// # Arguments -/// * `program_id` - The program to check -/// * `program_data_account` - The program's data account (ProgramData) -/// * `authority` - The authority to verify +/// Checks that the signer is the program's upgrade authority. /// -/// # Returns -/// * `Ok(())` if authority is valid -/// * `Err(LightPdaError)` if authority is invalid or verification fails -pub fn check_program_upgrade_authority( - program_id: &Pubkey, - program_data_account: &AccountInfo, - authority: &AccountInfo, -) -> Result<(), ProgramError> { +/// Manually parses the UpgradeableLoaderState::ProgramData layout (45 bytes) +/// to avoid a bincode dependency. +pub fn check_program_upgrade_authority( + program_id: &[u8; 32], + program_data_account: &AI, + authority: &AI, +) -> Result<(), LightPdaError> { // CHECK: program data PDA let (expected_program_data, _) = - Pubkey::find_program_address(&[program_id.as_ref()], &BPF_LOADER_UPGRADEABLE_ID); - if program_data_account.key != &expected_program_data { - msg!("Invalid program data account"); - return Err(LightPdaError::ConstraintViolation.into()); + AI::find_program_address(&[program_id], &BPF_LOADER_UPGRADEABLE_ID); + if program_data_account.key() != expected_program_data { + return Err(LightPdaError::ConstraintViolation); + } + + let data = program_data_account + .try_borrow_data() + .map_err(LightPdaError::AccountCheck)?; + + if data.len() < PROGRAM_DATA_MIN_LEN { + return Err(LightPdaError::AccountDataTooSmall); + } + + // Parse variant tag (4 bytes, u32 LE) + let variant_tag = u32::from_le_bytes(data[0..4].try_into().unwrap()); + if variant_tag != PROGRAM_DATA_VARIANT_TAG { + return Err(LightPdaError::ConstraintViolation); } - let data = program_data_account.try_borrow_data()?; - let program_state: UpgradeableLoaderState = bincode::deserialize(&data).map_err(|_| { - msg!("Failed to deserialize program data account"); - LightPdaError::ConstraintViolation - })?; - - // Extract upgrade authority - let upgrade_authority = match program_state { - UpgradeableLoaderState::ProgramData { - slot: _, - upgrade_authority_address, - } => { - match upgrade_authority_address { - Some(auth) => { - // Check for invalid zero authority when authority exists - if auth == Pubkey::default() { - msg!("Invalid state: authority is zero pubkey"); - return Err(LightPdaError::ConstraintViolation.into()); - } - auth - } - None => { - msg!("Program has no upgrade authority"); - return Err(LightPdaError::ConstraintViolation.into()); - } + // Parse Option at offset 12 + let option_discriminant = data[12]; + let upgrade_authority: [u8; 32] = match option_discriminant { + 0 => { + // None - program has no upgrade authority + return Err(LightPdaError::ConstraintViolation); + } + 1 => { + let mut auth = [0u8; 32]; + auth.copy_from_slice(&data[13..45]); + // Check for invalid zero authority + if auth == [0u8; 32] { + return Err(LightPdaError::ConstraintViolation); } + auth } _ => { - msg!("Account is not ProgramData, found: {:?}", program_state); - return Err(LightPdaError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation); } }; - // CHECK: upgrade authority is signer - check_signer(authority).inspect_err(|_| { - msg!("Authority must be signer"); - })?; - - // CHECK: upgrade authority is program's upgrade authority - if *authority.key != upgrade_authority { - msg!( - "Signer is not the program's upgrade authority. Signer: {:?}, Expected Authority: {:?}", - authority.key, - upgrade_authority - ); - return Err(LightPdaError::ConstraintViolation.into()); + // CHECK: authority is signer + check_signer(authority).map_err(LightPdaError::AccountCheck)?; + + // CHECK: authority matches upgrade authority + if authority.key() != upgrade_authority { + return Err(LightPdaError::ConstraintViolation); } Ok(()) } -/// Creates a new compressible config PDA. -/// -/// # Arguments -/// * `config_account` - The config PDA account to initialize -/// * `update_authority` - Must be the program's upgrade authority -/// * `program_data_account` - The program's data account for validation -/// * `rent_sponsor` - Account that receives rent from compressed PDAs -/// * `compression_authority` - Authority that can compress/close PDAs -/// * `rent_config` - Rent function parameters -/// * `write_top_up` - Lamports to top up on each write -/// * `address_space` - Address spaces for compressed accounts (exactly 1 -/// allowed) -/// * `config_bump` - Config bump seed (must be 0 for now) -/// * `payer` - Account paying for the PDA creation -/// * `system_program` - System program -/// * `program_id` - The program that owns the config -/// -/// # Returns -/// * `Ok(())` if config was created successfully -/// * `Err(ProgramError)` if there was an error or authority validation fails +/// Creates a new compressible config PDA with upgrade authority check. #[allow(clippy::too_many_arguments)] -pub fn process_initialize_light_config_checked<'info>( - config_account: &AccountInfo<'info>, - update_authority: &AccountInfo<'info>, - program_data_account: &AccountInfo<'info>, - rent_sponsor: &Pubkey, - compression_authority: &Pubkey, +pub fn process_initialize_light_config_checked( + config_account: &AI, + update_authority: &AI, + program_data_account: &AI, + rent_sponsor: &[u8; 32], + compression_authority: &[u8; 32], rent_config: RentConfig, write_top_up: u32, - address_space: Vec, + address_space: Vec<[u8; 32]>, config_bump: u8, - payer: &AccountInfo<'info>, - system_program: &AccountInfo<'info>, - program_id: &Pubkey, -) -> Result<(), ProgramError> { - msg!( - "create_compression_config_checked program_data_account: {:?}", - program_data_account.key - ); - msg!( - "create_compression_config_checked program_id: {:?}", - program_id - ); - // Verify the signer is the program's upgrade authority - check_program_upgrade_authority(program_id, program_data_account, update_authority)?; - - // Create the config with validated authority + payer: &AI, + system_program: &AI, + program_id: &[u8; 32], +) -> Result<(), LightPdaError> { + check_program_upgrade_authority::(program_id, program_data_account, update_authority)?; + process_initialize_light_config( config_account, update_authority, @@ -303,3 +221,45 @@ pub fn process_initialize_light_config_checked<'info>( program_id, ) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_upgradeable_loader_state_parsing() { + // Build a synthetic ProgramData account matching the manual layout + let mut data = vec![0u8; 45]; + + // Variant tag = 3 (ProgramData) + data[0..4].copy_from_slice(&3u32.to_le_bytes()); + + // Slot = 42 + data[4..12].copy_from_slice(&42u64.to_le_bytes()); + + // Option discriminant = 1 (Some) + data[12] = 1; + + // Authority pubkey = [1..=32] + let authority: [u8; 32] = core::array::from_fn(|i| (i + 1) as u8); + data[13..45].copy_from_slice(&authority); + + // Parse variant tag + let tag = u32::from_le_bytes(data[0..4].try_into().unwrap()); + assert_eq!(tag, PROGRAM_DATA_VARIANT_TAG); + + // Parse slot + let slot = u64::from_le_bytes(data[4..12].try_into().unwrap()); + assert_eq!(slot, 42); + + // Parse authority + assert_eq!(data[12], 1); + let mut parsed_auth = [0u8; 32]; + parsed_auth.copy_from_slice(&data[13..45]); + assert_eq!(parsed_auth, authority); + + // Test None case + data[12] = 0; + assert_eq!(data[12], 0); + } +} diff --git a/sdk-libs/sdk-interface/src/program/config/mod.rs b/sdk-libs/sdk-interface/src/program/config/mod.rs index bbe17261d0..4df3e108f7 100644 --- a/sdk-libs/sdk-interface/src/program/config/mod.rs +++ b/sdk-libs/sdk-interface/src/program/config/mod.rs @@ -1,43 +1,135 @@ //! LightConfig management for compressible accounts. -use std::collections::HashSet; +use light_account_checks::AccountInfoTrait; +use light_compressible::rent::RentConfig; -use solana_msg::msg; -use solana_pubkey::Pubkey; - -use crate::error::LightPdaError; +use crate::{ + error::LightPdaError, + AnchorDeserialize, AnchorSerialize, +}; -mod create; +pub mod create; mod state; -mod update; +pub mod update; // --- Constants --- pub const COMPRESSIBLE_CONFIG_SEED: &[u8] = b"compressible_config"; pub const MAX_ADDRESS_TREES_PER_SPACE: usize = 1; -// Re-export from sdk-types // --- Re-exports --- -pub use create::{ - check_program_upgrade_authority, process_initialize_light_config, - process_initialize_light_config_checked, -}; // Re-export Discriminator trait so users can access LightConfig::LIGHT_DISCRIMINATOR pub use light_account_checks::discriminator::Discriminator; pub use light_sdk_types::constants::RENT_SPONSOR_SEED; pub use state::LightConfig; -pub use update::process_update_light_config; + +// ============================================================================= +// Instruction params (serialized by client, deserialized by program) +// ============================================================================= + +/// Parameters for initialize_compression_config instruction. +/// Uses `[u8; 32]` for pubkeys - borsh-compatible with `solana_pubkey::Pubkey`. +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] +pub struct InitializeLightConfigParams { + pub rent_sponsor: [u8; 32], + pub compression_authority: [u8; 32], + pub rent_config: RentConfig, + pub write_top_up: u32, + pub address_space: Vec<[u8; 32]>, + pub config_bump: u8, +} + +/// Parameters for update_compression_config instruction. +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] +pub struct UpdateLightConfigParams { + pub new_update_authority: Option<[u8; 32]>, + pub new_rent_sponsor: Option<[u8; 32]>, + pub new_compression_authority: Option<[u8; 32]>, + pub new_rent_config: Option, + pub new_write_top_up: Option, + pub new_address_space: Option>, +} + +// ============================================================================= +// Top-level wrapper functions (remaining_accounts + instruction_data) +// ============================================================================= + +/// Initialize a LightConfig PDA with upgrade authority check. +/// +/// Account layout in remaining_accounts: +/// - [0] payer (signer, mut) +/// - [1] config_account (mut) +/// - [2] program_data_account (readonly) +/// - [3] authority (signer) +/// - [4] system_program +pub fn process_initialize_light_config_checked( + remaining_accounts: &[AI], + instruction_data: &[u8], + program_id: &[u8; 32], +) -> Result<(), LightPdaError> { + if remaining_accounts.len() < 5 { + return Err(LightPdaError::NotEnoughAccountKeys); + } + + let params = InitializeLightConfigParams::try_from_slice(instruction_data) + .map_err(|_| LightPdaError::Borsh)?; + + create::process_initialize_light_config_checked( + &remaining_accounts[1], // config_account + &remaining_accounts[3], // authority + &remaining_accounts[2], // program_data_account + ¶ms.rent_sponsor, + ¶ms.compression_authority, + params.rent_config, + params.write_top_up, + params.address_space, + params.config_bump, + &remaining_accounts[0], // payer + &remaining_accounts[4], // system_program + program_id, + ) +} + +/// Update an existing LightConfig PDA. +/// +/// Account layout in remaining_accounts: +/// - [0] config_account (mut) +/// - [1] authority (signer) +pub fn process_update_light_config( + remaining_accounts: &[AI], + instruction_data: &[u8], + program_id: &[u8; 32], +) -> Result<(), LightPdaError> { + if remaining_accounts.len() < 2 { + return Err(LightPdaError::NotEnoughAccountKeys); + } + + let params = UpdateLightConfigParams::try_from_slice(instruction_data) + .map_err(|_| LightPdaError::Borsh)?; + + update::process_update_light_config( + &remaining_accounts[0], // config_account + &remaining_accounts[1], // authority + params.new_update_authority.as_ref(), + params.new_rent_sponsor.as_ref(), + params.new_compression_authority.as_ref(), + params.new_rent_config, + params.new_write_top_up, + params.new_address_space, + program_id, + ) +} // --- Shared validators (used by create and update) --- /// Validates that address_space contains no duplicate pubkeys pub(super) fn validate_address_space_no_duplicates( - address_space: &[Pubkey], + address_space: &[[u8; 32]], ) -> Result<(), LightPdaError> { + use std::collections::HashSet; let mut seen = HashSet::new(); for pubkey in address_space { if !seen.insert(pubkey) { - msg!("Duplicate pubkey found in address_space: {}", pubkey); return Err(LightPdaError::ConstraintViolation); } } @@ -46,15 +138,11 @@ pub(super) fn validate_address_space_no_duplicates( /// Validates that new_address_space only adds to existing address_space (no removals) pub(super) fn validate_address_space_only_adds( - existing_address_space: &[Pubkey], - new_address_space: &[Pubkey], + existing_address_space: &[[u8; 32]], + new_address_space: &[[u8; 32]], ) -> Result<(), LightPdaError> { for existing_pubkey in existing_address_space { if !new_address_space.contains(existing_pubkey) { - msg!( - "Cannot remove existing pubkey from address_space: {}", - existing_pubkey - ); return Err(LightPdaError::ConstraintViolation); } } diff --git a/sdk-libs/sdk-interface/src/program/config/state.rs b/sdk-libs/sdk-interface/src/program/config/state.rs index bfb274dadd..178dc59c21 100644 --- a/sdk-libs/sdk-interface/src/program/config/state.rs +++ b/sdk-libs/sdk-interface/src/program/config/state.rs @@ -3,14 +3,11 @@ use light_account_checks::{ checks::check_discriminator, discriminator::{Discriminator, DISCRIMINATOR_LEN}, + AccountInfoTrait, }; use light_compressible::rent::RentConfig; -use solana_account_info::AccountInfo; -use solana_msg::msg; -use solana_program_error::ProgramError; -use solana_pubkey::Pubkey; -use super::{COMPRESSIBLE_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE, RENT_SPONSOR_SEED}; +use super::{COMPRESSIBLE_CONFIG_SEED, MAX_ADDRESS_TREES_PER_SPACE}; use crate::{error::LightPdaError, AnchorDeserialize, AnchorSerialize}; /// Global configuration for compressible accounts @@ -21,11 +18,11 @@ pub struct LightConfig { /// Lamports to top up on each write (heuristic) pub write_top_up: u32, /// Authority that can update the config - pub update_authority: Pubkey, + pub update_authority: [u8; 32], /// Account that receives rent from compressed PDAs - pub rent_sponsor: Pubkey, + pub rent_sponsor: [u8; 32], /// Authority that can compress/close PDAs (distinct from rent_sponsor) - pub compression_authority: Pubkey, + pub compression_authority: [u8; 32], /// Rent function parameters for compressibility and distribution pub rent_config: RentConfig, /// Config bump seed (0) @@ -35,7 +32,7 @@ pub struct LightConfig { /// Rent sponsor PDA bump seed pub rent_sponsor_bump: u8, /// Address space for compressed accounts (currently 1 address_tree allowed) - pub address_space: Vec, + pub address_space: Vec<[u8; 32]>, } /// Implement the Light Discriminator trait for LightConfig @@ -76,113 +73,86 @@ impl LightConfig { + (32 * num_address_trees) } - /// Derives the config PDA address with config bump - pub fn derive_pda(program_id: &Pubkey, config_bump: u8) -> (Pubkey, u8) { - // Convert u8 to u16 to match program-libs derivation (uses u16 with to_le_bytes) + /// Derives the config PDA address (returns raw bytes). + /// Generic over AccountInfoTrait for framework-agnostic PDA derivation. + pub fn derive_pda_bytes( + program_id: &[u8; 32], + config_bump: u8, + ) -> ([u8; 32], u8) { let config_bump_u16 = config_bump as u16; - Pubkey::find_program_address( + AI::find_program_address( &[COMPRESSIBLE_CONFIG_SEED, &config_bump_u16.to_le_bytes()], program_id, ) } - /// Derives the default config PDA address (config_bump = 0) - pub fn derive_default_pda(program_id: &Pubkey) -> (Pubkey, u8) { - Self::derive_pda(program_id, 0) - } - - /// Derives the rent sponsor PDA address for a program. - /// Seeds: ["rent_sponsor"] - pub fn derive_rent_sponsor_pda(program_id: &Pubkey) -> (Pubkey, u8) { - Pubkey::find_program_address(&[RENT_SPONSOR_SEED], program_id) + /// Derives the rent sponsor PDA address (returns raw bytes). + pub fn derive_rent_sponsor_pda_bytes( + program_id: &[u8; 32], + ) -> ([u8; 32], u8) { + AI::find_program_address(&[super::RENT_SPONSOR_SEED], program_id) } /// Validates rent_sponsor matches config and returns stored bump for signing. - pub fn validate_rent_sponsor( + pub fn validate_rent_sponsor_account( &self, - rent_sponsor: &AccountInfo, - ) -> Result { - if *rent_sponsor.key != self.rent_sponsor { - msg!( - "rent_sponsor mismatch: expected {:?}, got {:?}", - self.rent_sponsor, - rent_sponsor.key - ); - return Err(LightPdaError::InvalidRentSponsor.into()); + rent_sponsor: &AI, + ) -> Result { + if rent_sponsor.key() != self.rent_sponsor { + return Err(LightPdaError::InvalidRentSponsor); } Ok(self.rent_sponsor_bump) } /// Checks the config account - pub fn validate(&self) -> Result<(), ProgramError> { + pub fn validate(&self) -> Result<(), LightPdaError> { if self.version != 1 { - msg!( - "LightConfig validation failed: Unsupported config version: {}", - self.version - ); - return Err(LightPdaError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation); } if self.address_space.len() != 1 { - msg!( - "LightConfig validation failed: Address space must contain exactly 1 pubkey, found: {}", - self.address_space.len() - ); - return Err(LightPdaError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation); } // For now, only allow config_bump = 0 to keep it simple if self.config_bump != 0 { - msg!( - "LightConfig validation failed: Config bump must be 0 for now, found: {}", - self.config_bump - ); - return Err(LightPdaError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation); } Ok(()) } - /// Loads and validates config from account, checking owner, discriminator, and PDA derivation + /// Loads and validates config from account, checking owner, discriminator, and PDA derivation. + /// Generic over AccountInfoTrait - works with both solana and pinocchio. #[inline(never)] - pub fn load_checked( - account: &AccountInfo, - program_id: &Pubkey, - ) -> Result { + pub fn load_checked( + account: &AI, + program_id: &[u8; 32], + ) -> Result { // CHECK: Owner - if account.owner != program_id { - msg!( - "LightConfig::load_checked failed: Config account owner mismatch. Expected: {:?}. Found: {:?}.", - program_id, - account.owner - ); - return Err(LightPdaError::ConstraintViolation.into()); + if !account.is_owned_by(program_id) { + return Err(LightPdaError::ConstraintViolation); } - let data = account.try_borrow_data()?; + let data = account + .try_borrow_data() + .map_err(|_| LightPdaError::ConstraintViolation)?; // CHECK: Discriminator using light-account-checks - check_discriminator::(&data).map_err(|e| { - msg!("LightConfig::load_checked failed: {:?}", e); - LightPdaError::ConstraintViolation - })?; + check_discriminator::(&data).map_err(|_| LightPdaError::ConstraintViolation)?; // Deserialize from offset after discriminator - let config = Self::try_from_slice(&data[DISCRIMINATOR_LEN..]).map_err(|err| { - msg!( - "LightConfig::load_checked failed: Failed to deserialize config data: {:?}", - err - ); - LightPdaError::Borsh - })?; + let config = + Self::try_from_slice(&data[DISCRIMINATOR_LEN..]).map_err(|_| LightPdaError::Borsh)?; config.validate()?; // CHECK: PDA derivation - let (expected_pda, _) = Self::derive_pda(program_id, config.config_bump); - if expected_pda != *account.key { - msg!( - "LightConfig::load_checked failed: Config account key mismatch. Expected PDA: {:?}. Found: {:?}.", - expected_pda, - account.key - ); - return Err(LightPdaError::ConstraintViolation.into()); + let (expected_pda, _) = AI::find_program_address( + &[ + COMPRESSIBLE_CONFIG_SEED, + &(config.config_bump as u16).to_le_bytes(), + ], + program_id, + ); + if expected_pda != account.key() { + return Err(LightPdaError::ConstraintViolation); } Ok(config) diff --git a/sdk-libs/sdk-interface/src/program/config/update.rs b/sdk-libs/sdk-interface/src/program/config/update.rs index a5571b2577..7f1e41d4db 100644 --- a/sdk-libs/sdk-interface/src/program/config/update.rs +++ b/sdk-libs/sdk-interface/src/program/config/update.rs @@ -1,11 +1,7 @@ -//! Config update instruction. +//! Config update instruction (generic over AccountInfoTrait). -use light_account_checks::{checks::check_signer, discriminator::DISCRIMINATOR_LEN}; +use light_account_checks::{checks::check_signer, discriminator::DISCRIMINATOR_LEN, AccountInfoTrait}; use light_compressible::rent::RentConfig; -use solana_account_info::AccountInfo; -use solana_msg::msg; -use solana_program_error::ProgramError; -use solana_pubkey::Pubkey; use super::{ state::LightConfig, validate_address_space_no_duplicates, validate_address_space_only_adds, @@ -13,45 +9,28 @@ use super::{ }; use crate::{error::LightPdaError, AnchorSerialize}; -/// Updates an existing compressible config -/// -/// # Arguments -/// * `config_account` - The config PDA account to update -/// * `authority` - Current update authority (must match config) -/// * `new_update_authority` - Optional new update authority -/// * `new_rent_sponsor` - Optional new rent recipient -/// * `new_compression_authority` - Optional new compression authority -/// * `new_rent_config` - Optional new rent function parameters -/// * `new_write_top_up` - Optional new write top-up amount -/// * `new_address_space` - Optional new address space (currently 1 address_tree allowed) -/// * `owner_program_id` - The program that owns the config -/// -/// # Returns -/// * `Ok(())` if config was updated successfully -/// * `Err(ProgramError)` if there was an error +/// Updates an existing compressible config. #[allow(clippy::too_many_arguments)] -pub fn process_update_light_config<'info>( - config_account: &AccountInfo<'info>, - authority: &AccountInfo<'info>, - new_update_authority: Option<&Pubkey>, - new_rent_sponsor: Option<&Pubkey>, - new_compression_authority: Option<&Pubkey>, +pub fn process_update_light_config( + config_account: &AI, + authority: &AI, + new_update_authority: Option<&[u8; 32]>, + new_rent_sponsor: Option<&[u8; 32]>, + new_compression_authority: Option<&[u8; 32]>, new_rent_config: Option, new_write_top_up: Option, - new_address_space: Option>, - owner_program_id: &Pubkey, -) -> Result<(), ProgramError> { - // CHECK: PDA derivation + new_address_space: Option>, + owner_program_id: &[u8; 32], +) -> Result<(), LightPdaError> { + // CHECK: PDA derivation + discriminator + owner let mut config = LightConfig::load_checked(config_account, owner_program_id)?; // CHECK: signer - check_signer(authority).inspect_err(|_| { - msg!("Update authority must be signer"); - })?; + check_signer(authority).map_err(LightPdaError::AccountCheck)?; + // CHECK: authority - if *authority.key != config.update_authority { - msg!("Invalid update authority"); - return Err(LightPdaError::ConstraintViolation.into()); + if authority.key() != config.update_authority { + return Err(LightPdaError::ConstraintViolation); } if let Some(new_authority) = new_update_authority { @@ -72,31 +51,22 @@ pub fn process_update_light_config<'info>( if let Some(new_address_space) = new_address_space { // CHECK: address space length if new_address_space.len() != MAX_ADDRESS_TREES_PER_SPACE { - msg!( - "New address space must contain exactly 1 pubkey, found: {}", - new_address_space.len() - ); - return Err(LightPdaError::ConstraintViolation.into()); + return Err(LightPdaError::ConstraintViolation); } validate_address_space_no_duplicates(&new_address_space)?; - validate_address_space_only_adds(&config.address_space, &new_address_space)?; config.address_space = new_address_space; } - let mut data = config_account.try_borrow_mut_data().map_err(|e| { - msg!("Failed to borrow mut data for config_account: {:?}", e); - LightPdaError::from(e) - })?; + let mut data = config_account + .try_borrow_mut_data() + .map_err(LightPdaError::AccountCheck)?; // Serialize after discriminator (discriminator is preserved from init) config .serialize(&mut &mut data[DISCRIMINATOR_LEN..]) - .map_err(|e| { - msg!("Failed to serialize updated config: {:?}", e); - LightPdaError::Borsh - })?; + .map_err(|_| LightPdaError::Borsh)?; Ok(()) } diff --git a/sdk-libs/sdk-interface/src/program/decompression/create_token_account.rs b/sdk-libs/sdk-interface/src/program/decompression/create_token_account.rs index ef03341a25..429dafc2d4 100644 --- a/sdk-libs/sdk-interface/src/program/decompression/create_token_account.rs +++ b/sdk-libs/sdk-interface/src/program/decompression/create_token_account.rs @@ -1,61 +1,50 @@ //! ATA and token account creation helpers for decompression. +//! +//! Returns `(instruction_data, account_metas)` tuples for use with `AI::invoke_cpi()`. -use light_token_interface::instructions::{ - create_token_account::CreateTokenAccountInstructionData, - extensions::{CompressToPubkey, CompressibleExtensionInstructionData}, +use light_account_checks::CpiMeta; +use light_token_interface::{ + instructions::{ + create_associated_token_account::CreateAssociatedTokenAccountInstructionData, + create_token_account::CreateTokenAccountInstructionData, + extensions::{CompressToPubkey, CompressibleExtensionInstructionData}, + }, + LIGHT_TOKEN_PROGRAM_ID, }; -use solana_instruction::{AccountMeta, Instruction}; -use solana_program_error::ProgramError; -use solana_pubkey::Pubkey; -use crate::AnchorSerialize; +use crate::{error::LightPdaError, AnchorSerialize}; -/// Build a CreateAssociatedTokenAccountIdempotent instruction for ATA decompression. +/// Build instruction data and account metas for creating a compressible ATA. /// -/// Creates a compressible ATA with compression_only mode (required for ATA decompression). +/// Returns `(data, account_metas, program_id)` for use with `AI::invoke_cpi()`. /// /// # Account order (per on-chain handler): -/// 0. owner (non-mut, non-signer) - The wallet owner -/// 1. mint (non-mut, non-signer) - The token mint -/// 2. fee_payer (signer, writable) - Pays for account creation -/// 3. associated_token_account (writable, NOT signer) - The ATA to create -/// 4. system_program (readonly) - System program -/// 5. compressible_config (readonly) - Compressible config PDA -/// 6. rent_payer (writable) - Rent sponsor account -/// -/// # Arguments -/// * `wallet_owner` - The wallet owner (ATA derivation seed) -/// * `mint` - The token mint -/// * `fee_payer` - Pays for account creation -/// * `ata` - The ATA pubkey (derived from wallet_owner, program_id, mint) -/// * `bump` - The ATA derivation bump -/// * `compressible_config` - Compressible config PDA -/// * `rent_sponsor` - Rent sponsor account -/// * `write_top_up` - Lamports per write for top-up +/// 0. owner (non-mut, non-signer) +/// 1. mint (non-mut, non-signer) +/// 2. fee_payer (signer, writable) +/// 3. associated_token_account (writable, NOT signer) +/// 4. system_program (readonly) +/// 5. compressible_config (readonly) +/// 6. rent_payer (writable) #[allow(clippy::too_many_arguments)] pub fn build_create_ata_instruction( - wallet_owner: &Pubkey, - mint: &Pubkey, - fee_payer: &Pubkey, - ata: &Pubkey, + wallet_owner: &[u8; 32], + mint: &[u8; 32], + fee_payer: &[u8; 32], + ata: &[u8; 32], bump: u8, - compressible_config: &Pubkey, - rent_sponsor: &Pubkey, + compressible_config: &[u8; 32], + rent_sponsor: &[u8; 32], write_top_up: u32, -) -> Result { - use light_token_interface::instructions::{ - create_associated_token_account::CreateAssociatedTokenAccountInstructionData, - extensions::CompressibleExtensionInstructionData, - }; - +) -> Result<(Vec, Vec), LightPdaError> { let instruction_data = CreateAssociatedTokenAccountInstructionData { bump, compressible_config: Some(CompressibleExtensionInstructionData { token_account_version: 3, // ShaFlat version (required) - rent_payment: 16, // 24h, TODO: make configurable + rent_payment: 16, // 24h compression_only: 1, // Required for ATA write_top_up, - compress_to_account_pubkey: None, // Required to be None for ATA + compress_to_account_pubkey: None, }), }; @@ -63,57 +52,76 @@ pub fn build_create_ata_instruction( data.push(102u8); // CreateAssociatedTokenAccountIdempotent discriminator instruction_data .serialize(&mut data) - .map_err(|e| ProgramError::BorshIoError(e.to_string()))?; + .map_err(|_| LightPdaError::Borsh)?; let accounts = vec![ - AccountMeta::new_readonly(*wallet_owner, false), - AccountMeta::new_readonly(*mint, false), - AccountMeta::new(*fee_payer, true), - AccountMeta::new(*ata, false), // NOT a signer - ATA is derived - AccountMeta::new_readonly(Pubkey::default(), false), // system_program - AccountMeta::new_readonly(*compressible_config, false), - AccountMeta::new(*rent_sponsor, false), + CpiMeta { + pubkey: *wallet_owner, + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: *mint, + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: *fee_payer, + is_signer: true, + is_writable: true, + }, + CpiMeta { + pubkey: *ata, + is_signer: false, + is_writable: true, + }, // NOT a signer - ATA is derived + CpiMeta { + pubkey: [0u8; 32], + is_signer: false, + is_writable: false, + }, // system_program + CpiMeta { + pubkey: *compressible_config, + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: *rent_sponsor, + is_signer: false, + is_writable: true, + }, ]; - Ok(Instruction { - program_id: light_token_interface::LIGHT_TOKEN_PROGRAM_ID.into(), - accounts, - data, - }) + Ok((data, accounts)) } -/// Build a CreateTokenAccount instruction for decompression. +/// Build instruction data and account metas for creating a compressible token account. /// -/// Creates a compressible token account with ShaFlat version (required by light token program). +/// Returns `(data, account_metas)` for use with `AI::invoke_cpi()`. /// /// # Account order: -/// 0. token_account (signer, writable) - The token account PDA to create -/// 1. mint (readonly) - The token mint -/// 2. fee_payer (signer, writable) - Pays for account creation -/// 3. compressible_config (readonly) - Compressible config PDA -/// 4. system_program (readonly) - System program -/// 5. rent_sponsor (writable) - Rent sponsor account -/// -/// # Arguments -/// * `signer_seeds` - Seeds including bump for the token account PDA -/// * `program_id` - Program ID that owns the token account PDA +/// 0. token_account (signer, writable) +/// 1. mint (readonly) +/// 2. fee_payer (signer, writable) +/// 3. compressible_config (readonly) +/// 4. system_program (readonly) +/// 5. rent_sponsor (writable) #[allow(clippy::too_many_arguments)] pub fn build_create_token_account_instruction( - token_account: &Pubkey, - mint: &Pubkey, - owner: &Pubkey, - fee_payer: &Pubkey, - compressible_config: &Pubkey, - rent_sponsor: &Pubkey, + token_account: &[u8; 32], + mint: &[u8; 32], + owner: &[u8; 32], + fee_payer: &[u8; 32], + compressible_config: &[u8; 32], + rent_sponsor: &[u8; 32], write_top_up: u32, signer_seeds: &[&[u8]], - program_id: &Pubkey, -) -> Result { - // Build CompressToPubkey from signer_seeds (last seed is bump) + program_id: &[u8; 32], +) -> Result<(Vec, Vec), LightPdaError> { let bump = signer_seeds .last() .and_then(|s| s.first().copied()) - .ok_or(ProgramError::InvalidSeeds)?; + .ok_or(LightPdaError::InvalidSeeds)?; let seeds_without_bump: Vec> = signer_seeds .iter() .take(signer_seeds.len().saturating_sub(1)) @@ -122,16 +130,16 @@ pub fn build_create_token_account_instruction( let compress_to_account_pubkey = CompressToPubkey { bump, - program_id: program_id.to_bytes(), + program_id: *program_id, seeds: seeds_without_bump, }; let instruction_data = CreateTokenAccountInstructionData { - owner: light_compressed_account::Pubkey::from(owner.to_bytes()), + owner: light_compressed_account::Pubkey::from(*owner), compressible_config: Some(CompressibleExtensionInstructionData { token_account_version: 3, // ShaFlat version (required) - rent_payment: 16, // 24h, TODO: make configurable - compression_only: 0, // Regular tokens can be transferred, not compression-only + rent_payment: 16, // 24h + compression_only: 0, // Regular tokens can be transferred write_top_up, compress_to_account_pubkey: Some(compress_to_account_pubkey), }), @@ -141,20 +149,43 @@ pub fn build_create_token_account_instruction( data.push(18u8); // InitializeAccount3 opcode instruction_data .serialize(&mut data) - .map_err(|e| ProgramError::BorshIoError(e.to_string()))?; + .map_err(|_| LightPdaError::Borsh)?; let accounts = vec![ - AccountMeta::new(*token_account, true), - AccountMeta::new_readonly(*mint, false), - AccountMeta::new(*fee_payer, true), - AccountMeta::new_readonly(*compressible_config, false), - AccountMeta::new_readonly(Pubkey::default(), false), // system_program - AccountMeta::new(*rent_sponsor, false), + CpiMeta { + pubkey: *token_account, + is_signer: true, + is_writable: true, + }, + CpiMeta { + pubkey: *mint, + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: *fee_payer, + is_signer: true, + is_writable: true, + }, + CpiMeta { + pubkey: *compressible_config, + is_signer: false, + is_writable: false, + }, + CpiMeta { + pubkey: [0u8; 32], + is_signer: false, + is_writable: false, + }, // system_program + CpiMeta { + pubkey: *rent_sponsor, + is_signer: false, + is_writable: true, + }, ]; - Ok(Instruction { - program_id: light_token_interface::LIGHT_TOKEN_PROGRAM_ID.into(), - accounts, - data, - }) + Ok((data, accounts)) } + +/// The Light Token Program ID for CPI calls. +pub const TOKEN_PROGRAM_ID: [u8; 32] = LIGHT_TOKEN_PROGRAM_ID; diff --git a/sdk-libs/sdk-interface/src/program/decompression/mod.rs b/sdk-libs/sdk-interface/src/program/decompression/mod.rs index d6d99bfac8..f647dc0f56 100644 --- a/sdk-libs/sdk-interface/src/program/decompression/mod.rs +++ b/sdk-libs/sdk-interface/src/program/decompression/mod.rs @@ -1,13 +1,8 @@ //! Decompression functions for PDA and token accounts. -#[cfg(feature = "anchor")] -pub mod create_token_account; - -#[cfg(feature = "anchor")] -pub mod processor; - -#[cfg(feature = "anchor")] pub mod pda; - -#[cfg(feature = "anchor")] +pub mod processor; +#[cfg(feature = "token")] +pub mod create_token_account; +#[cfg(feature = "token")] pub mod token; diff --git a/sdk-libs/sdk-interface/src/program/decompression/pda.rs b/sdk-libs/sdk-interface/src/program/decompression/pda.rs index 2fd0cca988..09cc845eab 100644 --- a/sdk-libs/sdk-interface/src/program/decompression/pda.rs +++ b/sdk-libs/sdk-interface/src/program/decompression/pda.rs @@ -1,22 +1,24 @@ -use anchor_lang::prelude::*; +//! Generic prepare_account_for_decompression. + +use light_account_checks::AccountInfoTrait; use light_compressed_account::{ address::derive_address, compressed_account::PackedMerkleContext, instruction_data::with_account_info::{CompressedAccountInfo, InAccountInfo, OutAccountInfo}, }; use light_compressible::DECOMPRESSED_PDA_DISCRIMINATOR; -use light_hasher::{sha256::Sha256BE, Hasher, Sha256}; +use light_hasher::{sha256::Sha256BE, Hasher}; use light_sdk_types::{constants::RENT_SPONSOR_SEED, instruction::PackedStateTreeInfo}; -use solana_account_info::AccountInfo; -use solana_program_error::ProgramError; -use solana_pubkey::Pubkey; use crate::{ - accounts::create_pda::create_pda_account, - program::decompression::processor::DecompressCtx, account::light_account::LightAccount, - program::variant::{LightAccountVariantTrait, PackedLightAccountVariantTrait}, - LightDiscriminator, + error::LightPdaError, + light_account_checks::discriminator::Discriminator as LightDiscriminator, + program::{ + decompression::processor::DecompressCtx, + variant::{LightAccountVariantTrait, PackedLightAccountVariantTrait}, + }, + AnchorSerialize, }; /// Generic prepare_account_for_decompression. @@ -37,27 +39,33 @@ use crate::{ /// # Type Parameters /// * `SEED_COUNT` - Number of seeds including bump /// * `P` - Packed variant type implementing PackedLightAccountVariantTrait -pub fn prepare_account_for_decompression<'info, const SEED_COUNT: usize, P>( +/// * `AI` - Account info type (solana or pinocchio) +#[inline(never)] +pub fn prepare_account_for_decompression( packed: &P, tree_info: &PackedStateTreeInfo, output_queue_index: u8, - pda_account: &AccountInfo<'info>, - ctx: &mut DecompressCtx<'_, 'info>, -) -> std::result::Result<(), ProgramError> + pda_account: &AI, + ctx: &mut DecompressCtx<'_, AI>, +) -> Result<(), LightPdaError> where + AI: AccountInfoTrait + Clone, P: PackedLightAccountVariantTrait, - >::Data: - LightAccount + LightDiscriminator + Clone + AnchorSerialize + AnchorDeserialize, + >::Data: LightAccount, { + // Type alias for the account data type + type Data = + <