Fix program-test's CPI support (#14594)

* Fix program-test's CPI support

* feedback
This commit is contained in:
Jack May
2021-01-14 19:27:37 -08:00
committed by GitHub
parent b758e4cb27
commit 0d29f9e82c

View File

@ -20,8 +20,9 @@ use {
solana_sdk::{ solana_sdk::{
account::Account, account::Account,
keyed_account::KeyedAccount, keyed_account::KeyedAccount,
process_instruction::BpfComputeBudget, process_instruction::{
process_instruction::{InvokeContext, MockInvokeContext, ProcessInstructionWithContext}, stable_log, BpfComputeBudget, InvokeContext, ProcessInstructionWithContext,
},
signature::{Keypair, Signer}, signature::{Keypair, Signer},
}, },
std::{ std::{
@ -30,6 +31,7 @@ use {
convert::TryFrom, convert::TryFrom,
fs::File, fs::File,
io::{self, Read}, io::{self, Read},
mem::transmute,
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::Rc, rc::Rc,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
@ -64,7 +66,21 @@ pub fn to_instruction_error(error: ProgramError) -> InstructionError {
} }
thread_local! { thread_local! {
static INVOKE_CONTEXT:RefCell<Rc<MockInvokeContext>> = RefCell::new(Rc::new(MockInvokeContext::default())); static INVOKE_CONTEXT:RefCell<Option<(usize, usize)>> = RefCell::new(None);
}
fn set_invoke_context(new: &mut dyn InvokeContext) {
INVOKE_CONTEXT.with(|invoke_context| {
if unsafe { invoke_context.replace(Some(transmute::<_, (usize, usize)>(new))) }.is_some() {
panic!("Overwiting invoke context!")
}
});
}
fn get_invoke_context<'a>() -> &'a mut dyn InvokeContext {
let fat = INVOKE_CONTEXT.with(|invoke_context| match *invoke_context.borrow() {
Some(val) => val,
None => panic!("Invoke context not set!"),
});
unsafe { transmute::<(usize, usize), &mut dyn InvokeContext>(fat) }
} }
pub fn builtin_process_instruction( pub fn builtin_process_instruction(
@ -74,15 +90,7 @@ pub fn builtin_process_instruction(
input: &[u8], input: &[u8],
invoke_context: &mut dyn InvokeContext, invoke_context: &mut dyn InvokeContext,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let mock_invoke_context = MockInvokeContext { set_invoke_context(invoke_context);
programs: invoke_context.get_programs().to_vec(),
key: *program_id,
..MockInvokeContext::default()
};
// TODO: Populate MockInvokeContext more, or rework to avoid MockInvokeContext entirely.
// The context being passed into the program is incomplete...
let local_invoke_context = RefCell::new(Rc::new(mock_invoke_context));
swap_invoke_context(&local_invoke_context);
// Copy all the accounts into a HashMap to ensure there are no duplicates // Copy all the accounts into a HashMap to ensure there are no duplicates
let mut accounts: HashMap<Pubkey, Account> = keyed_accounts let mut accounts: HashMap<Pubkey, Account> = keyed_accounts
@ -139,18 +147,6 @@ pub fn builtin_process_instruction(
} }
} }
swap_invoke_context(&local_invoke_context);
// Propagate logs back to caller's invoke context
// (TODO: This goes away if MockInvokeContext usage can be removed)
let logger = invoke_context.get_logger();
let logger = logger.borrow_mut();
for message in local_invoke_context.borrow().logger.log.borrow_mut().iter() {
if logger.log_enabled() {
logger.log(message);
}
}
result result
} }
@ -176,24 +172,15 @@ macro_rules! processor {
}; };
} }
pub fn swap_invoke_context(other_invoke_context: &RefCell<Rc<MockInvokeContext>>) {
INVOKE_CONTEXT.with(|invoke_context| {
invoke_context.swap(&other_invoke_context);
});
}
struct SyscallStubs {} struct SyscallStubs {}
impl program_stubs::SyscallStubs for SyscallStubs { impl program_stubs::SyscallStubs for SyscallStubs {
fn sol_log(&self, message: &str) { fn sol_log(&self, message: &str) {
INVOKE_CONTEXT.with(|invoke_context| { let invoke_context = get_invoke_context();
let invoke_context = invoke_context.borrow_mut(); let logger = invoke_context.get_logger();
let logger = invoke_context.get_logger(); let logger = logger.borrow_mut();
let logger = logger.borrow_mut(); if logger.log_enabled() {
logger.log(&format!("Program log: {}", message));
if logger.log_enabled() { }
logger.log(&format!("Program log: {}", message));
}
});
} }
fn sol_invoke_signed( fn sol_invoke_signed(
@ -206,21 +193,11 @@ impl program_stubs::SyscallStubs for SyscallStubs {
// TODO: Merge the business logic below with the BPF invoke path in // TODO: Merge the business logic below with the BPF invoke path in
// programs/bpf_loader/src/syscalls.rs // programs/bpf_loader/src/syscalls.rs
// //
info!("SyscallStubs::sol_invoke_signed()");
let mut caller = Pubkey::default(); let invoke_context = get_invoke_context();
let mut mock_invoke_context = MockInvokeContext::default(); let logger = invoke_context.get_logger();
INVOKE_CONTEXT.with(|invoke_context| {
let invoke_context = invoke_context.borrow_mut();
caller = *invoke_context.get_caller().expect("get_caller");
invoke_context.record_instruction(&instruction);
mock_invoke_context.programs = invoke_context.get_programs().to_vec();
// TODO: Populate MockInvokeContext more, or rework to avoid MockInvokeContext entirely.
// The context being passed into the program is incomplete...
});
let caller = *invoke_context.get_caller().expect("get_caller");
if instruction.accounts.len() + 1 != account_infos.len() { if instruction.accounts.len() + 1 != account_infos.len() {
panic!( panic!(
"Instruction accounts mismatch. Instruction contains {} accounts, with {} "Instruction accounts mismatch. Instruction contains {} accounts, with {}
@ -230,17 +207,11 @@ impl program_stubs::SyscallStubs for SyscallStubs {
); );
} }
let message = Message::new(&[instruction.clone()], None); let message = Message::new(&[instruction.clone()], None);
let program_id_index = message.instructions[0].program_id_index as usize; let program_id_index = message.instructions[0].program_id_index as usize;
let program_id = message.account_keys[program_id_index]; let program_id = message.account_keys[program_id_index];
let program_account_info = &account_infos[program_id_index]; let program_account_info = &account_infos[program_id_index];
if !program_account_info.executable {
panic!("Program account is not executable"); stable_log::program_invoke(&logger, &program_id, invoke_context.invoke_depth());
}
if program_account_info.is_writable {
panic!("Program account is writable");
}
fn ai_to_a(ai: &AccountInfo) -> Account { fn ai_to_a(ai: &AccountInfo) -> Account {
Account { Account {
@ -251,55 +222,56 @@ impl program_stubs::SyscallStubs for SyscallStubs {
rent_epoch: ai.rent_epoch, rent_epoch: ai.rent_epoch,
} }
} }
let executable_accounts = vec![(program_id, RefCell::new(ai_to_a(program_account_info)))]; let executables = vec![(program_id, RefCell::new(ai_to_a(program_account_info)))];
// Convert AccountInfos into Accounts
let mut accounts = vec![]; let mut accounts = vec![];
for instruction_account in &instruction.accounts { for key in &message.account_keys {
for account_info in account_infos { for account_info in account_infos {
if *account_info.unsigned_key() == instruction_account.pubkey { if account_info.unsigned_key() == key {
if instruction_account.is_writable && !account_info.is_writable {
panic!("Writeable mismatch for {}", instruction_account.pubkey);
}
if instruction_account.is_signer && !account_info.is_signer {
let mut program_signer = false;
for seeds in signers_seeds.iter() {
let signer = Pubkey::create_program_address(&seeds, &caller).unwrap();
if instruction_account.pubkey == signer {
program_signer = true;
break;
}
}
if !program_signer {
panic!("Signer mismatch for {}", instruction_account.pubkey);
}
}
accounts.push(Rc::new(RefCell::new(ai_to_a(account_info)))); accounts.push(Rc::new(RefCell::new(ai_to_a(account_info))));
break; break;
} }
} }
} }
assert_eq!(accounts.len(), instruction.accounts.len()); assert_eq!(
accounts.len(),
message.account_keys.len(),
"Missing or not enough accounts passed to invoke"
);
// Check Signers
for account_info in account_infos {
for instruction_account in &instruction.accounts {
if *account_info.unsigned_key() == instruction_account.pubkey
&& instruction_account.is_signer
&& !account_info.is_signer
{
let mut program_signer = false;
for seeds in signers_seeds.iter() {
let signer = Pubkey::create_program_address(&seeds, &caller).unwrap();
if instruction_account.pubkey == signer {
program_signer = true;
break;
}
}
if !program_signer {
panic!("Missing signer for {}", instruction_account.pubkey);
}
}
}
}
invoke_context.record_instruction(&instruction);
solana_runtime::message_processor::MessageProcessor::process_cross_program_instruction( solana_runtime::message_processor::MessageProcessor::process_cross_program_instruction(
&message, &message,
&executable_accounts, &executables,
&accounts, &accounts,
&mut mock_invoke_context, invoke_context,
) )
.map_err(|err| ProgramError::try_from(err).unwrap_or_else(|err| panic!("{}", err)))?; .map_err(|err| ProgramError::try_from(err).unwrap_or_else(|err| panic!("{}", err)))?;
// Propagate logs back to caller's invoke context
// (TODO: This goes away if MockInvokeContext usage can be removed)
INVOKE_CONTEXT.with(|invoke_context| {
let logger = invoke_context.borrow().get_logger();
let logger = logger.borrow_mut();
for message in mock_invoke_context.logger.log.borrow_mut().iter() {
if logger.log_enabled() {
logger.log(message);
}
}
});
// Copy writeable account modifications back into the caller's AccountInfos // Copy writeable account modifications back into the caller's AccountInfos
for (i, instruction_account) in instruction.accounts.iter().enumerate() { for (i, instruction_account) in instruction.accounts.iter().enumerate() {
if !instruction_account.is_writable { if !instruction_account.is_writable {
@ -314,13 +286,12 @@ impl program_stubs::SyscallStubs for SyscallStubs {
let mut data = account_info.try_borrow_mut_data()?; let mut data = account_info.try_borrow_mut_data()?;
let new_data = &account.borrow().data; let new_data = &account.borrow().data;
if *account_info.owner != account.borrow().owner { if *account_info.owner != account.borrow().owner {
// TODO: Figure out how to allow the System Program to change the account owner // TODO Figure out a better way to allow the System Program to set the account owner
panic!( #[allow(clippy::transmute_ptr_to_ptr)]
"Account ownership change not supported yet: {} -> {}. \ #[allow(mutable_transmutes)]
Consider making this test conditional on `#[cfg(feature = \"test-bpf\")]`", let account_info_mut =
*account_info.owner, unsafe { transmute::<&Pubkey, &mut Pubkey>(account_info.owner) };
account.borrow().owner *account_info_mut = account.borrow().owner;
);
} }
if data.len() != new_data.len() { if data.len() != new_data.len() {
// TODO: Figure out how to allow the System Program to resize the account data // TODO: Figure out how to allow the System Program to resize the account data
@ -336,6 +307,7 @@ impl program_stubs::SyscallStubs for SyscallStubs {
} }
} }
stable_log::program_success(&logger, &program_id);
Ok(()) Ok(())
} }
} }