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