@@ -7,7 +7,51 @@ use light_sdk::{
7
7
instruction:: { account_meta:: CompressedAccountMeta , ValidityProof } ,
8
8
LightDiscriminator ,
9
9
} ;
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
+ }
11
55
12
56
/// Helper function to compress a PDA and reclaim rent.
13
57
///
@@ -32,40 +76,58 @@ use solana_program::{account_info::AccountInfo, program_error::ProgramError, pub
32
76
/// * `rent_recipient` - The account to receive the PDA's rent
33
77
//
34
78
// TODO:
35
- // - rent recipient check, eg hardcoded in caller program
36
79
// - 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 ,
41
83
compressed_account_meta : & CompressedAccountMeta ,
42
84
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 ,
47
86
owner_program : & Pubkey ,
48
- rent_recipient : & AccountInfo < ' a > ,
87
+ rent_recipient : & AccountInfo ,
88
+ custom_seeds : & [ & [ u8 ] ] ,
49
89
) -> Result < ( ) , LightSdkError >
50
90
where
51
- A : DataHasher + LightDiscriminator + BorshSerialize + BorshDeserialize + Default ,
91
+ A : DataHasher
92
+ + LightDiscriminator
93
+ + BorshSerialize
94
+ + BorshDeserialize
95
+ + Default
96
+ + PdaTimingData ,
52
97
{
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
+
53
124
// Get the PDA lamports before we close it
54
125
let pda_lamports = pda_account. lamports ( ) ;
55
126
56
- // Always use default/empty data since we're updating an existing compressed account
57
- let compressed_account =
127
+ let mut compressed_account =
58
128
LightAccount :: < ' _ , A > :: new_mut ( owner_program, compressed_account_meta, A :: default ( ) ) ?;
59
129
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;
69
131
70
132
// Create CPI inputs
71
133
let cpi_inputs = CpiInputs :: new (
74
136
) ;
75
137
76
138
// 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 ) ?;
78
140
79
141
// Close the PDA account
80
142
// 1. Transfer all lamports to the rent recipient
0 commit comments