use crate::{
account_set::{AccountSetCleanup, AccountSetDecode, AccountSetValidate},
prelude::*,
};
use bytemuck::{bytes_of, Pod};
use pinocchio::cpi::set_return_data;
pub use star_frame_proc::{
star_frame_instruction, InstructionArgs, InstructionSet, InstructionToIdl,
};
mod no_op;
mod un_callable;
pub use un_callable::UnCallable;
pub trait InstructionSet {
type Discriminant: Pod;
fn dispatch(
program_id: &'static Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> Result<()>;
}
pub trait InstructionDiscriminant<IxSet>
where
IxSet: InstructionSet,
{
const DISCRIMINANT: <IxSet as InstructionSet>::Discriminant;
#[must_use]
fn discriminant_bytes() -> Vec<u8> {
bytes_of(&Self::DISCRIMINANT).into()
}
}
pub trait Instruction {
fn process_from_raw(
program_id: &'static Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> Result<()>;
}
#[derive(derive_where::DeriveWhere)]
#[derive_where(
Default, Debug;
<T as InstructionArgs>::DecodeArg<'a>,
<T as InstructionArgs>::ValidateArg<'a>,
<T as InstructionArgs>::RunArg<'a>,
<T as InstructionArgs>::CleanupArg<'a>
)]
pub struct IxArgs<'a, T: InstructionArgs> {
pub decode: <T as InstructionArgs>::DecodeArg<'a>,
pub validate: <T as InstructionArgs>::ValidateArg<'a>,
pub run: <T as InstructionArgs>::RunArg<'a>,
pub cleanup: <T as InstructionArgs>::CleanupArg<'a>,
}
pub trait InstructionArgs: Sized {
type DecodeArg<'a>;
type ValidateArg<'a>;
type RunArg<'a>;
type CleanupArg<'a>;
fn split_to_args(r: &mut Self) -> IxArgs<'_, Self>;
}
#[doc(hidden)]
#[diagnostic::on_unimplemented(
message = "`StarFrameInstruction` requires the return type to be `Result<T>`"
)]
pub trait IxReturnType {
type ReturnType;
}
impl<T, E> IxReturnType for Result<T, E> {
type ReturnType = T;
}
pub trait StarFrameInstruction: BorshDeserialize + InstructionArgs {
type ReturnType: NoUninit;
type Accounts<'decode, 'arg>: AccountSetDecode<'decode, Self::DecodeArg<'arg>>
+ AccountSetValidate<Self::ValidateArg<'arg>>
+ AccountSetCleanup<Self::CleanupArg<'arg>>;
fn process(
accounts: &mut Self::Accounts<'_, '_>,
run_arg: Self::RunArg<'_>,
ctx: &mut Context,
) -> Result<Self::ReturnType>;
}
impl<T> Instruction for T
where
T: StarFrameInstruction,
{
#[inline]
fn process_from_raw(
program_id: &'static Pubkey,
mut accounts: &[AccountInfo],
mut data: &[u8],
) -> Result<()> {
let mut ctx = Context::new(program_id);
let mut data = <T as BorshDeserialize>::deserialize(&mut data)
.ctx("Failed to deserialize instruction data")?;
let IxArgs {
decode,
validate,
run,
cleanup,
} = Self::split_to_args(&mut data);
let mut account_set: <T as StarFrameInstruction>::Accounts<'_, '_> =
<Self as StarFrameInstruction>::Accounts::decode_accounts(
&mut accounts,
decode,
&mut ctx,
)
.ctx("Failed to decode accounts")?;
account_set
.validate_accounts(validate, &mut ctx)
.ctx("Failed to validate accounts")?;
let ret: <T as StarFrameInstruction>::ReturnType =
Self::process(&mut account_set, run, &mut ctx).ctx("Failed to run instruction")?;
account_set
.cleanup_accounts(cleanup, &mut ctx)
.ctx("Failed to cleanup accounts")?;
if size_of::<T::ReturnType>() > 0 {
set_return_data(bytemuck::bytes_of(&ret));
}
Ok(())
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! empty_star_frame_instruction {
($ix:ident, $accounts:ident) => {
impl $crate::instruction::StarFrameInstruction for $ix {
type ReturnType = ();
type Accounts<'decode, 'arg> = $accounts;
fn process(
_accounts: &mut Self::Accounts<'_, '_>,
_run_arg: Self::RunArg<'_>,
_ctx: &mut $crate::context::Context,
) -> $crate::Result<Self::ReturnType> {
Ok(())
}
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_blank_ix {
($($ix:ident),*) => {
$(
impl $crate::instruction::Instruction for $ix {
fn process_from_raw(
_program_id: &'static $crate::prelude::Pubkey,
_accounts: &[$crate::prelude::AccountInfo],
_data: &[u8],
) -> $crate::Result<()> {
todo!()
}
}
)*
};
}
#[cfg(test)]
mod test {
use star_frame_proc::InstructionSet;
#[allow(dead_code)]
struct Ix1 {
val: u8,
}
#[allow(dead_code)]
struct Ix2 {
val: u64,
}
impl_blank_ix!(Ix1, Ix2);
#[allow(dead_code)]
#[derive(InstructionSet)]
#[ix_set(skip_idl)]
enum TestInstructionSet3 {
Ix1(Ix1),
Ix2(Ix2),
}
}