use {
crate::{
bytes::{
advance_offset_for_array, advance_offset_for_type, check_remaining,
optimized_read_compressed_u16, read_byte, read_slice_data, read_type,
},
result::{Result, TransactionViewError},
},
core::fmt::{Debug, Formatter},
solana_hash::Hash,
solana_packet::PACKET_DATA_SIZE,
solana_pubkey::Pubkey,
solana_signature::Signature,
solana_svm_transaction::message_address_table_lookup::SVMMessageAddressTableLookup,
};
const MIN_SIZED_ATL: usize = {
core::mem::size_of::<Pubkey>() + 1 + 1 + 1 };
const MIN_SIZED_PACKET_WITH_ATLS: usize = {
1 + core::mem::size_of::<Signature>() + 1 + 3 + 1 + core::mem::size_of::<Pubkey>() + core::mem::size_of::<Hash>() + 1 + 1 };
const MAX_ATLS_PER_PACKET: u8 =
((PACKET_DATA_SIZE - MIN_SIZED_PACKET_WITH_ATLS) / MIN_SIZED_ATL) as u8;
#[derive(Debug)]
pub(crate) struct AddressTableLookupFrame {
pub(crate) num_address_table_lookups: u8,
pub(crate) offset: u16,
pub(crate) total_writable_lookup_accounts: u16,
pub(crate) total_readonly_lookup_accounts: u16,
}
impl AddressTableLookupFrame {
#[inline(always)]
pub(crate) fn try_new(bytes: &[u8], offset: &mut usize) -> Result<Self> {
const _: () = assert!(MAX_ATLS_PER_PACKET & 0b1000_0000 == 0);
let num_address_table_lookups = read_byte(bytes, offset)?;
if num_address_table_lookups > MAX_ATLS_PER_PACKET {
return Err(TransactionViewError::ParseError);
}
check_remaining(
bytes,
*offset,
MIN_SIZED_ATL.wrapping_mul(usize::from(num_address_table_lookups)),
)?;
let address_table_lookups_offset = *offset as u16;
const _: () =
assert!(u16::MAX as usize * MAX_ATLS_PER_PACKET as usize <= u32::MAX as usize);
let mut total_writable_lookup_accounts: u32 = 0;
let mut total_readonly_lookup_accounts: u32 = 0;
for _index in 0..num_address_table_lookups {
advance_offset_for_type::<Pubkey>(bytes, offset)?;
let num_write_accounts = optimized_read_compressed_u16(bytes, offset)?;
total_writable_lookup_accounts =
total_writable_lookup_accounts.wrapping_add(u32::from(num_write_accounts));
advance_offset_for_array::<u8>(bytes, offset, num_write_accounts)?;
let num_read_accounts = optimized_read_compressed_u16(bytes, offset)?;
total_readonly_lookup_accounts =
total_readonly_lookup_accounts.wrapping_add(u32::from(num_read_accounts));
advance_offset_for_array::<u8>(bytes, offset, num_read_accounts)?;
}
Ok(Self {
num_address_table_lookups,
offset: address_table_lookups_offset,
total_writable_lookup_accounts: u16::try_from(total_writable_lookup_accounts)
.map_err(|_| TransactionViewError::SanitizeError)?,
total_readonly_lookup_accounts: u16::try_from(total_readonly_lookup_accounts)
.map_err(|_| TransactionViewError::SanitizeError)?,
})
}
}
#[derive(Clone)]
pub struct AddressTableLookupIterator<'a> {
pub(crate) bytes: &'a [u8],
pub(crate) offset: usize,
pub(crate) num_address_table_lookups: u8,
pub(crate) index: u8,
}
impl<'a> Iterator for AddressTableLookupIterator<'a> {
type Item = SVMMessageAddressTableLookup<'a>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.num_address_table_lookups {
self.index = self.index.wrapping_add(1);
const _: () = assert!(core::mem::align_of::<Pubkey>() == 1, "Pubkey alignment");
let account_key = unsafe { read_type::<Pubkey>(self.bytes, &mut self.offset) }.ok()?;
let num_write_accounts =
optimized_read_compressed_u16(self.bytes, &mut self.offset).ok()?;
const _: () = assert!(core::mem::align_of::<u8>() == 1, "u8 alignment");
let writable_indexes =
unsafe { read_slice_data::<u8>(self.bytes, &mut self.offset, num_write_accounts) }
.ok()?;
let num_read_accounts =
optimized_read_compressed_u16(self.bytes, &mut self.offset).ok()?;
const _: () = assert!(core::mem::align_of::<u8>() == 1, "u8 alignment");
let readonly_indexes =
unsafe { read_slice_data::<u8>(self.bytes, &mut self.offset, num_read_accounts) }
.ok()?;
Some(SVMMessageAddressTableLookup {
account_key,
writable_indexes,
readonly_indexes,
})
} else {
None
}
}
}
impl ExactSizeIterator for AddressTableLookupIterator<'_> {
fn len(&self) -> usize {
usize::from(self.num_address_table_lookups.wrapping_sub(self.index))
}
}
impl Debug for AddressTableLookupIterator<'_> {
fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
f.debug_list().entries(self.clone()).finish()
}
}
#[cfg(test)]
mod tests {
use {super::*, solana_message::v0::MessageAddressTableLookup, solana_short_vec::ShortVec};
#[test]
fn test_zero_atls() {
let bytes = bincode::serialize(&ShortVec::<MessageAddressTableLookup>(vec![])).unwrap();
let mut offset = 0;
let frame = AddressTableLookupFrame::try_new(&bytes, &mut offset).unwrap();
assert_eq!(frame.num_address_table_lookups, 0);
assert_eq!(frame.offset, 1);
assert_eq!(offset, bytes.len());
assert_eq!(frame.total_writable_lookup_accounts, 0);
assert_eq!(frame.total_readonly_lookup_accounts, 0);
}
#[test]
fn test_length_too_high() {
let mut bytes = bincode::serialize(&ShortVec::<MessageAddressTableLookup>(vec![])).unwrap();
let mut offset = 0;
bytes[0] = 5;
assert!(AddressTableLookupFrame::try_new(&bytes, &mut offset).is_err());
}
#[test]
fn test_single_atl() {
let bytes = bincode::serialize(&ShortVec::<MessageAddressTableLookup>(vec![
MessageAddressTableLookup {
account_key: Pubkey::new_unique(),
writable_indexes: vec![1, 2, 3],
readonly_indexes: vec![4, 5, 6],
},
]))
.unwrap();
let mut offset = 0;
let frame = AddressTableLookupFrame::try_new(&bytes, &mut offset).unwrap();
assert_eq!(frame.num_address_table_lookups, 1);
assert_eq!(frame.offset, 1);
assert_eq!(offset, bytes.len());
assert_eq!(frame.total_writable_lookup_accounts, 3);
assert_eq!(frame.total_readonly_lookup_accounts, 3);
}
#[test]
fn test_multiple_atls() {
let bytes = bincode::serialize(&ShortVec::<MessageAddressTableLookup>(vec![
MessageAddressTableLookup {
account_key: Pubkey::new_unique(),
writable_indexes: vec![1, 2, 3],
readonly_indexes: vec![4, 5, 6],
},
MessageAddressTableLookup {
account_key: Pubkey::new_unique(),
writable_indexes: vec![1, 2, 3],
readonly_indexes: vec![4, 5],
},
]))
.unwrap();
let mut offset = 0;
let frame = AddressTableLookupFrame::try_new(&bytes, &mut offset).unwrap();
assert_eq!(frame.num_address_table_lookups, 2);
assert_eq!(frame.offset, 1);
assert_eq!(offset, bytes.len());
assert_eq!(frame.total_writable_lookup_accounts, 6);
assert_eq!(frame.total_readonly_lookup_accounts, 5);
}
#[test]
fn test_invalid_writable_indexes_vec() {
let mut bytes = bincode::serialize(&ShortVec(vec![MessageAddressTableLookup {
account_key: Pubkey::new_unique(),
writable_indexes: vec![1, 2, 3],
readonly_indexes: vec![4, 5, 6],
}]))
.unwrap();
bytes[33] = 127;
let mut offset = 0;
assert!(AddressTableLookupFrame::try_new(&bytes, &mut offset).is_err());
}
#[test]
fn test_invalid_readonly_indexes_vec() {
let mut bytes = bincode::serialize(&ShortVec(vec![MessageAddressTableLookup {
account_key: Pubkey::new_unique(),
writable_indexes: vec![1, 2, 3],
readonly_indexes: vec![4, 5, 6],
}]))
.unwrap();
bytes[37] = 127;
let mut offset = 0;
assert!(AddressTableLookupFrame::try_new(&bytes, &mut offset).is_err());
}
}