use std::collections::HashSet;
pub use loaded::*;
#[cfg(feature = "frozen-abi")]
use rialo_frozen_abi_macro::AbiExample;
use rialo_s_instruction::Instruction;
use rialo_s_pubkey::Pubkey;
use rialo_s_sdk_ids::bpf_loader_upgradeable;
use rialo_sanitize::SanitizeError;
#[cfg(feature = "serde")]
use serde_derive::{Deserialize, Serialize};
use crate::{
compiled_instruction::CompiledInstruction,
compiled_keys::{CompileError, CompiledKeys},
AccountKeys, ConfigHashPrefix, MessageHeader,
};
mod loaded;
pub const MAX_NUM_STATIC_ACCOUNT_KEYS: usize = 32;
#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
#[cfg_attr(
feature = "serde",
derive(Deserialize, Serialize),
serde(rename_all = "camelCase")
)]
#[derive(Default, Debug, PartialEq, Eq, Clone)]
pub struct Message {
pub header: MessageHeader,
#[cfg_attr(feature = "serde", serde(with = "rialo_s_short_vec"))]
pub account_keys: Vec<Pubkey>,
pub valid_from: i64,
pub config_hash_prefix: ConfigHashPrefix,
pub occ: bool,
#[cfg_attr(feature = "serde", serde(with = "rialo_s_short_vec"))]
pub instructions: Vec<CompiledInstruction>,
}
impl Message {
pub fn sanitize(&self) -> Result<(), SanitizeError> {
let num_static_account_keys = self.account_keys.len();
if usize::from(self.header.num_required_signatures)
.saturating_add(usize::from(self.header.num_readonly_unsigned_accounts))
> num_static_account_keys
{
return Err(SanitizeError::IndexOutOfBounds);
}
if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
return Err(SanitizeError::InvalidValue);
}
if num_static_account_keys == 0 {
return Err(SanitizeError::InvalidValue);
}
if num_static_account_keys > MAX_NUM_STATIC_ACCOUNT_KEYS {
return Err(SanitizeError::IndexOutOfBounds);
}
let max_account_ix = num_static_account_keys
.checked_sub(1)
.expect("message doesn't contain any account keys");
let max_program_id_ix =
num_static_account_keys
.checked_sub(1)
.expect("message doesn't contain any static account keys");
for ci in &self.instructions {
if usize::from(ci.program_id_index) > max_program_id_ix {
return Err(SanitizeError::IndexOutOfBounds);
}
if ci.program_id_index == 0 {
return Err(SanitizeError::IndexOutOfBounds);
}
for ai in &ci.accounts {
if usize::from(*ai) > max_account_ix {
return Err(SanitizeError::IndexOutOfBounds);
}
}
}
Ok(())
}
}
impl Message {
pub fn try_compile(
payer: &Pubkey,
instructions: &[Instruction],
valid_from: i64,
config_hash_prefix: ConfigHashPrefix,
occ: bool,
) -> Result<Self, CompileError> {
let compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
let (header, static_keys) = compiled_keys.try_into_message_components()?;
let account_keys = AccountKeys::new(&static_keys, None);
let instructions = account_keys.try_compile_instructions(instructions)?;
Ok(Self {
header,
account_keys: static_keys,
valid_from,
config_hash_prefix,
instructions,
occ,
})
}
#[cfg(feature = "bincode")]
pub fn serialize(&self) -> Vec<u8> {
bincode::serialize(&(crate::MESSAGE_VERSION_PREFIX, self)).unwrap()
}
pub fn config_hash_prefix(&self) -> ConfigHashPrefix {
self.config_hash_prefix
}
pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
if let Ok(key_index) = u8::try_from(key_index) {
self.instructions
.iter()
.any(|ix| ix.program_id_index == key_index)
} else {
false
}
}
fn is_writable_index(&self, key_index: usize) -> bool {
let header = &self.header;
let num_account_keys = self.account_keys.len();
let num_signed_accounts = usize::from(header.num_required_signatures);
if key_index >= num_account_keys {
false
} else if key_index >= num_signed_accounts {
let num_unsigned_accounts = num_account_keys.saturating_sub(num_signed_accounts);
let num_writable_unsigned_accounts = num_unsigned_accounts
.saturating_sub(usize::from(header.num_readonly_unsigned_accounts));
let unsigned_account_index = key_index.saturating_sub(num_signed_accounts);
unsigned_account_index < num_writable_unsigned_accounts
} else {
let num_writable_signed_accounts = num_signed_accounts
.saturating_sub(usize::from(header.num_readonly_signed_accounts));
key_index < num_writable_signed_accounts
}
}
fn is_upgradeable_loader_in_static_keys(&self) -> bool {
self.account_keys
.iter()
.any(|&key| key == bpf_loader_upgradeable::id())
}
pub fn is_maybe_writable(
&self,
key_index: usize,
reserved_account_keys: Option<&HashSet<Pubkey>>,
) -> bool {
self.is_writable_index(key_index)
&& !self.is_account_maybe_reserved(key_index, reserved_account_keys)
&& !{
self.is_key_called_as_program(key_index)
&& !self.is_upgradeable_loader_in_static_keys()
}
}
fn is_account_maybe_reserved(
&self,
key_index: usize,
reserved_account_keys: Option<&HashSet<Pubkey>>,
) -> bool {
let mut is_maybe_reserved = false;
if let Some(reserved_account_keys) = reserved_account_keys {
if let Some(key) = self.account_keys.get(key_index) {
is_maybe_reserved = reserved_account_keys.contains(key);
}
}
is_maybe_reserved
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::VersionedMessage;
#[test]
fn test_sanitize() {
assert!(Message {
header: MessageHeader {
num_required_signatures: 1,
..MessageHeader::default()
},
account_keys: vec![Pubkey::new_unique()],
..Message::default()
}
.sanitize()
.is_ok());
}
#[test]
fn test_sanitize_with_instruction() {
assert!(Message {
header: MessageHeader {
num_required_signatures: 1,
..MessageHeader::default()
},
account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
instructions: vec![CompiledInstruction {
program_id_index: 1,
accounts: vec![0],
data: vec![]
}],
..Message::default()
}
.sanitize()
.is_ok());
}
#[test]
fn test_sanitize_without_signer() {
assert!(Message {
header: MessageHeader::default(),
account_keys: vec![Pubkey::new_unique()],
..Message::default()
}
.sanitize()
.is_err());
}
#[test]
fn test_sanitize_without_writable_signer() {
assert!(Message {
header: MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 1,
..MessageHeader::default()
},
account_keys: vec![Pubkey::new_unique()],
..Message::default()
}
.sanitize()
.is_err());
}
#[test]
#[ignore] fn test_sanitize_with_max_account_keys() {
assert!(Message {
header: MessageHeader {
num_required_signatures: 1,
..MessageHeader::default()
},
account_keys: (0..=u8::MAX).map(|_| Pubkey::new_unique()).collect(),
..Message::default()
}
.sanitize()
.is_ok());
}
#[test]
fn test_sanitize_with_too_many_account_keys() {
assert!(Message {
header: MessageHeader {
num_required_signatures: 1,
..MessageHeader::default()
},
account_keys: (0..=256).map(|_| Pubkey::new_unique()).collect(),
..Message::default()
}
.sanitize()
.is_err());
}
#[test]
fn test_sanitize_with_max_table_loaded_keys() {
assert!(Message {
header: MessageHeader {
num_required_signatures: 1,
..MessageHeader::default()
},
account_keys: vec![Pubkey::new_unique()],
..Message::default()
}
.sanitize()
.is_ok());
}
#[test]
#[ignore] fn test_sanitize_with_too_many_table_loaded_keys() {
assert!(Message {
header: MessageHeader {
num_required_signatures: 1,
..MessageHeader::default()
},
account_keys: vec![Pubkey::new_unique()],
..Message::default()
}
.sanitize()
.is_err());
}
#[test]
fn test_sanitize_with_invalid_ix_program_id() {
let message = Message {
header: MessageHeader {
num_required_signatures: 1,
..MessageHeader::default()
},
account_keys: vec![Pubkey::new_unique()],
instructions: vec![CompiledInstruction {
program_id_index: 2,
accounts: vec![],
data: vec![],
}],
..Message::default()
};
assert!(message.sanitize().is_err());
}
#[test]
fn test_sanitize_with_invalid_ix_account() {
assert!(Message {
header: MessageHeader {
num_required_signatures: 1,
..MessageHeader::default()
},
account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
instructions: vec![CompiledInstruction {
program_id_index: 1,
accounts: vec![3],
data: vec![]
}],
..Message::default()
}
.sanitize()
.is_err());
}
#[test]
fn test_serialize() {
let message = Message::default();
let versioned_msg = VersionedMessage::V0(message.clone());
assert_eq!(message.serialize(), versioned_msg.serialize());
}
#[test]
#[ignore] fn test_is_maybe_writable() {
let key0 = Pubkey::new_unique();
let key1 = Pubkey::new_unique();
let key2 = Pubkey::new_unique();
let key3 = Pubkey::new_unique();
let key4 = Pubkey::new_unique();
let key5 = Pubkey::new_unique();
let message = Message {
header: MessageHeader {
num_required_signatures: 3,
num_readonly_signed_accounts: 2,
num_readonly_unsigned_accounts: 1,
},
account_keys: vec![key0, key1, key2, key3, key4, key5],
..Message::default()
};
let reserved_account_keys = HashSet::from([key3]);
assert!(message.is_maybe_writable(0, Some(&reserved_account_keys)));
assert!(!message.is_maybe_writable(1, Some(&reserved_account_keys)));
assert!(!message.is_maybe_writable(2, Some(&reserved_account_keys)));
assert!(!message.is_maybe_writable(3, Some(&reserved_account_keys)));
assert!(message.is_maybe_writable(3, None));
assert!(message.is_maybe_writable(4, Some(&reserved_account_keys)));
assert!(!message.is_maybe_writable(5, Some(&reserved_account_keys)));
assert!(message.is_maybe_writable(6, Some(&reserved_account_keys)));
assert!(!message.is_maybe_writable(7, Some(&reserved_account_keys)));
assert!(!message.is_maybe_writable(8, Some(&reserved_account_keys)));
}
#[test]
fn test_is_account_maybe_reserved() {
let key0 = Pubkey::new_unique();
let key1 = Pubkey::new_unique();
let message = Message {
account_keys: vec![key0, key1],
..Message::default()
};
let reserved_account_keys = HashSet::from([key1]);
assert!(!message.is_account_maybe_reserved(0, Some(&reserved_account_keys)));
assert!(message.is_account_maybe_reserved(1, Some(&reserved_account_keys)));
assert!(!message.is_account_maybe_reserved(2, Some(&reserved_account_keys)));
assert!(!message.is_account_maybe_reserved(3, Some(&reserved_account_keys)));
assert!(!message.is_account_maybe_reserved(4, Some(&reserved_account_keys)));
assert!(!message.is_account_maybe_reserved(0, None));
assert!(!message.is_account_maybe_reserved(1, None));
assert!(!message.is_account_maybe_reserved(2, None));
assert!(!message.is_account_maybe_reserved(3, None));
assert!(!message.is_account_maybe_reserved(4, None));
}
}