use {
crate::rent_calculator::{RentState, check_rent_state, get_account_rent_state},
solana_account::ReadableAccount,
solana_rent::Rent,
solana_svm_transaction::svm_message::SVMMessage,
solana_transaction_context::{IndexOfAccount, transaction::TransactionContext},
solana_transaction_error::TransactionResult as Result,
};
#[derive(PartialEq, Debug)]
pub(crate) struct TransactionAccountStateInfo {
info: Option<WritableTransactionAccountStateInfo>, }
impl TransactionAccountStateInfo {
pub(crate) fn new(
transaction_context: &TransactionContext,
message: &impl SVMMessage,
rent: &Rent,
) -> Vec<Self> {
(0..message.account_keys().len())
.map(|i| {
let info = if message.is_writable(i) {
let state = if let Ok(account) = transaction_context
.accounts()
.try_borrow(i as IndexOfAccount)
{
let balance = account.lamports();
let data_size = account.data().len();
let rent_state = get_account_rent_state(rent, balance, data_size);
Some(WritableTransactionAccountStateInfo {
rent_state,
data_size,
})
} else {
None
};
debug_assert!(
state.is_some(),
"message and transaction context out of sync, fatal"
);
state
} else {
None
};
Self { info }
})
.collect()
}
pub(crate) fn verify_changes(
pre_state_infos: &[Self],
post_state_infos: &[Self],
transaction_context: &TransactionContext,
) -> Result<()> {
for (i, (pre_state_info, post_state_info)) in
pre_state_infos.iter().zip(post_state_infos).enumerate()
{
if let (Some(pre_state_info), Some(post_state_info)) =
(pre_state_info.info.as_ref(), post_state_info.info.as_ref())
{
check_rent_state(
&pre_state_info.rent_state,
&post_state_info.rent_state,
transaction_context,
i as IndexOfAccount,
)?;
}
}
Ok(())
}
}
#[derive(PartialEq, Debug)]
struct WritableTransactionAccountStateInfo {
rent_state: RentState,
data_size: usize,
}
pub(crate) fn get_uninitialized_accounts_size(post: &[TransactionAccountStateInfo]) -> u64 {
post.iter()
.filter_map(|post_info| post_info.info.as_ref())
.filter_map(|post| {
matches!(&post.rent_state, RentState::Uninitialized).then_some(post.data_size as u64)
})
.sum()
}
#[cfg(test)]
mod test {
use {
super::*,
solana_account::AccountSharedData,
solana_hash::Hash,
solana_keypair::Keypair,
solana_message::{
LegacyMessage, Message, MessageHeader, SanitizedMessage,
compiled_instruction::CompiledInstruction,
},
solana_rent::Rent,
solana_signer::Signer,
solana_transaction_context::transaction::TransactionContext,
solana_transaction_error::TransactionError,
std::collections::HashSet,
};
#[test]
fn test_new() {
let rent = Rent::default();
let key1 = Keypair::new();
let key2 = Keypair::new();
let key3 = Keypair::new();
let key4 = Keypair::new();
let message = Message {
account_keys: vec![key2.pubkey(), key1.pubkey(), key4.pubkey()],
header: MessageHeader::default(),
instructions: vec![
CompiledInstruction {
program_id_index: 1,
accounts: vec![0],
data: vec![],
},
CompiledInstruction {
program_id_index: 1,
accounts: vec![2],
data: vec![],
},
],
recent_blockhash: Hash::default(),
};
let sanitized_message =
SanitizedMessage::Legacy(LegacyMessage::new(message, &HashSet::new()));
let transaction_accounts = vec![
(key1.pubkey(), AccountSharedData::default()),
(key2.pubkey(), AccountSharedData::default()),
(key3.pubkey(), AccountSharedData::default()),
];
let context = TransactionContext::new(transaction_accounts, rent.clone(), 20, 20, 1);
let result = TransactionAccountStateInfo::new(&context, &sanitized_message, &rent);
assert_eq!(
result,
vec![
TransactionAccountStateInfo {
info: Some(WritableTransactionAccountStateInfo {
rent_state: RentState::Uninitialized,
data_size: 0,
})
},
TransactionAccountStateInfo { info: None },
TransactionAccountStateInfo {
info: Some(WritableTransactionAccountStateInfo {
rent_state: RentState::Uninitialized,
data_size: 0,
})
}
]
);
}
#[test]
#[should_panic(expected = "message and transaction context out of sync, fatal")]
fn test_new_panic() {
let rent = Rent::default();
let key1 = Keypair::new();
let key2 = Keypair::new();
let key3 = Keypair::new();
let key4 = Keypair::new();
let message = Message {
account_keys: vec![key2.pubkey(), key1.pubkey(), key4.pubkey(), key3.pubkey()],
header: MessageHeader::default(),
instructions: vec![
CompiledInstruction {
program_id_index: 1,
accounts: vec![0],
data: vec![],
},
CompiledInstruction {
program_id_index: 1,
accounts: vec![2],
data: vec![],
},
],
recent_blockhash: Hash::default(),
};
let sanitized_message =
SanitizedMessage::Legacy(LegacyMessage::new(message, &HashSet::new()));
let transaction_accounts = vec![
(key1.pubkey(), AccountSharedData::default()),
(key2.pubkey(), AccountSharedData::default()),
(key3.pubkey(), AccountSharedData::default()),
];
let context = TransactionContext::new(transaction_accounts, rent.clone(), 20, 20, 1);
let _result = TransactionAccountStateInfo::new(&context, &sanitized_message, &rent);
}
#[test]
fn test_verify_changes() {
let key1 = Keypair::new();
let key2 = Keypair::new();
let pre_rent_state = vec![
TransactionAccountStateInfo {
info: Some(WritableTransactionAccountStateInfo {
rent_state: RentState::Uninitialized,
data_size: 0,
}),
},
TransactionAccountStateInfo {
info: Some(WritableTransactionAccountStateInfo {
rent_state: RentState::Uninitialized,
data_size: 0,
}),
},
];
let post_rent_state = vec![TransactionAccountStateInfo {
info: Some(WritableTransactionAccountStateInfo {
rent_state: RentState::Uninitialized,
data_size: 0,
}),
}];
let transaction_accounts = vec![
(key1.pubkey(), AccountSharedData::default()),
(key2.pubkey(), AccountSharedData::default()),
];
let context = TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 1);
let result = TransactionAccountStateInfo::verify_changes(
&pre_rent_state,
&post_rent_state,
&context,
);
assert!(result.is_ok());
let pre_rent_state = vec![TransactionAccountStateInfo {
info: Some(WritableTransactionAccountStateInfo {
rent_state: RentState::Uninitialized,
data_size: 0,
}),
}];
let post_rent_state = vec![TransactionAccountStateInfo {
info: Some(WritableTransactionAccountStateInfo {
rent_state: RentState::RentPaying {
data_size: 2,
lamports: 5,
},
data_size: 2,
}),
}];
let transaction_accounts = vec![
(key1.pubkey(), AccountSharedData::default()),
(key2.pubkey(), AccountSharedData::default()),
];
let context = TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 1);
let result = TransactionAccountStateInfo::verify_changes(
&pre_rent_state,
&post_rent_state,
&context,
);
assert_eq!(
result.err(),
Some(TransactionError::InsufficientFundsForRent { account_index: 0 })
);
}
#[test]
fn test_get_uninitialized_accounts_size_with_deleted_accounts() {
let post_state_infos = vec![
TransactionAccountStateInfo {
info: Some(WritableTransactionAccountStateInfo {
rent_state: RentState::Uninitialized,
data_size: 50,
}),
},
TransactionAccountStateInfo {
info: Some(WritableTransactionAccountStateInfo {
rent_state: RentState::Uninitialized,
data_size: 50,
}),
},
TransactionAccountStateInfo {
info: Some(WritableTransactionAccountStateInfo {
rent_state: RentState::Uninitialized,
data_size: 50,
}),
},
TransactionAccountStateInfo {
info: Some(WritableTransactionAccountStateInfo {
rent_state: RentState::RentExempt,
data_size: 50,
}),
},
];
assert_eq!(get_uninitialized_accounts_size(&post_state_infos), 150);
}
}