use {
crate::rent_calculator::{check_rent_state, get_account_rent_state, RentState},
solana_account::ReadableAccount,
solana_rent::Rent,
solana_svm_transaction::svm_message::SVMMessage,
solana_transaction_context::{IndexOfAccount, TransactionContext},
solana_transaction_error::TransactionResult as Result,
};
#[derive(PartialEq, Debug)]
pub(crate) struct TransactionAccountStateInfo {
rent_state: Option<RentState>, }
impl TransactionAccountStateInfo {
pub(crate) fn new(
transaction_context: &TransactionContext,
message: &impl SVMMessage,
rent: &Rent,
) -> Vec<Self> {
(0..message.account_keys().len())
.map(|i| {
let rent_state = if message.is_writable(i) {
let state = if let Ok(account) = transaction_context
.accounts()
.try_borrow(i as IndexOfAccount)
{
Some(get_account_rent_state(
rent,
account.lamports(),
account.data().len(),
))
} else {
None
};
debug_assert!(
state.is_some(),
"message and transaction context out of sync, fatal"
);
state
} else {
None
};
Self { rent_state }
})
.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()
{
check_rent_state(
pre_state_info.rent_state.as_ref(),
post_state_info.rent_state.as_ref(),
transaction_context,
i as IndexOfAccount,
)?;
}
Ok(())
}
}
#[cfg(test)]
mod test {
use {
super::*,
solana_account::AccountSharedData,
solana_hash::Hash,
solana_keypair::Keypair,
solana_message::{
compiled_instruction::CompiledInstruction, LegacyMessage, Message, MessageHeader,
SanitizedMessage,
},
solana_rent::Rent,
solana_signer::Signer,
solana_transaction_context::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);
let result = TransactionAccountStateInfo::new(&context, &sanitized_message, &rent);
assert_eq!(
result,
vec![
TransactionAccountStateInfo {
rent_state: Some(RentState::Uninitialized)
},
TransactionAccountStateInfo { rent_state: None },
TransactionAccountStateInfo {
rent_state: Some(RentState::Uninitialized)
}
]
);
}
#[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);
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 {
rent_state: Some(RentState::Uninitialized),
},
TransactionAccountStateInfo {
rent_state: Some(RentState::Uninitialized),
},
];
let post_rent_state = vec![TransactionAccountStateInfo {
rent_state: Some(RentState::Uninitialized),
}];
let transaction_accounts = vec![
(key1.pubkey(), AccountSharedData::default()),
(key2.pubkey(), AccountSharedData::default()),
];
let context = TransactionContext::new(transaction_accounts, Rent::default(), 20, 20);
let result = TransactionAccountStateInfo::verify_changes(
&pre_rent_state,
&post_rent_state,
&context,
);
assert!(result.is_ok());
let pre_rent_state = vec![TransactionAccountStateInfo {
rent_state: Some(RentState::Uninitialized),
}];
let post_rent_state = vec![TransactionAccountStateInfo {
rent_state: Some(RentState::RentPaying {
data_size: 2,
lamports: 5,
}),
}];
let transaction_accounts = vec![
(key1.pubkey(), AccountSharedData::default()),
(key2.pubkey(), AccountSharedData::default()),
];
let context = TransactionContext::new(transaction_accounts, Rent::default(), 20, 20);
let result = TransactionAccountStateInfo::verify_changes(
&pre_rent_state,
&post_rent_state,
&context,
);
assert_eq!(
result.err(),
Some(TransactionError::InsufficientFundsForRent { account_index: 0 })
);
}
}