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#[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 let owner_changed = pre.owner() != post.owner();
126 if owner_changed
127 && (!is_writable || pre.executable()
129 || program_id != pre.owner()
130 || !Self::is_zeroed(post.data()))
131 {
132 return Err(InstructionError::ModifiedProgramId);
133 }
134
135 if program_id != pre.owner() && pre.carats() > post.carats()
138 {
139 return Err(InstructionError::ExternalAccountCaratSpend);
140 }
141
142 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 let data_len_changed = pre.data().len() != post.data().len();
156 if data_len_changed
157 && (!system_program::check_id(program_id) || !system_program::check_id(pre.owner()))
159 {
160 return Err(InstructionError::AccountDataSizeChanged);
161 }
162
163 if !(program_id == pre.owner()
167 && is_writable && !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 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 || pre.executable()
188 || program_id != post.owner()
189 {
190 return Err(InstructionError::ExecutableModified);
191 }
192 }
193
194 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 type ErasedProcessInstructionWithContext = fn(
271 &'static Pubkey,
272 &'static [u8],
273 &'static mut dyn InvokeContext,
274 ) -> Result<(), InstructionError>;
275
276 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 InstructionProcessor::default()
318 }
319}
320
321impl InstructionProcessor {
322 pub fn programs(&self) -> &[(Pubkey, ProcessInstructionWithContext)] {
323 &self.programs
324 }
325
326 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 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 return process_instruction(program_id, instruction_data, invoke_context);
354 }
355 }
356 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 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 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 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 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 && !( keyed_account.signer_key().is_some() || signers.contains(&account.pubkey) ) {
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 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 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 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 invoke_context.record_instruction(&instruction);
532
533 InstructionProcessor::process_cross_program_instruction(
535 &message,
536 &program_indices,
537 &account_indices,
538 &caller_write_privileges,
539 *invoke_context,
540 )?;
541
542 for (account, prev_size) in accounts.iter() {
544 if *prev_size != account.borrow().data().len() && *prev_size != 0 {
545 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 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 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 invoke_context.verify_and_update(instruction, account_indices, caller_write_privileges)?;
577
578 invoke_context.set_return_data(None);
580
581 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 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 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 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}