1use aligned_sized::aligned_sized;
2use bytemuck::{Pod, Zeroable};
3use light_program_profiler::profile;
4use light_zero_copy::{ZeroCopy, ZeroCopyMut};
5use zerocopy::U64;
6
7use crate::{
8 config::CompressibleConfig,
9 error::CompressibleError,
10 rent::{get_last_funded_epoch, AccountRentState, RentConfig, SLOTS_PER_EPOCH},
11 AnchorDeserialize, AnchorSerialize,
12};
13
14#[derive(
16 Debug,
17 Clone,
18 Hash,
19 Copy,
20 PartialEq,
21 Eq,
22 Default,
23 AnchorSerialize,
24 AnchorDeserialize,
25 ZeroCopy,
26 ZeroCopyMut,
27 Pod,
28 Zeroable,
29)]
30#[repr(C)]
31#[aligned_sized]
32pub struct CompressionInfo {
33 pub config_account_version: u16, pub compress_to_pubkey: u8,
36 pub account_version: u8,
39 pub lamports_per_write: u32,
42 pub compression_authority: [u8; 32],
44 pub rent_sponsor: [u8; 32],
46 pub last_claimed_slot: u64,
48 pub rent_exemption_paid: u32,
52 pub _reserved: u32,
54 pub rent_config: RentConfig,
57}
58
59macro_rules! impl_is_compressible {
61 ($struct_name:ty) => {
62 impl $struct_name {
63 #[profile]
69 pub fn is_compressible(
70 &self,
71 bytes: u64,
72 current_slot: u64,
73 current_lamports: u64,
74 ) -> Result<Option<u64>, CompressibleError> {
75 let rent_exemption_paid: u32 = self.rent_exemption_paid.into();
76 let rent_exemption_lamports: u64 = rent_exemption_paid as u64;
77 Ok(crate::rent::AccountRentState {
78 num_bytes: bytes,
79 current_slot,
80 current_lamports,
81 last_claimed_slot: self.last_claimed_slot.into(),
82 }
83 .is_compressible(&self.rent_config, rent_exemption_lamports))
84 }
85
86 pub fn compress_to_pubkey(&self) -> bool {
88 self.compress_to_pubkey != 0
89 }
90
91 #[profile]
96 pub fn calculate_top_up_lamports(
97 &self,
98 num_bytes: u64,
99 current_slot: u64,
100 current_lamports: u64,
101 ) -> Result<u64, CompressibleError> {
102 let lamports_per_write: u32 = self.lamports_per_write.into();
103 let rent_exemption_paid: u32 = self.rent_exemption_paid.into();
104 let rent_exemption_lamports: u64 = rent_exemption_paid as u64;
105
106 let state = crate::rent::AccountRentState {
108 num_bytes,
109 current_slot,
110 current_lamports,
111 last_claimed_slot: self.last_claimed_slot.into(),
112 };
113 let is_compressible =
114 state.is_compressible(&self.rent_config, rent_exemption_lamports);
115 if let Some(rent_deficit) = is_compressible {
116 Ok(lamports_per_write as u64 + rent_deficit)
117 } else {
118 let epochs_funded_ahead =
119 state.epochs_funded_ahead(&self.rent_config, rent_exemption_lamports);
120
121 if epochs_funded_ahead >= self.rent_config.max_funded_epochs as u64 {
123 Ok(0)
124 } else {
125 Ok(lamports_per_write as u64)
126 }
127 }
128 }
129 }
130 };
131}
132impl_is_compressible!(CompressionInfo);
133impl_is_compressible!(ZCompressionInfo<'_>);
134impl_is_compressible!(ZCompressionInfoMut<'_>);
135
136macro_rules! impl_get_last_paid_epoch {
138 ($struct_name:ty) => {
139 impl $struct_name {
140 pub fn get_last_funded_epoch(
143 &self,
144 num_bytes: u64,
145 current_lamports: u64,
146 rent_exemption_lamports: u64,
147 ) -> Result<u64, CompressibleError> {
148 Ok(get_last_funded_epoch(
149 num_bytes,
150 current_lamports,
151 self.last_claimed_slot,
152 &self.rent_config,
153 rent_exemption_lamports,
154 ))
155 }
156 }
157 };
158}
159
160impl_get_last_paid_epoch!(CompressionInfo);
161impl_get_last_paid_epoch!(ZCompressionInfo<'_>);
162impl_get_last_paid_epoch!(ZCompressionInfoMut<'_>);
163
164pub struct ClaimAndUpdate<'a> {
165 pub compression_authority: &'a [u8; 32],
166 pub rent_sponsor: &'a [u8; 32],
167 pub config_account: &'a CompressibleConfig,
168 pub bytes: u64,
169 pub current_slot: u64,
170 pub current_lamports: u64,
171}
172
173impl ZCompressionInfoMut<'_> {
174 pub fn claim(
177 &mut self,
178 num_bytes: u64,
179 current_slot: u64,
180 current_lamports: u64,
181 ) -> Result<Option<u64>, CompressibleError> {
182 let rent_exemption_paid: u32 = self.rent_exemption_paid.into();
183 let rent_exemption_lamports: u64 = rent_exemption_paid as u64;
184 let state = AccountRentState {
185 num_bytes,
186 current_slot,
187 current_lamports,
188 last_claimed_slot: self.last_claimed_slot.into(),
189 };
190 let claimed = state.calculate_claimable_rent(&self.rent_config, rent_exemption_lamports);
191
192 if let Some(claimed_amount) = claimed {
193 if claimed_amount > 0 {
194 let completed_epochs = state.get_completed_epochs();
195
196 self.last_claimed_slot += U64::from(completed_epochs * SLOTS_PER_EPOCH);
197 Ok(Some(claimed_amount))
198 } else {
199 Ok(None)
200 }
201 } else {
202 Ok(None)
203 }
204 }
205
206 pub fn claim_and_update(
207 &mut self,
208 ClaimAndUpdate {
209 compression_authority,
210 rent_sponsor,
211 config_account,
212 bytes,
213 current_slot,
214 current_lamports,
215 }: ClaimAndUpdate,
216 ) -> Result<Option<u64>, CompressibleError> {
217 if self.compression_authority != *compression_authority {
218 #[cfg(feature = "solana")]
219 solana_msg::msg!("Rent authority mismatch");
220 return Ok(None);
221 }
222 if self.rent_sponsor != *rent_sponsor {
223 #[cfg(feature = "solana")]
224 solana_msg::msg!("Rent sponsor PDA does not match rent recipient");
225 return Ok(None);
226 }
227
228 let account_version: u16 = self.config_account_version.into();
230 let config_version = config_account.version;
231
232 if account_version != config_version {
233 #[cfg(feature = "solana")]
234 solana_msg::msg!(
235 "Config version mismatch: account has v{}, config is v{}",
236 account_version,
237 config_version
238 );
239 return Err(CompressibleError::InvalidVersion);
240 }
241
242 let claim_result = self.claim(bytes, current_slot, current_lamports)?;
243
244 self.rent_config.set(&config_account.rent_config);
246
247 Ok(claim_result)
248 }
249}