Skip to content

Commit 2769f3b

Browse files
authored
p-token: Add custom entrypoint (#85)
* Add fast path entrypoint * Reorder processor * Organize imports * Add missing logs * Fix review comments * Fix comment in constant * Use non-duplicate constant
1 parent 84fcb28 commit 2769f3b

File tree

1 file changed

+232
-26
lines changed

1 file changed

+232
-26
lines changed

p-token/src/entrypoint.rs

Lines changed: 232 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,235 @@
11
use {
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.
1524
no_allocator!();
1625
// Use the no_std panic handler.
1726
nostd_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]
21231
fn 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

Comments
 (0)