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