spl_instruction_padding/
instruction.rs

1//! Instruction creators for large instructions
2
3use {
4    num_enum::{IntoPrimitive, TryFromPrimitive},
5    miraland_program::{
6        instruction::{AccountMeta, Instruction},
7        program_error::ProgramError,
8        pubkey::Pubkey,
9        syscalls::{MAX_CPI_ACCOUNT_INFOS, MAX_CPI_INSTRUCTION_DATA_LEN},
10    },
11    std::{convert::TryInto, mem::size_of},
12};
13
14/// Instructions supported by the padding program, which takes in additional
15/// account data or accounts and does nothing with them. It's meant for testing
16/// larger transactions with bench-tps.
17#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive)]
18#[repr(u8)]
19pub enum PadInstruction {
20    /// Does no work, but accepts a large amount of data and accounts
21    Noop,
22    /// Wraps the provided instruction, calling the provided program via CPI
23    ///
24    /// Accounts expected by this instruction:
25    ///
26    /// * All accounts required for the inner instruction
27    /// * The program invoked by the inner instruction
28    /// * Additional padding accounts
29    ///
30    /// Data expected by this instruction:
31    /// * WrapData
32    Wrap,
33}
34
35/// Data wrapping any inner instruction
36pub struct WrapData<'a> {
37    /// Number of accounts required by the inner instruction
38    pub num_accounts: u32,
39    /// the size of the inner instruction data
40    pub instruction_size: u32,
41    /// actual inner instruction data
42    pub instruction_data: &'a [u8],
43    // additional padding bytes come after, not captured in this struct
44}
45
46const U32_BYTES: usize = 4;
47fn unpack_u32(input: &[u8]) -> Result<(u32, &[u8]), ProgramError> {
48    let value = input
49        .get(..U32_BYTES)
50        .and_then(|slice| slice.try_into().ok())
51        .map(u32::from_le_bytes)
52        .ok_or(ProgramError::InvalidInstructionData)?;
53    Ok((value, &input[U32_BYTES..]))
54}
55
56impl<'a> WrapData<'a> {
57    /// Unpacks instruction data
58    pub fn unpack(data: &'a [u8]) -> Result<Self, ProgramError> {
59        let (num_accounts, rest) = unpack_u32(data)?;
60        let (instruction_size, rest) = unpack_u32(rest)?;
61
62        let (instruction_data, _rest) = rest.split_at(instruction_size as usize);
63        Ok(Self {
64            num_accounts,
65            instruction_size,
66            instruction_data,
67        })
68    }
69}
70
71pub fn noop(
72    program_id: Pubkey,
73    padding_accounts: Vec<AccountMeta>,
74    padding_data: u32,
75) -> Result<Instruction, ProgramError> {
76    let total_data_size = size_of::<u8>().saturating_add(padding_data as usize);
77    // crude, but can find a potential issue right away
78    if total_data_size > MAX_CPI_INSTRUCTION_DATA_LEN as usize {
79        return Err(ProgramError::InvalidInstructionData);
80    }
81    let mut data = Vec::with_capacity(total_data_size);
82    data.push(PadInstruction::Noop.into());
83    for i in 0..padding_data {
84        data.push(i.checked_rem(u8::MAX as u32).unwrap() as u8);
85    }
86
87    let num_accounts = padding_accounts.len().saturating_add(1);
88    if num_accounts > MAX_CPI_ACCOUNT_INFOS {
89        return Err(ProgramError::InvalidAccountData);
90    }
91    let mut accounts = Vec::with_capacity(num_accounts);
92    accounts.extend(padding_accounts);
93
94    Ok(Instruction {
95        program_id,
96        accounts,
97        data,
98    })
99}
100
101pub fn wrap_instruction(
102    program_id: Pubkey,
103    instruction: Instruction,
104    padding_accounts: Vec<AccountMeta>,
105    padding_data: u32,
106) -> Result<Instruction, ProgramError> {
107    let total_data_size = size_of::<u8>()
108        .saturating_add(size_of::<u32>())
109        .saturating_add(size_of::<u32>())
110        .saturating_add(instruction.data.len())
111        .saturating_add(padding_data as usize);
112    // crude, but can find a potential issue right away
113    if total_data_size > MAX_CPI_INSTRUCTION_DATA_LEN as usize {
114        return Err(ProgramError::InvalidInstructionData);
115    }
116    let mut data = Vec::with_capacity(total_data_size);
117    data.push(PadInstruction::Wrap.into());
118    let num_accounts: u32 = instruction
119        .accounts
120        .len()
121        .try_into()
122        .map_err(|_| ProgramError::InvalidInstructionData)?;
123    data.extend(num_accounts.to_le_bytes().iter());
124
125    let data_size: u32 = instruction
126        .data
127        .len()
128        .try_into()
129        .map_err(|_| ProgramError::InvalidInstructionData)?;
130    data.extend(data_size.to_le_bytes().iter());
131    data.extend(instruction.data);
132    for i in 0..padding_data {
133        data.push(i.checked_rem(u8::MAX as u32).unwrap() as u8);
134    }
135
136    // The format for account data goes:
137    // * accounts required for the CPI
138    // * program account to call into
139    // * additional accounts may be included as padding or to test loading / locks
140    let num_accounts = instruction
141        .accounts
142        .len()
143        .saturating_add(1)
144        .saturating_add(padding_accounts.len());
145    if num_accounts > MAX_CPI_ACCOUNT_INFOS {
146        return Err(ProgramError::InvalidAccountData);
147    }
148    let mut accounts = Vec::with_capacity(num_accounts);
149    accounts.extend(instruction.accounts);
150    accounts.push(AccountMeta::new_readonly(instruction.program_id, false));
151    accounts.extend(padding_accounts);
152
153    Ok(Instruction {
154        program_id,
155        accounts,
156        data,
157    })
158}