use crate::types::{CompiledInstruction, MessageAddressTableLookup, Pubkey};
use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
fn serialize_message_body(
header: &MessageHeader,
account_keys: &[Pubkey],
recent_blockhash: &[u8; 32],
instructions: &[CompiledInstruction],
) -> Result<Vec<u8>, String> {
let mut bytes = Vec::new();
bytes.push(header.num_required_signatures);
bytes.push(header.num_readonly_signed_accounts);
bytes.push(header.num_readonly_unsigned_accounts);
let len = crate::encode_length_to_compact_u16_bytes(account_keys.len())?;
bytes.extend_from_slice(&len);
for pubkey in account_keys {
bytes.extend_from_slice(pubkey.as_bytes());
}
bytes.extend_from_slice(recent_blockhash);
let len = crate::encode_length_to_compact_u16_bytes(instructions.len())?;
bytes.extend_from_slice(&len);
for ix in instructions {
bytes.push(ix.program_id_index);
let len = crate::encode_length_to_compact_u16_bytes(ix.accounts.len())?;
bytes.extend_from_slice(&len);
bytes.extend_from_slice(&ix.accounts);
let len = crate::encode_length_to_compact_u16_bytes(ix.data.len())?;
bytes.extend_from_slice(&len);
bytes.extend_from_slice(&ix.data);
}
Ok(bytes)
}
#[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
pub struct MessageHeader {
pub num_required_signatures: u8,
pub num_readonly_signed_accounts: u8,
pub num_readonly_unsigned_accounts: u8,
}
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
pub struct LegacyMessage {
pub header: MessageHeader,
pub account_keys: Vec<Pubkey>,
pub recent_blockhash: [u8; 32],
pub instructions: Vec<CompiledInstruction>,
}
impl LegacyMessage {
pub fn serialize_for_signing(&self) -> Result<Vec<u8>, String> {
serialize_message_body(
&self.header,
&self.account_keys,
&self.recent_blockhash,
&self.instructions,
)
}
}
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
pub struct VersionedMessageV0 {
pub header: MessageHeader,
pub account_keys: Vec<Pubkey>,
pub recent_blockhash: [u8; 32],
pub instructions: Vec<CompiledInstruction>,
pub address_table_lookups: Vec<MessageAddressTableLookup>,
}
impl VersionedMessageV0 {
pub fn serialize_for_signing(&self) -> Result<Vec<u8>, String> {
let mut bytes = Vec::new();
bytes.push(0x80);
let body = serialize_message_body(
&self.header,
&self.account_keys,
&self.recent_blockhash,
&self.instructions,
)?;
bytes.extend_from_slice(&body);
let lookup_len =
crate::encode_length_to_compact_u16_bytes(self.address_table_lookups.len())?;
bytes.extend_from_slice(&lookup_len);
for lookup in &self.address_table_lookups {
bytes.extend_from_slice(lookup.account_key.as_bytes());
let writable_len =
crate::encode_length_to_compact_u16_bytes(lookup.writable_indexes.len())?;
bytes.extend_from_slice(&writable_len);
bytes.extend_from_slice(&lookup.writable_indexes);
let readonly_len =
crate::encode_length_to_compact_u16_bytes(lookup.readonly_indexes.len())?;
bytes.extend_from_slice(&readonly_len);
bytes.extend_from_slice(&lookup.readonly_indexes);
}
Ok(bytes)
}
}
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
pub enum VersionedMessage {
Legacy(LegacyMessage),
V0(VersionedMessageV0),
}
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
pub struct Message {
pub header: MessageHeader,
pub account_keys: Vec<Pubkey>,
pub recent_blockhash: [u8; 32],
pub instructions: Vec<CompiledInstruction>,
}
impl Message {
pub fn new(
header: MessageHeader,
account_keys: Vec<Pubkey>,
recent_blockhash: [u8; 32],
instructions: Vec<CompiledInstruction>,
) -> Self {
Self {
header,
account_keys,
recent_blockhash,
instructions,
}
}
pub fn num_required_signatures(&self) -> u8 {
self.header.num_required_signatures
}
pub fn num_readonly_signed_accounts(&self) -> u8 {
self.header.num_readonly_signed_accounts
}
pub fn num_readonly_unsigned_accounts(&self) -> u8 {
self.header.num_readonly_unsigned_accounts
}
pub fn serialize_for_signing(&self) -> Result<Vec<u8>, String> {
serialize_message_body(
&self.header,
&self.account_keys,
&self.recent_blockhash,
&self.instructions,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{CompiledInstruction, Pubkey};
#[test]
fn test_message() {
let header = MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 1,
};
let account_keys = vec![Pubkey::new([0; 32]), Pubkey::new([1; 32])];
let recent_blockhash = [0u8; 32];
let instructions = vec![CompiledInstruction {
program_id_index: 1,
accounts: vec![0],
data: vec![],
}];
let message = Message::new(header, account_keys, recent_blockhash, instructions);
assert_eq!(message.num_required_signatures(), 1);
assert_eq!(message.num_readonly_signed_accounts(), 0);
assert_eq!(message.num_readonly_unsigned_accounts(), 1);
}
#[test]
fn test_versioned_message() {
let header = MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 1,
};
let account_keys = vec![Pubkey::new([0; 32]), Pubkey::new([1; 32])];
let recent_blockhash = [0u8; 32];
let instructions = vec![CompiledInstruction {
program_id_index: 1,
accounts: vec![0],
data: vec![],
}];
let address_table_lookups = vec![MessageAddressTableLookup::new(
Pubkey::new([2; 32]),
vec![0, 1], vec![2], )];
let v0_message = VersionedMessageV0 {
header: header.clone(),
account_keys: account_keys.clone(),
recent_blockhash,
instructions: instructions.clone(),
address_table_lookups,
};
let versioned_message = VersionedMessage::V0(v0_message);
match versioned_message {
VersionedMessage::Legacy(_) => panic!("Expected V0 message"),
VersionedMessage::V0(msg) => {
assert_eq!(msg.header.num_required_signatures, 1);
assert_eq!(msg.account_keys.len(), 2);
assert_eq!(msg.instructions.len(), 1);
assert_eq!(msg.address_table_lookups.len(), 1);
assert_eq!(
msg.address_table_lookups[0].account_key,
Pubkey::new([2; 32])
);
assert_eq!(msg.address_table_lookups[0].writable_indexes, vec![0, 1]);
assert_eq!(msg.address_table_lookups[0].readonly_indexes, vec![2]);
}
}
}
}