Skip to content

Commit baa5918

Browse files
authored
p-token: Add batch instruction (#35)
* Update workspace * Rename interface crate * Fix spelling * [wip]: Fix review comments * A few more fixes * Add batch instruction * Add test for batch * Clean up comments * Fix spelling * Fix merge * Remove test-sbf feature * Fix review comments
1 parent 3daf448 commit baa5918

29 files changed

+383
-105
lines changed

Diff for: interface/src/instruction.rs

+19-2
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,24 @@ pub enum TokenInstruction {
477477
///
478478
/// - `&str` The `ui_amount` of tokens to reformat.
479479
UiAmountToAmount,
480+
481+
/// Executes a batch of instructions. The instructions to be executed are specified
482+
/// in sequence on the instruction data. Each instruction provides:
483+
/// - `u8`: number of accounts
484+
/// - `u8`: instruction data length (includes the discriminator)
485+
/// - `u8`: instruction discriminator
486+
/// - `[u8]`: instruction data
487+
///
488+
/// Accounts follow a similar pattern, where accounts for each instruction are
489+
/// specified in sequence. Therefore, the number of accounts expected by this
490+
/// instruction is variable, i.e., it depends on the instructions provided.
491+
///
492+
/// Both the number of accounts and instruction data length are used to identify
493+
/// the slice of accounts and instruction data for each instruction.
494+
///
495+
/// Note that it is not sound to have a `batch` instruction that contains other
496+
/// `batch` instruction; an error will be raised when this is detected.
497+
Batch = 255,
480498
// Any new variants also need to be added to program-2022 `TokenInstruction`, so that the
481499
// latter remains a superset of this instruction set. New variants also need to be added to
482500
// token/js/src/instructions/types.ts to maintain @solana/spl-token compatibility
@@ -485,11 +503,10 @@ pub enum TokenInstruction {
485503
impl TryFrom<u8> for TokenInstruction {
486504
type Error = ProgramError;
487505

488-
#[inline(always)]
489506
fn try_from(value: u8) -> Result<Self, Self::Error> {
490507
match value {
491508
// SAFETY: `value` is guaranteed to be in the range of the enum variants.
492-
0..=24 => Ok(unsafe { core::mem::transmute::<u8, TokenInstruction>(value) }),
509+
0..=24 | 255 => Ok(unsafe { core::mem::transmute::<u8, TokenInstruction>(value) }),
493510
_ => Err(ProgramError::InvalidInstructionData),
494511
}
495512
}

Diff for: p-token/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ crate-type = ["cdylib"]
1313

1414
[features]
1515
logging = []
16-
test-sbf = []
1716

1817
[dependencies]
1918
pinocchio = { version = "0.7", git = "https://github.com/febo/pinocchio.git", branch = "febo/close-unstable" }

Diff for: p-token/src/entrypoint.rs

+83-57
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use pinocchio::{
22
account_info::AccountInfo, default_panic_handler, no_allocator, program_entrypoint,
33
program_error::ProgramError, pubkey::Pubkey, ProgramResult,
44
};
5-
use spl_token_interface::instruction::TokenInstruction;
65

76
use crate::processor::*;
87

@@ -14,218 +13,245 @@ default_panic_handler!();
1413

1514
/// Process an instruction.
1615
///
16+
/// In the first stage, the entrypoint checks the discriminator of the instruction data
17+
/// to determine whether the instruction is a "batch" instruction or a "regular" instruction.
18+
/// This avoids nesting of "batch" instructions, since it is not sound to have a "batch"
19+
/// instruction inside another "batch" instruction.
20+
#[inline(always)]
21+
pub fn process_instruction(
22+
_program_id: &Pubkey,
23+
accounts: &[AccountInfo],
24+
instruction_data: &[u8],
25+
) -> ProgramResult {
26+
let [discriminator, remaining @ ..] = instruction_data else {
27+
return Err(ProgramError::InvalidInstructionData);
28+
};
29+
30+
if *discriminator == 255 {
31+
// 255 - Batch
32+
#[cfg(feature = "logging")]
33+
pinocchio::msg!("Instruction: Batch");
34+
35+
return process_batch(accounts, remaining);
36+
}
37+
38+
inner_process_instruction(accounts, instruction_data)
39+
}
40+
41+
/// Process a "regular" instruction.
42+
///
1743
/// The processor of the token program is divided into two parts to reduce the overhead
1844
/// of having a large `match` statement. The first part of the processor handles the
1945
/// most common instructions, while the second part handles the remaining instructions.
46+
///
2047
/// The rationale is to reduce the overhead of making multiple comparisons for popular
2148
/// instructions.
2249
///
23-
/// Instructions on the first part of the processor:
50+
/// Instructions on the first part of the inner processor:
2451
///
25-
/// - `0`: `InitializeMint`
26-
/// - `3`: `Transfer`
27-
/// - `7`: `MintTo`
28-
/// - `9`: `CloseAccount`
52+
/// - `0`: `InitializeMint`
53+
/// - `1`: `InitializeAccount`
54+
/// - `3`: `Transfer`
55+
/// - `7`: `MintTo`
56+
/// - `9`: `CloseAccount`
57+
/// - `16`: `InitializeAccount2`
2958
/// - `18`: `InitializeAccount3`
3059
/// - `20`: `InitializeMint2`
3160
#[inline(always)]
32-
pub fn process_instruction(
33-
_program_id: &Pubkey,
61+
pub(crate) fn inner_process_instruction(
3462
accounts: &[AccountInfo],
3563
instruction_data: &[u8],
3664
) -> ProgramResult {
37-
let (discriminator, instruction_data) = instruction_data
38-
.split_first()
39-
.ok_or(ProgramError::InvalidInstructionData)?;
40-
let instruction = TokenInstruction::try_from(*discriminator)?;
65+
let [discriminator, instruction_data @ ..] = instruction_data else {
66+
return Err(ProgramError::InvalidInstructionData);
67+
};
4168

42-
match instruction {
69+
match *discriminator {
4370
// 0 - InitializeMint
44-
TokenInstruction::InitializeMint => {
71+
0 => {
4572
#[cfg(feature = "logging")]
4673
pinocchio::msg!("Instruction: InitializeMint");
4774

4875
process_initialize_mint(accounts, instruction_data)
4976
}
77+
// 1 - InitializeAccount
78+
1 => {
79+
#[cfg(feature = "logging")]
80+
pinocchio::msg!("Instruction: InitializeAccount");
5081

82+
process_initialize_account(accounts)
83+
}
5184
// 3 - Transfer
52-
TokenInstruction::Transfer => {
85+
3 => {
5386
#[cfg(feature = "logging")]
5487
pinocchio::msg!("Instruction: Transfer");
5588

5689
process_transfer(accounts, instruction_data)
5790
}
5891
// 7 - MintTo
59-
TokenInstruction::MintTo => {
92+
7 => {
6093
#[cfg(feature = "logging")]
6194
pinocchio::msg!("Instruction: MintTo");
6295

6396
process_mint_to(accounts, instruction_data)
6497
}
6598
// 9 - CloseAccount
66-
TokenInstruction::CloseAccount => {
99+
9 => {
67100
#[cfg(feature = "logging")]
68101
pinocchio::msg!("Instruction: CloseAccount");
69102

70103
process_close_account(accounts)
71104
}
105+
// 16 - InitializeAccount2
106+
16 => {
107+
#[cfg(feature = "logging")]
108+
pinocchio::msg!("Instruction: InitializeAccount2");
109+
110+
process_initialize_account2(accounts, instruction_data)
111+
}
72112
// 18 - InitializeAccount3
73-
TokenInstruction::InitializeAccount3 => {
113+
18 => {
74114
#[cfg(feature = "logging")]
75115
pinocchio::msg!("Instruction: InitializeAccount3");
76116

77117
process_initialize_account3(accounts, instruction_data)
78118
}
79119
// 20 - InitializeMint2
80-
TokenInstruction::InitializeMint2 => {
120+
20 => {
81121
#[cfg(feature = "logging")]
82122
pinocchio::msg!("Instruction: InitializeMint2");
83123

84124
process_initialize_mint2(accounts, instruction_data)
85125
}
86-
_ => process_remaining_instruction(accounts, instruction_data, instruction),
126+
d => inner_process_remaining_instruction(accounts, instruction_data, d),
87127
}
88128
}
89129

90-
/// Process the remaining instructions.
130+
/// Process a remaining "regular" instruction.
91131
///
92-
/// This function is called by the `process_instruction` function if the discriminator
132+
/// This function is called by the [`inner_process_instruction`] function if the discriminator
93133
/// does not match any of the common instructions. This function is used to reduce the
94-
/// overhead of having a large `match` statement in the `process_instruction` function.
95-
fn process_remaining_instruction(
134+
/// overhead of having a large `match` statement in the [`inner_process_instruction`] function.
135+
fn inner_process_remaining_instruction(
96136
accounts: &[AccountInfo],
97137
instruction_data: &[u8],
98-
instruction: TokenInstruction,
138+
discriminator: u8,
99139
) -> ProgramResult {
100-
match instruction {
101-
// 1 - InitializeAccount
102-
TokenInstruction::InitializeAccount => {
103-
#[cfg(feature = "logging")]
104-
pinocchio::msg!("Instruction: InitializeAccount");
105-
106-
process_initialize_account(accounts)
107-
}
140+
match discriminator {
108141
// 2 - InitializeMultisig
109-
TokenInstruction::InitializeMultisig => {
142+
2 => {
110143
#[cfg(feature = "logging")]
111144
pinocchio::msg!("Instruction: InitializeMultisig");
112145

113146
process_initialize_multisig(accounts, instruction_data)
114147
}
115148
// 4 - Approve
116-
TokenInstruction::Approve => {
149+
4 => {
117150
#[cfg(feature = "logging")]
118151
pinocchio::msg!("Instruction: Approve");
119152

120153
process_approve(accounts, instruction_data)
121154
}
122155
// 5 - Revoke
123-
TokenInstruction::Revoke => {
156+
5 => {
124157
#[cfg(feature = "logging")]
125158
pinocchio::msg!("Instruction: Revoke");
126159

127160
process_revoke(accounts, instruction_data)
128161
}
129162
// 6 - SetAuthority
130-
TokenInstruction::SetAuthority => {
163+
6 => {
131164
#[cfg(feature = "logging")]
132165
pinocchio::msg!("Instruction: SetAuthority");
133166

134167
process_set_authority(accounts, instruction_data)
135168
}
136169
// 8 - Burn
137-
TokenInstruction::Burn => {
170+
8 => {
138171
#[cfg(feature = "logging")]
139172
pinocchio::msg!("Instruction: Burn");
140173

141174
process_burn(accounts, instruction_data)
142175
}
143176
// 10 - FreezeAccount
144-
TokenInstruction::FreezeAccount => {
177+
10 => {
145178
#[cfg(feature = "logging")]
146179
pinocchio::msg!("Instruction: FreezeAccount");
147180

148181
process_freeze_account(accounts)
149182
}
150183
// 11 - ThawAccount
151-
TokenInstruction::ThawAccount => {
184+
11 => {
152185
#[cfg(feature = "logging")]
153186
pinocchio::msg!("Instruction: ThawAccount");
154187

155188
process_thaw_account(accounts)
156189
}
157190
// 12 - TransferChecked
158-
TokenInstruction::TransferChecked => {
191+
12 => {
159192
#[cfg(feature = "logging")]
160193
pinocchio::msg!("Instruction: TransferChecked");
161194

162195
process_transfer_checked(accounts, instruction_data)
163196
}
164197
// 13 - ApproveChecked
165-
TokenInstruction::ApproveChecked => {
198+
13 => {
166199
#[cfg(feature = "logging")]
167200
pinocchio::msg!("Instruction: ApproveChecked");
168201

169202
process_approve_checked(accounts, instruction_data)
170203
}
171204
// 14 - MintToChecked
172-
TokenInstruction::MintToChecked => {
205+
14 => {
173206
#[cfg(feature = "logging")]
174207
pinocchio::msg!("Instruction: MintToChecked");
175208

176209
process_mint_to_checked(accounts, instruction_data)
177210
}
178211
// 15 - BurnChecked
179-
TokenInstruction::BurnChecked => {
212+
15 => {
180213
#[cfg(feature = "logging")]
181214
pinocchio::msg!("Instruction: BurnChecked");
182215

183216
process_burn_checked(accounts, instruction_data)
184217
}
185-
// 16 - InitializeAccount2
186-
TokenInstruction::InitializeAccount2 => {
187-
#[cfg(feature = "logging")]
188-
pinocchio::msg!("Instruction: InitializeAccount2");
189-
190-
process_initialize_account2(accounts, instruction_data)
191-
}
192218
// 17 - SyncNative
193-
TokenInstruction::SyncNative => {
219+
17 => {
194220
#[cfg(feature = "logging")]
195221
pinocchio::msg!("Instruction: SyncNative");
196222

197223
process_sync_native(accounts)
198224
}
199225
// 19 - InitializeMultisig2
200-
TokenInstruction::InitializeMultisig2 => {
226+
19 => {
201227
#[cfg(feature = "logging")]
202228
pinocchio::msg!("Instruction: InitializeMultisig2");
203229

204230
process_initialize_multisig2(accounts, instruction_data)
205231
}
206232
// 21 - GetAccountDataSize
207-
TokenInstruction::GetAccountDataSize => {
233+
21 => {
208234
#[cfg(feature = "logging")]
209235
pinocchio::msg!("Instruction: GetAccountDataSize");
210236

211237
process_get_account_data_size(accounts)
212238
}
213239
// 22 - InitializeImmutableOwner
214-
TokenInstruction::InitializeImmutableOwner => {
240+
22 => {
215241
#[cfg(feature = "logging")]
216242
pinocchio::msg!("Instruction: InitializeImmutableOwner");
217243

218244
process_initialize_immutable_owner(accounts)
219245
}
220246
// 23 - AmountToUiAmount
221-
TokenInstruction::AmountToUiAmount => {
247+
23 => {
222248
#[cfg(feature = "logging")]
223249
pinocchio::msg!("Instruction: AmountToUiAmount");
224250

225251
process_amount_to_ui_amount(accounts, instruction_data)
226252
}
227253
// 24 - UiAmountToAmount
228-
TokenInstruction::UiAmountToAmount => {
254+
24 => {
229255
#[cfg(feature = "logging")]
230256
pinocchio::msg!("Instruction: UiAmountToAmount");
231257

0 commit comments

Comments
 (0)