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