#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
pub mod compiled_instruction;
mod compiled_keys;
pub mod inline_nonce;
pub mod inner_instruction;
pub mod legacy;
#[cfg(feature = "serde")]
use serde_derive::{Deserialize, Serialize};
#[cfg(feature = "frozen-abi")]
use solana_frozen_abi_macro::AbiExample;
#[cfg(feature = "wincode")]
use wincode::{SchemaRead, SchemaWrite, UninitBuilder};
use {solana_sdk_ids::bpf_loader_upgradeable, std::collections::HashSet};
#[cfg(not(target_os = "solana"))]
#[path = ""]
mod non_bpf_modules {
mod account_keys;
mod address_loader;
mod sanitized;
mod versions;
pub use {account_keys::*, address_loader::*, sanitized::*, versions::*};
}
use crate::compiled_instruction::CompiledInstruction;
#[cfg(not(target_os = "solana"))]
pub use non_bpf_modules::*;
pub use {
compiled_keys::CompileError,
legacy::Message,
solana_address::Address,
solana_hash::Hash,
solana_instruction::{AccountMeta, Instruction},
};
pub const MESSAGE_HEADER_LENGTH: usize = 3;
#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
#[cfg_attr(
feature = "serde",
derive(Deserialize, Serialize),
serde(rename_all = "camelCase")
)]
#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead, UninitBuilder))]
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
pub struct MessageHeader {
pub num_required_signatures: u8,
pub num_readonly_signed_accounts: u8,
pub num_readonly_unsigned_accounts: u8,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct AddressLookupTableAccount {
pub key: Address,
pub addresses: Vec<Address>,
}
#[inline(always)]
fn is_writable_index(i: usize, header: MessageHeader, account_keys: &[Address]) -> bool {
i < (header.num_required_signatures as usize)
.saturating_sub(header.num_readonly_signed_accounts as usize)
|| (i >= header.num_required_signatures as usize
&& i < account_keys
.len()
.saturating_sub(header.num_readonly_unsigned_accounts as usize))
}
#[inline(always)]
fn is_account_maybe_reserved(
i: usize,
account_keys: &[Address],
reserved_account_keys: Option<&HashSet<Address>>,
) -> bool {
let mut is_maybe_reserved = false;
if let Some(reserved_account_keys) = reserved_account_keys {
if let Some(key) = account_keys.get(i) {
is_maybe_reserved = reserved_account_keys.contains(key);
}
}
is_maybe_reserved
}
#[inline(always)]
fn is_program_id_write_demoted(
i: usize,
account_keys: &[Address],
instructions: &[CompiledInstruction],
) -> bool {
is_key_called_as_program(instructions, i) && !is_upgradeable_loader_present(account_keys)
}
#[inline(always)]
fn is_key_called_as_program(instructions: &[CompiledInstruction], key_index: usize) -> bool {
if let Ok(key_index) = u8::try_from(key_index) {
instructions
.iter()
.any(|ix| ix.program_id_index == key_index)
} else {
false
}
}
#[inline(always)]
fn is_upgradeable_loader_present(account_keys: &[Address]) -> bool {
account_keys
.iter()
.any(|&key| key == bpf_loader_upgradeable::id())
}
#[inline(always)]
fn is_maybe_writable(
i: usize,
header: MessageHeader,
account_keys: &[Address],
instructions: &[CompiledInstruction],
reserved_account_keys: Option<&HashSet<Address>>,
) -> bool {
(is_writable_index(i, header, account_keys))
&& !is_account_maybe_reserved(i, account_keys, reserved_account_keys)
&& !is_program_id_write_demoted(i, account_keys, instructions)
}
#[cfg(test)]
mod tests {
use {
crate::{is_account_maybe_reserved, Message},
solana_address::Address,
std::collections::HashSet,
};
#[test]
fn test_is_account_maybe_reserved() {
let key0 = Address::new_unique();
let key1 = Address::new_unique();
let message = Message {
account_keys: vec![key0, key1],
..Message::default()
};
let reserved_account_keys = HashSet::from([key1]);
assert!(!is_account_maybe_reserved(
0,
&message.account_keys,
Some(&reserved_account_keys)
));
assert!(is_account_maybe_reserved(
1,
&message.account_keys,
Some(&reserved_account_keys)
));
assert!(!is_account_maybe_reserved(
2,
&message.account_keys,
Some(&reserved_account_keys)
));
assert!(!is_account_maybe_reserved(0, &message.account_keys, None));
assert!(!is_account_maybe_reserved(1, &message.account_keys, None));
assert!(!is_account_maybe_reserved(2, &message.account_keys, None));
}
}