1
1
use {
2
2
crate :: processor:: * ,
3
+ core:: {
4
+ mem:: { size_of, transmute, MaybeUninit } ,
5
+ slice:: from_raw_parts,
6
+ } ,
3
7
pinocchio:: {
4
8
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,
6
13
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 } ,
9
20
} ,
10
- pinocchio_token_interface:: error:: TokenError ,
11
21
} ;
12
22
13
- program_entrypoint ! ( process_instruction) ;
14
23
// Do not allocate memory.
15
24
no_allocator ! ( ) ;
16
25
// Use the no_std panic handler.
17
26
nostd_panic_handler ! ( ) ;
18
27
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
+
19
229
/// Log an error.
20
230
#[ cold]
21
231
fn log_error ( error : & ProgramError ) {
22
- pinocchio :: log :: sol_log ( error. to_str :: < TokenError > ( ) ) ;
232
+ sol_log ( error. to_str :: < TokenError > ( ) ) ;
23
233
}
24
234
25
235
/// Process an instruction.
@@ -30,11 +240,7 @@ fn log_error(error: &ProgramError) {
30
240
/// instructions, since it is not sound to have a "batch" instruction inside
31
241
/// another "batch" instruction.
32
242
#[ 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 {
38
244
let [ discriminator, remaining @ ..] = instruction_data else {
39
245
return Err ( TokenError :: InvalidInstruction . into ( ) ) ;
40
246
} ;
@@ -138,12 +344,12 @@ pub(crate) fn inner_process_instruction(
138
344
139
345
process_burn_checked ( accounts, instruction_data)
140
346
}
141
- // 16 - InitializeAccount2
142
- 16 => {
347
+ // 17 - SyncNative
348
+ 17 => {
143
349
#[ cfg( feature = "logging" ) ]
144
- pinocchio:: msg!( "Instruction: InitializeAccount2 " ) ;
350
+ pinocchio:: msg!( "Instruction: SyncNative " ) ;
145
351
146
- process_initialize_account2 ( accounts, instruction_data )
352
+ process_sync_native ( accounts)
147
353
}
148
354
// 18 - InitializeAccount3
149
355
18 => {
@@ -159,6 +365,13 @@ pub(crate) fn inner_process_instruction(
159
365
160
366
process_initialize_mint2 ( accounts, instruction_data)
161
367
}
368
+ // 22 - InitializeImmutableOwner
369
+ 22 => {
370
+ #[ cfg( feature = "logging" ) ]
371
+ pinocchio:: msg!( "Instruction: InitializeImmutableOwner" ) ;
372
+
373
+ process_initialize_immutable_owner ( accounts)
374
+ }
162
375
d => inner_process_remaining_instruction ( accounts, instruction_data, d) ,
163
376
}
164
377
}
@@ -231,12 +444,12 @@ fn inner_process_remaining_instruction(
231
444
232
445
process_mint_to_checked ( accounts, instruction_data)
233
446
}
234
- // 17 - SyncNative
235
- 17 => {
447
+ // 16 - InitializeAccount2
448
+ 16 => {
236
449
#[ cfg( feature = "logging" ) ]
237
- pinocchio:: msg!( "Instruction: SyncNative " ) ;
450
+ pinocchio:: msg!( "Instruction: InitializeAccount2 " ) ;
238
451
239
- process_sync_native ( accounts)
452
+ process_initialize_account2 ( accounts, instruction_data )
240
453
}
241
454
// 19 - InitializeMultisig2
242
455
19 => {
@@ -252,13 +465,6 @@ fn inner_process_remaining_instruction(
252
465
253
466
process_get_account_data_size ( accounts)
254
467
}
255
- // 22 - InitializeImmutableOwner
256
- 22 => {
257
- #[ cfg( feature = "logging" ) ]
258
- pinocchio:: msg!( "Instruction: InitializeImmutableOwner" ) ;
259
-
260
- process_initialize_immutable_owner ( accounts)
261
- }
262
468
// 23 - AmountToUiAmount
263
469
23 => {
264
470
#[ cfg( feature = "logging" ) ]
0 commit comments