solana_address_lookup_table_program/
processor.rs

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