miraland_address_lookup_table_program/
processor.rs1use {
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 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 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}