Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/actions/setup-and-build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ runs:
target/deploy/create_address_test_program.so
target/deploy/sdk_anchor_test.so
target/deploy/sdk-compressible-test.so
target/deploy/csdk_anchor_derived_test.so
target/deploy/csdk_anchor_full_derived_test.so
key: ${{ runner.os }}-program-tests-${{ hashFiles('program-tests/**/Cargo.toml', 'program-tests/**/Cargo.lock', 'program-tests/**/*.rs', 'test-programs/**/Cargo.toml', 'test-programs/**/*.rs', 'sdk-tests/**/Cargo.toml', 'sdk-tests/**/*.rs') }}
restore-keys: |
${{ runner.os }}-program-tests-
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sdk-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
- program: native
sub-tests: '["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v1-native-test", "cargo-test-sbf -p client-test"]'
- program: anchor & pinocchio
sub-tests: '["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -p sdk-compressible-test", "cargo-test-sbf -p sdk-pinocchio-v1-test", "cargo-test-sbf -p sdk-pinocchio-v2-test", "cargo-test-sbf -p pinocchio-nostd-test"]'
sub-tests: '["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -p sdk-compressible-test", "cargo-test-sbf -p csdk-anchor-derived-test", "cargo-test-sbf -p csdk-anchor-full-derived-test", "cargo-test-sbf -p sdk-pinocchio-v1-test", "cargo-test-sbf -p sdk-pinocchio-v2-test", "cargo-test-sbf -p pinocchio-nostd-test"]'
- program: token test
sub-tests: '["cargo-test-sbf -p sdk-token-test"]'
- program: sdk-libs
Expand Down
74 changes: 73 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ members = [
"sdk-tests/sdk-v1-native-test",
"sdk-tests/sdk-token-test",
"sdk-tests/sdk-compressible-test",
"sdk-tests/csdk-anchor-derived-test",
"sdk-tests/csdk-anchor-full-derived-test",
"forester-utils",
"forester",
"sparse-merkle-tree",
Expand Down
6 changes: 5 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions programs/system/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ reinit = []
default = ["reinit"]
test-sbf = []
readonly = []
profile-program = ["light-program-profiler/profile-program"]
profile-heap = ["light-program-profiler/profile-heap", "dep:light-heap"]
profile-program = []
profile-heap = ["dep:light-heap"]
custom-heap = []

[dependencies]
Expand Down
160 changes: 160 additions & 0 deletions sdk-libs/compressed-token-sdk/src/decompress_runtime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//! Runtime helpers for token decompression.
use light_ctoken_types::instructions::transfer2::MultiInputTokenDataWithContext;
use light_sdk::{cpi::v2::CpiAccounts, instruction::ValidityProof};
use light_sdk_types::instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress;
use solana_account_info::AccountInfo;
use solana_msg::msg;
use solana_program_error::ProgramError;
use solana_pubkey::Pubkey;

use crate::compat::PackedCTokenData;

/// Trait for getting token account seeds.
pub trait CTokenSeedProvider: Copy {
/// Type of accounts struct needed for seed derivation.
type Accounts<'info>;

/// Get seeds for the token account PDA (used for decompression).
fn get_seeds<'a, 'info>(
&self,
accounts: &'a Self::Accounts<'info>,
remaining_accounts: &'a [AccountInfo<'info>],
) -> Result<(Vec<Vec<u8>>, Pubkey), ProgramError>;

/// Get authority seeds for signing during compression.
fn get_authority_seeds<'a, 'info>(
&self,
accounts: &'a Self::Accounts<'info>,
remaining_accounts: &'a [AccountInfo<'info>],
) -> Result<(Vec<Vec<u8>>, Pubkey), ProgramError>;
}

/// Token decompression processor.
#[inline(never)]
#[allow(clippy::too_many_arguments)]
pub fn process_decompress_tokens_runtime<'info, 'a, 'b, V, A>(
accounts_for_seeds: &A,
remaining_accounts: &[AccountInfo<'info>],
fee_payer: &AccountInfo<'info>,
ctoken_program: &AccountInfo<'info>,
ctoken_rent_sponsor: &AccountInfo<'info>,
ctoken_cpi_authority: &AccountInfo<'info>,
ctoken_config: &AccountInfo<'info>,
config: &AccountInfo<'info>,
ctoken_accounts: Vec<(
PackedCTokenData<V>,
CompressedAccountMetaNoLamportsNoAddress,
)>,
proof: ValidityProof,
cpi_accounts: &CpiAccounts<'b, 'info>,
post_system_accounts: &[AccountInfo<'info>],
has_pdas: bool,
program_id: &Pubkey,
) -> Result<(), ProgramError>
where
V: CTokenSeedProvider<Accounts<'info> = A>,
A: 'info,
{
let mut token_decompress_indices: Box<Vec<crate::instructions::DecompressFullIndices>> =
Box::new(Vec::with_capacity(ctoken_accounts.len()));
let mut token_signers_seed_groups: Vec<Vec<Vec<u8>>> =
Vec::with_capacity(ctoken_accounts.len());
let packed_accounts = post_system_accounts;

let authority = cpi_accounts
.authority()
.map_err(|_| ProgramError::MissingRequiredSignature)?;
let cpi_context = cpi_accounts
.cpi_context()
.map_err(|_| ProgramError::MissingRequiredSignature)?;

for (token_data, meta) in ctoken_accounts.into_iter() {
let owner_index: u8 = token_data.token_data.owner;
let mint_index: u8 = token_data.token_data.mint;
let mint_info = &packed_accounts[mint_index as usize];
let owner_info = &packed_accounts[owner_index as usize];

// Use trait method to get seeds (program-specific)
let (ctoken_signer_seeds, derived_token_account_address) = token_data
.variant
.get_seeds(accounts_for_seeds, remaining_accounts)?;

if derived_token_account_address != *owner_info.key {
msg!(
"derived_token_account_address: {:?}",
derived_token_account_address
);
msg!("owner_info.key: {:?}", owner_info.key);
return Err(ProgramError::InvalidAccountData);
}

let seed_refs: Vec<&[u8]> = ctoken_signer_seeds.iter().map(|s| s.as_slice()).collect();
let seeds_slice: &[&[u8]] = &seed_refs;

crate::instructions::create_token_account::create_ctoken_account_signed(
*program_id,
fee_payer.clone(),
owner_info.clone(),
mint_info.clone(),
*authority.key,
seeds_slice,
ctoken_rent_sponsor.clone(),
ctoken_config.clone(),
Some(2),
None,
)?;

let source = MultiInputTokenDataWithContext {
owner: token_data.token_data.owner,
amount: token_data.token_data.amount,
has_delegate: token_data.token_data.has_delegate,
delegate: token_data.token_data.delegate,
mint: token_data.token_data.mint,
version: token_data.token_data.version,
merkle_context: meta.tree_info.into(),
root_index: meta.tree_info.root_index,
};
let decompress_index = crate::instructions::DecompressFullIndices {
source,
destination_index: owner_index,
};
token_decompress_indices.push(decompress_index);
token_signers_seed_groups.push(ctoken_signer_seeds);
}

let ctoken_ix = crate::instructions::decompress_full_ctoken_accounts_with_indices(
*fee_payer.key,
proof,
if has_pdas {
Some(*cpi_context.key)
} else {
None
},
&token_decompress_indices,
packed_accounts,
)
.map_err(ProgramError::from)?;

let mut all_account_infos: Vec<AccountInfo<'info>> =
Vec::with_capacity(1 + post_system_accounts.len() + 3);
all_account_infos.push(fee_payer.clone());
all_account_infos.push(ctoken_cpi_authority.clone());
all_account_infos.push(ctoken_program.clone());
all_account_infos.push(ctoken_rent_sponsor.clone());
all_account_infos.push(config.clone());
all_account_infos.extend_from_slice(post_system_accounts);

let signer_seed_refs: Vec<Vec<&[u8]>> = token_signers_seed_groups
.iter()
.map(|group| group.iter().map(|s| s.as_slice()).collect())
.collect();
let signer_seed_slices: Vec<&[&[u8]]> = signer_seed_refs.iter().map(|g| g.as_slice()).collect();

solana_cpi::invoke_signed(
&ctoken_ix,
all_account_infos.as_slice(),
signer_seed_slices.as_slice(),
)?;

Ok(())
}
3 changes: 2 additions & 1 deletion sdk-libs/compressed-token-sdk/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod account;
pub mod account2;
pub mod ctoken;
pub mod decompress_runtime;
pub mod error;
pub mod instructions;
pub mod pack;
Expand All @@ -13,7 +14,7 @@ pub mod utils;
use anchor_lang::{AnchorDeserialize, AnchorSerialize};
#[cfg(not(feature = "anchor"))]
use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
// Re-export
pub use decompress_runtime::{process_decompress_tokens_runtime, CTokenSeedProvider};
pub use light_compressed_token_types::*;
pub use pack::{compat, Pack, Unpack};
pub use utils::{
Expand Down
43 changes: 1 addition & 42 deletions sdk-libs/compressible-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ pub struct DecompressMultipleAccountsIdempotentData<T> {
pub struct CompressAccountsIdempotentData {
pub proof: ValidityProof,
pub compressed_accounts: Vec<CompressedAccountMetaNoLamportsNoAddress>,
pub signer_seeds: Vec<Vec<Vec<u8>>>,
pub system_accounts_offset: u8,
}

Expand Down Expand Up @@ -279,59 +278,20 @@ pub mod compressible_instruction {
})
}

/// Builds compress_accounts_idempotent instruction for PDAs and token accounts
/// Builds compress_accounts_idempotent instruction for PDAs only
#[allow(clippy::too_many_arguments)]
pub fn compress_accounts_idempotent(
program_id: &Pubkey,
discriminator: &[u8],
account_pubkeys: &[Pubkey],
accounts_to_compress: &[Account],
program_account_metas: &[AccountMeta],
signer_seeds: Vec<Vec<Vec<u8>>>,
validity_proof_with_context: ValidityProofWithContext,
output_state_tree_info: TreeInfo,
) -> Result<Instruction, Box<dyn std::error::Error>> {
if account_pubkeys.len() != accounts_to_compress.len() {
return Err("Accounts pubkeys length must match accounts length".into());
}
println!(
"compress_accounts_idempotent - account_pubkeys: {:?}",
account_pubkeys
);
// Sanity checks.
if !signer_seeds.is_empty() && signer_seeds.len() != accounts_to_compress.len() {
return Err("Signer seeds length must match accounts length or be empty".into());
}
for (i, account) in account_pubkeys.iter().enumerate() {
if !signer_seeds.is_empty() {
let seeds = &signer_seeds[i];
if !seeds.is_empty() {
let derived = Pubkey::create_program_address(
&seeds.iter().map(|v| v.as_slice()).collect::<Vec<&[u8]>>(),
program_id,
);
match derived {
Ok(derived_pubkey) => {
if derived_pubkey != *account {
return Err(format!(
"Derived PDA does not match account_to_compress at index {}: expected {}, got {:?}",
i,
account,
derived_pubkey
).into());
}
}
Err(e) => {
return Err(format!(
"Failed to derive PDA for account_to_compress at index {}: {}",
i, e
)
.into());
}
}
}
}
}

let mut remaining_accounts = PackedAccounts::default();

Expand Down Expand Up @@ -373,7 +333,6 @@ pub mod compressible_instruction {
let instruction_data = CompressAccountsIdempotentData {
proof: validity_proof_with_context.proof,
compressed_accounts: compressed_account_metas_no_lamports_no_address,
signer_seeds,
system_accounts_offset: system_accounts_offset as u8,
};

Expand Down
Loading