#![allow(clippy::arithmetic_side_effects)]
#[cfg(feature = "serde")]
use serde_derive::{Deserialize, Serialize};
#[cfg(feature = "frozen-abi")]
use solana_frozen_abi_macro::{frozen_abi, AbiExample, StableAbi, StableAbiSample};
use {
crate::{
compiled_instruction::CompiledInstruction, compiled_keys::CompiledKeys,
inline_nonce::advance_nonce_account_instruction, AddressSet, MessageHeader,
},
alloc::vec::Vec,
core::convert::TryFrom,
solana_address::Address,
solana_hash::Hash,
solana_instruction::Instruction,
solana_sanitize::{Sanitize, SanitizeError},
};
#[cfg(feature = "wincode")]
use {
core::mem::MaybeUninit,
solana_short_vec::ShortU16,
wincode::{
config::Config, containers, io::Reader, ReadResult, SchemaRead, SchemaReadContext,
SchemaWrite,
},
};
fn position(keys: &[Address], key: &Address) -> u8 {
keys.iter().position(|k| k == key).unwrap() as u8
}
fn compile_instruction(ix: &Instruction, keys: &[Address]) -> CompiledInstruction {
let accounts: Vec<_> = ix
.accounts
.iter()
.map(|account_meta| position(keys, &account_meta.pubkey))
.collect();
CompiledInstruction {
program_id_index: position(keys, &ix.program_id),
data: ix.data.clone(),
accounts,
}
}
fn compile_instructions(ixs: &[Instruction], keys: &[Address]) -> Vec<CompiledInstruction> {
ixs.iter().map(|ix| compile_instruction(ix, keys)).collect()
}
#[cfg(feature = "frozen-abi")]
fn sample_legacy_header(
rng: &mut (impl solana_frozen_abi::rand::RngCore + ?Sized),
) -> MessageHeader {
use solana_frozen_abi::stable_abi::StableAbi;
let mut header = MessageHeader::random(rng);
header.num_required_signatures &= !crate::MESSAGE_VERSION_PREFIX;
header
}
#[cfg_attr(
feature = "frozen-abi",
frozen_abi(digest = "GXpvLNiMCnjnZpQEDKpc2NBpsqmRnAX7ZTCy9JmvG8Dg"),
derive(AbiExample, StableAbi, StableAbiSample)
)]
#[cfg_attr(
feature = "serde",
derive(Deserialize, Serialize),
serde(rename_all = "camelCase")
)]
#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
#[derive(Default, Debug, PartialEq, Eq, Clone)]
pub struct Message {
#[cfg_attr(
feature = "frozen-abi",
stable_abi_sample(with = "sample_legacy_header(rng)")
)]
pub header: MessageHeader,
#[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
#[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
pub account_keys: Vec<Address>,
pub recent_blockhash: Hash,
#[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
#[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
pub instructions: Vec<CompiledInstruction>,
}
#[cfg(feature = "wincode")]
unsafe impl<'de, C: Config> SchemaReadContext<'de, C, u8> for Message {
type Dst = Self;
fn read_with_context(
num_required_signatures: u8,
mut reader: impl Reader<'de>,
dst: &mut MaybeUninit<Self::Dst>,
) -> ReadResult<()> {
let header = {
let mut reader = unsafe { reader.as_trusted_for(2) }?;
MessageHeader {
num_required_signatures,
num_readonly_signed_accounts: reader.take_byte()?,
num_readonly_unsigned_accounts: reader.take_byte()?,
}
};
let account_keys =
<containers::Vec<Address, ShortU16> as SchemaRead<C>>::get(reader.by_ref())?;
let recent_blockhash = <Hash as SchemaRead<C>>::get(reader.by_ref())?;
let instructions =
<containers::Vec<CompiledInstruction, ShortU16> as SchemaRead<C>>::get(reader)?;
dst.write(Message {
header,
account_keys,
recent_blockhash,
instructions,
});
Ok(())
}
}
impl Sanitize for Message {
fn sanitize(&self) -> Result<(), SanitizeError> {
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);
}
}
}
self.account_keys.sanitize()?;
self.recent_blockhash.sanitize()?;
self.instructions.sanitize()?;
Ok(())
}
}
impl Message {
pub fn new(instructions: &[Instruction], payer: Option<&Address>) -> Self {
Self::new_with_blockhash(instructions, payer, &Hash::default())
}
pub fn new_with_blockhash(
instructions: &[Instruction],
payer: Option<&Address>,
blockhash: &Hash,
) -> Self {
let compiled_keys = CompiledKeys::compile(instructions, payer.cloned());
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,
Hash::new_from_array(blockhash.to_bytes()),
instructions,
)
}
pub fn new_with_nonce(
mut instructions: Vec<Instruction>,
payer: Option<&Address>,
nonce_account_pubkey: &Address,
nonce_authority_pubkey: &Address,
) -> Self {
let nonce_ix =
advance_nonce_account_instruction(nonce_account_pubkey, nonce_authority_pubkey);
instructions.insert(0, nonce_ix);
Self::new(&instructions, payer)
}
pub fn new_with_compiled_instructions(
num_required_signatures: u8,
num_readonly_signed_accounts: u8,
num_readonly_unsigned_accounts: u8,
account_keys: Vec<Address>,
recent_blockhash: Hash,
instructions: Vec<CompiledInstruction>,
) -> Self {
Self {
header: MessageHeader {
num_required_signatures,
num_readonly_signed_accounts,
num_readonly_unsigned_accounts,
},
account_keys,
recent_blockhash,
instructions,
}
}
#[cfg(all(feature = "wincode", feature = "blake3"))]
pub fn hash(&self) -> Hash {
let message_bytes = self.serialize();
Self::hash_raw_message(&message_bytes)
}
#[cfg(feature = "blake3")]
pub fn hash_raw_message(message_bytes: &[u8]) -> Hash {
use {blake3::traits::digest::Digest, solana_hash::HASH_BYTES};
let mut hasher = blake3::Hasher::new();
hasher.update(b"solana-tx-message-v1");
hasher.update(message_bytes);
let hash_bytes: [u8; HASH_BYTES] = hasher.finalize().into();
hash_bytes.into()
}
pub fn compile_instruction(&self, ix: &Instruction) -> CompiledInstruction {
compile_instruction(ix, &self.account_keys)
}
#[cfg(feature = "wincode")]
pub fn serialize(&self) -> Vec<u8> {
wincode::serialize(self).unwrap()
}
pub fn program_id(&self, instruction_index: usize) -> Option<&Address> {
Some(
&self.account_keys[self.instructions.get(instruction_index)?.program_id_index as usize],
)
}
pub fn program_index(&self, instruction_index: usize) -> Option<usize> {
Some(self.instructions.get(instruction_index)?.program_id_index as usize)
}
pub fn program_ids(&self) -> Vec<&Address> {
self.instructions
.iter()
.map(|ix| &self.account_keys[ix.program_id_index as usize])
.collect()
}
pub fn is_instruction_account(&self, key_index: usize) -> bool {
if let Ok(key_index) = u8::try_from(key_index) {
self.instructions
.iter()
.any(|ix| ix.accounts.contains(&key_index))
} else {
false
}
}
pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
super::is_key_called_as_program(&self.instructions, key_index)
}
pub fn program_position(&self, index: usize) -> Option<usize> {
let program_ids = self.program_ids();
program_ids
.iter()
.position(|&&pubkey| pubkey == self.account_keys[index])
}
pub fn maybe_executable(&self, i: usize) -> bool {
self.program_position(i).is_some()
}
pub fn demote_program_id(&self, i: usize) -> bool {
super::is_program_id_write_demoted(i, &self.account_keys, &self.instructions)
}
#[cfg(feature = "std")]
pub(super) fn is_writable_index(&self, i: usize) -> bool {
super::is_writable_index(i, self.header, &self.account_keys)
}
pub fn is_maybe_writable_with_reserved_addresses(
&self,
i: usize,
reserved_addresses: Option<&impl AddressSet>,
) -> bool {
super::is_maybe_writable(
i,
self.header,
&self.account_keys,
&self.instructions,
reserved_addresses,
)
}
pub fn is_signer(&self, i: usize) -> bool {
i < self.header.num_required_signatures as usize
}
pub fn signer_keys(&self) -> Vec<&Address> {
let last_key = self
.account_keys
.len()
.min(self.header.num_required_signatures as usize);
self.account_keys[..last_key].iter().collect()
}
pub fn has_duplicates(&self) -> bool {
for i in 1..self.account_keys.len() {
#[allow(clippy::arithmetic_side_effects)]
if self.account_keys[i..].contains(&self.account_keys[i - 1]) {
return true;
}
}
false
}
pub fn is_upgradeable_loader_present(&self) -> bool {
super::is_upgradeable_loader_present(&self.account_keys)
}
}
#[cfg(test)]
mod tests {
use {
super::*, crate::MESSAGE_HEADER_LENGTH, alloc::vec, core::str::FromStr,
solana_instruction::AccountMeta,
};
#[test]
fn test_message_signed_keys_len() {
let program_id = Address::default();
let id0 = Address::default();
let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, false)]);
let message = Message::new(&[ix], None);
assert_eq!(message.header.num_required_signatures, 0);
let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, true)]);
let message = Message::new(&[ix], Some(&id0));
assert_eq!(message.header.num_required_signatures, 1);
}
#[test]
fn test_message_kitchen_sink() {
let program_id0 = Address::new_unique();
let program_id1 = Address::new_unique();
let id0 = Address::default();
let id1 = Address::new_unique();
let message = Message::new(
&[
Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
Instruction::new_with_bincode(program_id1, &0, vec![AccountMeta::new(id1, true)]),
Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, false)]),
],
Some(&id1),
);
assert_eq!(
message.instructions[0],
CompiledInstruction::new(2, &0, vec![1])
);
assert_eq!(
message.instructions[1],
CompiledInstruction::new(3, &0, vec![0])
);
assert_eq!(
message.instructions[2],
CompiledInstruction::new(2, &0, vec![0])
);
}
#[test]
fn test_message_payer_first() {
let program_id = Address::default();
let payer = Address::new_unique();
let id0 = Address::default();
let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, false)]);
let message = Message::new(&[ix], Some(&payer));
assert_eq!(message.header.num_required_signatures, 1);
let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, true)]);
let message = Message::new(&[ix], Some(&payer));
assert_eq!(message.header.num_required_signatures, 2);
let ix = Instruction::new_with_bincode(
program_id,
&0,
vec![AccountMeta::new(payer, true), AccountMeta::new(id0, true)],
);
let message = Message::new(&[ix], Some(&payer));
assert_eq!(message.header.num_required_signatures, 2);
}
#[test]
fn test_program_position() {
let program_id0 = Address::default();
let program_id1 = Address::new_unique();
let id = Address::new_unique();
let message = Message::new(
&[
Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id, false)]),
Instruction::new_with_bincode(program_id1, &0, vec![AccountMeta::new(id, true)]),
],
Some(&id),
);
assert_eq!(message.program_position(0), None);
assert_eq!(message.program_position(1), Some(0));
assert_eq!(message.program_position(2), Some(1));
}
#[test]
fn test_program_ids() {
let key0 = Address::new_unique();
let key1 = Address::new_unique();
let loader2 = Address::new_unique();
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let message = Message::new_with_compiled_instructions(
1,
0,
2,
vec![key0, key1, loader2],
Hash::default(),
instructions,
);
assert_eq!(message.program_ids(), vec![&loader2]);
}
#[test]
fn test_is_instruction_account() {
let key0 = Address::new_unique();
let key1 = Address::new_unique();
let loader2 = Address::new_unique();
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let message = Message::new_with_compiled_instructions(
1,
0,
2,
vec![key0, key1, loader2],
Hash::default(),
instructions,
);
assert!(message.is_instruction_account(0));
assert!(message.is_instruction_account(1));
assert!(!message.is_instruction_account(2));
}
#[test]
fn test_message_header_len_constant() {
assert_eq!(
bincode::serialized_size(&MessageHeader::default()).unwrap() as usize,
MESSAGE_HEADER_LENGTH
);
}
#[test]
fn test_message_hash() {
let program_id0 = Address::from_str("4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM").unwrap();
let program_id1 = Address::from_str("8opHzTAnfzRpPEx21XtnrVTX28YQuCpAjcn1PczScKh").unwrap();
let id0 = Address::from_str("CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3").unwrap();
let id1 = Address::from_str("GcdayuLaLyrdmUu324nahyv33G5poQdLUEZ1nEytDeP").unwrap();
let id2 = Address::from_str("LX3EUdRUBUa3TbsYXLEUdj9J3prXkWXvLYSWyYyc2Jj").unwrap();
let id3 = Address::from_str("QRSsyMWN1yHT9ir42bgNZUNZ4PdEhcSWCrL2AryKpy5").unwrap();
let instructions = vec![
Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, true)]),
Instruction::new_with_bincode(
program_id1,
&0,
vec![AccountMeta::new_readonly(id2, false)],
),
Instruction::new_with_bincode(
program_id1,
&0,
vec![AccountMeta::new_readonly(id3, true)],
),
];
let message = Message::new(&instructions, Some(&id1));
assert_eq!(
message.hash(),
Hash::from_str("7VWCF4quo2CcWQFNUayZiorxpiR5ix8YzLebrXKf3fMF").unwrap()
)
}
#[test]
fn test_is_writable_index_saturating_behavior() {
let key0 = Address::new_unique();
let message1 = Message {
header: MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 2, num_readonly_unsigned_accounts: 0,
},
account_keys: vec![key0],
recent_blockhash: Hash::default(),
instructions: vec![],
};
assert!(!message1.is_writable_index(0));
let key_for_poc2 = Address::new_unique();
let message2 = Message {
header: MessageHeader {
num_required_signatures: 0,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 2, },
account_keys: vec![key_for_poc2],
recent_blockhash: Hash::default(),
instructions: vec![],
};
assert!(!message2.is_writable_index(0));
let message3 = Message {
header: MessageHeader {
num_required_signatures: 1, num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 2, },
account_keys: vec![key0],
recent_blockhash: Hash::default(),
instructions: vec![],
};
assert!(message3.is_writable_index(0));
let key1 = Address::new_unique();
let message4 = Message {
header: MessageHeader {
num_required_signatures: 1, num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 3, },
account_keys: vec![key0, key1],
recent_blockhash: Hash::default(),
instructions: vec![],
};
assert!(message4.is_writable_index(0));
assert!(!message4.is_writable_index(1));
let message5 = Message {
header: MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 2, num_readonly_unsigned_accounts: 3, },
account_keys: vec![key0, key1], recent_blockhash: Hash::default(),
instructions: vec![],
};
assert!(!message5.is_writable_index(0));
assert!(!message5.is_writable_index(1));
}
}