1use {
2 crate::timings::ExecuteDetailsTimings,
3 cbe_sdk::{
4 account::{AccountSharedData, ReadableAccount, WritableAccount},
5 instruction::InstructionError,
6 pubkey::Pubkey,
7 rent::Rent,
8 system_instruction::MAX_PERMITTED_DATA_LENGTH,
9 },
10 std::fmt::Debug,
11};
12
13#[derive(Clone, Debug, Default)]
16pub struct PreAccount {
17 key: Pubkey,
18 account: AccountSharedData,
19 changed: bool,
20}
21impl PreAccount {
22 pub fn new(key: &Pubkey, account: AccountSharedData) -> Self {
23 Self {
24 key: *key,
25 account,
26 changed: false,
27 }
28 }
29
30 pub fn verify(
31 &self,
32 program_id: &Pubkey,
33 is_writable: bool,
34 rent: &Rent,
35 post: &AccountSharedData,
36 timings: &mut ExecuteDetailsTimings,
37 outermost_call: bool,
38 ) -> Result<(), InstructionError> {
39 let pre = &self.account;
40
41 let owner_changed = pre.owner() != post.owner();
46 if owner_changed
47 && (!is_writable || pre.executable()
49 || program_id != pre.owner()
50 || !Self::is_zeroed(post.data()))
51 {
52 return Err(InstructionError::ModifiedProgramId);
53 }
54
55 if program_id != pre.owner() && pre.scoobies() > post.scoobies()
58 {
59 return Err(InstructionError::ExternalAccountScoobieSpend);
60 }
61
62 let scoobies_changed = pre.scoobies() != post.scoobies();
64 if scoobies_changed {
65 if !is_writable {
66 return Err(InstructionError::ReadonlyScoobieChange);
67 }
68 if pre.executable() {
69 return Err(InstructionError::ExecutableScoobieChange);
70 }
71 }
72
73 if post.data().len() > MAX_PERMITTED_DATA_LENGTH as usize {
75 return Err(InstructionError::InvalidRealloc);
76 }
77
78 let data_len_changed = pre.data().len() != post.data().len();
80 if data_len_changed && program_id != pre.owner() {
81 return Err(InstructionError::AccountDataSizeChanged);
82 }
83
84 if !(program_id == pre.owner()
88 && is_writable && !pre.executable())
90 && pre.data() != post.data()
91 {
92 if pre.executable() {
93 return Err(InstructionError::ExecutableDataModified);
94 } else if is_writable {
95 return Err(InstructionError::ExternalAccountDataModified);
96 } else {
97 return Err(InstructionError::ReadonlyDataModified);
98 }
99 }
100
101 let executable_changed = pre.executable() != post.executable();
103 if executable_changed {
104 if !rent.is_exempt(post.scoobies(), post.data().len()) {
105 return Err(InstructionError::ExecutableAccountNotRentExempt);
106 }
107 if !is_writable || pre.executable()
109 || program_id != post.owner()
110 {
111 return Err(InstructionError::ExecutableModified);
112 }
113 }
114
115 let rent_epoch_changed = pre.rent_epoch() != post.rent_epoch();
117 if rent_epoch_changed {
118 return Err(InstructionError::RentEpochModified);
119 }
120
121 if outermost_call {
122 timings.total_account_count = timings.total_account_count.saturating_add(1);
123 if owner_changed
124 || scoobies_changed
125 || data_len_changed
126 || executable_changed
127 || rent_epoch_changed
128 || self.changed
129 {
130 timings.changed_account_count = timings.changed_account_count.saturating_add(1);
131 }
132 }
133
134 Ok(())
135 }
136
137 pub fn update(&mut self, account: AccountSharedData) {
138 let rent_epoch = self.account.rent_epoch();
139 self.account = account;
140 self.account.set_rent_epoch(rent_epoch);
141
142 self.changed = true;
143 }
144
145 pub fn key(&self) -> &Pubkey {
146 &self.key
147 }
148
149 pub fn data(&self) -> &[u8] {
150 self.account.data()
151 }
152
153 pub fn scoobies(&self) -> u64 {
154 self.account.scoobies()
155 }
156
157 pub fn executable(&self) -> bool {
158 self.account.executable()
159 }
160
161 pub fn is_zeroed(buf: &[u8]) -> bool {
162 const ZEROS_LEN: usize = 1024;
163 static ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN];
164 let mut chunks = buf.chunks_exact(ZEROS_LEN);
165
166 #[allow(clippy::indexing_slicing)]
167 {
168 chunks.all(|chunk| chunk == &ZEROS[..])
169 && chunks.remainder() == &ZEROS[..chunks.remainder().len()]
170 }
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use {
177 super::*,
178 cbe_sdk::{account::Account, instruction::InstructionError, system_program},
179 };
180
181 #[test]
182 fn test_is_zeroed() {
183 const ZEROS_LEN: usize = 1024;
184 let mut buf = [0; ZEROS_LEN];
185 assert!(PreAccount::is_zeroed(&buf));
186 buf[0] = 1;
187 assert!(!PreAccount::is_zeroed(&buf));
188
189 let mut buf = [0; ZEROS_LEN - 1];
190 assert!(PreAccount::is_zeroed(&buf));
191 buf[0] = 1;
192 assert!(!PreAccount::is_zeroed(&buf));
193
194 let mut buf = [0; ZEROS_LEN + 1];
195 assert!(PreAccount::is_zeroed(&buf));
196 buf[0] = 1;
197 assert!(!PreAccount::is_zeroed(&buf));
198
199 let buf = vec![];
200 assert!(PreAccount::is_zeroed(&buf));
201 }
202
203 struct Change {
204 program_id: Pubkey,
205 is_writable: bool,
206 rent: Rent,
207 pre: PreAccount,
208 post: AccountSharedData,
209 }
210 impl Change {
211 pub fn new(owner: &Pubkey, program_id: &Pubkey) -> Self {
212 Self {
213 program_id: *program_id,
214 rent: Rent::default(),
215 is_writable: true,
216 pre: PreAccount::new(
217 &cbe_sdk::pubkey::new_rand(),
218 AccountSharedData::from(Account {
219 owner: *owner,
220 scoobies: std::u64::MAX,
221 ..Account::default()
222 }),
223 ),
224 post: AccountSharedData::from(Account {
225 owner: *owner,
226 scoobies: std::u64::MAX,
227 ..Account::default()
228 }),
229 }
230 }
231 pub fn read_only(mut self) -> Self {
232 self.is_writable = false;
233 self
234 }
235 pub fn executable(mut self, pre: bool, post: bool) -> Self {
236 self.pre.account.set_executable(pre);
237 self.post.set_executable(post);
238 self
239 }
240 pub fn scoobies(mut self, pre: u64, post: u64) -> Self {
241 self.pre.account.set_scoobies(pre);
242 self.post.set_scoobies(post);
243 self
244 }
245 pub fn owner(mut self, post: &Pubkey) -> Self {
246 self.post.set_owner(*post);
247 self
248 }
249 pub fn data(mut self, pre: Vec<u8>, post: Vec<u8>) -> Self {
250 self.pre.account.set_data(pre);
251 self.post.set_data(post);
252 self
253 }
254 pub fn rent_epoch(mut self, pre: u64, post: u64) -> Self {
255 self.pre.account.set_rent_epoch(pre);
256 self.post.set_rent_epoch(post);
257 self
258 }
259 pub fn verify(&self) -> Result<(), InstructionError> {
260 self.pre.verify(
261 &self.program_id,
262 self.is_writable,
263 &self.rent,
264 &self.post,
265 &mut ExecuteDetailsTimings::default(),
266 false,
267 )
268 }
269 }
270
271 #[test]
272 fn test_verify_account_changes_owner() {
273 let system_program_id = system_program::id();
274 let alice_program_id = cbe_sdk::pubkey::new_rand();
275 let mallory_program_id = cbe_sdk::pubkey::new_rand();
276
277 assert_eq!(
278 Change::new(&system_program_id, &system_program_id)
279 .owner(&alice_program_id)
280 .verify(),
281 Ok(()),
282 "system program should be able to change the account owner"
283 );
284 assert_eq!(
285 Change::new(&system_program_id, &system_program_id)
286 .owner(&alice_program_id)
287 .read_only()
288 .verify(),
289 Err(InstructionError::ModifiedProgramId),
290 "system program should not be able to change the account owner of a read-only account"
291 );
292 assert_eq!(
293 Change::new(&mallory_program_id, &system_program_id)
294 .owner(&alice_program_id)
295 .verify(),
296 Err(InstructionError::ModifiedProgramId),
297 "system program should not be able to change the account owner of a non-system account"
298 );
299 assert_eq!(
300 Change::new(&mallory_program_id, &mallory_program_id)
301 .owner(&alice_program_id)
302 .verify(),
303 Ok(()),
304 "mallory should be able to change the account owner, if she leaves clear data"
305 );
306 assert_eq!(
307 Change::new(&mallory_program_id, &mallory_program_id)
308 .owner(&alice_program_id)
309 .data(vec![42], vec![0])
310 .verify(),
311 Ok(()),
312 "mallory should be able to change the account owner, if she leaves clear data"
313 );
314 assert_eq!(
315 Change::new(&mallory_program_id, &mallory_program_id)
316 .owner(&alice_program_id)
317 .executable(true, true)
318 .data(vec![42], vec![0])
319 .verify(),
320 Err(InstructionError::ModifiedProgramId),
321 "mallory should not be able to change the account owner, if the account executable"
322 );
323 assert_eq!(
324 Change::new(&mallory_program_id, &mallory_program_id)
325 .owner(&alice_program_id)
326 .data(vec![42], vec![42])
327 .verify(),
328 Err(InstructionError::ModifiedProgramId),
329 "mallory should not be able to inject data into the alice program"
330 );
331 }
332
333 #[test]
334 fn test_verify_account_changes_executable() {
335 let owner = cbe_sdk::pubkey::new_rand();
336 let mallory_program_id = cbe_sdk::pubkey::new_rand();
337 let system_program_id = system_program::id();
338
339 assert_eq!(
340 Change::new(&owner, &system_program_id)
341 .executable(false, true)
342 .verify(),
343 Err(InstructionError::ExecutableModified),
344 "system program can't change executable if system doesn't own the account"
345 );
346 assert_eq!(
347 Change::new(&owner, &system_program_id)
348 .executable(true, true)
349 .data(vec![1], vec![2])
350 .verify(),
351 Err(InstructionError::ExecutableDataModified),
352 "system program can't change executable data if system doesn't own the account"
353 );
354 assert_eq!(
355 Change::new(&owner, &owner).executable(false, true).verify(),
356 Ok(()),
357 "owner should be able to change executable"
358 );
359 assert_eq!(
360 Change::new(&owner, &owner)
361 .executable(false, true)
362 .read_only()
363 .verify(),
364 Err(InstructionError::ExecutableModified),
365 "owner can't modify executable of read-only accounts"
366 );
367 assert_eq!(
368 Change::new(&owner, &owner).executable(true, false).verify(),
369 Err(InstructionError::ExecutableModified),
370 "owner program can't reverse executable"
371 );
372 assert_eq!(
373 Change::new(&owner, &mallory_program_id)
374 .executable(false, true)
375 .verify(),
376 Err(InstructionError::ExecutableModified),
377 "malicious Mallory should not be able to change the account executable"
378 );
379 assert_eq!(
380 Change::new(&owner, &owner)
381 .executable(false, true)
382 .data(vec![1], vec![2])
383 .verify(),
384 Ok(()),
385 "account data can change in the same instruction that sets the bit"
386 );
387 assert_eq!(
388 Change::new(&owner, &owner)
389 .executable(true, true)
390 .data(vec![1], vec![2])
391 .verify(),
392 Err(InstructionError::ExecutableDataModified),
393 "owner should not be able to change an account's data once its marked executable"
394 );
395 assert_eq!(
396 Change::new(&owner, &owner)
397 .executable(true, true)
398 .scoobies(1, 2)
399 .verify(),
400 Err(InstructionError::ExecutableScoobieChange),
401 "owner should not be able to add scoobies once marked executable"
402 );
403 assert_eq!(
404 Change::new(&owner, &owner)
405 .executable(true, true)
406 .scoobies(1, 2)
407 .verify(),
408 Err(InstructionError::ExecutableScoobieChange),
409 "owner should not be able to add scoobies once marked executable"
410 );
411 assert_eq!(
412 Change::new(&owner, &owner)
413 .executable(true, true)
414 .scoobies(2, 1)
415 .verify(),
416 Err(InstructionError::ExecutableScoobieChange),
417 "owner should not be able to subtract scoobies once marked executable"
418 );
419 let data = vec![1; 100];
420 let min_scoobies = Rent::default().minimum_balance(data.len());
421 assert_eq!(
422 Change::new(&owner, &owner)
423 .executable(false, true)
424 .scoobies(0, min_scoobies)
425 .data(data.clone(), data.clone())
426 .verify(),
427 Ok(()),
428 );
429 assert_eq!(
430 Change::new(&owner, &owner)
431 .executable(false, true)
432 .scoobies(0, min_scoobies - 1)
433 .data(data.clone(), data)
434 .verify(),
435 Err(InstructionError::ExecutableAccountNotRentExempt),
436 "owner should not be able to change an account's data once its marked executable"
437 );
438 }
439
440 #[test]
441 fn test_verify_account_changes_data_len() {
442 let alice_program_id = cbe_sdk::pubkey::new_rand();
443
444 assert_eq!(
445 Change::new(&system_program::id(), &system_program::id())
446 .data(vec![0], vec![0, 0])
447 .verify(),
448 Ok(()),
449 "system program should be able to change the data len"
450 );
451 assert_eq!(
452 Change::new(&alice_program_id, &system_program::id())
453 .data(vec![0], vec![0,0])
454 .verify(),
455 Err(InstructionError::AccountDataSizeChanged),
456 "system program should not be able to change the data length of accounts it does not own"
457 );
458 }
459
460 #[test]
461 fn test_verify_account_changes_data() {
462 let alice_program_id = cbe_sdk::pubkey::new_rand();
463 let mallory_program_id = cbe_sdk::pubkey::new_rand();
464
465 assert_eq!(
466 Change::new(&alice_program_id, &alice_program_id)
467 .data(vec![0], vec![42])
468 .verify(),
469 Ok(()),
470 "alice program should be able to change the data"
471 );
472 assert_eq!(
473 Change::new(&mallory_program_id, &alice_program_id)
474 .data(vec![0], vec![42])
475 .verify(),
476 Err(InstructionError::ExternalAccountDataModified),
477 "non-owner mallory should not be able to change the account data"
478 );
479 assert_eq!(
480 Change::new(&alice_program_id, &alice_program_id)
481 .data(vec![0], vec![42])
482 .read_only()
483 .verify(),
484 Err(InstructionError::ReadonlyDataModified),
485 "alice isn't allowed to touch a CO account"
486 );
487 }
488
489 #[test]
490 fn test_verify_account_changes_rent_epoch() {
491 let alice_program_id = cbe_sdk::pubkey::new_rand();
492
493 assert_eq!(
494 Change::new(&alice_program_id, &system_program::id()).verify(),
495 Ok(()),
496 "nothing changed!"
497 );
498 assert_eq!(
499 Change::new(&alice_program_id, &system_program::id())
500 .rent_epoch(0, 1)
501 .verify(),
502 Err(InstructionError::RentEpochModified),
503 "no one touches rent_epoch"
504 );
505 }
506
507 #[test]
508 fn test_verify_account_changes_deduct_scoobies_and_reassign_account() {
509 let alice_program_id = cbe_sdk::pubkey::new_rand();
510 let bob_program_id = cbe_sdk::pubkey::new_rand();
511
512 assert_eq!(
514 Change::new(&alice_program_id, &alice_program_id)
515 .owner(&bob_program_id)
516 .scoobies(42, 1)
517 .data(vec![42], vec![0])
518 .verify(),
519 Ok(()),
520 "alice should be able to deduct scoobies and give the account to bob if the data is zeroed",
521 );
522 }
523
524 #[test]
525 fn test_verify_account_changes_scoobies() {
526 let alice_program_id = cbe_sdk::pubkey::new_rand();
527
528 assert_eq!(
529 Change::new(&alice_program_id, &system_program::id())
530 .scoobies(42, 0)
531 .read_only()
532 .verify(),
533 Err(InstructionError::ExternalAccountScoobieSpend),
534 "debit should fail, even if system program"
535 );
536 assert_eq!(
537 Change::new(&alice_program_id, &alice_program_id)
538 .scoobies(42, 0)
539 .read_only()
540 .verify(),
541 Err(InstructionError::ReadonlyScoobieChange),
542 "debit should fail, even if owning program"
543 );
544 assert_eq!(
545 Change::new(&alice_program_id, &system_program::id())
546 .scoobies(42, 0)
547 .owner(&system_program::id())
548 .verify(),
549 Err(InstructionError::ModifiedProgramId),
550 "system program can't debit the account unless it was the pre.owner"
551 );
552 assert_eq!(
553 Change::new(&system_program::id(), &system_program::id())
554 .scoobies(42, 0)
555 .owner(&alice_program_id)
556 .verify(),
557 Ok(()),
558 "system can spend (and change owner)"
559 );
560 }
561
562 #[test]
563 fn test_verify_account_changes_data_size_changed() {
564 let alice_program_id = cbe_sdk::pubkey::new_rand();
565
566 assert_eq!(
567 Change::new(&alice_program_id, &system_program::id())
568 .data(vec![0], vec![0, 0])
569 .verify(),
570 Err(InstructionError::AccountDataSizeChanged),
571 "system program should not be able to change another program's account data size"
572 );
573 assert_eq!(
574 Change::new(&alice_program_id, &cbe_sdk::pubkey::new_rand())
575 .data(vec![0], vec![0, 0])
576 .verify(),
577 Err(InstructionError::AccountDataSizeChanged),
578 "one program should not be able to change another program's account data size"
579 );
580 assert_eq!(
581 Change::new(&alice_program_id, &alice_program_id)
582 .data(vec![0], vec![0, 0])
583 .verify(),
584 Ok(()),
585 "programs can change their own data size"
586 );
587 assert_eq!(
588 Change::new(&system_program::id(), &system_program::id())
589 .data(vec![0], vec![0, 0])
590 .verify(),
591 Ok(()),
592 "system program should be able to change account data size"
593 );
594 }
595
596 #[test]
597 fn test_verify_account_changes_owner_executable() {
598 let alice_program_id = cbe_sdk::pubkey::new_rand();
599 let bob_program_id = cbe_sdk::pubkey::new_rand();
600
601 assert_eq!(
602 Change::new(&alice_program_id, &alice_program_id)
603 .owner(&bob_program_id)
604 .executable(false, true)
605 .verify(),
606 Err(InstructionError::ExecutableModified),
607 "program should not be able to change owner and executable at the same time"
608 );
609 }
610}