gemachain_program_runtime/
instruction_processor.rs

1use crate::native_loader::NativeLoader;
2use serde::{Deserialize, Serialize};
3use gemachain_sdk::{
4    account::{AccountSharedData, ReadableAccount, WritableAccount},
5    account_utils::StateMut,
6    bpf_loader_upgradeable::{self, UpgradeableLoaderState},
7    feature_set::{demote_program_write_locks, fix_write_privs},
8    ic_msg,
9    instruction::{Instruction, InstructionError},
10    message::Message,
11    process_instruction::{Executor, InvokeContext, ProcessInstructionWithContext},
12    pubkey::Pubkey,
13    rent::Rent,
14    system_program,
15};
16use std::{
17    cell::{Ref, RefCell, RefMut},
18    collections::HashMap,
19    rc::Rc,
20    sync::Arc,
21};
22
23pub struct Executors {
24    pub executors: HashMap<Pubkey, Arc<dyn Executor>>,
25    pub is_dirty: bool,
26}
27impl Default for Executors {
28    fn default() -> Self {
29        Self {
30            executors: HashMap::default(),
31            is_dirty: false,
32        }
33    }
34}
35impl Executors {
36    pub fn insert(&mut self, key: Pubkey, executor: Arc<dyn Executor>) {
37        let _ = self.executors.insert(key, executor);
38        self.is_dirty = true;
39    }
40    pub fn get(&self, key: &Pubkey) -> Option<Arc<dyn Executor>> {
41        self.executors.get(key).cloned()
42    }
43}
44
45#[derive(Default, Debug)]
46pub struct ProgramTiming {
47    pub accumulated_us: u64,
48    pub accumulated_units: u64,
49    pub count: u32,
50}
51
52#[derive(Default, Debug)]
53pub struct ExecuteDetailsTimings {
54    pub serialize_us: u64,
55    pub create_vm_us: u64,
56    pub execute_us: u64,
57    pub deserialize_us: u64,
58    pub changed_account_count: u64,
59    pub total_account_count: u64,
60    pub total_data_size: usize,
61    pub data_size_changed: usize,
62    pub per_program_timings: HashMap<Pubkey, ProgramTiming>,
63}
64impl ExecuteDetailsTimings {
65    pub fn accumulate(&mut self, other: &ExecuteDetailsTimings) {
66        self.serialize_us += other.serialize_us;
67        self.create_vm_us += other.create_vm_us;
68        self.execute_us += other.execute_us;
69        self.deserialize_us += other.deserialize_us;
70        self.changed_account_count += other.changed_account_count;
71        self.total_account_count += other.total_account_count;
72        self.total_data_size += other.total_data_size;
73        self.data_size_changed += other.data_size_changed;
74        for (id, other) in &other.per_program_timings {
75            let program_timing = self.per_program_timings.entry(*id).or_default();
76            program_timing.accumulated_us = program_timing
77                .accumulated_us
78                .saturating_add(other.accumulated_us);
79            program_timing.accumulated_units = program_timing
80                .accumulated_units
81                .saturating_add(other.accumulated_units);
82            program_timing.count = program_timing.count.saturating_add(other.count);
83        }
84    }
85    pub fn accumulate_program(&mut self, program_id: &Pubkey, us: u64, units: u64) {
86        let program_timing = self.per_program_timings.entry(*program_id).or_default();
87        program_timing.accumulated_us = program_timing.accumulated_us.saturating_add(us);
88        program_timing.accumulated_units = program_timing.accumulated_units.saturating_add(units);
89        program_timing.count = program_timing.count.saturating_add(1);
90    }
91}
92
93// The relevant state of an account before an Instruction executes, used
94// to verify account integrity after the Instruction completes
95#[derive(Clone, Debug, Default)]
96pub struct PreAccount {
97    key: Pubkey,
98    account: Rc<RefCell<AccountSharedData>>,
99    changed: bool,
100}
101impl PreAccount {
102    pub fn new(key: &Pubkey, account: &AccountSharedData) -> Self {
103        Self {
104            key: *key,
105            account: Rc::new(RefCell::new(account.clone())),
106            changed: false,
107        }
108    }
109
110    pub fn verify(
111        &self,
112        program_id: &Pubkey,
113        is_writable: bool,
114        rent: &Rent,
115        post: &AccountSharedData,
116        timings: &mut ExecuteDetailsTimings,
117        outermost_call: bool,
118    ) -> Result<(), InstructionError> {
119        let pre = self.account.borrow();
120
121        // Only the owner of the account may change owner and
122        //   only if the account is writable and
123        //   only if the account is not executable and
124        //   only if the data is zero-initialized or empty
125        let owner_changed = pre.owner() != post.owner();
126        if owner_changed
127            && (!is_writable // line coverage used to get branch coverage
128                || pre.executable()
129                || program_id != pre.owner()
130            || !Self::is_zeroed(post.data()))
131        {
132            return Err(InstructionError::ModifiedProgramId);
133        }
134
135        // An account not assigned to the program cannot have its balance decrease.
136        if program_id != pre.owner() // line coverage used to get branch coverage
137         && pre.carats() > post.carats()
138        {
139            return Err(InstructionError::ExternalAccountCaratSpend);
140        }
141
142        // The balance of read-only and executable accounts may not change
143        let carats_changed = pre.carats() != post.carats();
144        if carats_changed {
145            if !is_writable {
146                return Err(InstructionError::ReadonlyCaratChange);
147            }
148            if pre.executable() {
149                return Err(InstructionError::ExecutableCaratChange);
150            }
151        }
152
153        // Only the system program can change the size of the data
154        //  and only if the system program owns the account
155        let data_len_changed = pre.data().len() != post.data().len();
156        if data_len_changed
157            && (!system_program::check_id(program_id) // line coverage used to get branch coverage
158                || !system_program::check_id(pre.owner()))
159        {
160            return Err(InstructionError::AccountDataSizeChanged);
161        }
162
163        // Only the owner may change account data
164        //   and if the account is writable
165        //   and if the account is not executable
166        if !(program_id == pre.owner()
167            && is_writable  // line coverage used to get branch coverage
168            && !pre.executable())
169            && pre.data() != post.data()
170        {
171            if pre.executable() {
172                return Err(InstructionError::ExecutableDataModified);
173            } else if is_writable {
174                return Err(InstructionError::ExternalAccountDataModified);
175            } else {
176                return Err(InstructionError::ReadonlyDataModified);
177            }
178        }
179
180        // executable is one-way (false->true) and only the account owner may set it.
181        let executable_changed = pre.executable() != post.executable();
182        if executable_changed {
183            if !rent.is_exempt(post.carats(), post.data().len()) {
184                return Err(InstructionError::ExecutableAccountNotRentExempt);
185            }
186            if !is_writable // line coverage used to get branch coverage
187                || pre.executable()
188                || program_id != post.owner()
189            {
190                return Err(InstructionError::ExecutableModified);
191            }
192        }
193
194        // No one modifies rent_epoch (yet).
195        let rent_epoch_changed = pre.rent_epoch() != post.rent_epoch();
196        if rent_epoch_changed {
197            return Err(InstructionError::RentEpochModified);
198        }
199
200        if outermost_call {
201            timings.total_account_count += 1;
202            timings.total_data_size += post.data().len();
203            if owner_changed
204                || carats_changed
205                || data_len_changed
206                || executable_changed
207                || rent_epoch_changed
208                || self.changed
209            {
210                timings.changed_account_count += 1;
211                timings.data_size_changed += post.data().len();
212            }
213        }
214
215        Ok(())
216    }
217
218    pub fn update(&mut self, account: &AccountSharedData) {
219        let mut pre = self.account.borrow_mut();
220        let rent_epoch = pre.rent_epoch();
221        *pre = account.clone();
222        pre.set_rent_epoch(rent_epoch);
223
224        self.changed = true;
225    }
226
227    pub fn key(&self) -> &Pubkey {
228        &self.key
229    }
230
231    pub fn data(&self) -> Ref<[u8]> {
232        Ref::map(self.account.borrow(), |account| account.data())
233    }
234
235    pub fn carats(&self) -> u64 {
236        self.account.borrow().carats()
237    }
238
239    pub fn executable(&self) -> bool {
240        self.account.borrow().executable()
241    }
242
243    pub fn is_zeroed(buf: &[u8]) -> bool {
244        const ZEROS_LEN: usize = 1024;
245        static ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN];
246        let mut chunks = buf.chunks_exact(ZEROS_LEN);
247
248        chunks.all(|chunk| chunk == &ZEROS[..])
249            && chunks.remainder() == &ZEROS[..chunks.remainder().len()]
250    }
251}
252
253#[derive(Deserialize, Serialize)]
254pub struct InstructionProcessor {
255    #[serde(skip)]
256    programs: Vec<(Pubkey, ProcessInstructionWithContext)>,
257    #[serde(skip)]
258    native_loader: NativeLoader,
259}
260
261impl std::fmt::Debug for InstructionProcessor {
262    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
263        #[derive(Debug)]
264        struct MessageProcessor<'a> {
265            programs: Vec<String>,
266            native_loader: &'a NativeLoader,
267        }
268
269        // These are just type aliases for work around of Debug-ing above pointers
270        type ErasedProcessInstructionWithContext = fn(
271            &'static Pubkey,
272            &'static [u8],
273            &'static mut dyn InvokeContext,
274        ) -> Result<(), InstructionError>;
275
276        // rustc doesn't compile due to bug without this work around
277        // https://github.com/rust-lang/rust/issues/50280
278        // https://users.rust-lang.org/t/display-function-pointer/17073/2
279        let processor = MessageProcessor {
280            programs: self
281                .programs
282                .iter()
283                .map(|(pubkey, instruction)| {
284                    let erased_instruction: ErasedProcessInstructionWithContext = *instruction;
285                    format!("{}: {:p}", pubkey, erased_instruction)
286                })
287                .collect::<Vec<_>>(),
288            native_loader: &self.native_loader,
289        };
290
291        write!(f, "{:?}", processor)
292    }
293}
294
295impl Default for InstructionProcessor {
296    fn default() -> Self {
297        Self {
298            programs: vec![],
299            native_loader: NativeLoader::default(),
300        }
301    }
302}
303impl Clone for InstructionProcessor {
304    fn clone(&self) -> Self {
305        InstructionProcessor {
306            programs: self.programs.clone(),
307            native_loader: NativeLoader::default(),
308        }
309    }
310}
311
312#[cfg(RUSTC_WITH_SPECIALIZATION)]
313impl ::gemachain_frozen_abi::abi_example::AbiExample for InstructionProcessor {
314    fn example() -> Self {
315        // MessageProcessor's fields are #[serde(skip)]-ed and not Serialize
316        // so, just rely on Default anyway.
317        InstructionProcessor::default()
318    }
319}
320
321impl InstructionProcessor {
322    pub fn programs(&self) -> &[(Pubkey, ProcessInstructionWithContext)] {
323        &self.programs
324    }
325
326    /// Add a static entrypoint to intercept instructions before the dynamic loader.
327    pub fn add_program(
328        &mut self,
329        program_id: Pubkey,
330        process_instruction: ProcessInstructionWithContext,
331    ) {
332        match self.programs.iter_mut().find(|(key, _)| program_id == *key) {
333            Some((_, processor)) => *processor = process_instruction,
334            None => self.programs.push((program_id, process_instruction)),
335        }
336    }
337
338    /// Process an instruction
339    /// This method calls the instruction's program entrypoint method
340    pub fn process_instruction(
341        &self,
342        program_id: &Pubkey,
343        instruction_data: &[u8],
344        invoke_context: &mut dyn InvokeContext,
345    ) -> Result<(), InstructionError> {
346        if let Some(root_account) = invoke_context.get_keyed_accounts()?.iter().next() {
347            let root_id = root_account.unsigned_key();
348            if gemachain_sdk::native_loader::check_id(&root_account.owner()?) {
349                for (id, process_instruction) in &self.programs {
350                    if id == root_id {
351                        invoke_context.remove_first_keyed_account()?;
352                        // Call the builtin program
353                        return process_instruction(program_id, instruction_data, invoke_context);
354                    }
355                }
356                // Call the program via the native loader
357                return self.native_loader.process_instruction(
358                    &gemachain_sdk::native_loader::id(),
359                    instruction_data,
360                    invoke_context,
361                );
362            } else {
363                let owner_id = &root_account.owner()?;
364                for (id, process_instruction) in &self.programs {
365                    if id == owner_id {
366                        // Call the program via a builtin loader
367                        return process_instruction(program_id, instruction_data, invoke_context);
368                    }
369                }
370            }
371        }
372        Err(InstructionError::UnsupportedProgramId)
373    }
374
375    pub fn create_message(
376        instruction: &Instruction,
377        signers: &[Pubkey],
378        invoke_context: &RefMut<&mut dyn InvokeContext>,
379    ) -> Result<(Message, Vec<bool>, Vec<usize>), InstructionError> {
380        let message = Message::new(&[instruction.clone()], None);
381
382        // Gather keyed_accounts in the order of message.account_keys
383        let caller_keyed_accounts = invoke_context.get_keyed_accounts()?;
384        let callee_keyed_accounts = message
385            .account_keys
386            .iter()
387            .map(|account_key| {
388                caller_keyed_accounts
389                    .iter()
390                    .find(|keyed_account| keyed_account.unsigned_key() == account_key)
391                    .ok_or_else(|| {
392                        ic_msg!(
393                            *invoke_context,
394                            "Instruction references an unknown account {}",
395                            account_key
396                        );
397                        InstructionError::MissingAccount
398                    })
399            })
400            .collect::<Result<Vec<_>, InstructionError>>()?;
401
402        // Check for privilege escalation
403        for account in instruction.accounts.iter() {
404            let keyed_account = callee_keyed_accounts
405                .iter()
406                .find_map(|keyed_account| {
407                    if &account.pubkey == keyed_account.unsigned_key() {
408                        Some(keyed_account)
409                    } else {
410                        None
411                    }
412                })
413                .ok_or_else(|| {
414                    ic_msg!(
415                        invoke_context,
416                        "Instruction references an unknown account {}",
417                        account.pubkey
418                    );
419                    InstructionError::MissingAccount
420                })?;
421            // Readonly account cannot become writable
422            if account.is_writable && !keyed_account.is_writable() {
423                ic_msg!(
424                    invoke_context,
425                    "{}'s writable privilege escalated",
426                    account.pubkey
427                );
428                return Err(InstructionError::PrivilegeEscalation);
429            }
430
431            if account.is_signer && // If message indicates account is signed
432            !( // one of the following needs to be true:
433                keyed_account.signer_key().is_some() // Signed in the parent instruction
434                || signers.contains(&account.pubkey) // Signed by the program
435            ) {
436                ic_msg!(
437                    invoke_context,
438                    "{}'s signer privilege escalated",
439                    account.pubkey
440                );
441                return Err(InstructionError::PrivilegeEscalation);
442            }
443        }
444        let caller_write_privileges = callee_keyed_accounts
445            .iter()
446            .map(|keyed_account| keyed_account.is_writable())
447            .collect::<Vec<bool>>();
448
449        // Find and validate executables / program accounts
450        let callee_program_id = instruction.program_id;
451        let (program_account_index, program_account) = callee_keyed_accounts
452            .iter()
453            .find(|keyed_account| &callee_program_id == keyed_account.unsigned_key())
454            .and_then(|_keyed_account| invoke_context.get_account(&callee_program_id))
455            .ok_or_else(|| {
456                ic_msg!(invoke_context, "Unknown program {}", callee_program_id);
457                InstructionError::MissingAccount
458            })?;
459        if !program_account.borrow().executable() {
460            ic_msg!(
461                invoke_context,
462                "Account {} is not executable",
463                callee_program_id
464            );
465            return Err(InstructionError::AccountNotExecutable);
466        }
467        let mut program_indices = vec![program_account_index];
468        if program_account.borrow().owner() == &bpf_loader_upgradeable::id() {
469            if let UpgradeableLoaderState::Program {
470                programdata_address,
471            } = program_account.borrow().state()?
472            {
473                if let Some((programdata_account_index, _programdata_account)) =
474                    invoke_context.get_account(&programdata_address)
475                {
476                    program_indices.push(programdata_account_index);
477                } else {
478                    ic_msg!(
479                        invoke_context,
480                        "Unknown upgradeable programdata account {}",
481                        programdata_address,
482                    );
483                    return Err(InstructionError::MissingAccount);
484                }
485            } else {
486                ic_msg!(
487                    invoke_context,
488                    "Invalid upgradeable program account {}",
489                    callee_program_id,
490                );
491                return Err(InstructionError::MissingAccount);
492            }
493        }
494
495        Ok((message, caller_write_privileges, program_indices))
496    }
497
498    /// Entrypoint for a cross-program invocation from a native program
499    pub fn native_invoke(
500        invoke_context: &mut dyn InvokeContext,
501        instruction: Instruction,
502        keyed_account_indices_obsolete: &[usize],
503        signers: &[Pubkey],
504    ) -> Result<(), InstructionError> {
505        let invoke_context = RefCell::new(invoke_context);
506        let mut invoke_context = invoke_context.borrow_mut();
507
508        // Translate and verify caller's data
509        let (message, mut caller_write_privileges, program_indices) =
510            Self::create_message(&instruction, signers, &invoke_context)?;
511        if !invoke_context.is_feature_active(&fix_write_privs::id()) {
512            let caller_keyed_accounts = invoke_context.get_keyed_accounts()?;
513            caller_write_privileges = Vec::with_capacity(1 + keyed_account_indices_obsolete.len());
514            caller_write_privileges.push(false);
515            for index in keyed_account_indices_obsolete.iter() {
516                caller_write_privileges.push(caller_keyed_accounts[*index].is_writable());
517            }
518        };
519        let mut account_indices = Vec::with_capacity(message.account_keys.len());
520        let mut accounts = Vec::with_capacity(message.account_keys.len());
521        for account_key in message.account_keys.iter() {
522            let (account_index, account) = invoke_context
523                .get_account(account_key)
524                .ok_or(InstructionError::MissingAccount)?;
525            let account_length = account.borrow().data().len();
526            account_indices.push(account_index);
527            accounts.push((account, account_length));
528        }
529
530        // Record the instruction
531        invoke_context.record_instruction(&instruction);
532
533        // Process instruction
534        InstructionProcessor::process_cross_program_instruction(
535            &message,
536            &program_indices,
537            &account_indices,
538            &caller_write_privileges,
539            *invoke_context,
540        )?;
541
542        // Verify the called program has not misbehaved
543        for (account, prev_size) in accounts.iter() {
544            if *prev_size != account.borrow().data().len() && *prev_size != 0 {
545                // Only support for `CreateAccount` at this time.
546                // Need a way to limit total realloc size across multiple CPI calls
547                ic_msg!(
548                    invoke_context,
549                    "Inner instructions do not support realloc, only SystemProgram::CreateAccount",
550                );
551                return Err(InstructionError::InvalidRealloc);
552            }
553        }
554
555        Ok(())
556    }
557
558    /// Process a cross-program instruction
559    /// This method calls the instruction's program entrypoint function
560    pub fn process_cross_program_instruction(
561        message: &Message,
562        program_indices: &[usize],
563        account_indices: &[usize],
564        caller_write_privileges: &[bool],
565        invoke_context: &mut dyn InvokeContext,
566    ) -> Result<(), InstructionError> {
567        // This function is always called with a valid instruction, if that changes return an error
568        let instruction = message
569            .instructions
570            .get(0)
571            .ok_or(InstructionError::GenericError)?;
572
573        let program_id = instruction.program_id(&message.account_keys);
574
575        // Verify the calling program hasn't misbehaved
576        invoke_context.verify_and_update(instruction, account_indices, caller_write_privileges)?;
577
578        // clear the return data
579        invoke_context.set_return_data(None);
580
581        // Invoke callee
582        invoke_context.push(
583            program_id,
584            message,
585            instruction,
586            program_indices,
587            account_indices,
588        )?;
589
590        let mut instruction_processor = InstructionProcessor::default();
591        for (program_id, process_instruction) in invoke_context.get_programs().iter() {
592            instruction_processor.add_program(*program_id, *process_instruction);
593        }
594
595        let mut result = instruction_processor.process_instruction(
596            program_id,
597            &instruction.data,
598            invoke_context,
599        );
600        if result.is_ok() {
601            // Verify the called program has not misbehaved
602            let demote_program_write_locks =
603                invoke_context.is_feature_active(&demote_program_write_locks::id());
604            let write_privileges: Vec<bool> = (0..message.account_keys.len())
605                .map(|i| message.is_writable(i, demote_program_write_locks))
606                .collect();
607            result =
608                invoke_context.verify_and_update(instruction, account_indices, &write_privileges);
609        }
610
611        // Restore previous state
612        invoke_context.pop();
613        result
614    }
615}
616
617#[cfg(test)]
618mod tests {
619    use super::*;
620    use gemachain_sdk::{account::Account, instruction::InstructionError};
621
622    #[test]
623    fn test_is_zeroed() {
624        const ZEROS_LEN: usize = 1024;
625        let mut buf = [0; ZEROS_LEN];
626        assert!(PreAccount::is_zeroed(&buf));
627        buf[0] = 1;
628        assert!(!PreAccount::is_zeroed(&buf));
629
630        let mut buf = [0; ZEROS_LEN - 1];
631        assert!(PreAccount::is_zeroed(&buf));
632        buf[0] = 1;
633        assert!(!PreAccount::is_zeroed(&buf));
634
635        let mut buf = [0; ZEROS_LEN + 1];
636        assert!(PreAccount::is_zeroed(&buf));
637        buf[0] = 1;
638        assert!(!PreAccount::is_zeroed(&buf));
639
640        let buf = vec![];
641        assert!(PreAccount::is_zeroed(&buf));
642    }
643
644    struct Change {
645        program_id: Pubkey,
646        is_writable: bool,
647        rent: Rent,
648        pre: PreAccount,
649        post: AccountSharedData,
650    }
651    impl Change {
652        pub fn new(owner: &Pubkey, program_id: &Pubkey) -> Self {
653            Self {
654                program_id: *program_id,
655                rent: Rent::default(),
656                is_writable: true,
657                pre: PreAccount::new(
658                    &gemachain_sdk::pubkey::new_rand(),
659                    &AccountSharedData::from(Account {
660                        owner: *owner,
661                        carats: std::u64::MAX,
662                        data: vec![],
663                        ..Account::default()
664                    }),
665                ),
666                post: AccountSharedData::from(Account {
667                    owner: *owner,
668                    carats: std::u64::MAX,
669                    ..Account::default()
670                }),
671            }
672        }
673        pub fn read_only(mut self) -> Self {
674            self.is_writable = false;
675            self
676        }
677        pub fn executable(mut self, pre: bool, post: bool) -> Self {
678            self.pre.account.borrow_mut().set_executable(pre);
679            self.post.set_executable(post);
680            self
681        }
682        pub fn carats(mut self, pre: u64, post: u64) -> Self {
683            self.pre.account.borrow_mut().set_carats(pre);
684            self.post.set_carats(post);
685            self
686        }
687        pub fn owner(mut self, post: &Pubkey) -> Self {
688            self.post.set_owner(*post);
689            self
690        }
691        pub fn data(mut self, pre: Vec<u8>, post: Vec<u8>) -> Self {
692            self.pre.account.borrow_mut().set_data(pre);
693            self.post.set_data(post);
694            self
695        }
696        pub fn rent_epoch(mut self, pre: u64, post: u64) -> Self {
697            self.pre.account.borrow_mut().set_rent_epoch(pre);
698            self.post.set_rent_epoch(post);
699            self
700        }
701        pub fn verify(&self) -> Result<(), InstructionError> {
702            self.pre.verify(
703                &self.program_id,
704                self.is_writable,
705                &self.rent,
706                &self.post,
707                &mut ExecuteDetailsTimings::default(),
708                false,
709            )
710        }
711    }
712
713    #[test]
714    fn test_verify_account_changes_owner() {
715        let system_program_id = system_program::id();
716        let alice_program_id = gemachain_sdk::pubkey::new_rand();
717        let mallory_program_id = gemachain_sdk::pubkey::new_rand();
718
719        assert_eq!(
720            Change::new(&system_program_id, &system_program_id)
721                .owner(&alice_program_id)
722                .verify(),
723            Ok(()),
724            "system program should be able to change the account owner"
725        );
726        assert_eq!(
727            Change::new(&system_program_id, &system_program_id)
728                .owner(&alice_program_id)
729                .read_only()
730                .verify(),
731            Err(InstructionError::ModifiedProgramId),
732            "system program should not be able to change the account owner of a read-only account"
733        );
734        assert_eq!(
735            Change::new(&mallory_program_id, &system_program_id)
736                .owner(&alice_program_id)
737                .verify(),
738            Err(InstructionError::ModifiedProgramId),
739            "system program should not be able to change the account owner of a non-system account"
740        );
741        assert_eq!(
742            Change::new(&mallory_program_id, &mallory_program_id)
743                .owner(&alice_program_id)
744                .verify(),
745            Ok(()),
746            "mallory should be able to change the account owner, if she leaves clear data"
747        );
748        assert_eq!(
749            Change::new(&mallory_program_id, &mallory_program_id)
750                .owner(&alice_program_id)
751                .data(vec![42], vec![0])
752                .verify(),
753            Ok(()),
754            "mallory should be able to change the account owner, if she leaves clear data"
755        );
756        assert_eq!(
757            Change::new(&mallory_program_id, &mallory_program_id)
758                .owner(&alice_program_id)
759                .executable(true, true)
760                .data(vec![42], vec![0])
761                .verify(),
762            Err(InstructionError::ModifiedProgramId),
763            "mallory should not be able to change the account owner, if the account executable"
764        );
765        assert_eq!(
766            Change::new(&mallory_program_id, &mallory_program_id)
767                .owner(&alice_program_id)
768                .data(vec![42], vec![42])
769                .verify(),
770            Err(InstructionError::ModifiedProgramId),
771            "mallory should not be able to inject data into the alice program"
772        );
773    }
774
775    #[test]
776    fn test_verify_account_changes_executable() {
777        let owner = gemachain_sdk::pubkey::new_rand();
778        let mallory_program_id = gemachain_sdk::pubkey::new_rand();
779        let system_program_id = system_program::id();
780
781        assert_eq!(
782            Change::new(&owner, &system_program_id)
783                .executable(false, true)
784                .verify(),
785            Err(InstructionError::ExecutableModified),
786            "system program can't change executable if system doesn't own the account"
787        );
788        assert_eq!(
789            Change::new(&owner, &system_program_id)
790                .executable(true, true)
791                .data(vec![1], vec![2])
792                .verify(),
793            Err(InstructionError::ExecutableDataModified),
794            "system program can't change executable data if system doesn't own the account"
795        );
796        assert_eq!(
797            Change::new(&owner, &owner).executable(false, true).verify(),
798            Ok(()),
799            "owner should be able to change executable"
800        );
801        assert_eq!(
802            Change::new(&owner, &owner)
803                .executable(false, true)
804                .read_only()
805                .verify(),
806            Err(InstructionError::ExecutableModified),
807            "owner can't modify executable of read-only accounts"
808        );
809        assert_eq!(
810            Change::new(&owner, &owner).executable(true, false).verify(),
811            Err(InstructionError::ExecutableModified),
812            "owner program can't reverse executable"
813        );
814        assert_eq!(
815            Change::new(&owner, &mallory_program_id)
816                .executable(false, true)
817                .verify(),
818            Err(InstructionError::ExecutableModified),
819            "malicious Mallory should not be able to change the account executable"
820        );
821        assert_eq!(
822            Change::new(&owner, &owner)
823                .executable(false, true)
824                .data(vec![1], vec![2])
825                .verify(),
826            Ok(()),
827            "account data can change in the same instruction that sets the bit"
828        );
829        assert_eq!(
830            Change::new(&owner, &owner)
831                .executable(true, true)
832                .data(vec![1], vec![2])
833                .verify(),
834            Err(InstructionError::ExecutableDataModified),
835            "owner should not be able to change an account's data once its marked executable"
836        );
837        assert_eq!(
838            Change::new(&owner, &owner)
839                .executable(true, true)
840                .carats(1, 2)
841                .verify(),
842            Err(InstructionError::ExecutableCaratChange),
843            "owner should not be able to add carats once marked executable"
844        );
845        assert_eq!(
846            Change::new(&owner, &owner)
847                .executable(true, true)
848                .carats(1, 2)
849                .verify(),
850            Err(InstructionError::ExecutableCaratChange),
851            "owner should not be able to add carats once marked executable"
852        );
853        assert_eq!(
854            Change::new(&owner, &owner)
855                .executable(true, true)
856                .carats(2, 1)
857                .verify(),
858            Err(InstructionError::ExecutableCaratChange),
859            "owner should not be able to subtract carats once marked executable"
860        );
861        let data = vec![1; 100];
862        let min_carats = Rent::default().minimum_balance(data.len());
863        assert_eq!(
864            Change::new(&owner, &owner)
865                .executable(false, true)
866                .carats(0, min_carats)
867                .data(data.clone(), data.clone())
868                .verify(),
869            Ok(()),
870        );
871        assert_eq!(
872            Change::new(&owner, &owner)
873                .executable(false, true)
874                .carats(0, min_carats - 1)
875                .data(data.clone(), data)
876                .verify(),
877            Err(InstructionError::ExecutableAccountNotRentExempt),
878            "owner should not be able to change an account's data once its marked executable"
879        );
880    }
881
882    #[test]
883    fn test_verify_account_changes_data_len() {
884        let alice_program_id = gemachain_sdk::pubkey::new_rand();
885
886        assert_eq!(
887            Change::new(&system_program::id(), &system_program::id())
888                .data(vec![0], vec![0, 0])
889                .verify(),
890            Ok(()),
891            "system program should be able to change the data len"
892        );
893        assert_eq!(
894            Change::new(&alice_program_id, &system_program::id())
895            .data(vec![0], vec![0,0])
896            .verify(),
897        Err(InstructionError::AccountDataSizeChanged),
898        "system program should not be able to change the data length of accounts it does not own"
899        );
900    }
901
902    #[test]
903    fn test_verify_account_changes_data() {
904        let alice_program_id = gemachain_sdk::pubkey::new_rand();
905        let mallory_program_id = gemachain_sdk::pubkey::new_rand();
906
907        assert_eq!(
908            Change::new(&alice_program_id, &alice_program_id)
909                .data(vec![0], vec![42])
910                .verify(),
911            Ok(()),
912            "alice program should be able to change the data"
913        );
914        assert_eq!(
915            Change::new(&mallory_program_id, &alice_program_id)
916                .data(vec![0], vec![42])
917                .verify(),
918            Err(InstructionError::ExternalAccountDataModified),
919            "non-owner mallory should not be able to change the account data"
920        );
921        assert_eq!(
922            Change::new(&alice_program_id, &alice_program_id)
923                .data(vec![0], vec![42])
924                .read_only()
925                .verify(),
926            Err(InstructionError::ReadonlyDataModified),
927            "alice isn't allowed to touch a CO account"
928        );
929    }
930
931    #[test]
932    fn test_verify_account_changes_rent_epoch() {
933        let alice_program_id = gemachain_sdk::pubkey::new_rand();
934
935        assert_eq!(
936            Change::new(&alice_program_id, &system_program::id()).verify(),
937            Ok(()),
938            "nothing changed!"
939        );
940        assert_eq!(
941            Change::new(&alice_program_id, &system_program::id())
942                .rent_epoch(0, 1)
943                .verify(),
944            Err(InstructionError::RentEpochModified),
945            "no one touches rent_epoch"
946        );
947    }
948
949    #[test]
950    fn test_verify_account_changes_deduct_carats_and_reassign_account() {
951        let alice_program_id = gemachain_sdk::pubkey::new_rand();
952        let bob_program_id = gemachain_sdk::pubkey::new_rand();
953
954        // positive test of this capability
955        assert_eq!(
956            Change::new(&alice_program_id, &alice_program_id)
957            .owner(&bob_program_id)
958            .carats(42, 1)
959            .data(vec![42], vec![0])
960            .verify(),
961        Ok(()),
962        "alice should be able to deduct carats and give the account to bob if the data is zeroed",
963    );
964    }
965
966    #[test]
967    fn test_verify_account_changes_carats() {
968        let alice_program_id = gemachain_sdk::pubkey::new_rand();
969
970        assert_eq!(
971            Change::new(&alice_program_id, &system_program::id())
972                .carats(42, 0)
973                .read_only()
974                .verify(),
975            Err(InstructionError::ExternalAccountCaratSpend),
976            "debit should fail, even if system program"
977        );
978        assert_eq!(
979            Change::new(&alice_program_id, &alice_program_id)
980                .carats(42, 0)
981                .read_only()
982                .verify(),
983            Err(InstructionError::ReadonlyCaratChange),
984            "debit should fail, even if owning program"
985        );
986        assert_eq!(
987            Change::new(&alice_program_id, &system_program::id())
988                .carats(42, 0)
989                .owner(&system_program::id())
990                .verify(),
991            Err(InstructionError::ModifiedProgramId),
992            "system program can't debit the account unless it was the pre.owner"
993        );
994        assert_eq!(
995            Change::new(&system_program::id(), &system_program::id())
996                .carats(42, 0)
997                .owner(&alice_program_id)
998                .verify(),
999            Ok(()),
1000            "system can spend (and change owner)"
1001        );
1002    }
1003
1004    #[test]
1005    fn test_verify_account_changes_data_size_changed() {
1006        let alice_program_id = gemachain_sdk::pubkey::new_rand();
1007
1008        assert_eq!(
1009            Change::new(&alice_program_id, &system_program::id())
1010                .data(vec![0], vec![0, 0])
1011                .verify(),
1012            Err(InstructionError::AccountDataSizeChanged),
1013            "system program should not be able to change another program's account data size"
1014        );
1015        assert_eq!(
1016            Change::new(&alice_program_id, &alice_program_id)
1017                .data(vec![0], vec![0, 0])
1018                .verify(),
1019            Err(InstructionError::AccountDataSizeChanged),
1020            "non-system programs cannot change their data size"
1021        );
1022        assert_eq!(
1023            Change::new(&system_program::id(), &system_program::id())
1024                .data(vec![0], vec![0, 0])
1025                .verify(),
1026            Ok(()),
1027            "system program should be able to change account data size"
1028        );
1029    }
1030
1031    #[test]
1032    fn test_verify_account_changes_owner_executable() {
1033        let alice_program_id = gemachain_sdk::pubkey::new_rand();
1034        let bob_program_id = gemachain_sdk::pubkey::new_rand();
1035
1036        assert_eq!(
1037            Change::new(&alice_program_id, &alice_program_id)
1038                .owner(&bob_program_id)
1039                .executable(false, true)
1040                .verify(),
1041            Err(InstructionError::ExecutableModified),
1042            "Program should not be able to change owner and executable at the same time"
1043        );
1044    }
1045
1046    #[test]
1047    fn test_debug() {
1048        let mut instruction_processor = InstructionProcessor::default();
1049        #[allow(clippy::unnecessary_wraps)]
1050        fn mock_process_instruction(
1051            _program_id: &Pubkey,
1052            _data: &[u8],
1053            _invoke_context: &mut dyn InvokeContext,
1054        ) -> Result<(), InstructionError> {
1055            Ok(())
1056        }
1057        #[allow(clippy::unnecessary_wraps)]
1058        fn mock_ix_processor(
1059            _pubkey: &Pubkey,
1060            _data: &[u8],
1061            _context: &mut dyn InvokeContext,
1062        ) -> Result<(), InstructionError> {
1063            Ok(())
1064        }
1065        let program_id = gemachain_sdk::pubkey::new_rand();
1066        instruction_processor.add_program(program_id, mock_process_instruction);
1067        instruction_processor.add_program(program_id, mock_ix_processor);
1068
1069        assert!(!format!("{:?}", instruction_processor).is_empty());
1070    }
1071}