miraland_address_lookup_table_program/
processor.rs

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