1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
31#![allow(clippy::arithmetic_side_effects)]
32
33#[cfg(feature = "dev-context-only-utils")]
34use qualifier_attr::qualifiers;
35pub use atlas_sdk_ids::sysvar::instructions::{check_id, id, ID};
36#[cfg(not(target_os = "atlas"))]
37use {
38 bitflags::bitflags,
39 atlas_instruction::BorrowedInstruction,
40 atlas_serialize_utils::{append_slice, append_u16, append_u8},
41};
42use {
43 atlas_account_info::AccountInfo,
44 atlas_instruction::{AccountMeta, Instruction},
45 atlas_instruction_error::InstructionError,
46 atlas_program_error::ProgramError,
47 atlas_sanitize::SanitizeError,
48 atlas_serialize_utils::{read_pubkey, read_slice, read_u16, read_u8},
49};
50
51pub struct Instructions();
62
63atlas_sysvar_id::impl_sysvar_id!(Instructions);
64
65#[cfg(not(target_os = "atlas"))]
69pub fn construct_instructions_data(instructions: &[BorrowedInstruction]) -> Vec<u8> {
70 let mut data = serialize_instructions(instructions);
71 data.resize(data.len() + 2, 0);
73
74 data
75}
76
77#[cfg(not(target_os = "atlas"))]
78bitflags! {
79 struct InstructionsSysvarAccountMeta: u8 {
80 const IS_SIGNER = 0b00000001;
81 const IS_WRITABLE = 0b00000010;
82 }
83}
84
85#[cfg(not(target_os = "atlas"))]
110#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
111fn serialize_instructions(instructions: &[BorrowedInstruction]) -> Vec<u8> {
112 let mut data = Vec::with_capacity(instructions.len() * (32 * 2));
114 append_u16(&mut data, instructions.len() as u16);
115 for _ in 0..instructions.len() {
116 append_u16(&mut data, 0);
117 }
118
119 for (i, instruction) in instructions.iter().enumerate() {
120 let start_instruction_offset = data.len() as u16;
121 let start = 2 + (2 * i);
122 data[start..start + 2].copy_from_slice(&start_instruction_offset.to_le_bytes());
123 append_u16(&mut data, instruction.accounts.len() as u16);
124 for account_meta in &instruction.accounts {
125 let mut account_meta_flags = InstructionsSysvarAccountMeta::empty();
126 if account_meta.is_signer {
127 account_meta_flags |= InstructionsSysvarAccountMeta::IS_SIGNER;
128 }
129 if account_meta.is_writable {
130 account_meta_flags |= InstructionsSysvarAccountMeta::IS_WRITABLE;
131 }
132 append_u8(&mut data, account_meta_flags.bits());
133 append_slice(&mut data, account_meta.pubkey.as_ref());
134 }
135
136 append_slice(&mut data, instruction.program_id.as_ref());
137 append_u16(&mut data, instruction.data.len() as u16);
138 append_slice(&mut data, instruction.data);
139 }
140 data
141}
142
143fn load_current_index(data: &[u8]) -> u16 {
151 let mut instr_fixed_data = [0u8; 2];
152 let len = data.len();
153 instr_fixed_data.copy_from_slice(&data[len - 2..len]);
154 u16::from_le_bytes(instr_fixed_data)
155}
156
157pub fn load_current_index_checked(
164 instruction_sysvar_account_info: &AccountInfo,
165) -> Result<u16, ProgramError> {
166 if !check_id(instruction_sysvar_account_info.key) {
167 return Err(ProgramError::UnsupportedSysvar);
168 }
169
170 let instruction_sysvar = instruction_sysvar_account_info.try_borrow_data()?;
171 let index = load_current_index(&instruction_sysvar);
172 Ok(index)
173}
174
175pub fn store_current_index_checked(
177 data: &mut [u8],
178 instruction_index: u16,
179) -> Result<(), InstructionError> {
180 if data.len() < 2 {
181 return Err(InstructionError::AccountDataTooSmall);
182 }
183 let last_index = data.len() - 2;
184 data[last_index..last_index + 2].copy_from_slice(&instruction_index.to_le_bytes());
185 Ok(())
186}
187
188#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
189fn deserialize_instruction(index: usize, data: &[u8]) -> Result<Instruction, SanitizeError> {
190 const IS_SIGNER_BIT: usize = 0;
191 const IS_WRITABLE_BIT: usize = 1;
192
193 let mut current = 0;
194 let num_instructions = read_u16(&mut current, data)?;
195 if index >= num_instructions as usize {
196 return Err(SanitizeError::IndexOutOfBounds);
197 }
198
199 current += index * 2;
201 let start = read_u16(&mut current, data)?;
202
203 current = start as usize;
204 let num_accounts = read_u16(&mut current, data)?;
205 let mut accounts = Vec::with_capacity(num_accounts as usize);
206 for _ in 0..num_accounts {
207 let meta_byte = read_u8(&mut current, data)?;
208 let mut is_signer = false;
209 let mut is_writable = false;
210 if meta_byte & (1 << IS_SIGNER_BIT) != 0 {
211 is_signer = true;
212 }
213 if meta_byte & (1 << IS_WRITABLE_BIT) != 0 {
214 is_writable = true;
215 }
216 let pubkey = read_pubkey(&mut current, data)?;
217 accounts.push(AccountMeta {
218 pubkey,
219 is_signer,
220 is_writable,
221 });
222 }
223 let program_id = read_pubkey(&mut current, data)?;
224 let data_len = read_u16(&mut current, data)?;
225 let data = read_slice(&mut current, data, data_len as usize)?;
226 Ok(Instruction {
227 program_id,
228 accounts,
229 data,
230 })
231}
232
233#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
241fn load_instruction_at(index: usize, data: &[u8]) -> Result<Instruction, SanitizeError> {
242 deserialize_instruction(index, data)
243}
244
245pub fn load_instruction_at_checked(
252 index: usize,
253 instruction_sysvar_account_info: &AccountInfo,
254) -> Result<Instruction, ProgramError> {
255 if !check_id(instruction_sysvar_account_info.key) {
256 return Err(ProgramError::UnsupportedSysvar);
257 }
258
259 let instruction_sysvar = instruction_sysvar_account_info.try_borrow_data()?;
260 load_instruction_at(index, &instruction_sysvar).map_err(|err| match err {
261 SanitizeError::IndexOutOfBounds => ProgramError::InvalidArgument,
262 _ => ProgramError::InvalidInstructionData,
263 })
264}
265
266pub fn get_instruction_relative(
273 index_relative_to_current: i64,
274 instruction_sysvar_account_info: &AccountInfo,
275) -> Result<Instruction, ProgramError> {
276 if !check_id(instruction_sysvar_account_info.key) {
277 return Err(ProgramError::UnsupportedSysvar);
278 }
279
280 let instruction_sysvar = instruction_sysvar_account_info.data.borrow();
281 let current_index = load_current_index(&instruction_sysvar) as i64;
282 let index = current_index.saturating_add(index_relative_to_current);
283 if index < 0 {
284 return Err(ProgramError::InvalidArgument);
285 }
286 load_instruction_at(
287 current_index.saturating_add(index_relative_to_current) as usize,
288 &instruction_sysvar,
289 )
290 .map_err(|err| match err {
291 SanitizeError::IndexOutOfBounds => ProgramError::InvalidArgument,
292 _ => ProgramError::InvalidInstructionData,
293 })
294}
295
296#[cfg(test)]
297mod tests {
298 use {
299 super::*,
300 atlas_account_info::AccountInfo,
301 atlas_instruction::{AccountMeta, BorrowedAccountMeta, BorrowedInstruction, Instruction},
302 atlas_program_error::ProgramError,
303 atlas_pubkey::Pubkey,
304 atlas_sanitize::SanitizeError,
305 atlas_sdk_ids::sysvar::instructions::id,
306 };
307
308 #[test]
309 fn test_load_store_instruction() {
310 let mut data = [4u8; 10];
311 let res = store_current_index_checked(&mut data, 3);
312 assert!(res.is_ok());
313 let index = load_current_index(&data);
314 assert_eq!(index, 3);
315 assert_eq!([4u8; 8], data[0..8]);
316 }
317
318 #[test]
319 fn test_store_instruction_too_small_data() {
320 let mut data = [4u8; 1];
321 let res = store_current_index_checked(&mut data, 3);
322 assert!(res.is_err());
323 }
324
325 #[derive(Copy, Clone)]
326 struct MakeInstructionParams {
327 program_id: Pubkey,
328 account_key: Pubkey,
329 is_signer: bool,
330 is_writable: bool,
331 }
332
333 fn make_borrowed_instruction(params: &MakeInstructionParams) -> BorrowedInstruction<'_> {
334 let MakeInstructionParams {
335 program_id,
336 account_key,
337 is_signer,
338 is_writable,
339 } = params;
340 BorrowedInstruction {
341 program_id,
342 accounts: vec![BorrowedAccountMeta {
343 pubkey: account_key,
344 is_signer: *is_signer,
345 is_writable: *is_writable,
346 }],
347 data: &[0],
348 }
349 }
350
351 fn make_instruction(params: MakeInstructionParams) -> Instruction {
352 let MakeInstructionParams {
353 program_id,
354 account_key,
355 is_signer,
356 is_writable,
357 } = params;
358 Instruction {
359 program_id,
360 accounts: vec![AccountMeta {
361 pubkey: account_key,
362 is_signer,
363 is_writable,
364 }],
365 data: vec![0],
366 }
367 }
368
369 #[test]
370 fn test_load_instruction_at_checked() {
371 let program_id0 = Pubkey::new_unique();
372 let program_id1 = Pubkey::new_unique();
373 let account_key0 = Pubkey::new_unique();
374 let account_key1 = Pubkey::new_unique();
375 let params0 = MakeInstructionParams {
376 program_id: program_id0,
377 account_key: account_key0,
378 is_signer: false,
379 is_writable: false,
380 };
381 let params1 = MakeInstructionParams {
382 program_id: program_id1,
383 account_key: account_key1,
384 is_signer: false,
385 is_writable: false,
386 };
387 let instruction0 = make_instruction(params0);
388 let instruction1 = make_instruction(params1);
389 let borrowed_instruction0 = make_borrowed_instruction(¶ms0);
390 let borrowed_instruction1 = make_borrowed_instruction(¶ms1);
391 let key = id();
392 let mut lamports = 0;
393 let mut data = construct_instructions_data(&[borrowed_instruction0, borrowed_instruction1]);
394 let owner = atlas_sdk_ids::sysvar::id();
395 let mut account_info =
396 AccountInfo::new(&key, false, false, &mut lamports, &mut data, &owner, false);
397
398 assert_eq!(
399 instruction0,
400 load_instruction_at_checked(0, &account_info).unwrap()
401 );
402 assert_eq!(
403 instruction1,
404 load_instruction_at_checked(1, &account_info).unwrap()
405 );
406 assert_eq!(
407 Err(ProgramError::InvalidArgument),
408 load_instruction_at_checked(2, &account_info)
409 );
410
411 let key = Pubkey::new_unique();
412 account_info.key = &key;
413 assert_eq!(
414 Err(ProgramError::UnsupportedSysvar),
415 load_instruction_at_checked(2, &account_info)
416 );
417 }
418
419 #[test]
420 fn test_load_current_index_checked() {
421 let program_id0 = Pubkey::new_unique();
422 let program_id1 = Pubkey::new_unique();
423 let account_key0 = Pubkey::new_unique();
424 let account_key1 = Pubkey::new_unique();
425 let params0 = MakeInstructionParams {
426 program_id: program_id0,
427 account_key: account_key0,
428 is_signer: false,
429 is_writable: false,
430 };
431 let params1 = MakeInstructionParams {
432 program_id: program_id1,
433 account_key: account_key1,
434 is_signer: false,
435 is_writable: false,
436 };
437 let borrowed_instruction0 = make_borrowed_instruction(¶ms0);
438 let borrowed_instruction1 = make_borrowed_instruction(¶ms1);
439
440 let key = id();
441 let mut lamports = 0;
442 let mut data = construct_instructions_data(&[borrowed_instruction0, borrowed_instruction1]);
443 let res = store_current_index_checked(&mut data, 1);
444 assert!(res.is_ok());
445 let owner = atlas_sdk_ids::sysvar::id();
446 let mut account_info =
447 AccountInfo::new(&key, false, false, &mut lamports, &mut data, &owner, false);
448
449 assert_eq!(1, load_current_index_checked(&account_info).unwrap());
450 {
451 let mut data = account_info.try_borrow_mut_data().unwrap();
452 let res = store_current_index_checked(&mut data, 0);
453 assert!(res.is_ok());
454 }
455 assert_eq!(0, load_current_index_checked(&account_info).unwrap());
456
457 let key = Pubkey::new_unique();
458 account_info.key = &key;
459 assert_eq!(
460 Err(ProgramError::UnsupportedSysvar),
461 load_current_index_checked(&account_info)
462 );
463 }
464
465 #[test]
466 fn test_get_instruction_relative() {
467 let program_id0 = Pubkey::new_unique();
468 let program_id1 = Pubkey::new_unique();
469 let program_id2 = Pubkey::new_unique();
470 let account_key0 = Pubkey::new_unique();
471 let account_key1 = Pubkey::new_unique();
472 let account_key2 = Pubkey::new_unique();
473 let params0 = MakeInstructionParams {
474 program_id: program_id0,
475 account_key: account_key0,
476 is_signer: false,
477 is_writable: false,
478 };
479 let params1 = MakeInstructionParams {
480 program_id: program_id1,
481 account_key: account_key1,
482 is_signer: false,
483 is_writable: false,
484 };
485 let params2 = MakeInstructionParams {
486 program_id: program_id2,
487 account_key: account_key2,
488 is_signer: false,
489 is_writable: false,
490 };
491 let instruction0 = make_instruction(params0);
492 let instruction1 = make_instruction(params1);
493 let instruction2 = make_instruction(params2);
494 let borrowed_instruction0 = make_borrowed_instruction(¶ms0);
495 let borrowed_instruction1 = make_borrowed_instruction(¶ms1);
496 let borrowed_instruction2 = make_borrowed_instruction(¶ms2);
497
498 let key = id();
499 let mut lamports = 0;
500 let mut data = construct_instructions_data(&[
501 borrowed_instruction0,
502 borrowed_instruction1,
503 borrowed_instruction2,
504 ]);
505 let res = store_current_index_checked(&mut data, 1);
506 assert!(res.is_ok());
507 let owner = atlas_sdk_ids::sysvar::id();
508 let mut account_info =
509 AccountInfo::new(&key, false, false, &mut lamports, &mut data, &owner, false);
510
511 assert_eq!(
512 Err(ProgramError::InvalidArgument),
513 get_instruction_relative(-2, &account_info)
514 );
515 assert_eq!(
516 instruction0,
517 get_instruction_relative(-1, &account_info).unwrap()
518 );
519 assert_eq!(
520 instruction1,
521 get_instruction_relative(0, &account_info).unwrap()
522 );
523 assert_eq!(
524 instruction2,
525 get_instruction_relative(1, &account_info).unwrap()
526 );
527 assert_eq!(
528 Err(ProgramError::InvalidArgument),
529 get_instruction_relative(2, &account_info)
530 );
531 {
532 let mut data = account_info.try_borrow_mut_data().unwrap();
533 let res = store_current_index_checked(&mut data, 0);
534 assert!(res.is_ok());
535 }
536 assert_eq!(
537 Err(ProgramError::InvalidArgument),
538 get_instruction_relative(-1, &account_info)
539 );
540 assert_eq!(
541 instruction0,
542 get_instruction_relative(0, &account_info).unwrap()
543 );
544 assert_eq!(
545 instruction1,
546 get_instruction_relative(1, &account_info).unwrap()
547 );
548 assert_eq!(
549 instruction2,
550 get_instruction_relative(2, &account_info).unwrap()
551 );
552 assert_eq!(
553 Err(ProgramError::InvalidArgument),
554 get_instruction_relative(3, &account_info)
555 );
556
557 let key = Pubkey::new_unique();
558 account_info.key = &key;
559 assert_eq!(
560 Err(ProgramError::UnsupportedSysvar),
561 get_instruction_relative(0, &account_info)
562 );
563 }
564
565 #[test]
566 fn test_serialize_instructions() {
567 let program_id0 = Pubkey::new_unique();
568 let program_id1 = Pubkey::new_unique();
569 let id0 = Pubkey::new_unique();
570 let id1 = Pubkey::new_unique();
571 let id2 = Pubkey::new_unique();
572 let id3 = Pubkey::new_unique();
573 let params = vec![
574 MakeInstructionParams {
575 program_id: program_id0,
576 account_key: id0,
577 is_signer: false,
578 is_writable: true,
579 },
580 MakeInstructionParams {
581 program_id: program_id0,
582 account_key: id1,
583 is_signer: true,
584 is_writable: true,
585 },
586 MakeInstructionParams {
587 program_id: program_id1,
588 account_key: id2,
589 is_signer: false,
590 is_writable: false,
591 },
592 MakeInstructionParams {
593 program_id: program_id1,
594 account_key: id3,
595 is_signer: true,
596 is_writable: false,
597 },
598 ];
599 let instructions: Vec<Instruction> =
600 params.clone().into_iter().map(make_instruction).collect();
601 let borrowed_instructions: Vec<BorrowedInstruction> =
602 params.iter().map(make_borrowed_instruction).collect();
603
604 let serialized = serialize_instructions(&borrowed_instructions);
605
606 for (i, instruction) in instructions.iter().enumerate() {
608 assert_eq!(
609 deserialize_instruction(i, &serialized).unwrap(),
610 *instruction
611 );
612 }
613 }
614
615 #[test]
616 fn test_decompile_instructions_out_of_bounds() {
617 let program_id0 = Pubkey::new_unique();
618 let id0 = Pubkey::new_unique();
619 let id1 = Pubkey::new_unique();
620 let params = vec![
621 MakeInstructionParams {
622 program_id: program_id0,
623 account_key: id0,
624 is_signer: false,
625 is_writable: true,
626 },
627 MakeInstructionParams {
628 program_id: program_id0,
629 account_key: id1,
630 is_signer: true,
631 is_writable: true,
632 },
633 ];
634 let instructions: Vec<Instruction> =
635 params.clone().into_iter().map(make_instruction).collect();
636 let borrowed_instructions: Vec<BorrowedInstruction> =
637 params.iter().map(make_borrowed_instruction).collect();
638
639 let serialized = serialize_instructions(&borrowed_instructions);
640 assert_eq!(
641 deserialize_instruction(instructions.len(), &serialized).unwrap_err(),
642 SanitizeError::IndexOutOfBounds,
643 );
644 }
645}