use crate::hash::Hash;
use crate::sanitize::{Sanitize, SanitizeError};
use crate::serde_error::{get_const_slice, get_slice, SerialisationErrors};
use crate::{compiled_keys::CompiledKeys, instruction::Instruction, pubkey::Pubkey};
use bitcode::{Decode, Encode};
use borsh::{BorshDeserialize, BorshSerialize};
#[cfg(feature = "fuzzing")]
use libfuzzer_sys::arbitrary;
use serde::{Deserialize, Serialize};
use sha256::digest;
use std::collections::HashSet;
pub const MAX_INSTRUCTION_COUNT_PER_TRANSACTION: usize = u8::MAX as usize;
pub const MAX_PUBKEYS_ALLOWED: u32 = u8::MAX as u32;
#[derive(Debug, Clone)]
pub struct SanitizedMessage {
pub message: ArchMessage,
pub is_writable_account_cache: Vec<bool>,
}
impl SanitizedMessage {
pub fn new(message: ArchMessage) -> Self {
let is_writable_account_cache = message
.account_keys
.iter()
.enumerate()
.map(|(i, _key)| message.is_writable_index(i))
.collect::<Vec<_>>();
Self {
message,
is_writable_account_cache,
}
}
pub fn is_signer(&self, index: usize) -> bool {
self.message.is_signer(index)
}
pub fn is_writable(&self, index: usize) -> bool {
*self.is_writable_account_cache.get(index).unwrap_or(&false)
}
pub fn instructions(&self) -> &Vec<SanitizedInstruction> {
&self.message.instructions
}
}
#[derive(
Clone,
Debug,
Eq,
PartialEq,
Serialize,
Deserialize,
BorshSerialize,
BorshDeserialize,
Default,
Hash,
Encode,
Decode,
)]
#[cfg_attr(feature = "fuzzing", derive(arbitrary::Arbitrary))]
pub struct ArchMessage {
pub header: MessageHeader,
pub account_keys: Vec<Pubkey>,
pub recent_blockhash: Hash,
pub instructions: Vec<SanitizedInstruction>,
}
impl Sanitize for ArchMessage {
fn sanitize(&self) -> Result<(), SanitizeError> {
let mut unique_keys = HashSet::new();
for key in &self.account_keys {
if !unique_keys.insert(key) {
return Err(SanitizeError::DuplicateAccount);
}
}
if self.header.num_required_signatures as usize
+ self.header.num_readonly_unsigned_accounts as usize
> self.account_keys.len()
{
return Err(SanitizeError::IndexOutOfBounds);
}
if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
return Err(SanitizeError::IndexOutOfBounds);
}
for ci in &self.instructions {
if ci.program_id_index as usize >= self.account_keys.len() {
return Err(SanitizeError::IndexOutOfBounds);
}
if ci.program_id_index == 0 {
return Err(SanitizeError::IndexOutOfBounds);
}
for ai in &ci.accounts {
if *ai as usize >= self.account_keys.len() {
return Err(SanitizeError::IndexOutOfBounds);
}
}
}
Ok(())
}
}
impl ArchMessage {
pub fn is_writable_index(&self, i: usize) -> bool {
i < (self.header.num_required_signatures - self.header.num_readonly_signed_accounts)
as usize
|| (i >= self.header.num_required_signatures as usize
&& i < self.account_keys.len()
- self.header.num_readonly_unsigned_accounts as usize)
}
pub fn header(&self) -> &MessageHeader {
&self.header
}
pub fn is_signer(&self, index: usize) -> bool {
index < usize::from(self.header().num_required_signatures)
}
pub fn get_account_key(&self, index: usize) -> Option<&Pubkey> {
self.account_keys.get(index)
}
pub fn get_unique_instruction_account_keys(&self) -> HashSet<Pubkey> {
let mut unique_keys = HashSet::new();
for instruction in &self.instructions {
for account_index in &instruction.accounts {
let pubkey = self
.account_keys
.get(*account_index as usize)
.expect("Account index out of bounds"); unique_keys.insert(*pubkey);
}
}
unique_keys
}
pub fn get_recent_blockhash(&self) -> Hash {
self.recent_blockhash
}
pub fn new(
instructions: &[Instruction],
payer: Option<Pubkey>,
recent_blockhash: Hash,
) -> Self {
let compiled_keys = CompiledKeys::compile(instructions, payer);
let (header, account_keys) = compiled_keys
.try_into_message_components()
.expect("overflow when compiling message keys");
let instructions = compile_instructions(instructions, &account_keys);
Self::new_with_compiled_instructions(
header.num_required_signatures,
header.num_readonly_signed_accounts,
header.num_readonly_unsigned_accounts,
account_keys,
recent_blockhash,
instructions,
)
}
pub fn new_with_compiled_instructions(
num_required_signatures: u8,
num_readonly_signed_accounts: u8,
num_readonly_unsigned_accounts: u8,
account_keys: Vec<Pubkey>,
recent_blockhash: Hash,
instructions: Vec<SanitizedInstruction>,
) -> Self {
Self {
header: MessageHeader {
num_required_signatures,
num_readonly_signed_accounts,
num_readonly_unsigned_accounts,
},
account_keys,
recent_blockhash,
instructions,
}
}
pub fn serialize(&self) -> Vec<u8> {
let mut buffer = Vec::new();
buffer.extend_from_slice(&[
self.header.num_required_signatures,
self.header.num_readonly_signed_accounts,
self.header.num_readonly_unsigned_accounts,
]);
buffer.extend_from_slice(&(self.account_keys.len() as u32).to_le_bytes());
for key in &self.account_keys {
buffer.extend_from_slice(key.as_ref());
}
buffer.extend_from_slice(&self.recent_blockhash.to_array());
buffer.extend_from_slice(&(self.instructions.len() as u32).to_le_bytes());
for instruction in &self.instructions {
buffer.extend(instruction.serialize());
}
buffer
}
pub fn deserialize(bytes: &[u8]) -> Result<Self, SerialisationErrors> {
let mut cursor: usize = 0;
const HEADER_SIZE: usize = 3;
let header_bytes = get_const_slice::<HEADER_SIZE>(bytes, cursor)?;
let header = MessageHeader {
num_required_signatures: header_bytes[0],
num_readonly_signed_accounts: header_bytes[1],
num_readonly_unsigned_accounts: header_bytes[2],
};
cursor += HEADER_SIZE;
const U32_SIZE: usize = 4;
let num_keys_bytes = get_const_slice::<U32_SIZE>(bytes, cursor)?;
let num_keys = u32::from_le_bytes(num_keys_bytes);
if num_keys > MAX_PUBKEYS_ALLOWED {
return Err(SerialisationErrors::MoreThanMaxAllowedKeys);
}
cursor += U32_SIZE;
const PUBKEY_SIZE: usize = 32;
let account_keys_size = num_keys
.checked_mul(PUBKEY_SIZE as u32)
.ok_or(SerialisationErrors::OverFlow)?;
let account_keys_slice = get_slice(bytes, cursor, account_keys_size as usize)?;
cursor += account_keys_size as usize;
let mut account_keys = Vec::with_capacity(num_keys as usize);
for key_bytes in account_keys_slice.chunks_exact(PUBKEY_SIZE) {
account_keys.push(Pubkey::from_slice(key_bytes));
}
let blockhash_bytes = get_const_slice::<PUBKEY_SIZE>(bytes, cursor)?;
let recent_blockhash = Hash::from(blockhash_bytes);
cursor += PUBKEY_SIZE;
let num_instructions_bytes = get_const_slice::<U32_SIZE>(bytes, cursor)?;
let num_instructions = u32::from_le_bytes(num_instructions_bytes);
cursor += U32_SIZE;
if num_instructions as usize > MAX_INSTRUCTION_COUNT_PER_TRANSACTION {
return Err(SerialisationErrors::MoreThanMaxInstructionsAllowed);
}
let mut instructions = Vec::with_capacity(num_instructions as usize);
for _ in 0..num_instructions {
let remaining_bytes = get_slice(bytes, cursor, bytes.len() - cursor)?;
let (instruction, bytes_read) = SanitizedInstruction::deserialize(remaining_bytes)?;
instructions.push(instruction);
cursor += bytes_read;
}
Ok(Self {
header,
account_keys,
recent_blockhash,
instructions,
})
}
pub fn hash(&self) -> Vec<u8> {
let serialized_message = self.serialize();
let first_hash = digest(serialized_message);
digest(first_hash.as_bytes()).as_bytes().to_vec()
}
pub fn program_instructions_iter(
&self,
) -> impl Iterator<Item = (&Pubkey, &SanitizedInstruction)> {
self.instructions.iter().map(|ix| {
(
self.account_keys
.get(usize::from(ix.program_id_index))
.expect("program id index is sanitized"),
ix,
)
})
}
}
fn position(keys: &[Pubkey], key: &Pubkey) -> u8 {
keys.iter().position(|k| k == key).unwrap() as u8
}
fn compile_instruction(ix: &Instruction, keys: &[Pubkey]) -> SanitizedInstruction {
let accounts: Vec<_> = ix
.accounts
.iter()
.map(|account_meta| position(keys, &account_meta.pubkey))
.collect();
SanitizedInstruction {
program_id_index: position(keys, &ix.program_id),
data: ix.data.clone(),
accounts,
}
}
fn compile_instructions(ixs: &[Instruction], keys: &[Pubkey]) -> Vec<SanitizedInstruction> {
ixs.iter().map(|ix| compile_instruction(ix, keys)).collect()
}
#[derive(
Clone,
Debug,
Eq,
PartialEq,
Serialize,
Deserialize,
BorshSerialize,
BorshDeserialize,
Hash,
Encode,
Decode,
)]
#[cfg_attr(feature = "fuzzing", derive(arbitrary::Arbitrary))]
pub struct SanitizedInstruction {
pub program_id_index: u8,
pub accounts: Vec<u8>,
pub data: Vec<u8>,
}
#[derive(
Clone,
Debug,
Eq,
PartialEq,
Serialize,
Deserialize,
BorshSerialize,
BorshDeserialize,
Default,
Hash,
Encode,
Decode,
)]
#[cfg_attr(feature = "fuzzing", derive(arbitrary::Arbitrary))]
pub struct MessageHeader {
pub num_required_signatures: u8,
pub num_readonly_signed_accounts: u8,
pub num_readonly_unsigned_accounts: u8,
}
impl SanitizedInstruction {
pub fn serialize(&self) -> Vec<u8> {
let mut buffer = Vec::new();
buffer.push(self.program_id_index);
buffer.extend_from_slice(&(self.accounts.len() as u32).to_le_bytes());
buffer.extend_from_slice(&self.accounts);
buffer.extend_from_slice(&(self.data.len() as u32).to_le_bytes());
buffer.extend_from_slice(&self.data);
buffer
}
pub fn deserialize(bytes: &[u8]) -> Result<(Self, usize), SerialisationErrors> {
if bytes.is_empty() {
return Err(SerialisationErrors::SizeTooSmall);
}
let mut cursor: usize = 0;
const PROGRAM_ID_SIZE: usize = 1;
let program_id_bytes = get_const_slice::<PROGRAM_ID_SIZE>(bytes, cursor)?;
let program_id_index = program_id_bytes[0];
cursor += PROGRAM_ID_SIZE;
const U32_SIZE: usize = 4;
let num_accounts_bytes = get_const_slice::<U32_SIZE>(bytes, cursor)?;
let num_accounts = u32::from_le_bytes(num_accounts_bytes);
cursor += U32_SIZE;
let accounts_slice = get_slice(bytes, cursor, num_accounts as usize)?;
let accounts = accounts_slice.to_vec();
cursor += num_accounts as usize;
let data_len_bytes = get_const_slice::<U32_SIZE>(bytes, cursor)?;
let data_len = u32::from_le_bytes(data_len_bytes);
cursor += U32_SIZE;
let data_slice = get_slice(bytes, cursor, data_len as usize)?;
let data = data_slice.to_vec();
cursor += data_len as usize;
Ok((
Self {
program_id_index,
accounts,
data,
},
cursor, ))
}
pub fn new_from_raw_parts(program_id_index: u8, data: Vec<u8>, accounts: Vec<u8>) -> Self {
Self {
program_id_index,
accounts,
data,
}
}
}
#[cfg(test)]
mod tests {
use crate::account::AccountMeta;
use super::*;
#[test]
fn test_message_with_mixed_signer_privileges() {
let signer_pubkey = Pubkey::new_unique();
let account_1 = Pubkey::new_unique();
let account_2 = Pubkey::new_unique();
let instruction_1 = Instruction {
program_id: Pubkey::new_unique(),
accounts: vec![
AccountMeta::new(account_1, false),
AccountMeta::new(signer_pubkey, true), ],
data: vec![],
};
let instruction_2 = Instruction {
program_id: Pubkey::new_unique(),
accounts: vec![
AccountMeta::new(account_2, false),
AccountMeta::new(signer_pubkey, false), ],
data: vec![],
};
let message = ArchMessage::new(&[instruction_1, instruction_2], None, Hash::from([0; 32]));
println!("Message Header:");
println!(
" Required signatures: {}",
message.header.num_required_signatures
);
println!(
" Readonly signed accounts: {}",
message.header.num_readonly_signed_accounts
);
println!(
" Readonly unsigned accounts: {}",
message.header.num_readonly_unsigned_accounts
);
println!("\nAccount Keys:");
for (i, key) in message.account_keys.iter().enumerate() {
println!(
" {}: {} (writable: {})",
i,
key,
message.is_writable_index(i)
);
}
println!("\nInstructions:");
for (i, instruction) in message.instructions.iter().enumerate() {
println!(" Instruction {}:", i);
println!(" Program ID Index: {}", instruction.program_id_index);
println!(" Account Indices: {:?}", instruction.accounts);
println!(" Data: {:?}", instruction.data);
}
assert!(
message.account_keys.contains(&signer_pubkey),
"Signer pubkey should be in account keys"
);
assert!(
message.account_keys.contains(&account_1),
"Account 1 should be in account keys"
);
assert!(
message.account_keys.contains(&account_2),
"Account 2 should be in account keys"
);
}
#[test]
fn test_message_serialization_deserialization() {
let header = MessageHeader {
num_required_signatures: 2,
num_readonly_signed_accounts: 1,
num_readonly_unsigned_accounts: 1,
};
let account_keys = vec![
Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), ];
let instruction1 = SanitizedInstruction {
program_id_index: 2, accounts: vec![0, 1, 3], data: vec![1, 2, 3, 4], };
let instruction2 = SanitizedInstruction {
program_id_index: 2,
accounts: vec![1, 3],
data: vec![5, 6, 7],
};
let original_message = ArchMessage {
header,
account_keys,
recent_blockhash: Hash::from([0; 32]),
instructions: vec![instruction1, instruction2],
};
let serialized = original_message.serialize();
let deserialized_message =
ArchMessage::deserialize(&serialized).expect("Failed to deserialize message");
assert_eq!(
deserialized_message.header.num_required_signatures,
original_message.header.num_required_signatures
);
assert_eq!(
deserialized_message.header.num_readonly_signed_accounts,
original_message.header.num_readonly_signed_accounts
);
assert_eq!(
deserialized_message.header.num_readonly_unsigned_accounts,
original_message.header.num_readonly_unsigned_accounts
);
assert_eq!(
deserialized_message.account_keys.len(),
original_message.account_keys.len()
);
for (original, deserialized) in original_message
.account_keys
.iter()
.zip(deserialized_message.account_keys.iter())
{
assert_eq!(original, deserialized);
}
assert_eq!(
deserialized_message.instructions.len(),
original_message.instructions.len()
);
for (original, deserialized) in original_message
.instructions
.iter()
.zip(deserialized_message.instructions.iter())
{
assert_eq!(original.program_id_index, deserialized.program_id_index);
assert_eq!(original.accounts, deserialized.accounts);
assert_eq!(original.data, deserialized.data);
}
}
#[test]
fn test_instruction_deserialization_error_cases() {
let result = SanitizedInstruction::deserialize(&[]);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Data is not large enough for the requested operation"
);
let result = SanitizedInstruction::deserialize(&[0]);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Data corrupted, unable to decode"
);
let invalid_instruction = vec![
0, 255, 255, 255, 255, ];
let result = SanitizedInstruction::deserialize(&invalid_instruction);
assert!(result.is_err());
let truncated_accounts = vec![
0, 2, 0, 0, 0, 0, ];
let result = SanitizedInstruction::deserialize(&truncated_accounts);
assert!(result.is_err());
let invalid_data = vec![
0, 1, 0, 0, 0, 0, 255, 255, 255, 255, ];
let result = SanitizedInstruction::deserialize(&invalid_data);
assert!(result.is_err());
let truncated_data = vec![
0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 1, ];
let result = SanitizedInstruction::deserialize(&truncated_data);
assert!(result.is_err());
}
#[test]
fn test_account_privileges_and_ordering() {
let header = MessageHeader {
num_required_signatures: 3, num_readonly_signed_accounts: 1, num_readonly_unsigned_accounts: 2, };
let account_keys = vec![
Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), ];
let instruction = SanitizedInstruction {
program_id_index: 3, accounts: vec![0, 1, 2, 4, 5], data: vec![1, 2, 3],
};
let message = ArchMessage {
header,
account_keys: account_keys.clone(),
recent_blockhash: Hash::from([0; 32]),
instructions: vec![instruction],
};
assert!(
message.is_writable_index(0),
"First signer should be writable"
);
assert!(
message.is_writable_index(1),
"Second signer should be writable"
);
assert!(
!message.is_writable_index(2),
"Third signer should be read-only"
);
assert!(
message.is_writable_index(3),
"First non-signer should be writable"
);
assert!(
!message.is_writable_index(4),
"Second non-signer should be read-only"
);
assert!(
!message.is_writable_index(5),
"Third non-signer should be read-only"
);
assert!(message.is_signer(0), "Account 0 should be a signer");
assert!(message.is_signer(1), "Account 1 should be a signer");
assert!(message.is_signer(2), "Account 2 should be a signer");
assert!(!message.is_signer(3), "Account 3 should not be a signer");
assert!(!message.is_signer(4), "Account 4 should not be a signer");
assert!(!message.is_signer(5), "Account 5 should not be a signer");
let serialized = message.serialize();
let deserialized = ArchMessage::deserialize(&serialized).unwrap();
assert_eq!(deserialized.header.num_required_signatures, 3);
assert_eq!(deserialized.header.num_readonly_signed_accounts, 1);
assert_eq!(deserialized.header.num_readonly_unsigned_accounts, 2);
assert_eq!(deserialized.account_keys, account_keys);
for i in 0..6 {
assert_eq!(
message.is_writable_index(i),
deserialized.is_writable_index(i),
"Writable privilege mismatch for account {}",
i
);
assert_eq!(
message.is_signer(i),
deserialized.is_signer(i),
"Signer privilege mismatch for account {}",
i
);
}
}
#[test]
fn test_sanitized_message_privilege_cache() {
let header = MessageHeader {
num_required_signatures: 2,
num_readonly_signed_accounts: 1,
num_readonly_unsigned_accounts: 1,
};
let account_keys = vec![
Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), ];
let message = ArchMessage {
header,
account_keys,
recent_blockhash: Hash::from([0; 32]),
instructions: vec![],
};
let sanitized_message = SanitizedMessage::new(message);
assert!(
sanitized_message.is_writable(0),
"First account should be writable"
);
assert!(
!sanitized_message.is_writable(1),
"Second account should be read-only"
);
assert!(
sanitized_message.is_writable(2),
"Third account should be writable"
);
assert!(
!sanitized_message.is_writable(3),
"Fourth account should be read-only"
);
assert!(
sanitized_message.is_signer(0),
"First account should be signer"
);
assert!(
sanitized_message.is_signer(1),
"Second account should be signer"
);
assert!(
!sanitized_message.is_signer(2),
"Third account should not be signer"
);
assert!(
!sanitized_message.is_signer(3),
"Fourth account should not be signer"
);
}
#[test]
fn test_get_unique_account_keys() {
let header = MessageHeader {
num_required_signatures: 2,
num_readonly_signed_accounts: 1,
num_readonly_unsigned_accounts: 1,
};
let account_keys = vec![
Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), ];
let instruction1 = SanitizedInstruction {
program_id_index: 2,
accounts: vec![0, 1, 4], data: vec![1, 2, 3],
};
let instruction2 = SanitizedInstruction {
program_id_index: 2, accounts: vec![1, 4], data: vec![4, 5, 6],
};
let message = ArchMessage {
header,
account_keys: account_keys.clone(),
recent_blockhash: Hash::from([0; 32]),
instructions: vec![instruction1, instruction2],
};
let unique_keys = message.get_unique_instruction_account_keys();
assert_eq!(unique_keys.len(), 3);
assert!(unique_keys.contains(&account_keys[0])); assert!(unique_keys.contains(&account_keys[1])); assert!(!unique_keys.contains(&account_keys[2])); assert!(!unique_keys.contains(&account_keys[3])); assert!(unique_keys.contains(&account_keys[4])); }
#[test]
fn test_arch_message_serde_roundtrip_basic() {
let header = MessageHeader {
num_required_signatures: 2,
num_readonly_signed_accounts: 1,
num_readonly_unsigned_accounts: 1,
};
let account_keys = vec![
Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), ];
let instruction = SanitizedInstruction {
program_id_index: 2,
accounts: vec![0, 1, 3],
data: vec![1, 2, 3, 4, 5],
};
let original_message = ArchMessage {
header,
account_keys,
recent_blockhash: Hash::from([0xab; 32]),
instructions: vec![instruction],
};
let serialized = original_message.serialize();
let deserialized =
ArchMessage::deserialize(&serialized).expect("Deserialization should succeed");
assert_eq!(original_message, deserialized);
}
#[test]
fn test_arch_message_serde_roundtrip_empty_instructions() {
let original_message = ArchMessage {
header: MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 0,
},
account_keys: vec![Pubkey::new_unique()],
recent_blockhash: Hash::from([0xff; 32]),
instructions: vec![], };
let serialized = original_message.serialize();
let deserialized = ArchMessage::deserialize(&serialized)
.expect("Deserialization should succeed with empty instructions");
assert_eq!(original_message, deserialized);
}
#[test]
fn test_arch_message_serde_roundtrip_max_instructions() {
let header = MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 1,
};
let account_keys = vec![
Pubkey::new_unique(), Pubkey::new_unique(), ];
let mut instructions = Vec::new();
for i in 0..MAX_INSTRUCTION_COUNT_PER_TRANSACTION {
instructions.push(SanitizedInstruction {
program_id_index: 1,
accounts: vec![0],
data: vec![i as u8], });
}
let original_message = ArchMessage {
header,
account_keys,
recent_blockhash: Hash::from([0x42; 32]),
instructions,
};
let serialized = original_message.serialize();
let deserialized = ArchMessage::deserialize(&serialized)
.expect("Deserialization should succeed with max instructions");
assert_eq!(original_message, deserialized);
}
#[test]
fn test_arch_message_serde_roundtrip_large_instruction_data() {
let original_message = ArchMessage {
header: MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 1,
},
account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
recent_blockhash: Hash::from([0x33; 32]),
instructions: vec![SanitizedInstruction {
program_id_index: 1,
accounts: vec![0],
data: vec![0xaa; 1024], }],
};
let serialized = original_message.serialize();
let deserialized = ArchMessage::deserialize(&serialized)
.expect("Deserialization should succeed with large instruction data");
assert_eq!(original_message, deserialized);
}
#[test]
fn test_arch_message_deserialize_insufficient_data() {
let original_message = ArchMessage {
header: MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 0,
},
account_keys: vec![Pubkey::new_unique()],
recent_blockhash: Hash::from([0x11; 32]),
instructions: vec![SanitizedInstruction {
program_id_index: 0,
accounts: vec![],
data: vec![1, 2, 3],
}],
};
let serialized = original_message.serialize();
for i in 0..serialized.len() {
let truncated = &serialized[..i];
assert!(
ArchMessage::deserialize(truncated).is_err(),
"Deserialization should fail for truncated data of length {}",
i
);
}
}
#[test]
fn test_arch_message_deserialize_too_many_instructions() {
let mut malicious_data = Vec::new();
malicious_data.extend_from_slice(&[1, 0, 0]);
malicious_data.extend_from_slice(&1u32.to_le_bytes());
malicious_data.extend_from_slice(&[0x11; 32]);
malicious_data.extend_from_slice(&[0x22; 32]);
let excessive_count = (MAX_INSTRUCTION_COUNT_PER_TRANSACTION + 1) as u32;
malicious_data.extend_from_slice(&excessive_count.to_le_bytes());
let result = ArchMessage::deserialize(&malicious_data);
assert!(result.is_err(), "Should reject excessive instruction count");
}
#[test]
fn test_arch_message_deserialize_account_key_overflow() {
let mut malicious_data = Vec::new();
malicious_data.extend_from_slice(&[1, 0, 0]);
let overflow_count = u32::MAX;
malicious_data.extend_from_slice(&overflow_count.to_le_bytes());
let result = ArchMessage::deserialize(&malicious_data);
assert!(
result.is_err(),
"Should reject account key count that causes overflow"
);
}
#[test]
fn test_arch_message_serde_roundtrip_multiple_account_types() {
let original_message = ArchMessage {
header: MessageHeader {
num_required_signatures: 3,
num_readonly_signed_accounts: 1,
num_readonly_unsigned_accounts: 2,
},
account_keys: vec![
Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), ],
recent_blockhash: Hash::from([0x99; 32]),
instructions: vec![
SanitizedInstruction {
program_id_index: 4,
accounts: vec![0, 1, 2, 3],
data: vec![0x01, 0x02],
},
SanitizedInstruction {
program_id_index: 5,
accounts: vec![1, 3],
data: vec![],
},
],
};
let serialized = original_message.serialize();
let deserialized = ArchMessage::deserialize(&serialized)
.expect("Complex message deserialization should succeed");
assert_eq!(original_message, deserialized);
}
}
#[cfg(test)]
mod sanitize_tests {
use super::*;
fn create_valid_message() -> ArchMessage {
ArchMessage {
header: MessageHeader {
num_required_signatures: 2,
num_readonly_signed_accounts: 1,
num_readonly_unsigned_accounts: 1,
},
account_keys: vec![
Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), ],
recent_blockhash: Hash::from([0; 32]),
instructions: vec![SanitizedInstruction {
program_id_index: 2,
accounts: vec![0, 1, 3, 4],
data: vec![1, 2, 3],
}],
}
}
#[test]
fn test_valid_message() {
let message = create_valid_message();
assert!(message.sanitize().is_ok());
}
#[test]
fn test_overlapping_signing_and_readonly_areas() {
let mut message = create_valid_message();
message.header.num_required_signatures = 3;
message.header.num_readonly_unsigned_accounts = 3;
assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
}
#[test]
fn test_no_writable_fee_payer() {
let mut message = create_valid_message();
message.header.num_readonly_signed_accounts = message.header.num_required_signatures;
assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
}
#[test]
fn test_invalid_program_id_index() {
let mut message = create_valid_message();
message.instructions[0].program_id_index = message.account_keys.len() as u8;
assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
}
#[test]
fn test_program_as_payer() {
let mut message = create_valid_message();
message.instructions[0].program_id_index = 0;
assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
}
#[test]
fn test_invalid_account_index() {
let mut message = create_valid_message();
message.instructions[0]
.accounts
.push(message.account_keys.len() as u8);
assert_eq!(message.sanitize(), Err(SanitizeError::IndexOutOfBounds));
}
#[test]
fn test_complex_valid_message() {
let message = ArchMessage {
header: MessageHeader {
num_required_signatures: 3,
num_readonly_signed_accounts: 1,
num_readonly_unsigned_accounts: 2,
},
account_keys: vec![
Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), ],
recent_blockhash: Hash::from([0; 32]),
instructions: vec![
SanitizedInstruction {
program_id_index: 3,
accounts: vec![0, 1, 5],
data: vec![1, 2, 3],
},
SanitizedInstruction {
program_id_index: 4,
accounts: vec![1, 2, 6, 7],
data: vec![4, 5, 6],
},
],
};
assert!(message.sanitize().is_ok());
}
#[test]
fn test_duplicate_account_in_instr() {
let message = ArchMessage {
header: MessageHeader {
num_required_signatures: 2,
num_readonly_signed_accounts: 1,
num_readonly_unsigned_accounts: 1,
},
account_keys: vec![
Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), ],
recent_blockhash: Hash::from([0; 32]),
instructions: vec![SanitizedInstruction {
program_id_index: 2,
accounts: vec![0, 1, 3, 3],
data: vec![1, 2, 3],
}],
};
assert!(message.sanitize().is_ok());
}
#[test]
fn test_duplicate_account_in_keys_list() {
let malicious = Pubkey::new_unique();
let message = ArchMessage {
header: MessageHeader {
num_required_signatures: 2,
num_readonly_signed_accounts: 1,
num_readonly_unsigned_accounts: 1,
},
account_keys: vec![
Pubkey::new_unique(), Pubkey::new_unique(), Pubkey::new_unique(), malicious,
malicious,
],
recent_blockhash: Hash::from([0; 32]),
instructions: vec![SanitizedInstruction {
program_id_index: 2,
accounts: vec![0, 1, 3, 4],
data: vec![1, 2, 3],
}],
};
assert_eq!(
message.sanitize().unwrap_err(),
SanitizeError::DuplicateAccount
);
}
#[test]
fn test_sanitized_instruction_serde_roundtrip_basic() {
let original_instruction = SanitizedInstruction {
program_id_index: 5,
accounts: vec![0, 1, 2, 3],
data: vec![0xaa, 0xbb, 0xcc, 0xdd],
};
let serialized = original_instruction.serialize();
let (deserialized, bytes_read) =
SanitizedInstruction::deserialize(&serialized).expect("Deserialization should succeed");
assert_eq!(original_instruction, deserialized);
assert_eq!(bytes_read, serialized.len());
}
#[test]
fn test_sanitized_instruction_serde_roundtrip_empty_data() {
let original_instruction = SanitizedInstruction {
program_id_index: 1,
accounts: vec![0],
data: vec![], };
let serialized = original_instruction.serialize();
let (deserialized, bytes_read) = SanitizedInstruction::deserialize(&serialized)
.expect("Deserialization should succeed with empty data");
assert_eq!(original_instruction, deserialized);
assert_eq!(bytes_read, serialized.len());
}
#[test]
fn test_sanitized_instruction_serde_roundtrip_empty_accounts() {
let original_instruction = SanitizedInstruction {
program_id_index: 42,
accounts: vec![], data: vec![1, 2, 3],
};
let serialized = original_instruction.serialize();
let (deserialized, bytes_read) = SanitizedInstruction::deserialize(&serialized)
.expect("Deserialization should succeed with empty accounts");
assert_eq!(original_instruction, deserialized);
assert_eq!(bytes_read, serialized.len());
}
#[test]
fn test_sanitized_instruction_serde_roundtrip_large_data() {
let original_instruction = SanitizedInstruction {
program_id_index: 255,
accounts: vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
data: vec![0x42; 2048], };
let serialized = original_instruction.serialize();
let (deserialized, bytes_read) = SanitizedInstruction::deserialize(&serialized)
.expect("Deserialization should succeed with large data");
assert_eq!(original_instruction, deserialized);
assert_eq!(bytes_read, serialized.len());
}
#[test]
fn test_sanitized_instruction_deserialize_insufficient_data() {
let original_instruction = SanitizedInstruction {
program_id_index: 10,
accounts: vec![1, 2, 3],
data: vec![0xaa, 0xbb, 0xcc],
};
let serialized = original_instruction.serialize();
for i in 0..serialized.len() {
let truncated = &serialized[..i];
assert!(
SanitizedInstruction::deserialize(truncated).is_err(),
"Deserialization should fail for truncated instruction data of length {}",
i
);
}
}
#[test]
fn test_sanitized_instruction_deserialize_empty_buffer() {
let result = SanitizedInstruction::deserialize(&[]);
assert!(result.is_err(), "Should fail with empty buffer");
}
#[test]
fn test_sanitized_instruction_deserialize_malformed_lengths() {
let mut malicious_data = Vec::new();
malicious_data.push(1);
malicious_data.extend_from_slice(&1000u32.to_le_bytes());
malicious_data.extend_from_slice(&[1, 2, 3]);
let result = SanitizedInstruction::deserialize(&malicious_data);
assert!(
result.is_err(),
"Should fail with insufficient account data"
);
}
#[test]
fn test_sanitized_instruction_deserialize_data_length_mismatch() {
let mut malicious_data = Vec::new();
malicious_data.push(5);
malicious_data.extend_from_slice(&0u32.to_le_bytes());
malicious_data.extend_from_slice(&1000u32.to_le_bytes());
malicious_data.extend_from_slice(&[0xaa, 0xbb]);
let result = SanitizedInstruction::deserialize(&malicious_data);
assert!(
result.is_err(),
"Should fail with insufficient instruction data"
);
}
#[test]
fn test_sanitized_instruction_bytes_consumed_accuracy() {
let instructions = vec![
SanitizedInstruction {
program_id_index: 1,
accounts: vec![],
data: vec![],
},
SanitizedInstruction {
program_id_index: 2,
accounts: vec![0, 1, 2],
data: vec![0xaa],
},
SanitizedInstruction {
program_id_index: 3,
accounts: vec![0],
data: vec![0xbb; 100],
},
];
for instruction in instructions {
let serialized = instruction.serialize();
let (_, bytes_read) = SanitizedInstruction::deserialize(&serialized)
.expect("Deserialization should succeed");
assert_eq!(
bytes_read,
serialized.len(),
"Bytes read should exactly match serialized length"
);
}
}
}