Skip to content

Commit f2051e5

Browse files
add compress_pda helper
1 parent 3486575 commit f2051e5

File tree

4 files changed

+116
-102
lines changed

4 files changed

+116
-102
lines changed
Lines changed: 21 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
use borsh::{BorshDeserialize, BorshSerialize};
22
use light_sdk::{
3-
account::LightAccount,
4-
cpi::{CpiAccounts, CpiAccountsConfig, CpiInputs},
53
error::LightSdkError,
6-
instruction::ValidityProof,
4+
instruction::{account_meta::CompressedAccountMeta, ValidityProof},
75
};
86
use solana_program::{
9-
account_info::AccountInfo, clock::Clock, msg, program_error::ProgramError, pubkey::Pubkey,
10-
sysvar::Sysvar,
7+
account_info::AccountInfo, clock::Clock, msg, pubkey::Pubkey, sysvar::Sysvar,
118
};
129

13-
use crate::{create_pda::MyCompressedAccount, decompress_to_pda::DecompressedPdaAccount};
10+
use crate::{
11+
create_pda::MyCompressedAccount, decompress_to_pda::DecompressedPdaAccount,
12+
sdk::compress_pda::compress_pda,
13+
};
1414

1515
/// Compresses a PDA back into a compressed account
1616
/// Anyone can call this after the timeout period has elapsed
17-
pub fn compress_from_pda(
18-
accounts: &[AccountInfo],
17+
/// pda check missing yet.
18+
pub fn compress_from_pda<'a>(
19+
accounts: &'a [AccountInfo<'a>],
1920
instruction_data: &[u8],
2021
) -> Result<(), LightSdkError> {
2122
msg!("Compressing PDA back to compressed account");
@@ -27,104 +28,25 @@ pub fn compress_from_pda(
2728
// Get accounts
2829
let fee_payer = &accounts[0];
2930
let pda_account = &accounts[1];
30-
let rent_recipient = &accounts[2]; // Hardcoded by caller program
31-
let _system_program = &accounts[3];
31+
let rent_recipient = &accounts[2]; // can be hardcoded by caller program
3232

3333
// Verify the PDA account is owned by our program
3434
if pda_account.owner != &crate::ID {
3535
msg!("PDA account not owned by this program");
3636
return Err(LightSdkError::ConstraintViolation);
3737
}
3838

39-
// Read and deserialize PDA data
40-
let pda_data = pda_account.try_borrow_data()?;
41-
42-
// Check discriminator
43-
if &pda_data[..8] != b"decomppd" {
44-
msg!("Invalid PDA discriminator");
45-
return Err(LightSdkError::ConstraintViolation);
46-
}
47-
48-
let decompressed_pda = DecompressedPdaAccount::deserialize(&mut &pda_data[8..])
49-
.map_err(|_| LightSdkError::Borsh)?;
50-
51-
// Check if enough time has passed
52-
let clock = Clock::get().map_err(|_| LightSdkError::Borsh)?;
53-
let current_slot = clock.slot;
54-
let slots_elapsed = current_slot.saturating_sub(decompressed_pda.last_written_slot);
55-
56-
if slots_elapsed < decompressed_pda.slots_until_compression {
57-
msg!(
58-
"Cannot compress yet. {} slots remaining",
59-
decompressed_pda
60-
.slots_until_compression
61-
.saturating_sub(slots_elapsed)
62-
);
63-
return Err(LightSdkError::ConstraintViolation);
64-
}
65-
66-
// Derive PDA to verify it matches
67-
let (pda_pubkey, _pda_bump) = Pubkey::find_program_address(
68-
&[
69-
b"decompressed_pda",
70-
&decompressed_pda.compressed_address,
71-
&instruction_data.additional_seed,
72-
],
73-
&crate::ID,
74-
);
75-
76-
if pda_pubkey != *pda_account.key {
77-
msg!("PDA derivation mismatch");
78-
return Err(LightSdkError::ConstraintViolation);
79-
}
80-
81-
// Drop the borrow before we close the account
82-
drop(pda_data);
83-
84-
// Close the PDA account and send rent to recipient
85-
let pda_lamports = pda_account.lamports();
86-
**pda_account.try_borrow_mut_lamports()? = 0;
87-
**rent_recipient.try_borrow_mut_lamports()? = rent_recipient
88-
.lamports()
89-
.checked_add(pda_lamports)
90-
.ok_or(ProgramError::ArithmeticOverflow)?;
91-
92-
// Clear the PDA data
93-
pda_account.try_borrow_mut_data()?.fill(0);
94-
95-
// Now create the compressed account with the latest data
96-
let mut compressed_account = LightAccount::<'_, MyCompressedAccount>::new_init(
97-
&crate::ID,
98-
Some(decompressed_pda.compressed_address),
99-
instruction_data.output_merkle_tree_index,
100-
);
101-
102-
compressed_account.data = decompressed_pda.data;
103-
104-
// Set up CPI accounts for light system program
105-
let mut config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER);
106-
config.sol_pool_pda = true; // We're compressing SOL
107-
108-
let cpi_accounts = CpiAccounts::new_with_config(
39+
compress_pda::<MyCompressedAccount>(
40+
pda_account,
41+
&instruction_data.compressed_account_meta,
42+
Some(instruction_data.proof),
43+
accounts,
44+
instruction_data.system_accounts_offset,
10945
fee_payer,
110-
&accounts[instruction_data.system_accounts_offset as usize..],
111-
config,
112-
);
113-
114-
// Create CPI inputs
115-
let mut cpi_inputs = CpiInputs::new_with_address(
116-
instruction_data.proof,
117-
vec![compressed_account.to_account_info()?],
118-
vec![instruction_data.new_address_params],
119-
);
120-
121-
// Set compression parameters
122-
// We're compressing the lamports that were in the PDA
123-
cpi_inputs.compress_or_decompress_lamports = Some(instruction_data.lamports_to_compress);
124-
cpi_inputs.is_compress = true;
125-
126-
// Invoke light system program
127-
cpi_inputs.invoke_light_system_program(cpi_accounts)?;
46+
crate::LIGHT_CPI_SIGNER,
47+
&crate::ID,
48+
rent_recipient,
49+
)?;
12850

12951
msg!("Successfully compressed PDA back to compressed account");
13052
Ok(())
@@ -133,9 +55,6 @@ pub fn compress_from_pda(
13355
#[derive(Clone, Debug, Default, BorshDeserialize, BorshSerialize)]
13456
pub struct CompressFromPdaInstructionData {
13557
pub proof: ValidityProof,
136-
pub new_address_params: light_sdk::address::PackedNewAddressParams,
137-
pub output_merkle_tree_index: u8,
138-
pub additional_seed: [u8; 32], // Must match the seed used in decompression
139-
pub lamports_to_compress: u64,
58+
pub compressed_account_meta: CompressedAccountMeta,
14059
pub system_accounts_offset: u8,
14160
}

program-tests/sdk-test/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use solana_program::{
77
pub mod compress_from_pda;
88
pub mod create_pda;
99
pub mod decompress_to_pda;
10+
pub mod sdk;
1011
pub mod update_decompressed_pda;
1112
pub mod update_pda;
1213

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use borsh::{BorshDeserialize, BorshSerialize};
2+
use light_hasher::{DataHasher, Hasher};
3+
use light_sdk::{
4+
account::LightAccount,
5+
cpi::{CpiAccounts, CpiAccountsConfig, CpiInputs, CpiSigner},
6+
error::LightSdkError,
7+
instruction::{account_meta::CompressedAccountMeta, ValidityProof},
8+
LightDiscriminator,
9+
};
10+
use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
11+
12+
/// Helper function to compress a PDA and reclaim rent.
13+
///
14+
/// 1. closes onchain PDA
15+
/// 2. transfers PDA lamports to rent_recipient
16+
/// 3. updates the empty compressed PDA with onchain PDA data
17+
///
18+
/// This requires the compressed PDA that is tied to the onchain PDA to already
19+
/// exist.
20+
///
21+
/// # Arguments
22+
/// * `pda_account` - The PDA account to compress (will be closed)
23+
/// * `compressed_account_meta` - Metadata for the compressed account (must be
24+
/// empty but have an address)
25+
/// * `proof` - Optional validity proof
26+
/// * `cpi_accounts` - Accounts needed for CPI starting from
27+
/// system_accounts_offset
28+
/// * `system_accounts_offset` - Offset where CPI accounts start
29+
/// * `fee_payer` - The fee payer account
30+
/// * `cpi_signer` - The CPI signer for the calling program
31+
/// * `owner_program` - The program that will own the compressed account
32+
/// * `rent_recipient` - The account to receive the PDA's rent
33+
//
34+
// TODO:
35+
// - rent recipient check, eg hardcoded in caller program
36+
// - check if any explicit checks required for compressed account?
37+
// - check that the account is owned by the owner program, and derived from the correct seeds.
38+
// - consider adding check here that the cAccount belongs to Account via seeds.
39+
pub fn compress_pda<'a, A>(
40+
pda_account: &AccountInfo<'a>,
41+
compressed_account_meta: &CompressedAccountMeta,
42+
proof: Option<ValidityProof>,
43+
cpi_accounts: &'a [AccountInfo<'a>],
44+
system_accounts_offset: u8,
45+
fee_payer: &AccountInfo<'a>,
46+
cpi_signer: CpiSigner,
47+
owner_program: &Pubkey,
48+
rent_recipient: &AccountInfo<'a>,
49+
) -> Result<(), LightSdkError>
50+
where
51+
A: DataHasher + LightDiscriminator + BorshSerialize + BorshDeserialize + Default,
52+
{
53+
// Get the PDA lamports before we close it
54+
let pda_lamports = pda_account.lamports();
55+
56+
// Always use default/empty data since we're updating an existing compressed account
57+
let compressed_account =
58+
LightAccount::<'_, A>::new_mut(owner_program, compressed_account_meta, A::default())?;
59+
60+
// Set up CPI configuration
61+
let config = CpiAccountsConfig::new(cpi_signer);
62+
63+
// Create CPI accounts structure
64+
let cpi_accounts_struct = CpiAccounts::new_with_config(
65+
fee_payer,
66+
&cpi_accounts[system_accounts_offset as usize..],
67+
config,
68+
);
69+
70+
// Create CPI inputs
71+
let cpi_inputs = CpiInputs::new(
72+
proof.unwrap_or_default(),
73+
vec![compressed_account.to_account_info()?],
74+
);
75+
76+
// Invoke light system program to create the compressed account
77+
cpi_inputs.invoke_light_system_program(cpi_accounts_struct)?;
78+
79+
// Close the PDA account
80+
// 1. Transfer all lamports to the rent recipient
81+
let dest_starting_lamports = rent_recipient.lamports();
82+
**rent_recipient.try_borrow_mut_lamports()? = dest_starting_lamports
83+
.checked_add(pda_lamports)
84+
.ok_or(ProgramError::ArithmeticOverflow)?;
85+
// 2. Decrement source account lamports
86+
**pda_account.try_borrow_mut_lamports()? = 0;
87+
// 3. Clear all account data
88+
pda_account.try_borrow_mut_data()?.fill(0);
89+
// 4. Assign ownership back to the system program
90+
pda_account.assign(&solana_program::system_program::ID);
91+
92+
Ok(())
93+
}

program-tests/sdk-test/src/sdk/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod compress_pda;

0 commit comments

Comments
 (0)