From 130c0b484da19732e4009f687bfe54cf0c2a26d0 Mon Sep 17 00:00:00 2001 From: Jack May Date: Tue, 31 Mar 2020 10:07:38 -0700 Subject: [PATCH] Enforce an executable's rent exemption in the runtime (#9134) --- programs/bpf_loader/src/lib.rs | 26 ++---- programs/move_loader/src/processor.rs | 31 ------ runtime/benches/message_processor.rs | 10 +- runtime/src/bank.rs | 1 + runtime/src/message_processor.rs | 130 ++++++++++++++++++-------- sdk/src/instruction.rs | 4 + 6 files changed, 110 insertions(+), 92 deletions(-) diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 48bc614fe8..dfd0b5c78b 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -21,7 +21,6 @@ use solana_sdk::{ program_utils::DecodeError, program_utils::{is_executable, limited_deserialize, next_keyed_account}, pubkey::Pubkey, - sysvar::rent, }; use std::{io::prelude::*, mem}; use thiserror::Error; @@ -229,7 +228,6 @@ pub fn process_instruction( LoaderInstruction::Finalize => { let mut keyed_accounts_iter = keyed_accounts.iter(); let program = next_keyed_account(&mut keyed_accounts_iter)?; - let rent = next_keyed_account(&mut keyed_accounts_iter)?; if program.signer_key().is_none() { warn!("key[0] did not sign the transaction"); @@ -241,8 +239,6 @@ pub fn process_instruction( return Err(InstructionError::InvalidAccountData); } - rent::verify_rent_exemption(&program, &rent)?; - program.try_account_ref_mut()?.executable = true; info!("Finalize: account {:?}", program.signer_key().unwrap()); } @@ -254,8 +250,8 @@ pub fn process_instruction( #[cfg(test)] mod tests { use super::*; - use solana_sdk::account::Account; - use std::{cell::RefCell, fs::File, io::Read}; + use solana_sdk::{account::Account, rent::Rent}; + use std::{fs::File, io::Read}; #[test] #[should_panic(expected = "ExceededMaxInstructions(10)")] @@ -324,14 +320,13 @@ mod tests { fn test_bpf_loader_finalize() { let program_id = Pubkey::new_rand(); let program_key = Pubkey::new_rand(); - let rent_key = rent::id(); let mut file = File::open("test_elfs/noop.so").expect("file open failed"); let mut elf = Vec::new(); - let rent = rent::Rent::default(); + let rent = Rent::default(); file.read_to_end(&mut elf).unwrap(); let program_account = Account::new_ref(rent.minimum_balance(elf.len()), 0, &program_id); program_account.borrow_mut().data = elf; - let mut keyed_accounts = vec![KeyedAccount::new(&program_key, false, &program_account)]; + let keyed_accounts = vec![KeyedAccount::new(&program_key, false, &program_account)]; let instruction_data = bincode::serialize(&LoaderInstruction::Finalize).unwrap(); // Case: Empty keyed accounts @@ -340,9 +335,6 @@ mod tests { process_instruction(&bpf_loader::id(), &vec![], &instruction_data) ); - let rent_account = RefCell::new(rent::create_account(1, &rent)); - keyed_accounts.push(KeyedAccount::new(&rent_key, false, &rent_account)); - // Case: Not signed assert_eq!( Err(InstructionError::MissingRequiredSignature), @@ -350,10 +342,7 @@ mod tests { ); // Case: Finalize - let keyed_accounts = vec![ - KeyedAccount::new(&program_key, true, &program_account), - KeyedAccount::new(&rent_key, false, &rent_account), - ]; + let keyed_accounts = vec![KeyedAccount::new(&program_key, true, &program_account)]; assert_eq!( Ok(()), process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data) @@ -364,10 +353,7 @@ mod tests { // Case: Finalize program_account.borrow_mut().data[0] = 0; // bad elf - let keyed_accounts = vec![ - KeyedAccount::new(&program_key, true, &program_account), - KeyedAccount::new(&rent_key, false, &rent_account), - ]; + let keyed_accounts = vec![KeyedAccount::new(&program_key, true, &program_account)]; assert_eq!( Err(InstructionError::InvalidAccountData), process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data) diff --git a/programs/move_loader/src/processor.rs b/programs/move_loader/src/processor.rs index b887c07016..96fa62298f 100644 --- a/programs/move_loader/src/processor.rs +++ b/programs/move_loader/src/processor.rs @@ -13,7 +13,6 @@ use solana_sdk::{ program_utils::DecodeError, program_utils::{is_executable, limited_deserialize, next_keyed_account}, pubkey::Pubkey, - sysvar::rent, }; use thiserror::Error; use types::{ @@ -73,7 +72,6 @@ pub enum MoveLoaderInstruction { /// bit of the Account /// /// * key[0] - the account to prepare for execution - /// * key[1] - rent sysvar account /// /// The transaction must be signed by key[0] Finalize, @@ -301,15 +299,12 @@ impl MoveProcessor { pub fn do_finalize(keyed_accounts: &[KeyedAccount]) -> Result<(), InstructionError> { let mut keyed_accounts_iter = keyed_accounts.iter(); let finalized = next_keyed_account(&mut keyed_accounts_iter)?; - let rent = next_keyed_account(&mut keyed_accounts_iter)?; if finalized.signer_key().is_none() { debug!("Error: account to finalize did not sign the transaction"); return Err(InstructionError::MissingRequiredSignature); } - rent::verify_rent_exemption(&finalized, &rent)?; - match finalized.state()? { LibraAccountState::CompiledScript(string) => { let script: Script = serde_json::from_str(&string).map_err(map_json_error)?; @@ -479,8 +474,6 @@ impl MoveProcessor { mod tests { use super::*; use solana_sdk::account::Account; - use solana_sdk::rent::Rent; - use solana_sdk::sysvar::rent; use std::cell::RefCell; const BIG_ENOUGH: usize = 10_000; @@ -518,11 +511,8 @@ mod tests { let code = "main() { return; }"; let sender_address = AccountAddress::default(); let script = LibraAccount::create_script(&sender_address, code, vec![]); - let rent_id = rent::id(); - let rent_account = RefCell::new(rent::create_account(1, &Rent::free())); let keyed_accounts = vec![ KeyedAccount::new(&script.key, true, &script.account), - KeyedAccount::new(&rent_id, false, &rent_account), ]; MoveProcessor::do_finalize(&keyed_accounts).unwrap(); let _ = MoveProcessor::deserialize_verified_script(&script.account.borrow().data).unwrap(); @@ -561,11 +551,8 @@ mod tests { let script = LibraAccount::create_script(&sender_address, code, vec![]); let genesis = LibraAccount::create_genesis(1_000_000_000); - let rent_id = rent::id(); - let rent_account = RefCell::new(rent::create_account(1, &Rent::free())); let keyed_accounts = vec![ KeyedAccount::new(&script.key, true, &script.account), - KeyedAccount::new(&rent_id, false, &rent_account), ]; MoveProcessor::do_finalize(&keyed_accounts).unwrap(); @@ -591,11 +578,8 @@ mod tests { } "; let module = LibraAccount::create_module(code, vec![]); - let rent_id = rent::id(); - let rent_account = RefCell::new(rent::create_account(1, &Rent::free())); let keyed_accounts = vec![ KeyedAccount::new(&module.key, true, &module.account), - KeyedAccount::new(&rent_id, false, &rent_account), ]; keyed_accounts[0] .account @@ -620,11 +604,8 @@ mod tests { let script = LibraAccount::create_script(&sender_address, code, vec![]); let genesis = LibraAccount::create_genesis(1_000_000_000); - let rent_id = rent::id(); - let rent_account = RefCell::new(rent::create_account(1, &Rent::free())); let keyed_accounts = vec![ KeyedAccount::new(&script.key, true, &script.account), - KeyedAccount::new(&rent_id, false, &rent_account), ]; MoveProcessor::do_finalize(&keyed_accounts).unwrap(); @@ -695,11 +676,8 @@ mod tests { let script = LibraAccount::create_script(&genesis.address, code, vec![]); let payee = LibraAccount::create_unallocated(BIG_ENOUGH); - let rent_id = rent::id(); - let rent_account = RefCell::new(rent::create_account(1, &Rent::free())); let keyed_accounts = vec![ KeyedAccount::new(&script.key, true, &script.account), - KeyedAccount::new(&rent_id, false, &rent_account), ]; MoveProcessor::do_finalize(&keyed_accounts).unwrap(); @@ -753,11 +731,8 @@ mod tests { ); let module = LibraAccount::create_module(&code, vec![]); - let rent_id = rent::id(); - let rent_account = RefCell::new(rent::create_account(1, &Rent::free())); let keyed_accounts = vec![ KeyedAccount::new(&module.key, true, &module.account), - KeyedAccount::new(&rent_id, false, &rent_account), ]; keyed_accounts[0] .account @@ -799,11 +774,8 @@ mod tests { ); let payee = LibraAccount::create_unallocated(BIG_ENOUGH); - let rent_id = rent::id(); - let rent_account = RefCell::new(rent::create_account(1, &Rent::free())); let keyed_accounts = vec![ KeyedAccount::new(&script.key, true, &script.account), - KeyedAccount::new(&rent_id, false, &rent_account), ]; MoveProcessor::do_finalize(&keyed_accounts).unwrap(); @@ -849,11 +821,8 @@ mod tests { let script = LibraAccount::create_script(&genesis.address.clone(), code, vec![]); let payee = LibraAccount::create_unallocated(BIG_ENOUGH); - let rent_id = rent::id(); - let rent_account = RefCell::new(rent::create_account(1, &Rent::free())); let keyed_accounts = vec![ KeyedAccount::new(&script.key, true, &script.account), - KeyedAccount::new(&rent_id, false, &rent_account), ]; MoveProcessor::do_finalize(&keyed_accounts).unwrap(); diff --git a/runtime/benches/message_processor.rs b/runtime/benches/message_processor.rs index 4aef94e302..4b193e32c8 100644 --- a/runtime/benches/message_processor.rs +++ b/runtime/benches/message_processor.rs @@ -3,7 +3,7 @@ extern crate test; use log::*; -use solana_runtime::message_processor::PreAccount; +use solana_runtime::{message_processor::PreAccount, rent_collector::RentCollector}; use solana_sdk::{account::Account, pubkey::Pubkey}; use test::Bencher; @@ -15,11 +15,12 @@ fn bench_verify_account_changes_data(bencher: &mut Bencher) { let non_owner = Pubkey::new_rand(); let pre = PreAccount::new(&Account::new(0, BUFSIZE, &owner), true, &owner); let post = Account::new(0, BUFSIZE, &owner); - assert_eq!(pre.verify(&owner, &post), Ok(())); + assert_eq!(pre.verify(&owner, &RentCollector::default(), &post), Ok(())); // this one should be faster bencher.iter(|| { - pre.verify(&owner, &post).unwrap(); + pre.verify(&owner, &RentCollector::default(), &post) + .unwrap(); }); let summary = bencher.bench(|_bencher| {}).unwrap(); info!("data no change by owner: {} ns/iter", summary.median); @@ -32,7 +33,8 @@ fn bench_verify_account_changes_data(bencher: &mut Bencher) { let summary = bencher.bench(|_bencher| {}).unwrap(); info!("data compare {} ns/iter", summary.median); bencher.iter(|| { - pre.verify(&non_owner, &post).unwrap(); + pre.verify(&non_owner, &RentCollector::default(), &post) + .unwrap(); }); let summary = bencher.bench(|_bencher| {}).unwrap(); info!("data no change by non owner: {} ns/iter", summary.median); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index b0a6bfbc55..9f49e1cf37 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -1375,6 +1375,7 @@ impl Bank { tx.message(), &loader_refcells, &account_refcells, + &self.rent_collector, ); Self::from_refcells(accounts, loaders, account_refcells, loader_refcells); diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index c5913fc0e2..8b739f1046 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -1,4 +1,4 @@ -use crate::{native_loader, system_instruction_processor}; +use crate::{native_loader, rent_collector::RentCollector, system_instruction_processor}; use serde::{Deserialize, Serialize}; use solana_sdk::{ account::{create_keyed_readonly_accounts, Account, KeyedAccount}, @@ -66,7 +66,12 @@ impl PreAccount { || is_executable } - pub fn verify(&self, program_id: &Pubkey, post: &Account) -> Result<(), InstructionError> { + pub fn verify( + &self, + program_id: &Pubkey, + rent_collector: &RentCollector, + post: &Account, + ) -> Result<(), InstructionError> { // Only the owner of the account may change owner and // only if the account is writable and // only if the data is zero-initialized or empty @@ -120,12 +125,19 @@ impl PreAccount { } // executable is one-way (false->true) and only the account owner may set it. - if self.executable != post.executable - && (!self.is_writable // line coverage used to get branch coverage - || self.executable // line coverage used to get branch coverage - || *program_id != self.owner) - { - return Err(InstructionError::ExecutableModified); + if self.executable != post.executable { + if !rent_collector + .rent + .is_exempt(post.lamports, post.data.len()) + { + return Err(InstructionError::ExecutableAccountNotRentExempt); + } + if !self.is_writable // line coverage used to get branch coverage + || self.executable // line coverage used to get branch coverage + || *program_id != self.owner + { + return Err(InstructionError::ExecutableModified); + } } // No one modifies rent_epoch (yet). @@ -156,7 +168,6 @@ pub struct MessageProcessor { #[serde(skip)] symbol_cache: SymbolCache, } - impl Default for MessageProcessor { fn default() -> Self { let instruction_processors: Vec<(Pubkey, ProcessInstruction)> = vec![( @@ -170,7 +181,6 @@ impl Default for MessageProcessor { } } } - impl MessageProcessor { /// Add a static entrypoint to intercept instructions before the dynamic loader. pub fn add_instruction_processor( @@ -275,6 +285,7 @@ impl MessageProcessor { pre_accounts: &[PreAccount], executable_accounts: &[(Pubkey, RefCell)], accounts: &[Rc>], + rent_collector: &RentCollector, ) -> Result<(), InstructionError> { // Verify all accounts have zero outstanding refs Self::verify_account_references(executable_accounts, accounts)?; @@ -285,7 +296,7 @@ impl MessageProcessor { let program_id = instruction.program_id(&message.account_keys); let mut work = |unique_index: usize, account_index: usize| { let account = accounts[account_index].borrow(); - pre_accounts[unique_index].verify(&program_id, &account)?; + pre_accounts[unique_index].verify(&program_id, rent_collector, &account)?; pre_sum += u128::from(pre_accounts[unique_index].lamports); post_sum += u128::from(account.lamports); Ok(()) @@ -310,6 +321,7 @@ impl MessageProcessor { instruction: &CompiledInstruction, executable_accounts: &[(Pubkey, RefCell)], accounts: &[Rc>], + rent_collector: &RentCollector, ) -> Result<(), InstructionError> { let pre_accounts = Self::create_pre_accounts(message, instruction, accounts); self.process_instruction(message, instruction, executable_accounts, accounts)?; @@ -319,6 +331,7 @@ impl MessageProcessor { &pre_accounts, executable_accounts, accounts, + rent_collector, )?; Ok(()) } @@ -331,6 +344,7 @@ impl MessageProcessor { message: &Message, loaders: &[Vec<(Pubkey, RefCell)>], accounts: &[Rc>], + rent_collector: &RentCollector, ) -> Result<(), TransactionError> { for (instruction_index, instruction) in message.instructions.iter().enumerate() { let executable_index = message @@ -338,8 +352,14 @@ impl MessageProcessor { .ok_or(TransactionError::InvalidAccountIndex)?; let executable_accounts = &loaders[executable_index]; - self.execute_instruction(message, instruction, executable_accounts, accounts) - .map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?; + self.execute_instruction( + message, + instruction, + executable_accounts, + accounts, + rent_collector, + ) + .map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?; } Ok(()) } @@ -410,8 +430,11 @@ mod tests { post: &Pubkey, is_writable: bool, ) -> Result<(), InstructionError> { - PreAccount::new(&Account::new(0, 0, pre), is_writable, ix) - .verify(ix, &Account::new(0, 0, post)) + PreAccount::new(&Account::new(0, 0, pre), is_writable, ix).verify( + ix, + &RentCollector::default(), + &Account::new(0, 0, post), + ) } let system_program_id = system_program::id(); @@ -448,7 +471,6 @@ mod tests { Err(InstructionError::ModifiedProgramId), "system program should not be able to change the account owner of a non-system account" ); - assert_eq!( change_owner( &mallory_program_id, @@ -459,7 +481,6 @@ mod tests { Ok(()), "mallory should be able to change the account owner, if she leaves clear data" ); - assert_eq!( PreAccount::new( &Account::new_data(0, &[42], &mallory_program_id).unwrap(), @@ -468,6 +489,7 @@ mod tests { ) .verify( &mallory_program_id, + &RentCollector::default(), &Account::new_data(0, &[0], &alice_program_id).unwrap(), ), Ok(()), @@ -481,6 +503,7 @@ mod tests { ) .verify( &mallory_program_id, + &RentCollector::default(), &Account::new_data(0, &[42], &alice_program_id).unwrap(), ), Err(InstructionError::ModifiedProgramId), @@ -505,6 +528,7 @@ mod tests { pre: PreAccount::new( &Account { owner: *owner, + lamports: std::u64::MAX, data: vec![], ..Account::default() }, @@ -513,6 +537,7 @@ mod tests { ), post: Account { owner: *owner, + lamports: std::u64::MAX, ..Account::default() }, program_id: *program_id, @@ -539,7 +564,8 @@ mod tests { self } pub fn verify(self) -> Result<(), InstructionError> { - self.pre.verify(&self.program_id, &self.post) + self.pre + .verify(&self.program_id, &RentCollector::default(), &self.post) } } @@ -623,6 +649,25 @@ mod tests { Err(InstructionError::ExecutableLamportChange), "owner should not be able to subtract lamports once marked executable" ); + let data = vec![1; 100]; + let min_lamports = RentCollector::default().rent.minimum_balance(data.len()); + assert_eq!( + Change::new(&owner, &owner) + .executable(false, true) + .lamports(0, min_lamports) + .data(data.clone(), data.clone()) + .verify(), + Ok(()), + ); + assert_eq!( + Change::new(&owner, &owner) + .executable(false, true) + .lamports(0, min_lamports - 1) + .data(data.clone(), data.clone()) + .verify(), + Err(InstructionError::ExecutableAccountNotRentExempt), + "owner should not be able to change an account's data once its marked executable" + ); } #[test] @@ -635,6 +680,7 @@ mod tests { ) .verify( &system_program::id(), + &RentCollector::default(), &Account::new_data(0, &[0, 0], &system_program::id()).unwrap() ), Ok(()), @@ -648,7 +694,7 @@ mod tests { true, &system_program::id(), ).verify( - &system_program::id(), + &system_program::id(), &RentCollector::default(), &Account::new_data(0, &[0, 0], &alice_program_id).unwrap(), ), Err(InstructionError::AccountDataSizeChanged), @@ -659,7 +705,6 @@ mod tests { #[test] fn test_verify_account_changes_data() { let alice_program_id = Pubkey::new_rand(); - let change_data = |program_id: &Pubkey, is_writable: bool| -> Result<(), InstructionError> { let pre = PreAccount::new( @@ -668,7 +713,7 @@ mod tests { &program_id, ); let post = Account::new_data(0, &[42], &alice_program_id).unwrap(); - pre.verify(&program_id, &post) + pre.verify(&program_id, &RentCollector::default(), &post) }; let mallory_program_id = Pubkey::new_rand(); @@ -694,6 +739,7 @@ mod tests { #[test] fn test_verify_account_changes_rent_epoch() { let alice_program_id = Pubkey::new_rand(); + let rent_collector = RentCollector::default(); let pre = PreAccount::new( &Account::new(0, 0, &alice_program_id), false, @@ -702,14 +748,14 @@ mod tests { let mut post = Account::new(0, 0, &alice_program_id); assert_eq!( - pre.verify(&system_program::id(), &post), + pre.verify(&system_program::id(), &rent_collector, &post), Ok(()), "nothing changed!" ); post.rent_epoch += 1; assert_eq!( - pre.verify(&system_program::id(), &post), + pre.verify(&system_program::id(), &rent_collector, &post), Err(InstructionError::RentEpochModified), "no one touches rent_epoch" ); @@ -728,7 +774,7 @@ mod tests { // positive test of this capability assert_eq!( - pre.verify(&alice_program_id, &post), + pre.verify(&alice_program_id, &RentCollector::default(), &post), Ok(()), "alice should be able to deduct lamports and give the account to bob if the data is zeroed", ); @@ -737,6 +783,7 @@ mod tests { #[test] fn test_verify_account_changes_lamports() { let alice_program_id = Pubkey::new_rand(); + let rent_collector = RentCollector::default(); let pre = PreAccount::new( &Account::new(42, 0, &alice_program_id), false, @@ -745,7 +792,7 @@ mod tests { let post = Account::new(0, 0, &alice_program_id); assert_eq!( - pre.verify(&system_program::id(), &post), + pre.verify(&system_program::id(), &rent_collector, &post), Err(InstructionError::ExternalAccountLamportSpend), "debit should fail, even if system program" ); @@ -757,7 +804,7 @@ mod tests { ); assert_eq!( - pre.verify(&alice_program_id, &post,), + pre.verify(&alice_program_id, &rent_collector, &post), Err(InstructionError::ReadonlyLamportChange), "debit should fail, even if owning program" ); @@ -769,7 +816,7 @@ mod tests { ); let post = Account::new(0, 0, &system_program::id()); assert_eq!( - pre.verify(&system_program::id(), &post), + pre.verify(&system_program::id(), &rent_collector, &post), Err(InstructionError::ModifiedProgramId), "system program can't debit the account unless it was the pre.owner" ); @@ -781,7 +828,7 @@ mod tests { ); let post = Account::new(0, 0, &alice_program_id); assert_eq!( - pre.verify(&system_program::id(), &post), + pre.verify(&system_program::id(), &rent_collector, &post), Ok(()), "system can spend (and change owner)" ); @@ -789,6 +836,7 @@ mod tests { #[test] fn test_verify_account_changes_data_size_changed() { + let rent_collector = RentCollector::default(); let alice_program_id = Pubkey::new_rand(); let pre = PreAccount::new( &Account::new_data(42, &[0], &alice_program_id).unwrap(), @@ -797,7 +845,7 @@ mod tests { ); let post = Account::new_data(42, &[0, 0], &alice_program_id).unwrap(); assert_eq!( - pre.verify(&system_program::id(), &post), + pre.verify(&system_program::id(), &rent_collector, &post), Err(InstructionError::AccountDataSizeChanged), "system program should not be able to change another program's account data size" ); @@ -807,7 +855,7 @@ mod tests { &alice_program_id, ); assert_eq!( - pre.verify(&alice_program_id, &post), + pre.verify(&alice_program_id, &rent_collector, &post), Err(InstructionError::AccountDataSizeChanged), "non-system programs cannot change their data size" ); @@ -817,7 +865,7 @@ mod tests { &system_program::id(), ); assert_eq!( - pre.verify(&system_program::id(), &post), + pre.verify(&system_program::id(), &rent_collector, &post), Ok(()), "system program should be able to change acount data size" ); @@ -857,6 +905,7 @@ mod tests { } let mock_system_program_id = Pubkey::new(&[2u8; 32]); + let rent_collector = RentCollector::default(); let mut message_processor = MessageProcessor::default(); message_processor .add_instruction_processor(mock_system_program_id, mock_system_process_instruction); @@ -883,7 +932,8 @@ mod tests { account_metas.clone(), )]); - let result = message_processor.process_message(&message, &loaders, &accounts); + let result = + message_processor.process_message(&message, &loaders, &accounts, &rent_collector); assert_eq!(result, Ok(())); assert_eq!(accounts[0].borrow().lamports, 100); assert_eq!(accounts[1].borrow().lamports, 0); @@ -894,7 +944,8 @@ mod tests { account_metas.clone(), )]); - let result = message_processor.process_message(&message, &loaders, &accounts); + let result = + message_processor.process_message(&message, &loaders, &accounts, &rent_collector); assert_eq!( result, Err(TransactionError::InstructionError( @@ -909,7 +960,8 @@ mod tests { account_metas, )]); - let result = message_processor.process_message(&message, &loaders, &accounts); + let result = + message_processor.process_message(&message, &loaders, &accounts, &rent_collector); assert_eq!( result, Err(TransactionError::InstructionError( @@ -976,6 +1028,7 @@ mod tests { } let mock_program_id = Pubkey::new(&[2u8; 32]); + let rent_collector = RentCollector::default(); let mut message_processor = MessageProcessor::default(); message_processor .add_instruction_processor(mock_program_id, mock_system_process_instruction); @@ -1005,7 +1058,8 @@ mod tests { &MockSystemInstruction::BorrowFail, account_metas.clone(), )]); - let result = message_processor.process_message(&message, &loaders, &accounts); + let result = + message_processor.process_message(&message, &loaders, &accounts, &rent_collector); assert_eq!( result, Err(TransactionError::InstructionError( @@ -1020,7 +1074,8 @@ mod tests { &MockSystemInstruction::MultiBorrowMut, account_metas.clone(), )]); - let result = message_processor.process_message(&message, &loaders, &accounts); + let result = + message_processor.process_message(&message, &loaders, &accounts, &rent_collector); assert_eq!(result, Ok(())); // Do work on the same account but at different location in keyed_accounts[] @@ -1032,7 +1087,8 @@ mod tests { }, account_metas, )]); - let result = message_processor.process_message(&message, &loaders, &accounts); + let result = + message_processor.process_message(&message, &loaders, &accounts, &rent_collector); assert_eq!(result, Ok(())); assert_eq!(accounts[0].borrow().lamports, 80); assert_eq!(accounts[1].borrow().lamports, 20); diff --git a/sdk/src/instruction.rs b/sdk/src/instruction.rs index 0651beeb5e..ef81c297ff 100644 --- a/sdk/src/instruction.rs +++ b/sdk/src/instruction.rs @@ -130,6 +130,10 @@ pub enum InstructionError { /// Executable account's lamports modified #[error("instruction changed the balance of a executable account")] ExecutableLamportChange, + + /// Executable accounts must be rent exempt + #[error("executable accounts must be rent exempt")] + ExecutableAccountNotRentExempt, } impl InstructionError {