This repository has been archived by the owner on Jan 22, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Prevent creation of rent-paying accounts with non-empty data #21142
Closed
CriesofCarrots
wants to merge
8
commits into
solana-labs:master
from
CriesofCarrots:prevent-nonexempt-account-creation
Closed
Changes from 5 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
34d72e7
Simplify types slightly
75dd046
Add new feature
c40aa70
Fixup typo
dcc1ab0
Add new TransactionError
e56efdd
Fail transactions that leave new rent-paying accounts with non-empty …
26a90c1
Fixup test
c937517
Bump frozen_abi hash (new TransactionError)
57e5887
Remove test no longer relevant
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
use { | ||
crate::bank::TransactionAccountRefCell, | ||
enum_iterator::IntoEnumIterator, | ||
solana_sdk::{account::ReadableAccount, native_loader, rent::Rent, sysvar}, | ||
}; | ||
|
||
#[derive(Debug, PartialEq, IntoEnumIterator)] | ||
pub(crate) enum RentState { | ||
Uninitialized, // account.lamports == 0 | ||
NativeOrSysvar, | ||
EmptyData, // account.data.len == 0 | ||
RentPaying, // 0 < account.lamports < rent-exempt-minimum for non-zero data size | ||
RentExempt, // account.lamports >= rent-exempt-minimum for non-zero data size | ||
} | ||
|
||
impl RentState { | ||
pub(crate) fn from_account(account: &TransactionAccountRefCell, rent: &Rent) -> Self { | ||
let account = account.1.borrow(); | ||
if account.lamports() == 0 { | ||
Self::Uninitialized | ||
} else if native_loader::check_id(account.owner()) || sysvar::is_sysvar_id(account.owner()) | ||
{ | ||
Self::NativeOrSysvar | ||
} else if account.data().is_empty() { | ||
Self::EmptyData | ||
} else if !rent.is_exempt(account.lamports(), account.data().len()) { | ||
Self::RentPaying | ||
} 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) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use { | ||
super::*, | ||
solana_sdk::{account::AccountSharedData, pubkey::Pubkey, system_program}, | ||
std::{cell::RefCell, rc::Rc}, | ||
}; | ||
|
||
#[test] | ||
fn test_from_account() { | ||
let account_address = Pubkey::new_unique(); | ||
let program_id = Pubkey::new_unique(); | ||
let uninitialized_account = Rc::new(RefCell::new(AccountSharedData::new( | ||
0, | ||
0, | ||
&Pubkey::default(), | ||
))); | ||
let native_program_account = Rc::new(RefCell::new(AccountSharedData::new( | ||
1, | ||
42, | ||
&native_loader::id(), | ||
))); | ||
let sysvar_account = Rc::new(RefCell::new(AccountSharedData::new( | ||
1, | ||
42, | ||
&sysvar::clock::id(), | ||
))); | ||
let empty_data_system_account = Rc::new(RefCell::new(AccountSharedData::new( | ||
10, | ||
0, | ||
&system_program::id(), | ||
))); | ||
let empty_data_other_account = | ||
Rc::new(RefCell::new(AccountSharedData::new(10, 0, &program_id))); | ||
|
||
let account_data_size = 100; | ||
|
||
let rent = Rent::free(); | ||
let rent_exempt_account = Rc::new(RefCell::new(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(&(account_address, uninitialized_account.clone()), &rent), | ||
RentState::Uninitialized | ||
); | ||
assert_eq!( | ||
RentState::from_account(&(account_address, native_program_account.clone()), &rent), | ||
RentState::NativeOrSysvar | ||
); | ||
assert_eq!( | ||
RentState::from_account(&(account_address, sysvar_account.clone()), &rent), | ||
RentState::NativeOrSysvar | ||
); | ||
assert_eq!( | ||
RentState::from_account(&(account_address, empty_data_system_account.clone()), &rent), | ||
RentState::EmptyData | ||
); | ||
assert_eq!( | ||
RentState::from_account(&(account_address, empty_data_other_account.clone()), &rent), | ||
RentState::EmptyData | ||
); | ||
assert_eq!( | ||
RentState::from_account(&(account_address, rent_exempt_account), &rent), | ||
RentState::RentExempt | ||
); | ||
|
||
let rent = Rent::default(); | ||
let rent_minimum_balance = rent.minimum_balance(account_data_size); | ||
let rent_paying_account = Rc::new(RefCell::new(AccountSharedData::new( | ||
rent_minimum_balance.saturating_sub(1), | ||
account_data_size, | ||
&program_id, | ||
))); | ||
let rent_exempt_account = Rc::new(RefCell::new(AccountSharedData::new( | ||
rent.minimum_balance(account_data_size), | ||
account_data_size, | ||
&program_id, | ||
))); | ||
|
||
assert_eq!( | ||
RentState::from_account(&(account_address, uninitialized_account), &rent), | ||
RentState::Uninitialized | ||
); | ||
assert_eq!( | ||
RentState::from_account(&(account_address, native_program_account), &rent), | ||
RentState::NativeOrSysvar | ||
); | ||
assert_eq!( | ||
RentState::from_account(&(account_address, sysvar_account), &rent), | ||
RentState::NativeOrSysvar | ||
); | ||
assert_eq!( | ||
RentState::from_account(&(account_address, empty_data_system_account), &rent), | ||
RentState::EmptyData | ||
); | ||
assert_eq!( | ||
RentState::from_account(&(account_address, empty_data_other_account), &rent), | ||
RentState::EmptyData | ||
); | ||
assert_eq!( | ||
RentState::from_account(&(account_address, rent_paying_account), &rent), | ||
RentState::RentPaying | ||
); | ||
assert_eq!( | ||
RentState::from_account(&(account_address, rent_exempt_account), &rent), | ||
RentState::RentExempt | ||
); | ||
} | ||
|
||
#[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)); | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically, we could make do without the enum, and boil this logic down to a
bool
: is RentPaying or not. I rather like how explicit and clear the variants make the logic and exceptions, but open to other opinions.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The enum is helpful to me! I always appreciate the added type safety as well.