use crate::account_view::AccountView;
use crate::address::{address_eq, Address};
use crate::error::ProgramError;
use crate::instruction::{
preflight_cpi_accounts, CpiAccount, InstructionAccount, InstructionView, Signer,
};
use crate::ProgramResult;
use core::mem::MaybeUninit;
#[cfg(all(test, not(target_os = "solana")))]
static LAST_HOST_ACCOUNT_INFOS_LEN: core::sync::atomic::AtomicUsize =
core::sync::atomic::AtomicUsize::new(usize::MAX);
pub const MAX_STATIC_CPI_ACCOUNTS: usize = 64;
pub const MAX_CPI_ACCOUNTS: usize = 255;
pub const MAX_RETURN_DATA: usize = 1024;
#[cfg(any(target_os = "solana", test))]
#[repr(C)]
struct CInstruction {
program_id: *const Address,
accounts: *const u8,
accounts_len: u64,
data: *const u8,
data_len: u64,
}
#[cfg(any(target_os = "solana", test))]
impl CInstruction {
#[inline(always)]
fn from_view(instruction: &InstructionView<'_, '_, '_, '_>) -> Self {
Self {
program_id: instruction.program_id as *const Address,
accounts: instruction.accounts.as_ptr() as *const u8,
accounts_len: instruction.accounts.len() as u64,
data: instruction.data.as_ptr(),
data_len: instruction.data.len() as u64,
}
}
}
#[cfg(any(target_os = "solana", test))]
const _: () = {
assert!(core::mem::size_of::<CInstruction>() == 40);
assert!(core::mem::align_of::<CInstruction>() == 8);
assert!(core::mem::offset_of!(CInstruction, program_id) == 0);
assert!(core::mem::offset_of!(CInstruction, accounts) == 8);
assert!(core::mem::offset_of!(CInstruction, accounts_len) == 16);
assert!(core::mem::offset_of!(CInstruction, data) == 24);
assert!(core::mem::offset_of!(CInstruction, data_len) == 32);
};
#[inline(always)]
fn specialized_instruction_accounts<'a, const ACCOUNTS: usize>(
accounts: &[CpiAccount<'a>; ACCOUNTS],
writable_mask: usize,
signer_mask: usize,
) -> [InstructionAccount<'a>; ACCOUNTS] {
core::array::from_fn(|index| {
accounts[index].instruction_account(
writable_mask & (1usize << index) != 0,
signer_mask & (1usize << index) != 0,
)
})
}
#[inline]
pub(crate) fn invoke_specialized_signed<'a, const ACCOUNTS: usize>(
program_id: &Address,
data: &[u8],
accounts: &[CpiAccount<'a>; ACCOUNTS],
writable_mask: usize,
signer_mask: usize,
signers_seeds: &[Signer<'_, '_>],
) -> ProgramResult {
preflight_cpi_accounts(
accounts,
writable_mask,
signer_mask,
!signers_seeds.is_empty(),
)?;
let instruction_accounts =
specialized_instruction_accounts(accounts, writable_mask, signer_mask);
let instruction = InstructionView {
program_id,
data,
accounts: &instruction_accounts,
};
unsafe { invoke_signed_unchecked(&instruction, accounts, signers_seeds) }
}
#[inline]
pub unsafe fn invoke_unchecked(
instruction: &InstructionView<'_, '_, '_, '_>,
accounts: &[CpiAccount<'_>],
) -> ProgramResult {
#[cfg(target_os = "solana")]
{
let c_instruction = CInstruction::from_view(instruction);
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
let result = unsafe {
crate::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"))]
{
#[cfg(test)]
LAST_HOST_ACCOUNT_INFOS_LEN.store(accounts.len(), core::sync::atomic::Ordering::SeqCst);
let _ = (instruction, accounts);
Ok(())
}
}
#[inline]
pub unsafe fn invoke_signed_unchecked(
instruction: &InstructionView<'_, '_, '_, '_>,
accounts: &[CpiAccount<'_>],
signers_seeds: &[Signer<'_, '_>],
) -> ProgramResult {
#[cfg(target_os = "solana")]
{
let c_instruction = CInstruction::from_view(instruction);
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
let result = unsafe {
crate::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"))]
{
#[cfg(test)]
LAST_HOST_ACCOUNT_INFOS_LEN.store(accounts.len(), core::sync::atomic::Ordering::SeqCst);
let _ = (instruction, accounts, signers_seeds);
Ok(())
}
}
#[inline]
pub fn invoke<const ACCOUNTS: usize>(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>; ACCOUNTS],
) -> ProgramResult {
invoke_signed::<ACCOUNTS>(instruction, account_views, &[])
}
#[inline]
pub fn invoke_signed<const ACCOUNTS: usize>(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>; ACCOUNTS],
signers_seeds: &[Signer<'_, '_>],
) -> ProgramResult {
let metas_len = instruction.accounts.len();
if ACCOUNTS < metas_len {
return Err(ProgramError::NotEnoughAccountKeys);
}
let mut cpi_accounts: [MaybeUninit<CpiAccount<'_>>; ACCOUNTS] =
unsafe { MaybeUninit::uninit().assume_init() };
let mut i = 0;
while i < metas_len {
let actual = account_views[i];
let expected = &instruction.accounts[i];
if !address_eq(actual.address(), expected.address) {
return Err(ProgramError::InvalidAccountData);
}
if expected.is_signer && !actual.is_signer() && signers_seeds.is_empty() {
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()?;
}
cpi_accounts[i] = MaybeUninit::new(CpiAccount::from(actual));
i += 1;
}
let accounts = unsafe {
core::slice::from_raw_parts(cpi_accounts.as_ptr() as *const CpiAccount<'_>, metas_len)
};
unsafe {
if signers_seeds.is_empty() {
invoke_unchecked(instruction, accounts)
} else {
invoke_signed_unchecked(instruction, accounts, signers_seeds)
}
}
}
#[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, &[])
}
#[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);
}
let metas_len = instruction.accounts.len();
let count = account_views.len();
if count < metas_len {
return Err(ProgramError::NotEnoughAccountKeys);
}
let mut cpi_accounts: [MaybeUninit<CpiAccount<'_>>; MAX_ACCOUNTS] =
unsafe { MaybeUninit::uninit().assume_init() };
let mut i = 0;
while i < metas_len {
let actual = account_views[i];
let expected = &instruction.accounts[i];
if !address_eq(actual.address(), expected.address) {
return Err(ProgramError::InvalidAccountData);
}
if expected.is_signer && !actual.is_signer() && signers_seeds.is_empty() {
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()?;
}
cpi_accounts[i] = MaybeUninit::new(CpiAccount::from(actual));
i += 1;
}
let accounts = unsafe {
core::slice::from_raw_parts(cpi_accounts.as_ptr() as *const CpiAccount<'_>, metas_len)
};
unsafe {
if signers_seeds.is_empty() {
invoke_unchecked(instruction, accounts)
} else {
invoke_signed_unchecked(instruction, accounts, signers_seeds)
}
}
}
#[inline(always)]
pub fn set_return_data(data: &[u8]) {
#[cfg(target_os = "solana")]
unsafe {
crate::syscalls::sol_set_return_data(data.as_ptr(), data.len() as u64);
}
#[cfg(not(target_os = "solana"))]
{
let _ = data;
}
}
#[cfg(test)]
mod abi_tests {
use super::*;
use crate::instruction::Seed;
use crate::{RuntimeAccount, NOT_BORROWED};
#[repr(C)]
struct Backing {
header: RuntimeAccount,
data: [u8; 8],
}
fn backing(tag: u8) -> Backing {
Backing {
header: RuntimeAccount {
borrow_state: NOT_BORROWED,
is_signer: 0,
is_writable: 1,
executable: 0,
resize_delta: 8,
address: Address::new_from_array([tag; 32]),
owner: Address::new_from_array([0xA5; 32]),
lamports: 5,
data_len: 8,
},
data: [tag; 8],
}
}
#[test]
fn c_instruction_and_specialized_meta_encoding_match_the_c_abi() {
let program_id = Address::new_from_array([9; 32]);
let key = Address::new_from_array([3; 32]);
let data = [1, 2, 3];
let metas = [InstructionAccount::new(&key, true, false)];
let view = InstructionView {
program_id: &program_id,
data: &data,
accounts: &metas,
};
let c = CInstruction::from_view(&view);
assert_eq!(c.program_id, &program_id as *const Address);
assert_eq!(c.accounts, metas.as_ptr() as *const u8);
assert_eq!(c.accounts_len, 1);
assert_eq!(c.data, data.as_ptr());
assert_eq!(c.data_len, 3);
let mut first_backing = backing(1);
let mut second_backing = backing(2);
let mut third_backing = backing(3);
let first = unsafe { AccountView::new_unchecked(&mut first_backing.header) };
let second = unsafe { AccountView::new_unchecked(&mut second_backing.header) };
let third = unsafe { AccountView::new_unchecked(&mut third_backing.header) };
let infos = [
CpiAccount::from(&first),
CpiAccount::from(&second),
CpiAccount::from(&third),
];
let encoded = specialized_instruction_accounts(&infos, 0b011, 0b100);
assert!(encoded[0].is_writable);
assert!(encoded[1].is_writable);
assert!(!encoded[2].is_writable);
assert!(!encoded[0].is_signer);
assert!(!encoded[1].is_signer);
assert!(encoded[2].is_signer);
assert_eq!(encoded[0].address, first.address());
assert_eq!(encoded[1].address, second.address());
assert_eq!(encoded[2].address, third.address());
}
#[test]
fn checked_paths_accept_pda_seeds_and_forward_only_instruction_metas() {
let mut signer_backing = backing(4);
let mut extra_backing = backing(5);
let signer_view = unsafe { AccountView::new_unchecked(&mut signer_backing.header) };
let extra_view = unsafe { AccountView::new_unchecked(&mut extra_backing.header) };
let metas = [InstructionAccount::readonly_signer(signer_view.address())];
let program_id = Address::new_from_array([6; 32]);
let instruction = InstructionView {
program_id: &program_id,
data: &[],
accounts: &metas,
};
LAST_HOST_ACCOUNT_INFOS_LEN.store(usize::MAX, core::sync::atomic::Ordering::SeqCst);
assert_eq!(
invoke::<1>(&instruction, &[&signer_view]),
Err(ProgramError::MissingRequiredSignature)
);
assert_eq!(
LAST_HOST_ACCOUNT_INFOS_LEN.load(core::sync::atomic::Ordering::SeqCst),
usize::MAX,
"unsigned signer failure must happen before invoke"
);
let seed = Seed::from(&b"pda"[..]);
let signer_seeds = [seed];
let signers = [Signer::from(&signer_seeds)];
invoke_signed::<1>(&instruction, &[&signer_view], &signers).unwrap();
assert_eq!(
LAST_HOST_ACCOUNT_INFOS_LEN.load(core::sync::atomic::Ordering::SeqCst),
1
);
let caller_views = [&signer_view, &extra_view];
invoke_signed::<2>(&instruction, &caller_views, &signers).unwrap();
assert_eq!(
LAST_HOST_ACCOUNT_INFOS_LEN.load(core::sync::atomic::Ordering::SeqCst),
1,
"fixed path must not forward caller views absent from metas"
);
invoke_signed_with_bounds::<2>(&instruction, &caller_views, &signers).unwrap();
assert_eq!(
LAST_HOST_ACCOUNT_INFOS_LEN.load(core::sync::atomic::Ordering::SeqCst),
1,
"bounded path must not forward caller views absent from metas"
);
}
}