Resized accounts must be rent exempt
This commit is contained in:
parent
82cb61dc36
commit
97d40ba3da
|
@ -5571,7 +5571,6 @@ dependencies = [
|
|||
"dashmap",
|
||||
"dir-diff",
|
||||
"ed25519-dalek",
|
||||
"enum-iterator",
|
||||
"flate2",
|
||||
"fnv",
|
||||
"index_list",
|
||||
|
|
|
@ -3433,7 +3433,6 @@ dependencies = [
|
|||
"crossbeam-channel",
|
||||
"dashmap",
|
||||
"dir-diff",
|
||||
"enum-iterator",
|
||||
"flate2",
|
||||
"fnv",
|
||||
"index_list",
|
||||
|
|
|
@ -190,7 +190,7 @@ fn process_instruction(
|
|||
&system_instruction::create_account(
|
||||
accounts[0].key,
|
||||
accounts[1].key,
|
||||
1,
|
||||
3000000, // large enough for rent exemption
|
||||
pre_len as u64,
|
||||
program_id,
|
||||
),
|
||||
|
|
|
@ -54,6 +54,7 @@ use {
|
|||
loader_instruction,
|
||||
message::{v0::LoadedAddresses, Message, SanitizedMessage},
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
signature::{keypair_from_seed, Keypair, Signer},
|
||||
system_instruction::{self, MAX_PERMITTED_DATA_LENGTH},
|
||||
system_program,
|
||||
|
@ -2641,11 +2642,13 @@ fn test_program_bpf_ro_account_modify() {
|
|||
fn test_program_bpf_realloc() {
|
||||
solana_logger::setup();
|
||||
|
||||
const START_BALANCE: u64 = 100_000_000_000;
|
||||
|
||||
let GenesisConfigInfo {
|
||||
genesis_config,
|
||||
mint_keypair,
|
||||
..
|
||||
} = create_genesis_config(50);
|
||||
} = create_genesis_config(1_000_000_000_000);
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let signer = &[&mint_keypair];
|
||||
|
||||
|
@ -2665,7 +2668,7 @@ fn test_program_bpf_realloc() {
|
|||
let mut bump = 0;
|
||||
let keypair = Keypair::new();
|
||||
let pubkey = keypair.pubkey();
|
||||
let account = AccountSharedData::new(42, 5, &program_id);
|
||||
let account = AccountSharedData::new(START_BALANCE, 5, &program_id);
|
||||
bank.store_account(&pubkey, &account);
|
||||
|
||||
// Realloc RO account
|
||||
|
@ -2897,11 +2900,14 @@ fn test_program_bpf_realloc() {
|
|||
fn test_program_bpf_realloc_invoke() {
|
||||
solana_logger::setup();
|
||||
|
||||
const START_BALANCE: u64 = 100_000_000_000;
|
||||
|
||||
let GenesisConfigInfo {
|
||||
genesis_config,
|
||||
mut genesis_config,
|
||||
mint_keypair,
|
||||
..
|
||||
} = create_genesis_config(50);
|
||||
} = create_genesis_config(1_000_000_000_000);
|
||||
genesis_config.rent = Rent::default();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let signer = &[&mint_keypair];
|
||||
|
||||
|
@ -2928,7 +2934,7 @@ fn test_program_bpf_realloc_invoke() {
|
|||
let mut bump = 0;
|
||||
let keypair = Keypair::new();
|
||||
let pubkey = keypair.pubkey().clone();
|
||||
let account = AccountSharedData::new(42, 5, &realloc_program_id);
|
||||
let account = AccountSharedData::new(START_BALANCE, 5, &realloc_program_id);
|
||||
bank.store_account(&pubkey, &account);
|
||||
let invoke_keypair = Keypair::new();
|
||||
let invoke_pubkey = invoke_keypair.pubkey().clone();
|
||||
|
@ -2954,6 +2960,8 @@ fn test_program_bpf_realloc_invoke() {
|
|||
.unwrap(),
|
||||
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
|
||||
);
|
||||
let account = bank.get_account(&pubkey).unwrap();
|
||||
assert_eq!(account.lamports(), START_BALANCE);
|
||||
|
||||
// Realloc account to 0
|
||||
bank_client
|
||||
|
@ -2965,6 +2973,8 @@ fn test_program_bpf_realloc_invoke() {
|
|||
),
|
||||
)
|
||||
.unwrap();
|
||||
let account = bank.get_account(&pubkey).unwrap();
|
||||
assert_eq!(account.lamports(), START_BALANCE);
|
||||
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
||||
assert_eq!(0, data.len());
|
||||
|
||||
|
@ -3012,54 +3022,7 @@ fn test_program_bpf_realloc_invoke() {
|
|||
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
|
||||
);
|
||||
|
||||
// Realloc to max length in max increase increments
|
||||
for i in 0..MAX_PERMITTED_DATA_LENGTH as usize / MAX_PERMITTED_DATA_INCREASE {
|
||||
bank_client
|
||||
.send_and_confirm_message(
|
||||
signer,
|
||||
Message::new(
|
||||
&[Instruction::new_with_bytes(
|
||||
realloc_invoke_program_id,
|
||||
&[INVOKE_REALLOC_EXTEND_MAX, 1, i as u8, (i / 255) as u8],
|
||||
vec![
|
||||
AccountMeta::new(pubkey, false),
|
||||
AccountMeta::new_readonly(realloc_program_id, false),
|
||||
],
|
||||
)],
|
||||
Some(&mint_pubkey),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
||||
assert_eq!((i + 1) * MAX_PERMITTED_DATA_INCREASE, data.len());
|
||||
}
|
||||
for i in 0..data.len() {
|
||||
assert_eq!(data[i], 1);
|
||||
}
|
||||
|
||||
// and one more time should fail
|
||||
assert_eq!(
|
||||
bank_client
|
||||
.send_and_confirm_message(
|
||||
signer,
|
||||
Message::new(
|
||||
&[Instruction::new_with_bytes(
|
||||
realloc_invoke_program_id,
|
||||
&[INVOKE_REALLOC_EXTEND_MAX, 2, 1, 1],
|
||||
vec![
|
||||
AccountMeta::new(pubkey, false),
|
||||
AccountMeta::new_readonly(realloc_program_id, false),
|
||||
],
|
||||
)],
|
||||
Some(&mint_pubkey),
|
||||
)
|
||||
)
|
||||
.unwrap_err()
|
||||
.unwrap(),
|
||||
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
|
||||
);
|
||||
|
||||
// Realloc to 0
|
||||
// Realloc account to 0
|
||||
bank_client
|
||||
.send_and_confirm_message(
|
||||
signer,
|
||||
|
@ -3069,6 +3032,8 @@ fn test_program_bpf_realloc_invoke() {
|
|||
),
|
||||
)
|
||||
.unwrap();
|
||||
let account = bank.get_account(&pubkey).unwrap();
|
||||
assert_eq!(account.lamports(), START_BALANCE);
|
||||
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
||||
assert_eq!(0, data.len());
|
||||
|
||||
|
@ -3169,7 +3134,7 @@ fn test_program_bpf_realloc_invoke() {
|
|||
assert_eq!(0, data.len());
|
||||
|
||||
// Realloc to 100 and check via CPI
|
||||
let invoke_account = AccountSharedData::new(42, 5, &realloc_invoke_program_id);
|
||||
let invoke_account = AccountSharedData::new(START_BALANCE, 5, &realloc_invoke_program_id);
|
||||
bank.store_account(&invoke_pubkey, &invoke_account);
|
||||
bank_client
|
||||
.send_and_confirm_message(
|
||||
|
@ -3199,42 +3164,6 @@ fn test_program_bpf_realloc_invoke() {
|
|||
assert_eq!(data[i], 2);
|
||||
}
|
||||
|
||||
// Realloc rescursively and fill data
|
||||
let invoke_keypair = Keypair::new();
|
||||
let invoke_pubkey = invoke_keypair.pubkey().clone();
|
||||
let invoke_account = AccountSharedData::new(42, 0, &realloc_invoke_program_id);
|
||||
bank.store_account(&invoke_pubkey, &invoke_account);
|
||||
let mut instruction_data = vec![];
|
||||
instruction_data.extend_from_slice(&[INVOKE_REALLOC_RECURSIVE, 1]);
|
||||
instruction_data.extend_from_slice(&100_usize.to_le_bytes());
|
||||
bank_client
|
||||
.send_and_confirm_message(
|
||||
signer,
|
||||
Message::new(
|
||||
&[Instruction::new_with_bytes(
|
||||
realloc_invoke_program_id,
|
||||
&instruction_data,
|
||||
vec![
|
||||
AccountMeta::new(invoke_pubkey, false),
|
||||
AccountMeta::new_readonly(realloc_invoke_program_id, false),
|
||||
],
|
||||
)],
|
||||
Some(&mint_pubkey),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
let data = bank_client
|
||||
.get_account_data(&invoke_pubkey)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(200, data.len());
|
||||
for i in 0..100 {
|
||||
assert_eq!(data[i], 1);
|
||||
}
|
||||
for i in 100..200 {
|
||||
assert_eq!(data[i], 2);
|
||||
}
|
||||
|
||||
// Create account, realloc, check
|
||||
let new_keypair = Keypair::new();
|
||||
let new_pubkey = new_keypair.pubkey().clone();
|
||||
|
@ -3267,7 +3196,7 @@ fn test_program_bpf_realloc_invoke() {
|
|||
// Invoke, dealloc, and assign
|
||||
let pre_len = 100;
|
||||
let new_len = pre_len * 2;
|
||||
let mut invoke_account = AccountSharedData::new(42, pre_len, &realloc_program_id);
|
||||
let mut invoke_account = AccountSharedData::new(START_BALANCE, pre_len, &realloc_program_id);
|
||||
invoke_account.set_data_from_slice(&vec![1; pre_len]);
|
||||
bank.store_account(&invoke_pubkey, &invoke_account);
|
||||
let mut instruction_data = vec![];
|
||||
|
@ -3347,6 +3276,102 @@ fn test_program_bpf_realloc_invoke() {
|
|||
.unwrap(),
|
||||
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
|
||||
);
|
||||
|
||||
// Realloc to 0
|
||||
bank_client
|
||||
.send_and_confirm_message(
|
||||
signer,
|
||||
Message::new(
|
||||
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
|
||||
Some(&mint_pubkey),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
||||
assert_eq!(0, data.len());
|
||||
|
||||
// Realloc to max length in max increase increments
|
||||
for i in 0..MAX_PERMITTED_DATA_LENGTH as usize / MAX_PERMITTED_DATA_INCREASE {
|
||||
bank_client
|
||||
.send_and_confirm_message(
|
||||
signer,
|
||||
Message::new(
|
||||
&[Instruction::new_with_bytes(
|
||||
realloc_invoke_program_id,
|
||||
&[INVOKE_REALLOC_EXTEND_MAX, 1, i as u8, (i / 255) as u8],
|
||||
vec![
|
||||
AccountMeta::new(pubkey, false),
|
||||
AccountMeta::new_readonly(realloc_program_id, false),
|
||||
],
|
||||
)],
|
||||
Some(&mint_pubkey),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
||||
assert_eq!((i + 1) * MAX_PERMITTED_DATA_INCREASE, data.len());
|
||||
}
|
||||
for i in 0..data.len() {
|
||||
assert_eq!(data[i], 1);
|
||||
}
|
||||
|
||||
// and one more time should fail
|
||||
assert_eq!(
|
||||
bank_client
|
||||
.send_and_confirm_message(
|
||||
signer,
|
||||
Message::new(
|
||||
&[Instruction::new_with_bytes(
|
||||
realloc_invoke_program_id,
|
||||
&[INVOKE_REALLOC_EXTEND_MAX, 2, 1, 1],
|
||||
vec![
|
||||
AccountMeta::new(pubkey, false),
|
||||
AccountMeta::new_readonly(realloc_program_id, false),
|
||||
],
|
||||
)],
|
||||
Some(&mint_pubkey),
|
||||
)
|
||||
)
|
||||
.unwrap_err()
|
||||
.unwrap(),
|
||||
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
|
||||
);
|
||||
|
||||
// Realloc rescursively and fill data
|
||||
let invoke_keypair = Keypair::new();
|
||||
let invoke_pubkey = invoke_keypair.pubkey().clone();
|
||||
let invoke_account = AccountSharedData::new(START_BALANCE, 0, &realloc_invoke_program_id);
|
||||
bank.store_account(&invoke_pubkey, &invoke_account);
|
||||
let mut instruction_data = vec![];
|
||||
instruction_data.extend_from_slice(&[INVOKE_REALLOC_RECURSIVE, 1]);
|
||||
instruction_data.extend_from_slice(&100_usize.to_le_bytes());
|
||||
bank_client
|
||||
.send_and_confirm_message(
|
||||
signer,
|
||||
Message::new(
|
||||
&[Instruction::new_with_bytes(
|
||||
realloc_invoke_program_id,
|
||||
&instruction_data,
|
||||
vec![
|
||||
AccountMeta::new(invoke_pubkey, false),
|
||||
AccountMeta::new_readonly(realloc_invoke_program_id, false),
|
||||
],
|
||||
)],
|
||||
Some(&mint_pubkey),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
let data = bank_client
|
||||
.get_account_data(&invoke_pubkey)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(200, data.len());
|
||||
for i in 0..100 {
|
||||
assert_eq!(data[i], 1);
|
||||
}
|
||||
for i in 100..200 {
|
||||
assert_eq!(data[i], 2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -20,7 +20,6 @@ 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"
|
||||
index_list = "0.2.7"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use {
|
||||
enum_iterator::IntoEnumIterator,
|
||||
log::*,
|
||||
solana_sdk::{
|
||||
account::{AccountSharedData, ReadableAccount},
|
||||
|
@ -10,11 +9,15 @@ use {
|
|||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, IntoEnumIterator)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum RentState {
|
||||
Uninitialized, // account.lamports == 0
|
||||
RentPaying, // 0 < account.lamports < rent-exempt-minimum
|
||||
RentExempt, // account.lamports >= rent-exempt-minimum
|
||||
/// 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,
|
||||
}
|
||||
|
||||
impl RentState {
|
||||
|
@ -22,27 +25,42 @@ impl RentState {
|
|||
if account.lamports() == 0 {
|
||||
Self::Uninitialized
|
||||
} else if !rent.is_exempt(account.lamports(), account.data().len()) {
|
||||
Self::RentPaying
|
||||
Self::RentPaying(account.data().len())
|
||||
} 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)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn submit_rent_state_metrics(pre_rent_state: &RentState, post_rent_state: &RentState) {
|
||||
match (pre_rent_state, post_rent_state) {
|
||||
(&RentState::Uninitialized, &RentState::RentPaying) => {
|
||||
(&RentState::Uninitialized, &RentState::RentPaying(_)) => {
|
||||
inc_new_counter_info!("rent_paying_err-new_account", 1);
|
||||
}
|
||||
(&RentState::RentPaying, &RentState::RentPaying) => {
|
||||
(&RentState::RentPaying(_), &RentState::RentPaying(_)) => {
|
||||
inc_new_counter_info!("rent_paying_ok-legacy", 1);
|
||||
}
|
||||
(_, &RentState::RentPaying) => {
|
||||
(_, &RentState::RentPaying(_)) => {
|
||||
inc_new_counter_info!("rent_paying_err-other", 1);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -54,6 +72,7 @@ pub(crate) fn check_rent_state(
|
|||
post_rent_state: Option<&RentState>,
|
||||
transaction_context: &TransactionContext,
|
||||
index: usize,
|
||||
do_support_realloc: bool,
|
||||
) -> Result<()> {
|
||||
if let Some((pre_rent_state, post_rent_state)) = pre_rent_state.zip(post_rent_state) {
|
||||
let expect_msg = "account must exist at TransactionContext index if rent-states are Some";
|
||||
|
@ -67,6 +86,7 @@ pub(crate) fn check_rent_state(
|
|||
.get_account_at_index(index)
|
||||
.expect(expect_msg)
|
||||
.borrow(),
|
||||
do_support_realloc,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
|
@ -77,10 +97,11 @@ pub(crate) fn check_rent_state_with_account(
|
|||
post_rent_state: &RentState,
|
||||
address: &Pubkey,
|
||||
account_state: &AccountSharedData,
|
||||
do_support_realloc: bool,
|
||||
) -> Result<()> {
|
||||
submit_rent_state_metrics(pre_rent_state, post_rent_state);
|
||||
if !solana_sdk::incinerator::check_id(address)
|
||||
&& !post_rent_state.transition_allowed_from(pre_rent_state)
|
||||
&& !post_rent_state.transition_allowed_from(pre_rent_state, do_support_realloc)
|
||||
{
|
||||
debug!(
|
||||
"Account {} not rent exempt, state {:?}",
|
||||
|
@ -133,7 +154,7 @@ mod tests {
|
|||
);
|
||||
assert_eq!(
|
||||
RentState::from_account(&rent_paying_account, &rent),
|
||||
RentState::RentPaying
|
||||
RentState::RentPaying(account_data_size)
|
||||
);
|
||||
assert_eq!(
|
||||
RentState::from_account(&rent_exempt_account, &rent),
|
||||
|
@ -143,16 +164,21 @@ mod tests {
|
|||
|
||||
#[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));
|
||||
}
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -377,6 +377,7 @@ impl Accounts {
|
|||
&payer_post_rent_state,
|
||||
payer_address,
|
||||
payer_account,
|
||||
feature_set.is_active(&feature_set::do_support_realloc::id()),
|
||||
);
|
||||
// Feature gate only wraps the actual error return so that the metrics and debug
|
||||
// logging generated by `check_rent_state_with_account()` can be examined before
|
||||
|
|
|
@ -16285,12 +16285,22 @@ pub(crate) mod tests {
|
|||
let rent_paying_account = Keypair::new();
|
||||
genesis_config.accounts.insert(
|
||||
rent_paying_account.pubkey(),
|
||||
Account::new(rent_exempt_minimum - 1, account_data_size, &mock_program_id),
|
||||
Account::new_rent_epoch(
|
||||
rent_exempt_minimum - 1,
|
||||
account_data_size,
|
||||
&mock_program_id,
|
||||
INITIAL_RENT_EPOCH + 1,
|
||||
),
|
||||
);
|
||||
let rent_exempt_account = Keypair::new();
|
||||
genesis_config.accounts.insert(
|
||||
rent_exempt_account.pubkey(),
|
||||
Account::new(rent_exempt_minimum, account_data_size, &mock_program_id),
|
||||
Account::new_rent_epoch(
|
||||
rent_exempt_minimum,
|
||||
account_data_size,
|
||||
&mock_program_id,
|
||||
INITIAL_RENT_EPOCH + 1,
|
||||
),
|
||||
);
|
||||
// Activate features, including require_rent_exempt_accounts
|
||||
activate_all_features(&mut genesis_config);
|
||||
|
@ -16427,6 +16437,97 @@ pub(crate) mod tests {
|
|||
assert!(check_account_is_rent_exempt(&rent_exempt_account.pubkey()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drained_created_account() {
|
||||
let GenesisConfigInfo {
|
||||
mut genesis_config,
|
||||
mint_keypair,
|
||||
..
|
||||
} = create_genesis_config_with_leader(sol_to_lamports(100.), &Pubkey::new_unique(), 42);
|
||||
genesis_config.rent = Rent::default();
|
||||
activate_all_features(&mut genesis_config);
|
||||
|
||||
let mock_program_id = Pubkey::new_unique();
|
||||
// small enough to not pay rent, thus bypassing the data clearing rent
|
||||
// mechanism
|
||||
let data_size_no_rent = 100;
|
||||
// large enough to pay rent, will have data cleared
|
||||
let data_size_rent = 10000;
|
||||
let lamports_to_transfer = 100;
|
||||
|
||||
// Create legacy accounts of various kinds
|
||||
let created_keypair = Keypair::new();
|
||||
|
||||
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||
bank.add_builtin(
|
||||
"mock_program",
|
||||
&mock_program_id,
|
||||
mock_transfer_process_instruction,
|
||||
);
|
||||
let recent_blockhash = bank.last_blockhash();
|
||||
|
||||
// Create and drain a small data size account
|
||||
let create_instruction = system_instruction::create_account(
|
||||
&mint_keypair.pubkey(),
|
||||
&created_keypair.pubkey(),
|
||||
lamports_to_transfer,
|
||||
data_size_no_rent,
|
||||
&mock_program_id,
|
||||
);
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(mint_keypair.pubkey(), true),
|
||||
AccountMeta::new(created_keypair.pubkey(), true),
|
||||
AccountMeta::new(mint_keypair.pubkey(), false),
|
||||
];
|
||||
let transfer_from_instruction = Instruction::new_with_bincode(
|
||||
mock_program_id,
|
||||
&MockTransferInstruction::Transfer(lamports_to_transfer),
|
||||
account_metas,
|
||||
);
|
||||
let tx = Transaction::new_signed_with_payer(
|
||||
&[create_instruction, transfer_from_instruction],
|
||||
Some(&mint_keypair.pubkey()),
|
||||
&[&mint_keypair, &created_keypair],
|
||||
recent_blockhash,
|
||||
);
|
||||
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
// account data is not stored because of zero balance even though its
|
||||
// data wasn't cleared
|
||||
assert!(bank.get_account(&created_keypair.pubkey()).is_none());
|
||||
|
||||
// Create and drain a large data size account
|
||||
let create_instruction = system_instruction::create_account(
|
||||
&mint_keypair.pubkey(),
|
||||
&created_keypair.pubkey(),
|
||||
lamports_to_transfer,
|
||||
data_size_rent,
|
||||
&mock_program_id,
|
||||
);
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(mint_keypair.pubkey(), true),
|
||||
AccountMeta::new(created_keypair.pubkey(), true),
|
||||
AccountMeta::new(mint_keypair.pubkey(), false),
|
||||
];
|
||||
let transfer_from_instruction = Instruction::new_with_bincode(
|
||||
mock_program_id,
|
||||
&MockTransferInstruction::Transfer(lamports_to_transfer),
|
||||
account_metas,
|
||||
);
|
||||
let tx = Transaction::new_signed_with_payer(
|
||||
&[create_instruction, transfer_from_instruction],
|
||||
Some(&mint_keypair.pubkey()),
|
||||
&[&mint_keypair, &created_keypair],
|
||||
recent_blockhash,
|
||||
);
|
||||
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
// account data is not stored because of zero balance
|
||||
assert!(bank.get_account(&created_keypair.pubkey()).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rent_state_changes_sysvars() {
|
||||
let GenesisConfigInfo {
|
||||
|
@ -16879,4 +16980,329 @@ pub(crate) mod tests {
|
|||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
enum MockReallocInstruction {
|
||||
Realloc(usize, u64, Pubkey),
|
||||
}
|
||||
|
||||
fn mock_realloc_process_instruction(
|
||||
_first_instruction_account: usize,
|
||||
data: &[u8],
|
||||
invoke_context: &mut InvokeContext,
|
||||
) -> result::Result<(), InstructionError> {
|
||||
let transaction_context = &invoke_context.transaction_context;
|
||||
let instruction_context = transaction_context.get_current_instruction_context()?;
|
||||
if let Ok(instruction) = bincode::deserialize(data) {
|
||||
match instruction {
|
||||
MockReallocInstruction::Realloc(new_size, new_balance, _) => {
|
||||
// Set data length
|
||||
instruction_context
|
||||
.try_borrow_instruction_account(transaction_context, 1)?
|
||||
.set_data_length(new_size);
|
||||
|
||||
// set balance
|
||||
let current_balance = instruction_context
|
||||
.try_borrow_instruction_account(transaction_context, 1)?
|
||||
.get_lamports();
|
||||
let diff_balance = (new_balance as i64).saturating_sub(current_balance as i64);
|
||||
let amount = diff_balance.abs() as u64;
|
||||
if diff_balance.is_positive() {
|
||||
instruction_context
|
||||
.try_borrow_instruction_account(transaction_context, 0)?
|
||||
.checked_sub_lamports(amount)?;
|
||||
instruction_context
|
||||
.try_borrow_instruction_account(transaction_context, 1)?
|
||||
.set_lamports(new_balance);
|
||||
} else {
|
||||
instruction_context
|
||||
.try_borrow_instruction_account(transaction_context, 0)?
|
||||
.checked_add_lamports(amount)?;
|
||||
instruction_context
|
||||
.try_borrow_instruction_account(transaction_context, 1)?
|
||||
.set_lamports(new_balance);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(InstructionError::InvalidInstructionData)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_mock_realloc_tx(
|
||||
payer: &Keypair,
|
||||
funder: &Keypair,
|
||||
reallocd: &Pubkey,
|
||||
new_size: usize,
|
||||
new_balance: u64,
|
||||
mock_program_id: Pubkey,
|
||||
recent_blockhash: Hash,
|
||||
) -> Transaction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(funder.pubkey(), false),
|
||||
AccountMeta::new(*reallocd, false),
|
||||
];
|
||||
let instruction = Instruction::new_with_bincode(
|
||||
mock_program_id,
|
||||
&MockReallocInstruction::Realloc(new_size, new_balance, Pubkey::new_unique()),
|
||||
account_metas,
|
||||
);
|
||||
Transaction::new_signed_with_payer(
|
||||
&[instruction],
|
||||
Some(&payer.pubkey()),
|
||||
&[payer],
|
||||
recent_blockhash,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resize_and_rent() {
|
||||
let GenesisConfigInfo {
|
||||
mut genesis_config,
|
||||
mint_keypair,
|
||||
..
|
||||
} = create_genesis_config_with_leader(1_000_000_000, &Pubkey::new_unique(), 42);
|
||||
genesis_config.rent = Rent::default();
|
||||
activate_all_features(&mut genesis_config);
|
||||
|
||||
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
let mock_program_id = Pubkey::new_unique();
|
||||
bank.add_builtin(
|
||||
"mock_realloc_program",
|
||||
&mock_program_id,
|
||||
mock_realloc_process_instruction,
|
||||
);
|
||||
let recent_blockhash = bank.last_blockhash();
|
||||
|
||||
let account_data_size_small = 1024;
|
||||
let rent_exempt_minimum_small =
|
||||
genesis_config.rent.minimum_balance(account_data_size_small);
|
||||
let account_data_size_large = 2048;
|
||||
let rent_exempt_minimum_large =
|
||||
genesis_config.rent.minimum_balance(account_data_size_large);
|
||||
|
||||
let funding_keypair = Keypair::new();
|
||||
bank.store_account(
|
||||
&funding_keypair.pubkey(),
|
||||
&AccountSharedData::new(1_000_000_000, 0, &mock_program_id),
|
||||
);
|
||||
|
||||
let rent_paying_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let mut rent_paying_account = AccountSharedData::new(
|
||||
rent_exempt_minimum_small - 1,
|
||||
account_data_size_small,
|
||||
&mock_program_id,
|
||||
);
|
||||
rent_paying_account.set_rent_epoch(1);
|
||||
|
||||
// restore program-owned account
|
||||
bank.store_account(&rent_paying_pubkey, &rent_paying_account);
|
||||
|
||||
// rent paying, realloc larger, fail because not rent exempt
|
||||
let tx = create_mock_realloc_tx(
|
||||
&mint_keypair,
|
||||
&funding_keypair,
|
||||
&rent_paying_pubkey,
|
||||
account_data_size_large,
|
||||
rent_exempt_minimum_small - 1,
|
||||
mock_program_id,
|
||||
recent_blockhash,
|
||||
);
|
||||
assert_eq!(
|
||||
bank.process_transaction(&tx).unwrap_err(),
|
||||
TransactionError::InvalidRentPayingAccount,
|
||||
);
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_small - 1,
|
||||
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
|
||||
);
|
||||
|
||||
// rent paying, realloc larger and rent exempt
|
||||
let tx = create_mock_realloc_tx(
|
||||
&mint_keypair,
|
||||
&funding_keypair,
|
||||
&rent_paying_pubkey,
|
||||
account_data_size_large,
|
||||
rent_exempt_minimum_large,
|
||||
mock_program_id,
|
||||
recent_blockhash,
|
||||
);
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_large,
|
||||
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
|
||||
);
|
||||
|
||||
// rent exempt, realloc small, fail because not rent exempt
|
||||
let tx = create_mock_realloc_tx(
|
||||
&mint_keypair,
|
||||
&funding_keypair,
|
||||
&rent_paying_pubkey,
|
||||
account_data_size_small,
|
||||
rent_exempt_minimum_small - 1,
|
||||
mock_program_id,
|
||||
recent_blockhash,
|
||||
);
|
||||
assert_eq!(
|
||||
bank.process_transaction(&tx).unwrap_err(),
|
||||
TransactionError::InvalidRentPayingAccount,
|
||||
);
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_large,
|
||||
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
|
||||
);
|
||||
|
||||
// rent exempt, realloc smaller and rent exempt
|
||||
let tx = create_mock_realloc_tx(
|
||||
&mint_keypair,
|
||||
&funding_keypair,
|
||||
&rent_paying_pubkey,
|
||||
account_data_size_small,
|
||||
rent_exempt_minimum_small,
|
||||
mock_program_id,
|
||||
recent_blockhash,
|
||||
);
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_small,
|
||||
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
|
||||
);
|
||||
|
||||
// rent exempt, realloc large, fail because not rent exempt
|
||||
let tx = create_mock_realloc_tx(
|
||||
&mint_keypair,
|
||||
&funding_keypair,
|
||||
&rent_paying_pubkey,
|
||||
account_data_size_large,
|
||||
rent_exempt_minimum_large - 1,
|
||||
mock_program_id,
|
||||
recent_blockhash,
|
||||
);
|
||||
assert_eq!(
|
||||
bank.process_transaction(&tx).unwrap_err(),
|
||||
TransactionError::InvalidRentPayingAccount,
|
||||
);
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_small,
|
||||
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
|
||||
);
|
||||
|
||||
// rent exempt, realloc large and rent exempt
|
||||
let tx = create_mock_realloc_tx(
|
||||
&mint_keypair,
|
||||
&funding_keypair,
|
||||
&rent_paying_pubkey,
|
||||
account_data_size_large,
|
||||
rent_exempt_minimum_large,
|
||||
mock_program_id,
|
||||
recent_blockhash,
|
||||
);
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_large,
|
||||
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
|
||||
);
|
||||
|
||||
let created_keypair = Keypair::new();
|
||||
|
||||
// create account, not rent exempt
|
||||
let tx = system_transaction::create_account(
|
||||
&mint_keypair,
|
||||
&created_keypair,
|
||||
recent_blockhash,
|
||||
rent_exempt_minimum_small - 1,
|
||||
account_data_size_small as u64,
|
||||
&system_program::id(),
|
||||
);
|
||||
assert_eq!(
|
||||
bank.process_transaction(&tx).unwrap_err(),
|
||||
TransactionError::InvalidRentPayingAccount,
|
||||
);
|
||||
|
||||
// create account, rent exempt
|
||||
let tx = system_transaction::create_account(
|
||||
&mint_keypair,
|
||||
&created_keypair,
|
||||
recent_blockhash,
|
||||
rent_exempt_minimum_small,
|
||||
account_data_size_small as u64,
|
||||
&system_program::id(),
|
||||
);
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_small,
|
||||
bank.get_account(&created_keypair.pubkey())
|
||||
.unwrap()
|
||||
.lamports()
|
||||
);
|
||||
|
||||
let created_keypair = Keypair::new();
|
||||
// create account, no data
|
||||
let tx = system_transaction::create_account(
|
||||
&mint_keypair,
|
||||
&created_keypair,
|
||||
recent_blockhash,
|
||||
rent_exempt_minimum_small - 1,
|
||||
0,
|
||||
&system_program::id(),
|
||||
);
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_small - 1,
|
||||
bank.get_account(&created_keypair.pubkey())
|
||||
.unwrap()
|
||||
.lamports()
|
||||
);
|
||||
|
||||
// alloc but not rent exempt
|
||||
let tx = system_transaction::allocate(
|
||||
&mint_keypair,
|
||||
&created_keypair,
|
||||
recent_blockhash,
|
||||
(account_data_size_small + 1) as u64,
|
||||
);
|
||||
assert_eq!(
|
||||
bank.process_transaction(&tx).unwrap_err(),
|
||||
TransactionError::InvalidRentPayingAccount,
|
||||
);
|
||||
|
||||
// bring balance of account up to rent exemption
|
||||
let tx = system_transaction::transfer(
|
||||
&mint_keypair,
|
||||
&created_keypair.pubkey(),
|
||||
1,
|
||||
recent_blockhash,
|
||||
);
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_small,
|
||||
bank.get_account(&created_keypair.pubkey())
|
||||
.unwrap()
|
||||
.lamports()
|
||||
);
|
||||
|
||||
// allocate as rent exempt
|
||||
let tx = system_transaction::allocate(
|
||||
&mint_keypair,
|
||||
&created_keypair,
|
||||
recent_blockhash,
|
||||
account_data_size_small as u64,
|
||||
);
|
||||
let result = bank.process_transaction(&tx);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
rent_exempt_minimum_small,
|
||||
bank.get_account(&created_keypair.pubkey())
|
||||
.unwrap()
|
||||
.lamports()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,9 @@ impl Bank {
|
|||
let require_rent_exempt_accounts = self
|
||||
.feature_set
|
||||
.is_active(&feature_set::require_rent_exempt_accounts::id());
|
||||
let do_support_realloc = self
|
||||
.feature_set
|
||||
.is_active(&feature_set::do_support_realloc::id());
|
||||
for (i, (pre_state_info, post_state_info)) in
|
||||
pre_state_infos.iter().zip(post_state_infos).enumerate()
|
||||
{
|
||||
|
@ -66,6 +69,7 @@ impl Bank {
|
|||
post_state_info.rent_state.as_ref(),
|
||||
transaction_context,
|
||||
i,
|
||||
do_support_realloc,
|
||||
) {
|
||||
// Feature gate only wraps the actual error return so that the metrics and debug
|
||||
// logging generated by `check_rent_state()` can be examined before feature
|
||||
|
|
|
@ -326,6 +326,21 @@ fn shared_new<T: WritableAccount>(lamports: u64, space: usize, owner: &Pubkey) -
|
|||
)
|
||||
}
|
||||
|
||||
fn shared_new_rent_epoch<T: WritableAccount>(
|
||||
lamports: u64,
|
||||
space: usize,
|
||||
owner: &Pubkey,
|
||||
rent_epoch: Epoch,
|
||||
) -> T {
|
||||
T::create(
|
||||
lamports,
|
||||
vec![0u8; space],
|
||||
*owner,
|
||||
bool::default(),
|
||||
rent_epoch,
|
||||
)
|
||||
}
|
||||
|
||||
fn shared_new_ref<T: WritableAccount>(
|
||||
lamports: u64,
|
||||
space: usize,
|
||||
|
@ -434,6 +449,9 @@ impl Account {
|
|||
) -> Result<RefCell<Self>, bincode::Error> {
|
||||
shared_new_ref_data_with_space(lamports, state, space, owner)
|
||||
}
|
||||
pub fn new_rent_epoch(lamports: u64, space: usize, owner: &Pubkey, rent_epoch: Epoch) -> Self {
|
||||
shared_new_rent_epoch(lamports, space, owner, rent_epoch)
|
||||
}
|
||||
pub fn deserialize_data<T: serde::de::DeserializeOwned>(&self) -> Result<T, bincode::Error> {
|
||||
shared_deserialize_data(self)
|
||||
}
|
||||
|
@ -490,6 +508,9 @@ impl AccountSharedData {
|
|||
) -> Result<RefCell<Self>, bincode::Error> {
|
||||
shared_new_ref_data_with_space(lamports, state, space, owner)
|
||||
}
|
||||
pub fn new_rent_epoch(lamports: u64, space: usize, owner: &Pubkey, rent_epoch: Epoch) -> Self {
|
||||
shared_new_rent_epoch(lamports, space, owner, rent_epoch)
|
||||
}
|
||||
pub fn deserialize_data<T: serde::de::DeserializeOwned>(&self) -> Result<T, bincode::Error> {
|
||||
shared_deserialize_data(self)
|
||||
}
|
||||
|
|
|
@ -27,6 +27,20 @@ pub fn create_account(
|
|||
Transaction::new(&[from_keypair, to_keypair], message, recent_blockhash)
|
||||
}
|
||||
|
||||
/// Create and sign new SystemInstruction::Allocate transaction
|
||||
pub fn allocate(
|
||||
payer_keypair: &Keypair,
|
||||
account_keypair: &Keypair,
|
||||
recent_blockhash: Hash,
|
||||
space: u64,
|
||||
) -> Transaction {
|
||||
let payer_pubkey = payer_keypair.pubkey();
|
||||
let account_pubkey = account_keypair.pubkey();
|
||||
let instruction = system_instruction::allocate(&account_pubkey, space);
|
||||
let message = Message::new(&[instruction], Some(&payer_pubkey));
|
||||
Transaction::new(&[payer_keypair, account_keypair], message, recent_blockhash)
|
||||
}
|
||||
|
||||
/// Create and sign new system_instruction::Assign transaction
|
||||
pub fn assign(from_keypair: &Keypair, recent_blockhash: Hash, program_id: &Pubkey) -> Transaction {
|
||||
let from_pubkey = from_keypair.pubkey();
|
||||
|
|
Loading…
Reference in New Issue