Skip to content
This repository has been archived by the owner on Jan 22, 2025. It is now read-only.

Prevent creation of rent-paying accounts with non-empty data #21142

Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions programs/bpf/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ bzip2 = "0.4.3"
dashmap = { version = "4.0.2", features = ["rayon", "raw-api"] }
crossbeam-channel = "0.5"
dir-diff = "0.3.2"
enum-iterator = "0.7.0"
flate2 = "1.0.22"
fnv = "1.0.7"
itertools = "0.10.1"
Expand Down
165 changes: 165 additions & 0 deletions runtime/src/account_rent_state.rs
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
}
Comment on lines +19 to +30
Copy link
Contributor Author

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.

Copy link
Contributor

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.

}

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));
}
}
}
}
}
Loading