Skip to main content

typhoon_context/
lib.rs

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