1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//! Instruction creators for large instructions

use {
    num_enum::{IntoPrimitive, TryFromPrimitive},
    solana_program::{
        instruction::{AccountMeta, Instruction},
        program_error::ProgramError,
        pubkey::Pubkey,
        syscalls::{MAX_CPI_ACCOUNT_INFOS, MAX_CPI_INSTRUCTION_DATA_LEN},
    },
    std::{convert::TryInto, mem::size_of},
};

/// Instructions supported by the padding program, which takes in additional
/// account data or accounts and does nothing with them. It's meant for testing
/// larger transactions with bench-tps.
#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive)]
#[repr(u8)]
pub enum PadInstruction {
    /// Does no work, but accepts a large amount of data and accounts
    Noop,
    /// Wraps the provided instruction, calling the provided program via CPI
    ///
    /// Accounts expected by this instruction:
    ///
    /// * All accounts required for the inner instruction
    /// * The program invoked by the inner instruction
    /// * Additional padding accounts
    ///
    /// Data expected by this instruction:
    /// * WrapData
    Wrap,
}

/// Data wrapping any inner instruction
pub struct WrapData<'a> {
    /// Number of accounts required by the inner instruction
    pub num_accounts: u32,
    /// the size of the inner instruction data
    pub instruction_size: u32,
    /// actual inner instruction data
    pub instruction_data: &'a [u8],
    // additional padding bytes come after, not captured in this struct
}

const U32_BYTES: usize = 4;
fn unpack_u32(input: &[u8]) -> Result<(u32, &[u8]), ProgramError> {
    let value = input
        .get(..U32_BYTES)
        .and_then(|slice| slice.try_into().ok())
        .map(u32::from_le_bytes)
        .ok_or(ProgramError::InvalidInstructionData)?;
    Ok((value, &input[U32_BYTES..]))
}

impl<'a> WrapData<'a> {
    /// Unpacks instruction data
    pub fn unpack(data: &'a [u8]) -> Result<Self, ProgramError> {
        let (num_accounts, rest) = unpack_u32(data)?;
        let (instruction_size, rest) = unpack_u32(rest)?;

        let (instruction_data, _rest) = rest.split_at(instruction_size as usize);
        Ok(Self {
            num_accounts,
            instruction_size,
            instruction_data,
        })
    }
}

pub fn noop(
    program_id: Pubkey,
    padding_accounts: Vec<AccountMeta>,
    padding_data: u32,
) -> Result<Instruction, ProgramError> {
    let total_data_size = size_of::<u8>().saturating_add(padding_data as usize);
    // crude, but can find a potential issue right away
    if total_data_size > MAX_CPI_INSTRUCTION_DATA_LEN as usize {
        return Err(ProgramError::InvalidInstructionData);
    }
    let mut data = Vec::with_capacity(total_data_size);
    data.push(PadInstruction::Noop.into());
    for i in 0..padding_data {
        data.push(i.checked_rem(u8::MAX as u32).unwrap() as u8);
    }

    let num_accounts = padding_accounts.len().saturating_add(1);
    if num_accounts > MAX_CPI_ACCOUNT_INFOS {
        return Err(ProgramError::InvalidAccountData);
    }
    let mut accounts = Vec::with_capacity(num_accounts);
    accounts.extend(padding_accounts);

    Ok(Instruction {
        program_id,
        accounts,
        data,
    })
}

pub fn wrap_instruction(
    program_id: Pubkey,
    instruction: Instruction,
    padding_accounts: Vec<AccountMeta>,
    padding_data: u32,
) -> Result<Instruction, ProgramError> {
    let total_data_size = size_of::<u8>()
        .saturating_add(size_of::<u32>())
        .saturating_add(size_of::<u32>())
        .saturating_add(instruction.data.len())
        .saturating_add(padding_data as usize);
    // crude, but can find a potential issue right away
    if total_data_size > MAX_CPI_INSTRUCTION_DATA_LEN as usize {
        return Err(ProgramError::InvalidInstructionData);
    }
    let mut data = Vec::with_capacity(total_data_size);
    data.push(PadInstruction::Wrap.into());
    let num_accounts: u32 = instruction
        .accounts
        .len()
        .try_into()
        .map_err(|_| ProgramError::InvalidInstructionData)?;
    data.extend(num_accounts.to_le_bytes().iter());

    let data_size: u32 = instruction
        .data
        .len()
        .try_into()
        .map_err(|_| ProgramError::InvalidInstructionData)?;
    data.extend(data_size.to_le_bytes().iter());
    data.extend(instruction.data);
    for i in 0..padding_data {
        data.push(i.checked_rem(u8::MAX as u32).unwrap() as u8);
    }

    // The format for account data goes:
    // * accounts required for the CPI
    // * program account to call into
    // * additional accounts may be included as padding or to test loading / locks
    let num_accounts = instruction
        .accounts
        .len()
        .saturating_add(1)
        .saturating_add(padding_accounts.len());
    if num_accounts > MAX_CPI_ACCOUNT_INFOS {
        return Err(ProgramError::InvalidAccountData);
    }
    let mut accounts = Vec::with_capacity(num_accounts);
    accounts.extend(instruction.accounts);
    accounts.push(AccountMeta::new_readonly(instruction.program_id, false));
    accounts.extend(padding_accounts);

    Ok(Instruction {
        program_id,
        accounts,
        data,
    })
}