Skip to content

Commit 88e043d

Browse files
compress_pda compiling
1 parent d2d0948 commit 88e043d

File tree

3 files changed

+135
-49
lines changed

3 files changed

+135
-49
lines changed
Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,58 @@
11
use borsh::{BorshDeserialize, BorshSerialize};
22
use light_sdk::{
3+
cpi::CpiAccounts,
34
error::LightSdkError,
45
instruction::{account_meta::CompressedAccountMeta, ValidityProof},
56
};
6-
use solana_program::{
7-
account_info::AccountInfo, clock::Clock, msg, pubkey::Pubkey, sysvar::Sysvar,
8-
};
7+
use light_sdk_types::CpiAccountsConfig;
8+
use solana_program::account_info::AccountInfo;
99

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

1512
/// Compresses a PDA back into a compressed account
1613
/// Anyone can call this after the timeout period has elapsed
1714
/// pda check missing yet.
18-
pub fn compress_from_pda<'a>(
19-
accounts: &'a [AccountInfo<'a>],
15+
pub fn compress_from_pda(
16+
accounts: &[AccountInfo],
2017
instruction_data: &[u8],
2118
) -> Result<(), LightSdkError> {
22-
msg!("Compressing PDA back to compressed account");
23-
2419
let mut instruction_data = instruction_data;
2520
let instruction_data = CompressFromPdaInstructionData::deserialize(&mut instruction_data)
2621
.map_err(|_| LightSdkError::Borsh)?;
2722

28-
// Get accounts
29-
let fee_payer = &accounts[0];
23+
// based on program...
24+
let custom_seeds: Vec<&[u8]> = vec![b"decompressed_pda"];
25+
3026
let pda_account = &accounts[1];
3127
let rent_recipient = &accounts[2]; // can be hardcoded by caller program
3228

33-
// Verify the PDA account is owned by our program
34-
if pda_account.owner != &crate::ID {
35-
msg!("PDA account not owned by this program");
36-
return Err(LightSdkError::ConstraintViolation);
37-
}
29+
// Cpi accounts
30+
let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER);
31+
let cpi_accounts_struct = CpiAccounts::new_with_config(
32+
&accounts[0],
33+
&accounts[instruction_data.system_accounts_offset as usize..],
34+
config,
35+
);
3836

39-
compress_pda::<MyCompressedAccount>(
37+
compress_pda::<DecompressedPdaAccount>(
4038
pda_account,
4139
&instruction_data.compressed_account_meta,
4240
Some(instruction_data.proof),
43-
accounts,
44-
instruction_data.system_accounts_offset,
45-
fee_payer,
46-
crate::LIGHT_CPI_SIGNER,
41+
cpi_accounts_struct,
4742
&crate::ID,
4843
rent_recipient,
44+
&custom_seeds,
4945
)?;
5046

51-
msg!("Successfully compressed PDA back to compressed account");
47+
// any other program logic here...
48+
5249
Ok(())
5350
}
5451

5552
#[derive(Clone, Debug, Default, BorshDeserialize, BorshSerialize)]
5653
pub struct CompressFromPdaInstructionData {
5754
pub proof: ValidityProof,
5855
pub compressed_account_meta: CompressedAccountMeta,
56+
pub additional_seed: [u8; 32], // Must match the seed used in decompression
5957
pub system_accounts_offset: u8,
6058
}

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use borsh::{BorshDeserialize, BorshSerialize};
2+
use light_hasher::{DataHasher, Hasher};
23
use light_sdk::{
34
account::LightAccount,
45
cpi::{CpiAccounts, CpiAccountsConfig, CpiInputs},
@@ -17,7 +18,7 @@ use solana_program::{
1718
pub const SLOTS_UNTIL_COMPRESSION: u64 = 100;
1819

1920
/// Account structure for the decompressed PDA
20-
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
21+
#[derive(Clone, Debug, Default, BorshDeserialize, BorshSerialize)]
2122
pub struct DecompressedPdaAccount {
2223
/// The compressed account address this PDA was derived from
2324
pub compressed_address: [u8; 32],
@@ -187,3 +188,28 @@ pub struct DecompressMyCompressedAccount {
187188
pub meta: CompressedAccountMeta,
188189
pub data: [u8; 31],
189190
}
191+
192+
// Implement required traits for DecompressedPdaAccount
193+
impl DataHasher for DecompressedPdaAccount {
194+
fn hash<H: Hasher>(&self) -> Result<[u8; 32], light_hasher::HasherError> {
195+
let mut bytes = vec![];
196+
self.serialize(&mut bytes).unwrap();
197+
H::hashv(&[&bytes])
198+
}
199+
}
200+
201+
impl LightDiscriminator for DecompressedPdaAccount {
202+
const LIGHT_DISCRIMINATOR: [u8; 8] = [0xDE, 0xC0, 0x11, 0x9D, 0xA0, 0x00, 0x00, 0x00];
203+
const LIGHT_DISCRIMINATOR_SLICE: &'static [u8] =
204+
&[0xDE, 0xC0, 0x11, 0x9D, 0xA0, 0x00, 0x00, 0x00];
205+
}
206+
207+
impl crate::sdk::compress_pda::PdaTimingData for DecompressedPdaAccount {
208+
fn last_touched_slot(&self) -> u64 {
209+
self.last_written_slot
210+
}
211+
212+
fn slots_buffer(&self) -> u64 {
213+
self.slots_until_compression
214+
}
215+
}

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

Lines changed: 86 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,51 @@ use light_sdk::{
77
instruction::{account_meta::CompressedAccountMeta, ValidityProof},
88
LightDiscriminator,
99
};
10-
use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
10+
use solana_program::sysvar::Sysvar;
11+
use solana_program::{
12+
account_info::AccountInfo, clock::Clock, msg, program_error::ProgramError, pubkey::Pubkey,
13+
};
14+
15+
/// Trait for PDA accounts that can be compressed
16+
pub trait PdaTimingData {
17+
fn last_touched_slot(&self) -> u64;
18+
fn slots_buffer(&self) -> u64;
19+
}
20+
21+
const DECOMP_SEED: &[u8] = b"decomp";
22+
23+
/// Check that the PDA account is owned by the caller program and derived from the correct seeds.
24+
///
25+
/// # Arguments
26+
/// * `custom_seeds` - Custom seeds to check against
27+
/// * `c_pda_address` - The address of the compressed PDA
28+
/// * `pda_account` - The address of the PDA account
29+
/// * `caller_program` - The program that owns the PDA.
30+
pub fn check_pda(
31+
custom_seeds: &[&[u8]],
32+
c_pda_address: &[u8; 32],
33+
pda_account: &Pubkey,
34+
caller_program: &Pubkey,
35+
) -> Result<(), ProgramError> {
36+
// Create seeds array: [custom_seeds..., c_pda_address, "decomp"]
37+
let mut seeds: Vec<&[u8]> = custom_seeds.to_vec();
38+
seeds.push(c_pda_address);
39+
seeds.push(DECOMP_SEED);
40+
41+
let derived_pda =
42+
Pubkey::create_program_address(&seeds, caller_program).expect("Invalid PDA seeds.");
43+
44+
if derived_pda != *pda_account {
45+
msg!(
46+
"Invalid PDA provided. Expected: {}. Found: {}.",
47+
derived_pda,
48+
pda_account
49+
);
50+
return Err(ProgramError::InvalidArgument);
51+
}
52+
53+
Ok(())
54+
}
1155

1256
/// Helper function to compress a PDA and reclaim rent.
1357
///
@@ -32,40 +76,58 @@ use solana_program::{account_info::AccountInfo, program_error::ProgramError, pub
3276
/// * `rent_recipient` - The account to receive the PDA's rent
3377
//
3478
// TODO:
35-
// - rent recipient check, eg hardcoded in caller program
3679
// - 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>,
80+
// - consider multiple accounts per ix.
81+
pub fn compress_pda<A>(
82+
pda_account: &AccountInfo,
4183
compressed_account_meta: &CompressedAccountMeta,
4284
proof: Option<ValidityProof>,
43-
cpi_accounts: &'a [AccountInfo<'a>],
44-
system_accounts_offset: u8,
45-
fee_payer: &AccountInfo<'a>,
46-
cpi_signer: CpiSigner,
85+
cpi_accounts: CpiAccounts,
4786
owner_program: &Pubkey,
48-
rent_recipient: &AccountInfo<'a>,
87+
rent_recipient: &AccountInfo,
88+
custom_seeds: &[&[u8]],
4989
) -> Result<(), LightSdkError>
5090
where
51-
A: DataHasher + LightDiscriminator + BorshSerialize + BorshDeserialize + Default,
91+
A: DataHasher
92+
+ LightDiscriminator
93+
+ BorshSerialize
94+
+ BorshDeserialize
95+
+ Default
96+
+ PdaTimingData,
5297
{
98+
// Check that the PDA account is owned by the caller program and derived from the address of the compressed PDA.
99+
check_pda(
100+
custom_seeds,
101+
&compressed_account_meta.address,
102+
pda_account.key,
103+
owner_program,
104+
)?;
105+
106+
let current_slot = Clock::get()?.slot;
107+
108+
// Deserialize the PDA data to check timing fields
109+
let pda_data = pda_account.try_borrow_data()?;
110+
let pda_account_data = A::try_from_slice(&pda_data[8..]).map_err(|_| LightSdkError::Borsh)?;
111+
drop(pda_data);
112+
113+
let last_touched_slot = pda_account_data.last_touched_slot();
114+
let slots_buffer = pda_account_data.slots_buffer();
115+
116+
if current_slot < last_touched_slot + slots_buffer {
117+
msg!(
118+
"Cannot compress yet. {} slots remaining",
119+
(last_touched_slot + slots_buffer).saturating_sub(current_slot)
120+
);
121+
return Err(LightSdkError::ConstraintViolation);
122+
}
123+
53124
// Get the PDA lamports before we close it
54125
let pda_lamports = pda_account.lamports();
55126

56-
// Always use default/empty data since we're updating an existing compressed account
57-
let compressed_account =
127+
let mut compressed_account =
58128
LightAccount::<'_, A>::new_mut(owner_program, compressed_account_meta, A::default())?;
59129

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-
);
130+
compressed_account.account = pda_account_data;
69131

70132
// Create CPI inputs
71133
let cpi_inputs = CpiInputs::new(
@@ -74,7 +136,7 @@ where
74136
);
75137

76138
// Invoke light system program to create the compressed account
77-
cpi_inputs.invoke_light_system_program(cpi_accounts_struct)?;
139+
cpi_inputs.invoke_light_system_program(cpi_accounts)?;
78140

79141
// Close the PDA account
80142
// 1. Transfer all lamports to the rent recipient

0 commit comments

Comments
 (0)