1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
31#![allow(clippy::arithmetic_side_effects)]
32
33pub use clone_solana_sdk_ids::sysvar::instructions::{check_id, id, ID};
34#[cfg(feature = "dev-context-only-utils")]
35use qualifier_attr::qualifiers;
36#[cfg(not(target_os = "solana"))]
37use {
38 bitflags::bitflags,
39 clone_solana_instruction::BorrowedInstruction,
40 clone_solana_serialize_utils::{append_slice, append_u16, append_u8},
41};
42use {
43 clone_solana_account_info::AccountInfo,
44 clone_solana_instruction::{AccountMeta, Instruction},
45 clone_solana_program_error::ProgramError,
46 clone_solana_sanitize::SanitizeError,
47 clone_solana_serialize_utils::{read_pubkey, read_slice, read_u16, read_u8},
48};
49
50pub struct Instructions();
61
62clone_solana_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 clone_solana_account_info::AccountInfo,
282 clone_solana_instruction::{
283 AccountMeta, BorrowedAccountMeta, BorrowedInstruction, Instruction,
284 },
285 clone_solana_program_error::ProgramError,
286 clone_solana_pubkey::Pubkey,
287 clone_solana_sanitize::SanitizeError,
288 clone_solana_sdk_ids::sysvar::instructions::id,
289 };
290
291 #[test]
292 fn test_load_store_instruction() {
293 let mut data = [4u8; 10];
294 store_current_index(&mut data, 3);
295 #[allow(deprecated)]
296 let index = load_current_index(&data);
297 assert_eq!(index, 3);
298 assert_eq!([4u8; 8], data[0..8]);
299 }
300
301 #[derive(Copy, Clone)]
302 struct MakeInstructionParams {
303 program_id: Pubkey,
304 account_key: Pubkey,
305 is_signer: bool,
306 is_writable: bool,
307 }
308
309 fn make_borrowed_instruction(params: &MakeInstructionParams) -> BorrowedInstruction {
310 let MakeInstructionParams {
311 program_id,
312 account_key,
313 is_signer,
314 is_writable,
315 } = params;
316 BorrowedInstruction {
317 program_id,
318 accounts: vec![BorrowedAccountMeta {
319 pubkey: account_key,
320 is_signer: *is_signer,
321 is_writable: *is_writable,
322 }],
323 data: &[0],
324 }
325 }
326
327 fn make_instruction(params: MakeInstructionParams) -> Instruction {
328 let MakeInstructionParams {
329 program_id,
330 account_key,
331 is_signer,
332 is_writable,
333 } = params;
334 Instruction {
335 program_id,
336 accounts: vec![AccountMeta {
337 pubkey: account_key,
338 is_signer,
339 is_writable,
340 }],
341 data: vec![0],
342 }
343 }
344
345 #[test]
346 fn test_load_instruction_at_checked() {
347 let program_id0 = Pubkey::new_unique();
348 let program_id1 = Pubkey::new_unique();
349 let account_key0 = Pubkey::new_unique();
350 let account_key1 = Pubkey::new_unique();
351 let params0 = MakeInstructionParams {
352 program_id: program_id0,
353 account_key: account_key0,
354 is_signer: false,
355 is_writable: false,
356 };
357 let params1 = MakeInstructionParams {
358 program_id: program_id1,
359 account_key: account_key1,
360 is_signer: false,
361 is_writable: false,
362 };
363 let instruction0 = make_instruction(params0);
364 let instruction1 = make_instruction(params1);
365 let borrowed_instruction0 = make_borrowed_instruction(¶ms0);
366 let borrowed_instruction1 = make_borrowed_instruction(¶ms1);
367 let key = id();
368 let mut lamports = 0;
369 let mut data = construct_instructions_data(&[borrowed_instruction0, borrowed_instruction1]);
370 let owner = clone_solana_sdk_ids::sysvar::id();
371 let mut account_info = AccountInfo::new(
372 &key,
373 false,
374 false,
375 &mut lamports,
376 &mut data,
377 &owner,
378 false,
379 0,
380 );
381
382 assert_eq!(
383 instruction0,
384 load_instruction_at_checked(0, &account_info).unwrap()
385 );
386 assert_eq!(
387 instruction1,
388 load_instruction_at_checked(1, &account_info).unwrap()
389 );
390 assert_eq!(
391 Err(ProgramError::InvalidArgument),
392 load_instruction_at_checked(2, &account_info)
393 );
394
395 let key = Pubkey::new_unique();
396 account_info.key = &key;
397 assert_eq!(
398 Err(ProgramError::UnsupportedSysvar),
399 load_instruction_at_checked(2, &account_info)
400 );
401 }
402
403 #[test]
404 fn test_load_current_index_checked() {
405 let program_id0 = Pubkey::new_unique();
406 let program_id1 = Pubkey::new_unique();
407 let account_key0 = Pubkey::new_unique();
408 let account_key1 = Pubkey::new_unique();
409 let params0 = MakeInstructionParams {
410 program_id: program_id0,
411 account_key: account_key0,
412 is_signer: false,
413 is_writable: false,
414 };
415 let params1 = MakeInstructionParams {
416 program_id: program_id1,
417 account_key: account_key1,
418 is_signer: false,
419 is_writable: false,
420 };
421 let borrowed_instruction0 = make_borrowed_instruction(¶ms0);
422 let borrowed_instruction1 = make_borrowed_instruction(¶ms1);
423
424 let key = id();
425 let mut lamports = 0;
426 let mut data = construct_instructions_data(&[borrowed_instruction0, borrowed_instruction1]);
427 store_current_index(&mut data, 1);
428 let owner = clone_solana_sdk_ids::sysvar::id();
429 let mut account_info = AccountInfo::new(
430 &key,
431 false,
432 false,
433 &mut lamports,
434 &mut data,
435 &owner,
436 false,
437 0,
438 );
439
440 assert_eq!(1, load_current_index_checked(&account_info).unwrap());
441 {
442 let mut data = account_info.try_borrow_mut_data().unwrap();
443 store_current_index(&mut data, 0);
444 }
445 assert_eq!(0, load_current_index_checked(&account_info).unwrap());
446
447 let key = Pubkey::new_unique();
448 account_info.key = &key;
449 assert_eq!(
450 Err(ProgramError::UnsupportedSysvar),
451 load_current_index_checked(&account_info)
452 );
453 }
454
455 #[test]
456 fn test_get_instruction_relative() {
457 let program_id0 = Pubkey::new_unique();
458 let program_id1 = Pubkey::new_unique();
459 let program_id2 = Pubkey::new_unique();
460 let account_key0 = Pubkey::new_unique();
461 let account_key1 = Pubkey::new_unique();
462 let account_key2 = Pubkey::new_unique();
463 let params0 = MakeInstructionParams {
464 program_id: program_id0,
465 account_key: account_key0,
466 is_signer: false,
467 is_writable: false,
468 };
469 let params1 = MakeInstructionParams {
470 program_id: program_id1,
471 account_key: account_key1,
472 is_signer: false,
473 is_writable: false,
474 };
475 let params2 = MakeInstructionParams {
476 program_id: program_id2,
477 account_key: account_key2,
478 is_signer: false,
479 is_writable: false,
480 };
481 let instruction0 = make_instruction(params0);
482 let instruction1 = make_instruction(params1);
483 let instruction2 = make_instruction(params2);
484 let borrowed_instruction0 = make_borrowed_instruction(¶ms0);
485 let borrowed_instruction1 = make_borrowed_instruction(¶ms1);
486 let borrowed_instruction2 = make_borrowed_instruction(¶ms2);
487
488 let key = id();
489 let mut lamports = 0;
490 let mut data = construct_instructions_data(&[
491 borrowed_instruction0,
492 borrowed_instruction1,
493 borrowed_instruction2,
494 ]);
495 store_current_index(&mut data, 1);
496 let owner = clone_solana_sdk_ids::sysvar::id();
497 let mut account_info = AccountInfo::new(
498 &key,
499 false,
500 false,
501 &mut lamports,
502 &mut data,
503 &owner,
504 false,
505 0,
506 );
507
508 assert_eq!(
509 Err(ProgramError::InvalidArgument),
510 get_instruction_relative(-2, &account_info)
511 );
512 assert_eq!(
513 instruction0,
514 get_instruction_relative(-1, &account_info).unwrap()
515 );
516 assert_eq!(
517 instruction1,
518 get_instruction_relative(0, &account_info).unwrap()
519 );
520 assert_eq!(
521 instruction2,
522 get_instruction_relative(1, &account_info).unwrap()
523 );
524 assert_eq!(
525 Err(ProgramError::InvalidArgument),
526 get_instruction_relative(2, &account_info)
527 );
528 {
529 let mut data = account_info.try_borrow_mut_data().unwrap();
530 store_current_index(&mut data, 0);
531 }
532 assert_eq!(
533 Err(ProgramError::InvalidArgument),
534 get_instruction_relative(-1, &account_info)
535 );
536 assert_eq!(
537 instruction0,
538 get_instruction_relative(0, &account_info).unwrap()
539 );
540 assert_eq!(
541 instruction1,
542 get_instruction_relative(1, &account_info).unwrap()
543 );
544 assert_eq!(
545 instruction2,
546 get_instruction_relative(2, &account_info).unwrap()
547 );
548 assert_eq!(
549 Err(ProgramError::InvalidArgument),
550 get_instruction_relative(3, &account_info)
551 );
552
553 let key = Pubkey::new_unique();
554 account_info.key = &key;
555 assert_eq!(
556 Err(ProgramError::UnsupportedSysvar),
557 get_instruction_relative(0, &account_info)
558 );
559 }
560
561 #[test]
562 fn test_serialize_instructions() {
563 let program_id0 = Pubkey::new_unique();
564 let program_id1 = Pubkey::new_unique();
565 let id0 = Pubkey::new_unique();
566 let id1 = Pubkey::new_unique();
567 let id2 = Pubkey::new_unique();
568 let id3 = Pubkey::new_unique();
569 let params = vec![
570 MakeInstructionParams {
571 program_id: program_id0,
572 account_key: id0,
573 is_signer: false,
574 is_writable: true,
575 },
576 MakeInstructionParams {
577 program_id: program_id0,
578 account_key: id1,
579 is_signer: true,
580 is_writable: true,
581 },
582 MakeInstructionParams {
583 program_id: program_id1,
584 account_key: id2,
585 is_signer: false,
586 is_writable: false,
587 },
588 MakeInstructionParams {
589 program_id: program_id1,
590 account_key: id3,
591 is_signer: true,
592 is_writable: false,
593 },
594 ];
595 let instructions: Vec<Instruction> =
596 params.clone().into_iter().map(make_instruction).collect();
597 let borrowed_instructions: Vec<BorrowedInstruction> =
598 params.iter().map(make_borrowed_instruction).collect();
599
600 let serialized = serialize_instructions(&borrowed_instructions);
601
602 for (i, instruction) in instructions.iter().enumerate() {
604 assert_eq!(
605 deserialize_instruction(i, &serialized).unwrap(),
606 *instruction
607 );
608 }
609 }
610
611 #[test]
612 fn test_decompile_instructions_out_of_bounds() {
613 let program_id0 = Pubkey::new_unique();
614 let id0 = Pubkey::new_unique();
615 let id1 = Pubkey::new_unique();
616 let params = vec![
617 MakeInstructionParams {
618 program_id: program_id0,
619 account_key: id0,
620 is_signer: false,
621 is_writable: true,
622 },
623 MakeInstructionParams {
624 program_id: program_id0,
625 account_key: id1,
626 is_signer: true,
627 is_writable: true,
628 },
629 ];
630 let instructions: Vec<Instruction> =
631 params.clone().into_iter().map(make_instruction).collect();
632 let borrowed_instructions: Vec<BorrowedInstruction> =
633 params.iter().map(make_borrowed_instruction).collect();
634
635 let serialized = serialize_instructions(&borrowed_instructions);
636 assert_eq!(
637 deserialize_instruction(instructions.len(), &serialized).unwrap_err(),
638 SanitizeError::IndexOutOfBounds,
639 );
640 }
641}