1
1
use {
2
2
crate :: processor:: * ,
3
+ core:: {
4
+ mem:: { 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_into,
10
+ hint:: likely,
11
+ no_allocator, nostd_panic_handler,
6
12
program_error:: { ProgramError , ToStr } ,
7
- pubkey:: Pubkey ,
8
- ProgramResult ,
13
+ ProgramResult , MAX_TX_ACCOUNTS , SUCCESS ,
9
14
} ,
10
15
pinocchio_token_interface:: error:: TokenError ,
11
16
} ;
12
17
13
- program_entrypoint ! ( process_instruction) ;
14
18
// Do not allocate memory.
15
19
no_allocator ! ( ) ;
16
20
// Use the no_std panic handler.
17
21
nostd_panic_handler ! ( ) ;
18
22
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
+
19
216
/// Log an error.
20
217
#[ cold]
21
218
fn log_error ( error : & ProgramError ) {
@@ -30,11 +227,7 @@ fn log_error(error: &ProgramError) {
30
227
/// instructions, since it is not sound to have a "batch" instruction inside
31
228
/// another "batch" instruction.
32
229
#[ 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 {
38
231
let [ discriminator, remaining @ ..] = instruction_data else {
39
232
return Err ( TokenError :: InvalidInstruction . into ( ) ) ;
40
233
} ;
0 commit comments