1use {
2 solana_account::ReadableAccount,
3 solana_sdk_ids::native_loader,
4 solana_svm_rent_collector::{rent_state::RentState, svm_rent_collector::SVMRentCollector},
5 solana_svm_transaction::svm_message::SVMMessage,
6 solana_transaction_context::{IndexOfAccount, TransactionContext},
7 solana_transaction_error::TransactionResult as Result,
8};
9
10#[derive(PartialEq, Debug)]
11pub(crate) struct TransactionAccountStateInfo {
12 rent_state: Option<RentState>, }
14
15impl TransactionAccountStateInfo {
16 pub(crate) fn new(
17 transaction_context: &TransactionContext,
18 message: &impl SVMMessage,
19 rent_collector: &dyn SVMRentCollector,
20 ) -> Vec<Self> {
21 (0..message.account_keys().len())
22 .map(|i| {
23 let rent_state = if message.is_writable(i) {
24 let state = if let Ok(account) =
25 transaction_context.get_account_at_index(i as IndexOfAccount)
26 {
27 let account = account.borrow();
28
29 debug_assert!(!native_loader::check_id(account.owner()));
32
33 Some(rent_collector.get_account_rent_state(&account))
34 } else {
35 None
36 };
37 debug_assert!(
38 state.is_some(),
39 "message and transaction context out of sync, fatal"
40 );
41 state
42 } else {
43 None
44 };
45 Self { rent_state }
46 })
47 .collect()
48 }
49
50 pub(crate) fn verify_changes(
51 pre_state_infos: &[Self],
52 post_state_infos: &[Self],
53 transaction_context: &TransactionContext,
54 rent_collector: &dyn SVMRentCollector,
55 ) -> Result<()> {
56 for (i, (pre_state_info, post_state_info)) in
57 pre_state_infos.iter().zip(post_state_infos).enumerate()
58 {
59 rent_collector.check_rent_state(
60 pre_state_info.rent_state.as_ref(),
61 post_state_info.rent_state.as_ref(),
62 transaction_context,
63 i as IndexOfAccount,
64 )?;
65 }
66 Ok(())
67 }
68}
69
70#[cfg(test)]
71mod test {
72 use {
73 super::*,
74 solana_account::AccountSharedData,
75 solana_hash::Hash,
76 solana_keypair::Keypair,
77 solana_message::{
78 compiled_instruction::CompiledInstruction, LegacyMessage, Message, MessageHeader,
79 SanitizedMessage,
80 },
81 solana_rent::Rent,
82 solana_reserved_account_keys::ReservedAccountKeys,
83 solana_sdk::rent_collector::RentCollector,
84 solana_signer::Signer,
85 solana_transaction_context::TransactionContext,
86 solana_transaction_error::TransactionError,
87 };
88
89 #[test]
90 fn test_new() {
91 let rent_collector = RentCollector::default();
92 let key1 = Keypair::new();
93 let key2 = Keypair::new();
94 let key3 = Keypair::new();
95 let key4 = Keypair::new();
96
97 let message = Message {
98 account_keys: vec![key2.pubkey(), key1.pubkey(), key4.pubkey()],
99 header: MessageHeader::default(),
100 instructions: vec![
101 CompiledInstruction {
102 program_id_index: 1,
103 accounts: vec![0],
104 data: vec![],
105 },
106 CompiledInstruction {
107 program_id_index: 1,
108 accounts: vec![2],
109 data: vec![],
110 },
111 ],
112 recent_blockhash: Hash::default(),
113 };
114
115 let sanitized_message = SanitizedMessage::Legacy(LegacyMessage::new(
116 message,
117 &ReservedAccountKeys::empty_key_set(),
118 ));
119
120 let transaction_accounts = vec![
121 (key1.pubkey(), AccountSharedData::default()),
122 (key2.pubkey(), AccountSharedData::default()),
123 (key3.pubkey(), AccountSharedData::default()),
124 ];
125
126 let context = TransactionContext::new(
127 transaction_accounts,
128 rent_collector.get_rent().clone(),
129 20,
130 20,
131 );
132 let result =
133 TransactionAccountStateInfo::new(&context, &sanitized_message, &rent_collector);
134 assert_eq!(
135 result,
136 vec![
137 TransactionAccountStateInfo {
138 rent_state: Some(RentState::Uninitialized)
139 },
140 TransactionAccountStateInfo { rent_state: None },
141 TransactionAccountStateInfo {
142 rent_state: Some(RentState::Uninitialized)
143 }
144 ]
145 );
146 }
147
148 #[test]
149 #[should_panic(expected = "message and transaction context out of sync, fatal")]
150 fn test_new_panic() {
151 let rent_collector = RentCollector::default();
152 let key1 = Keypair::new();
153 let key2 = Keypair::new();
154 let key3 = Keypair::new();
155 let key4 = Keypair::new();
156
157 let message = Message {
158 account_keys: vec![key2.pubkey(), key1.pubkey(), key4.pubkey(), key3.pubkey()],
159 header: MessageHeader::default(),
160 instructions: vec![
161 CompiledInstruction {
162 program_id_index: 1,
163 accounts: vec![0],
164 data: vec![],
165 },
166 CompiledInstruction {
167 program_id_index: 1,
168 accounts: vec![2],
169 data: vec![],
170 },
171 ],
172 recent_blockhash: Hash::default(),
173 };
174
175 let sanitized_message = SanitizedMessage::Legacy(LegacyMessage::new(
176 message,
177 &ReservedAccountKeys::empty_key_set(),
178 ));
179
180 let transaction_accounts = vec![
181 (key1.pubkey(), AccountSharedData::default()),
182 (key2.pubkey(), AccountSharedData::default()),
183 (key3.pubkey(), AccountSharedData::default()),
184 ];
185
186 let context = TransactionContext::new(
187 transaction_accounts,
188 rent_collector.get_rent().clone(),
189 20,
190 20,
191 );
192 let _result =
193 TransactionAccountStateInfo::new(&context, &sanitized_message, &rent_collector);
194 }
195
196 #[test]
197 fn test_verify_changes() {
198 let rent_collector = RentCollector::default();
199 let key1 = Keypair::new();
200 let key2 = Keypair::new();
201 let pre_rent_state = vec![
202 TransactionAccountStateInfo {
203 rent_state: Some(RentState::Uninitialized),
204 },
205 TransactionAccountStateInfo {
206 rent_state: Some(RentState::Uninitialized),
207 },
208 ];
209 let post_rent_state = vec![TransactionAccountStateInfo {
210 rent_state: Some(RentState::Uninitialized),
211 }];
212
213 let transaction_accounts = vec![
214 (key1.pubkey(), AccountSharedData::default()),
215 (key2.pubkey(), AccountSharedData::default()),
216 ];
217
218 let context = TransactionContext::new(transaction_accounts, Rent::default(), 20, 20);
219
220 let result = TransactionAccountStateInfo::verify_changes(
221 &pre_rent_state,
222 &post_rent_state,
223 &context,
224 &rent_collector,
225 );
226 assert!(result.is_ok());
227
228 let pre_rent_state = vec![TransactionAccountStateInfo {
229 rent_state: Some(RentState::Uninitialized),
230 }];
231 let post_rent_state = vec![TransactionAccountStateInfo {
232 rent_state: Some(RentState::RentPaying {
233 data_size: 2,
234 lamports: 5,
235 }),
236 }];
237
238 let transaction_accounts = vec![
239 (key1.pubkey(), AccountSharedData::default()),
240 (key2.pubkey(), AccountSharedData::default()),
241 ];
242
243 let context = TransactionContext::new(transaction_accounts, Rent::default(), 20, 20);
244 let result = TransactionAccountStateInfo::verify_changes(
245 &pre_rent_state,
246 &post_rent_state,
247 &context,
248 &rent_collector,
249 );
250 assert_eq!(
251 result.err(),
252 Some(TransactionError::InsufficientFundsForRent { account_index: 0 })
253 );
254 }
255}