solana_svm/
transaction_account_state_info.rs

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>, // None: readonly account
13}
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                        // Native programs appear to be RentPaying because they carry low lamport
30                        // balances; however they will never be loaded as writable
31                        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}