1pub mod fast;
15#[cfg(feature = "graph")]
16pub mod graph;
17pub mod guards;
18pub mod modifier;
19pub mod trust;
20
21use hopper_runtime::{
22 address::address_eq, error::ProgramError, AccountView, Address, ProgramResult,
23};
24
25#[inline(always)]
29pub fn check_signer(account: &AccountView) -> ProgramResult {
30 if !account.is_signer() {
31 return Err(ProgramError::MissingRequiredSignature);
32 }
33 Ok(())
34}
35
36#[inline(always)]
38pub fn check_writable(account: &AccountView) -> ProgramResult {
39 if !account.is_writable() {
40 return Err(ProgramError::InvalidAccountData);
41 }
42 Ok(())
43}
44
45#[inline(always)]
47pub fn check_owner(account: &AccountView, expected: &Address) -> ProgramResult {
48 if !account.owned_by(expected) {
49 return Err(ProgramError::IncorrectProgramId);
50 }
51 Ok(())
52}
53
54#[inline(always)]
56pub fn check_executable(account: &AccountView) -> ProgramResult {
57 if !account.executable() {
58 return Err(ProgramError::InvalidAccountData);
59 }
60 Ok(())
61}
62
63#[inline(always)]
73pub fn check_program(account: &AccountView, expected_program_id: &Address) -> ProgramResult {
74 if !address_eq(account.address(), expected_program_id) {
75 return Err(ProgramError::IncorrectProgramId);
76 }
77 if !account.executable() {
78 return Err(ProgramError::InvalidAccountData);
79 }
80 Ok(())
81}
82
83#[inline(always)]
85pub fn check_size(data: &[u8], min_len: usize) -> ProgramResult {
86 if data.len() < min_len {
87 return Err(ProgramError::AccountDataTooSmall);
88 }
89 Ok(())
90}
91
92#[inline(always)]
94pub fn check_discriminator(data: &[u8], expected: u8) -> ProgramResult {
95 if data.is_empty() || data[0] != expected {
96 return Err(ProgramError::InvalidAccountData);
97 }
98 Ok(())
99}
100
101#[inline(always)]
103pub fn check_uninitialized(account: &AccountView) -> ProgramResult {
104 if !account.is_data_empty() {
105 return Err(ProgramError::AccountAlreadyInitialized);
106 }
107 Ok(())
108}
109
110#[inline(always)]
112pub fn check_not_closed(data: &[u8]) -> ProgramResult {
113 if !data.is_empty() && data[0] == crate::account::CLOSE_SENTINEL {
114 return Err(ProgramError::InvalidAccountData);
115 }
116 Ok(())
117}
118
119#[inline(always)]
121pub fn rent_exempt_min(data_len: usize) -> u64 {
122 ((128 + data_len) as u64) * 6960
123}
124
125#[inline(always)]
127pub fn check_rent_exempt(account: &AccountView) -> ProgramResult {
128 let lamports = account.lamports();
129 let data = account.try_borrow()?;
130 let min = rent_exempt_min(data.len());
131 if lamports < min {
132 return Err(ProgramError::InsufficientFunds);
133 }
134 Ok(())
135}
136
137#[inline(always)]
139pub fn check_lamports_gte(account: &AccountView, min: u64) -> ProgramResult {
140 let lamports = account.lamports();
141 if lamports < min {
142 return Err(ProgramError::InsufficientFunds);
143 }
144 Ok(())
145}
146
147#[inline(always)]
151pub fn check_keys_eq(a: &AccountView, b: &AccountView) -> ProgramResult {
152 if !address_eq(a.address(), b.address()) {
153 return Err(ProgramError::InvalidAccountData);
154 }
155 Ok(())
156}
157
158#[inline(always)]
164pub fn keys_eq_fast(a: &[u8; 32], b: &[u8; 32]) -> bool {
165 unsafe {
168 let a_ptr = a.as_ptr() as *const u64;
169 let b_ptr = b.as_ptr() as *const u64;
170 core::ptr::read_unaligned(a_ptr) == core::ptr::read_unaligned(b_ptr)
171 && core::ptr::read_unaligned(a_ptr.add(1)) == core::ptr::read_unaligned(b_ptr.add(1))
172 && core::ptr::read_unaligned(a_ptr.add(2)) == core::ptr::read_unaligned(b_ptr.add(2))
173 && core::ptr::read_unaligned(a_ptr.add(3)) == core::ptr::read_unaligned(b_ptr.add(3))
174 }
175}
176
177#[inline(always)]
182pub fn is_zero_address(addr: &[u8; 32]) -> bool {
183 unsafe {
185 let ptr = addr.as_ptr() as *const u64;
186 let combined = core::ptr::read_unaligned(ptr)
187 | core::ptr::read_unaligned(ptr.add(1))
188 | core::ptr::read_unaligned(ptr.add(2))
189 | core::ptr::read_unaligned(ptr.add(3));
190 combined == 0
191 }
192}
193
194#[inline(always)]
199pub fn check_has_one(stored: &[u8; 32], account: &AccountView) -> ProgramResult {
200 let addr: &[u8; 32] = unsafe { &*(account.address() as *const Address as *const [u8; 32]) };
202 if !keys_eq_fast(stored, addr) {
203 return Err(ProgramError::InvalidAccountData);
204 }
205 Ok(())
206}
207
208#[inline(always)]
210pub fn check_accounts_unique(a: &AccountView, b: &AccountView) -> ProgramResult {
211 if address_eq(a.address(), b.address()) {
212 return Err(ProgramError::InvalidArgument);
213 }
214 Ok(())
215}
216
217#[inline(always)]
219pub fn check_accounts_unique_3(a: &AccountView, b: &AccountView, c: &AccountView) -> ProgramResult {
220 if address_eq(a.address(), b.address())
221 || address_eq(a.address(), c.address())
222 || address_eq(b.address(), c.address())
223 {
224 return Err(ProgramError::InvalidArgument);
225 }
226 Ok(())
227}
228
229#[inline(always)]
231pub fn check_address(account: &AccountView, expected: &Address) -> ProgramResult {
232 if !address_eq(account.address(), expected) {
233 return Err(ProgramError::InvalidAccountData);
234 }
235 Ok(())
236}
237
238#[inline(always)]
240pub fn check_instruction_data_min(data: &[u8], min: usize) -> ProgramResult {
241 if data.len() < min {
242 return Err(ProgramError::InvalidInstructionData);
243 }
244 Ok(())
245}
246
247#[inline(always)]
253pub fn check_account(
254 account: &AccountView,
255 program_id: &Address,
256 disc: u8,
257 min_size: usize,
258) -> ProgramResult {
259 check_owner(account, program_id)?;
260 let data = account.try_borrow()?;
261 check_size(&data, min_size)?;
262 check_discriminator(&data, disc)?;
263 Ok(())
264}
265
266#[inline(always)]
268pub fn check_system_program(account: &AccountView) -> ProgramResult {
269 const SYSTEM_PROGRAM: Address = Address::new_from_array([0; 32]);
271 if *account.address() != SYSTEM_PROGRAM {
272 return Err(ProgramError::IncorrectProgramId);
273 }
274 Ok(())
275}
276
277#[inline(always)]
284pub fn verify_pda(
285 account: &AccountView,
286 seeds: &[&[u8]],
287 bump: u8,
288 program_id: &Address,
289) -> ProgramResult {
290 hopper_runtime::pda::verify_pda_with_bump(account, seeds, bump, program_id)
291}
292
293#[inline(always)]
299pub fn find_and_verify_pda(
300 account: &AccountView,
301 seeds: &[&[u8]],
302 program_id: &Address,
303) -> Result<u8, ProgramError> {
304 hopper_runtime::pda::find_and_verify_pda(account, seeds, program_id)
305}
306
307#[inline(always)]
327pub fn verify_pda_cached(
328 account: &AccountView,
329 seeds: &[&[u8]],
330 bump_offset: usize,
331 program_id: &Address,
332) -> ProgramResult {
333 #[cfg(target_os = "solana")]
334 {
335 let data = account.try_borrow()?;
336 if bump_offset >= data.len() {
337 return Err(ProgramError::AccountDataTooSmall);
338 }
339 let bump = data[bump_offset];
340 let bump_seed = [bump];
341 let mut all_seeds: [&[u8]; 17] = [&[]; 17];
342 let seed_count = seeds.len();
343 if seed_count > 16 {
344 return Err(ProgramError::InvalidSeeds);
345 }
346 let mut i = 0;
347 while i < seed_count {
348 all_seeds[i] = seeds[i];
349 i += 1;
350 }
351 all_seeds[seed_count] = &bump_seed;
352
353 let derived = Address::create_program_address(&all_seeds[..seed_count + 1], program_id)?;
354
355 if !address_eq(account.address(), &derived) {
356 return Err(ProgramError::InvalidSeeds);
357 }
358 Ok(())
359 }
360 #[cfg(not(target_os = "solana"))]
361 {
362 let _ = (account, seeds, bump_offset, program_id);
363 Err(ProgramError::InvalidSeeds)
364 }
365}
366
367#[inline]
378pub fn check_owner_multi(
379 account: &AccountView,
380 owners: &[&Address],
381) -> Result<usize, ProgramError> {
382 let acct_owner = unsafe { account.owner() };
385 for (i, expected) in owners.iter().enumerate() {
386 if acct_owner == *expected {
387 return Ok(i);
388 }
389 }
390 Err(ProgramError::IncorrectProgramId)
391}
392
393#[allow(dead_code)]
400const INSTRUCTIONS_SYSVAR: Address = {
401 let mut addr = [0u8; 32];
404 addr[0] = 0x06;
405 addr[1] = 0xa7;
406 addr[2] = 0xd5;
407 addr[3] = 0x17;
408 addr[4] = 0x18;
409 addr[5] = 0x7b;
410 addr[6] = 0xd1;
411 addr[7] = 0x66;
412 addr[8] = 0x35;
413 addr[9] = 0xda;
414 addr[10] = 0xd4;
415 addr[11] = 0x04;
416 addr[12] = 0x55;
417 addr[13] = 0xfb;
418 addr[14] = 0x04;
419 addr[15] = 0x6e;
420 addr[16] = 0x12;
421 addr[17] = 0x46;
422 addr[18] = 0x00;
423 addr[19] = 0x00;
424 addr[20] = 0x00;
425 addr[21] = 0x00;
426 addr[22] = 0x00;
427 addr[23] = 0x00;
428 addr[24] = 0x00;
429 addr[25] = 0x00;
430 addr[26] = 0x00;
431 addr[27] = 0x00;
432 addr[28] = 0x00;
433 addr[29] = 0x00;
434 addr[30] = 0x00;
435 addr[31] = 0x00;
436 Address::new_from_array(addr)
437};
438
439#[inline(always)]
444pub fn instruction_count(sysvar_data: &[u8]) -> Result<u16, ProgramError> {
445 if sysvar_data.len() < 2 {
446 return Err(ProgramError::InvalidAccountData);
447 }
448 Ok(u16::from_le_bytes([sysvar_data[0], sysvar_data[1]]))
449}
450
451#[inline(always)]
456pub fn current_instruction_index(sysvar_data: &[u8]) -> Result<u16, ProgramError> {
457 let len = sysvar_data.len();
458 if len < 2 {
459 return Err(ProgramError::InvalidAccountData);
460 }
461 Ok(u16::from_le_bytes([
462 sysvar_data[len - 2],
463 sysvar_data[len - 1],
464 ]))
465}
466
467#[inline(always)]
468fn instruction_offset(sysvar_data: &[u8], index: u16) -> Result<usize, ProgramError> {
469 let num_ix = instruction_count(sysvar_data)?;
470 if index >= num_ix {
471 return Err(ProgramError::InvalidArgument);
472 }
473 let offset_entry = 2usize
474 .checked_add((index as usize).saturating_mul(2))
475 .ok_or(ProgramError::ArithmeticOverflow)?;
476 if offset_entry + 2 > sysvar_data.len() {
477 return Err(ProgramError::InvalidAccountData);
478 }
479 Ok(u16::from_le_bytes([sysvar_data[offset_entry], sysvar_data[offset_entry + 1]]) as usize)
480}
481
482#[inline]
483fn instruction_layout(
484 sysvar_data: &[u8],
485 index: u16,
486) -> Result<(usize, usize, usize), ProgramError> {
487 let ix_offset = instruction_offset(sysvar_data, index)?;
488 if ix_offset + 2 > sysvar_data.len() {
489 return Err(ProgramError::InvalidAccountData);
490 }
491 let account_count =
492 u16::from_le_bytes([sysvar_data[ix_offset], sysvar_data[ix_offset + 1]]) as usize;
493 let accounts_offset = ix_offset + 2;
494 let metas_len = account_count
495 .checked_mul(33)
496 .ok_or(ProgramError::ArithmeticOverflow)?;
497 let program_id_offset = accounts_offset
498 .checked_add(metas_len)
499 .ok_or(ProgramError::ArithmeticOverflow)?;
500 if program_id_offset + 34 > sysvar_data.len() {
501 return Err(ProgramError::InvalidAccountData);
502 }
503 let data_len_offset = program_id_offset + 32;
504 let data_len = u16::from_le_bytes([
505 sysvar_data[data_len_offset],
506 sysvar_data[data_len_offset + 1],
507 ]) as usize;
508 let data_offset = data_len_offset + 2;
509 if data_offset
510 .checked_add(data_len)
511 .ok_or(ProgramError::ArithmeticOverflow)?
512 > sysvar_data.len()
513 {
514 return Err(ProgramError::InvalidAccountData);
515 }
516 Ok((account_count, program_id_offset, data_offset))
517}
518
519pub struct InstructionsSysvar<'a> {
521 data: &'a [u8],
522}
523
524impl<'a> InstructionsSysvar<'a> {
525 #[inline(always)]
527 pub const fn new(data: &'a [u8]) -> Self {
528 Self { data }
529 }
530
531 #[inline(always)]
533 pub const fn as_bytes(&self) -> &'a [u8] {
534 self.data
535 }
536
537 #[inline(always)]
539 pub fn len(&self) -> Result<u16, ProgramError> {
540 instruction_count(self.data)
541 }
542
543 #[inline(always)]
545 pub fn is_empty(&self) -> Result<bool, ProgramError> {
546 Ok(self.len()? == 0)
547 }
548
549 #[inline(always)]
551 pub fn current_index(&self) -> Result<u16, ProgramError> {
552 current_instruction_index(self.data)
553 }
554
555 #[inline(always)]
557 pub fn instruction(&self, index: u16) -> Result<IntrospectedInstruction<'a>, ProgramError> {
558 IntrospectedInstruction::new(self.data, index)
559 }
560
561 #[inline(always)]
563 pub fn current_instruction(&self) -> Result<IntrospectedInstruction<'a>, ProgramError> {
564 self.instruction(self.current_index()?)
565 }
566
567 #[inline(always)]
569 pub fn program_id_at(&self, index: u16) -> Result<Address, ProgramError> {
570 let bytes = read_program_id_at(self.data, index)?;
571 Ok(Address::new_from_array(bytes))
572 }
573}
574
575pub struct IntrospectedInstruction<'a> {
577 data: &'a [u8],
578 account_count: usize,
579 accounts_offset: usize,
580 program_id_offset: usize,
581 data_offset: usize,
582 data_len: usize,
583}
584
585impl<'a> IntrospectedInstruction<'a> {
586 #[inline]
587 fn new(sysvar_data: &'a [u8], index: u16) -> Result<Self, ProgramError> {
588 let ix_offset = instruction_offset(sysvar_data, index)?;
589 let (account_count, program_id_offset, data_offset) =
590 instruction_layout(sysvar_data, index)?;
591 let data_len_offset = program_id_offset + 32;
592 let data_len = u16::from_le_bytes([
593 sysvar_data[data_len_offset],
594 sysvar_data[data_len_offset + 1],
595 ]) as usize;
596 Ok(Self {
597 data: sysvar_data,
598 account_count,
599 accounts_offset: ix_offset + 2,
600 program_id_offset,
601 data_offset,
602 data_len,
603 })
604 }
605
606 #[inline(always)]
608 pub const fn account_count(&self) -> usize {
609 self.account_count
610 }
611
612 #[inline(always)]
614 pub fn program_id(&self) -> Address {
615 let mut bytes = [0u8; 32];
616 bytes.copy_from_slice(&self.data[self.program_id_offset..self.program_id_offset + 32]);
617 Address::new_from_array(bytes)
618 }
619
620 #[inline(always)]
622 pub fn instruction_data(&self) -> &'a [u8] {
623 &self.data[self.data_offset..self.data_offset + self.data_len]
624 }
625
626 #[inline]
628 pub fn account(&self, index: usize) -> Result<Option<InstructionAccountMeta>, ProgramError> {
629 if index >= self.account_count {
630 return Ok(None);
631 }
632 let meta_offset = index
633 .checked_mul(33)
634 .ok_or(ProgramError::ArithmeticOverflow)?;
635 let offset = self
636 .accounts_offset
637 .checked_add(meta_offset)
638 .ok_or(ProgramError::ArithmeticOverflow)?;
639 if offset + 33 > self.data.len() {
640 return Err(ProgramError::InvalidAccountData);
641 }
642 let mut pubkey = [0u8; 32];
643 pubkey.copy_from_slice(&self.data[offset + 1..offset + 33]);
644 Ok(Some(InstructionAccountMeta {
645 flags: self.data[offset],
646 pubkey,
647 }))
648 }
649}
650
651#[derive(Clone, Copy, Debug, PartialEq, Eq)]
653pub struct InstructionAccountMeta {
654 flags: u8,
655 pubkey: [u8; 32],
656}
657
658impl InstructionAccountMeta {
659 #[inline(always)]
661 pub const fn flags(&self) -> u8 {
662 self.flags
663 }
664
665 #[inline(always)]
667 pub const fn pubkey_bytes(&self) -> &[u8; 32] {
668 &self.pubkey
669 }
670
671 #[inline(always)]
673 pub const fn address(&self) -> Address {
674 Address::new_from_array(self.pubkey)
675 }
676
677 #[inline(always)]
679 pub const fn is_signer(&self) -> bool {
680 self.flags & 0x01 != 0
681 }
682
683 #[inline(always)]
685 pub const fn is_writable(&self) -> bool {
686 self.flags & 0x02 != 0
687 }
688}
689
690#[inline]
711pub fn read_program_id_at(sysvar_data: &[u8], index: u16) -> Result<[u8; 32], ProgramError> {
712 let (_, program_id_offset, _) = instruction_layout(sysvar_data, index)?;
713 if program_id_offset + 32 > sysvar_data.len() {
714 return Err(ProgramError::InvalidAccountData);
715 }
716 let mut pid = [0u8; 32];
717 pid.copy_from_slice(&sysvar_data[program_id_offset..program_id_offset + 32]);
718 Ok(pid)
719}
720
721#[inline]
727pub fn require_top_level(sysvar_data: &[u8], our_program: &Address) -> ProgramResult {
728 let current_idx = current_instruction_index(sysvar_data)?;
729 let pid = read_program_id_at(sysvar_data, current_idx)?;
730 if pid != *our_program.as_array() {
731 return Err(ProgramError::InvalidAccountData);
732 }
733 Ok(())
734}
735
736#[inline]
741pub fn detect_flash_loan_bracket(sysvar_data: &[u8], our_program: &Address) -> ProgramResult {
742 let current_idx = current_instruction_index(sysvar_data)?;
743 let num_ix = instruction_count(sysvar_data)?;
744
745 let mut before = false;
746 let mut after = false;
747
748 let mut i: u16 = 0;
749 while i < num_ix {
750 if i == current_idx {
751 i += 1;
752 continue;
753 }
754 if let Ok(pid) = read_program_id_at(sysvar_data, i) {
755 if pid == *our_program.as_array() {
756 if i < current_idx {
757 before = true;
758 } else {
759 after = true;
760 }
761 }
762 }
763 i += 1;
764 }
765
766 if before && after {
767 return Err(ProgramError::InvalidAccountData);
768 }
769 Ok(())
770}
771
772#[inline]
776pub fn check_no_subsequent_invocation(sysvar_data: &[u8], our_program: &Address) -> ProgramResult {
777 let current_idx = current_instruction_index(sysvar_data)?;
778 let num_ix = instruction_count(sysvar_data)?;
779
780 let mut i = current_idx + 1;
781 while i < num_ix {
782 if let Ok(pid) = read_program_id_at(sysvar_data, i) {
783 if pid == *our_program.as_array() {
784 return Err(ProgramError::InvalidAccountData);
785 }
786 }
787 i += 1;
788 }
789 Ok(())
790}