pub mod pda;
use hopper_runtime::{ProgramError, AccountView, Address, ProgramResult};
#[cfg(feature = "programs")]
use crate::programs;
const SYSTEM_PROGRAM_ID: Address = Address::new_from_array([0u8; 32]);
#[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::InvalidArgument);
}
Ok(())
}
#[inline(always)]
pub fn check_owner(account: &AccountView, program_id: &Address) -> ProgramResult {
if !account.owned_by(program_id) {
return Err(ProgramError::IncorrectProgramId);
}
Ok(())
}
#[inline(always)]
pub fn check_pda(account: &AccountView, expected: &Address) -> ProgramResult {
if *account.address() != *expected {
return Err(ProgramError::InvalidSeeds);
}
Ok(())
}
#[inline(always)]
pub fn check_system_program(account: &AccountView) -> ProgramResult {
if *account.address() != SYSTEM_PROGRAM_ID {
return Err(ProgramError::IncorrectProgramId);
}
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_executable(account: &AccountView) -> ProgramResult {
if !account.executable() {
return Err(ProgramError::IncorrectProgramId);
}
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_account(
account: &AccountView,
program_id: &Address,
discriminator: u8,
min_len: usize,
) -> ProgramResult {
check_owner(account, program_id)?;
let data = account.try_borrow()?;
check_size(&data, min_len)?;
check_discriminator(&data, discriminator)?;
Ok(())
}
#[inline(always)]
pub fn check_version(data: &[u8], min_version: u8) -> ProgramResult {
if data.len() < 2 {
return Err(ProgramError::AccountDataTooSmall);
}
if data[1] < min_version {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
#[inline(always)]
pub fn check_keys_eq(a: &Address, b: &Address) -> ProgramResult {
if *a != *b {
return Err(ProgramError::InvalidArgument);
}
Ok(())
}
#[inline(always)]
pub fn check_has_one(stored: &Address, account: &AccountView) -> ProgramResult {
if stored != account.address() {
return Err(ProgramError::InvalidArgument);
}
Ok(())
}
#[inline(always)]
pub fn rent_exempt_min(data_len: usize) -> u64 {
128u64
.checked_add(data_len as u64)
.and_then(|n| n.checked_mul(6960))
.unwrap_or(u64::MAX)
}
#[inline(always)]
pub fn check_rent_exempt(account: &AccountView) -> ProgramResult {
let data = account.try_borrow()?;
let min = rent_exempt_min(data.len());
drop(data);
if account.lamports() < min {
return Err(ProgramError::InsufficientFunds);
}
Ok(())
}
#[inline(always)]
pub fn check_lamports_gte(account: &AccountView, min_lamports: u64) -> ProgramResult {
if account.lamports() < min_lamports {
return Err(ProgramError::InsufficientFunds);
}
Ok(())
}
#[inline(always)]
pub fn check_closed(account: &AccountView) -> ProgramResult {
if account.lamports() != 0 || !account.is_data_empty() {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
#[inline(always)]
pub fn check_instruction_data_len(data: &[u8], expected_len: usize) -> ProgramResult {
if data.len() != expected_len {
return Err(ProgramError::InvalidInstructionData);
}
Ok(())
}
#[inline(always)]
pub fn check_instruction_data_min(data: &[u8], min_len: usize) -> ProgramResult {
if data.len() < min_len {
return Err(ProgramError::InvalidInstructionData);
}
Ok(())
}
#[inline(always)]
pub fn check_accounts_unique_2(a: &AccountView, b: &AccountView) -> ProgramResult {
if 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 a.address() == b.address()
|| a.address() == c.address()
|| b.address() == c.address()
{
return Err(ProgramError::InvalidArgument);
}
Ok(())
}
#[inline(always)]
pub fn check_accounts_unique_4(
a: &AccountView,
b: &AccountView,
c: &AccountView,
d: &AccountView,
) -> ProgramResult {
if a.address() == b.address()
|| a.address() == c.address()
|| a.address() == d.address()
|| b.address() == c.address()
|| b.address() == d.address()
|| c.address() == d.address()
{
return Err(ProgramError::InvalidArgument);
}
Ok(())
}
#[inline(always)]
pub fn assert_pda(
account: &AccountView,
seeds: &[&[u8]],
program_id: &Address,
) -> Result<u8, ProgramError> {
#[cfg(target_os = "solana")]
{
let (derived, bump) = Address::find_program_address(seeds, program_id);
if derived != *account.address() {
return Err(ProgramError::InvalidSeeds);
}
Ok(bump)
}
#[cfg(not(target_os = "solana"))]
{
let _ = (account, seeds, program_id);
Err(ProgramError::InvalidSeeds)
}
}
#[inline(always)]
pub fn assert_pda_with_bump(
account: &AccountView,
seeds: &[&[u8]],
bump: u8,
program_id: &Address,
) -> ProgramResult {
#[cfg(target_os = "solana")]
{
let bump_bytes = [bump];
let n = seeds.len();
let mut all_seeds: [&[u8]; 17] = [&[]; 17];
let mut i = 0;
while i < n {
all_seeds[i] = seeds[i];
i += 1;
}
all_seeds[n] = &bump_bytes;
let derived = Address::create_program_address(&all_seeds[..n + 1], program_id)
.map_err(|_| ProgramError::InvalidSeeds)?;
if derived != *account.address() {
return Err(ProgramError::InvalidSeeds);
}
Ok(())
}
#[cfg(not(target_os = "solana"))]
{
let _ = (account, seeds, bump, program_id);
Err(ProgramError::InvalidSeeds)
}
}
#[inline(always)]
pub fn assert_pda_external(
account: &AccountView,
seeds: &[&[u8]],
program_id: &Address,
) -> Result<u8, ProgramError> {
assert_pda(account, seeds, program_id)
}
#[cfg(feature = "programs")]
#[inline(always)]
pub fn assert_token_program(account: &AccountView) -> ProgramResult {
if *account.address() != programs::TOKEN && *account.address() != programs::TOKEN_2022 {
return Err(ProgramError::IncorrectProgramId);
}
Ok(())
}
#[inline(always)]
pub fn assert_address(account: &AccountView, expected: &Address) -> ProgramResult {
if *account.address() != *expected {
return Err(ProgramError::InvalidArgument);
}
Ok(())
}
#[inline(always)]
pub fn assert_program(account: &AccountView, expected_program: &Address) -> ProgramResult {
if *account.address() != *expected_program {
return Err(ProgramError::IncorrectProgramId);
}
if !account.executable() {
return Err(ProgramError::IncorrectProgramId);
}
Ok(())
}
#[inline(always)]
pub fn assert_not_initialized(account: &AccountView) -> ProgramResult {
if account.lamports() != 0 {
return Err(ProgramError::AccountAlreadyInitialized);
}
Ok(())
}
#[inline(always)]
pub fn check_program_allowed(
account: &AccountView,
allowed: &[Address],
) -> ProgramResult {
let mut i = 0;
while i < allowed.len() {
if account.owned_by(&allowed[i]) {
return Ok(());
}
i += 1;
}
Err(ProgramError::IncorrectProgramId)
}