1use {
2 crate::nonce_info::NonceInfo,
3 solana_sdk::{
4 account::{AccountSharedData, ReadableAccount, WritableAccount},
5 clock::Epoch,
6 pubkey::Pubkey,
7 },
8};
9
10#[derive(PartialEq, Eq, Debug, Clone)]
13pub enum RollbackAccounts {
14 FeePayerOnly {
15 fee_payer_account: AccountSharedData,
16 },
17 SameNonceAndFeePayer {
18 nonce: NonceInfo,
19 },
20 SeparateNonceAndFeePayer {
21 nonce: NonceInfo,
22 fee_payer_account: AccountSharedData,
23 },
24}
25
26#[cfg(feature = "dev-context-only-utils")]
27impl Default for RollbackAccounts {
28 fn default() -> Self {
29 Self::FeePayerOnly {
30 fee_payer_account: AccountSharedData::default(),
31 }
32 }
33}
34
35impl RollbackAccounts {
36 pub fn new(
37 nonce: Option<NonceInfo>,
38 fee_payer_address: Pubkey,
39 mut fee_payer_account: AccountSharedData,
40 fee_payer_rent_debit: u64,
41 fee_payer_loaded_rent_epoch: Epoch,
42 ) -> Self {
43 fee_payer_account.set_lamports(
47 fee_payer_account
48 .lamports()
49 .saturating_add(fee_payer_rent_debit),
50 );
51
52 if let Some(nonce) = nonce {
53 if &fee_payer_address == nonce.address() {
54 fee_payer_account.set_data_from_slice(nonce.account().data());
59
60 RollbackAccounts::SameNonceAndFeePayer {
61 nonce: NonceInfo::new(fee_payer_address, fee_payer_account),
62 }
63 } else {
64 RollbackAccounts::SeparateNonceAndFeePayer {
65 nonce,
66 fee_payer_account,
67 }
68 }
69 } else {
70 fee_payer_account.set_rent_epoch(fee_payer_loaded_rent_epoch);
77 RollbackAccounts::FeePayerOnly { fee_payer_account }
78 }
79 }
80
81 pub fn count(&self) -> usize {
83 match self {
84 Self::FeePayerOnly { .. } | Self::SameNonceAndFeePayer { .. } => 1,
85 Self::SeparateNonceAndFeePayer { .. } => 2,
86 }
87 }
88
89 pub fn data_size(&self) -> usize {
92 match self {
93 Self::FeePayerOnly { fee_payer_account } => fee_payer_account.data().len(),
94 Self::SameNonceAndFeePayer { nonce } => nonce.account().data().len(),
95 Self::SeparateNonceAndFeePayer {
96 nonce,
97 fee_payer_account,
98 } => fee_payer_account
99 .data()
100 .len()
101 .saturating_add(nonce.account().data().len()),
102 }
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use {
109 super::*,
110 solana_sdk::{
111 account::{ReadableAccount, WritableAccount},
112 hash::Hash,
113 nonce::state::{
114 Data as NonceData, DurableNonce, State as NonceState, Versions as NonceVersions,
115 },
116 system_program,
117 },
118 };
119
120 #[test]
121 fn test_new_fee_payer_only() {
122 let fee_payer_address = Pubkey::new_unique();
123 let fee_payer_account = AccountSharedData::new(100, 0, &Pubkey::default());
124 let fee_payer_rent_epoch = fee_payer_account.rent_epoch();
125
126 const TEST_RENT_DEBIT: u64 = 1;
127 let rent_collected_fee_payer_account = {
128 let mut account = fee_payer_account.clone();
129 account.set_lamports(fee_payer_account.lamports() - TEST_RENT_DEBIT);
130 account.set_rent_epoch(fee_payer_rent_epoch + 1);
131 account
132 };
133
134 let rollback_accounts = RollbackAccounts::new(
135 None,
136 fee_payer_address,
137 rent_collected_fee_payer_account,
138 TEST_RENT_DEBIT,
139 fee_payer_rent_epoch,
140 );
141
142 let expected_fee_payer_account = fee_payer_account;
143 match rollback_accounts {
144 RollbackAccounts::FeePayerOnly { fee_payer_account } => {
145 assert_eq!(expected_fee_payer_account, fee_payer_account);
146 }
147 _ => panic!("Expected FeePayerOnly variant"),
148 }
149 }
150
151 #[test]
152 fn test_new_same_nonce_and_fee_payer() {
153 let nonce_address = Pubkey::new_unique();
154 let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
155 let lamports_per_signature = 42;
156 let nonce_account = AccountSharedData::new_data(
157 43,
158 &NonceVersions::new(NonceState::Initialized(NonceData::new(
159 Pubkey::default(),
160 durable_nonce,
161 lamports_per_signature,
162 ))),
163 &system_program::id(),
164 )
165 .unwrap();
166
167 const TEST_RENT_DEBIT: u64 = 1;
168 let rent_collected_nonce_account = {
169 let mut account = nonce_account.clone();
170 account.set_lamports(nonce_account.lamports() - TEST_RENT_DEBIT);
171 account
172 };
173
174 let nonce = NonceInfo::new(nonce_address, rent_collected_nonce_account.clone());
175 let rollback_accounts = RollbackAccounts::new(
176 Some(nonce),
177 nonce_address,
178 rent_collected_nonce_account,
179 TEST_RENT_DEBIT,
180 u64::MAX, );
182
183 match rollback_accounts {
184 RollbackAccounts::SameNonceAndFeePayer { nonce } => {
185 assert_eq!(nonce.address(), &nonce_address);
186 assert_eq!(nonce.account(), &nonce_account);
187 }
188 _ => panic!("Expected SameNonceAndFeePayer variant"),
189 }
190 }
191
192 #[test]
193 fn test_separate_nonce_and_fee_payer() {
194 let nonce_address = Pubkey::new_unique();
195 let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
196 let lamports_per_signature = 42;
197 let nonce_account = AccountSharedData::new_data(
198 43,
199 &NonceVersions::new(NonceState::Initialized(NonceData::new(
200 Pubkey::default(),
201 durable_nonce,
202 lamports_per_signature,
203 ))),
204 &system_program::id(),
205 )
206 .unwrap();
207
208 let fee_payer_address = Pubkey::new_unique();
209 let fee_payer_account = AccountSharedData::new(44, 0, &Pubkey::default());
210
211 const TEST_RENT_DEBIT: u64 = 1;
212 let rent_collected_fee_payer_account = {
213 let mut account = fee_payer_account.clone();
214 account.set_lamports(fee_payer_account.lamports() - TEST_RENT_DEBIT);
215 account
216 };
217
218 let nonce = NonceInfo::new(nonce_address, nonce_account.clone());
219 let rollback_accounts = RollbackAccounts::new(
220 Some(nonce),
221 fee_payer_address,
222 rent_collected_fee_payer_account.clone(),
223 TEST_RENT_DEBIT,
224 u64::MAX, );
226
227 let expected_fee_payer_account = fee_payer_account;
228 match rollback_accounts {
229 RollbackAccounts::SeparateNonceAndFeePayer {
230 nonce,
231 fee_payer_account,
232 } => {
233 assert_eq!(nonce.address(), &nonce_address);
234 assert_eq!(nonce.account(), &nonce_account);
235 assert_eq!(expected_fee_payer_account, fee_payer_account);
236 }
237 _ => panic!("Expected SeparateNonceAndFeePayer variant"),
238 }
239 }
240}