use crate::account::AccountView;
use crate::address::{address_eq, Address};
use crate::error::ProgramError;
use crate::instruction::{CpiAccount, InstructionView};
use crate::ProgramResult;
use core::mem::MaybeUninit;
#[cfg(target_os = "solana")]
use crate::instruction::InstructionAccount;
pub use crate::instruction::{Seed, Signer};
pub const MAX_STATIC_CPI_ACCOUNTS: usize = 64;
pub const MAX_CPI_ACCOUNTS: usize = 255;
pub const MAX_RETURN_DATA: usize = 1024;
#[cfg(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,
}
#[inline]
pub unsafe fn invoke_unchecked(
instruction: &InstructionView<'_, '_, '_, '_>,
accounts: &[CpiAccount<'_>],
) -> ProgramResult {
unsafe { invoke_signed_unchecked(instruction, accounts, &[]) }
}
#[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_authority_supplied(signers_seeds: &[Signer<'_, '_>]) -> bool {
!signers_seeds.is_empty()
}
#[inline]
#[cfg_attr(target_os = "solana", allow(dead_code))]
fn validate_cpi_borrows(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>],
) -> ProgramResult {
if account_views.len() < instruction.accounts.len() {
return Err(ProgramError::NotEnoughAccountKeys);
}
let mut i = 0;
while i < instruction.accounts.len() {
if !address_eq(account_views[i].address(), instruction.accounts[i].address) {
return Err(ProgramError::InvalidArgument);
}
if instruction.accounts[i].is_writable {
account_views[i].check_borrow_mut()?;
} else {
account_views[i].check_borrow()?;
}
i += 1;
}
if crate::write_policy::lamport_gate_active() {
let mut m = 0;
while m < instruction.accounts.len() {
if instruction.accounts[m].is_writable {
crate::write_policy::check_lamport_delegation(account_views[m].address())?;
}
m += 1;
}
}
Ok(())
}
#[cfg(not(target_os = "solana"))]
fn is_host_system_transfer(instruction: &InstructionView<'_, '_, '_, '_>) -> bool {
crate::address::address_is_zero(instruction.program_id)
&& instruction.data.len() == 12
&& instruction.data[0..4] == [2, 0, 0, 0]
}
#[cfg(not(target_os = "solana"))]
fn validate_host_system_transfer(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>],
signers_seeds: &[Signer<'_, '_>],
min_views: usize,
) -> ProgramResult {
if account_views.len() < instruction.accounts.len() || account_views.len() < min_views {
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_authority_supplied(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;
}
if crate::write_policy::lamport_gate_active() {
let mut m = 0;
while m < instruction.accounts.len() {
if instruction.accounts[m].is_writable {
crate::write_policy::check_lamport_delegation(account_views[m].address())?;
}
m += 1;
}
}
validate_no_duplicate_writable(instruction, account_views)
}
#[cfg(not(target_os = "solana"))]
fn emulate_host_system_transfer(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>],
) -> ProgramResult {
let amount = u64::from_le_bytes([
instruction.data[4],
instruction.data[5],
instruction.data[6],
instruction.data[7],
instruction.data[8],
instruction.data[9],
instruction.data[10],
instruction.data[11],
]);
let from = account_views[0];
let to = account_views[1];
crate::write_policy::check_lamport_mutation(from.address())?;
crate::write_policy::check_lamport_mutation(to.address())?;
if address_eq(from.address(), to.address()) {
if from.lamports() < amount {
return Err(ProgramError::InsufficientFunds);
}
return Ok(());
}
let debited = from
.lamports()
.checked_sub(amount)
.ok_or(ProgramError::InsufficientFunds)?;
let credited = to
.lamports()
.checked_add(amount)
.ok_or(ProgramError::ArithmeticOverflow)?;
from.set_lamports(debited)?;
to.set_lamports(credited)?;
Ok(())
}
#[cfg(not(target_os = "solana"))]
fn is_host_system_create_account(instruction: &InstructionView<'_, '_, '_, '_>) -> bool {
crate::address::address_is_zero(instruction.program_id)
&& instruction.data.len() == 52
&& instruction.data[0..4] == [0, 0, 0, 0]
}
#[cfg(not(target_os = "solana"))]
fn emulate_host_system_create_account(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>],
) -> ProgramResult {
let lamports = u64::from_le_bytes(instruction.data[4..12].try_into().unwrap());
let space = u64::from_le_bytes(instruction.data[12..20].try_into().unwrap()) as usize;
let mut owner_bytes = [0u8; 32];
owner_bytes.copy_from_slice(&instruction.data[20..52]);
let owner = Address::new_from_array(owner_bytes);
let from = account_views[0];
let to = account_views[1];
if to.lamports() != 0 || to.data_len() != 0 {
return Err(ProgramError::AccountAlreadyInitialized);
}
crate::write_policy::check_lamport_mutation(from.address())?;
crate::write_policy::check_lamport_mutation(to.address())?;
let debited = from
.lamports()
.checked_sub(lamports)
.ok_or(ProgramError::InsufficientFunds)?;
let credited = to
.lamports()
.checked_add(lamports)
.ok_or(ProgramError::ArithmeticOverflow)?;
from.set_lamports(debited)?;
to.set_lamports(credited)?;
to.resize(space)?;
unsafe {
to.assign(&owner);
}
Ok(())
}
#[cfg(not(target_os = "solana"))]
fn is_host_system_create_account_allow_prefund(
instruction: &InstructionView<'_, '_, '_, '_>,
) -> bool {
crate::address::address_is_zero(instruction.program_id)
&& instruction.data.len() == 52
&& instruction.data[0..4] == [13, 0, 0, 0]
}
#[cfg(not(target_os = "solana"))]
fn emulate_host_system_create_account_allow_prefund(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>],
) -> ProgramResult {
let lamports = u64::from_le_bytes(instruction.data[4..12].try_into().unwrap());
let space = u64::from_le_bytes(instruction.data[12..20].try_into().unwrap()) as usize;
let mut owner_bytes = [0u8; 32];
owner_bytes.copy_from_slice(&instruction.data[20..52]);
let owner = Address::new_from_array(owner_bytes);
let to = account_views[0];
let system_owned = crate::address::address_is_zero(unsafe { to.owner() });
if to.data_len() != 0 || !system_owned {
return Err(ProgramError::AccountAlreadyInitialized);
}
let funding = if lamports > 0 {
let from = *account_views
.get(1)
.ok_or(ProgramError::NotEnoughAccountKeys)?;
crate::write_policy::check_lamport_mutation(from.address())?;
crate::write_policy::check_lamport_mutation(to.address())?;
let debited = from
.lamports()
.checked_sub(lamports)
.ok_or(ProgramError::InsufficientFunds)?;
let credited = to
.lamports()
.checked_add(lamports)
.ok_or(ProgramError::ArithmeticOverflow)?;
Some((from, debited, credited))
} else {
None
};
to.resize(space)?;
unsafe {
to.assign(&owner);
}
if let Some((from, debited, credited)) = funding {
from.set_lamports(debited)?;
to.set_lamports(credited)?;
}
Ok(())
}
#[cfg(not(target_os = "solana"))]
fn is_host_system_allocate(instruction: &InstructionView<'_, '_, '_, '_>) -> bool {
crate::address::address_is_zero(instruction.program_id)
&& instruction.data.len() == 12
&& instruction.data[0..4] == [8, 0, 0, 0]
}
#[cfg(not(target_os = "solana"))]
fn emulate_host_system_allocate(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>],
) -> ProgramResult {
let space = u64::from_le_bytes(instruction.data[4..12].try_into().unwrap()) as usize;
let target = account_views[0];
if target.data_len() != 0 {
return Err(ProgramError::AccountAlreadyInitialized);
}
target.resize(space)
}
#[cfg(not(target_os = "solana"))]
fn is_host_system_assign(instruction: &InstructionView<'_, '_, '_, '_>) -> bool {
crate::address::address_is_zero(instruction.program_id)
&& instruction.data.len() == 36
&& instruction.data[0..4] == [1, 0, 0, 0]
}
#[cfg(not(target_os = "solana"))]
fn emulate_host_system_assign(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>],
) -> ProgramResult {
let mut owner_bytes = [0u8; 32];
owner_bytes.copy_from_slice(&instruction.data[4..36]);
let owner = Address::new_from_array(owner_bytes);
let target = account_views[0];
unsafe {
target.assign(&owner);
}
Ok(())
}
#[inline]
pub fn invoke<const ACCOUNTS: usize>(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>; ACCOUNTS],
) -> ProgramResult {
invoke_signed::<ACCOUNTS>(instruction, account_views, &[])
}
#[cfg(not(target_os = "solana"))]
#[inline]
fn emulate_host_system(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>],
signers_seeds: &[Signer<'_, '_>],
) -> Option<ProgramResult> {
if is_host_system_transfer(instruction) {
return Some(
validate_host_system_transfer(instruction, account_views, signers_seeds, 2)
.and_then(|()| emulate_host_system_transfer(instruction, account_views)),
);
}
if is_host_system_create_account(instruction) {
return Some(
validate_host_system_transfer(instruction, account_views, signers_seeds, 2)
.and_then(|()| emulate_host_system_create_account(instruction, account_views)),
);
}
if is_host_system_create_account_allow_prefund(instruction) {
return Some(
validate_host_system_transfer(instruction, account_views, signers_seeds, 1).and_then(
|()| emulate_host_system_create_account_allow_prefund(instruction, account_views),
),
);
}
if is_host_system_allocate(instruction) {
return Some(
validate_host_system_transfer(instruction, account_views, signers_seeds, 1)
.and_then(|()| emulate_host_system_allocate(instruction, account_views)),
);
}
if is_host_system_assign(instruction) {
return Some(
validate_host_system_transfer(instruction, account_views, signers_seeds, 1)
.and_then(|()| emulate_host_system_assign(instruction, account_views)),
);
}
None
}
#[inline]
pub fn invoke_signed<const ACCOUNTS: usize>(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>; ACCOUNTS],
signers_seeds: &[Signer<'_, '_>],
) -> ProgramResult {
#[cfg(not(target_os = "solana"))]
if let Some(result) = emulate_host_system(instruction, &account_views[..], signers_seeds) {
return result;
}
let metas_len = instruction.accounts.len();
dispatch_cpi_fixed::<ACCOUNTS>(
instruction,
account_views,
signers_seeds,
metas_len,
|i| {
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_authority_supplied(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()?;
}
Ok(())
},
|| {
if crate::write_policy::lamport_gate_active() {
let mut i = 0;
while i < metas_len {
if instruction.accounts[i].is_writable {
crate::write_policy::check_lamport_delegation(account_views[i].address())?;
}
i += 1;
}
}
validate_no_duplicate_writable(instruction, &account_views[..])
},
)
}
#[inline]
fn dispatch_cpi_fixed<const ACCOUNTS: usize>(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>; ACCOUNTS],
signers_seeds: &[Signer<'_, '_>],
metas_len: usize,
check_meta: impl Fn(usize) -> ProgramResult,
post_check: impl FnOnce() -> ProgramResult,
) -> ProgramResult {
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 < ACCOUNTS {
if i < metas_len {
check_meta(i)?;
}
cpi_accounts[i] = MaybeUninit::new(CpiAccount::from(account_views[i]));
i += 1;
}
post_check()?;
let accounts: &[CpiAccount<'_>; ACCOUNTS] =
unsafe { &*(cpi_accounts.as_ptr() as *const [CpiAccount<'_>; ACCOUNTS]) };
unsafe { invoke_signed_unchecked(instruction, accounts.as_slice(), 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);
}
#[cfg(not(target_os = "solana"))]
if let Some(result) = emulate_host_system(instruction, account_views, signers_seeds) {
return result;
}
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 < count {
let actual = account_views[i];
if i < metas_len {
let expected = &instruction.accounts[i];
if !address_eq(actual.address(), expected.address) {
return Err(ProgramError::InvalidAccountData);
}
if expected.is_signer
&& !actual.is_signer()
&& !signer_authority_supplied(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()?;
}
}
cpi_accounts[i] = MaybeUninit::new(CpiAccount::from(actual));
i += 1;
}
if crate::write_policy::lamport_gate_active() {
let mut m = 0;
while m < instruction.accounts.len() {
if instruction.accounts[m].is_writable {
crate::write_policy::check_lamport_delegation(account_views[m].address())?;
}
m += 1;
}
}
validate_no_duplicate_writable(instruction, account_views)?;
let accounts = unsafe {
core::slice::from_raw_parts(cpi_accounts.as_ptr() as *const CpiAccount<'_>, count)
};
unsafe { invoke_signed_unchecked(instruction, accounts, signers_seeds) }
}
#[inline]
fn find_info(infos: &[&AccountView<'_>], address: &Address) -> Option<usize> {
let mut i = 0;
while i < infos.len() {
if address_eq(infos[i].address(), address) {
return Some(i);
}
i += 1;
}
None
}
#[inline]
fn validate_cpi_accounts_deduped(
instruction: &InstructionView<'_, '_, '_, '_>,
infos: &[&AccountView<'_>],
signers_seeds: &[Signer<'_, '_>],
) -> 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(
instruction.accounts[i].address,
instruction.accounts[j].address,
)
{
return Err(ProgramError::AccountBorrowFailed);
}
j += 1;
}
}
i += 1;
}
let mut i = 0;
while i < instruction.accounts.len() {
let expected = &instruction.accounts[i];
let info = match find_info(infos, expected.address) {
Some(idx) => infos[idx],
None => return Err(ProgramError::NotEnoughAccountKeys),
};
if expected.is_signer && !info.is_signer() && !signer_authority_supplied(signers_seeds) {
return Err(ProgramError::MissingRequiredSignature);
}
if expected.is_writable && !info.is_writable() {
return Err(ProgramError::Immutable);
}
if expected.is_writable {
info.check_borrow_mut()?;
} else {
info.check_borrow()?;
}
i += 1;
}
if crate::write_policy::lamport_gate_active() {
let mut m = 0;
while m < instruction.accounts.len() {
let expected = &instruction.accounts[m];
if expected.is_writable {
if let Some(idx) = find_info(infos, expected.address) {
crate::write_policy::check_lamport_delegation(infos[idx].address())?;
}
}
m += 1;
}
}
Ok(())
}
#[inline]
pub fn invoke_signed_deduped<const MAX_INFOS: usize>(
instruction: &InstructionView<'_, '_, '_, '_>,
infos: &[&AccountView<'_>],
signers_seeds: &[Signer<'_, '_>],
) -> ProgramResult {
if infos.len() > MAX_INFOS {
return Err(ProgramError::InvalidArgument);
}
#[cfg(not(target_os = "solana"))]
if is_host_system_transfer(instruction) {
validate_cpi_accounts_deduped(instruction, infos, signers_seeds)?;
if instruction.accounts.len() < 2 {
return Err(ProgramError::NotEnoughAccountKeys);
}
let source = find_info(infos, instruction.accounts[0].address)
.ok_or(ProgramError::NotEnoughAccountKeys)?;
let destination = find_info(infos, instruction.accounts[1].address)
.ok_or(ProgramError::NotEnoughAccountKeys)?;
return emulate_host_system_transfer(instruction, &[infos[source], infos[destination]]);
}
validate_cpi_accounts_deduped(instruction, infos, signers_seeds)?;
let mut cpi_accounts: [MaybeUninit<CpiAccount<'_>>; MAX_INFOS] =
unsafe { MaybeUninit::uninit().assume_init() };
let count = infos.len();
let mut i = 0;
while i < count {
cpi_accounts[i] = MaybeUninit::new(CpiAccount::from(infos[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)
}
}
}
#[inline]
pub fn invoke_checked<const ACCOUNTS: usize>(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>; ACCOUNTS],
) -> ProgramResult {
invoke::<ACCOUNTS>(instruction, account_views)
}
#[inline]
pub fn invoke_signed_checked<const ACCOUNTS: usize>(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>; ACCOUNTS],
signers_seeds: &[Signer<'_, '_>],
) -> ProgramResult {
invoke_signed::<ACCOUNTS>(instruction, account_views, signers_seeds)
}
#[inline]
pub fn invoke_borrow_checked<const ACCOUNTS: usize>(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>; ACCOUNTS],
) -> ProgramResult {
invoke_signed_borrow_checked::<ACCOUNTS>(instruction, account_views, &[])
}
#[inline]
pub fn invoke_signed_borrow_checked<const ACCOUNTS: usize>(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>; ACCOUNTS],
signers_seeds: &[Signer<'_, '_>],
) -> ProgramResult {
#[cfg(not(target_os = "solana"))]
if is_host_system_transfer(instruction) {
if account_views.len() < 2 {
return Err(ProgramError::NotEnoughAccountKeys);
}
validate_cpi_borrows(instruction, &account_views[..])?;
return emulate_host_system_transfer(instruction, &account_views[..]);
}
let metas_len = instruction.accounts.len();
dispatch_cpi_fixed::<ACCOUNTS>(
instruction,
account_views,
signers_seeds,
metas_len,
|i| {
if !address_eq(account_views[i].address(), instruction.accounts[i].address) {
return Err(ProgramError::InvalidArgument);
}
if instruction.accounts[i].is_writable {
account_views[i].check_borrow_mut()?;
} else {
account_views[i].check_borrow()?;
}
Ok(())
},
|| {
if crate::write_policy::lamport_gate_active() {
let mut i = 0;
while i < metas_len {
if instruction.accounts[i].is_writable {
crate::write_policy::check_lamport_delegation(account_views[i].address())?;
}
i += 1;
}
}
Ok(())
},
)
}
#[inline(always)]
pub fn set_return_data(data: &[u8]) {
crate::return_data::set_return_data(data)
}
#[cfg(test)]
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<u64>, AccountView<'static>) {
let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + 16).div_ceil(8)];
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);
}
#[test]
fn borrow_checked_rejects_live_mutable_data_borrow() {
let (_backing, account) = make_account([21; 32]);
let metas = [InstructionAccount::writable(account.address())];
let program_id = Address::new_from_array([7; 32]);
let instruction = InstructionView {
program_id: &program_id,
data: &[0u8],
accounts: &metas,
};
let guard = account.try_borrow_mut().unwrap();
let err = invoke_borrow_checked::<1>(&instruction, &[&account]).unwrap_err();
assert_eq!(err, ProgramError::AccountBorrowFailed);
drop(guard);
}
#[test]
fn borrow_checked_succeeds_after_borrow_release() {
let (_backing, account) = make_account([22; 32]);
let metas = [InstructionAccount::writable(account.address())];
let program_id = Address::new_from_array([7; 32]);
let instruction = InstructionView {
program_id: &program_id,
data: &[0u8],
accounts: &metas,
};
let guard = account.try_borrow_mut().unwrap();
assert!(invoke_borrow_checked::<1>(&instruction, &[&account]).is_err());
drop(guard);
invoke_borrow_checked::<1>(&instruction, &[&account]).unwrap();
}
#[test]
fn borrow_checked_permits_duplicate_writable_metas_unlike_default_tier() {
let (_first_backing, first) = make_account([23; 32]);
let (_second_backing, second) = make_account([23; 32]);
let metas = [
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: &metas,
};
let err = invoke::<2>(&instruction, &[&first, &second]).unwrap_err();
assert_eq!(err, ProgramError::AccountBorrowFailed);
invoke_borrow_checked::<2>(&instruction, &[&first, &second]).unwrap();
}
#[test]
fn borrow_checked_offchain_noop_path_returns_ok() {
let (_backing, account) = make_account([24; 32]);
let metas = [InstructionAccount::readonly(account.address())];
let program_id = Address::new_from_array([7; 32]);
let instruction = InstructionView {
program_id: &program_id,
data: &[0u8],
accounts: &metas,
};
assert_eq!(
invoke_borrow_checked::<1>(&instruction, &[&account]),
Ok(())
);
assert_eq!(
invoke_signed_borrow_checked::<1>(&instruction, &[&account], &[]),
Ok(())
);
}
#[test]
#[cfg(not(feature = "unguarded-raw-surfaces"))]
fn writable_meta_is_refused_unless_both_dimensions_are_declared() {
use crate::write_policy::{
install_lamport_gate, write_policy_violation, WritePolicy, WriteRange,
};
let (_b0, delegable) = make_account([31; 32]);
let (_b1, lamports_only) = make_account([32; 32]);
let (_b2, undeclared) = make_account([33; 32]);
let accounts = [delegable, lamports_only, undeclared];
static P: WritePolicy =
WritePolicy::with_lamports(&[WriteRange::whole_account(0)], &[0, 1]);
let _gate = install_lamport_gate(&accounts, &P);
let program_id = Address::new_from_array([7; 32]);
let metas0 = [InstructionAccount::writable(accounts[0].address())];
let ix0 = InstructionView {
program_id: &program_id,
data: &[0u8],
accounts: &metas0,
};
invoke::<1>(&ix0, &[&accounts[0]]).unwrap();
invoke_borrow_checked::<1>(&ix0, &[&accounts[0]]).unwrap();
let metas1 = [InstructionAccount::writable(accounts[1].address())];
let ix1 = InstructionView {
program_id: &program_id,
data: &[0u8],
accounts: &metas1,
};
assert_eq!(
invoke::<1>(&ix1, &[&accounts[1]]).unwrap_err(),
write_policy_violation(1)
);
assert_eq!(
invoke_borrow_checked::<1>(&ix1, &[&accounts[1]]).unwrap_err(),
write_policy_violation(1)
);
let metas2 = [InstructionAccount::writable(accounts[2].address())];
let ix2 = InstructionView {
program_id: &program_id,
data: &[0u8],
accounts: &metas2,
};
assert_eq!(
invoke_signed_deduped::<1>(&ix2, &[&accounts[2]], &[]).unwrap_err(),
write_policy_violation(2)
);
let metas_ro = [InstructionAccount::readonly(accounts[2].address())];
let ix_ro = InstructionView {
program_id: &program_id,
data: &[0u8],
accounts: &metas_ro,
};
invoke::<1>(&ix_ro, &[&accounts[2]]).unwrap();
}
#[test]
#[cfg(not(feature = "unguarded-raw-surfaces"))]
fn host_system_transfer_is_gated_through_the_lamport_funnel() {
use crate::write_policy::{
install_lamport_gate, write_policy_violation, WritePolicy, WriteRange,
};
let (_b0, from) = make_account([41; 32]);
let (_b1, to) = make_account([42; 32]);
let accounts = [from, to];
static OPEN: WritePolicy = WritePolicy::with_lamports(
&[WriteRange::whole_account(0), WriteRange::whole_account(1)],
&[0, 1],
);
static HALF: WritePolicy =
WritePolicy::with_lamports(&[WriteRange::whole_account(0)], &[0]);
let system_id = Address::new_from_array([0; 32]);
let mut data = [0u8; 12];
data[0] = 2; data[4..12].copy_from_slice(&1u64.to_le_bytes());
let metas = [
InstructionAccount::writable(accounts[0].address()),
InstructionAccount::writable(accounts[1].address()),
];
let ix = InstructionView {
program_id: &system_id,
data: &data,
accounts: &metas,
};
{
let _gate = install_lamport_gate(&accounts, &OPEN);
invoke::<2>(&ix, &[&accounts[0], &accounts[1]]).unwrap();
assert_eq!(accounts[0].lamports(), 0);
assert_eq!(accounts[1].lamports(), 2);
}
{
let _gate = install_lamport_gate(&accounts, &HALF);
assert_eq!(
invoke::<2>(&ix, &[&accounts[0], &accounts[1]]).unwrap_err(),
write_policy_violation(1)
);
assert_eq!(accounts[0].lamports(), 0);
assert_eq!(accounts[1].lamports(), 2);
}
}
#[test]
#[cfg(not(feature = "unguarded-raw-surfaces"))]
fn host_system_transfer_refusal_leaves_both_balances_untouched() {
use crate::write_policy::{
install_lamport_gate, write_policy_violation, WritePolicy, WriteRange,
};
let (_b0, from) = make_account([43; 32]);
let (_b1, to) = make_account([44; 32]);
let accounts = [from, to];
static HALF: WritePolicy =
WritePolicy::with_lamports(&[WriteRange::whole_account(0)], &[0]);
let _gate = install_lamport_gate(&accounts, &HALF);
let system_id = Address::new_from_array([0; 32]);
let mut data = [0u8; 12];
data[0] = 2; data[4..12].copy_from_slice(&1u64.to_le_bytes());
let metas = [
InstructionAccount::writable(accounts[0].address()),
InstructionAccount::readonly(accounts[1].address()),
];
let ix = InstructionView {
program_id: &system_id,
data: &data,
accounts: &metas,
};
assert_eq!(
invoke_borrow_checked::<2>(&ix, &[&accounts[0], &accounts[1]]).unwrap_err(),
write_policy_violation(1)
);
assert_eq!(accounts[0].lamports(), 1);
assert_eq!(accounts[1].lamports(), 1);
}
#[test]
fn borrow_checked_requires_enough_account_views() {
let (_first_backing, first) = make_account([25; 32]);
let (_second_backing, second) = make_account([26; 32]);
let metas = [
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: &metas,
};
let err = invoke_borrow_checked::<1>(&instruction, &[&first]).unwrap_err();
assert_eq!(err, ProgramError::NotEnoughAccountKeys);
}
fn scratch_fingerprint(account_views: &[&AccountView<'_>]) -> std::string::String {
let mut s = std::string::String::new();
let mut i = 0;
while i < account_views.len() {
s.push_str(&std::format!(
"[{}]={:?};",
i,
CpiAccount::from(account_views[i])
));
i += 1;
}
s
}
fn reference_split_default(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>],
signers_seeds: &[Signer<'_, '_>],
) -> Result<std::string::String, ProgramError> {
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_authority_supplied(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;
}
if crate::write_policy::lamport_gate_active() {
let mut m = 0;
while m < instruction.accounts.len() {
if instruction.accounts[m].is_writable {
crate::write_policy::check_lamport_delegation(account_views[m].address())?;
}
m += 1;
}
}
validate_no_duplicate_writable(instruction, account_views)?;
Ok(scratch_fingerprint(account_views))
}
fn reference_fused_default(
instruction: &InstructionView<'_, '_, '_, '_>,
account_views: &[&AccountView<'_>],
signers_seeds: &[Signer<'_, '_>],
) -> Result<std::string::String, ProgramError> {
let metas_len = instruction.accounts.len();
if account_views.len() < metas_len {
return Err(ProgramError::NotEnoughAccountKeys);
}
let mut s = std::string::String::new();
let mut i = 0;
while i < account_views.len() {
let actual = account_views[i];
if i < metas_len {
let expected = &instruction.accounts[i];
if !address_eq(actual.address(), expected.address) {
return Err(ProgramError::InvalidAccountData);
}
if expected.is_signer
&& !actual.is_signer()
&& !signer_authority_supplied(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()?;
}
}
s.push_str(&std::format!("[{}]={:?};", i, CpiAccount::from(actual)));
i += 1;
}
if crate::write_policy::lamport_gate_active() {
let mut m = 0;
while m < metas_len {
if instruction.accounts[m].is_writable {
crate::write_policy::check_lamport_delegation(account_views[m].address())?;
}
m += 1;
}
}
validate_no_duplicate_writable(instruction, account_views)?;
Ok(s)
}
#[test]
fn signed_preflight_defers_pda_derivation_to_the_svm() {
let (_backing, account) = make_account([50; 32]);
let callee = Address::new_from_array([7; 32]);
let metas = [InstructionAccount::readonly_signer(account.address())];
let instruction = InstructionView {
program_id: &callee,
data: &[0u8],
accounts: &metas,
};
let views = [&account];
let seed_bytes = [9u8];
let seeds = [Seed::from(&seed_bytes)];
let signers = [Signer::from(&seeds)];
assert_eq!(invoke_signed(&instruction, &views, &signers), Ok(()));
assert_eq!(
invoke_signed(&instruction, &views, &[]),
Err(ProgramError::MissingRequiredSignature)
);
}
#[test]
fn fused_build_matches_split_build_and_per_tier_errors() {
use crate::write_policy::{install_lamport_gate, write_policy_violation, WritePolicy};
let program_id = Address::new_from_array([7; 32]);
{
let (_a, first) = make_account([51; 32]);
let (_b, second) = make_account([52; 32]);
let metas = [
InstructionAccount::writable(first.address()),
InstructionAccount::writable(second.address()),
];
let ix = InstructionView {
program_id: &program_id,
data: &[0u8],
accounts: &metas,
};
let views: [&AccountView<'_>; 2] = [&first, &second];
let split = reference_split_default(&ix, &views[..], &[]);
let fused = reference_fused_default(&ix, &views[..], &[]);
assert!(split.is_ok());
assert_eq!(split, fused);
assert_eq!(invoke::<2>(&ix, &views), Ok(()));
}
{
let (_a, acct) = make_account([53; 32]);
let metas = [InstructionAccount::readonly_signer(acct.address())];
let ix = InstructionView {
program_id: &program_id,
data: &[0u8],
accounts: &metas,
};
let views: [&AccountView<'_>; 1] = [&acct];
let split = reference_split_default(&ix, &views[..], &[]);
let fused = reference_fused_default(&ix, &views[..], &[]);
assert_eq!(split, Err(ProgramError::MissingRequiredSignature));
assert_eq!(split, fused);
assert_eq!(
invoke::<1>(&ix, &views).unwrap_err(),
ProgramError::MissingRequiredSignature
);
}
{
let (_a, acct) = make_account([54; 32]);
let accounts = [acct];
static P: WritePolicy = WritePolicy::with_lamports(&[], &[]);
let _gate = install_lamport_gate(&accounts, &P);
let metas = [InstructionAccount::writable(accounts[0].address())];
let ix = InstructionView {
program_id: &program_id,
data: &[0u8],
accounts: &metas,
};
let views: [&AccountView<'_>; 1] = [&accounts[0]];
let split = reference_split_default(&ix, &views[..], &[]);
let fused = reference_fused_default(&ix, &views[..], &[]);
assert_eq!(split, Err(write_policy_violation(0)));
assert_eq!(split, fused);
assert_eq!(
invoke::<1>(&ix, &views).unwrap_err(),
write_policy_violation(0)
);
}
{
let (_a, acct) = make_account([55; 32]);
let metas = [
InstructionAccount::writable(acct.address()),
InstructionAccount::writable(acct.address()),
];
let ix = InstructionView {
program_id: &program_id,
data: &[0u8],
accounts: &metas,
};
assert_eq!(
invoke_signed_deduped::<1>(&ix, &[&acct], &[]).unwrap_err(),
ProgramError::AccountBorrowFailed
);
}
}
}
#[cfg(test)]
#[path = "cpi_dedup_tests.rs"]
mod dedup_tests;