use crate::address::{address_eq, Address};
use crate::error::ProgramError;
use crate::ProgramResult;
use crate::instruction::InstructionView;
use crate::account::AccountView;
#[cfg(all(feature = "hopper-native-backend", target_os = "solana"))]
use crate::instruction::InstructionAccount;
pub use crate::instruction::{Signer, Seed};
pub const MAX_STATIC_CPI_ACCOUNTS: usize = 64;
pub const MAX_CPI_ACCOUNTS: usize = 128;
pub const MAX_RETURN_DATA: usize = 1024;
#[cfg(feature = "hopper-native-backend")]
use crate::instruction::CpiAccount;
#[cfg(feature = "hopper-native-backend")]
use core::mem::MaybeUninit;
#[cfg(all(feature = "hopper-native-backend", target_os = "solana"))]
#[repr(C)]
struct CInstruction<'a> {
program_id: *const Address,
accounts: *const InstructionAccount<'a>,
accounts_len: u64,
data: *const u8,
data_len: u64,
}
#[cfg(feature = "hopper-native-backend")]
#[inline]
pub unsafe fn invoke_unchecked(
instruction: &InstructionView,
accounts: &[CpiAccount],
) -> ProgramResult {
#[cfg(target_os = "solana")]
{
let c_instruction = CInstruction {
program_id: instruction.program_id as *const Address,
accounts: instruction.accounts.as_ptr(),
accounts_len: instruction.accounts.len() as u64,
data: instruction.data.as_ptr(),
data_len: instruction.data.len() as u64,
};
let result = unsafe {
hopper_native::syscalls::sol_invoke_signed_c(
&c_instruction as *const _ as *const u8,
accounts.as_ptr() as *const u8,
accounts.len() as u64,
core::ptr::null(),
0,
)
};
if result == 0 { Ok(()) } else { Err(ProgramError::from(result)) }
}
#[cfg(not(target_os = "solana"))]
{
let _ = (instruction, accounts);
Ok(())
}
}
#[cfg(feature = "hopper-native-backend")]
#[inline]
pub unsafe fn invoke_signed_unchecked(
instruction: &InstructionView,
accounts: &[CpiAccount],
signers_seeds: &[Signer],
) -> ProgramResult {
#[cfg(target_os = "solana")]
{
let c_instruction = CInstruction {
program_id: instruction.program_id as *const Address,
accounts: instruction.accounts.as_ptr(),
accounts_len: instruction.accounts.len() as u64,
data: instruction.data.as_ptr(),
data_len: instruction.data.len() as u64,
};
let result = unsafe {
hopper_native::syscalls::sol_invoke_signed_c(
&c_instruction as *const _ as *const u8,
accounts.as_ptr() as *const u8,
accounts.len() as u64,
signers_seeds.as_ptr() as *const u8,
signers_seeds.len() as u64,
)
};
if result == 0 { Ok(()) } else { Err(ProgramError::from(result)) }
}
#[cfg(not(target_os = "solana"))]
{
let _ = (instruction, accounts, signers_seeds);
Ok(())
}
}
#[inline]
fn validate_no_duplicate_writable(
instruction: &InstructionView,
account_views: &[&AccountView],
) -> ProgramResult {
let mut i = 0;
while i < instruction.accounts.len() {
if instruction.accounts[i].is_writable {
let mut j = i + 1;
while j < instruction.accounts.len() {
if instruction.accounts[j].is_writable
&& address_eq(account_views[i].address(), account_views[j].address())
{
return Err(ProgramError::AccountBorrowFailed);
}
j += 1;
}
}
i += 1;
}
Ok(())
}
#[inline]
fn signer_matches_pda(program_id: &Address, account: &Address, signers_seeds: &[Signer]) -> bool {
let mut i = 0;
while i < signers_seeds.len() {
let signer = &signers_seeds[i];
let seeds = unsafe {
core::slice::from_raw_parts(signer.seeds, signer.len as usize)
};
if seeds.len() <= crate::address::MAX_SEEDS {
let mut seed_refs: [&[u8]; crate::address::MAX_SEEDS] = [&[]; crate::address::MAX_SEEDS];
let mut j = 0;
while j < seeds.len() {
seed_refs[j] = unsafe {
core::slice::from_raw_parts(seeds[j].seed, seeds[j].len as usize)
};
j += 1;
}
if let Ok(derived) = crate::compat::create_program_address(&seed_refs[..seeds.len()], program_id) {
if address_eq(&derived, account) {
return true;
}
}
}
i += 1;
}
false
}
#[inline]
fn validate_cpi_accounts(
instruction: &InstructionView,
account_views: &[&AccountView],
signers_seeds: &[Signer],
) -> ProgramResult {
if account_views.len() < instruction.accounts.len() {
return Err(ProgramError::NotEnoughAccountKeys);
}
let mut i = 0;
while i < instruction.accounts.len() {
let expected = &instruction.accounts[i];
let actual = account_views[i];
if !address_eq(actual.address(), expected.address) {
return Err(ProgramError::InvalidAccountData);
}
if expected.is_signer
&& !actual.is_signer()
&& !signer_matches_pda(instruction.program_id, actual.address(), signers_seeds)
{
return Err(ProgramError::MissingRequiredSignature);
}
if expected.is_writable && !actual.is_writable() {
return Err(ProgramError::Immutable);
}
if expected.is_writable {
actual.check_borrow_mut()?;
} else {
actual.check_borrow()?;
}
i += 1;
}
validate_no_duplicate_writable(instruction, account_views)?;
Ok(())
}
#[cfg(feature = "hopper-native-backend")]
#[inline]
pub fn invoke<const ACCOUNTS: usize>(
instruction: &InstructionView,
account_views: &[&AccountView; ACCOUNTS],
) -> ProgramResult {
invoke_signed::<ACCOUNTS>(instruction, account_views, &[])
}
#[cfg(feature = "hopper-native-backend")]
#[inline]
pub fn invoke_signed<const ACCOUNTS: usize>(
instruction: &InstructionView,
account_views: &[&AccountView; ACCOUNTS],
signers_seeds: &[Signer],
) -> ProgramResult {
validate_cpi_accounts(instruction, &account_views[..], signers_seeds)?;
let mut cpi_accounts: [MaybeUninit<CpiAccount>; ACCOUNTS] =
unsafe { MaybeUninit::uninit().assume_init() };
let mut i = 0;
while i < ACCOUNTS {
cpi_accounts[i] = MaybeUninit::new(CpiAccount::from(account_views[i]));
i += 1;
}
let accounts: &[CpiAccount; ACCOUNTS] = unsafe {
&*(cpi_accounts.as_ptr() as *const [CpiAccount; ACCOUNTS])
};
unsafe {
if signers_seeds.is_empty() {
invoke_unchecked(instruction, accounts.as_slice())
} else {
invoke_signed_unchecked(instruction, accounts.as_slice(), signers_seeds)
}
}
}
#[cfg(feature = "hopper-native-backend")]
#[inline]
pub fn invoke_with_bounds<const MAX_ACCOUNTS: usize>(
instruction: &InstructionView,
account_views: &[&AccountView],
) -> ProgramResult {
invoke_signed_with_bounds::<MAX_ACCOUNTS>(instruction, account_views, &[])
}
#[cfg(feature = "hopper-native-backend")]
#[inline]
pub fn invoke_signed_with_bounds<const MAX_ACCOUNTS: usize>(
instruction: &InstructionView,
account_views: &[&AccountView],
signers_seeds: &[Signer],
) -> ProgramResult {
if account_views.len() > MAX_ACCOUNTS {
return Err(ProgramError::InvalidArgument);
}
validate_cpi_accounts(instruction, account_views, signers_seeds)?;
let mut cpi_accounts: [MaybeUninit<CpiAccount>; MAX_ACCOUNTS] =
unsafe { MaybeUninit::uninit().assume_init() };
let count = account_views.len();
let mut i = 0;
while i < count {
cpi_accounts[i] = MaybeUninit::new(CpiAccount::from(account_views[i]));
i += 1;
}
let accounts = unsafe {
core::slice::from_raw_parts(cpi_accounts.as_ptr() as *const CpiAccount, count)
};
unsafe {
if signers_seeds.is_empty() {
invoke_unchecked(instruction, accounts)
} else {
invoke_signed_unchecked(instruction, accounts, signers_seeds)
}
}
}
#[cfg(any(feature = "legacy-pinocchio-compat", feature = "solana-program-backend"))]
#[inline]
pub fn invoke<const ACCOUNTS: usize>(
instruction: &InstructionView,
account_views: &[&AccountView; ACCOUNTS],
) -> ProgramResult {
invoke_signed::<ACCOUNTS>(instruction, account_views, &[])
}
#[cfg(any(feature = "legacy-pinocchio-compat", feature = "solana-program-backend"))]
#[inline]
pub fn invoke_signed<const ACCOUNTS: usize>(
instruction: &InstructionView,
account_views: &[&AccountView; ACCOUNTS],
signers_seeds: &[Signer],
) -> ProgramResult {
validate_cpi_accounts(instruction, &account_views[..], signers_seeds)?;
crate::compat::invoke_signed(instruction, account_views, signers_seeds)
}
#[inline(always)]
pub fn set_return_data(data: &[u8]) {
crate::compat::set_return_data(data)
}
#[cfg(all(test, feature = "hopper-native-backend"))]
mod tests {
use super::*;
use crate::InstructionAccount;
use hopper_native::{AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED};
fn make_account(address: [u8; 32]) -> (std::vec::Vec<u8>, AccountView) {
let mut backing = std::vec![0u8; RuntimeAccount::SIZE + 16];
let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
unsafe {
raw.write(RuntimeAccount {
borrow_state: NOT_BORROWED,
is_signer: 0,
is_writable: 1,
executable: 0,
resize_delta: 0,
address: NativeAddress::new_from_array(address),
owner: NativeAddress::new_from_array([9; 32]),
lamports: 1,
data_len: 16,
});
}
let backend = unsafe { NativeAccountView::new_unchecked(raw) };
(backing, AccountView::from_backend(backend))
}
#[test]
fn duplicate_writable_accounts_are_rejected_before_cpi() {
let (_first_backing, first) = make_account([3; 32]);
let (_second_backing, second) = make_account([3; 32]);
let instruction_accounts = [
InstructionAccount::writable(first.address()),
InstructionAccount::writable(second.address()),
];
let program_id = Address::new_from_array([7; 32]);
let instruction = InstructionView {
program_id: &program_id,
data: &[0u8],
accounts: &instruction_accounts,
};
let err = validate_no_duplicate_writable(&instruction, &[&first, &second]).unwrap_err();
assert_eq!(err, ProgramError::AccountBorrowFailed);
}
}