solana_address_lookup_table_program/
processor.rs

1use {
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        // Use a derived address to ensure that an address table can never be
100        // initialized more than once at the same address.
101        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                    // This is impossible as long as the length of new_addresses
278                    // is non-zero and LOOKUP_TABLE_MAX_ADDRESSES == u8::MAX + 1.
279                    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}