use {
crate::{
cpi::validate::check_account_change,
errors::{RuntimeError, RuntimeResult},
},
solana_account::Account,
solana_address::Address,
solana_instruction::AccountMeta,
std::{collections::HashMap, mem::size_of},
};
const ALIGN_OF_U64: usize = 8;
const MAX_PERMITTED_DATA_INCREASE: usize = 10240;
const NON_DUP_MARKER: u8 = 0xff;
struct Serializer {
buffer: Vec<u8>,
}
impl Serializer {
fn new() -> Self {
Self { buffer: Vec::new() }
}
fn write<T>(&mut self, value: T) {
let bytes =
unsafe { std::slice::from_raw_parts(&value as *const T as *const u8, size_of::<T>()) };
self.buffer.extend_from_slice(bytes);
}
fn write_all(&mut self, data: &[u8]) {
self.buffer.extend_from_slice(data);
}
fn write_account_data(&mut self, data: &[u8]) {
self.write_all(data);
self.buffer
.extend(std::iter::repeat_n(0u8, MAX_PERMITTED_DATA_INCREASE));
let alignment_needed = (ALIGN_OF_U64 - (self.buffer.len() % ALIGN_OF_U64)) % ALIGN_OF_U64;
self.buffer
.extend(std::iter::repeat_n(0u8, alignment_needed));
}
fn finish(self) -> Vec<u8> {
self.buffer
}
}
enum SerializeAccount {
Account(Address, Account, bool, bool),
Duplicate(u8),
}
pub fn serialize_parameters(
accounts: &HashMap<Address, Account>,
account_metas: &[AccountMeta],
instruction_data: &[u8],
program_id: &Address,
) -> RuntimeResult<(Vec<u8>, Vec<usize>, usize)> {
let mut seen: HashMap<Address, usize> = HashMap::new();
let mut serialize_accounts = Vec::with_capacity(account_metas.len());
for (i, meta) in account_metas.iter().enumerate() {
if let Some(&first_idx) = seen.get(&meta.pubkey) {
serialize_accounts.push(SerializeAccount::Duplicate(first_idx as u8));
} else {
seen.insert(meta.pubkey, i);
let account = accounts.get(&meta.pubkey).ok_or_else(|| {
RuntimeError::MissingAccount(format!("Missing account data for {}", meta.pubkey))
})?;
serialize_accounts.push(SerializeAccount::Account(
meta.pubkey,
account.clone(),
meta.is_signer,
meta.is_writable,
));
}
}
let mut s = Serializer::new();
let mut pre_lens: Vec<usize> = Vec::new();
s.write::<u64>((serialize_accounts.len() as u64).to_le());
for account in serialize_accounts {
match account {
SerializeAccount::Account(pubkey, acct, is_signer, is_writable) => {
s.write::<u8>(NON_DUP_MARKER);
s.write::<u8>(is_signer as u8);
s.write::<u8>(is_writable as u8);
s.write::<u8>(acct.executable as u8);
s.write_all(&[0u8; 4]);
s.write_all(pubkey.as_ref());
s.write_all(acct.owner.as_ref());
s.write::<u64>(acct.lamports.to_le());
pre_lens.push(acct.data.len());
s.write::<u64>((acct.data.len() as u64).to_le());
s.write_account_data(&acct.data);
s.write::<u64>(acct.rent_epoch.to_le());
}
SerializeAccount::Duplicate(position) => {
s.write::<u8>(position);
s.write_all(&[0u8; 7]);
}
}
}
s.write::<u64>((instruction_data.len() as u64).to_le());
let instruction_data_offset = s.buffer.len();
s.write_all(instruction_data);
s.write_all(program_id.as_ref());
Ok((s.finish(), pre_lens, instruction_data_offset))
}
struct Deserializer<'a> {
buffer: &'a [u8],
offset: usize,
}
impl<'a> Deserializer<'a> {
fn new(buffer: &'a [u8]) -> Self {
Self { buffer, offset: 0 }
}
fn read_u64(&mut self) -> u64 {
let bytes: [u8; 8] = self.buffer[self.offset..self.offset + 8]
.try_into()
.unwrap_or([0u8; 8]);
self.offset += 8;
u64::from_le_bytes(bytes)
}
fn skip(&mut self, n: usize) {
self.offset += n;
}
fn read_address(&mut self) -> Address {
let bytes: [u8; 32] = self.buffer[self.offset..self.offset + 32]
.try_into()
.unwrap_or([0u8; 32]);
self.offset += 32;
Address::from(bytes)
}
fn read_account_data(&mut self, post_len: usize, pre_len: usize) -> &'a [u8] {
let data = &self.buffer[self.offset..self.offset + post_len];
self.offset += pre_len;
self.offset += MAX_PERMITTED_DATA_INCREASE;
let align = (ALIGN_OF_U64 - (self.offset % ALIGN_OF_U64)) % ALIGN_OF_U64;
self.offset += align;
data
}
}
pub fn deserialize_parameters(
accounts: &mut HashMap<Address, Account>,
account_metas: &[AccountMeta],
input: &[u8],
pre_lens: &[usize],
callee_program_id: &Address,
) -> RuntimeResult<()> {
let mut d = Deserializer::new(input);
let _num_accounts = d.read_u64();
let mut seen: HashMap<Address, usize> = HashMap::new();
let mut pre_len_idx = 0;
for (i, meta) in account_metas.iter().enumerate() {
if seen.contains_key(&meta.pubkey) {
d.skip(8); continue;
}
seen.insert(meta.pubkey, i);
let pre_len = pre_lens.get(pre_len_idx).copied().unwrap_or(0);
pre_len_idx += 1;
d.skip(8); d.skip(32); let owner = d.read_address();
let lamports = d.read_u64();
let data_len = d.read_u64() as usize;
let data = d.read_account_data(data_len, pre_len);
let _rent_epoch = d.read_u64();
if meta.is_writable
&& let Some(account) = accounts.get_mut(&meta.pubkey)
{
check_account_change(
callee_program_id,
&meta.pubkey,
account,
&owner,
lamports,
data,
)?;
account.lamports = lamports;
account.data = data.to_vec();
account.owner = owner;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
const PROGRAM: Address = Address::new_from_array([1u8; 32]);
const OTHER: Address = Address::new_from_array([2u8; 32]);
const ACCT_1: Address = Address::new_from_array([10u8; 32]);
const ACCT_2: Address = Address::new_from_array([11u8; 32]);
fn make_account(owner: Address, lamports: u64, data: &[u8]) -> Account {
Account {
lamports,
data: data.to_vec(),
owner,
executable: false,
rent_epoch: 0,
}
}
fn accounts_map(list: &[(Address, Account)]) -> HashMap<Address, Account> {
list.iter().cloned().collect()
}
#[test]
fn e2e_accounts_preserved() {
let mut accounts = accounts_map(&[
(ACCT_1, make_account(PROGRAM, 100, b"hello")),
(ACCT_2, make_account(PROGRAM, 200, b"world")),
]);
let metas = vec![
AccountMeta {
pubkey: ACCT_1,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: ACCT_2,
is_signer: false,
is_writable: true,
},
];
let (input, pre_lens, _) =
serialize_parameters(&accounts, &metas, b"ix", &PROGRAM).unwrap();
deserialize_parameters(&mut accounts, &metas, &input, &pre_lens, &PROGRAM).unwrap();
assert_eq!(accounts[&ACCT_1].lamports, 100);
assert_eq!(accounts[&ACCT_1].data, b"hello");
assert_eq!(accounts[&ACCT_2].lamports, 200);
assert_eq!(accounts[&ACCT_2].data, b"world");
}
#[test]
fn read_only_account_not_updated() {
let mut accounts = accounts_map(&[(ACCT_1, make_account(PROGRAM, 100, b"data"))]);
let metas = vec![AccountMeta {
pubkey: ACCT_1,
is_signer: false,
is_writable: false,
}];
let (mut input, pre_lens, _) =
serialize_parameters(&accounts, &metas, b"", &PROGRAM).unwrap();
let lamport_offset = 8 + 8 + 32 + 32;
input[lamport_offset..lamport_offset + 8].copy_from_slice(&999u64.to_le_bytes());
deserialize_parameters(&mut accounts, &metas, &input, &pre_lens, &PROGRAM).unwrap();
assert_eq!(accounts[&ACCT_1].lamports, 100); }
#[test]
fn non_owner_data_change_rejected() {
let mut accounts = accounts_map(&[(ACCT_1, make_account(OTHER, 100, b"original"))]);
let metas = vec![AccountMeta {
pubkey: ACCT_1,
is_signer: false,
is_writable: true,
}];
let (mut input, pre_lens, _) =
serialize_parameters(&accounts, &metas, b"", &PROGRAM).unwrap();
let data_offset = 8 + 8 + 32 + 32 + 8 + 8;
input[data_offset..data_offset + 4].copy_from_slice(b"fake");
let result = deserialize_parameters(&mut accounts, &metas, &input, &pre_lens, &PROGRAM);
assert!(result.is_err());
assert_eq!(accounts[&ACCT_1].data, b"original");
}
#[test]
fn non_owner_lamport_debit_rejected() {
let mut accounts = accounts_map(&[(ACCT_1, make_account(OTHER, 100, b""))]);
let metas = vec![AccountMeta {
pubkey: ACCT_1,
is_signer: false,
is_writable: true,
}];
let (mut input, pre_lens, _) =
serialize_parameters(&accounts, &metas, b"", &PROGRAM).unwrap();
let lamport_offset = 8 + 8 + 32 + 32;
input[lamport_offset..lamport_offset + 8].copy_from_slice(&50u64.to_le_bytes());
let result = deserialize_parameters(&mut accounts, &metas, &input, &pre_lens, &PROGRAM);
assert!(result.is_err());
assert_eq!(accounts[&ACCT_1].lamports, 100);
}
#[test]
fn missing_account_errors() {
let accounts = accounts_map(&[]);
let metas = vec![AccountMeta {
pubkey: ACCT_1,
is_signer: false,
is_writable: false,
}];
let result = serialize_parameters(&accounts, &metas, b"", &PROGRAM);
assert!(result.is_err());
}
#[test]
fn check_instruction_data_offset() {
let accounts = accounts_map(&[(ACCT_1, make_account(PROGRAM, 100, b"data"))]);
let metas = vec![AccountMeta {
pubkey: ACCT_1,
is_signer: false,
is_writable: true,
}];
let ix_data = b"instruction";
let (input, _, offset) =
serialize_parameters(&accounts, &metas, ix_data, &PROGRAM).unwrap();
assert_eq!(&input[offset..(offset + ix_data.len())], ix_data);
}
#[test]
fn check_instruction_data_offset_no_data() {
let accounts = accounts_map(&[(ACCT_1, make_account(PROGRAM, 100, b"data"))]);
let metas = vec![AccountMeta {
pubkey: ACCT_1,
is_signer: false,
is_writable: true,
}];
let ix_data = b""; let (input, _, offset) =
serialize_parameters(&accounts, &metas, ix_data, &PROGRAM).unwrap();
assert_eq!(&input[offset..offset + 32], PROGRAM.as_ref());
}
}