use core::mem::MaybeUninit;
use hopper_runtime::error::ProgramError;
use hopper_runtime::ProgramResult;
pub struct HopperCpi<'a, const ACCTS: usize, const DATA: usize> {
#[allow(dead_code)]
program_id: &'a hopper_runtime::Address,
account_keys: [&'a hopper_runtime::Address; ACCTS],
account_flags: [(bool, bool); ACCTS], account_views: [MaybeUninit<&'a hopper_runtime::AccountView>; ACCTS],
data: [u8; DATA],
acct_cursor: usize,
}
impl<'a, const ACCTS: usize, const DATA: usize> HopperCpi<'a, ACCTS, DATA> {
#[inline(always)]
pub fn new(program_id: &'a hopper_runtime::Address) -> Self {
Self {
program_id,
account_keys: [program_id; ACCTS], account_flags: [(false, false); ACCTS],
account_views: unsafe { MaybeUninit::uninit().assume_init() },
data: [0u8; DATA],
acct_cursor: 0,
}
}
#[inline(always)]
pub fn add_account(
mut self,
view: &'a hopper_runtime::AccountView,
is_writable: bool,
is_signer: bool,
) -> Self {
let idx = self.acct_cursor;
debug_assert!(idx < ACCTS, "Too many accounts added to CPI");
self.account_keys[idx] = view.address();
self.account_flags[idx] = (is_writable, is_signer);
self.account_views[idx] = MaybeUninit::new(view);
self.acct_cursor += 1;
self
}
#[inline(always)]
pub fn set_data(mut self, src: &[u8; DATA]) -> Self {
self.data = *src;
self
}
#[inline(always)]
pub fn set_data_from_slice(mut self, src: &[u8]) -> Result<Self, ProgramError> {
if src.len() != DATA {
return Err(ProgramError::InvalidInstructionData);
}
self.data.copy_from_slice(src);
Ok(self)
}
#[inline]
pub fn invoke(&self) -> ProgramResult {
debug_assert_eq!(self.acct_cursor, ACCTS, "Not all accounts added to CPI");
self.invoke_signed(&[])
}
#[inline]
pub fn invoke_signed(&self, seeds: &[&[&[u8]]]) -> ProgramResult {
#[cfg(target_os = "solana")]
{
use hopper_runtime::instruction::{InstructionAccount, InstructionView, Seed, Signer};
debug_assert_eq!(self.acct_cursor, ACCTS, "Not all accounts added to CPI");
let views: &[&hopper_runtime::AccountView; ACCTS] = unsafe {
&*(&self.account_views as *const [MaybeUninit<&hopper_runtime::AccountView>; ACCTS]
as *const [&hopper_runtime::AccountView; ACCTS])
};
let mut ix_accounts: [InstructionAccount; ACCTS] = unsafe { core::mem::zeroed() };
let mut i = 0;
while i < ACCTS {
ix_accounts[i] = InstructionAccount {
address: self.account_keys[i],
is_writable: self.account_flags[i].0,
is_signer: self.account_flags[i].1,
};
i += 1;
}
let ix = InstructionView {
program_id: self.program_id,
accounts: &ix_accounts,
data: &self.data,
};
if seeds.is_empty() {
hopper_runtime::cpi::invoke(&ix, views)
} else {
let mut signers_buf: [Signer; 4] = unsafe { core::mem::zeroed() };
let signer_count = seeds.len().min(4);
let mut seed_bufs: [[Seed; 16]; 4] = unsafe { core::mem::zeroed() };
let mut seed_lens = [0usize; 4];
let mut s = 0;
while s < signer_count {
let signer_seeds = seeds[s];
let num_seeds = signer_seeds.len().min(16);
let mut sd = 0;
while sd < num_seeds {
seed_bufs[s][sd] = Seed::from(signer_seeds[sd]);
sd += 1;
}
seed_lens[s] = num_seeds;
s += 1;
}
let mut s = 0;
while s < signer_count {
signers_buf[s] = Signer::from(&seed_bufs[s][..seed_lens[s]]);
s += 1;
}
hopper_runtime::cpi::invoke_signed(&ix, views, &signers_buf[..signer_count])
}
}
#[cfg(not(target_os = "solana"))]
{
let _ = seeds;
Ok(())
}
}
}
pub struct HopperCpiBuf<'a, const ACCTS: usize, const MAX: usize> {
#[allow(dead_code)]
program_id: &'a hopper_runtime::Address,
account_keys: [&'a hopper_runtime::Address; ACCTS],
account_flags: [(bool, bool); ACCTS],
account_views: [MaybeUninit<&'a hopper_runtime::AccountView>; ACCTS],
data: [u8; MAX],
data_len: usize,
acct_cursor: usize,
}
impl<'a, const ACCTS: usize, const MAX: usize> HopperCpiBuf<'a, ACCTS, MAX> {
#[inline(always)]
pub fn new(program_id: &'a hopper_runtime::Address) -> Self {
Self {
program_id,
account_keys: [program_id; ACCTS],
account_flags: [(false, false); ACCTS],
account_views: unsafe { MaybeUninit::uninit().assume_init() },
data: [0u8; MAX],
data_len: 0,
acct_cursor: 0,
}
}
#[inline(always)]
pub fn add_account(
mut self,
view: &'a hopper_runtime::AccountView,
is_writable: bool,
is_signer: bool,
) -> Self {
let idx = self.acct_cursor;
debug_assert!(idx < ACCTS);
self.account_keys[idx] = view.address();
self.account_flags[idx] = (is_writable, is_signer);
self.account_views[idx] = MaybeUninit::new(view);
self.acct_cursor += 1;
self
}
#[inline]
pub fn write_data(mut self, src: &[u8]) -> Result<Self, ProgramError> {
if src.len() > MAX {
return Err(ProgramError::InvalidInstructionData);
}
self.data[..src.len()].copy_from_slice(src);
self.data_len = src.len();
Ok(self)
}
#[inline]
pub fn invoke(&self) -> ProgramResult {
self.invoke_signed(&[])
}
#[inline]
pub fn invoke_signed(&self, seeds: &[&[&[u8]]]) -> ProgramResult {
#[cfg(target_os = "solana")]
{
use hopper_runtime::instruction::{InstructionAccount, InstructionView, Seed, Signer};
debug_assert_eq!(self.acct_cursor, ACCTS, "Not all accounts added to CPI");
let views: &[&hopper_runtime::AccountView; ACCTS] = unsafe {
&*(&self.account_views as *const [MaybeUninit<&hopper_runtime::AccountView>; ACCTS]
as *const [&hopper_runtime::AccountView; ACCTS])
};
let mut ix_accounts: [InstructionAccount; ACCTS] = unsafe { core::mem::zeroed() };
let mut i = 0;
while i < ACCTS {
ix_accounts[i] = InstructionAccount {
address: self.account_keys[i],
is_writable: self.account_flags[i].0,
is_signer: self.account_flags[i].1,
};
i += 1;
}
let ix = InstructionView {
program_id: self.program_id,
accounts: &ix_accounts,
data: &self.data[..self.data_len],
};
if seeds.is_empty() {
hopper_runtime::cpi::invoke(&ix, views)
} else {
let mut signers_buf: [Signer; 4] = unsafe { core::mem::zeroed() };
let signer_count = seeds.len().min(4);
let mut seed_bufs: [[Seed; 16]; 4] = unsafe { core::mem::zeroed() };
let mut seed_lens = [0usize; 4];
let mut s = 0;
while s < signer_count {
let signer_seeds = seeds[s];
let num_seeds = signer_seeds.len().min(16);
let mut sd = 0;
while sd < num_seeds {
seed_bufs[s][sd] = Seed::from(signer_seeds[sd]);
sd += 1;
}
seed_lens[s] = num_seeds;
s += 1;
}
let mut s = 0;
while s < signer_count {
signers_buf[s] = Signer::from(&seed_bufs[s][..seed_lens[s]]);
s += 1;
}
hopper_runtime::cpi::invoke_signed(&ix, views, &signers_buf[..signer_count])
}
}
#[cfg(not(target_os = "solana"))]
{
let _ = seeds;
Ok(())
}
}
}