11use {
22 crate :: processor:: * ,
3+ core:: {
4+ mem:: { transmute, MaybeUninit } ,
5+ slice:: from_raw_parts,
6+ } ,
37 pinocchio:: {
48 account_info:: AccountInfo ,
5- no_allocator, nostd_panic_handler, program_entrypoint,
9+ entrypoint:: deserialize_into,
10+ hint:: likely,
11+ no_allocator, nostd_panic_handler,
612 program_error:: { ProgramError , ToStr } ,
7- pubkey:: Pubkey ,
8- ProgramResult ,
13+ ProgramResult , MAX_TX_ACCOUNTS , SUCCESS ,
914 } ,
1015 pinocchio_token_interface:: error:: TokenError ,
1116} ;
1217
13- program_entrypoint ! ( process_instruction) ;
1418// Do not allocate memory.
1519no_allocator ! ( ) ;
1620// Use the no_std panic handler.
1721nostd_panic_handler ! ( ) ;
1822
23+ /// Custom program entrypoint to give priority to `transfer` and
24+ /// `transfer_checked` instructions.
25+ ///
26+ /// The entrypoint prioritizes the transfer instruction by validating
27+ /// account data lengths and instruction data. When it can reliably
28+ /// determine that the instruction is a transfer, it will invoke the
29+ /// processor directly.
30+ #[ no_mangle]
31+ #[ allow( clippy:: arithmetic_side_effects) ]
32+ pub unsafe extern "C" fn entrypoint ( input : * mut u8 ) -> u64 {
33+ // Constants that apply to both `transfer` and `transfer_checked`.
34+
35+ /// Offset for the first account.
36+ const ACCOUNT1_HEADER_OFFSET : usize = 0x0008 ;
37+
38+ /// Offset for the first account data length. This is
39+ /// expected to be a token account (165 bytes).
40+ const ACCOUNT1_DATA_LEN : usize = 0x0058 ;
41+
42+ /// Offset for the second account.
43+ const ACCOUNT2_HEADER_OFFSET : usize = 0x2910 ;
44+
45+ /// Offset for the second account data length. This is
46+ /// expected to be a token account for `transfer` (165 bytes)
47+ /// or a mint account for `transfer_checked` (82 bytes).
48+ const ACCOUNT2_DATA_LEN : usize = 0x2960 ;
49+
50+ // Constants that apply to `transfer_checked` (instruction 12).
51+
52+ /// Offset for the third account.
53+ const IX12_ACCOUNT3_HEADER_OFFSET : usize = 0x51c8 ;
54+
55+ /// Offset for the third account data length. This is
56+ /// expected to be a token account (165 bytes).
57+ const IX12_ACCOUNT3_DATA_LEN : usize = 0x5218 ;
58+
59+ /// Offset for the fourth account.
60+ const IX12_ACCOUNT4_HEADER_OFFSET : usize = 0x7ad0 ;
61+
62+ /// Offset for the fourth account data length.
63+ ///
64+ /// This is expected to be an account with variable data
65+ /// length.
66+ const IX12_ACCOUNT4_DATA_LEN : usize = 0x7b20 ;
67+
68+ /// Expected offset for the instruction data in the case all
69+ /// previous accounts have zero data.
70+ ///
71+ /// This value is adjusted before it is used.
72+ const IX12_EXPECTED_INSTRUCTION_DATA_LEN_OFFSET : usize = 0xa330 ;
73+
74+ // Constants that apply to `transfer` (instruction 3).
75+
76+ /// Offset for the second account.
77+ ///
78+ /// Note that this assumes that both first and second accounts
79+ /// have zero data, which is being validated before the offset
80+ /// is used.
81+ const IX3_ACCOUNT3_HEADER_OFFSET : usize = 0x5218 ;
82+
83+ /// Offset for the third account data length. This is
84+ /// expected to be a mint account (82 bytes).
85+ const IX3_ACCOUNT3_DATA_LEN : usize = 0x5268 ;
86+
87+ /// Expected offset for the instruction data in the case all
88+ /// previous accounts have zero data.
89+ ///
90+ /// This value is adjusted before it is used.
91+ const IX3_INSTRUCTION_DATA_LEN_OFFSET : usize = 0x7a28 ;
92+
93+ /// Align an address to the next multiple of 8.
94+ #[ inline( always) ]
95+ fn align ( input : u64 ) -> u64 {
96+ ( input + 7 ) & ( !7 )
97+ }
98+
99+ // Fast path for `transfer_checked`.
100+ //
101+ // It expects 4 accounts:
102+ // 1. source: must be a token account (165 length)
103+ // 2. mint: must be a mint account (82 length)
104+ // 3. destination: must be a token account (165 length)
105+ // 4. authority: can be any account (variable length)
106+ //
107+ // Instruction data is expected to be at least 9 bytes
108+ // and discriminator equal to 12.
109+ if * input == 4
110+ && ( * input. add ( ACCOUNT1_DATA_LEN ) . cast :: < u64 > ( ) == 165 )
111+ && ( * input. add ( ACCOUNT2_HEADER_OFFSET ) == 255 )
112+ && ( * input. add ( ACCOUNT2_DATA_LEN ) . cast :: < u64 > ( ) == 82 )
113+ && ( * input. add ( IX12_ACCOUNT3_HEADER_OFFSET ) == 255 )
114+ && ( * input. add ( IX12_ACCOUNT3_DATA_LEN ) . cast :: < u64 > ( ) == 165 )
115+ && ( * input. add ( IX12_ACCOUNT4_HEADER_OFFSET ) == 255 )
116+ {
117+ // The `authority` account can have variable data length.
118+ let account_4_data_len_aligned =
119+ align ( * input. add ( IX12_ACCOUNT4_DATA_LEN ) . cast :: < u64 > ( ) ) as usize ;
120+ let offset = IX12_EXPECTED_INSTRUCTION_DATA_LEN_OFFSET + account_4_data_len_aligned;
121+
122+ // Check that we have enough instruction data.
123+ //
124+ // Expected: instruction discriminator (u8) + amount (u64) + decimals (u8)
125+ if input. add ( offset) . cast :: < usize > ( ) . read ( ) >= 10 {
126+ let discriminator = input. add ( offset + size_of :: < u64 > ( ) ) . cast :: < u8 > ( ) . read ( ) ;
127+
128+ // Check for transfer discriminator.
129+ if likely ( discriminator == 12 ) {
130+ // instruction data length (u64) + discriminator (u8)
131+ let instruction_data = unsafe { from_raw_parts ( input. add ( offset + 9 ) , 9 ) } ;
132+
133+ let accounts = unsafe {
134+ [
135+ transmute :: < * mut u8 , AccountInfo > ( input. add ( ACCOUNT1_HEADER_OFFSET ) ) ,
136+ transmute :: < * mut u8 , AccountInfo > ( input. add ( ACCOUNT2_HEADER_OFFSET ) ) ,
137+ transmute :: < * mut u8 , AccountInfo > ( input. add ( IX12_ACCOUNT3_HEADER_OFFSET ) ) ,
138+ transmute :: < * mut u8 , AccountInfo > ( input. add ( IX12_ACCOUNT4_HEADER_OFFSET ) ) ,
139+ ]
140+ } ;
141+
142+ return match process_transfer_checked ( & accounts, instruction_data) {
143+ Ok ( ( ) ) => SUCCESS ,
144+ Err ( error) => {
145+ log_error ( & error) ;
146+ error. into ( )
147+ }
148+ } ;
149+ }
150+ }
151+ }
152+ // Fast path for `transfer`.
153+ //
154+ // It expects 3 accounts:
155+ // 1. source: must be a token account (165 length)
156+ // 2. destination: must be a token account (165 length)
157+ // 3. authority: can be any account (variable length)
158+ //
159+ // Instruction data is expected to be at least 8 bytes
160+ // and discriminator equal to 3.
161+ else if * input == 3
162+ && ( * input. add ( ACCOUNT1_DATA_LEN ) . cast :: < u64 > ( ) == 165 )
163+ && ( * input. add ( ACCOUNT2_HEADER_OFFSET ) == 255 )
164+ && ( * input. add ( ACCOUNT2_DATA_LEN ) . cast :: < u64 > ( ) == 165 )
165+ && ( * input. add ( IX3_ACCOUNT3_HEADER_OFFSET ) == 255 )
166+ {
167+ // The `authority` account can have variable data length.
168+ let account_3_data_len_aligned =
169+ align ( * input. add ( IX3_ACCOUNT3_DATA_LEN ) . cast :: < u64 > ( ) ) as usize ;
170+ let offset = IX3_INSTRUCTION_DATA_LEN_OFFSET + account_3_data_len_aligned;
171+
172+ // Check that we have enough instruction data.
173+ if likely ( input. add ( offset) . cast :: < usize > ( ) . read ( ) >= 9 ) {
174+ let discriminator = input. add ( offset + size_of :: < u64 > ( ) ) . cast :: < u8 > ( ) . read ( ) ;
175+
176+ // Check for transfer discriminator.
177+ if likely ( discriminator == 3 ) {
178+ let instruction_data =
179+ unsafe { from_raw_parts ( input. add ( offset + 9 ) , size_of :: < u64 > ( ) ) } ;
180+
181+ let accounts = unsafe {
182+ [
183+ transmute :: < * mut u8 , AccountInfo > ( input. add ( ACCOUNT1_HEADER_OFFSET ) ) ,
184+ transmute :: < * mut u8 , AccountInfo > ( input. add ( ACCOUNT2_HEADER_OFFSET ) ) ,
185+ transmute :: < * mut u8 , AccountInfo > ( input. add ( IX3_ACCOUNT3_HEADER_OFFSET ) ) ,
186+ ]
187+ } ;
188+
189+ return match process_transfer ( & accounts, instruction_data) {
190+ Ok ( ( ) ) => SUCCESS ,
191+ Err ( error) => {
192+ log_error ( & error) ;
193+ error. into ( )
194+ }
195+ } ;
196+ }
197+ }
198+ }
199+
200+ // Entrypoint for the remaining instructions.
201+
202+ const UNINIT : MaybeUninit < AccountInfo > = MaybeUninit :: < AccountInfo > :: uninit ( ) ;
203+ let mut accounts = [ UNINIT ; { MAX_TX_ACCOUNTS } ] ;
204+
205+ let ( count, instruction_data) = deserialize_into ( input, & mut accounts) ;
206+
207+ match process_instruction (
208+ from_raw_parts ( accounts. as_ptr ( ) as _ , count as usize ) ,
209+ instruction_data,
210+ ) {
211+ Ok ( ( ) ) => SUCCESS ,
212+ Err ( error) => error. into ( ) ,
213+ }
214+ }
215+
19216/// Log an error.
20217#[ cold]
21218fn log_error ( error : & ProgramError ) {
@@ -30,11 +227,7 @@ fn log_error(error: &ProgramError) {
30227/// instructions, since it is not sound to have a "batch" instruction inside
31228/// another "batch" instruction.
32229#[ inline( always) ]
33- pub fn process_instruction (
34- _program_id : & Pubkey ,
35- accounts : & [ AccountInfo ] ,
36- instruction_data : & [ u8 ] ,
37- ) -> ProgramResult {
230+ pub fn process_instruction ( accounts : & [ AccountInfo ] , instruction_data : & [ u8 ] ) -> ProgramResult {
38231 let [ discriminator, remaining @ ..] = instruction_data else {
39232 return Err ( TokenError :: InvalidInstruction . into ( ) ) ;
40233 } ;
0 commit comments