1#![cfg_attr(docsrs, feature(doc_cfg))]
31#![allow(clippy::arithmetic_side_effects)]
32#![no_std]
33
34extern crate alloc;
35
36#[cfg(feature = "dev-context-only-utils")]
37use qualifier_attr::qualifiers;
38pub use solana_sdk_ids::sysvar::instructions::{check_id, id, ID};
39use {
40 alloc::vec::Vec,
41 solana_account_info::AccountInfo,
42 solana_instruction::{AccountMeta, Instruction},
43 solana_instruction_error::InstructionError,
44 solana_program_error::ProgramError,
45 solana_sanitize::SanitizeError,
46 solana_serialize_utils::{read_pubkey, read_slice, read_u16, read_u8},
47};
48#[cfg(not(target_os = "solana"))]
49use {
50 bitflags::bitflags,
51 solana_instruction::BorrowedInstruction,
52 solana_serialize_utils::{append_slice, append_u16, append_u8},
53};
54
55pub struct Instructions();
66
67solana_sysvar_id::impl_sysvar_id!(Instructions);
68
69#[cfg(not(target_os = "solana"))]
73pub fn construct_instructions_data(
74 instructions: &[BorrowedInstruction],
75) -> Result<Vec<u8>, InstructionsSysvarError> {
76 let mut data = serialize_instructions(instructions)?;
77 data.resize(data.len() + 2, 0);
79
80 Ok(data)
81}
82
83#[cfg(not(target_os = "solana"))]
84bitflags! {
85 struct InstructionsSysvarAccountMeta: u8 {
86 const IS_SIGNER = 0b00000001;
87 const IS_WRITABLE = 0b00000010;
88 }
89}
90
91#[derive(Debug, PartialEq, Eq)]
92pub enum InstructionsSysvarError {
93 InstructionIndexOutOfBounds,
95}
96
97#[cfg(not(target_os = "solana"))]
122#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
123fn serialize_instructions(
124 instructions: &[BorrowedInstruction],
125) -> Result<Vec<u8>, InstructionsSysvarError> {
126 let mut data = Vec::with_capacity(instructions.len() * (32 * 2));
128 append_u16(&mut data, instructions.len() as u16);
129 for _ in 0..instructions.len() {
130 append_u16(&mut data, 0);
131 }
132
133 for (i, instruction) in instructions.iter().enumerate() {
134 let start_instruction_offset = u16::try_from(data.len())
135 .map_err(|_| InstructionsSysvarError::InstructionIndexOutOfBounds)?;
136 let start = 2 + (2 * i);
137 data[start..start + 2].copy_from_slice(&start_instruction_offset.to_le_bytes());
138 append_u16(&mut data, instruction.accounts.len() as u16);
139 for account_meta in &instruction.accounts {
140 let mut account_meta_flags = InstructionsSysvarAccountMeta::empty();
141 if account_meta.is_signer {
142 account_meta_flags |= InstructionsSysvarAccountMeta::IS_SIGNER;
143 }
144 if account_meta.is_writable {
145 account_meta_flags |= InstructionsSysvarAccountMeta::IS_WRITABLE;
146 }
147 append_u8(&mut data, account_meta_flags.bits());
148 append_slice(&mut data, account_meta.pubkey.as_ref());
149 }
150
151 append_slice(&mut data, instruction.program_id.as_ref());
152 append_u16(&mut data, instruction.data.len() as u16);
153 append_slice(&mut data, instruction.data);
154 }
155 Ok(data)
156}
157
158fn load_current_index(data: &[u8]) -> u16 {
166 let mut instr_fixed_data = [0u8; 2];
167 let len = data.len();
168 instr_fixed_data.copy_from_slice(&data[len - 2..len]);
169 u16::from_le_bytes(instr_fixed_data)
170}
171
172pub fn load_current_index_checked(
179 instruction_sysvar_account_info: &AccountInfo,
180) -> Result<u16, ProgramError> {
181 if !check_id(instruction_sysvar_account_info.key) {
182 return Err(ProgramError::UnsupportedSysvar);
183 }
184
185 let instruction_sysvar = instruction_sysvar_account_info.try_borrow_data()?;
186 let index = load_current_index(&instruction_sysvar);
187 Ok(index)
188}
189
190pub fn store_current_index_checked(
192 data: &mut [u8],
193 instruction_index: u16,
194) -> Result<(), InstructionError> {
195 if data.len() < 2 {
196 return Err(InstructionError::AccountDataTooSmall);
197 }
198 let last_index = data.len() - 2;
199 data[last_index..last_index + 2].copy_from_slice(&instruction_index.to_le_bytes());
200 Ok(())
201}
202
203#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
204fn deserialize_instruction(index: usize, data: &[u8]) -> Result<Instruction, SanitizeError> {
205 const IS_SIGNER_BIT: usize = 0;
206 const IS_WRITABLE_BIT: usize = 1;
207
208 let mut current = 0;
209 let num_instructions = read_u16(&mut current, data)?;
210 if index >= num_instructions as usize {
211 return Err(SanitizeError::IndexOutOfBounds);
212 }
213
214 current += index * 2;
216 let start = read_u16(&mut current, data)?;
217
218 current = start as usize;
219 let num_accounts = read_u16(&mut current, data)?;
220 let mut accounts = Vec::with_capacity(num_accounts as usize);
221 for _ in 0..num_accounts {
222 let meta_byte = read_u8(&mut current, data)?;
223 let mut is_signer = false;
224 let mut is_writable = false;
225 if meta_byte & (1 << IS_SIGNER_BIT) != 0 {
226 is_signer = true;
227 }
228 if meta_byte & (1 << IS_WRITABLE_BIT) != 0 {
229 is_writable = true;
230 }
231 let pubkey = read_pubkey(&mut current, data)?;
232 accounts.push(AccountMeta {
233 pubkey,
234 is_signer,
235 is_writable,
236 });
237 }
238 let program_id = read_pubkey(&mut current, data)?;
239 let data_len = read_u16(&mut current, data)?;
240 let data = read_slice(&mut current, data, data_len as usize)?;
241 Ok(Instruction {
242 program_id,
243 accounts,
244 data,
245 })
246}
247
248#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
256fn load_instruction_at(index: usize, data: &[u8]) -> Result<Instruction, SanitizeError> {
257 deserialize_instruction(index, data)
258}
259
260pub fn load_instruction_at_checked(
267 index: usize,
268 instruction_sysvar_account_info: &AccountInfo,
269) -> Result<Instruction, ProgramError> {
270 if !check_id(instruction_sysvar_account_info.key) {
271 return Err(ProgramError::UnsupportedSysvar);
272 }
273
274 let instruction_sysvar = instruction_sysvar_account_info.try_borrow_data()?;
275 load_instruction_at(index, &instruction_sysvar).map_err(|err| match err {
276 SanitizeError::IndexOutOfBounds => ProgramError::InvalidArgument,
277 _ => ProgramError::InvalidInstructionData,
278 })
279}
280
281pub fn get_instruction_relative(
288 index_relative_to_current: i64,
289 instruction_sysvar_account_info: &AccountInfo,
290) -> Result<Instruction, ProgramError> {
291 if !check_id(instruction_sysvar_account_info.key) {
292 return Err(ProgramError::UnsupportedSysvar);
293 }
294
295 let instruction_sysvar = instruction_sysvar_account_info.data.borrow();
296 let current_index = load_current_index(&instruction_sysvar) as i64;
297 let index = current_index.saturating_add(index_relative_to_current);
298 if index < 0 {
299 return Err(ProgramError::InvalidArgument);
300 }
301 load_instruction_at(
302 current_index.saturating_add(index_relative_to_current) as usize,
303 &instruction_sysvar,
304 )
305 .map_err(|err| match err {
306 SanitizeError::IndexOutOfBounds => ProgramError::InvalidArgument,
307 _ => ProgramError::InvalidInstructionData,
308 })
309}
310
311#[cfg(test)]
312mod tests {
313 use {
314 super::*,
315 alloc::vec,
316 solana_account_info::AccountInfo,
317 solana_address::Address,
318 solana_instruction::{AccountMeta, BorrowedAccountMeta, BorrowedInstruction, Instruction},
319 solana_program_error::ProgramError,
320 solana_sanitize::SanitizeError,
321 solana_sdk_ids::sysvar::instructions::id,
322 };
323
324 #[test]
325 fn test_load_store_instruction() {
326 let mut data = [4u8; 10];
327 let res = store_current_index_checked(&mut data, 3);
328 assert!(res.is_ok());
329 let index = load_current_index(&data);
330 assert_eq!(index, 3);
331 assert_eq!([4u8; 8], data[0..8]);
332 }
333
334 #[test]
335 fn test_store_instruction_too_small_data() {
336 let mut data = [4u8; 1];
337 let res = store_current_index_checked(&mut data, 3);
338 assert!(res.is_err());
339 }
340
341 #[derive(Copy, Clone)]
342 struct MakeInstructionParams {
343 program_id: Address,
344 account_key: Address,
345 is_signer: bool,
346 is_writable: bool,
347 }
348
349 fn make_borrowed_instruction(params: &MakeInstructionParams) -> BorrowedInstruction<'_> {
350 let MakeInstructionParams {
351 program_id,
352 account_key,
353 is_signer,
354 is_writable,
355 } = params;
356 BorrowedInstruction {
357 program_id,
358 accounts: vec![BorrowedAccountMeta {
359 pubkey: account_key,
360 is_signer: *is_signer,
361 is_writable: *is_writable,
362 }],
363 data: &[0],
364 }
365 }
366
367 fn make_instruction(params: MakeInstructionParams) -> Instruction {
368 let MakeInstructionParams {
369 program_id,
370 account_key,
371 is_signer,
372 is_writable,
373 } = params;
374 Instruction {
375 program_id,
376 accounts: vec![AccountMeta {
377 pubkey: account_key,
378 is_signer,
379 is_writable,
380 }],
381 data: vec![0],
382 }
383 }
384
385 #[test]
386 fn test_load_instruction_at_checked() {
387 let program_id0 = Address::new_unique();
388 let program_id1 = Address::new_unique();
389 let account_key0 = Address::new_unique();
390 let account_key1 = Address::new_unique();
391 let params0 = MakeInstructionParams {
392 program_id: program_id0,
393 account_key: account_key0,
394 is_signer: false,
395 is_writable: false,
396 };
397 let params1 = MakeInstructionParams {
398 program_id: program_id1,
399 account_key: account_key1,
400 is_signer: false,
401 is_writable: false,
402 };
403 let instruction0 = make_instruction(params0);
404 let instruction1 = make_instruction(params1);
405 let borrowed_instruction0 = make_borrowed_instruction(¶ms0);
406 let borrowed_instruction1 = make_borrowed_instruction(¶ms1);
407 let key = id();
408 let mut lamports = 0;
409 let mut data =
410 construct_instructions_data(&[borrowed_instruction0, borrowed_instruction1]).unwrap();
411 let owner = solana_sdk_ids::sysvar::id();
412 let mut account_info =
413 AccountInfo::new(&key, false, false, &mut lamports, &mut data, &owner, false);
414
415 assert_eq!(
416 instruction0,
417 load_instruction_at_checked(0, &account_info).unwrap()
418 );
419 assert_eq!(
420 instruction1,
421 load_instruction_at_checked(1, &account_info).unwrap()
422 );
423 assert_eq!(
424 Err(ProgramError::InvalidArgument),
425 load_instruction_at_checked(2, &account_info)
426 );
427
428 let key = Address::new_unique();
429 account_info.key = &key;
430 assert_eq!(
431 Err(ProgramError::UnsupportedSysvar),
432 load_instruction_at_checked(2, &account_info)
433 );
434 }
435
436 #[test]
437 fn test_load_current_index_checked() {
438 let program_id0 = Address::new_unique();
439 let program_id1 = Address::new_unique();
440 let account_key0 = Address::new_unique();
441 let account_key1 = Address::new_unique();
442 let params0 = MakeInstructionParams {
443 program_id: program_id0,
444 account_key: account_key0,
445 is_signer: false,
446 is_writable: false,
447 };
448 let params1 = MakeInstructionParams {
449 program_id: program_id1,
450 account_key: account_key1,
451 is_signer: false,
452 is_writable: false,
453 };
454 let borrowed_instruction0 = make_borrowed_instruction(¶ms0);
455 let borrowed_instruction1 = make_borrowed_instruction(¶ms1);
456
457 let key = id();
458 let mut lamports = 0;
459 let mut data =
460 construct_instructions_data(&[borrowed_instruction0, borrowed_instruction1]).unwrap();
461 let res = store_current_index_checked(&mut data, 1);
462 assert!(res.is_ok());
463 let owner = solana_sdk_ids::sysvar::id();
464 let mut account_info =
465 AccountInfo::new(&key, false, false, &mut lamports, &mut data, &owner, false);
466
467 assert_eq!(1, load_current_index_checked(&account_info).unwrap());
468 {
469 let mut data = account_info.try_borrow_mut_data().unwrap();
470 let res = store_current_index_checked(&mut data, 0);
471 assert!(res.is_ok());
472 }
473 assert_eq!(0, load_current_index_checked(&account_info).unwrap());
474
475 let key = Address::new_unique();
476 account_info.key = &key;
477 assert_eq!(
478 Err(ProgramError::UnsupportedSysvar),
479 load_current_index_checked(&account_info)
480 );
481 }
482
483 #[test]
484 fn test_get_instruction_relative() {
485 let program_id0 = Address::new_unique();
486 let program_id1 = Address::new_unique();
487 let program_id2 = Address::new_unique();
488 let account_key0 = Address::new_unique();
489 let account_key1 = Address::new_unique();
490 let account_key2 = Address::new_unique();
491 let params0 = MakeInstructionParams {
492 program_id: program_id0,
493 account_key: account_key0,
494 is_signer: false,
495 is_writable: false,
496 };
497 let params1 = MakeInstructionParams {
498 program_id: program_id1,
499 account_key: account_key1,
500 is_signer: false,
501 is_writable: false,
502 };
503 let params2 = MakeInstructionParams {
504 program_id: program_id2,
505 account_key: account_key2,
506 is_signer: false,
507 is_writable: false,
508 };
509 let instruction0 = make_instruction(params0);
510 let instruction1 = make_instruction(params1);
511 let instruction2 = make_instruction(params2);
512 let borrowed_instruction0 = make_borrowed_instruction(¶ms0);
513 let borrowed_instruction1 = make_borrowed_instruction(¶ms1);
514 let borrowed_instruction2 = make_borrowed_instruction(¶ms2);
515
516 let key = id();
517 let mut lamports = 0;
518 let mut data = construct_instructions_data(&[
519 borrowed_instruction0,
520 borrowed_instruction1,
521 borrowed_instruction2,
522 ])
523 .unwrap();
524 let res = store_current_index_checked(&mut data, 1);
525 assert!(res.is_ok());
526 let owner = solana_sdk_ids::sysvar::id();
527 let mut account_info =
528 AccountInfo::new(&key, false, false, &mut lamports, &mut data, &owner, false);
529
530 assert_eq!(
531 Err(ProgramError::InvalidArgument),
532 get_instruction_relative(-2, &account_info)
533 );
534 assert_eq!(
535 instruction0,
536 get_instruction_relative(-1, &account_info).unwrap()
537 );
538 assert_eq!(
539 instruction1,
540 get_instruction_relative(0, &account_info).unwrap()
541 );
542 assert_eq!(
543 instruction2,
544 get_instruction_relative(1, &account_info).unwrap()
545 );
546 assert_eq!(
547 Err(ProgramError::InvalidArgument),
548 get_instruction_relative(2, &account_info)
549 );
550 {
551 let mut data = account_info.try_borrow_mut_data().unwrap();
552 let res = store_current_index_checked(&mut data, 0);
553 assert!(res.is_ok());
554 }
555 assert_eq!(
556 Err(ProgramError::InvalidArgument),
557 get_instruction_relative(-1, &account_info)
558 );
559 assert_eq!(
560 instruction0,
561 get_instruction_relative(0, &account_info).unwrap()
562 );
563 assert_eq!(
564 instruction1,
565 get_instruction_relative(1, &account_info).unwrap()
566 );
567 assert_eq!(
568 instruction2,
569 get_instruction_relative(2, &account_info).unwrap()
570 );
571 assert_eq!(
572 Err(ProgramError::InvalidArgument),
573 get_instruction_relative(3, &account_info)
574 );
575
576 let key = Address::new_unique();
577 account_info.key = &key;
578 assert_eq!(
579 Err(ProgramError::UnsupportedSysvar),
580 get_instruction_relative(0, &account_info)
581 );
582 }
583
584 #[test]
585 fn test_serialize_instructions() {
586 let program_id0 = Address::new_unique();
587 let program_id1 = Address::new_unique();
588 let id0 = Address::new_unique();
589 let id1 = Address::new_unique();
590 let id2 = Address::new_unique();
591 let id3 = Address::new_unique();
592 let params = vec![
593 MakeInstructionParams {
594 program_id: program_id0,
595 account_key: id0,
596 is_signer: false,
597 is_writable: true,
598 },
599 MakeInstructionParams {
600 program_id: program_id0,
601 account_key: id1,
602 is_signer: true,
603 is_writable: true,
604 },
605 MakeInstructionParams {
606 program_id: program_id1,
607 account_key: id2,
608 is_signer: false,
609 is_writable: false,
610 },
611 MakeInstructionParams {
612 program_id: program_id1,
613 account_key: id3,
614 is_signer: true,
615 is_writable: false,
616 },
617 ];
618 let instructions: Vec<Instruction> =
619 params.clone().into_iter().map(make_instruction).collect();
620 let borrowed_instructions: Vec<BorrowedInstruction> =
621 params.iter().map(make_borrowed_instruction).collect();
622
623 let serialized = serialize_instructions(&borrowed_instructions).unwrap();
624
625 for (i, instruction) in instructions.iter().enumerate() {
627 assert_eq!(
628 deserialize_instruction(i, &serialized).unwrap(),
629 *instruction
630 );
631 }
632 }
633
634 #[test]
635 fn test_serialize_instructions_rejects_instruction_offset_overflow() {
636 fn make_borrowed_instruction_with_accounts<'a>(
637 program_id: &'a Address,
638 account_key: &'a Address,
639 num_account_refs: usize,
640 data: &'a [u8],
641 ) -> BorrowedInstruction<'a> {
642 BorrowedInstruction {
643 program_id,
644 accounts: (0..num_account_refs)
645 .map(|_| BorrowedAccountMeta {
646 pubkey: account_key,
647 is_signer: false,
648 is_writable: false,
649 })
650 .collect(),
651 data,
652 }
653 }
654
655 fn make_instructions<'a>(
656 program_id: &'a Address,
657 account_key: &'a Address,
658 padding_data: &'a [u8],
659 ) -> Vec<BorrowedInstruction<'a>> {
660 let mut instructions = (0..7)
661 .map(|_| make_borrowed_instruction_with_accounts(program_id, account_key, 255, &[]))
662 .collect::<Vec<_>>();
663 instructions.push(make_borrowed_instruction_with_accounts(
664 program_id,
665 account_key,
666 191,
667 padding_data,
668 ));
669 instructions.push(make_borrowed_instruction_with_accounts(
670 program_id,
671 account_key,
672 255,
673 &[],
674 ));
675 instructions
676 }
677
678 let program_id = Address::new_unique();
679 let account_key = Address::new_unique();
680
681 let boundary_padding_data = [0; 19];
682 let instructions = make_instructions(&program_id, &account_key, &boundary_padding_data);
683 assert!(serialize_instructions(&instructions).is_ok());
684
685 let overflow_padding_data = [0; 20];
686 let instructions = make_instructions(&program_id, &account_key, &overflow_padding_data);
687 assert_eq!(
688 serialize_instructions(&instructions),
689 Err(InstructionsSysvarError::InstructionIndexOutOfBounds)
690 );
691 assert_eq!(
692 construct_instructions_data(&instructions),
693 Err(InstructionsSysvarError::InstructionIndexOutOfBounds)
694 );
695 }
696
697 #[test]
698 fn test_decompile_instructions_out_of_bounds() {
699 let program_id0 = Address::new_unique();
700 let id0 = Address::new_unique();
701 let id1 = Address::new_unique();
702 let params = vec![
703 MakeInstructionParams {
704 program_id: program_id0,
705 account_key: id0,
706 is_signer: false,
707 is_writable: true,
708 },
709 MakeInstructionParams {
710 program_id: program_id0,
711 account_key: id1,
712 is_signer: true,
713 is_writable: true,
714 },
715 ];
716 let instructions: Vec<Instruction> =
717 params.clone().into_iter().map(make_instruction).collect();
718 let borrowed_instructions: Vec<BorrowedInstruction> =
719 params.iter().map(make_borrowed_instruction).collect();
720
721 let serialized = serialize_instructions(&borrowed_instructions).unwrap();
722 assert_eq!(
723 deserialize_instruction(instructions.len(), &serialized).unwrap_err(),
724 SanitizeError::IndexOutOfBounds,
725 );
726 }
727}