clone_spl_instruction_padding/
instruction.rs

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