1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
use {
crate::{instructions::invalid_argument_error, TokenInterface},
core::{marker::PhantomData, mem::MaybeUninit, slice::from_raw_parts},
solana_address::Address,
solana_instruction_view::{
cpi::{invoke_signed_unchecked, CpiAccount, Signer, MAX_CPI_ACCOUNTS},
InstructionAccount, InstructionView,
},
solana_program_error::{ProgramError, ProgramResult},
};
/// The instruction discriminator.
const DISCRIMINATOR: u8 = 255;
/// The maximum instruction data buffer length required for a batch.
const MAX_DATA_LEN: usize = 10 * 1024;
/// The maximum account buffer length required for a batch.
const MAX_ACCOUNTS_LEN: usize = MAX_CPI_ACCOUNTS;
/// The size of the batch instruction header.
///
/// The header of each instruction consists of two `u8` values:
/// - number of the accounts
/// - length of the instruction data
const IX_HEADER_SIZE: usize = 2;
/// A collection of instructions that can be serialized into a token `Batch`
/// instruction.
pub struct Batch<'account, 'state, Program: TokenInterface> {
/// The instruction data for the batch instruction.
///
/// The first byte is reserved for the batch instruction discriminator,
/// and each instruction's data is prefixed with a byte indicating the
/// number of instruction accounts and a byte indicating the length of
/// the instruction data.
data: &'state mut [MaybeUninit<u8>],
/// The instruction accounts for the batch instruction.
instruction_accounts: &'state mut [MaybeUninit<InstructionAccount<'account>>],
/// The accounts for the batch instruction.
accounts: &'state mut [MaybeUninit<CpiAccount<'account>>],
/// The current length of the instruction data.
data_len: usize,
/// The current length of the accounts.
accounts_len: usize,
/// The current length of the instruction accounts.
instruction_accounts_len: usize,
/// Phantom data for the program.
_program: PhantomData<Program>,
}
impl<'account, 'state, Program: TokenInterface> Batch<'account, 'state, Program>
where
'account: 'state,
{
/// The instruction discriminator.
pub const DISCRIMINATOR: u8 = DISCRIMINATOR;
/// The maximum instruction data buffer length required for a batch.
pub const MAX_DATA_LEN: usize = MAX_DATA_LEN;
/// The maximum account buffer length required for a batch.
pub const MAX_ACCOUNTS_LEN: usize = MAX_ACCOUNTS_LEN;
/// The size of the batch instruction header.
pub const IX_HEADER_SIZE: usize = IX_HEADER_SIZE;
/// Creates a new `Batch` with the provided buffers.
#[inline(always)]
pub fn new(
data: &'state mut [MaybeUninit<u8>],
instruction_accounts: &'state mut [MaybeUninit<InstructionAccount<'account>>],
accounts: &'state mut [MaybeUninit<CpiAccount<'account>>],
) -> Result<Self, ProgramError> {
if data.is_empty() {
return Err(invalid_argument_error());
}
// The first byte of the instruction data is reserved for
// the batch discriminator.
data[0].write(DISCRIMINATOR);
Ok(Self {
data,
instruction_accounts,
accounts,
data_len: 1,
accounts_len: 0,
instruction_accounts_len: 0,
_program: PhantomData,
})
}
/// Invokes the instruction with `Program::ID`.
#[inline(always)]
pub fn invoke(&self) -> ProgramResult {
self.invoke_with_unverified_program(&Program::ID)
}
/// Invokes the instruction with `Program::ID` and signer seeds.
#[inline(always)]
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
self.invoke_signed_with_unverified_program(signers, &Program::ID)
}
/// Invokes the instruction after verifying the `program` address.
#[inline(always)]
pub fn invoke_with_program(&self, program: &Address) -> ProgramResult {
self.invoke_signed_with_program(&[], program)
}
/// Invokes the instruction with signer seeds after verifying the `program`
/// address.
#[inline(always)]
pub fn invoke_signed_with_program(
&self,
signers: &[Signer],
program: &Address,
) -> ProgramResult {
Program::verify(program)?;
self.invoke_signed_with_unverified_program(signers, program)
}
/// Invokes the instruction with `program` without verifying it.
///
/// Use this when `program` has already been verified. Otherwise, prefer
/// `invoke_with_program`.
///
/// # Important
///
/// This method does not verify that `program` satisfies
/// [`TokenInterface::verify`]. The caller must ensure the program address
/// has already been checked and corresponds to the expected
/// token program.
#[inline(always)]
pub fn invoke_with_unverified_program(&self, program: &Address) -> ProgramResult {
self.invoke_signed_with_unverified_program(&[], program)
}
/// Invokes the instruction with signer seeds and `program` without
/// verifying the program address.
///
/// Use this when `program` has already been verified. Otherwise, prefer
/// `invoke_signed_with_program`.
///
/// # Important
///
/// This method does not verify that `program` satisfies
/// [`TokenInterface::verify`]. The caller must ensure the program address
/// has already been checked and corresponds to the expected
/// token program.
#[inline(always)]
pub fn invoke_signed_with_unverified_program(
&self,
signers: &[Signer],
program: &Address,
) -> ProgramResult {
unsafe {
invoke_signed_unchecked(
&InstructionView {
program_id: program,
accounts: from_raw_parts(
self.instruction_accounts.as_ptr() as _,
self.instruction_accounts_len,
),
data: from_raw_parts(self.data.as_ptr() as _, self.data_len),
},
from_raw_parts(self.accounts.as_ptr() as _, self.accounts_len),
signers,
);
}
Ok(())
}
#[inline(always)]
pub fn push(
&mut self,
write_accounts: impl FnOnce(
&mut [MaybeUninit<CpiAccount<'account>>],
) -> Result<usize, ProgramError>,
write_instruction_accounts: impl FnOnce(
&mut [MaybeUninit<InstructionAccount<'account>>],
) -> Result<usize, ProgramError>,
write_data: impl FnOnce(&mut [MaybeUninit<u8>]) -> Result<usize, ProgramError>,
) -> ProgramResult {
// Ensure that there is enough space for another instruction.
if self.data_len + IX_HEADER_SIZE > self.data.len() {
return Err(invalid_argument_error());
}
let written_data = write_data(&mut self.data[self.data_len + IX_HEADER_SIZE..])?;
let written_accounts = write_accounts(&mut self.accounts[self.accounts_len..])?;
let written_instruction_accounts = write_instruction_accounts(
&mut self.instruction_accounts[self.instruction_accounts_len..],
)?;
// If all writes succeeded, update the lengths and write the instruction
// header.
self.accounts_len += written_accounts;
self.instruction_accounts_len += written_instruction_accounts;
self.data[self.data_len].write(written_instruction_accounts as u8);
self.data[self.data_len + 1].write(written_data as u8);
self.data_len += written_data + IX_HEADER_SIZE;
Ok(())
}
/// Returns the length of the instruction data header for a batch with the
/// given number of instructions.
pub const fn header_data_len(instructions_len: usize) -> usize {
// 1 bytes discriminator + 2 bytes (header) per instruction
1usize.saturating_add(instructions_len.saturating_mul(IX_HEADER_SIZE))
}
}
#[cfg(feature = "alloc")]
/// A state object that contains the buffers for a `Batch` instruction.
pub struct BatchState<'account, Program: TokenInterface> {
/// Container for the instruction data of the batch instruction.
data: Box<[MaybeUninit<u8>]>,
/// Container for the instruction accounts of the batch instruction.
instruction_accounts: Box<[MaybeUninit<InstructionAccount<'account>>]>,
/// Container for the accounts of the batch instruction.
accounts: Box<[MaybeUninit<CpiAccount<'account>>]>,
/// Phantom data for the program.
_program: PhantomData<Program>,
}
#[cfg(feature = "alloc")]
impl<'account, Program: TokenInterface> BatchState<'account, Program> {
#[inline(always)]
pub fn new(accounts_len: usize, data_len: usize) -> Self {
Self {
data: Box::new_uninit_slice(data_len),
instruction_accounts: Box::new_uninit_slice(accounts_len),
accounts: Box::new_uninit_slice(accounts_len),
_program: PhantomData,
}
}
#[inline(always)]
pub fn as_batch<'state>(
&'state mut self,
) -> Result<Batch<'account, 'state, Program>, ProgramError>
where
Self: 'account,
{
Batch::new(
self.data.as_mut(),
self.instruction_accounts.as_mut(),
self.accounts.as_mut(),
)
}
}
/// A trait for instructions that can be consumed directly into a `Batch`.
pub trait IntoBatch<Program: TokenInterface> {
/// Serializes `self` into the provided batch.
fn into_batch<'account, 'state>(
self,
batch: &mut Batch<'account, 'state, Program>,
) -> ProgramResult
where
Self: 'account + 'state;
}