2022-01-11 10:32:25 -08:00
use {
log ::* ,
solana_sdk ::{
account ::{ AccountSharedData , ReadableAccount } ,
2022-02-26 11:10:01 -08:00
pubkey ::Pubkey ,
2022-01-11 10:32:25 -08:00
rent ::Rent ,
transaction ::{ Result , TransactionError } ,
2022-09-06 02:31:40 -07:00
transaction_context ::{ IndexOfAccount , TransactionContext } ,
2022-01-11 10:32:25 -08:00
} ,
} ;
2022-05-22 18:00:42 -07:00
#[ derive(Debug, PartialEq, Eq) ]
2022-01-11 10:32:25 -08:00
pub ( crate ) enum RentState {
2022-02-24 17:49:33 -08:00
/// account.lamports == 0
Uninitialized ,
/// 0 < account.lamports < rent-exempt-minimum
2022-07-15 06:23:37 -07:00
RentPaying {
lamports : u64 , // account.lamports()
data_size : usize , // account.data().len()
} ,
2022-02-24 17:49:33 -08:00
/// account.lamports >= rent-exempt-minimum
RentExempt ,
2022-01-11 10:32:25 -08:00
}
impl RentState {
pub ( crate ) fn from_account ( account : & AccountSharedData , rent : & Rent ) -> Self {
if account . lamports ( ) = = 0 {
Self ::Uninitialized
2022-07-15 06:23:37 -07:00
} else if rent . is_exempt ( account . lamports ( ) , account . data ( ) . len ( ) ) {
2022-01-11 10:32:25 -08:00
Self ::RentExempt
2022-07-15 06:23:37 -07:00
} else {
Self ::RentPaying {
data_size : account . data ( ) . len ( ) ,
lamports : account . lamports ( ) ,
}
2022-01-11 10:32:25 -08:00
}
}
2022-07-15 06:23:37 -07:00
pub ( crate ) fn transition_allowed_from (
& self ,
pre_rent_state : & RentState ,
prevent_crediting_accounts_that_end_rent_paying : bool ,
) -> bool {
match self {
Self ::Uninitialized | Self ::RentExempt = > true ,
Self ::RentPaying {
data_size : post_data_size ,
lamports : post_lamports ,
} = > {
match pre_rent_state {
Self ::Uninitialized | Self ::RentExempt = > false ,
Self ::RentPaying {
data_size : pre_data_size ,
lamports : pre_lamports ,
} = > {
// Cannot remain RentPaying if resized
if post_data_size ! = pre_data_size {
false
} else if prevent_crediting_accounts_that_end_rent_paying {
// Cannot remain RentPaying if credited
post_lamports < = pre_lamports
} else {
true
}
}
}
2022-02-24 17:49:33 -08:00
}
}
2022-01-11 10:32:25 -08:00
}
}
pub ( crate ) fn submit_rent_state_metrics ( pre_rent_state : & RentState , post_rent_state : & RentState ) {
match ( pre_rent_state , post_rent_state ) {
2022-07-15 06:23:37 -07:00
( & RentState ::Uninitialized , & RentState ::RentPaying { .. } ) = > {
2022-01-11 10:32:25 -08:00
inc_new_counter_info! ( " rent_paying_err-new_account " , 1 ) ;
}
2022-07-15 06:23:37 -07:00
( & RentState ::RentPaying { .. } , & RentState ::RentPaying { .. } ) = > {
2022-01-11 10:32:25 -08:00
inc_new_counter_info! ( " rent_paying_ok-legacy " , 1 ) ;
}
2022-07-15 06:23:37 -07:00
( _ , & RentState ::RentPaying { .. } ) = > {
2022-01-11 10:32:25 -08:00
inc_new_counter_info! ( " rent_paying_err-other " , 1 ) ;
}
_ = > { }
}
}
pub ( crate ) fn check_rent_state (
pre_rent_state : Option < & RentState > ,
post_rent_state : Option < & RentState > ,
transaction_context : & TransactionContext ,
2022-09-06 02:31:40 -07:00
index : IndexOfAccount ,
2022-05-16 08:35:34 -07:00
include_account_index_in_err : bool ,
2022-07-15 06:23:37 -07:00
prevent_crediting_accounts_that_end_rent_paying : bool ,
2022-01-11 10:32:25 -08:00
) -> Result < ( ) > {
if let Some ( ( pre_rent_state , post_rent_state ) ) = pre_rent_state . zip ( post_rent_state ) {
2022-02-26 11:10:01 -08:00
let expect_msg = " account must exist at TransactionContext index if rent-states are Some " ;
check_rent_state_with_account (
pre_rent_state ,
post_rent_state ,
transaction_context
. get_key_of_account_at_index ( index )
. expect ( expect_msg ) ,
& transaction_context
. get_account_at_index ( index )
. expect ( expect_msg )
. borrow ( ) ,
2022-08-22 18:01:03 -07:00
include_account_index_in_err . then_some ( index ) ,
2022-07-15 06:23:37 -07:00
prevent_crediting_accounts_that_end_rent_paying ,
2022-02-26 11:10:01 -08:00
) ? ;
}
Ok ( ( ) )
}
pub ( crate ) fn check_rent_state_with_account (
pre_rent_state : & RentState ,
post_rent_state : & RentState ,
address : & Pubkey ,
account_state : & AccountSharedData ,
2022-09-06 02:31:40 -07:00
account_index : Option < IndexOfAccount > ,
2022-07-15 06:23:37 -07:00
prevent_crediting_accounts_that_end_rent_paying : bool ,
2022-02-26 11:10:01 -08:00
) -> Result < ( ) > {
submit_rent_state_metrics ( pre_rent_state , post_rent_state ) ;
2022-02-28 22:56:34 -08:00
if ! solana_sdk ::incinerator ::check_id ( address )
2022-07-15 06:23:37 -07:00
& & ! post_rent_state . transition_allowed_from (
pre_rent_state ,
prevent_crediting_accounts_that_end_rent_paying ,
)
2022-02-28 22:56:34 -08:00
{
2022-02-26 11:10:01 -08:00
debug! (
" Account {} not rent exempt, state {:?} " ,
address , account_state ,
) ;
2022-05-16 08:35:34 -07:00
if let Some ( account_index ) = account_index {
let account_index = account_index as u8 ;
Err ( TransactionError ::InsufficientFundsForRent { account_index } )
} else {
Err ( TransactionError ::InvalidRentPayingAccount )
}
} else {
Ok ( ( ) )
2022-01-11 10:32:25 -08:00
}
}
#[ cfg(test) ]
mod tests {
use { super ::* , solana_sdk ::pubkey ::Pubkey } ;
#[ test ]
fn test_from_account ( ) {
let program_id = Pubkey ::new_unique ( ) ;
let uninitialized_account = AccountSharedData ::new ( 0 , 0 , & Pubkey ::default ( ) ) ;
let account_data_size = 100 ;
let rent = Rent ::free ( ) ;
let rent_exempt_account = AccountSharedData ::new ( 1 , account_data_size , & program_id ) ; // if rent is free, all accounts with non-zero lamports and non-empty data are rent-exempt
assert_eq! (
RentState ::from_account ( & uninitialized_account , & rent ) ,
RentState ::Uninitialized
) ;
assert_eq! (
RentState ::from_account ( & rent_exempt_account , & rent ) ,
RentState ::RentExempt
) ;
let rent = Rent ::default ( ) ;
let rent_minimum_balance = rent . minimum_balance ( account_data_size ) ;
let rent_paying_account = AccountSharedData ::new (
rent_minimum_balance . saturating_sub ( 1 ) ,
account_data_size ,
& program_id ,
) ;
let rent_exempt_account = AccountSharedData ::new (
rent . minimum_balance ( account_data_size ) ,
account_data_size ,
& program_id ,
) ;
assert_eq! (
RentState ::from_account ( & uninitialized_account , & rent ) ,
RentState ::Uninitialized
) ;
assert_eq! (
RentState ::from_account ( & rent_paying_account , & rent ) ,
2022-07-15 06:23:37 -07:00
RentState ::RentPaying {
data_size : account_data_size ,
lamports : rent_paying_account . lamports ( ) ,
}
2022-01-11 10:32:25 -08:00
) ;
assert_eq! (
RentState ::from_account ( & rent_exempt_account , & rent ) ,
RentState ::RentExempt
) ;
}
#[ test ]
fn test_transition_allowed_from ( ) {
2022-07-15 06:23:37 -07:00
check_transition_allowed_from (
/* prevent_crediting_accounts_that_end_rent_paying: */ false ,
) ;
check_transition_allowed_from (
/* prevent_crediting_accounts_that_end_rent_paying: */ true ,
) ;
}
fn check_transition_allowed_from ( prevent_crediting_accounts_that_end_rent_paying : bool ) {
2022-02-24 17:49:33 -08:00
let post_rent_state = RentState ::Uninitialized ;
2022-07-15 06:23:37 -07:00
assert! ( post_rent_state . transition_allowed_from (
& RentState ::Uninitialized ,
prevent_crediting_accounts_that_end_rent_paying
) ) ;
assert! ( post_rent_state . transition_allowed_from (
& RentState ::RentExempt ,
prevent_crediting_accounts_that_end_rent_paying
) ) ;
assert! ( post_rent_state . transition_allowed_from (
& RentState ::RentPaying {
data_size : 0 ,
lamports : 1 ,
} ,
prevent_crediting_accounts_that_end_rent_paying
) ) ;
2022-02-24 17:49:33 -08:00
let post_rent_state = RentState ::RentExempt ;
2022-07-15 06:23:37 -07:00
assert! ( post_rent_state . transition_allowed_from (
& RentState ::Uninitialized ,
prevent_crediting_accounts_that_end_rent_paying
) ) ;
assert! ( post_rent_state . transition_allowed_from (
& RentState ::RentExempt ,
prevent_crediting_accounts_that_end_rent_paying
) ) ;
assert! ( post_rent_state . transition_allowed_from (
& RentState ::RentPaying {
data_size : 0 ,
lamports : 1 ,
} ,
prevent_crediting_accounts_that_end_rent_paying
) ) ;
let post_rent_state = RentState ::RentPaying {
data_size : 2 ,
lamports : 5 ,
} ;
assert! ( ! post_rent_state . transition_allowed_from (
& RentState ::Uninitialized ,
prevent_crediting_accounts_that_end_rent_paying
) ) ;
assert! ( ! post_rent_state . transition_allowed_from (
& RentState ::RentExempt ,
prevent_crediting_accounts_that_end_rent_paying
) ) ;
assert! ( ! post_rent_state . transition_allowed_from (
& RentState ::RentPaying {
data_size : 3 ,
lamports : 5
} ,
prevent_crediting_accounts_that_end_rent_paying
) ) ;
assert! ( ! post_rent_state . transition_allowed_from (
& RentState ::RentPaying {
data_size : 1 ,
lamports : 5
} ,
prevent_crediting_accounts_that_end_rent_paying
) ) ;
// Transition is always allowed if there is no account data resize or
// change in account's lamports.
assert! ( post_rent_state . transition_allowed_from (
& RentState ::RentPaying {
data_size : 2 ,
lamports : 5
} ,
prevent_crediting_accounts_that_end_rent_paying
) ) ;
// Transition is always allowed if there is no account data resize and
// account's lamports is reduced.
assert! ( post_rent_state . transition_allowed_from (
& RentState ::RentPaying {
data_size : 2 ,
lamports : 7
} ,
prevent_crediting_accounts_that_end_rent_paying
) ) ;
// Once the feature is activated, transition is not allowed if the
// account is credited with more lamports and remains rent-paying.
assert_eq! (
post_rent_state . transition_allowed_from (
& RentState ::RentPaying {
data_size : 2 ,
lamports : 3
} ,
prevent_crediting_accounts_that_end_rent_paying
) ,
! prevent_crediting_accounts_that_end_rent_paying
) ;
2022-01-11 10:32:25 -08:00
}
}