2019-03-24 20:20:34 -07:00
|
|
|
//! A library for generating a message from a sequence of instructions
|
2019-03-24 20:06:02 -07:00
|
|
|
|
2020-04-27 11:06:00 -07:00
|
|
|
use crate::sanitize::{Sanitize, SanitizeError};
|
2020-01-28 16:11:22 -08:00
|
|
|
use crate::{
|
|
|
|
hash::Hash,
|
|
|
|
instruction::{AccountMeta, CompiledInstruction, Instruction},
|
|
|
|
pubkey::Pubkey,
|
2020-02-20 19:04:53 -08:00
|
|
|
short_vec, system_instruction,
|
2020-01-28 16:11:22 -08:00
|
|
|
};
|
2019-03-24 20:06:02 -07:00
|
|
|
use itertools::Itertools;
|
2020-03-05 10:57:12 -08:00
|
|
|
use std::convert::TryFrom;
|
2019-03-24 20:06:02 -07:00
|
|
|
|
|
|
|
fn position(keys: &[Pubkey], key: &Pubkey) -> u8 {
|
|
|
|
keys.iter().position(|k| k == key).unwrap() as u8
|
|
|
|
}
|
|
|
|
|
2020-03-11 14:37:23 -07:00
|
|
|
fn compile_instruction(ix: &Instruction, keys: &[Pubkey]) -> CompiledInstruction {
|
2019-03-24 20:06:02 -07:00
|
|
|
let accounts: Vec<_> = ix
|
|
|
|
.accounts
|
|
|
|
.iter()
|
|
|
|
.map(|account_meta| position(keys, &account_meta.pubkey))
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
CompiledInstruction {
|
2019-07-01 17:34:22 -07:00
|
|
|
program_id_index: position(keys, &ix.program_id),
|
2019-03-24 20:06:02 -07:00
|
|
|
data: ix.data.clone(),
|
|
|
|
accounts,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-11 14:37:23 -07:00
|
|
|
fn compile_instructions(ixs: &[Instruction], keys: &[Pubkey]) -> Vec<CompiledInstruction> {
|
|
|
|
ixs.iter().map(|ix| compile_instruction(ix, keys)).collect()
|
2019-03-24 20:06:02 -07:00
|
|
|
}
|
|
|
|
|
2019-11-05 08:38:35 -08:00
|
|
|
/// A helper struct to collect pubkeys referenced by a set of instructions and read-only counts
|
2019-05-23 15:19:53 -07:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
|
struct InstructionKeys {
|
|
|
|
pub signed_keys: Vec<Pubkey>,
|
|
|
|
pub unsigned_keys: Vec<Pubkey>,
|
2019-11-05 08:38:35 -08:00
|
|
|
pub num_readonly_signed_accounts: u8,
|
|
|
|
pub num_readonly_unsigned_accounts: u8,
|
2019-05-23 15:19:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl InstructionKeys {
|
|
|
|
fn new(
|
|
|
|
signed_keys: Vec<Pubkey>,
|
|
|
|
unsigned_keys: Vec<Pubkey>,
|
2019-11-05 08:38:35 -08:00
|
|
|
num_readonly_signed_accounts: u8,
|
|
|
|
num_readonly_unsigned_accounts: u8,
|
2019-05-23 15:19:53 -07:00
|
|
|
) -> Self {
|
|
|
|
Self {
|
|
|
|
signed_keys,
|
|
|
|
unsigned_keys,
|
2019-11-05 08:38:35 -08:00
|
|
|
num_readonly_signed_accounts,
|
|
|
|
num_readonly_unsigned_accounts,
|
2019-05-23 15:19:53 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-15 12:23:09 -07:00
|
|
|
/// Return the pubkey of the first writable signer in the given set of instructions.
|
|
|
|
fn find_writable_signer(instructions: &[Instruction]) -> Option<&Pubkey> {
|
|
|
|
for instruction in instructions {
|
|
|
|
for account in &instruction.accounts {
|
|
|
|
if account.is_signer && account.is_writable {
|
|
|
|
return Some(&account.pubkey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2019-05-23 15:19:53 -07:00
|
|
|
/// Return pubkeys referenced by all instructions, with the ones needing signatures first. If the
|
2019-11-05 08:38:35 -08:00
|
|
|
/// payer key is provided, it is always placed first in the list of signed keys. Read-only signed
|
|
|
|
/// accounts are placed last in the set of signed accounts. Read-only unsigned accounts,
|
2019-05-23 15:19:53 -07:00
|
|
|
/// including program ids, are placed last in the set. No duplicates and order is preserved.
|
|
|
|
fn get_keys(instructions: &[Instruction], payer: Option<&Pubkey>) -> InstructionKeys {
|
|
|
|
let programs: Vec<_> = get_program_ids(instructions)
|
|
|
|
.iter()
|
|
|
|
.map(|program_id| AccountMeta {
|
|
|
|
pubkey: *program_id,
|
|
|
|
is_signer: false,
|
2019-11-05 08:38:35 -08:00
|
|
|
is_writable: false,
|
2019-05-23 15:19:53 -07:00
|
|
|
})
|
|
|
|
.collect();
|
2019-03-24 20:20:34 -07:00
|
|
|
let mut keys_and_signed: Vec<_> = instructions
|
|
|
|
.iter()
|
|
|
|
.flat_map(|ix| ix.accounts.iter())
|
|
|
|
.collect();
|
2019-05-23 15:19:53 -07:00
|
|
|
keys_and_signed.extend(&programs);
|
|
|
|
keys_and_signed.sort_by(|x, y| {
|
|
|
|
y.is_signer
|
|
|
|
.cmp(&x.is_signer)
|
2019-11-05 08:38:35 -08:00
|
|
|
.then(y.is_writable.cmp(&x.is_writable))
|
2019-05-23 15:19:53 -07:00
|
|
|
});
|
2019-03-24 20:20:34 -07:00
|
|
|
|
2019-05-07 18:48:31 -07:00
|
|
|
let payer_account_meta;
|
|
|
|
if let Some(payer) = payer {
|
|
|
|
payer_account_meta = AccountMeta {
|
|
|
|
pubkey: *payer,
|
|
|
|
is_signer: true,
|
2019-11-05 08:38:35 -08:00
|
|
|
is_writable: true,
|
2019-05-07 18:48:31 -07:00
|
|
|
};
|
|
|
|
keys_and_signed.insert(0, &payer_account_meta);
|
|
|
|
}
|
|
|
|
|
2020-05-29 23:17:44 -07:00
|
|
|
let mut unique_metas: Vec<AccountMeta> = vec![];
|
|
|
|
for account_meta in keys_and_signed {
|
|
|
|
// Promote to writable if a later AccountMeta requires it
|
|
|
|
if let Some(x) = unique_metas
|
|
|
|
.iter_mut()
|
|
|
|
.find(|x| x.pubkey == account_meta.pubkey)
|
|
|
|
{
|
|
|
|
x.is_writable |= account_meta.is_writable;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
unique_metas.push(account_meta.clone());
|
|
|
|
}
|
|
|
|
|
2019-03-24 20:20:34 -07:00
|
|
|
let mut signed_keys = vec![];
|
|
|
|
let mut unsigned_keys = vec![];
|
2019-11-05 08:38:35 -08:00
|
|
|
let mut num_readonly_signed_accounts = 0;
|
|
|
|
let mut num_readonly_unsigned_accounts = 0;
|
2020-05-29 23:17:44 -07:00
|
|
|
for account_meta in unique_metas {
|
2019-03-24 20:20:34 -07:00
|
|
|
if account_meta.is_signer {
|
|
|
|
signed_keys.push(account_meta.pubkey);
|
2019-11-05 08:38:35 -08:00
|
|
|
if !account_meta.is_writable {
|
|
|
|
num_readonly_signed_accounts += 1;
|
2019-05-23 15:19:53 -07:00
|
|
|
}
|
2019-03-24 20:20:34 -07:00
|
|
|
} else {
|
|
|
|
unsigned_keys.push(account_meta.pubkey);
|
2019-11-05 08:38:35 -08:00
|
|
|
if !account_meta.is_writable {
|
|
|
|
num_readonly_unsigned_accounts += 1;
|
2019-05-23 15:19:53 -07:00
|
|
|
}
|
2019-03-24 20:06:02 -07:00
|
|
|
}
|
|
|
|
}
|
2019-05-23 15:19:53 -07:00
|
|
|
InstructionKeys::new(
|
|
|
|
signed_keys,
|
|
|
|
unsigned_keys,
|
2019-11-05 08:38:35 -08:00
|
|
|
num_readonly_signed_accounts,
|
|
|
|
num_readonly_unsigned_accounts,
|
2019-05-23 15:19:53 -07:00
|
|
|
)
|
2019-03-24 20:20:34 -07:00
|
|
|
}
|
2019-03-24 20:06:02 -07:00
|
|
|
|
2019-03-24 20:20:34 -07:00
|
|
|
/// Return program ids referenced by all instructions. No duplicates and order is preserved.
|
|
|
|
fn get_program_ids(instructions: &[Instruction]) -> Vec<Pubkey> {
|
|
|
|
instructions
|
|
|
|
.iter()
|
2019-07-01 17:34:22 -07:00
|
|
|
.map(|ix| ix.program_id)
|
2019-03-24 20:20:34 -07:00
|
|
|
.unique()
|
|
|
|
.collect()
|
2019-03-24 20:06:02 -07:00
|
|
|
}
|
2019-03-24 19:55:32 -07:00
|
|
|
|
2019-05-22 15:23:16 -07:00
|
|
|
#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)]
|
2019-11-19 08:39:55 -08:00
|
|
|
#[serde(rename_all = "camelCase")]
|
2019-05-22 15:23:16 -07:00
|
|
|
pub struct MessageHeader {
|
2019-03-29 12:21:32 -07:00
|
|
|
/// The number of signatures required for this message to be considered valid. The
|
|
|
|
/// signatures must match the first `num_required_signatures` of `account_keys`.
|
2019-10-18 09:48:35 -07:00
|
|
|
/// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
|
2019-03-29 12:21:32 -07:00
|
|
|
pub num_required_signatures: u8,
|
2019-03-29 09:05:06 -07:00
|
|
|
|
2019-11-05 08:38:35 -08:00
|
|
|
/// The last num_readonly_signed_accounts of the signed keys are read-only accounts. Programs
|
|
|
|
/// may process multiple transactions that load read-only accounts within a single PoH entry,
|
|
|
|
/// but are not permitted to credit or debit lamports or modify account data. Transactions
|
|
|
|
/// targeting the same read-write account are evaluated sequentially.
|
|
|
|
pub num_readonly_signed_accounts: u8,
|
2019-05-22 15:23:16 -07:00
|
|
|
|
2019-11-05 08:38:35 -08:00
|
|
|
/// The last num_readonly_unsigned_accounts of the unsigned keys are read-only accounts.
|
|
|
|
pub num_readonly_unsigned_accounts: u8,
|
2019-05-22 15:23:16 -07:00
|
|
|
}
|
|
|
|
|
2020-05-15 12:23:09 -07:00
|
|
|
#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)]
|
2019-11-19 08:39:55 -08:00
|
|
|
#[serde(rename_all = "camelCase")]
|
2019-05-22 15:23:16 -07:00
|
|
|
pub struct Message {
|
2019-11-05 08:38:35 -08:00
|
|
|
/// The message header, identifying signed and read-only `account_keys`
|
2019-10-18 09:48:35 -07:00
|
|
|
/// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
|
2019-05-22 15:23:16 -07:00
|
|
|
pub header: MessageHeader,
|
|
|
|
|
2019-03-29 09:05:06 -07:00
|
|
|
/// All the account keys used by this transaction
|
2019-03-25 08:15:16 -07:00
|
|
|
#[serde(with = "short_vec")]
|
2019-03-24 19:55:32 -07:00
|
|
|
pub account_keys: Vec<Pubkey>,
|
2019-03-29 09:05:06 -07:00
|
|
|
|
|
|
|
/// The id of a recent ledger entry.
|
2019-03-24 19:55:32 -07:00
|
|
|
pub recent_blockhash: Hash,
|
2019-03-29 09:05:06 -07:00
|
|
|
|
|
|
|
/// Programs that will be executed in sequence and committed in one atomic transaction if all
|
|
|
|
/// succeed.
|
2019-03-25 08:15:16 -07:00
|
|
|
#[serde(with = "short_vec")]
|
2019-03-24 19:55:32 -07:00
|
|
|
pub instructions: Vec<CompiledInstruction>,
|
|
|
|
}
|
2019-03-24 20:06:02 -07:00
|
|
|
|
2020-04-27 11:06:00 -07:00
|
|
|
impl Sanitize for Message {
|
|
|
|
fn sanitize(&self) -> std::result::Result<(), SanitizeError> {
|
2020-05-01 17:23:33 -07:00
|
|
|
// signing area and read-only non-signing area should not overlap
|
|
|
|
if self.header.num_required_signatures as usize
|
|
|
|
+ self.header.num_readonly_unsigned_accounts as usize
|
2020-04-27 11:06:00 -07:00
|
|
|
> self.account_keys.len()
|
|
|
|
{
|
|
|
|
return Err(SanitizeError::IndexOutOfBounds);
|
|
|
|
}
|
2020-05-01 17:23:33 -07:00
|
|
|
|
|
|
|
// there should be at least 1 RW fee-payer account.
|
|
|
|
if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
|
|
|
|
return Err(SanitizeError::IndexOutOfBounds);
|
|
|
|
}
|
|
|
|
|
2020-04-27 11:06:00 -07:00
|
|
|
for ci in &self.instructions {
|
|
|
|
if ci.program_id_index as usize >= self.account_keys.len() {
|
|
|
|
return Err(SanitizeError::IndexOutOfBounds);
|
|
|
|
}
|
2020-05-01 17:23:33 -07:00
|
|
|
// A program cannot be a payer.
|
|
|
|
if ci.program_id_index == 0 {
|
|
|
|
return Err(SanitizeError::IndexOutOfBounds);
|
|
|
|
}
|
2020-04-27 11:06:00 -07:00
|
|
|
for ai in &ci.accounts {
|
|
|
|
if *ai as usize >= self.account_keys.len() {
|
|
|
|
return Err(SanitizeError::IndexOutOfBounds);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-04-29 18:12:51 -07:00
|
|
|
self.account_keys.sanitize()?;
|
|
|
|
self.recent_blockhash.sanitize()?;
|
|
|
|
self.instructions.sanitize()?;
|
2020-04-27 11:06:00 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-24 20:06:02 -07:00
|
|
|
impl Message {
|
2019-03-29 09:05:06 -07:00
|
|
|
pub fn new_with_compiled_instructions(
|
2019-03-29 12:21:32 -07:00
|
|
|
num_required_signatures: u8,
|
2019-11-05 08:38:35 -08:00
|
|
|
num_readonly_signed_accounts: u8,
|
|
|
|
num_readonly_unsigned_accounts: u8,
|
2019-03-29 09:05:06 -07:00
|
|
|
account_keys: Vec<Pubkey>,
|
|
|
|
recent_blockhash: Hash,
|
|
|
|
instructions: Vec<CompiledInstruction>,
|
|
|
|
) -> Self {
|
|
|
|
Self {
|
2019-05-22 15:23:16 -07:00
|
|
|
header: MessageHeader {
|
|
|
|
num_required_signatures,
|
2019-11-05 08:38:35 -08:00
|
|
|
num_readonly_signed_accounts,
|
|
|
|
num_readonly_unsigned_accounts,
|
2019-05-22 15:23:16 -07:00
|
|
|
},
|
2019-03-29 09:05:06 -07:00
|
|
|
account_keys,
|
|
|
|
recent_blockhash,
|
|
|
|
instructions,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-11 14:37:23 -07:00
|
|
|
pub fn new(instructions: &[Instruction]) -> Self {
|
2020-05-15 12:23:09 -07:00
|
|
|
let payer = find_writable_signer(instructions).expect("no suitable key for fee-payer");
|
|
|
|
Self::new_with_payer(instructions, Some(payer))
|
2019-05-07 15:00:54 -07:00
|
|
|
}
|
|
|
|
|
2020-03-11 14:37:23 -07:00
|
|
|
pub fn new_with_payer(instructions: &[Instruction], payer: Option<&Pubkey>) -> Self {
|
2019-05-23 15:19:53 -07:00
|
|
|
let InstructionKeys {
|
|
|
|
mut signed_keys,
|
|
|
|
unsigned_keys,
|
2019-11-05 08:38:35 -08:00
|
|
|
num_readonly_signed_accounts,
|
|
|
|
num_readonly_unsigned_accounts,
|
2020-03-11 14:37:23 -07:00
|
|
|
} = get_keys(instructions, payer);
|
2019-03-29 12:21:32 -07:00
|
|
|
let num_required_signatures = signed_keys.len() as u8;
|
2019-03-24 20:20:34 -07:00
|
|
|
signed_keys.extend(&unsigned_keys);
|
2019-05-22 15:23:16 -07:00
|
|
|
let instructions = compile_instructions(instructions, &signed_keys);
|
2019-03-29 09:05:06 -07:00
|
|
|
Self::new_with_compiled_instructions(
|
2019-03-29 12:21:32 -07:00
|
|
|
num_required_signatures,
|
2019-11-05 08:38:35 -08:00
|
|
|
num_readonly_signed_accounts,
|
|
|
|
num_readonly_unsigned_accounts,
|
2019-03-29 09:05:06 -07:00
|
|
|
signed_keys,
|
|
|
|
Hash::default(),
|
2019-03-24 20:20:34 -07:00
|
|
|
instructions,
|
2019-03-29 09:05:06 -07:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-02-20 19:04:53 -08:00
|
|
|
pub fn new_with_nonce(
|
|
|
|
mut instructions: Vec<Instruction>,
|
|
|
|
payer: Option<&Pubkey>,
|
|
|
|
nonce_account_pubkey: &Pubkey,
|
|
|
|
nonce_authority_pubkey: &Pubkey,
|
|
|
|
) -> Self {
|
|
|
|
let nonce_ix = system_instruction::advance_nonce_account(
|
|
|
|
&nonce_account_pubkey,
|
|
|
|
&nonce_authority_pubkey,
|
|
|
|
);
|
|
|
|
instructions.insert(0, nonce_ix);
|
2020-03-11 14:37:23 -07:00
|
|
|
Self::new_with_payer(&instructions, payer)
|
2020-02-20 19:04:53 -08:00
|
|
|
}
|
|
|
|
|
2020-02-12 14:38:51 -08:00
|
|
|
pub fn serialize(&self) -> Vec<u8> {
|
|
|
|
bincode::serialize(self).unwrap()
|
|
|
|
}
|
|
|
|
|
2019-05-23 15:19:53 -07:00
|
|
|
pub fn program_ids(&self) -> Vec<&Pubkey> {
|
|
|
|
self.instructions
|
|
|
|
.iter()
|
2019-07-01 17:34:22 -07:00
|
|
|
.map(|ix| &self.account_keys[ix.program_id_index as usize])
|
2019-05-23 15:19:53 -07:00
|
|
|
.collect()
|
2019-05-22 15:23:16 -07:00
|
|
|
}
|
|
|
|
|
2020-03-05 10:57:12 -08:00
|
|
|
pub fn is_key_passed_to_program(&self, index: usize) -> bool {
|
|
|
|
if let Ok(index) = u8::try_from(index) {
|
|
|
|
for ix in self.instructions.iter() {
|
|
|
|
if ix.accounts.contains(&index) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2019-05-23 15:19:53 -07:00
|
|
|
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])
|
|
|
|
}
|
|
|
|
|
2019-11-05 08:38:35 -08:00
|
|
|
pub fn is_writable(&self, i: usize) -> bool {
|
|
|
|
i < (self.header.num_required_signatures - self.header.num_readonly_signed_accounts)
|
2019-05-23 15:19:53 -07:00
|
|
|
as usize
|
|
|
|
|| (i >= self.header.num_required_signatures as usize
|
|
|
|
&& i < self.account_keys.len()
|
2019-11-05 08:38:35 -08:00
|
|
|
- self.header.num_readonly_unsigned_accounts as usize)
|
2019-05-23 15:19:53 -07:00
|
|
|
}
|
|
|
|
|
2020-03-09 22:03:09 -07:00
|
|
|
pub fn is_signer(&self, i: usize) -> bool {
|
|
|
|
i < self.header.num_required_signatures as usize
|
|
|
|
}
|
|
|
|
|
2019-05-23 15:19:53 -07:00
|
|
|
pub fn get_account_keys_by_lock_type(&self) -> (Vec<&Pubkey>, Vec<&Pubkey>) {
|
2019-11-05 08:38:35 -08:00
|
|
|
let mut writable_keys = vec![];
|
|
|
|
let mut readonly_keys = vec![];
|
2019-05-23 15:19:53 -07:00
|
|
|
for (i, key) in self.account_keys.iter().enumerate() {
|
2019-11-05 08:38:35 -08:00
|
|
|
if self.is_writable(i) {
|
|
|
|
writable_keys.push(key);
|
2019-05-23 15:19:53 -07:00
|
|
|
} else {
|
2019-11-05 08:38:35 -08:00
|
|
|
readonly_keys.push(key);
|
2019-05-23 15:19:53 -07:00
|
|
|
}
|
|
|
|
}
|
2019-11-05 08:38:35 -08:00
|
|
|
(writable_keys, readonly_keys)
|
2019-03-24 20:06:02 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2020-01-28 16:11:22 -08:00
|
|
|
use crate::{
|
|
|
|
instruction::AccountMeta,
|
2020-02-20 13:28:55 -08:00
|
|
|
signature::{Keypair, Signer},
|
2020-01-28 16:11:22 -08:00
|
|
|
};
|
2019-03-24 20:06:02 -07:00
|
|
|
|
|
|
|
#[test]
|
2019-03-24 20:20:34 -07:00
|
|
|
fn test_message_unique_program_ids() {
|
2019-03-24 20:06:02 -07:00
|
|
|
let program_id0 = Pubkey::default();
|
2019-03-24 20:20:34 -07:00
|
|
|
let program_ids = get_program_ids(&[
|
2019-03-24 20:06:02 -07:00
|
|
|
Instruction::new(program_id0, &0, vec![]),
|
|
|
|
Instruction::new(program_id0, &0, vec![]),
|
2019-03-24 20:20:34 -07:00
|
|
|
]);
|
2019-03-24 20:06:02 -07:00
|
|
|
assert_eq!(program_ids, vec![program_id0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2019-03-24 20:20:34 -07:00
|
|
|
fn test_message_unique_program_ids_not_adjacent() {
|
2019-03-24 20:06:02 -07:00
|
|
|
let program_id0 = Pubkey::default();
|
2019-03-30 20:37:33 -07:00
|
|
|
let program_id1 = Pubkey::new_rand();
|
2019-03-24 20:20:34 -07:00
|
|
|
let program_ids = get_program_ids(&[
|
2019-03-24 20:06:02 -07:00
|
|
|
Instruction::new(program_id0, &0, vec![]),
|
|
|
|
Instruction::new(program_id1, &0, vec![]),
|
|
|
|
Instruction::new(program_id0, &0, vec![]),
|
2019-03-24 20:20:34 -07:00
|
|
|
]);
|
2019-03-24 20:06:02 -07:00
|
|
|
assert_eq!(program_ids, vec![program_id0, program_id1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2019-03-24 20:20:34 -07:00
|
|
|
fn test_message_unique_program_ids_order_preserved() {
|
2019-03-30 20:37:33 -07:00
|
|
|
let program_id0 = Pubkey::new_rand();
|
2019-03-24 20:06:02 -07:00
|
|
|
let program_id1 = Pubkey::default(); // Key less than program_id0
|
2019-03-24 20:20:34 -07:00
|
|
|
let program_ids = get_program_ids(&[
|
2019-03-24 20:06:02 -07:00
|
|
|
Instruction::new(program_id0, &0, vec![]),
|
|
|
|
Instruction::new(program_id1, &0, vec![]),
|
|
|
|
Instruction::new(program_id0, &0, vec![]),
|
2019-03-24 20:20:34 -07:00
|
|
|
]);
|
2019-03-24 20:06:02 -07:00
|
|
|
assert_eq!(program_ids, vec![program_id0, program_id1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2019-03-24 20:20:34 -07:00
|
|
|
fn test_message_unique_keys_both_signed() {
|
2019-03-24 20:06:02 -07:00
|
|
|
let program_id = Pubkey::default();
|
|
|
|
let id0 = Pubkey::default();
|
2019-05-07 18:48:31 -07:00
|
|
|
let keys = get_keys(
|
|
|
|
&[
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]),
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]),
|
|
|
|
],
|
|
|
|
None,
|
|
|
|
);
|
2019-05-23 15:19:53 -07:00
|
|
|
assert_eq!(keys, InstructionKeys::new(vec![id0], vec![], 0, 0));
|
2019-05-07 18:48:31 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_message_unique_keys_signed_and_payer() {
|
|
|
|
let program_id = Pubkey::default();
|
|
|
|
let id0 = Pubkey::default();
|
|
|
|
let keys = get_keys(
|
|
|
|
&[Instruction::new(
|
|
|
|
program_id,
|
|
|
|
&0,
|
|
|
|
vec![AccountMeta::new(id0, true)],
|
|
|
|
)],
|
|
|
|
Some(&id0),
|
|
|
|
);
|
2019-05-23 15:19:53 -07:00
|
|
|
assert_eq!(keys, InstructionKeys::new(vec![id0], vec![], 0, 0));
|
2019-05-07 18:48:31 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_message_unique_keys_unsigned_and_payer() {
|
|
|
|
let program_id = Pubkey::default();
|
|
|
|
let id0 = Pubkey::default();
|
|
|
|
let keys = get_keys(
|
|
|
|
&[Instruction::new(
|
|
|
|
program_id,
|
|
|
|
&0,
|
|
|
|
vec![AccountMeta::new(id0, false)],
|
|
|
|
)],
|
|
|
|
Some(&id0),
|
|
|
|
);
|
2019-05-23 15:19:53 -07:00
|
|
|
assert_eq!(keys, InstructionKeys::new(vec![id0], vec![], 0, 0));
|
2019-03-24 20:06:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2019-03-24 20:20:34 -07:00
|
|
|
fn test_message_unique_keys_one_signed() {
|
2019-03-24 20:06:02 -07:00
|
|
|
let program_id = Pubkey::default();
|
|
|
|
let id0 = Pubkey::default();
|
2019-05-07 18:48:31 -07:00
|
|
|
let keys = get_keys(
|
|
|
|
&[
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]),
|
|
|
|
],
|
|
|
|
None,
|
|
|
|
);
|
2019-05-23 15:19:53 -07:00
|
|
|
assert_eq!(keys, InstructionKeys::new(vec![id0], vec![], 0, 0));
|
2019-03-24 20:06:02 -07:00
|
|
|
}
|
|
|
|
|
2020-05-29 23:17:44 -07:00
|
|
|
#[test]
|
|
|
|
fn test_message_unique_keys_one_readonly_signed() {
|
|
|
|
let program_id = Pubkey::default();
|
|
|
|
let id0 = Pubkey::default();
|
|
|
|
let keys = get_keys(
|
|
|
|
&[
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id0, true)]),
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]),
|
|
|
|
],
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
|
|
|
|
// Ensure the key is no longer readonly
|
|
|
|
assert_eq!(keys, InstructionKeys::new(vec![id0], vec![], 0, 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_message_unique_keys_one_readonly_unsigned() {
|
|
|
|
let program_id = Pubkey::default();
|
|
|
|
let id0 = Pubkey::default();
|
|
|
|
let keys = get_keys(
|
|
|
|
&[
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id0, false)]),
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
|
|
|
|
],
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
|
|
|
|
// Ensure the key is no longer readonly
|
|
|
|
assert_eq!(keys, InstructionKeys::new(vec![], vec![id0], 0, 0));
|
|
|
|
}
|
|
|
|
|
2019-03-24 20:06:02 -07:00
|
|
|
#[test]
|
2019-03-24 20:20:34 -07:00
|
|
|
fn test_message_unique_keys_order_preserved() {
|
2019-03-24 20:06:02 -07:00
|
|
|
let program_id = Pubkey::default();
|
2019-03-30 20:37:33 -07:00
|
|
|
let id0 = Pubkey::new_rand();
|
2019-03-24 20:06:02 -07:00
|
|
|
let id1 = Pubkey::default(); // Key less than id0
|
2019-05-07 18:48:31 -07:00
|
|
|
let keys = get_keys(
|
|
|
|
&[
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new(id1, false)]),
|
|
|
|
],
|
|
|
|
None,
|
|
|
|
);
|
2019-05-23 15:19:53 -07:00
|
|
|
assert_eq!(keys, InstructionKeys::new(vec![], vec![id0, id1], 0, 0));
|
2019-03-24 20:06:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2019-03-24 20:20:34 -07:00
|
|
|
fn test_message_unique_keys_not_adjacent() {
|
2019-03-24 20:06:02 -07:00
|
|
|
let program_id = Pubkey::default();
|
|
|
|
let id0 = Pubkey::default();
|
2019-03-30 20:37:33 -07:00
|
|
|
let id1 = Pubkey::new_rand();
|
2019-05-07 18:48:31 -07:00
|
|
|
let keys = get_keys(
|
|
|
|
&[
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new(id1, false)]),
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]),
|
|
|
|
],
|
|
|
|
None,
|
|
|
|
);
|
2019-05-23 15:19:53 -07:00
|
|
|
assert_eq!(keys, InstructionKeys::new(vec![id0], vec![id1], 0, 0));
|
2019-03-24 20:06:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2019-03-24 20:20:34 -07:00
|
|
|
fn test_message_signed_keys_first() {
|
2019-03-24 20:06:02 -07:00
|
|
|
let program_id = Pubkey::default();
|
|
|
|
let id0 = Pubkey::default();
|
2019-03-30 20:37:33 -07:00
|
|
|
let id1 = Pubkey::new_rand();
|
2019-05-07 18:48:31 -07:00
|
|
|
let keys = get_keys(
|
|
|
|
&[
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new(id1, true)]),
|
|
|
|
],
|
|
|
|
None,
|
|
|
|
);
|
2019-05-23 15:19:53 -07:00
|
|
|
assert_eq!(keys, InstructionKeys::new(vec![id1], vec![id0], 0, 0));
|
2019-03-24 20:06:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
// Ensure there's a way to calculate the number of required signatures.
|
2019-03-24 20:20:34 -07:00
|
|
|
fn test_message_signed_keys_len() {
|
2019-03-24 20:06:02 -07:00
|
|
|
let program_id = Pubkey::default();
|
|
|
|
let id0 = Pubkey::default();
|
|
|
|
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]);
|
2020-05-15 12:23:09 -07:00
|
|
|
let message = Message::new_with_payer(&[ix], None);
|
2019-05-22 15:23:16 -07:00
|
|
|
assert_eq!(message.header.num_required_signatures, 0);
|
2019-03-24 20:06:02 -07:00
|
|
|
|
|
|
|
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]);
|
2020-03-11 14:37:23 -07:00
|
|
|
let message = Message::new(&[ix]);
|
2019-05-22 15:23:16 -07:00
|
|
|
assert_eq!(message.header.num_required_signatures, 1);
|
2019-03-24 20:06:02 -07:00
|
|
|
}
|
|
|
|
|
2019-05-23 15:19:53 -07:00
|
|
|
#[test]
|
2019-11-05 08:38:35 -08:00
|
|
|
fn test_message_readonly_keys_last() {
|
2019-05-23 15:19:53 -07:00
|
|
|
let program_id = Pubkey::default();
|
|
|
|
let id0 = Pubkey::default(); // Identical key/program_id should be de-duped
|
|
|
|
let id1 = Pubkey::new_rand();
|
|
|
|
let id2 = Pubkey::new_rand();
|
|
|
|
let id3 = Pubkey::new_rand();
|
|
|
|
let keys = get_keys(
|
|
|
|
&[
|
2019-11-05 08:38:35 -08:00
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id0, false)]),
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id1, true)]),
|
2019-05-23 15:19:53 -07:00
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new(id2, false)]),
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new(id3, true)]),
|
|
|
|
],
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
keys,
|
|
|
|
InstructionKeys::new(vec![id3, id1], vec![id2, id0], 1, 1)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-03-24 20:06:02 -07:00
|
|
|
#[test]
|
2019-03-24 20:20:34 -07:00
|
|
|
fn test_message_kitchen_sink() {
|
2019-05-22 15:23:16 -07:00
|
|
|
let program_id0 = Pubkey::new_rand();
|
2019-03-30 20:37:33 -07:00
|
|
|
let program_id1 = Pubkey::new_rand();
|
2019-03-24 20:06:02 -07:00
|
|
|
let id0 = Pubkey::default();
|
|
|
|
let keypair1 = Keypair::new();
|
|
|
|
let id1 = keypair1.pubkey();
|
2020-03-11 14:37:23 -07:00
|
|
|
let message = Message::new(&[
|
2019-03-24 20:06:02 -07:00
|
|
|
Instruction::new(program_id0, &0, vec![AccountMeta::new(id0, false)]),
|
|
|
|
Instruction::new(program_id1, &0, vec![AccountMeta::new(id1, true)]),
|
|
|
|
Instruction::new(program_id0, &0, vec![AccountMeta::new(id1, false)]),
|
2019-03-24 20:20:34 -07:00
|
|
|
]);
|
|
|
|
assert_eq!(
|
|
|
|
message.instructions[0],
|
2019-05-22 15:23:16 -07:00
|
|
|
CompiledInstruction::new(2, &0, vec![1])
|
2019-03-24 20:20:34 -07:00
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
message.instructions[1],
|
2019-05-22 15:23:16 -07:00
|
|
|
CompiledInstruction::new(3, &0, vec![0])
|
2019-03-24 20:20:34 -07:00
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
message.instructions[2],
|
2019-05-22 15:23:16 -07:00
|
|
|
CompiledInstruction::new(2, &0, vec![0])
|
2019-03-24 20:20:34 -07:00
|
|
|
);
|
2019-03-24 20:06:02 -07:00
|
|
|
}
|
2019-05-07 15:00:54 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_message_payer_first() {
|
|
|
|
let program_id = Pubkey::default();
|
|
|
|
let payer = Pubkey::new_rand();
|
|
|
|
let id0 = Pubkey::default();
|
|
|
|
|
|
|
|
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]);
|
2020-03-11 14:37:23 -07:00
|
|
|
let message = Message::new_with_payer(&[ix], Some(&payer));
|
2019-05-22 15:23:16 -07:00
|
|
|
assert_eq!(message.header.num_required_signatures, 1);
|
2019-05-07 15:00:54 -07:00
|
|
|
|
|
|
|
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]);
|
2020-03-11 14:37:23 -07:00
|
|
|
let message = Message::new_with_payer(&[ix], Some(&payer));
|
2019-05-22 15:23:16 -07:00
|
|
|
assert_eq!(message.header.num_required_signatures, 2);
|
2019-05-07 15:00:54 -07:00
|
|
|
|
|
|
|
let ix = Instruction::new(
|
|
|
|
program_id,
|
|
|
|
&0,
|
|
|
|
vec![AccountMeta::new(payer, true), AccountMeta::new(id0, true)],
|
|
|
|
);
|
2020-03-11 14:37:23 -07:00
|
|
|
let message = Message::new_with_payer(&[ix], Some(&payer));
|
2019-05-22 15:23:16 -07:00
|
|
|
assert_eq!(message.header.num_required_signatures, 2);
|
2019-05-07 15:00:54 -07:00
|
|
|
}
|
|
|
|
|
2019-05-23 15:19:53 -07:00
|
|
|
#[test]
|
|
|
|
fn test_message_program_last() {
|
|
|
|
let program_id = Pubkey::default();
|
|
|
|
let id0 = Pubkey::new_rand();
|
|
|
|
let id1 = Pubkey::new_rand();
|
|
|
|
let keys = get_keys(
|
|
|
|
&[
|
2019-11-05 08:38:35 -08:00
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id0, false)]),
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id1, true)]),
|
2019-05-23 15:19:53 -07:00
|
|
|
],
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
keys,
|
|
|
|
InstructionKeys::new(vec![id1], vec![id0, program_id], 1, 2)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_program_position() {
|
|
|
|
let program_id0 = Pubkey::default();
|
|
|
|
let program_id1 = Pubkey::new_rand();
|
|
|
|
let id = Pubkey::new_rand();
|
2020-03-11 14:37:23 -07:00
|
|
|
let message = Message::new(&[
|
2019-05-23 15:19:53 -07:00
|
|
|
Instruction::new(program_id0, &0, vec![AccountMeta::new(id, false)]),
|
|
|
|
Instruction::new(program_id1, &0, vec![AccountMeta::new(id, true)]),
|
|
|
|
]);
|
|
|
|
assert_eq!(message.program_position(0), None);
|
|
|
|
assert_eq!(message.program_position(1), Some(0));
|
|
|
|
assert_eq!(message.program_position(2), Some(1));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2019-11-05 08:38:35 -08:00
|
|
|
fn test_is_writable() {
|
2019-05-23 15:19:53 -07:00
|
|
|
let key0 = Pubkey::new_rand();
|
|
|
|
let key1 = Pubkey::new_rand();
|
|
|
|
let key2 = Pubkey::new_rand();
|
|
|
|
let key3 = Pubkey::new_rand();
|
|
|
|
let key4 = Pubkey::new_rand();
|
|
|
|
let key5 = Pubkey::new_rand();
|
|
|
|
|
|
|
|
let message = Message {
|
|
|
|
header: MessageHeader {
|
|
|
|
num_required_signatures: 3,
|
2019-11-05 08:38:35 -08:00
|
|
|
num_readonly_signed_accounts: 2,
|
|
|
|
num_readonly_unsigned_accounts: 1,
|
2019-05-23 15:19:53 -07:00
|
|
|
},
|
|
|
|
account_keys: vec![key0, key1, key2, key3, key4, key5],
|
|
|
|
recent_blockhash: Hash::default(),
|
|
|
|
instructions: vec![],
|
|
|
|
};
|
2019-11-05 08:38:35 -08:00
|
|
|
assert_eq!(message.is_writable(0), true);
|
|
|
|
assert_eq!(message.is_writable(1), false);
|
|
|
|
assert_eq!(message.is_writable(2), false);
|
|
|
|
assert_eq!(message.is_writable(3), true);
|
|
|
|
assert_eq!(message.is_writable(4), true);
|
|
|
|
assert_eq!(message.is_writable(5), false);
|
2019-05-23 15:19:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_account_keys_by_lock_type() {
|
|
|
|
let program_id = Pubkey::default();
|
|
|
|
let id0 = Pubkey::new_rand();
|
|
|
|
let id1 = Pubkey::new_rand();
|
|
|
|
let id2 = Pubkey::new_rand();
|
|
|
|
let id3 = Pubkey::new_rand();
|
2020-03-11 14:37:23 -07:00
|
|
|
let message = Message::new(&[
|
2019-05-23 15:19:53 -07:00
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new(id1, true)]),
|
2019-11-05 08:38:35 -08:00
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id2, false)]),
|
|
|
|
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id3, true)]),
|
2019-05-23 15:19:53 -07:00
|
|
|
]);
|
|
|
|
assert_eq!(
|
|
|
|
message.get_account_keys_by_lock_type(),
|
|
|
|
(vec![&id1, &id0], vec![&id3, &id2, &program_id])
|
|
|
|
);
|
|
|
|
}
|
2019-03-24 20:06:02 -07:00
|
|
|
}
|