1#![allow(clippy::integer_arithmetic)]
2use crate::{
4 account_info::AccountInfo,
5 instruction::{AccountMeta, Instruction},
6 program_error::ProgramError,
7 pubkey::Pubkey,
8 sanitize::SanitizeError,
9 serialize_utils::{read_pubkey, read_slice, read_u16, read_u8},
10};
11#[cfg(not(target_os = "solana"))]
12use {
13 crate::serialize_utils::{append_slice, append_u16, append_u8},
14 bitflags::bitflags,
15};
16
17pub struct Instructions();
19
20crate::declare_sysvar_id!("Sysvar1nstructions1111111111111111111111111", Instructions);
21
22#[cfg(not(target_os = "solana"))]
24pub fn construct_instructions_data(instructions: &[BorrowedInstruction]) -> Vec<u8> {
25 let mut data = serialize_instructions(instructions);
26 data.resize(data.len() + 2, 0);
28
29 data
30}
31
32pub struct BorrowedAccountMeta<'a> {
34 pub pubkey: &'a Pubkey,
35 pub is_signer: bool,
36 pub is_writable: bool,
37}
38
39pub struct BorrowedInstruction<'a> {
41 pub program_id: &'a Pubkey,
42 pub accounts: Vec<BorrowedAccountMeta<'a>>,
43 pub data: &'a [u8],
44}
45
46#[cfg(not(target_os = "solana"))]
47bitflags! {
48 struct InstructionsSysvarAccountMeta: u8 {
49 const NONE = 0b00000000;
50 const IS_SIGNER = 0b00000001;
51 const IS_WRITABLE = 0b00000010;
52 }
53}
54
55#[cfg(not(target_os = "solana"))]
69fn serialize_instructions(instructions: &[BorrowedInstruction]) -> Vec<u8> {
70 let mut data = Vec::with_capacity(instructions.len() * (32 * 2));
72 append_u16(&mut data, instructions.len() as u16);
73 for _ in 0..instructions.len() {
74 append_u16(&mut data, 0);
75 }
76
77 for (i, instruction) in instructions.iter().enumerate() {
78 let start_instruction_offset = data.len() as u16;
79 let start = 2 + (2 * i);
80 data[start..start + 2].copy_from_slice(&start_instruction_offset.to_le_bytes());
81 append_u16(&mut data, instruction.accounts.len() as u16);
82 for account_meta in &instruction.accounts {
83 let mut account_meta_flags = InstructionsSysvarAccountMeta::NONE;
84 if account_meta.is_signer {
85 account_meta_flags |= InstructionsSysvarAccountMeta::IS_SIGNER;
86 }
87 if account_meta.is_writable {
88 account_meta_flags |= InstructionsSysvarAccountMeta::IS_WRITABLE;
89 }
90 append_u8(&mut data, account_meta_flags.bits());
91 append_slice(&mut data, account_meta.pubkey.as_ref());
92 }
93
94 append_slice(&mut data, instruction.program_id.as_ref());
95 append_u16(&mut data, instruction.data.len() as u16);
96 append_slice(&mut data, instruction.data);
97 }
98 data
99}
100
101#[deprecated(
104 since = "1.8.0",
105 note = "Unsafe because the sysvar accounts address is not checked, please use `load_current_index_checked` instead"
106)]
107pub fn load_current_index(data: &[u8]) -> u16 {
108 let mut instr_fixed_data = [0u8; 2];
109 let len = data.len();
110 instr_fixed_data.copy_from_slice(&data[len - 2..len]);
111 u16::from_le_bytes(instr_fixed_data)
112}
113
114pub fn load_current_index_checked(
117 instruction_sysvar_account_info: &AccountInfo,
118) -> Result<u16, ProgramError> {
119 if !check_id(instruction_sysvar_account_info.key) {
120 return Err(ProgramError::UnsupportedSysvar);
121 }
122
123 let instruction_sysvar = instruction_sysvar_account_info.try_borrow_data()?;
124 let mut instr_fixed_data = [0u8; 2];
125 let len = instruction_sysvar.len();
126 instr_fixed_data.copy_from_slice(&instruction_sysvar[len - 2..len]);
127 Ok(u16::from_le_bytes(instr_fixed_data))
128}
129
130pub fn store_current_index(data: &mut [u8], instruction_index: u16) {
132 let last_index = data.len() - 2;
133 data[last_index..last_index + 2].copy_from_slice(&instruction_index.to_le_bytes());
134}
135
136fn deserialize_instruction(index: usize, data: &[u8]) -> Result<Instruction, SanitizeError> {
137 const IS_SIGNER_BIT: usize = 0;
138 const IS_WRITABLE_BIT: usize = 1;
139
140 let mut current = 0;
141 let num_instructions = read_u16(&mut current, data)?;
142 if index >= num_instructions as usize {
143 return Err(SanitizeError::IndexOutOfBounds);
144 }
145
146 current += index * 2;
148 let start = read_u16(&mut current, data)?;
149
150 current = start as usize;
151 let num_accounts = read_u16(&mut current, data)?;
152 let mut accounts = Vec::with_capacity(num_accounts as usize);
153 for _ in 0..num_accounts {
154 let meta_byte = read_u8(&mut current, data)?;
155 let mut is_signer = false;
156 let mut is_writable = false;
157 if meta_byte & (1 << IS_SIGNER_BIT) != 0 {
158 is_signer = true;
159 }
160 if meta_byte & (1 << IS_WRITABLE_BIT) != 0 {
161 is_writable = true;
162 }
163 let pubkey = read_pubkey(&mut current, data)?;
164 accounts.push(AccountMeta {
165 pubkey,
166 is_signer,
167 is_writable,
168 });
169 }
170 let program_id = read_pubkey(&mut current, data)?;
171 let data_len = read_u16(&mut current, data)?;
172 let data = read_slice(&mut current, data, data_len as usize)?;
173 Ok(Instruction {
174 program_id,
175 accounts,
176 data,
177 })
178}
179
180#[deprecated(
183 since = "1.8.0",
184 note = "Unsafe because the sysvar accounts address is not checked, please use `load_instruction_at_checked` instead"
185)]
186pub fn load_instruction_at(index: usize, data: &[u8]) -> Result<Instruction, SanitizeError> {
187 deserialize_instruction(index, data)
188}
189
190pub fn load_instruction_at_checked(
193 index: usize,
194 instruction_sysvar_account_info: &AccountInfo,
195) -> Result<Instruction, ProgramError> {
196 if !check_id(instruction_sysvar_account_info.key) {
197 return Err(ProgramError::UnsupportedSysvar);
198 }
199
200 let instruction_sysvar = instruction_sysvar_account_info.try_borrow_data()?;
201 deserialize_instruction(index, &instruction_sysvar).map_err(|err| match err {
202 SanitizeError::IndexOutOfBounds => ProgramError::InvalidArgument,
203 _ => ProgramError::InvalidInstructionData,
204 })
205}
206
207pub fn get_instruction_relative(
210 index_relative_to_current: i64,
211 instruction_sysvar_account_info: &AccountInfo,
212) -> Result<Instruction, ProgramError> {
213 if !check_id(instruction_sysvar_account_info.key) {
214 return Err(ProgramError::UnsupportedSysvar);
215 }
216
217 let instruction_sysvar = instruction_sysvar_account_info.data.borrow();
218 #[allow(deprecated)]
219 let current_index = load_current_index(&instruction_sysvar) as i64;
220 let index = current_index.saturating_add(index_relative_to_current);
221 if index < 0 {
222 return Err(ProgramError::InvalidArgument);
223 }
224 #[allow(deprecated)]
225 load_instruction_at(
226 current_index.saturating_add(index_relative_to_current) as usize,
227 &instruction_sysvar,
228 )
229 .map_err(|err| match err {
230 SanitizeError::IndexOutOfBounds => ProgramError::InvalidArgument,
231 _ => ProgramError::InvalidInstructionData,
232 })
233}
234
235#[cfg(test)]
236mod tests {
237 use {
238 super::*,
239 crate::{
240 instruction::AccountMeta,
241 message::{Message as LegacyMessage, SanitizedMessage},
242 pubkey::Pubkey,
243 },
244 std::convert::TryFrom,
245 };
246
247 #[test]
248 fn test_load_store_instruction() {
249 let mut data = [4u8; 10];
250 store_current_index(&mut data, 3);
251 #[allow(deprecated)]
252 let index = load_current_index(&data);
253 assert_eq!(index, 3);
254 assert_eq!([4u8; 8], data[0..8]);
255 }
256
257 #[test]
258 fn test_load_instruction_at_checked() {
259 let instruction0 = Instruction::new_with_bincode(
260 Pubkey::new_unique(),
261 &0,
262 vec![AccountMeta::new(Pubkey::new_unique(), false)],
263 );
264 let instruction1 = Instruction::new_with_bincode(
265 Pubkey::new_unique(),
266 &0,
267 vec![AccountMeta::new(Pubkey::new_unique(), false)],
268 );
269 let sanitized_message = SanitizedMessage::try_from(LegacyMessage::new(
270 &[instruction0.clone(), instruction1.clone()],
271 Some(&Pubkey::new_unique()),
272 ))
273 .unwrap();
274
275 let key = id();
276 let mut lamports = 0;
277 let mut data = construct_instructions_data(&sanitized_message.decompile_instructions());
278 let owner = crate::sysvar::id();
279 let mut account_info = AccountInfo::new(
280 &key,
281 false,
282 false,
283 &mut lamports,
284 &mut data,
285 &owner,
286 false,
287 0,
288 );
289
290 assert_eq!(
291 instruction0,
292 load_instruction_at_checked(0, &account_info).unwrap()
293 );
294 assert_eq!(
295 instruction1,
296 load_instruction_at_checked(1, &account_info).unwrap()
297 );
298 assert_eq!(
299 Err(ProgramError::InvalidArgument),
300 load_instruction_at_checked(2, &account_info)
301 );
302
303 let key = Pubkey::new_unique();
304 account_info.key = &key;
305 assert_eq!(
306 Err(ProgramError::UnsupportedSysvar),
307 load_instruction_at_checked(2, &account_info)
308 );
309 }
310
311 #[test]
312 fn test_load_current_index_checked() {
313 let instruction0 = Instruction::new_with_bincode(
314 Pubkey::new_unique(),
315 &0,
316 vec![AccountMeta::new(Pubkey::new_unique(), false)],
317 );
318 let instruction1 = Instruction::new_with_bincode(
319 Pubkey::new_unique(),
320 &0,
321 vec![AccountMeta::new(Pubkey::new_unique(), false)],
322 );
323 let sanitized_message = SanitizedMessage::try_from(LegacyMessage::new(
324 &[instruction0, instruction1],
325 Some(&Pubkey::new_unique()),
326 ))
327 .unwrap();
328
329 let key = id();
330 let mut lamports = 0;
331 let mut data = construct_instructions_data(&sanitized_message.decompile_instructions());
332 store_current_index(&mut data, 1);
333 let owner = crate::sysvar::id();
334 let mut account_info = AccountInfo::new(
335 &key,
336 false,
337 false,
338 &mut lamports,
339 &mut data,
340 &owner,
341 false,
342 0,
343 );
344
345 assert_eq!(1, load_current_index_checked(&account_info).unwrap());
346 {
347 let mut data = account_info.try_borrow_mut_data().unwrap();
348 store_current_index(&mut data, 0);
349 }
350 assert_eq!(0, load_current_index_checked(&account_info).unwrap());
351
352 let key = Pubkey::new_unique();
353 account_info.key = &key;
354 assert_eq!(
355 Err(ProgramError::UnsupportedSysvar),
356 load_current_index_checked(&account_info)
357 );
358 }
359
360 #[test]
361 fn test_get_instruction_relative() {
362 let instruction0 = Instruction::new_with_bincode(
363 Pubkey::new_unique(),
364 &0,
365 vec![AccountMeta::new(Pubkey::new_unique(), false)],
366 );
367 let instruction1 = Instruction::new_with_bincode(
368 Pubkey::new_unique(),
369 &0,
370 vec![AccountMeta::new(Pubkey::new_unique(), false)],
371 );
372 let instruction2 = Instruction::new_with_bincode(
373 Pubkey::new_unique(),
374 &0,
375 vec![AccountMeta::new(Pubkey::new_unique(), false)],
376 );
377 let sanitized_message = SanitizedMessage::try_from(LegacyMessage::new(
378 &[
379 instruction0.clone(),
380 instruction1.clone(),
381 instruction2.clone(),
382 ],
383 Some(&Pubkey::new_unique()),
384 ))
385 .unwrap();
386
387 let key = id();
388 let mut lamports = 0;
389 let mut data = construct_instructions_data(&sanitized_message.decompile_instructions());
390 store_current_index(&mut data, 1);
391 let owner = crate::sysvar::id();
392 let mut account_info = AccountInfo::new(
393 &key,
394 false,
395 false,
396 &mut lamports,
397 &mut data,
398 &owner,
399 false,
400 0,
401 );
402
403 assert_eq!(
404 Err(ProgramError::InvalidArgument),
405 get_instruction_relative(-2, &account_info)
406 );
407 assert_eq!(
408 instruction0,
409 get_instruction_relative(-1, &account_info).unwrap()
410 );
411 assert_eq!(
412 instruction1,
413 get_instruction_relative(0, &account_info).unwrap()
414 );
415 assert_eq!(
416 instruction2,
417 get_instruction_relative(1, &account_info).unwrap()
418 );
419 assert_eq!(
420 Err(ProgramError::InvalidArgument),
421 get_instruction_relative(2, &account_info)
422 );
423 {
424 let mut data = account_info.try_borrow_mut_data().unwrap();
425 store_current_index(&mut data, 0);
426 }
427 assert_eq!(
428 Err(ProgramError::InvalidArgument),
429 get_instruction_relative(-1, &account_info)
430 );
431 assert_eq!(
432 instruction0,
433 get_instruction_relative(0, &account_info).unwrap()
434 );
435 assert_eq!(
436 instruction1,
437 get_instruction_relative(1, &account_info).unwrap()
438 );
439 assert_eq!(
440 instruction2,
441 get_instruction_relative(2, &account_info).unwrap()
442 );
443 assert_eq!(
444 Err(ProgramError::InvalidArgument),
445 get_instruction_relative(3, &account_info)
446 );
447
448 let key = Pubkey::new_unique();
449 account_info.key = &key;
450 assert_eq!(
451 Err(ProgramError::UnsupportedSysvar),
452 get_instruction_relative(0, &account_info)
453 );
454 }
455
456 #[test]
457 fn test_serialize_instructions() {
458 let program_id0 = Pubkey::new_unique();
459 let program_id1 = Pubkey::new_unique();
460 let id0 = Pubkey::new_unique();
461 let id1 = Pubkey::new_unique();
462 let id2 = Pubkey::new_unique();
463 let id3 = Pubkey::new_unique();
464 let instructions = vec![
465 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
466 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, true)]),
467 Instruction::new_with_bincode(
468 program_id1,
469 &0,
470 vec![AccountMeta::new_readonly(id2, false)],
471 ),
472 Instruction::new_with_bincode(
473 program_id1,
474 &0,
475 vec![AccountMeta::new_readonly(id3, true)],
476 ),
477 ];
478
479 let message = LegacyMessage::new(&instructions, Some(&id1));
480 let sanitized_message = SanitizedMessage::try_from(message).unwrap();
481 let serialized = serialize_instructions(&sanitized_message.decompile_instructions());
482
483 for (i, instruction) in instructions.iter().enumerate() {
485 assert_eq!(
486 deserialize_instruction(i, &serialized).unwrap(),
487 *instruction
488 );
489 }
490 }
491
492 #[test]
493 fn test_decompile_instructions_out_of_bounds() {
494 let program_id0 = Pubkey::new_unique();
495 let id0 = Pubkey::new_unique();
496 let id1 = Pubkey::new_unique();
497 let instructions = vec![
498 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
499 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, true)]),
500 ];
501
502 let message =
503 SanitizedMessage::try_from(LegacyMessage::new(&instructions, Some(&id1))).unwrap();
504 let serialized = serialize_instructions(&message.decompile_instructions());
505 assert_eq!(
506 deserialize_instruction(instructions.len(), &serialized).unwrap_err(),
507 SanitizeError::IndexOutOfBounds,
508 );
509 }
510}