use {
crate::{
bytes::{
advance_offset_for_array, check_remaining, optimized_read_compressed_u16, read_byte,
unchecked_read_byte, unchecked_read_slice_data,
},
result::Result,
},
core::fmt::{Debug, Formatter},
solana_svm_transaction::instruction::SVMInstruction,
};
#[derive(Debug, Default)]
pub(crate) struct InstructionsFrame {
pub(crate) num_instructions: u16,
pub(crate) offset: u16,
pub(crate) frames: Vec<InstructionFrame>,
}
#[derive(Debug)]
pub(crate) struct InstructionFrame {
num_accounts: u16,
data_len: u16,
num_accounts_len: u8, data_len_len: u8, }
impl InstructionsFrame {
#[inline(always)]
pub(crate) fn try_new(bytes: &[u8], offset: &mut usize) -> Result<Self> {
let num_instructions = optimized_read_compressed_u16(bytes, offset)?;
check_remaining(
bytes,
*offset,
3usize.wrapping_mul(usize::from(num_instructions)),
)?;
let instructions_offset = *offset as u16;
let mut frames = Vec::with_capacity(usize::from(num_instructions));
for _index in 0..num_instructions {
let _program_id_index = read_byte(bytes, offset)?;
let num_accounts_offset = *offset;
let num_accounts = optimized_read_compressed_u16(bytes, offset)?;
let num_accounts_len = offset.wrapping_sub(num_accounts_offset) as u8;
advance_offset_for_array::<u8>(bytes, offset, num_accounts)?;
let data_len_offset = *offset;
let data_len = optimized_read_compressed_u16(bytes, offset)?;
let data_len_len = offset.wrapping_sub(data_len_offset) as u8;
advance_offset_for_array::<u8>(bytes, offset, data_len)?;
frames.push(InstructionFrame {
num_accounts,
num_accounts_len,
data_len,
data_len_len,
});
}
Ok(Self {
num_instructions,
offset: instructions_offset,
frames,
})
}
}
#[derive(Clone)]
pub struct InstructionsIterator<'a> {
pub(crate) bytes: &'a [u8],
pub(crate) offset: usize,
pub(crate) num_instructions: u16,
pub(crate) index: u16,
pub(crate) frames: &'a [InstructionFrame],
}
impl<'a> Iterator for InstructionsIterator<'a> {
type Item = SVMInstruction<'a>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.num_instructions {
let InstructionFrame {
num_accounts,
num_accounts_len,
data_len,
data_len_len,
} = self.frames[usize::from(self.index)];
self.index = self.index.wrapping_add(1);
let program_id_index = unsafe { unchecked_read_byte(self.bytes, &mut self.offset) };
self.offset = self.offset.wrapping_add(usize::from(num_accounts_len));
const _: () = assert!(core::mem::align_of::<u8>() == 1, "u8 alignment");
let accounts = unsafe {
unchecked_read_slice_data::<u8>(self.bytes, &mut self.offset, num_accounts)
};
self.offset = self.offset.wrapping_add(usize::from(data_len_len));
const _: () = assert!(core::mem::align_of::<u8>() == 1, "u8 alignment");
let data =
unsafe { unchecked_read_slice_data::<u8>(self.bytes, &mut self.offset, data_len) };
Some(SVMInstruction {
program_id_index,
accounts,
data,
})
} else {
None
}
}
}
impl ExactSizeIterator for InstructionsIterator<'_> {
fn len(&self) -> usize {
usize::from(self.num_instructions.wrapping_sub(self.index))
}
}
impl Debug for InstructionsIterator<'_> {
fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
f.debug_list().entries(self.clone()).finish()
}
}
#[cfg(test)]
mod tests {
use {
super::*, solana_message::compiled_instruction::CompiledInstruction,
solana_short_vec::ShortVec,
};
#[test]
fn test_zero_instructions() {
let bytes = bincode::serialize(&ShortVec(Vec::<CompiledInstruction>::new())).unwrap();
let mut offset = 0;
let instructions_frame = InstructionsFrame::try_new(&bytes, &mut offset).unwrap();
assert_eq!(instructions_frame.num_instructions, 0);
assert_eq!(instructions_frame.offset, 1);
assert_eq!(offset, bytes.len());
}
#[test]
fn test_num_instructions_too_high() {
let mut bytes = bincode::serialize(&ShortVec(vec![CompiledInstruction {
program_id_index: 0,
accounts: vec![],
data: vec![],
}]))
.unwrap();
bytes[0] = 0x02;
let mut offset = 0;
assert!(InstructionsFrame::try_new(&bytes, &mut offset).is_err());
}
#[test]
fn test_single_instruction() {
let bytes = bincode::serialize(&ShortVec(vec![CompiledInstruction {
program_id_index: 0,
accounts: vec![1, 2, 3],
data: vec![4, 5, 6, 7, 8, 9, 10],
}]))
.unwrap();
let mut offset = 0;
let instructions_frame = InstructionsFrame::try_new(&bytes, &mut offset).unwrap();
assert_eq!(instructions_frame.num_instructions, 1);
assert_eq!(instructions_frame.offset, 1);
assert_eq!(offset, bytes.len());
}
#[test]
fn test_multiple_instructions() {
let bytes = bincode::serialize(&ShortVec(vec![
CompiledInstruction {
program_id_index: 0,
accounts: vec![1, 2, 3],
data: vec![4, 5, 6, 7, 8, 9, 10],
},
CompiledInstruction {
program_id_index: 1,
accounts: vec![4, 5, 6],
data: vec![7, 8, 9, 10, 11, 12, 13],
},
]))
.unwrap();
let mut offset = 0;
let instructions_frame = InstructionsFrame::try_new(&bytes, &mut offset).unwrap();
assert_eq!(instructions_frame.num_instructions, 2);
assert_eq!(instructions_frame.offset, 1);
assert_eq!(offset, bytes.len());
}
#[test]
fn test_invalid_instruction_accounts_vec() {
let mut bytes = bincode::serialize(&ShortVec(vec![CompiledInstruction {
program_id_index: 0,
accounts: vec![1, 2, 3],
data: vec![4, 5, 6, 7, 8, 9, 10],
}]))
.unwrap();
bytes[2] = 127;
let mut offset = 0;
assert!(InstructionsFrame::try_new(&bytes, &mut offset).is_err());
}
#[test]
fn test_invalid_instruction_data_vec() {
let mut bytes = bincode::serialize(&ShortVec(vec![CompiledInstruction {
program_id_index: 0,
accounts: vec![1, 2, 3],
data: vec![4, 5, 6, 7, 8, 9, 10],
}]))
.unwrap();
bytes[6] = 127;
let mut offset = 0;
assert!(InstructionsFrame::try_new(&bytes, &mut offset).is_err());
}
}