typhoon_context/
lib.rs

1#![no_std]
2
3use {bytemuck::NoUninit, paste::paste, pinocchio::program_error::ProgramError};
4
5mod args;
6mod array;
7mod iterator;
8mod program_id;
9mod remaining_accounts;
10
11pub use {args::*, array::*, iterator::*, program_id::*, remaining_accounts::*};
12use {
13    pinocchio::{account_info::AccountInfo, cpi::set_return_data, pubkey::Pubkey},
14    typhoon_errors::Error,
15};
16
17/// Marker trait for context types. This trait is used only for identification purposes.
18pub trait Context {}
19
20pub trait HandlerContext<'a, 'b, 'c>: Sized {
21    fn from_entrypoint(
22        program_id: &'a Pubkey,
23        accounts: &mut &'b [AccountInfo],
24        instruction_data: &mut &'c [u8],
25    ) -> Result<Self, Error>;
26}
27
28pub trait Handler<'a, 'b, 'c, T> {
29    type Output: NoUninit;
30
31    fn call(
32        self,
33        program_id: &'a Pubkey,
34        accounts: &mut &'b [AccountInfo],
35        instruction_data: &mut &'c [u8],
36    ) -> Result<Self::Output, Error>;
37}
38
39impl<F, O> Handler<'_, '_, '_, ()> for F
40where
41    F: FnOnce() -> Result<O, Error>,
42    O: NoUninit,
43{
44    type Output = O;
45
46    fn call(
47        self,
48        _program_id: &Pubkey,
49        _accounts: &mut &[AccountInfo],
50        _instruction_data: &mut &[u8],
51    ) -> Result<Self::Output, Error> {
52        (self)()
53    }
54}
55
56macro_rules! impl_handler {
57    ($( $t:ident ),+) => {
58        impl<'a, 'b, 'c, $( $t, )* F, O> Handler<'a, 'b, 'c, ($( $t, )*)> for F
59        where
60            F: FnOnce($( $t ),*) -> Result<O, Error>,
61            O: NoUninit,
62            $(
63                $t: HandlerContext<'a, 'b, 'c>,
64            )*
65        {
66            type Output = O;
67
68            fn call(
69                self,
70                program_id: &'a Pubkey,
71                accounts: &mut &'b [AccountInfo],
72                instruction_data: &mut &'c [u8],
73            ) -> Result<Self::Output, Error> {
74                paste! {
75                    $(
76                        let [<$t:lower>] = $t::from_entrypoint(program_id, accounts, instruction_data)?;
77                    )*
78                    (self)($( [<$t:lower>], )*)
79                }
80            }
81        }
82    };
83}
84
85impl_handler!(T1);
86impl_handler!(T1, T2);
87impl_handler!(T1, T2, T3);
88impl_handler!(T1, T2, T3, T4);
89impl_handler!(T1, T2, T3, T4, T5);
90impl_handler!(T1, T2, T3, T4, T5, T6);
91impl_handler!(T1, T2, T3, T4, T5, T6, T7);
92
93pub fn handle<'a, 'b, 'c, T, H>(
94    program_id: &'a Pubkey,
95    mut accounts: &'b [AccountInfo],
96    mut instruction_data: &'c [u8],
97    handler: H,
98) -> Result<(), Error>
99where
100    H: Handler<'a, 'b, 'c, T>,
101{
102    match handler.call(program_id, &mut accounts, &mut instruction_data) {
103        Ok(res) => {
104            if core::mem::size_of::<H::Output>() > 0 {
105                set_return_data(bytemuck::bytes_of(&res));
106            }
107
108            Ok(())
109        }
110        Err(err) => Err(err),
111    }
112}
113
114#[macro_export]
115macro_rules! basic_router {
116    ($($dis:literal => $fn_ident: ident),+ $(,)?) => {
117        |program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]| {
118            let (discriminator, data) = instruction_data
119                .split_first()
120                .ok_or(ProgramError::InvalidInstructionData)?;
121
122            let result = match discriminator {
123                $($dis => handle(program_id, accounts, data, $fn_ident),)*
124                _ => Err(ErrorCode::UnknownInstruction.into()),
125            };
126
127            #[cfg(feature = "logging")]
128            result.inspect_err(log_error)?;
129
130            #[cfg(not(feature = "logging"))]
131            result?;
132
133            Ok(())
134        }
135    };
136}
137
138pub type EntryFn = fn(&Pubkey, &[AccountInfo], &[u8]) -> Result<(), ProgramError>;
139
140#[macro_export]
141macro_rules! entrypoint {
142    () => {
143        $crate::entrypoint!(@inner inline(always));
144    };
145    (no_inline) => {
146        $crate::entrypoint!(@inner inline(never));
147    };
148    (@inner $($inline:tt)*) => {
149        program_entrypoint!(process_instruction);
150
151        #[ $($inline)* ]
152        pub fn process_instruction(
153            program_id: &Pubkey,
154            accounts: &[AccountInfo],
155            instruction_data: &[u8],
156        ) -> Result<(), ProgramError> {
157            ROUTER(program_id, accounts, instruction_data)
158        }
159    };
160}