clone_solana_address_lookup_table_program/
processor.rs

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