11use {
22 crate :: processor:: * ,
3+ core:: {
4+ mem:: { size_of, 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, NON_DUP_MARKER } ,
10+ hint:: likely,
11+ log:: sol_log,
12+ no_allocator, nostd_panic_handler,
613 program_error:: { ProgramError , ToStr } ,
7- pubkey:: Pubkey ,
8- ProgramResult ,
14+ ProgramResult , MAX_TX_ACCOUNTS , SUCCESS ,
15+ } ,
16+ pinocchio_token_interface:: {
17+ error:: TokenError ,
18+ instruction:: TokenInstruction ,
19+ state:: { account:: Account , mint:: Mint , Transmutable } ,
920 } ,
10- pinocchio_token_interface:: error:: TokenError ,
1121} ;
1222
13- program_entrypoint ! ( process_instruction) ;
1423// Do not allocate memory.
1524no_allocator ! ( ) ;
1625// Use the no_std panic handler.
1726nostd_panic_handler ! ( ) ;
1827
28+ /// Custom program entrypoint to give priority to `transfer` and
29+ /// `transfer_checked` instructions.
30+ ///
31+ /// The entrypoint prioritizes the transfer instruction by validating
32+ /// account data lengths and instruction data. When it can reliably
33+ /// determine that the instruction is a transfer, it will invoke the
34+ /// processor directly.
35+ #[ no_mangle]
36+ #[ allow( clippy:: arithmetic_side_effects) ]
37+ pub unsafe extern "C" fn entrypoint ( input : * mut u8 ) -> u64 {
38+ // Constants that apply to both `transfer` and `transfer_checked`.
39+
40+ /// Offset for the first account.
41+ const ACCOUNT1_HEADER_OFFSET : usize = 0x0008 ;
42+
43+ /// Offset for the first account data length. This is
44+ /// expected to be a token account (165 bytes).
45+ const ACCOUNT1_DATA_LEN : usize = 0x0058 ;
46+
47+ /// Offset for the second account.
48+ const ACCOUNT2_HEADER_OFFSET : usize = 0x2910 ;
49+
50+ /// Offset for the second account data length. This is
51+ /// expected to be a token account for `transfer` (165 bytes)
52+ /// or a mint account for `transfer_checked` (82 bytes).
53+ const ACCOUNT2_DATA_LEN : usize = 0x2960 ;
54+
55+ // Constants that apply to `transfer_checked` (instruction 12).
56+
57+ /// Offset for the third account.
58+ const IX12_ACCOUNT3_HEADER_OFFSET : usize = 0x51c8 ;
59+
60+ /// Offset for the third account data length. This is
61+ /// expected to be a token account (165 bytes).
62+ const IX12_ACCOUNT3_DATA_LEN : usize = 0x5218 ;
63+
64+ /// Offset for the fourth account.
65+ const IX12_ACCOUNT4_HEADER_OFFSET : usize = 0x7ad0 ;
66+
67+ /// Offset for the fourth account data length.
68+ ///
69+ /// This is expected to be an account with variable data
70+ /// length.
71+ const IX12_ACCOUNT4_DATA_LEN : usize = 0x7b20 ;
72+
73+ /// Expected offset for the instruction data in the case the
74+ /// fourth (authority) account has zero data.
75+ ///
76+ /// This value is adjusted before it is used.
77+ const IX12_EXPECTED_INSTRUCTION_DATA_LEN_OFFSET : usize = 0xa330 ;
78+
79+ // Constants that apply to `transfer` (instruction 3).
80+
81+ /// Offset for the third account.
82+ ///
83+ /// Note that this assumes that both first and second accounts
84+ /// have zero data, which is being validated before the offset
85+ /// is used.
86+ const IX3_ACCOUNT3_HEADER_OFFSET : usize = 0x5218 ;
87+
88+ /// Offset for the third account data length.
89+ ///
90+ /// This is expected to be an account with variable data
91+ /// length.
92+ const IX3_ACCOUNT3_DATA_LEN : usize = 0x5268 ;
93+
94+ /// Expected offset for the instruction data in the case the
95+ /// third (authority) account has zero data.
96+ ///
97+ /// This value is adjusted before it is used.
98+ const IX3_INSTRUCTION_DATA_LEN_OFFSET : usize = 0x7a78 ;
99+
100+ /// Align an address to the next multiple of 8.
101+ #[ inline( always) ]
102+ fn align ( input : u64 ) -> u64 {
103+ ( input + 7 ) & ( !7 )
104+ }
105+
106+ // Fast path for `transfer_checked`.
107+ //
108+ // It expects 4 accounts:
109+ // 1. source: must be a token account (165 length)
110+ // 2. mint: must be a mint account (82 length)
111+ // 3. destination: must be a token account (165 length)
112+ // 4. authority: can be any account (variable length)
113+ //
114+ // Instruction data is expected to be at least 9 bytes
115+ // and discriminator equal to 12.
116+ if * input == 4
117+ && ( * input. add ( ACCOUNT1_DATA_LEN ) . cast :: < u64 > ( ) == Account :: LEN as u64 )
118+ && ( * input. add ( ACCOUNT2_HEADER_OFFSET ) == NON_DUP_MARKER )
119+ && ( * input. add ( ACCOUNT2_DATA_LEN ) . cast :: < u64 > ( ) == Mint :: LEN as u64 )
120+ && ( * input. add ( IX12_ACCOUNT3_HEADER_OFFSET ) == NON_DUP_MARKER )
121+ && ( * input. add ( IX12_ACCOUNT3_DATA_LEN ) . cast :: < u64 > ( ) == Account :: LEN as u64 )
122+ && ( * input. add ( IX12_ACCOUNT4_HEADER_OFFSET ) == NON_DUP_MARKER )
123+ {
124+ // The `authority` account can have variable data length.
125+ let account_4_data_len_aligned =
126+ align ( * input. add ( IX12_ACCOUNT4_DATA_LEN ) . cast :: < u64 > ( ) ) as usize ;
127+ let offset = IX12_EXPECTED_INSTRUCTION_DATA_LEN_OFFSET + account_4_data_len_aligned;
128+
129+ // Check that we have enough instruction data.
130+ //
131+ // Expected: instruction discriminator (u8) + amount (u64) + decimals (u8)
132+ if input. add ( offset) . cast :: < u64 > ( ) . read ( ) >= 10 {
133+ let discriminator = input. add ( offset + size_of :: < u64 > ( ) ) . cast :: < u8 > ( ) . read ( ) ;
134+
135+ // Check for transfer discriminator.
136+ if likely ( discriminator == TokenInstruction :: TransferChecked as u8 ) {
137+ // instruction data length (u64) + discriminator (u8)
138+ let instruction_data = unsafe { from_raw_parts ( input. add ( offset + 9 ) , 9 ) } ;
139+
140+ let accounts = unsafe {
141+ [
142+ transmute :: < * mut u8 , AccountInfo > ( input. add ( ACCOUNT1_HEADER_OFFSET ) ) ,
143+ transmute :: < * mut u8 , AccountInfo > ( input. add ( ACCOUNT2_HEADER_OFFSET ) ) ,
144+ transmute :: < * mut u8 , AccountInfo > ( input. add ( IX12_ACCOUNT3_HEADER_OFFSET ) ) ,
145+ transmute :: < * mut u8 , AccountInfo > ( input. add ( IX12_ACCOUNT4_HEADER_OFFSET ) ) ,
146+ ]
147+ } ;
148+
149+ #[ cfg( feature = "logging" ) ]
150+ pinocchio:: msg!( "Instruction: TransferChecked" ) ;
151+
152+ return match process_transfer_checked ( & accounts, instruction_data) {
153+ Ok ( ( ) ) => SUCCESS ,
154+ Err ( error) => {
155+ log_error ( & error) ;
156+ error. into ( )
157+ }
158+ } ;
159+ }
160+ }
161+ }
162+ // Fast path for `transfer`.
163+ //
164+ // It expects 3 accounts:
165+ // 1. source: must be a token account (165 length)
166+ // 2. destination: must be a token account (165 length)
167+ // 3. authority: can be any account (variable length)
168+ //
169+ // Instruction data is expected to be at least 8 bytes
170+ // and discriminator equal to 3.
171+ else if * input == 3
172+ && ( * input. add ( ACCOUNT1_DATA_LEN ) . cast :: < u64 > ( ) == Account :: LEN as u64 )
173+ && ( * input. add ( ACCOUNT2_HEADER_OFFSET ) == NON_DUP_MARKER )
174+ && ( * input. add ( ACCOUNT2_DATA_LEN ) . cast :: < u64 > ( ) == Account :: LEN as u64 )
175+ && ( * input. add ( IX3_ACCOUNT3_HEADER_OFFSET ) == NON_DUP_MARKER )
176+ {
177+ // The `authority` account can have variable data length.
178+ let account_3_data_len_aligned =
179+ align ( * input. add ( IX3_ACCOUNT3_DATA_LEN ) . cast :: < u64 > ( ) ) as usize ;
180+ let offset = IX3_INSTRUCTION_DATA_LEN_OFFSET + account_3_data_len_aligned;
181+
182+ // Check that we have enough instruction data.
183+ if likely ( input. add ( offset) . cast :: < u64 > ( ) . read ( ) >= 9 ) {
184+ let discriminator = input. add ( offset + size_of :: < u64 > ( ) ) . cast :: < u8 > ( ) . read ( ) ;
185+
186+ // Check for transfer discriminator.
187+ if likely ( discriminator == TokenInstruction :: Transfer as u8 ) {
188+ let instruction_data =
189+ unsafe { from_raw_parts ( input. add ( offset + 9 ) , size_of :: < u64 > ( ) ) } ;
190+
191+ let accounts = unsafe {
192+ [
193+ transmute :: < * mut u8 , AccountInfo > ( input. add ( ACCOUNT1_HEADER_OFFSET ) ) ,
194+ transmute :: < * mut u8 , AccountInfo > ( input. add ( ACCOUNT2_HEADER_OFFSET ) ) ,
195+ transmute :: < * mut u8 , AccountInfo > ( input. add ( IX3_ACCOUNT3_HEADER_OFFSET ) ) ,
196+ ]
197+ } ;
198+
199+ #[ cfg( feature = "logging" ) ]
200+ pinocchio:: msg!( "Instruction: Transfer" ) ;
201+
202+ return match process_transfer ( & accounts, instruction_data) {
203+ Ok ( ( ) ) => SUCCESS ,
204+ Err ( error) => {
205+ log_error ( & error) ;
206+ error. into ( )
207+ }
208+ } ;
209+ }
210+ }
211+ }
212+
213+ // Entrypoint for the remaining instructions.
214+
215+ const UNINIT : MaybeUninit < AccountInfo > = MaybeUninit :: < AccountInfo > :: uninit ( ) ;
216+ let mut accounts = [ UNINIT ; { MAX_TX_ACCOUNTS } ] ;
217+
218+ let ( _, count, instruction_data) = deserialize ( input, & mut accounts) ;
219+
220+ match process_instruction (
221+ from_raw_parts ( accounts. as_ptr ( ) as _ , count) ,
222+ instruction_data,
223+ ) {
224+ Ok ( ( ) ) => SUCCESS ,
225+ Err ( error) => error. into ( ) ,
226+ }
227+ }
228+
19229/// Log an error.
20230#[ cold]
21231fn log_error ( error : & ProgramError ) {
22- pinocchio :: log :: sol_log ( error. to_str :: < TokenError > ( ) ) ;
232+ sol_log ( error. to_str :: < TokenError > ( ) ) ;
23233}
24234
25235/// Process an instruction.
@@ -30,11 +240,7 @@ fn log_error(error: &ProgramError) {
30240/// instructions, since it is not sound to have a "batch" instruction inside
31241/// another "batch" instruction.
32242#[ inline( always) ]
33- pub fn process_instruction (
34- _program_id : & Pubkey ,
35- accounts : & [ AccountInfo ] ,
36- instruction_data : & [ u8 ] ,
37- ) -> ProgramResult {
243+ pub fn process_instruction ( accounts : & [ AccountInfo ] , instruction_data : & [ u8 ] ) -> ProgramResult {
38244 let [ discriminator, remaining @ ..] = instruction_data else {
39245 return Err ( TokenError :: InvalidInstruction . into ( ) ) ;
40246 } ;
@@ -138,12 +344,12 @@ pub(crate) fn inner_process_instruction(
138344
139345 process_burn_checked ( accounts, instruction_data)
140346 }
141- // 16 - InitializeAccount2
142- 16 => {
347+ // 17 - SyncNative
348+ 17 => {
143349 #[ cfg( feature = "logging" ) ]
144- pinocchio:: msg!( "Instruction: InitializeAccount2 " ) ;
350+ pinocchio:: msg!( "Instruction: SyncNative " ) ;
145351
146- process_initialize_account2 ( accounts, instruction_data )
352+ process_sync_native ( accounts)
147353 }
148354 // 18 - InitializeAccount3
149355 18 => {
@@ -159,6 +365,13 @@ pub(crate) fn inner_process_instruction(
159365
160366 process_initialize_mint2 ( accounts, instruction_data)
161367 }
368+ // 22 - InitializeImmutableOwner
369+ 22 => {
370+ #[ cfg( feature = "logging" ) ]
371+ pinocchio:: msg!( "Instruction: InitializeImmutableOwner" ) ;
372+
373+ process_initialize_immutable_owner ( accounts)
374+ }
162375 d => inner_process_remaining_instruction ( accounts, instruction_data, d) ,
163376 }
164377}
@@ -231,12 +444,12 @@ fn inner_process_remaining_instruction(
231444
232445 process_mint_to_checked ( accounts, instruction_data)
233446 }
234- // 17 - SyncNative
235- 17 => {
447+ // 16 - InitializeAccount2
448+ 16 => {
236449 #[ cfg( feature = "logging" ) ]
237- pinocchio:: msg!( "Instruction: SyncNative " ) ;
450+ pinocchio:: msg!( "Instruction: InitializeAccount2 " ) ;
238451
239- process_sync_native ( accounts)
452+ process_initialize_account2 ( accounts, instruction_data )
240453 }
241454 // 19 - InitializeMultisig2
242455 19 => {
@@ -252,13 +465,6 @@ fn inner_process_remaining_instruction(
252465
253466 process_get_account_data_size ( accounts)
254467 }
255- // 22 - InitializeImmutableOwner
256- 22 => {
257- #[ cfg( feature = "logging" ) ]
258- pinocchio:: msg!( "Instruction: InitializeImmutableOwner" ) ;
259-
260- process_initialize_immutable_owner ( accounts)
261- }
262468 // 23 - AmountToUiAmount
263469 23 => {
264470 #[ cfg( feature = "logging" ) ]
0 commit comments