|
| 1 | +use core::mem::MaybeUninit; |
| 2 | + |
1 | 3 | use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; |
2 | 4 |
|
3 | 5 | use crate::entrypoint::inner_process_instruction; |
4 | 6 |
|
5 | | -/// The size of the batch instruction header. |
6 | | -/// |
7 | | -/// The header of each instruction consists of two `u8` values: |
8 | | -/// * number of the accounts |
9 | | -/// * length of the instruction data |
10 | | -const IX_HEADER_SIZE: usize = 2; |
11 | | - |
12 | | -pub fn process_batch(mut accounts: &[AccountInfo], mut instruction_data: &[u8]) -> ProgramResult { |
13 | | - loop { |
14 | | - // Validates the instruction data and accounts offset. |
| 7 | +macro_rules! write_account { |
| 8 | + ( $index_source:expr, $source:ident, $index_target:literal, $target:ident ) => { |
| 9 | + // TODO: need to validate that the indices are within bounds. |
| 10 | + unsafe { |
| 11 | + $target |
| 12 | + .get_unchecked_mut($index_target) |
| 13 | + .write($source.get_unchecked($index_source).clone()) |
| 14 | + } |
| 15 | + }; |
| 16 | +} |
15 | 17 |
|
16 | | - if instruction_data.len() < IX_HEADER_SIZE { |
17 | | - // The instruction data must have at least two bytes. |
18 | | - return Err(ProgramError::InvalidInstructionData); |
| 18 | +macro_rules! fill_accounts { |
| 19 | + ( $indices:ident, $accounts:ident, $instruction_accounts:ident ) => { |
| 20 | + match $indices.len() { |
| 21 | + 1 => { |
| 22 | + write_account!($indices[0] as usize, $accounts, 0, $instruction_accounts); |
| 23 | + } |
| 24 | + 2 => { |
| 25 | + write_account!($indices[0] as usize, $accounts, 0, $instruction_accounts); |
| 26 | + write_account!($indices[1] as usize, $accounts, 1, $instruction_accounts); |
| 27 | + } |
| 28 | + 3 => { |
| 29 | + write_account!($indices[0] as usize, $accounts, 0, $instruction_accounts); |
| 30 | + write_account!($indices[1] as usize, $accounts, 1, $instruction_accounts); |
| 31 | + write_account!($indices[2] as usize, $accounts, 2, $instruction_accounts); |
| 32 | + } |
| 33 | + 4 => { |
| 34 | + write_account!($indices[0] as usize, $accounts, 0, $instruction_accounts); |
| 35 | + write_account!($indices[1] as usize, $accounts, 1, $instruction_accounts); |
| 36 | + write_account!($indices[2] as usize, $accounts, 2, $instruction_accounts); |
| 37 | + write_account!($indices[3] as usize, $accounts, 3, $instruction_accounts); |
| 38 | + } |
| 39 | + // TODO: Add more cases up to 15. |
| 40 | + _ => return Err(ProgramError::NotEnoughAccountKeys), |
19 | 41 | } |
| 42 | + }; |
| 43 | +} |
| 44 | + |
| 45 | +pub fn process_batch(accounts: &[AccountInfo], mut instruction_data: &[u8]) -> ProgramResult { |
| 46 | + const UNINIT_ACCOUNT: MaybeUninit<AccountInfo> = MaybeUninit::<AccountInfo>::uninit(); |
| 47 | + // Instructions take at most 15 accounts. |
| 48 | + let mut instruction_accounts: [MaybeUninit<AccountInfo>; 15] = [UNINIT_ACCOUNT; 15]; |
20 | 49 |
|
21 | | - // SAFETY: The instruction data is guaranteed to have at least two bytes (header) |
22 | | - // + one byte (discriminator). |
| 50 | + if instruction_data.is_empty() { |
| 51 | + return Err(ProgramError::InvalidInstructionData); |
| 52 | + } |
| 53 | + |
| 54 | + loop { |
23 | 55 | let expected_accounts = unsafe { *instruction_data.get_unchecked(0) as usize }; |
24 | | - let data_offset = IX_HEADER_SIZE + unsafe { *instruction_data.get_unchecked(1) as usize }; |
| 56 | + // There must be at least: |
| 57 | + // - 1 byte for the number of accounts. |
| 58 | + // - `expected_accounts` bytes for instruction accounts indices. |
| 59 | + // - 1 byte for the length of the instruction data. |
| 60 | + let data_offset = expected_accounts + 2; |
25 | 61 |
|
26 | | - if instruction_data.len() < data_offset || data_offset == 0 { |
| 62 | + if instruction_data.len() < data_offset { |
27 | 63 | return Err(ProgramError::InvalidInstructionData); |
28 | 64 | } |
29 | 65 |
|
30 | | - if accounts.len() < expected_accounts { |
31 | | - return Err(ProgramError::NotEnoughAccountKeys); |
32 | | - } |
| 66 | + let indices = unsafe { instruction_data.get_unchecked(1..1 + expected_accounts) }; |
| 67 | + fill_accounts!(indices, accounts, instruction_accounts); |
33 | 68 |
|
34 | | - // Process the instruction. |
| 69 | + let expected_data = |
| 70 | + data_offset + unsafe { *instruction_data.get_unchecked(data_offset - 1) as usize }; |
| 71 | + |
| 72 | + if instruction_data.len() < expected_data || expected_data == 0 { |
| 73 | + return Err(ProgramError::InvalidInstructionData); |
| 74 | + } |
35 | 75 |
|
36 | 76 | // SAFETY: The instruction data and accounts lengths are already validated so all |
37 | 77 | // the slices are guaranteed to be valid. |
38 | 78 | inner_process_instruction( |
39 | | - unsafe { accounts.get_unchecked(..expected_accounts) }, |
40 | | - unsafe { instruction_data.get_unchecked(IX_HEADER_SIZE + 1..data_offset) }, |
41 | | - unsafe { *instruction_data.get_unchecked(IX_HEADER_SIZE) }, |
| 79 | + unsafe { |
| 80 | + core::slice::from_raw_parts(instruction_accounts.as_ptr() as _, expected_accounts) |
| 81 | + }, |
| 82 | + unsafe { instruction_data.get_unchecked(data_offset + 1..expected_data) }, |
| 83 | + unsafe { *instruction_data.get_unchecked(data_offset) }, |
42 | 84 | )?; |
43 | 85 |
|
44 | | - if data_offset == instruction_data.len() { |
| 86 | + if expected_data == instruction_data.len() { |
45 | 87 | // The batch is complete. |
46 | 88 | break; |
47 | 89 | } |
48 | 90 |
|
49 | | - accounts = &accounts[expected_accounts..]; |
50 | | - instruction_data = &instruction_data[data_offset..]; |
| 91 | + instruction_data = &instruction_data[expected_data..]; |
51 | 92 | } |
52 | 93 |
|
53 | 94 | Ok(()) |
|
0 commit comments