use {
solana_clock::Epoch,
solana_pubkey::Pubkey,
solana_rent::Rent,
solana_transaction_context::{IndexOfAccount, transaction::TransactionContext},
solana_transaction_error::{TransactionError, TransactionResult},
};
pub const RENT_EXEMPT_RENT_EPOCH: Epoch = Epoch::MAX;
#[derive(Debug, PartialEq, Eq)]
pub enum RentState {
Uninitialized,
RentPaying {
lamports: u64, data_size: usize, },
RentExempt,
}
pub fn check_rent_state(
pre_rent_state: &RentState,
post_rent_state: &RentState,
transaction_context: &TransactionContext,
index: IndexOfAccount,
) -> TransactionResult<()> {
let expect_msg = "account must exist at TransactionContext index";
check_rent_state_with_account(
pre_rent_state,
post_rent_state,
transaction_context
.get_key_of_account_at_index(index)
.expect(expect_msg),
index,
)?;
Ok(())
}
pub fn check_rent_state_with_account(
pre_rent_state: &RentState,
post_rent_state: &RentState,
address: &Pubkey,
account_index: IndexOfAccount,
) -> TransactionResult<()> {
if !solana_sdk_ids::incinerator::check_id(address)
&& !transition_allowed(pre_rent_state, post_rent_state)
{
let account_index = account_index as u8;
Err(TransactionError::InsufficientFundsForRent { account_index })
} else {
Ok(())
}
}
pub fn get_account_rent_state(
account_lamports: u64,
account_size: usize,
min_balance: u64,
) -> RentState {
if account_lamports == 0 {
RentState::Uninitialized
} else if account_lamports >= min_balance {
RentState::RentExempt
} else {
RentState::RentPaying {
data_size: account_size,
lamports: account_lamports,
}
}
}
pub fn get_pre_exec_account_rent_state(
account_lamports: u64,
account_size: usize,
min_balance: u64,
disallow_rent_paying: bool,
) -> RentState {
match get_account_rent_state(account_lamports, account_size, min_balance) {
RentState::RentPaying { .. } if disallow_rent_paying => RentState::RentExempt,
rent_state => rent_state,
}
}
pub fn get_post_exec_account_rent_state(
account_lamports: u64,
account_size: usize,
min_balance: u64,
pre_rent_state: &RentState,
pre_exec_balance: u64,
relax_rent_exempt_criteria: bool,
) -> RentState {
if !relax_rent_exempt_criteria {
return get_account_rent_state(account_lamports, account_size, min_balance);
};
match (account_lamports, &pre_rent_state) {
(0, _) => RentState::Uninitialized,
(post_balance, _) if post_balance >= min_balance => RentState::RentExempt,
(post_balance, RentState::RentExempt) if post_balance >= pre_exec_balance => {
RentState::RentExempt
}
(post_balance, _) => RentState::RentPaying {
data_size: account_size,
lamports: post_balance,
},
}
}
pub fn check_static_account_rent_state_transition(
pre_exec_balance: u64,
post_exec_balance: u64,
data_size: usize,
rent: &Rent,
account_index: IndexOfAccount,
relax_post_exec_min_balance_check: bool,
) -> TransactionResult<()> {
let rent_min_balance = rent.minimum_balance(data_size);
let pre_state = get_pre_exec_account_rent_state(
pre_exec_balance,
data_size,
rent_min_balance,
relax_post_exec_min_balance_check,
);
let post_state = get_post_exec_account_rent_state(
post_exec_balance,
data_size,
rent_min_balance,
&pre_state,
pre_exec_balance,
relax_post_exec_min_balance_check,
);
if !transition_allowed(&pre_state, &post_state) {
let account_index = account_index as u8;
Err(TransactionError::InsufficientFundsForRent { account_index })
} else {
Ok(())
}
}
pub fn transition_allowed(pre_rent_state: &RentState, post_rent_state: &RentState) -> bool {
match post_rent_state {
RentState::Uninitialized | RentState::RentExempt => true,
RentState::RentPaying {
data_size: post_data_size,
lamports: post_lamports,
} => {
match pre_rent_state {
RentState::Uninitialized | RentState::RentExempt => false,
RentState::RentPaying {
data_size: pre_data_size,
lamports: pre_lamports,
} => {
post_data_size == pre_data_size && post_lamports <= pre_lamports
}
}
}
}
}