Skip to content

Commit 1099452

Browse files
committed
Add fast path entrypoint
1 parent d161b3b commit 1099452

File tree

1 file changed

+202
-9
lines changed

1 file changed

+202
-9
lines changed

p-token/src/entrypoint.rs

Lines changed: 202 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,218 @@
11
use {
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.
1519
no_allocator!();
1620
// Use the no_std panic handler.
1721
nostd_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]
21218
fn 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

Comments
 (0)