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]
488pub fn read_program_id_at(sysvar_data: &[u8], index: u16) -> Result<[u8; 32], ProgramError> {
489 let num_ix = instruction_count(sysvar_data)?;
490 if index >= num_ix {
491 return Err(ProgramError::InvalidArgument);
492 }
493 let offset_entry = 2 + (index as usize) * 2;
495 if offset_entry + 2 > sysvar_data.len() {
496 return Err(ProgramError::InvalidAccountData);
497 }
498 let ix_offset =
499 u16::from_le_bytes([sysvar_data[offset_entry], sysvar_data[offset_entry + 1]]) as usize;
500
501 if ix_offset + 2 > sysvar_data.len() {
505 return Err(ProgramError::InvalidAccountData);
506 }
507 let num_accounts =
508 u16::from_le_bytes([sysvar_data[ix_offset], sysvar_data[ix_offset + 1]]) as usize;
509 let program_id_offset = ix_offset + 2 + num_accounts * 33;
511 if program_id_offset + 32 > sysvar_data.len() {
512 return Err(ProgramError::InvalidAccountData);
513 }
514 let mut pid = [0u8; 32];
515 pid.copy_from_slice(&sysvar_data[program_id_offset..program_id_offset + 32]);
516 Ok(pid)
517}
518
519#[inline]
525pub fn require_top_level(sysvar_data: &[u8], our_program: &Address) -> ProgramResult {
526 let current_idx = current_instruction_index(sysvar_data)?;
527 let pid = read_program_id_at(sysvar_data, current_idx)?;
528 if pid != *our_program.as_array() {
529 return Err(ProgramError::InvalidAccountData);
530 }
531 Ok(())
532}
533
534#[inline]
539pub fn detect_flash_loan_bracket(sysvar_data: &[u8], our_program: &Address) -> ProgramResult {
540 let current_idx = current_instruction_index(sysvar_data)?;
541 let num_ix = instruction_count(sysvar_data)?;
542
543 let mut before = false;
544 let mut after = false;
545
546 let mut i: u16 = 0;
547 while i < num_ix {
548 if i == current_idx {
549 i += 1;
550 continue;
551 }
552 if let Ok(pid) = read_program_id_at(sysvar_data, i) {
553 if pid == *our_program.as_array() {
554 if i < current_idx {
555 before = true;
556 } else {
557 after = true;
558 }
559 }
560 }
561 i += 1;
562 }
563
564 if before && after {
565 return Err(ProgramError::InvalidAccountData);
566 }
567 Ok(())
568}
569
570#[inline]
574pub fn check_no_subsequent_invocation(sysvar_data: &[u8], our_program: &Address) -> ProgramResult {
575 let current_idx = current_instruction_index(sysvar_data)?;
576 let num_ix = instruction_count(sysvar_data)?;
577
578 let mut i = current_idx + 1;
579 while i < num_ix {
580 if let Ok(pid) = read_program_id_at(sysvar_data, i) {
581 if pid == *our_program.as_array() {
582 return Err(ProgramError::InvalidAccountData);
583 }
584 }
585 i += 1;
586 }
587 Ok(())
588}