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) ]
2023-08-09 13:03:36 -07:00
pub 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 {
2023-08-09 13:03:36 -07:00
pub fn from_account ( account : & AccountSharedData , rent : & Rent ) -> Self {
2022-01-11 10:32:25 -08:00
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
}
}
2023-08-09 13:03:36 -07:00
pub fn transition_allowed_from ( & self , pre_rent_state : & RentState ) -> bool {
2022-07-15 06:23:37 -07:00
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 ,
} = > {
2022-11-15 11:55:34 -08:00
// Cannot remain RentPaying if resized or credited.
post_data_size = = pre_data_size & & post_lamports < = pre_lamports
2022-07-15 06:23:37 -07:00
}
}
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 ) ;
}
_ = > { }
}
}
2023-08-09 13:03:36 -07:00
pub fn check_rent_state (
2022-01-11 10:32:25 -08:00
pre_rent_state : Option < & RentState > ,
post_rent_state : Option < & RentState > ,
transaction_context : & TransactionContext ,
2022-09-06 02:31:40 -07:00
index : IndexOfAccount ,
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 ( ) ,
2023-05-23 06:02:55 -07:00
index ,
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 ,
2023-05-23 06:02:55 -07:00
account_index : IndexOfAccount ,
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-11-15 11:55:34 -08:00
& & ! post_rent_state . transition_allowed_from ( pre_rent_state )
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 ,
) ;
2023-05-23 06:02:55 -07:00
let account_index = account_index as u8 ;
Err ( TransactionError ::InsufficientFundsForRent { account_index } )
2022-05-16 08:35:34 -07:00
} 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-02-24 17:49:33 -08:00
let post_rent_state = RentState ::Uninitialized ;
2022-11-15 11:55:34 -08:00
assert! ( post_rent_state . transition_allowed_from ( & RentState ::Uninitialized ) ) ;
assert! ( post_rent_state . transition_allowed_from ( & RentState ::RentExempt ) ) ;
assert! (
post_rent_state . transition_allowed_from ( & RentState ::RentPaying {
2022-07-15 06:23:37 -07:00
data_size : 0 ,
lamports : 1 ,
2022-11-15 11:55:34 -08:00
} )
) ;
2022-02-24 17:49:33 -08:00
let post_rent_state = RentState ::RentExempt ;
2022-11-15 11:55:34 -08:00
assert! ( post_rent_state . transition_allowed_from ( & RentState ::Uninitialized ) ) ;
assert! ( post_rent_state . transition_allowed_from ( & RentState ::RentExempt ) ) ;
assert! (
post_rent_state . transition_allowed_from ( & RentState ::RentPaying {
2022-07-15 06:23:37 -07:00
data_size : 0 ,
lamports : 1 ,
2022-11-15 11:55:34 -08:00
} )
) ;
2022-07-15 06:23:37 -07:00
let post_rent_state = RentState ::RentPaying {
data_size : 2 ,
lamports : 5 ,
} ;
2022-11-15 11:55:34 -08:00
assert! ( ! post_rent_state . transition_allowed_from ( & RentState ::Uninitialized ) ) ;
assert! ( ! post_rent_state . transition_allowed_from ( & RentState ::RentExempt ) ) ;
assert! (
! post_rent_state . transition_allowed_from ( & RentState ::RentPaying {
2022-07-15 06:23:37 -07:00
data_size : 3 ,
lamports : 5
2022-11-15 11:55:34 -08:00
} )
) ;
assert! (
! post_rent_state . transition_allowed_from ( & RentState ::RentPaying {
2022-07-15 06:23:37 -07:00
data_size : 1 ,
lamports : 5
2022-11-15 11:55:34 -08:00
} )
) ;
2022-07-15 06:23:37 -07:00
// Transition is always allowed if there is no account data resize or
// change in account's lamports.
2022-11-15 11:55:34 -08:00
assert! (
post_rent_state . transition_allowed_from ( & RentState ::RentPaying {
2022-07-15 06:23:37 -07:00
data_size : 2 ,
lamports : 5
2022-11-15 11:55:34 -08:00
} )
) ;
2022-07-15 06:23:37 -07:00
// Transition is always allowed if there is no account data resize and
// account's lamports is reduced.
2022-11-15 11:55:34 -08:00
assert! (
post_rent_state . transition_allowed_from ( & RentState ::RentPaying {
2022-07-15 06:23:37 -07:00
data_size : 2 ,
lamports : 7
2022-11-15 11:55:34 -08:00
} )
) ;
// Transition is not allowed if the account is credited with more
// lamports and remains rent-paying.
assert! (
! post_rent_state . transition_allowed_from ( & RentState ::RentPaying {
data_size : 2 ,
lamports : 3
} ) ,
2022-07-15 06:23:37 -07:00
) ;
2022-01-11 10:32:25 -08:00
}
}