use light_compressed_account::instruction_data::{
cpi_context::CompressedCpiContext,
with_account_info::{CompressedAccountInfo, InstructionDataInvokeCpiWithAccountInfo},
};
use light_sdk_types::{
cpi_accounts::CpiAccountsConfig, cpi_context_write::CpiContextWriteAccounts,
instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, CpiSigner,
};
use solana_account_info::AccountInfo;
use solana_msg::msg;
use solana_program_error::ProgramError;
use solana_pubkey::Pubkey;
use crate::{
cpi::{v2::CpiAccounts, InvokeLightSystemProgram},
AnchorDeserialize, AnchorSerialize, LightDiscriminator,
};
pub trait HasTokenVariant {
fn is_packed_token(&self) -> bool;
}
pub trait TokenSeedProvider: Copy {
fn get_seeds(&self, program_id: &Pubkey) -> Result<(Vec<Vec<u8>>, Pubkey), ProgramError>;
fn get_authority_seeds(
&self,
program_id: &Pubkey,
) -> Result<(Vec<Vec<u8>>, Pubkey), ProgramError>;
}
pub trait DecompressContext<'info> {
type CompressedData: HasTokenVariant;
type PackedTokenData;
type CompressedMeta: Clone;
type SeedParams;
fn fee_payer(&self) -> &AccountInfo<'info>;
fn config(&self) -> &AccountInfo<'info>;
fn rent_sponsor(&self) -> &AccountInfo<'info>;
fn token_rent_sponsor(&self) -> Option<&AccountInfo<'info>>;
fn token_program(&self) -> Option<&AccountInfo<'info>>;
fn token_cpi_authority(&self) -> Option<&AccountInfo<'info>>;
fn token_config(&self) -> Option<&AccountInfo<'info>>;
#[allow(clippy::type_complexity)]
#[allow(clippy::too_many_arguments)]
fn collect_pda_and_token<'b>(
&self,
cpi_accounts: &CpiAccounts<'b, 'info>,
address_space: Pubkey,
compressed_accounts: Vec<Self::CompressedData>,
solana_accounts: &[AccountInfo<'info>],
seed_params: Option<&Self::SeedParams>,
) -> Result<(
Vec<light_compressed_account::instruction_data::with_account_info::CompressedAccountInfo>,
Vec<(Self::PackedTokenData, Self::CompressedMeta)>
), ProgramError>;
#[allow(clippy::too_many_arguments)]
fn process_tokens<'b>(
&self,
remaining_accounts: &[AccountInfo<'info>],
fee_payer: &AccountInfo<'info>,
token_program: &AccountInfo<'info>,
token_rent_sponsor: &AccountInfo<'info>,
token_cpi_authority: &AccountInfo<'info>,
token_config: &AccountInfo<'info>,
config: &AccountInfo<'info>,
token_accounts: Vec<(Self::PackedTokenData, Self::CompressedMeta)>,
proof: crate::instruction::ValidityProof,
cpi_accounts: &CpiAccounts<'b, 'info>,
post_system_accounts: &[AccountInfo<'info>],
has_prior_context: bool,
) -> Result<(), ProgramError>;
}
pub trait PdaSeedDerivation<A, S> {
fn derive_pda_seeds_with_accounts(
&self,
program_id: &Pubkey,
accounts: &A,
seed_params: &S,
) -> Result<(Vec<Vec<u8>>, Pubkey), ProgramError>;
}
#[inline(never)]
pub fn check_account_types<T: HasTokenVariant>(compressed_accounts: &[T]) -> (bool, bool) {
let (mut has_tokens, mut has_pdas) = (false, false);
for account in compressed_accounts {
if account.is_packed_token() {
has_tokens = true;
} else {
has_pdas = true;
}
if has_tokens && has_pdas {
break;
}
}
(has_tokens, has_pdas)
}
#[inline(never)]
#[allow(clippy::too_many_arguments)]
pub fn handle_packed_pda_variant<'a, 'b, 'info, T, P, A, S>(
accounts_rent_sponsor: &AccountInfo<'info>,
cpi_accounts: &CpiAccounts<'b, 'info>,
address_space: Pubkey,
solana_account: &AccountInfo<'info>,
index: usize,
packed: &P,
meta: &CompressedAccountMetaNoLamportsNoAddress,
post_system_accounts: &[AccountInfo<'info>],
compressed_pda_infos: &mut Vec<CompressedAccountInfo>,
program_id: &Pubkey,
seed_accounts: &A,
seed_params: Option<&S>,
) -> Result<(), ProgramError>
where
T: PdaSeedDerivation<A, S>
+ Clone
+ crate::account::Size
+ LightDiscriminator
+ Default
+ AnchorSerialize
+ AnchorDeserialize
+ crate::interface::HasCompressionInfo
+ 'info,
P: crate::interface::Unpack<Unpacked = T>,
S: Default,
{
let data: T = P::unpack(packed, post_system_accounts)?;
let (seeds_vec, derived_pda) = if let Some(params) = seed_params {
data.derive_pda_seeds_with_accounts(program_id, seed_accounts, params)?
} else {
let default_params = S::default();
data.derive_pda_seeds_with_accounts(program_id, seed_accounts, &default_params)?
};
if derived_pda != *solana_account.key {
msg!(
"Derived PDA does not match account at index {}: expected {:?}, got {:?}, seeds: {:?}",
index,
solana_account.key,
derived_pda,
seeds_vec
);
return Err(ProgramError::from(
crate::error::LightSdkError::ConstraintViolation,
));
}
let compressed_infos = {
const MAX_SEEDS: usize = 16;
let mut seed_refs: [&[u8]; MAX_SEEDS] = [&[]; MAX_SEEDS];
let len = seeds_vec.len().min(MAX_SEEDS);
for i in 0..len {
seed_refs[i] = seeds_vec[i].as_slice();
}
crate::interface::decompress_idempotent::prepare_account_for_decompression_idempotent::<T>(
program_id,
data,
crate::interface::decompress_idempotent::into_compressed_meta_with_address(
meta,
solana_account,
address_space,
program_id,
),
solana_account,
accounts_rent_sponsor,
cpi_accounts,
&seed_refs[..len],
)?
};
compressed_pda_infos.extend(compressed_infos);
Ok(())
}
#[inline(never)]
#[allow(clippy::too_many_arguments)]
pub fn process_decompress_accounts_idempotent<'info, Ctx>(
ctx: &Ctx,
remaining_accounts: &[AccountInfo<'info>],
compressed_accounts: Vec<Ctx::CompressedData>,
proof: crate::instruction::ValidityProof,
system_accounts_offset: u8,
cpi_signer: CpiSigner,
program_id: &Pubkey,
seed_params: Option<&Ctx::SeedParams>,
) -> Result<(), ProgramError>
where
Ctx: DecompressContext<'info>,
{
let compression_config = crate::interface::LightConfig::load_checked(ctx.config(), program_id)?;
let address_space = compression_config.address_space[0];
let (has_tokens, has_pdas) = check_account_types(&compressed_accounts);
if !has_tokens && !has_pdas {
return Ok(());
}
let system_accounts_offset_usize = system_accounts_offset as usize;
if system_accounts_offset_usize > remaining_accounts.len() {
return Err(ProgramError::NotEnoughAccountKeys);
}
let needs_cpi_context = has_tokens && has_pdas;
let cpi_accounts = if needs_cpi_context {
CpiAccounts::new_with_config(
ctx.fee_payer(),
&remaining_accounts[system_accounts_offset_usize..],
CpiAccountsConfig::new_with_cpi_context(cpi_signer),
)
} else {
CpiAccounts::new(
ctx.fee_payer(),
&remaining_accounts[system_accounts_offset_usize..],
cpi_signer,
)
};
let pda_accounts_start = remaining_accounts
.len()
.checked_sub(compressed_accounts.len())
.ok_or_else(|| ProgramError::from(crate::error::LightSdkError::ConstraintViolation))?;
let solana_accounts = remaining_accounts
.get(pda_accounts_start..)
.ok_or_else(|| ProgramError::from(crate::error::LightSdkError::ConstraintViolation))?;
let (compressed_pda_infos, compressed_token_accounts) = ctx.collect_pda_and_token(
&cpi_accounts,
address_space,
compressed_accounts,
solana_accounts,
seed_params,
)?;
let has_pdas = !compressed_pda_infos.is_empty();
let has_tokens = !compressed_token_accounts.is_empty();
if !has_pdas && !has_tokens {
return Ok(());
}
let fee_payer = ctx.fee_payer();
if has_pdas {
if !has_tokens {
let cpi_signer_config = cpi_accounts.config().cpi_signer;
let instruction_data = InstructionDataInvokeCpiWithAccountInfo {
mode: 1,
bump: cpi_signer_config.bump,
invoking_program_id: cpi_signer_config.program_id.into(),
compress_or_decompress_lamports: 0,
is_compress: false,
with_cpi_context: false,
with_transaction_hash: false,
cpi_context: CompressedCpiContext::default(),
proof: proof.0,
new_address_params: Vec::new(),
account_infos: compressed_pda_infos,
read_only_addresses: Vec::new(),
read_only_accounts: Vec::new(),
};
instruction_data.invoke(cpi_accounts.clone())?;
} else {
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,
};
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: Vec::new(),
account_infos: compressed_pda_infos,
read_only_addresses: Vec::new(),
read_only_accounts: Vec::new(),
};
instruction_data.invoke_write_to_cpi_context_first(system_cpi_accounts)?;
}
}
if has_tokens {
let post_system_offset = cpi_accounts.system_accounts_end_offset();
let all_infos = cpi_accounts.account_infos();
let post_system_accounts = all_infos
.get(post_system_offset..)
.ok_or_else(|| ProgramError::from(crate::error::LightSdkError::ConstraintViolation))?;
let light_token_program = ctx
.token_program()
.ok_or(ProgramError::NotEnoughAccountKeys)?;
let token_rent_sponsor = ctx
.token_rent_sponsor()
.ok_or(ProgramError::NotEnoughAccountKeys)?;
let token_cpi_authority = ctx
.token_cpi_authority()
.ok_or(ProgramError::NotEnoughAccountKeys)?;
let token_config = ctx
.token_config()
.ok_or(ProgramError::NotEnoughAccountKeys)?;
ctx.process_tokens(
remaining_accounts,
fee_payer,
light_token_program,
token_rent_sponsor,
token_cpi_authority,
token_config,
ctx.config(),
compressed_token_accounts,
proof,
&cpi_accounts,
post_system_accounts,
has_pdas, )?;
}
Ok(())
}