use crate::account::AccountView;
use crate::address::Address;
use crate::error::ProgramError;
use core::marker::PhantomData;
use core::mem::MaybeUninit;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct InstructionAccount<'a> {
pub address: &'a Address,
pub is_writable: bool,
pub is_signer: bool,
}
impl<'a> InstructionAccount<'a> {
#[inline(always)]
pub const fn new(address: &'a Address, is_writable: bool, is_signer: bool) -> Self {
Self {
address,
is_writable,
is_signer,
}
}
#[inline(always)]
pub const fn readonly(address: &'a Address) -> Self {
Self {
address,
is_writable: false,
is_signer: false,
}
}
#[inline(always)]
pub const fn writable(address: &'a Address) -> Self {
Self {
address,
is_writable: true,
is_signer: false,
}
}
#[inline(always)]
pub const fn readonly_signer(address: &'a Address) -> Self {
Self {
address,
is_writable: false,
is_signer: true,
}
}
#[inline(always)]
pub const fn writable_signer(address: &'a Address) -> Self {
Self {
address,
is_writable: true,
is_signer: true,
}
}
}
impl<'a> From<&'a AccountView<'a>> for InstructionAccount<'a> {
#[inline(always)]
fn from(view: &'a AccountView<'a>) -> Self {
Self {
address: view.address(),
is_writable: view.is_writable(),
is_signer: view.is_signer(),
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StoredAccountMeta {
pub pubkey: Address,
pub flags: u8,
}
impl StoredAccountMeta {
pub const SIGNER: u8 = 0b0000_0001;
pub const WRITABLE: u8 = 0b0000_0010;
#[inline(always)]
pub const fn new(pubkey: Address, is_signer: bool, is_writable: bool) -> Self {
let mut flags = 0u8;
if is_signer {
flags |= Self::SIGNER;
}
if is_writable {
flags |= Self::WRITABLE;
}
Self { pubkey, flags }
}
#[inline(always)]
pub const fn readonly(pubkey: Address) -> Self {
Self::new(pubkey, false, false)
}
#[inline(always)]
pub const fn writable(pubkey: Address) -> Self {
Self::new(pubkey, false, true)
}
#[inline(always)]
pub const fn readonly_signer(pubkey: Address) -> Self {
Self::new(pubkey, true, false)
}
#[inline(always)]
pub const fn writable_signer(pubkey: Address) -> Self {
Self::new(pubkey, true, true)
}
#[inline(always)]
pub const fn is_signer(&self) -> bool {
self.flags & Self::SIGNER != 0
}
#[inline(always)]
pub const fn is_writable(&self) -> bool {
self.flags & Self::WRITABLE != 0
}
#[inline(always)]
pub fn to_instruction_account(&self) -> InstructionAccount<'_> {
InstructionAccount::new(&self.pubkey, self.is_writable(), self.is_signer())
}
}
#[derive(Debug, Clone, Copy)]
pub struct StoredInstruction<'a> {
pub program_id: Address,
pub account_metas: &'a [StoredAccountMeta],
pub instruction_data: &'a [u8],
}
impl<'a> StoredInstruction<'a> {
#[inline]
pub fn new(
program_id: Address,
account_metas: &'a [StoredAccountMeta],
instruction_data: &'a [u8],
) -> Result<Self, ProgramError> {
if account_metas.len() > crate::cpi::MAX_CPI_ACCOUNTS {
return Err(ProgramError::InvalidArgument);
}
Ok(Self {
program_id,
account_metas,
instruction_data,
})
}
#[inline(always)]
pub const fn account_count(&self) -> usize {
self.account_metas.len()
}
#[inline]
pub fn write_instruction_accounts<const N: usize>(
&'a self,
out: &'a mut [MaybeUninit<InstructionAccount<'a>>; N],
) -> Result<&'a [InstructionAccount<'a>], ProgramError> {
if self.account_metas.len() > N {
return Err(ProgramError::InvalidArgument);
}
let mut index = 0;
while index < self.account_metas.len() {
out[index].write(self.account_metas[index].to_instruction_account());
index += 1;
}
Ok(unsafe {
core::slice::from_raw_parts(
out.as_ptr() as *const InstructionAccount<'a>,
self.account_metas.len(),
)
})
}
#[inline]
pub fn to_instruction_view<const N: usize>(
&'a self,
out: &'a mut [MaybeUninit<InstructionAccount<'a>>; N],
) -> Result<InstructionView<'a, 'a, 'a, 'a>, ProgramError> {
let accounts = self.write_instruction_accounts(out)?;
Ok(InstructionView {
program_id: &self.program_id,
data: self.instruction_data,
accounts,
})
}
}
#[derive(Debug, Clone)]
pub struct InstructionView<'a, 'b, 'c, 'd>
where
'a: 'b,
{
pub program_id: &'c Address,
pub data: &'d [u8],
pub accounts: &'b [InstructionAccount<'a>],
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct CpiAccount<'a> {
address: *const Address,
lamports: *const u64,
data_len: u64,
data: *const u8,
owner: *const Address,
rent_epoch: u64,
is_signer: bool,
is_writable: bool,
executable: bool,
_account_view: PhantomData<&'a AccountView<'a>>,
}
impl<'a> From<&'a AccountView<'a>> for CpiAccount<'a> {
#[inline]
fn from(view: &'a AccountView<'a>) -> Self {
let raw = view.account_ptr();
Self {
address: unsafe { core::ptr::addr_of!((*raw).address) as *const Address },
lamports: unsafe { core::ptr::addr_of!((*raw).lamports) },
data_len: view.data_len() as u64,
data: view.data_ptr_unchecked(),
owner: unsafe { core::ptr::addr_of!((*raw).owner) as *const Address },
rent_epoch: 0,
is_signer: view.is_signer(),
is_writable: view.is_writable(),
executable: view.executable(),
_account_view: PhantomData,
}
}
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct Seed<'a> {
pub(crate) seed: *const u8,
pub(crate) len: u64,
_bytes: PhantomData<&'a [u8]>,
}
impl<'a> From<&'a [u8]> for Seed<'a> {
#[inline(always)]
fn from(bytes: &'a [u8]) -> Self {
Self {
seed: bytes.as_ptr(),
len: bytes.len() as u64,
_bytes: PhantomData,
}
}
}
impl<'a, const N: usize> From<&'a [u8; N]> for Seed<'a> {
#[inline(always)]
fn from(bytes: &'a [u8; N]) -> Self {
Self {
seed: bytes.as_ptr(),
len: N as u64,
_bytes: PhantomData,
}
}
}
impl core::ops::Deref for Seed<'_> {
type Target = [u8];
#[inline(always)]
fn deref(&self) -> &[u8] {
unsafe { core::slice::from_raw_parts(self.seed, self.len as usize) }
}
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct Signer<'a, 'b> {
pub(crate) seeds: *const Seed<'a>,
pub(crate) len: u64,
_seeds: PhantomData<&'b [Seed<'a>]>,
}
impl<'a, 'b> From<&'b [Seed<'a>]> for Signer<'a, 'b> {
#[inline(always)]
fn from(seeds: &'b [Seed<'a>]) -> Self {
Self {
seeds: seeds.as_ptr(),
len: seeds.len() as u64,
_seeds: PhantomData,
}
}
}
impl<'a, 'b, const N: usize> From<&'b [Seed<'a>; N]> for Signer<'a, 'b> {
#[inline(always)]
fn from(seeds: &'b [Seed<'a>; N]) -> Self {
Self {
seeds: seeds.as_ptr(),
len: N as u64,
_seeds: PhantomData,
}
}
}
#[macro_export]
macro_rules! seeds {
( $($seed:expr),* $(,)? ) => {
[$(
$crate::instruction::Seed::from($seed),
)*]
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stored_account_meta_flags_round_trip() {
let key = Address::new_from_array([3; 32]);
let meta = StoredAccountMeta::writable_signer(key);
assert!(meta.is_signer());
assert!(meta.is_writable());
let ix_meta = meta.to_instruction_account();
assert_eq!(ix_meta.address, &key);
assert!(ix_meta.is_signer);
assert!(ix_meta.is_writable);
}
#[test]
fn stored_instruction_builds_instruction_view_without_alloc() {
let program = Address::new_from_array([9; 32]);
let first = Address::new_from_array([1; 32]);
let second = Address::new_from_array([2; 32]);
let metas = [
StoredAccountMeta::readonly(first),
StoredAccountMeta::writable(second),
];
let data = [7u8, 8, 9];
let stored = StoredInstruction::new(program, &metas, &data).unwrap();
let mut out: [MaybeUninit<InstructionAccount<'_>>; 2] =
[MaybeUninit::uninit(), MaybeUninit::uninit()];
let view = stored.to_instruction_view(&mut out).unwrap();
assert_eq!(view.program_id, &program);
assert_eq!(view.data, &data);
assert_eq!(view.accounts.len(), 2);
assert_eq!(view.accounts[0].address, &first);
assert!(!view.accounts[0].is_writable);
assert_eq!(view.accounts[1].address, &second);
assert!(view.accounts[1].is_writable);
}
#[test]
fn stored_instruction_rejects_small_output_buffer() {
let program = Address::new_from_array([9; 32]);
let first = Address::new_from_array([1; 32]);
let second = Address::new_from_array([2; 32]);
let metas = [
StoredAccountMeta::readonly(first),
StoredAccountMeta::writable(second),
];
let stored = StoredInstruction::new(program, &metas, &[]).unwrap();
let mut out: [MaybeUninit<InstructionAccount<'_>>; 1] = [MaybeUninit::uninit()];
assert_eq!(
stored.to_instruction_view(&mut out).unwrap_err(),
ProgramError::InvalidArgument
);
}
}