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 } ,
transaction_context ::TransactionContext ,
} ,
} ;
2022-02-24 17:49:33 -08:00
#[ derive(Debug, PartialEq) ]
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
/// Parameter is the size of the account data
RentPaying ( usize ) ,
/// 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
} else if ! rent . is_exempt ( account . lamports ( ) , account . data ( ) . len ( ) ) {
2022-02-24 17:49:33 -08:00
Self ::RentPaying ( account . data ( ) . len ( ) )
2022-01-11 10:32:25 -08:00
} else {
Self ::RentExempt
}
}
2022-02-24 17:49:33 -08:00
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
}
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-02-24 17:49:33 -08:00
( & RentState ::Uninitialized , & RentState ::RentPaying ( _ ) ) = > {
2022-01-11 10:32:25 -08:00
inc_new_counter_info! ( " rent_paying_err-new_account " , 1 ) ;
}
2022-02-24 17:49:33 -08:00
( & RentState ::RentPaying ( _ ) , & RentState ::RentPaying ( _ ) ) = > {
2022-01-11 10:32:25 -08:00
inc_new_counter_info! ( " rent_paying_ok-legacy " , 1 ) ;
}
2022-02-24 17:49:33 -08: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 ,
index : usize ,
2022-02-24 17:49:33 -08:00
do_support_realloc : 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-02-24 17:49:33 -08:00
do_support_realloc ,
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-02-24 17:49:33 -08:00
do_support_realloc : 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-02-24 17:49:33 -08:00
& & ! post_rent_state . transition_allowed_from ( pre_rent_state , do_support_realloc )
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 ,
) ;
return Err ( TransactionError ::InvalidRentPayingAccount ) ;
2022-01-11 10:32:25 -08:00
}
Ok ( ( ) )
}
#[ 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-02-24 17:49:33 -08:00
RentState ::RentPaying ( account_data_size )
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-02-24 17:49:33 -08:00
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 ) ) ;
2022-01-11 10:32:25 -08:00
}
}