pub mod fast;
#[cfg(feature = "graph")]
pub mod graph;
pub mod guards;
pub mod modifier;
pub mod trust;
use hopper_runtime::{
address::address_eq, error::ProgramError, AccountView, Address, ProgramResult,
};
#[inline(always)]
pub fn check_signer(account: &AccountView) -> ProgramResult {
if !account.is_signer() {
return Err(ProgramError::MissingRequiredSignature);
}
Ok(())
}
#[inline(always)]
pub fn check_writable(account: &AccountView) -> ProgramResult {
if !account.is_writable() {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
#[inline(always)]
pub fn check_owner(account: &AccountView, expected: &Address) -> ProgramResult {
if !account.owned_by(expected) {
return Err(ProgramError::IncorrectProgramId);
}
Ok(())
}
#[inline(always)]
pub fn check_executable(account: &AccountView) -> ProgramResult {
if !account.executable() {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
#[inline(always)]
pub fn check_program(account: &AccountView, expected_program_id: &Address) -> ProgramResult {
if !address_eq(account.address(), expected_program_id) {
return Err(ProgramError::IncorrectProgramId);
}
if !account.executable() {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
#[inline(always)]
pub fn check_size(data: &[u8], min_len: usize) -> ProgramResult {
if data.len() < min_len {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(())
}
#[inline(always)]
pub fn check_discriminator(data: &[u8], expected: u8) -> ProgramResult {
if data.is_empty() || data[0] != expected {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
#[inline(always)]
pub fn check_uninitialized(account: &AccountView) -> ProgramResult {
if !account.is_data_empty() {
return Err(ProgramError::AccountAlreadyInitialized);
}
Ok(())
}
#[inline(always)]
pub fn check_not_closed(data: &[u8]) -> ProgramResult {
if !data.is_empty() && data[0] == crate::account::CLOSE_SENTINEL {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
#[inline(always)]
pub fn rent_exempt_min(data_len: usize) -> u64 {
((128 + data_len) as u64) * 6960
}
#[inline(always)]
pub fn check_rent_exempt(account: &AccountView) -> ProgramResult {
let lamports = account.lamports();
let data = account.try_borrow()?;
let min = rent_exempt_min(data.len());
if lamports < min {
return Err(ProgramError::InsufficientFunds);
}
Ok(())
}
#[inline(always)]
pub fn check_lamports_gte(account: &AccountView, min: u64) -> ProgramResult {
let lamports = account.lamports();
if lamports < min {
return Err(ProgramError::InsufficientFunds);
}
Ok(())
}
#[inline(always)]
pub fn check_keys_eq(a: &AccountView, b: &AccountView) -> ProgramResult {
if !address_eq(a.address(), b.address()) {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
#[inline(always)]
pub fn keys_eq_fast(a: &[u8; 32], b: &[u8; 32]) -> bool {
unsafe {
let a_ptr = a.as_ptr() as *const u64;
let b_ptr = b.as_ptr() as *const u64;
core::ptr::read_unaligned(a_ptr) == core::ptr::read_unaligned(b_ptr)
&& core::ptr::read_unaligned(a_ptr.add(1)) == core::ptr::read_unaligned(b_ptr.add(1))
&& core::ptr::read_unaligned(a_ptr.add(2)) == core::ptr::read_unaligned(b_ptr.add(2))
&& core::ptr::read_unaligned(a_ptr.add(3)) == core::ptr::read_unaligned(b_ptr.add(3))
}
}
#[inline(always)]
pub fn is_zero_address(addr: &[u8; 32]) -> bool {
unsafe {
let ptr = addr.as_ptr() as *const u64;
let combined = core::ptr::read_unaligned(ptr)
| core::ptr::read_unaligned(ptr.add(1))
| core::ptr::read_unaligned(ptr.add(2))
| core::ptr::read_unaligned(ptr.add(3));
combined == 0
}
}
#[inline(always)]
pub fn check_has_one(stored: &[u8; 32], account: &AccountView) -> ProgramResult {
let addr: &[u8; 32] = unsafe { &*(account.address() as *const Address as *const [u8; 32]) };
if !keys_eq_fast(stored, addr) {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
#[inline(always)]
pub fn check_accounts_unique(a: &AccountView, b: &AccountView) -> ProgramResult {
if address_eq(a.address(), b.address()) {
return Err(ProgramError::InvalidArgument);
}
Ok(())
}
#[inline(always)]
pub fn check_accounts_unique_3(a: &AccountView, b: &AccountView, c: &AccountView) -> ProgramResult {
if address_eq(a.address(), b.address())
|| address_eq(a.address(), c.address())
|| address_eq(b.address(), c.address())
{
return Err(ProgramError::InvalidArgument);
}
Ok(())
}
#[inline(always)]
pub fn check_address(account: &AccountView, expected: &Address) -> ProgramResult {
if !address_eq(account.address(), expected) {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
#[inline(always)]
pub fn check_instruction_data_min(data: &[u8], min: usize) -> ProgramResult {
if data.len() < min {
return Err(ProgramError::InvalidInstructionData);
}
Ok(())
}
#[inline(always)]
pub fn check_account(
account: &AccountView,
program_id: &Address,
disc: u8,
min_size: usize,
) -> ProgramResult {
check_owner(account, program_id)?;
let data = account.try_borrow()?;
check_size(&data, min_size)?;
check_discriminator(&data, disc)?;
Ok(())
}
#[inline(always)]
pub fn check_system_program(account: &AccountView) -> ProgramResult {
const SYSTEM_PROGRAM: Address = Address::new_from_array([0; 32]);
if *account.address() != SYSTEM_PROGRAM {
return Err(ProgramError::IncorrectProgramId);
}
Ok(())
}
#[inline(always)]
pub fn verify_pda(
account: &AccountView,
seeds: &[&[u8]],
bump: u8,
program_id: &Address,
) -> ProgramResult {
hopper_runtime::pda::verify_pda_with_bump(account, seeds, bump, program_id)
}
#[inline(always)]
pub fn find_and_verify_pda(
account: &AccountView,
seeds: &[&[u8]],
program_id: &Address,
) -> Result<u8, ProgramError> {
hopper_runtime::pda::find_and_verify_pda(account, seeds, program_id)
}
#[inline(always)]
pub fn verify_pda_cached(
account: &AccountView,
seeds: &[&[u8]],
bump_offset: usize,
program_id: &Address,
) -> ProgramResult {
#[cfg(target_os = "solana")]
{
let data = account.try_borrow()?;
if bump_offset >= data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
let bump = data[bump_offset];
let bump_seed = [bump];
let mut all_seeds: [&[u8]; 17] = [&[]; 17];
let seed_count = seeds.len();
if seed_count > 16 {
return Err(ProgramError::InvalidSeeds);
}
let mut i = 0;
while i < seed_count {
all_seeds[i] = seeds[i];
i += 1;
}
all_seeds[seed_count] = &bump_seed;
let derived = Address::create_program_address(&all_seeds[..seed_count + 1], program_id)?;
if !address_eq(account.address(), &derived) {
return Err(ProgramError::InvalidSeeds);
}
Ok(())
}
#[cfg(not(target_os = "solana"))]
{
let _ = (account, seeds, bump_offset, program_id);
Err(ProgramError::InvalidSeeds)
}
}
#[inline]
pub fn check_owner_multi(
account: &AccountView,
owners: &[&Address],
) -> Result<usize, ProgramError> {
let acct_owner = unsafe { account.owner() };
for (i, expected) in owners.iter().enumerate() {
if acct_owner == *expected {
return Ok(i);
}
}
Err(ProgramError::IncorrectProgramId)
}
#[allow(dead_code)]
const INSTRUCTIONS_SYSVAR: Address = {
let mut addr = [0u8; 32];
addr[0] = 0x06;
addr[1] = 0xa7;
addr[2] = 0xd5;
addr[3] = 0x17;
addr[4] = 0x18;
addr[5] = 0x7b;
addr[6] = 0xd1;
addr[7] = 0x66;
addr[8] = 0x35;
addr[9] = 0xda;
addr[10] = 0xd4;
addr[11] = 0x04;
addr[12] = 0x55;
addr[13] = 0xfb;
addr[14] = 0x04;
addr[15] = 0x6e;
addr[16] = 0x12;
addr[17] = 0x46;
addr[18] = 0x00;
addr[19] = 0x00;
addr[20] = 0x00;
addr[21] = 0x00;
addr[22] = 0x00;
addr[23] = 0x00;
addr[24] = 0x00;
addr[25] = 0x00;
addr[26] = 0x00;
addr[27] = 0x00;
addr[28] = 0x00;
addr[29] = 0x00;
addr[30] = 0x00;
addr[31] = 0x00;
Address::new_from_array(addr)
};
#[inline(always)]
pub fn instruction_count(sysvar_data: &[u8]) -> Result<u16, ProgramError> {
if sysvar_data.len() < 2 {
return Err(ProgramError::InvalidAccountData);
}
Ok(u16::from_le_bytes([sysvar_data[0], sysvar_data[1]]))
}
#[inline(always)]
pub fn current_instruction_index(sysvar_data: &[u8]) -> Result<u16, ProgramError> {
let len = sysvar_data.len();
if len < 2 {
return Err(ProgramError::InvalidAccountData);
}
Ok(u16::from_le_bytes([
sysvar_data[len - 2],
sysvar_data[len - 1],
]))
}
#[inline]
pub fn read_program_id_at(sysvar_data: &[u8], index: u16) -> Result<[u8; 32], ProgramError> {
let num_ix = instruction_count(sysvar_data)?;
if index >= num_ix {
return Err(ProgramError::InvalidArgument);
}
let offset_entry = 2 + (index as usize) * 2;
if offset_entry + 2 > sysvar_data.len() {
return Err(ProgramError::InvalidAccountData);
}
let ix_offset =
u16::from_le_bytes([sysvar_data[offset_entry], sysvar_data[offset_entry + 1]]) as usize;
if ix_offset + 2 > sysvar_data.len() {
return Err(ProgramError::InvalidAccountData);
}
let num_accounts =
u16::from_le_bytes([sysvar_data[ix_offset], sysvar_data[ix_offset + 1]]) as usize;
let program_id_offset = ix_offset + 2 + num_accounts * 33;
if program_id_offset + 32 > sysvar_data.len() {
return Err(ProgramError::InvalidAccountData);
}
let mut pid = [0u8; 32];
pid.copy_from_slice(&sysvar_data[program_id_offset..program_id_offset + 32]);
Ok(pid)
}
#[inline]
pub fn require_top_level(sysvar_data: &[u8], our_program: &Address) -> ProgramResult {
let current_idx = current_instruction_index(sysvar_data)?;
let pid = read_program_id_at(sysvar_data, current_idx)?;
if pid != *our_program.as_array() {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
#[inline]
pub fn detect_flash_loan_bracket(sysvar_data: &[u8], our_program: &Address) -> ProgramResult {
let current_idx = current_instruction_index(sysvar_data)?;
let num_ix = instruction_count(sysvar_data)?;
let mut before = false;
let mut after = false;
let mut i: u16 = 0;
while i < num_ix {
if i == current_idx {
i += 1;
continue;
}
if let Ok(pid) = read_program_id_at(sysvar_data, i) {
if pid == *our_program.as_array() {
if i < current_idx {
before = true;
} else {
after = true;
}
}
}
i += 1;
}
if before && after {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
#[inline]
pub fn check_no_subsequent_invocation(sysvar_data: &[u8], our_program: &Address) -> ProgramResult {
let current_idx = current_instruction_index(sysvar_data)?;
let num_ix = instruction_count(sysvar_data)?;
let mut i = current_idx + 1;
while i < num_ix {
if let Ok(pid) = read_program_id_at(sysvar_data, i) {
if pid == *our_program.as_array() {
return Err(ProgramError::InvalidAccountData);
}
}
i += 1;
}
Ok(())
}