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