Skip to content

Commit 152b6e2

Browse files
decompress_idempotent.rs
1 parent 88e043d commit 152b6e2

File tree

7 files changed

+296
-264
lines changed

7 files changed

+296
-264
lines changed

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ use light_sdk::{
77
use light_sdk_types::CpiAccountsConfig;
88
use solana_program::account_info::AccountInfo;
99

10-
use crate::{decompress_to_pda::DecompressedPdaAccount, sdk::compress_pda::compress_pda};
10+
use crate::{decompress_to_pda::MyPdaAccount, sdk::compress_pda::compress_pda};
1111

1212
/// Compresses a PDA back into a compressed account
1313
/// Anyone can call this after the timeout period has elapsed
1414
/// pda check missing yet.
15+
// TODO: add macro that create the full instruction. and takes: programid, account and seeds, rent_recipient (to hardcode). low code solution.
1516
pub fn compress_from_pda(
1617
accounts: &[AccountInfo],
1718
instruction_data: &[u8],
@@ -20,7 +21,7 @@ pub fn compress_from_pda(
2021
let instruction_data = CompressFromPdaInstructionData::deserialize(&mut instruction_data)
2122
.map_err(|_| LightSdkError::Borsh)?;
2223

23-
// based on program...
24+
// Custom seeds for PDA derivation (must match decompress_idempotent)
2425
let custom_seeds: Vec<&[u8]> = vec![b"decompressed_pda"];
2526

2627
let pda_account = &accounts[1];
@@ -34,10 +35,10 @@ pub fn compress_from_pda(
3435
config,
3536
);
3637

37-
compress_pda::<DecompressedPdaAccount>(
38+
compress_pda::<MyPdaAccount>(
3839
pda_account,
3940
&instruction_data.compressed_account_meta,
40-
Some(instruction_data.proof),
41+
instruction_data.proof,
4142
cpi_accounts_struct,
4243
&crate::ID,
4344
rent_recipient,
@@ -53,6 +54,5 @@ pub fn compress_from_pda(
5354
pub struct CompressFromPdaInstructionData {
5455
pub proof: ValidityProof,
5556
pub compressed_account_meta: CompressedAccountMeta,
56-
pub additional_seed: [u8; 32], // Must match the seed used in decompression
5757
pub system_accounts_offset: u8,
5858
}
Lines changed: 47 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,21 @@
11
use borsh::{BorshDeserialize, BorshSerialize};
2-
use light_hasher::{DataHasher, Hasher};
32
use light_sdk::{
4-
account::LightAccount,
5-
cpi::{CpiAccounts, CpiAccountsConfig, CpiInputs},
3+
cpi::{CpiAccounts, CpiAccountsConfig},
64
error::LightSdkError,
7-
instruction::{
8-
account_meta::{CompressedAccountMeta, CompressedAccountMetaTrait},
9-
ValidityProof,
10-
},
5+
instruction::{account_meta::CompressedAccountMeta, ValidityProof},
116
LightDiscriminator, LightHasher,
127
};
13-
use solana_program::{
14-
account_info::AccountInfo, clock::Clock, msg, program::invoke_signed, pubkey::Pubkey,
15-
rent::Rent, system_instruction, sysvar::Sysvar,
16-
};
17-
18-
pub const SLOTS_UNTIL_COMPRESSION: u64 = 100;
8+
use solana_program::account_info::AccountInfo;
199

20-
/// Account structure for the decompressed PDA
21-
#[derive(Clone, Debug, Default, BorshDeserialize, BorshSerialize)]
22-
pub struct DecompressedPdaAccount {
23-
/// The compressed account address this PDA was derived from
24-
pub compressed_address: [u8; 32],
25-
/// Slot when this account was last written
26-
pub last_written_slot: u64,
27-
/// Number of slots until this account can be compressed again
28-
pub slots_until_compression: u64,
29-
/// The actual account data
30-
pub data: [u8; 31],
31-
/// Flag to indicate if this is a decompressed account
32-
pub is_decompressed: bool,
33-
}
10+
use crate::sdk::decompress_idempotent::decompress_idempotent;
3411

35-
/// Compressed account structure with decompression flag
36-
#[derive(
37-
Clone, Debug, Default, LightHasher, LightDiscriminator, BorshDeserialize, BorshSerialize,
38-
)]
39-
pub struct DecompressedMarkerAccount {
40-
/// Flag to indicate this account has been decompressed
41-
pub is_decompressed: bool,
42-
}
12+
pub const SLOTS_UNTIL_COMPRESSION: u64 = 100;
4313

44-
/// Decompresses a compressed account into a PDA
45-
/// The PDA is derived from the compressed account's address and other seeds
14+
/// Decompresses a compressed account into a PDA idempotently.
4615
pub fn decompress_to_pda(
4716
accounts: &[AccountInfo],
4817
instruction_data: &[u8],
4918
) -> Result<(), LightSdkError> {
50-
msg!("Decompressing compressed account to PDA");
51-
5219
let mut instruction_data = instruction_data;
5320
let instruction_data = DecompressToPdaInstructionData::deserialize(&mut instruction_data)
5421
.map_err(|_| LightSdkError::Borsh)?;
@@ -59,157 +26,75 @@ pub fn decompress_to_pda(
5926
let rent_payer = &accounts[2]; // Account that pays for PDA rent
6027
let system_program = &accounts[3];
6128

62-
// Derive PDA from compressed address
63-
let compressed_address = instruction_data.compressed_account.meta.address;
64-
let (pda_pubkey, pda_bump) = Pubkey::find_program_address(
65-
&[
66-
b"decompressed_pda",
67-
&compressed_address,
68-
&instruction_data.additional_seed,
69-
],
70-
&crate::ID,
71-
);
72-
73-
// Verify PDA matches
74-
if pda_pubkey != *pda_account.key {
75-
msg!("Invalid PDA pubkey");
76-
return Err(LightSdkError::ConstraintViolation);
77-
}
78-
79-
// Get current slot
80-
let clock = Clock::get().map_err(|_| LightSdkError::Borsh)?;
81-
let current_slot = clock.slot;
82-
83-
// Calculate space needed for PDA
84-
let space = std::mem::size_of::<DecompressedPdaAccount>() + 8; // +8 for discriminator
85-
86-
// Get minimum rent
87-
let rent = Rent::get().map_err(|_| LightSdkError::Borsh)?;
88-
let minimum_balance = rent.minimum_balance(space);
89-
90-
// Create PDA account (rent payer pays for the PDA creation)
91-
let create_account_ix = system_instruction::create_account(
92-
rent_payer.key,
93-
pda_account.key,
94-
minimum_balance,
95-
space as u64,
96-
&crate::ID,
97-
);
98-
99-
let signer_seeds = &[
100-
b"decompressed_pda".as_ref(),
101-
compressed_address.as_ref(),
102-
instruction_data.additional_seed.as_ref(),
103-
&[pda_bump],
104-
];
105-
106-
invoke_signed(
107-
&create_account_ix,
108-
&[
109-
rent_payer.clone(),
110-
pda_account.clone(),
111-
system_program.clone(),
112-
],
113-
&[signer_seeds],
114-
)?;
115-
116-
// Initialize PDA with decompressed data
117-
let decompressed_pda = DecompressedPdaAccount {
118-
compressed_address,
119-
last_written_slot: current_slot,
120-
slots_until_compression: SLOTS_UNTIL_COMPRESSION,
121-
data: instruction_data.compressed_account.data,
122-
is_decompressed: true,
123-
};
124-
125-
// Write data to PDA
126-
decompressed_pda
127-
.serialize(&mut &mut pda_account.try_borrow_mut_data()?[8..])
128-
.map_err(|_| LightSdkError::Borsh)?;
129-
130-
// Write discriminator
131-
pda_account.try_borrow_mut_data()?[..8].copy_from_slice(b"decomppd");
132-
133-
// Now handle the compressed account side
134-
// Create a marker account that indicates this compressed account has been decompressed
135-
let marker_account = LightAccount::<'_, DecompressedMarkerAccount>::new_mut(
136-
&crate::ID,
137-
&instruction_data.compressed_account.meta,
138-
DecompressedMarkerAccount {
139-
is_decompressed: true,
140-
},
141-
)?;
142-
143-
// Set up CPI accounts for light system program
144-
let mut config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER);
145-
config.sol_pool_pda = false;
146-
config.sol_compression_recipient = true; // We need to decompress SOL to the PDA
147-
29+
// Cpi accounts
14830
let cpi_accounts = CpiAccounts::new_with_config(
14931
fee_payer,
15032
&accounts[instruction_data.system_accounts_offset as usize..],
151-
config,
33+
CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER),
15234
);
15335

154-
// Create CPI inputs with decompression
155-
let mut cpi_inputs = CpiInputs::new(
156-
instruction_data.proof,
157-
vec![marker_account.to_account_info()?],
158-
);
36+
// Custom seeds for PDA derivation
37+
let custom_seeds: Vec<&[u8]> = vec![b"decompressed_pda"];
15938

160-
// Set decompression parameters
161-
// Transfer all lamports from compressed account to the PDA
162-
let lamports_to_decompress = instruction_data
163-
.compressed_account
164-
.meta
165-
.get_lamports()
166-
.unwrap_or(0);
167-
168-
cpi_inputs.compress_or_decompress_lamports = Some(lamports_to_decompress);
169-
cpi_inputs.is_compress = false; // This is decompression
39+
// Call the SDK function to decompress idempotently
40+
// this inits pda_account if not already initialized
41+
decompress_idempotent::<MyPdaAccount>(
42+
pda_account,
43+
Some(&instruction_data.compressed_account.meta),
44+
&instruction_data.compressed_account.data,
45+
instruction_data.proof,
46+
cpi_accounts,
47+
&crate::ID,
48+
rent_payer,
49+
system_program,
50+
&custom_seeds,
51+
&instruction_data.additional_seed,
52+
)?;
17053

171-
// Invoke light system program
172-
cpi_inputs.invoke_light_system_program(cpi_accounts)?;
54+
// do something with pda_account...
17355

174-
msg!("Successfully decompressed account to PDA");
17556
Ok(())
17657
}
17758

17859
#[derive(Clone, Debug, Default, BorshDeserialize, BorshSerialize)]
17960
pub struct DecompressToPdaInstructionData {
18061
pub proof: ValidityProof,
181-
pub compressed_account: DecompressMyCompressedAccount,
182-
pub additional_seed: [u8; 32], // Additional seed for PDA derivation
62+
pub compressed_account: MyCompressedAccount,
63+
pub additional_seed: [u8; 32], // ... some seed
18364
pub system_accounts_offset: u8,
18465
}
18566

67+
// just a wrapper
18668
#[derive(Clone, Debug, Default, BorshDeserialize, BorshSerialize)]
187-
pub struct DecompressMyCompressedAccount {
69+
pub struct MyCompressedAccount {
18870
pub meta: CompressedAccountMeta,
189-
pub data: [u8; 31],
71+
pub data: MyPdaAccount,
19072
}
19173

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];
74+
/// Account structure for the PDA
75+
#[derive(
76+
Clone, Debug, LightHasher, LightDiscriminator, Default, BorshDeserialize, BorshSerialize,
77+
)]
78+
pub struct MyPdaAccount {
79+
/// Slot when this account was last written
80+
pub last_written_slot: u64,
81+
/// Number of slots after last_written_slot until this account can be compressed again
82+
pub slots_until_compression: u64,
83+
/// The actual account data
84+
pub data: [u8; 31],
20585
}
20686

207-
impl crate::sdk::compress_pda::PdaTimingData for DecompressedPdaAccount {
87+
// We require this trait to be implemented for the custom PDA account.
88+
impl crate::sdk::compress_pda::PdaTimingData for MyPdaAccount {
20889
fn last_touched_slot(&self) -> u64 {
20990
self.last_written_slot
21091
}
21192

21293
fn slots_buffer(&self) -> u64 {
21394
self.slots_until_compression
21495
}
96+
97+
fn set_last_written_slot(&mut self, slot: u64) {
98+
self.last_written_slot = slot;
99+
}
215100
}

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ pub mod compress_from_pda;
88
pub mod create_pda;
99
pub mod decompress_to_pda;
1010
pub mod sdk;
11-
pub mod update_decompressed_pda;
11+
1212
pub mod update_pda;
1313

1414
pub const ID: Pubkey = pubkey!("FNt7byTHev1k5x2cXZLBr8TdWiC3zoP5vcnZR4P682Uy");
@@ -23,7 +23,6 @@ pub enum InstructionType {
2323
UpdatePdaBorsh = 1,
2424
DecompressToPda = 2,
2525
CompressFromPda = 3,
26-
UpdateDecompressedPda = 4,
2726
}
2827

2928
impl TryFrom<u8> for InstructionType {
@@ -35,7 +34,6 @@ impl TryFrom<u8> for InstructionType {
3534
1 => Ok(InstructionType::UpdatePdaBorsh),
3635
2 => Ok(InstructionType::DecompressToPda),
3736
3 => Ok(InstructionType::CompressFromPda),
38-
4 => Ok(InstructionType::UpdateDecompressedPda),
3937
_ => panic!("Invalid instruction discriminator."),
4038
}
4139
}
@@ -60,9 +58,6 @@ pub fn process_instruction(
6058
InstructionType::CompressFromPda => {
6159
compress_from_pda::compress_from_pda(accounts, &instruction_data[1..])
6260
}
63-
InstructionType::UpdateDecompressedPda => {
64-
update_decompressed_pda::update_decompressed_pda(accounts, &instruction_data[1..])
65-
}
6661
}?;
6762
Ok(())
6863
}

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use borsh::{BorshDeserialize, BorshSerialize};
2-
use light_hasher::{DataHasher, Hasher};
2+
use light_hasher::DataHasher;
33
use light_sdk::{
44
account::LightAccount,
5-
cpi::{CpiAccounts, CpiAccountsConfig, CpiInputs, CpiSigner},
5+
cpi::{CpiAccounts, CpiInputs},
66
error::LightSdkError,
77
instruction::{account_meta::CompressedAccountMeta, ValidityProof},
88
LightDiscriminator,
@@ -16,6 +16,7 @@ use solana_program::{
1616
pub trait PdaTimingData {
1717
fn last_touched_slot(&self) -> u64;
1818
fn slots_buffer(&self) -> u64;
19+
fn set_last_written_slot(&mut self, slot: u64);
1920
}
2021

2122
const DECOMP_SEED: &[u8] = b"decomp";
@@ -81,7 +82,7 @@ pub fn check_pda(
8182
pub fn compress_pda<A>(
8283
pda_account: &AccountInfo,
8384
compressed_account_meta: &CompressedAccountMeta,
84-
proof: Option<ValidityProof>,
85+
proof: ValidityProof,
8586
cpi_accounts: CpiAccounts,
8687
owner_program: &Pubkey,
8788
rent_recipient: &AccountInfo,
@@ -130,10 +131,7 @@ where
130131
compressed_account.account = pda_account_data;
131132

132133
// Create CPI inputs
133-
let cpi_inputs = CpiInputs::new(
134-
proof.unwrap_or_default(),
135-
vec![compressed_account.to_account_info()?],
136-
);
134+
let cpi_inputs = CpiInputs::new(proof, vec![compressed_account.to_account_info()?]);
137135

138136
// Invoke light system program to create the compressed account
139137
cpi_inputs.invoke_light_system_program(cpi_accounts)?;

0 commit comments

Comments
 (0)