Resized accounts must be rent exempt
This commit is contained in:
@ -20,7 +20,6 @@ bzip2 = "0.4.3"
|
||||
dashmap = { version = "4.0.2", features = ["rayon", "raw-api"] }
|
||||
crossbeam-channel = "0.5"
|
||||
dir-diff = "0.3.2"
|
||||
enum-iterator = "0.7.0"
|
||||
flate2 = "1.0.22"
|
||||
fnv = "1.0.7"
|
||||
index_list = "0.2.7"
|
||||
|
@ -1,5 +1,4 @@
|
||||
use {
|
||||
enum_iterator::IntoEnumIterator,
|
||||
log::*,
|
||||
solana_sdk::{
|
||||
account::{AccountSharedData, ReadableAccount},
|
||||
@ -10,11 +9,15 @@ use {
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, IntoEnumIterator)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum RentState {
|
||||
Uninitialized, // account.lamports == 0
|
||||
RentPaying, // 0 < account.lamports < rent-exempt-minimum
|
||||
RentExempt, // account.lamports >= rent-exempt-minimum
|
||||
/// account.lamports == 0
|
||||
Uninitialized,
|
||||
/// 0 < account.lamports < rent-exempt-minimum
|
||||
/// Parameter is the size of the account data
|
||||
RentPaying(usize),
|
||||
/// account.lamports >= rent-exempt-minimum
|
||||
RentExempt,
|
||||
}
|
||||
|
||||
impl RentState {
|
||||
@ -22,27 +25,42 @@ impl RentState {
|
||||
if account.lamports() == 0 {
|
||||
Self::Uninitialized
|
||||
} else if !rent.is_exempt(account.lamports(), account.data().len()) {
|
||||
Self::RentPaying
|
||||
Self::RentPaying(account.data().len())
|
||||
} else {
|
||||
Self::RentExempt
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn transition_allowed_from(&self, pre_rent_state: &RentState) -> bool {
|
||||
// Only a legacy RentPaying account may end in the RentPaying state after message processing
|
||||
!(self == &Self::RentPaying && pre_rent_state != &Self::RentPaying)
|
||||
pub(crate) fn transition_allowed_from(
|
||||
&self,
|
||||
pre_rent_state: &RentState,
|
||||
do_support_realloc: bool,
|
||||
) -> bool {
|
||||
if let Self::RentPaying(post_data_size) = self {
|
||||
if let Self::RentPaying(pre_data_size) = pre_rent_state {
|
||||
if do_support_realloc {
|
||||
post_data_size == pre_data_size // Cannot be RentPaying if resized
|
||||
} else {
|
||||
true // RentPaying can continue to be RentPaying
|
||||
}
|
||||
} else {
|
||||
false // Only RentPaying can continue to be RentPaying
|
||||
}
|
||||
} else {
|
||||
true // Post not-RentPaying always ok
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn submit_rent_state_metrics(pre_rent_state: &RentState, post_rent_state: &RentState) {
|
||||
match (pre_rent_state, post_rent_state) {
|
||||
(&RentState::Uninitialized, &RentState::RentPaying) => {
|
||||
(&RentState::Uninitialized, &RentState::RentPaying(_)) => {
|
||||
inc_new_counter_info!("rent_paying_err-new_account", 1);
|
||||
}
|
||||
(&RentState::RentPaying, &RentState::RentPaying) => {
|
||||
(&RentState::RentPaying(_), &RentState::RentPaying(_)) => {
|
||||
inc_new_counter_info!("rent_paying_ok-legacy", 1);
|
||||
}
|
||||
(_, &RentState::RentPaying) => {
|
||||
(_, &RentState::RentPaying(_)) => {
|
||||
inc_new_counter_info!("rent_paying_err-other", 1);
|
||||
}
|
||||
_ => {}
|
||||
@ -54,6 +72,7 @@ pub(crate) fn check_rent_state(
|
||||
post_rent_state: Option<&RentState>,
|
||||
transaction_context: &TransactionContext,
|
||||
index: usize,
|
||||
do_support_realloc: bool,
|
||||
) -> Result<()> {
|
||||
if let Some((pre_rent_state, post_rent_state)) = pre_rent_state.zip(post_rent_state) {
|
||||
let expect_msg = "account must exist at TransactionContext index if rent-states are Some";
|
||||
@ -67,6 +86,7 @@ pub(crate) fn check_rent_state(
|
||||
.get_account_at_index(index)
|
||||
.expect(expect_msg)
|
||||
.borrow(),
|
||||
do_support_realloc,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
@ -77,10 +97,11 @@ pub(crate) fn check_rent_state_with_account(
|
||||
post_rent_state: &RentState,
|
||||
address: &Pubkey,
|
||||
account_state: &AccountSharedData,
|
||||
do_support_realloc: bool,
|
||||
) -> Result<()> {
|
||||
submit_rent_state_metrics(pre_rent_state, post_rent_state);
|
||||
if !solana_sdk::incinerator::check_id(address)
|
||||
&& !post_rent_state.transition_allowed_from(pre_rent_state)
|
||||
&& !post_rent_state.transition_allowed_from(pre_rent_state, do_support_realloc)
|
||||
{
|
||||
debug!(
|
||||
"Account {} not rent exempt, state {:?}",
|
||||
@ -133,7 +154,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
RentState::from_account(&rent_paying_account, &rent),
|
||||
RentState::RentPaying
|
||||
RentState::RentPaying(account_data_size)
|
||||
);
|
||||
assert_eq!(
|
||||
RentState::from_account(&rent_exempt_account, &rent),
|
||||
@ -143,16 +164,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_transition_allowed_from() {
|
||||
for post_rent_state in RentState::into_enum_iter() {
|
||||
for pre_rent_state in RentState::into_enum_iter() {
|
||||
if post_rent_state == RentState::RentPaying
|
||||
&& pre_rent_state != RentState::RentPaying
|
||||
{
|
||||
assert!(!post_rent_state.transition_allowed_from(&pre_rent_state));
|
||||
} else {
|
||||
assert!(post_rent_state.transition_allowed_from(&pre_rent_state));
|
||||
}
|
||||
}
|
||||
}
|
||||
let post_rent_state = RentState::Uninitialized;
|
||||
assert!(post_rent_state.transition_allowed_from(&RentState::Uninitialized, true));
|
||||
assert!(post_rent_state.transition_allowed_from(&RentState::RentExempt, true));
|
||||
assert!(post_rent_state.transition_allowed_from(&RentState::RentPaying(0), true));
|
||||
|
||||
let post_rent_state = RentState::RentExempt;
|
||||
assert!(post_rent_state.transition_allowed_from(&RentState::Uninitialized, true));
|
||||
assert!(post_rent_state.transition_allowed_from(&RentState::RentExempt, true));
|
||||
assert!(post_rent_state.transition_allowed_from(&RentState::RentPaying(0), true));
|
||||
|
||||
let post_rent_state = RentState::RentPaying(2);
|
||||
assert!(!post_rent_state.transition_allowed_from(&RentState::Uninitialized, true));
|
||||
assert!(!post_rent_state.transition_allowed_from(&RentState::RentExempt, true));
|
||||
assert!(!post_rent_state.transition_allowed_from(&RentState::RentPaying(3), true));
|
||||
assert!(!post_rent_state.transition_allowed_from(&RentState::RentPaying(1), true));
|
||||
assert!(post_rent_state.transition_allowed_from(&RentState::RentPaying(2), true));
|
||||
}
|
||||
}
|
||||
|
@ -377,6 +377,7 @@ impl Accounts {
|
||||
&payer_post_rent_state,
|
||||
payer_address,
|
||||
payer_account,
|
||||
feature_set.is_active(&feature_set::do_support_realloc::id()),
|
||||
);
|
||||
// Feature gate only wraps the actual error return so that the metrics and debug
|
||||
// logging generated by `check_rent_state_with_account()` can be examined before
|
||||
|
@ -16285,12 +16285,22 @@ pub(crate) mod tests {
|
||||
let rent_paying_account = Keypair::new();
|
||||
genesis_config.accounts.insert(
|
||||
rent_paying_account.pubkey(),
|
||||
Account::new(rent_exempt_minimum - 1, account_data_size, &mock_program_id),
|
||||
Account::new_rent_epoch(
|
||||
rent_exempt_minimum - 1,
|
||||
account_data_size,
|
||||
&mock_program_id,
|
||||
INITIAL_RENT_EPOCH + 1,
|
||||
),
|
||||
);
|
||||
let rent_exempt_account = Keypair::new();
|
||||
genesis_config.accounts.insert(
|
||||
rent_exempt_account.pubkey(),
|
||||
Account::new(rent_exempt_minimum, account_data_size, &mock_program_id),
|
||||
Account::new_rent_epoch(
|
||||
rent_exempt_minimum,
|
||||
account_data_size,
|
||||
&mock_program_id,
|
||||
INITIAL_RENT_EPOCH + 1,
|
||||
),
|
||||
);
|
||||
// Activate features, including require_rent_exempt_accounts
|
||||
activate_all_features(&mut genesis_config);
|
||||
@ -16427,6 +16437,97 @@ pub(crate) mod tests {
|
||||
assert!(check_account_is_rent_exempt(&rent_exempt_account.pubkey()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drained_created_account() {
|
||||
let GenesisConfigInfo {
|
||||
mut genesis_config,
|
||||
mint_keypair,
|
||||
..
|
||||
} = create_genesis_config_with_leader(sol_to_lamports(100.), &Pubkey::new_unique(), 42);
|
||||
genesis_config.rent = Rent::default();
|
||||
activate_all_features(&mut genesis_config);
|
||||
|
||||
let mock_program_id = Pubkey::new_unique();
|
||||
// small enough to not pay rent, thus bypassing the data clearing rent
|
||||
// mechanism
|
||||
let data_size_no_rent = 100;
|
||||
// large enough to pay rent, will have data cleared
|
||||
let data_size_rent = 10000;
|
||||
let lamports_to_transfer = 100;
|
||||
|
||||
// Create legacy accounts of various kinds
|
||||
let created_keypair = Keypair::new();
|
||||
|
||||
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||
bank.add_builtin(
|
||||
"mock_program",
|
||||
&mock_program_id,
|
||||
mock_transfer_process_instruction,
|
||||
);
|
||||
let recent_blockhash = bank.last_blockhash();
|
||||
|
||||
// Create and drain a small data size account
|
||||
let create_instruction = system_instruction::create_account(
|
||||
&mint_keypair.pubkey(),
|
||||
&created_keypair.pubkey(),
|
||||
lamports_to_transfer,
|
||||
data_size_no_rent,
|
||||
&mock_program_id,
|
||||
);
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(mint_keypair.pubkey(), true),
|
||||
AccountMeta::new(created_keypair.pubkey(), true),
|
||||
AccountMeta::new(mint_keypair.pubkey(), false),
|
||||
];
|
||||
let transfer_from_instruction = Instruction::new_with_bincode(
|
||||
mock_program_id,
|
||||
&MockTransferInstruction::Transfer(lamports_to_transfer),
|
||||
account_metas,
|
||||
);
|
||||
let tx = Transaction::new_signed_with_payer(
|
||||
&[create_instruction, transfer_from_instruction],
|
||||
Some(&mint_keypair.pubkey()),
|
||||
&[&mint_keypair, &created_keypair],
|
||||
recent_blockhash,
|
||||
);
|
||||
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
// account data is not stored because of zero balance even though its
|
||||
// data wasn't cleared
|
||||
assert!(bank.get_account(&created_keypair.pubkey()).is_none());
|
||||
|
||||
// Create and drain a large data size account
|
||||
let create_instruction = system_instruction::create_account(
|
||||
&mint_keypair.pubkey(),
|
||||
&created_keypair.pubkey(),
|
||||
lamports_to_transfer,
|
||||
data_size_rent,
|
||||
&mock_program_id,
|
||||
);
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(mint_keypair.pubkey(), true),
|
||||
AccountMeta::new(created_keypair.pubkey(), true),
|
||||
AccountMeta::new(mint_keypair.pubkey(), false),
|
||||
];
|
||||
let transfer_from_instruction = Instruction::new_with_bincode(
|
||||
mock_program_id,
|
||||
&MockTransferInstruction::Transfer(lamports_to_transfer),
|
||||
account_metas,
|
||||
);
|
||||
let tx = Transaction::new_signed_with_payer(
|
||||
&[create_instruction, transfer_from_instruction],
|
||||
Some(&mint_keypair.pubkey()),
|
||||
&[&mint_keypair, &created_keypair],
|
||||
recent_blockhash,
|
||||
);
|
||||
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
// account data is not stored because of zero balance
|
||||
assert!(bank.get_account(&created_keypair.pubkey()).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rent_state_changes_sysvars() {
|
||||
let GenesisConfigInfo {
|
||||
@ -16879,4 +16980,329 @@ pub(crate) mod tests {
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
enum MockReallocInstruction {
|
||||
Realloc(usize, u64, Pubkey),
|
||||
}
|
||||
|
||||
fn mock_realloc_process_instruction(
|
||||
_first_instruction_account: usize,
|
||||
data: &[u8],
|
||||
invoke_context: &mut InvokeContext,
|
||||
) -> result::Result<(), InstructionError> {
|
||||
let transaction_context = &invoke_context.transaction_context;
|
||||
let instruction_context = transaction_context.get_current_instruction_context()?;
|
||||
if let Ok(instruction) = bincode::deserialize(data) {
|
||||
match instruction {
|
||||
MockReallocInstruction::Realloc(new_size, new_balance, _) => {
|
||||
// Set data length
|
||||
instruction_context
|
||||
.try_borrow_instruction_account(transaction_context, 1)?
|
||||
.set_data_length(new_size);
|
||||
|
||||
// set balance
|
||||
let current_balance = instruction_context
|
||||
.try_borrow_instruction_account(transaction_context, 1)?
|
||||
.get_lamports();
|
||||
let diff_balance = (new_balance as i64).saturating_sub(current_balance as i64);
|
||||
let amount = diff_balance.abs() as u64;
|
||||
if diff_balance.is_positive() {
|
||||
instruction_context
|
||||
.try_borrow_instruction_account(transaction_context, 0)?
|
||||
.checked_sub_lamports(amount)?;
|
||||
instruction_context
|
||||
.try_borrow_instruction_account(transaction_context, 1)?
|
||||
.set_lamports(new_balance);
|
||||
} else {
|
||||
instruction_context
|
||||
.try_borrow_instruction_account(transaction_context, 0)?
|
||||
.checked_add_lamports(amount)?;
|
||||
instruction_context
|
||||
.try_borrow_instruction_account(transaction_context, 1)?
|
||||
.set_lamports(new_balance);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(InstructionError::InvalidInstructionData)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_mock_realloc_tx(
|
||||
payer: &Keypair,
|
||||
funder: &Keypair,
|
||||
reallocd: &Pubkey,
|
||||
new_size: usize,
|
||||
new_balance: u64,
|
||||
mock_program_id: Pubkey,
|
||||
recent_blockhash: Hash,
|
||||
) -> Transaction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(funder.pubkey(), false),
|
||||
AccountMeta::new(*reallocd, false),
|
||||
];
|
||||
let instruction = Instruction::new_with_bincode(
|
||||
mock_program_id,
|
||||
&MockReallocInstruction::Realloc(new_size, new_balance, Pubkey::new_unique()),
|
||||
account_metas,
|
||||
);
|
||||
Transaction::new_signed_with_payer(
|
||||
&[instruction],
|
||||
Some(&payer.pubkey()),
|
||||
&[payer],
|
||||
recent_blockhash,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resize_and_rent() {
|
||||
let GenesisConfigInfo {
|
||||
mut genesis_config,
|
||||
mint_keypair,
|
||||
..
|
||||
} = create_genesis_config_with_leader(1_000_000_000, &Pubkey::new_unique(), 42);
|
||||
genesis_config.rent = Rent::default();
|
||||
activate_all_features(&mut genesis_config);
|
||||
|
||||
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
let mock_program_id = Pubkey::new_unique();
|
||||
bank.add_builtin(
|
||||
"mock_realloc_program",
|
||||
&mock_program_id,
|
||||
mock_realloc_process_instruction,
|
||||
);
|
||||
let recent_blockhash = bank.last_blockhash();
|
||||
|
||||
let account_data_size_small = 1024;
|
||||
let rent_exempt_minimum_small =
|
||||
genesis_config.rent.minimum_balance(account_data_size_small);
|
||||
let account_data_size_large = 2048;
|
||||
let rent_exempt_minimum_large =
|
||||
genesis_config.rent.minimum_balance(account_data_size_large);
|
||||
|
||||
let funding_keypair = Keypair::new();
|
||||
bank.store_account(
|
||||
&funding_keypair.pubkey(),
|
||||
&AccountSharedData::new(1_000_000_000, 0, &mock_program_id),
|
||||
);
|
||||
|
||||
let rent_paying_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let mut rent_paying_account = AccountSharedData::new(
|
||||
rent_exempt_minimum_small - 1,
|
||||
account_data_size_small,
|
||||
&mock_program_id,
|
||||
);
|
||||
rent_paying_account.set_rent_epoch(1);
|
||||
|
||||
// restore program-owned account
|
||||
bank.store_account(&rent_paying_pubkey, &rent_paying_account);
|
||||
|
||||
// rent paying, realloc larger, fail because not rent exempt
|
||||
let tx = create_mock_realloc_tx(
|
||||
&mint_keypair,
|
||||
&funding_keypair,
|
||||
&rent_paying_pubkey,
|
||||
account_data_size_large,
|
||||
rent_exempt_minimum_small - 1,
|
||||
mock_program_id,
|
||||
recent_blockhash,
|
||||
);
|
||||
assert_eq!(
|
||||
bank.process_transaction(&tx).unwrap_err(),
|
||||
TransactionError::InvalidRentPayingAccount,
|
||||
);
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_small - 1,
|
||||
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
|
||||
);
|
||||
|
||||
// rent paying, realloc larger and rent exempt
|
||||
let tx = create_mock_realloc_tx(
|
||||
&mint_keypair,
|
||||
&funding_keypair,
|
||||
&rent_paying_pubkey,
|
||||
account_data_size_large,
|
||||
rent_exempt_minimum_large,
|
||||
mock_program_id,
|
||||
recent_blockhash,
|
||||
);
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_large,
|
||||
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
|
||||
);
|
||||
|
||||
// rent exempt, realloc small, fail because not rent exempt
|
||||
let tx = create_mock_realloc_tx(
|
||||
&mint_keypair,
|
||||
&funding_keypair,
|
||||
&rent_paying_pubkey,
|
||||
account_data_size_small,
|
||||
rent_exempt_minimum_small - 1,
|
||||
mock_program_id,
|
||||
recent_blockhash,
|
||||
);
|
||||
assert_eq!(
|
||||
bank.process_transaction(&tx).unwrap_err(),
|
||||
TransactionError::InvalidRentPayingAccount,
|
||||
);
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_large,
|
||||
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
|
||||
);
|
||||
|
||||
// rent exempt, realloc smaller and rent exempt
|
||||
let tx = create_mock_realloc_tx(
|
||||
&mint_keypair,
|
||||
&funding_keypair,
|
||||
&rent_paying_pubkey,
|
||||
account_data_size_small,
|
||||
rent_exempt_minimum_small,
|
||||
mock_program_id,
|
||||
recent_blockhash,
|
||||
);
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_small,
|
||||
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
|
||||
);
|
||||
|
||||
// rent exempt, realloc large, fail because not rent exempt
|
||||
let tx = create_mock_realloc_tx(
|
||||
&mint_keypair,
|
||||
&funding_keypair,
|
||||
&rent_paying_pubkey,
|
||||
account_data_size_large,
|
||||
rent_exempt_minimum_large - 1,
|
||||
mock_program_id,
|
||||
recent_blockhash,
|
||||
);
|
||||
assert_eq!(
|
||||
bank.process_transaction(&tx).unwrap_err(),
|
||||
TransactionError::InvalidRentPayingAccount,
|
||||
);
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_small,
|
||||
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
|
||||
);
|
||||
|
||||
// rent exempt, realloc large and rent exempt
|
||||
let tx = create_mock_realloc_tx(
|
||||
&mint_keypair,
|
||||
&funding_keypair,
|
||||
&rent_paying_pubkey,
|
||||
account_data_size_large,
|
||||
rent_exempt_minimum_large,
|
||||
mock_program_id,
|
||||
recent_blockhash,
|
||||
);
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_large,
|
||||
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
|
||||
);
|
||||
|
||||
let created_keypair = Keypair::new();
|
||||
|
||||
// create account, not rent exempt
|
||||
let tx = system_transaction::create_account(
|
||||
&mint_keypair,
|
||||
&created_keypair,
|
||||
recent_blockhash,
|
||||
rent_exempt_minimum_small - 1,
|
||||
account_data_size_small as u64,
|
||||
&system_program::id(),
|
||||
);
|
||||
assert_eq!(
|
||||
bank.process_transaction(&tx).unwrap_err(),
|
||||
TransactionError::InvalidRentPayingAccount,
|
||||
);
|
||||
|
||||
// create account, rent exempt
|
||||
let tx = system_transaction::create_account(
|
||||
&mint_keypair,
|
||||
&created_keypair,
|
||||
recent_blockhash,
|
||||
rent_exempt_minimum_small,
|
||||
account_data_size_small as u64,
|
||||
&system_program::id(),
|
||||
);
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_small,
|
||||
bank.get_account(&created_keypair.pubkey())
|
||||
.unwrap()
|
||||
.lamports()
|
||||
);
|
||||
|
||||
let created_keypair = Keypair::new();
|
||||
// create account, no data
|
||||
let tx = system_transaction::create_account(
|
||||
&mint_keypair,
|
||||
&created_keypair,
|
||||
recent_blockhash,
|
||||
rent_exempt_minimum_small - 1,
|
||||
0,
|
||||
&system_program::id(),
|
||||
);
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_small - 1,
|
||||
bank.get_account(&created_keypair.pubkey())
|
||||
.unwrap()
|
||||
.lamports()
|
||||
);
|
||||
|
||||
// alloc but not rent exempt
|
||||
let tx = system_transaction::allocate(
|
||||
&mint_keypair,
|
||||
&created_keypair,
|
||||
recent_blockhash,
|
||||
(account_data_size_small + 1) as u64,
|
||||
);
|
||||
assert_eq!(
|
||||
bank.process_transaction(&tx).unwrap_err(),
|
||||
TransactionError::InvalidRentPayingAccount,
|
||||
);
|
||||
|
||||
// bring balance of account up to rent exemption
|
||||
let tx = system_transaction::transfer(
|
||||
&mint_keypair,
|
||||
&created_keypair.pubkey(),
|
||||
1,
|
||||
recent_blockhash,
|
||||
);
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_small,
|
||||
bank.get_account(&created_keypair.pubkey())
|
||||
.unwrap()
|
||||
.lamports()
|
||||
);
|
||||
|
||||
// allocate as rent exempt
|
||||
let tx = system_transaction::allocate(
|
||||
&mint_keypair,
|
||||
&created_keypair,
|
||||
recent_blockhash,
|
||||
account_data_size_small as u64,
|
||||
);
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_small,
|
||||
bank.get_account(&created_keypair.pubkey())
|
||||
.unwrap()
|
||||
.lamports()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,9 @@ impl Bank {
|
||||
let require_rent_exempt_accounts = self
|
||||
.feature_set
|
||||
.is_active(&feature_set::require_rent_exempt_accounts::id());
|
||||
let do_support_realloc = self
|
||||
.feature_set
|
||||
.is_active(&feature_set::do_support_realloc::id());
|
||||
for (i, (pre_state_info, post_state_info)) in
|
||||
pre_state_infos.iter().zip(post_state_infos).enumerate()
|
||||
{
|
||||
@ -66,6 +69,7 @@ impl Bank {
|
||||
post_state_info.rent_state.as_ref(),
|
||||
transaction_context,
|
||||
i,
|
||||
do_support_realloc,
|
||||
) {
|
||||
// Feature gate only wraps the actual error return so that the metrics and debug
|
||||
// logging generated by `check_rent_state()` can be examined before feature
|
||||
|
Reference in New Issue
Block a user