clone_solana_address_lookup_table_program/
processor.rs1use {
2 clone_solana_address_lookup_table_interface::{
3 instruction::ProgramInstruction,
4 program::{check_id, id},
5 state::{
6 AddressLookupTable, LookupTableMeta, LookupTableStatus, ProgramState,
7 LOOKUP_TABLE_MAX_ADDRESSES, LOOKUP_TABLE_META_SIZE,
8 },
9 },
10 clone_solana_bincode::limited_deserialize,
11 clone_solana_clock::Slot,
12 clone_solana_instruction::error::InstructionError,
13 clone_solana_log_collector::ic_msg,
14 clone_solana_program_runtime::{declare_process_instruction, invoke_context::InvokeContext},
15 clone_solana_pubkey::{Pubkey, PUBKEY_BYTES},
16 clone_solana_system_interface::instruction as system_instruction,
17 std::convert::TryFrom,
18};
19
20pub const DEFAULT_COMPUTE_UNITS: u64 = 750;
21
22declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| {
23 let transaction_context = &invoke_context.transaction_context;
24 let instruction_context = transaction_context.get_current_instruction_context()?;
25 let instruction_data = instruction_context.get_instruction_data();
26 match limited_deserialize(
27 instruction_data,
28 clone_solana_packet::PACKET_DATA_SIZE as u64,
29 )? {
30 ProgramInstruction::CreateLookupTable {
31 recent_slot,
32 bump_seed,
33 } => Processor::create_lookup_table(invoke_context, recent_slot, bump_seed),
34 ProgramInstruction::FreezeLookupTable => Processor::freeze_lookup_table(invoke_context),
35 ProgramInstruction::ExtendLookupTable { new_addresses } => {
36 Processor::extend_lookup_table(invoke_context, new_addresses)
37 }
38 ProgramInstruction::DeactivateLookupTable => {
39 Processor::deactivate_lookup_table(invoke_context)
40 }
41 ProgramInstruction::CloseLookupTable => Processor::close_lookup_table(invoke_context),
42 }
43});
44
45fn checked_add(a: usize, b: usize) -> Result<usize, InstructionError> {
46 a.checked_add(b).ok_or(InstructionError::ArithmeticOverflow)
47}
48
49pub struct Processor;
50impl Processor {
51 fn create_lookup_table(
52 invoke_context: &mut InvokeContext,
53 untrusted_recent_slot: Slot,
54 bump_seed: u8,
55 ) -> Result<(), InstructionError> {
56 let transaction_context = &invoke_context.transaction_context;
57 let instruction_context = transaction_context.get_current_instruction_context()?;
58
59 let lookup_table_account =
60 instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
61 let lookup_table_lamports = lookup_table_account.get_lamports();
62 let table_key = *lookup_table_account.get_key();
63 let lookup_table_owner = *lookup_table_account.get_owner();
64 drop(lookup_table_account);
65
66 let authority_account =
67 instruction_context.try_borrow_instruction_account(transaction_context, 1)?;
68 let authority_key = *authority_account.get_key();
69 drop(authority_account);
70
71 let payer_account =
72 instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
73 let payer_key = *payer_account.get_key();
74 if !payer_account.is_signer() {
75 ic_msg!(invoke_context, "Payer account must be a signer");
76 return Err(InstructionError::MissingRequiredSignature);
77 }
78 drop(payer_account);
79
80 let derivation_slot = {
81 let slot_hashes = invoke_context.get_sysvar_cache().get_slot_hashes()?;
82 if slot_hashes.get(&untrusted_recent_slot).is_some() {
83 Ok(untrusted_recent_slot)
84 } else {
85 ic_msg!(
86 invoke_context,
87 "{} is not a recent slot",
88 untrusted_recent_slot
89 );
90 Err(InstructionError::InvalidInstructionData)
91 }
92 }?;
93
94 let derived_table_key = Pubkey::create_program_address(
97 &[
98 authority_key.as_ref(),
99 &derivation_slot.to_le_bytes(),
100 &[bump_seed],
101 ],
102 &id(),
103 )?;
104
105 if table_key != derived_table_key {
106 ic_msg!(
107 invoke_context,
108 "Table address must match derived address: {}",
109 derived_table_key
110 );
111 return Err(InstructionError::InvalidArgument);
112 }
113
114 if check_id(&lookup_table_owner) {
115 return Ok(());
116 }
117
118 let table_account_data_len = LOOKUP_TABLE_META_SIZE;
119 let rent = invoke_context.get_sysvar_cache().get_rent()?;
120 let required_lamports = rent
121 .minimum_balance(table_account_data_len)
122 .max(1)
123 .saturating_sub(lookup_table_lamports);
124
125 if required_lamports > 0 {
126 invoke_context.native_invoke(
127 system_instruction::transfer(&payer_key, &table_key, required_lamports).into(),
128 &[payer_key],
129 )?;
130 }
131
132 invoke_context.native_invoke(
133 system_instruction::allocate(&table_key, table_account_data_len as u64).into(),
134 &[table_key],
135 )?;
136
137 invoke_context.native_invoke(
138 system_instruction::assign(&table_key, &id()).into(),
139 &[table_key],
140 )?;
141
142 let transaction_context = &invoke_context.transaction_context;
143 let instruction_context = transaction_context.get_current_instruction_context()?;
144 let mut lookup_table_account =
145 instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
146 lookup_table_account.set_state(&ProgramState::LookupTable(LookupTableMeta::new(
147 authority_key,
148 )))?;
149
150 Ok(())
151 }
152
153 fn freeze_lookup_table(invoke_context: &mut InvokeContext) -> Result<(), InstructionError> {
154 let transaction_context = &invoke_context.transaction_context;
155 let instruction_context = transaction_context.get_current_instruction_context()?;
156
157 let lookup_table_account =
158 instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
159 if *lookup_table_account.get_owner() != id() {
160 return Err(InstructionError::InvalidAccountOwner);
161 }
162 drop(lookup_table_account);
163
164 let authority_account =
165 instruction_context.try_borrow_instruction_account(transaction_context, 1)?;
166 let authority_key = *authority_account.get_key();
167 if !authority_account.is_signer() {
168 ic_msg!(invoke_context, "Authority account must be a signer");
169 return Err(InstructionError::MissingRequiredSignature);
170 }
171 drop(authority_account);
172
173 let mut lookup_table_account =
174 instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
175 let lookup_table_data = lookup_table_account.get_data();
176 let lookup_table = AddressLookupTable::deserialize(lookup_table_data)?;
177
178 if lookup_table.meta.authority.is_none() {
179 ic_msg!(invoke_context, "Lookup table is already frozen");
180 return Err(InstructionError::Immutable);
181 }
182 if lookup_table.meta.authority != Some(authority_key) {
183 return Err(InstructionError::IncorrectAuthority);
184 }
185 if lookup_table.meta.deactivation_slot != Slot::MAX {
186 ic_msg!(invoke_context, "Deactivated tables cannot be frozen");
187 return Err(InstructionError::InvalidArgument);
188 }
189 if lookup_table.addresses.is_empty() {
190 ic_msg!(invoke_context, "Empty lookup tables cannot be frozen");
191 return Err(InstructionError::InvalidInstructionData);
192 }
193
194 let mut lookup_table_meta = lookup_table.meta;
195 lookup_table_meta.authority = None;
196 AddressLookupTable::overwrite_meta_data(
197 lookup_table_account.get_data_mut()?,
198 lookup_table_meta,
199 )?;
200
201 Ok(())
202 }
203
204 fn extend_lookup_table(
205 invoke_context: &mut InvokeContext,
206 new_addresses: Vec<Pubkey>,
207 ) -> Result<(), InstructionError> {
208 let transaction_context = &invoke_context.transaction_context;
209 let instruction_context = transaction_context.get_current_instruction_context()?;
210
211 let lookup_table_account =
212 instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
213 let table_key = *lookup_table_account.get_key();
214 if *lookup_table_account.get_owner() != id() {
215 return Err(InstructionError::InvalidAccountOwner);
216 }
217 drop(lookup_table_account);
218
219 let authority_account =
220 instruction_context.try_borrow_instruction_account(transaction_context, 1)?;
221 let authority_key = *authority_account.get_key();
222 if !authority_account.is_signer() {
223 ic_msg!(invoke_context, "Authority account must be a signer");
224 return Err(InstructionError::MissingRequiredSignature);
225 }
226 drop(authority_account);
227
228 let mut lookup_table_account =
229 instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
230 let lookup_table_data = lookup_table_account.get_data();
231 let lookup_table_lamports = lookup_table_account.get_lamports();
232 let mut lookup_table = AddressLookupTable::deserialize(lookup_table_data)?;
233
234 if lookup_table.meta.authority.is_none() {
235 return Err(InstructionError::Immutable);
236 }
237 if lookup_table.meta.authority != Some(authority_key) {
238 return Err(InstructionError::IncorrectAuthority);
239 }
240 if lookup_table.meta.deactivation_slot != Slot::MAX {
241 ic_msg!(invoke_context, "Deactivated tables cannot be extended");
242 return Err(InstructionError::InvalidArgument);
243 }
244 if lookup_table.addresses.len() >= LOOKUP_TABLE_MAX_ADDRESSES {
245 ic_msg!(
246 invoke_context,
247 "Lookup table is full and cannot contain more addresses"
248 );
249 return Err(InstructionError::InvalidArgument);
250 }
251
252 if new_addresses.is_empty() {
253 ic_msg!(invoke_context, "Must extend with at least one address");
254 return Err(InstructionError::InvalidInstructionData);
255 }
256
257 let new_table_addresses_len = lookup_table
258 .addresses
259 .len()
260 .saturating_add(new_addresses.len());
261 if new_table_addresses_len > LOOKUP_TABLE_MAX_ADDRESSES {
262 ic_msg!(
263 invoke_context,
264 "Extended lookup table length {} would exceed max capacity of {}",
265 new_table_addresses_len,
266 LOOKUP_TABLE_MAX_ADDRESSES
267 );
268 return Err(InstructionError::InvalidInstructionData);
269 }
270
271 let clock = invoke_context.get_sysvar_cache().get_clock()?;
272 if clock.slot != lookup_table.meta.last_extended_slot {
273 lookup_table.meta.last_extended_slot = clock.slot;
274 lookup_table.meta.last_extended_slot_start_index =
275 u8::try_from(lookup_table.addresses.len()).map_err(|_| {
276 InstructionError::InvalidAccountData
279 })?;
280 }
281
282 let lookup_table_meta = lookup_table.meta;
283 let new_table_data_len = checked_add(
284 LOOKUP_TABLE_META_SIZE,
285 new_table_addresses_len.saturating_mul(PUBKEY_BYTES),
286 )?;
287 {
288 AddressLookupTable::overwrite_meta_data(
289 lookup_table_account.get_data_mut()?,
290 lookup_table_meta,
291 )?;
292 for new_address in new_addresses {
293 lookup_table_account.extend_from_slice(new_address.as_ref())?;
294 }
295 }
296 drop(lookup_table_account);
297
298 let rent = invoke_context.get_sysvar_cache().get_rent()?;
299 let required_lamports = rent
300 .minimum_balance(new_table_data_len)
301 .max(1)
302 .saturating_sub(lookup_table_lamports);
303
304 if required_lamports > 0 {
305 let payer_account =
306 instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
307 let payer_key = *payer_account.get_key();
308 if !payer_account.is_signer() {
309 ic_msg!(invoke_context, "Payer account must be a signer");
310 return Err(InstructionError::MissingRequiredSignature);
311 }
312 drop(payer_account);
313
314 invoke_context.native_invoke(
315 system_instruction::transfer(&payer_key, &table_key, required_lamports).into(),
316 &[payer_key],
317 )?;
318 }
319
320 Ok(())
321 }
322
323 fn deactivate_lookup_table(invoke_context: &mut InvokeContext) -> Result<(), InstructionError> {
324 let transaction_context = &invoke_context.transaction_context;
325 let instruction_context = transaction_context.get_current_instruction_context()?;
326
327 let lookup_table_account =
328 instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
329 if *lookup_table_account.get_owner() != id() {
330 return Err(InstructionError::InvalidAccountOwner);
331 }
332 drop(lookup_table_account);
333
334 let authority_account =
335 instruction_context.try_borrow_instruction_account(transaction_context, 1)?;
336 let authority_key = *authority_account.get_key();
337 if !authority_account.is_signer() {
338 ic_msg!(invoke_context, "Authority account must be a signer");
339 return Err(InstructionError::MissingRequiredSignature);
340 }
341 drop(authority_account);
342
343 let mut lookup_table_account =
344 instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
345 let lookup_table_data = lookup_table_account.get_data();
346 let lookup_table = AddressLookupTable::deserialize(lookup_table_data)?;
347
348 if lookup_table.meta.authority.is_none() {
349 ic_msg!(invoke_context, "Lookup table is frozen");
350 return Err(InstructionError::Immutable);
351 }
352 if lookup_table.meta.authority != Some(authority_key) {
353 return Err(InstructionError::IncorrectAuthority);
354 }
355 if lookup_table.meta.deactivation_slot != Slot::MAX {
356 ic_msg!(invoke_context, "Lookup table is already deactivated");
357 return Err(InstructionError::InvalidArgument);
358 }
359
360 let mut lookup_table_meta = lookup_table.meta;
361 let clock = invoke_context.get_sysvar_cache().get_clock()?;
362 lookup_table_meta.deactivation_slot = clock.slot;
363
364 AddressLookupTable::overwrite_meta_data(
365 lookup_table_account.get_data_mut()?,
366 lookup_table_meta,
367 )?;
368
369 Ok(())
370 }
371
372 fn close_lookup_table(invoke_context: &mut InvokeContext) -> Result<(), InstructionError> {
373 let transaction_context = &invoke_context.transaction_context;
374 let instruction_context = transaction_context.get_current_instruction_context()?;
375
376 let lookup_table_account =
377 instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
378 if *lookup_table_account.get_owner() != id() {
379 return Err(InstructionError::InvalidAccountOwner);
380 }
381 drop(lookup_table_account);
382
383 let authority_account =
384 instruction_context.try_borrow_instruction_account(transaction_context, 1)?;
385 let authority_key = *authority_account.get_key();
386 if !authority_account.is_signer() {
387 ic_msg!(invoke_context, "Authority account must be a signer");
388 return Err(InstructionError::MissingRequiredSignature);
389 }
390 drop(authority_account);
391
392 instruction_context.check_number_of_instruction_accounts(3)?;
393 if instruction_context.get_index_of_instruction_account_in_transaction(0)?
394 == instruction_context.get_index_of_instruction_account_in_transaction(2)?
395 {
396 ic_msg!(
397 invoke_context,
398 "Lookup table cannot be the recipient of reclaimed lamports"
399 );
400 return Err(InstructionError::InvalidArgument);
401 }
402
403 let lookup_table_account =
404 instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
405 let withdrawn_lamports = lookup_table_account.get_lamports();
406 let lookup_table_data = lookup_table_account.get_data();
407 let lookup_table = AddressLookupTable::deserialize(lookup_table_data)?;
408
409 if lookup_table.meta.authority.is_none() {
410 ic_msg!(invoke_context, "Lookup table is frozen");
411 return Err(InstructionError::Immutable);
412 }
413 if lookup_table.meta.authority != Some(authority_key) {
414 return Err(InstructionError::IncorrectAuthority);
415 }
416
417 let sysvar_cache = invoke_context.get_sysvar_cache();
418 let clock = sysvar_cache.get_clock()?;
419 let slot_hashes = sysvar_cache.get_slot_hashes()?;
420
421 match lookup_table.meta.status(clock.slot, &slot_hashes) {
422 LookupTableStatus::Activated => {
423 ic_msg!(invoke_context, "Lookup table is not deactivated");
424 Err(InstructionError::InvalidArgument)
425 }
426 LookupTableStatus::Deactivating { remaining_blocks } => {
427 ic_msg!(
428 invoke_context,
429 "Table cannot be closed until it's fully deactivated in {} blocks",
430 remaining_blocks
431 );
432 Err(InstructionError::InvalidArgument)
433 }
434 LookupTableStatus::Deactivated => Ok(()),
435 }?;
436 drop(lookup_table_account);
437
438 let mut recipient_account =
439 instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
440 recipient_account.checked_add_lamports(withdrawn_lamports)?;
441 drop(recipient_account);
442
443 let mut lookup_table_account =
444 instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
445 lookup_table_account.set_data_length(0)?;
446 lookup_table_account.set_lamports(0)?;
447
448 Ok(())
449 }
450}