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::{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
163pub fn store_current_index(data: &mut [u8], instruction_index: u16) {
165 let last_index = data.len() - 2;
166 data[last_index..last_index + 2].copy_from_slice(&instruction_index.to_le_bytes());
167}
168
169#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
170fn deserialize_instruction(index: usize, data: &[u8]) -> Result<Instruction, SanitizeError> {
171 const IS_SIGNER_BIT: usize = 0;
172 const IS_WRITABLE_BIT: usize = 1;
173
174 let mut current = 0;
175 let num_instructions = read_u16(&mut current, data)?;
176 if index >= num_instructions as usize {
177 return Err(SanitizeError::IndexOutOfBounds);
178 }
179
180 current += index * 2;
182 let start = read_u16(&mut current, data)?;
183
184 current = start as usize;
185 let num_accounts = read_u16(&mut current, data)?;
186 let mut accounts = Vec::with_capacity(num_accounts as usize);
187 for _ in 0..num_accounts {
188 let meta_byte = read_u8(&mut current, data)?;
189 let mut is_signer = false;
190 let mut is_writable = false;
191 if meta_byte & (1 << IS_SIGNER_BIT) != 0 {
192 is_signer = true;
193 }
194 if meta_byte & (1 << IS_WRITABLE_BIT) != 0 {
195 is_writable = true;
196 }
197 let pubkey = read_pubkey(&mut current, data)?;
198 accounts.push(AccountMeta {
199 pubkey,
200 is_signer,
201 is_writable,
202 });
203 }
204 let program_id = read_pubkey(&mut current, data)?;
205 let data_len = read_u16(&mut current, data)?;
206 let data = read_slice(&mut current, data, data_len as usize)?;
207 Ok(Instruction {
208 program_id,
209 accounts,
210 data,
211 })
212}
213
214#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
222fn load_instruction_at(index: usize, data: &[u8]) -> Result<Instruction, SanitizeError> {
223 deserialize_instruction(index, data)
224}
225
226pub fn load_instruction_at_checked(
233 index: usize,
234 instruction_sysvar_account_info: &AccountInfo,
235) -> Result<Instruction, ProgramError> {
236 if !check_id(instruction_sysvar_account_info.key) {
237 return Err(ProgramError::UnsupportedSysvar);
238 }
239
240 let instruction_sysvar = instruction_sysvar_account_info.try_borrow_data()?;
241 load_instruction_at(index, &instruction_sysvar).map_err(|err| match err {
242 SanitizeError::IndexOutOfBounds => ProgramError::InvalidArgument,
243 _ => ProgramError::InvalidInstructionData,
244 })
245}
246
247pub fn get_instruction_relative(
254 index_relative_to_current: i64,
255 instruction_sysvar_account_info: &AccountInfo,
256) -> Result<Instruction, ProgramError> {
257 if !check_id(instruction_sysvar_account_info.key) {
258 return Err(ProgramError::UnsupportedSysvar);
259 }
260
261 let instruction_sysvar = instruction_sysvar_account_info.data.borrow();
262 let current_index = load_current_index(&instruction_sysvar) as i64;
263 let index = current_index.saturating_add(index_relative_to_current);
264 if index < 0 {
265 return Err(ProgramError::InvalidArgument);
266 }
267 load_instruction_at(
268 current_index.saturating_add(index_relative_to_current) as usize,
269 &instruction_sysvar,
270 )
271 .map_err(|err| match err {
272 SanitizeError::IndexOutOfBounds => ProgramError::InvalidArgument,
273 _ => ProgramError::InvalidInstructionData,
274 })
275}
276
277#[cfg(test)]
278mod tests {
279 use {
280 super::*,
281 solana_account_info::AccountInfo,
282 solana_instruction::{AccountMeta, BorrowedAccountMeta, BorrowedInstruction, Instruction},
283 solana_program_error::ProgramError,
284 solana_pubkey::Pubkey,
285 solana_sanitize::SanitizeError,
286 solana_sdk_ids::sysvar::instructions::id,
287 };
288
289 #[test]
290 fn test_load_store_instruction() {
291 let mut data = [4u8; 10];
292 store_current_index(&mut data, 3);
293 #[allow(deprecated)]
294 let index = load_current_index(&data);
295 assert_eq!(index, 3);
296 assert_eq!([4u8; 8], data[0..8]);
297 }
298
299 #[derive(Copy, Clone)]
300 struct MakeInstructionParams {
301 program_id: Pubkey,
302 account_key: Pubkey,
303 is_signer: bool,
304 is_writable: bool,
305 }
306
307 fn make_borrowed_instruction(params: &MakeInstructionParams) -> BorrowedInstruction {
308 let MakeInstructionParams {
309 program_id,
310 account_key,
311 is_signer,
312 is_writable,
313 } = params;
314 BorrowedInstruction {
315 program_id,
316 accounts: vec![BorrowedAccountMeta {
317 pubkey: account_key,
318 is_signer: *is_signer,
319 is_writable: *is_writable,
320 }],
321 data: &[0],
322 }
323 }
324
325 fn make_instruction(params: MakeInstructionParams) -> Instruction {
326 let MakeInstructionParams {
327 program_id,
328 account_key,
329 is_signer,
330 is_writable,
331 } = params;
332 Instruction {
333 program_id,
334 accounts: vec![AccountMeta {
335 pubkey: account_key,
336 is_signer,
337 is_writable,
338 }],
339 data: vec![0],
340 }
341 }
342
343 #[test]
344 fn test_load_instruction_at_checked() {
345 let program_id0 = Pubkey::new_unique();
346 let program_id1 = Pubkey::new_unique();
347 let account_key0 = Pubkey::new_unique();
348 let account_key1 = Pubkey::new_unique();
349 let params0 = MakeInstructionParams {
350 program_id: program_id0,
351 account_key: account_key0,
352 is_signer: false,
353 is_writable: false,
354 };
355 let params1 = MakeInstructionParams {
356 program_id: program_id1,
357 account_key: account_key1,
358 is_signer: false,
359 is_writable: false,
360 };
361 let instruction0 = make_instruction(params0);
362 let instruction1 = make_instruction(params1);
363 let borrowed_instruction0 = make_borrowed_instruction(¶ms0);
364 let borrowed_instruction1 = make_borrowed_instruction(¶ms1);
365 let key = id();
366 let mut lamports = 0;
367 let mut data = construct_instructions_data(&[borrowed_instruction0, borrowed_instruction1]);
368 let owner = solana_sdk_ids::sysvar::id();
369 let mut account_info = AccountInfo::new(
370 &key,
371 false,
372 false,
373 &mut lamports,
374 &mut data,
375 &owner,
376 false,
377 0,
378 );
379
380 assert_eq!(
381 instruction0,
382 load_instruction_at_checked(0, &account_info).unwrap()
383 );
384 assert_eq!(
385 instruction1,
386 load_instruction_at_checked(1, &account_info).unwrap()
387 );
388 assert_eq!(
389 Err(ProgramError::InvalidArgument),
390 load_instruction_at_checked(2, &account_info)
391 );
392
393 let key = Pubkey::new_unique();
394 account_info.key = &key;
395 assert_eq!(
396 Err(ProgramError::UnsupportedSysvar),
397 load_instruction_at_checked(2, &account_info)
398 );
399 }
400
401 #[test]
402 fn test_load_current_index_checked() {
403 let program_id0 = Pubkey::new_unique();
404 let program_id1 = Pubkey::new_unique();
405 let account_key0 = Pubkey::new_unique();
406 let account_key1 = Pubkey::new_unique();
407 let params0 = MakeInstructionParams {
408 program_id: program_id0,
409 account_key: account_key0,
410 is_signer: false,
411 is_writable: false,
412 };
413 let params1 = MakeInstructionParams {
414 program_id: program_id1,
415 account_key: account_key1,
416 is_signer: false,
417 is_writable: false,
418 };
419 let borrowed_instruction0 = make_borrowed_instruction(¶ms0);
420 let borrowed_instruction1 = make_borrowed_instruction(¶ms1);
421
422 let key = id();
423 let mut lamports = 0;
424 let mut data = construct_instructions_data(&[borrowed_instruction0, borrowed_instruction1]);
425 store_current_index(&mut data, 1);
426 let owner = solana_sdk_ids::sysvar::id();
427 let mut account_info = AccountInfo::new(
428 &key,
429 false,
430 false,
431 &mut lamports,
432 &mut data,
433 &owner,
434 false,
435 0,
436 );
437
438 assert_eq!(1, load_current_index_checked(&account_info).unwrap());
439 {
440 let mut data = account_info.try_borrow_mut_data().unwrap();
441 store_current_index(&mut data, 0);
442 }
443 assert_eq!(0, load_current_index_checked(&account_info).unwrap());
444
445 let key = Pubkey::new_unique();
446 account_info.key = &key;
447 assert_eq!(
448 Err(ProgramError::UnsupportedSysvar),
449 load_current_index_checked(&account_info)
450 );
451 }
452
453 #[test]
454 fn test_get_instruction_relative() {
455 let program_id0 = Pubkey::new_unique();
456 let program_id1 = Pubkey::new_unique();
457 let program_id2 = Pubkey::new_unique();
458 let account_key0 = Pubkey::new_unique();
459 let account_key1 = Pubkey::new_unique();
460 let account_key2 = Pubkey::new_unique();
461 let params0 = MakeInstructionParams {
462 program_id: program_id0,
463 account_key: account_key0,
464 is_signer: false,
465 is_writable: false,
466 };
467 let params1 = MakeInstructionParams {
468 program_id: program_id1,
469 account_key: account_key1,
470 is_signer: false,
471 is_writable: false,
472 };
473 let params2 = MakeInstructionParams {
474 program_id: program_id2,
475 account_key: account_key2,
476 is_signer: false,
477 is_writable: false,
478 };
479 let instruction0 = make_instruction(params0);
480 let instruction1 = make_instruction(params1);
481 let instruction2 = make_instruction(params2);
482 let borrowed_instruction0 = make_borrowed_instruction(¶ms0);
483 let borrowed_instruction1 = make_borrowed_instruction(¶ms1);
484 let borrowed_instruction2 = make_borrowed_instruction(¶ms2);
485
486 let key = id();
487 let mut lamports = 0;
488 let mut data = construct_instructions_data(&[
489 borrowed_instruction0,
490 borrowed_instruction1,
491 borrowed_instruction2,
492 ]);
493 store_current_index(&mut data, 1);
494 let owner = solana_sdk_ids::sysvar::id();
495 let mut account_info = AccountInfo::new(
496 &key,
497 false,
498 false,
499 &mut lamports,
500 &mut data,
501 &owner,
502 false,
503 0,
504 );
505
506 assert_eq!(
507 Err(ProgramError::InvalidArgument),
508 get_instruction_relative(-2, &account_info)
509 );
510 assert_eq!(
511 instruction0,
512 get_instruction_relative(-1, &account_info).unwrap()
513 );
514 assert_eq!(
515 instruction1,
516 get_instruction_relative(0, &account_info).unwrap()
517 );
518 assert_eq!(
519 instruction2,
520 get_instruction_relative(1, &account_info).unwrap()
521 );
522 assert_eq!(
523 Err(ProgramError::InvalidArgument),
524 get_instruction_relative(2, &account_info)
525 );
526 {
527 let mut data = account_info.try_borrow_mut_data().unwrap();
528 store_current_index(&mut data, 0);
529 }
530 assert_eq!(
531 Err(ProgramError::InvalidArgument),
532 get_instruction_relative(-1, &account_info)
533 );
534 assert_eq!(
535 instruction0,
536 get_instruction_relative(0, &account_info).unwrap()
537 );
538 assert_eq!(
539 instruction1,
540 get_instruction_relative(1, &account_info).unwrap()
541 );
542 assert_eq!(
543 instruction2,
544 get_instruction_relative(2, &account_info).unwrap()
545 );
546 assert_eq!(
547 Err(ProgramError::InvalidArgument),
548 get_instruction_relative(3, &account_info)
549 );
550
551 let key = Pubkey::new_unique();
552 account_info.key = &key;
553 assert_eq!(
554 Err(ProgramError::UnsupportedSysvar),
555 get_instruction_relative(0, &account_info)
556 );
557 }
558
559 #[test]
560 fn test_serialize_instructions() {
561 let program_id0 = Pubkey::new_unique();
562 let program_id1 = Pubkey::new_unique();
563 let id0 = Pubkey::new_unique();
564 let id1 = Pubkey::new_unique();
565 let id2 = Pubkey::new_unique();
566 let id3 = Pubkey::new_unique();
567 let params = vec![
568 MakeInstructionParams {
569 program_id: program_id0,
570 account_key: id0,
571 is_signer: false,
572 is_writable: true,
573 },
574 MakeInstructionParams {
575 program_id: program_id0,
576 account_key: id1,
577 is_signer: true,
578 is_writable: true,
579 },
580 MakeInstructionParams {
581 program_id: program_id1,
582 account_key: id2,
583 is_signer: false,
584 is_writable: false,
585 },
586 MakeInstructionParams {
587 program_id: program_id1,
588 account_key: id3,
589 is_signer: true,
590 is_writable: false,
591 },
592 ];
593 let instructions: Vec<Instruction> =
594 params.clone().into_iter().map(make_instruction).collect();
595 let borrowed_instructions: Vec<BorrowedInstruction> =
596 params.iter().map(make_borrowed_instruction).collect();
597
598 let serialized = serialize_instructions(&borrowed_instructions);
599
600 for (i, instruction) in instructions.iter().enumerate() {
602 assert_eq!(
603 deserialize_instruction(i, &serialized).unwrap(),
604 *instruction
605 );
606 }
607 }
608
609 #[test]
610 fn test_decompile_instructions_out_of_bounds() {
611 let program_id0 = Pubkey::new_unique();
612 let id0 = Pubkey::new_unique();
613 let id1 = Pubkey::new_unique();
614 let params = vec![
615 MakeInstructionParams {
616 program_id: program_id0,
617 account_key: id0,
618 is_signer: false,
619 is_writable: true,
620 },
621 MakeInstructionParams {
622 program_id: program_id0,
623 account_key: id1,
624 is_signer: true,
625 is_writable: true,
626 },
627 ];
628 let instructions: Vec<Instruction> =
629 params.clone().into_iter().map(make_instruction).collect();
630 let borrowed_instructions: Vec<BorrowedInstruction> =
631 params.iter().map(make_borrowed_instruction).collect();
632
633 let serialized = serialize_instructions(&borrowed_instructions);
634 assert_eq!(
635 deserialize_instruction(instructions.len(), &serialized).unwrap_err(),
636 SanitizeError::IndexOutOfBounds,
637 );
638 }
639}