solana_svm/
transaction_account_state_info.rs

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