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