cbe_program_runtime/
pre_account.rs

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// The relevant state of an account before an Instruction executes, used
14// to verify account integrity after the Instruction completes
15#[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        // Only the owner of the account may change owner and
42        //   only if the account is writable and
43        //   only if the account is not executable and
44        //   only if the data is zero-initialized or empty
45        let owner_changed = pre.owner() != post.owner();
46        if owner_changed
47            && (!is_writable // line coverage used to get branch coverage
48                || pre.executable()
49                || program_id != pre.owner()
50            || !Self::is_zeroed(post.data()))
51        {
52            return Err(InstructionError::ModifiedProgramId);
53        }
54
55        // An account not assigned to the program cannot have its balance decrease.
56        if program_id != pre.owner() // line coverage used to get branch coverage
57         && pre.scoobies() > post.scoobies()
58        {
59            return Err(InstructionError::ExternalAccountScoobieSpend);
60        }
61
62        // The balance of read-only and executable accounts may not change
63        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        // Account data size cannot exceed a maxumum length
74        if post.data().len() > MAX_PERMITTED_DATA_LENGTH as usize {
75            return Err(InstructionError::InvalidRealloc);
76        }
77
78        // The owner of the account can change the size of the data
79        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        // Only the owner may change account data
85        //   and if the account is writable
86        //   and if the account is not executable
87        if !(program_id == pre.owner()
88            && is_writable  // line coverage used to get branch coverage
89            && !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        // executable is one-way (false->true) and only the account owner may set it.
102        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 // line coverage used to get branch coverage
108                || pre.executable()
109                || program_id != post.owner()
110            {
111                return Err(InstructionError::ExecutableModified);
112            }
113        }
114
115        // No one modifies rent_epoch (yet).
116        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        // positive test of this capability
513        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}