solana_program/sysvar/
instructions.rs

1#![allow(clippy::integer_arithmetic)]
2//! This account contains the serialized transaction instructions
3use 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
17// Instructions Sysvar, dummy type, use the associated helpers instead of the Sysvar trait
18pub struct Instructions();
19
20crate::declare_sysvar_id!("Sysvar1nstructions1111111111111111111111111", Instructions);
21
22// Construct the account data for the Instructions Sysvar
23#[cfg(not(target_os = "solana"))]
24pub fn construct_instructions_data(instructions: &[BorrowedInstruction]) -> Vec<u8> {
25    let mut data = serialize_instructions(instructions);
26    // add room for current instruction index.
27    data.resize(data.len() + 2, 0);
28
29    data
30}
31
32/// Borrowed version of AccountMeta
33pub struct BorrowedAccountMeta<'a> {
34    pub pubkey: &'a Pubkey,
35    pub is_signer: bool,
36    pub is_writable: bool,
37}
38
39/// Borrowed version of Instruction
40pub 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// First encode the number of instructions:
56// [0..2 - num_instructions
57//
58// Then a table of offsets of where to find them in the data
59//  3..2 * num_instructions table of instruction offsets
60//
61// Each instruction is then encoded as:
62//   0..2 - num_accounts
63//   2 - meta_byte -> (bit 0 signer, bit 1 is_writable)
64//   3..35 - pubkey - 32 bytes
65//   35..67 - program_id
66//   67..69 - data len - u16
67//   69..data_len - data
68#[cfg(not(target_os = "solana"))]
69fn serialize_instructions(instructions: &[BorrowedInstruction]) -> Vec<u8> {
70    // 64 bytes is a reasonable guess, calculating exactly is slower in benchmarks
71    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/// Load the current `Instruction`'s index in the currently executing
102/// `Transaction` from the Instructions Sysvar data
103#[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
114/// Load the current `Instruction`'s index in the currently executing
115/// `Transaction`
116pub 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
130/// Store the current `Instruction`'s index in the Instructions Sysvar data
131pub 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    // index into the instruction byte-offset table.
147    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/// Load an `Instruction` in the currently executing `Transaction` at the
181/// specified index
182#[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
190/// Load an `Instruction` in the currently executing `Transaction` at the
191/// specified index
192pub 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
207/// Returns the `Instruction` relative to the current `Instruction` in the
208/// currently executing `Transaction`
209pub 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        // assert that deserialize_instruction is compatible with SanitizedMessage::serialize_instructions
484        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}