pinocchio_token/instructions/
transfer_checked.rs1use {
2 crate::{
3 instructions::{
4 account_borrow_failed_error, invalid_argument_error, CpiWriter, MAX_MULTISIG_SIGNERS,
5 },
6 write_bytes, UNINIT_BYTE, UNINIT_CPI_ACCOUNT, UNINIT_INSTRUCTION_ACCOUNT,
7 },
8 core::{mem::MaybeUninit, slice::from_raw_parts},
9 solana_account_view::AccountView,
10 solana_instruction_view::{
11 cpi::{invoke_signed_unchecked, CpiAccount, Signer},
12 InstructionAccount, InstructionView,
13 },
14 solana_program_error::{ProgramError, ProgramResult},
15};
16
17pub struct TransferChecked<'account, 'multisig, MultisigSigner: AsRef<AccountView>> {
41 pub from: &'account AccountView,
43
44 pub mint: &'account AccountView,
46
47 pub to: &'account AccountView,
49
50 pub authority: &'account AccountView,
52
53 pub multisig_signers: &'multisig [MultisigSigner],
55
56 pub amount: u64,
58
59 pub decimals: u8,
61}
62
63impl<'account> TransferChecked<'account, '_, &'account AccountView> {
64 pub const DISCRIMINATOR: u8 = 12;
66
67 pub const MAX_ACCOUNTS_LEN: usize = 4 + MAX_MULTISIG_SIGNERS;
73
74 pub const DATA_LEN: usize = 10;
79
80 #[inline(always)]
83 pub fn new(
84 from: &'account AccountView,
85 mint: &'account AccountView,
86 to: &'account AccountView,
87 authority: &'account AccountView,
88 amount: u64,
89 decimals: u8,
90 ) -> Self {
91 Self::with_multisig_signers(from, mint, to, authority, amount, decimals, &[])
92 }
93}
94
95impl<'account, 'multisig, MultisigSigner: AsRef<AccountView>>
96 TransferChecked<'account, 'multisig, MultisigSigner>
97{
98 #[inline(always)]
101 pub fn with_multisig_signers(
102 from: &'account AccountView,
103 mint: &'account AccountView,
104 to: &'account AccountView,
105 authority: &'account AccountView,
106 amount: u64,
107 decimals: u8,
108 multisig_signers: &'multisig [MultisigSigner],
109 ) -> Self {
110 Self {
111 from,
112 mint,
113 to,
114 authority,
115 multisig_signers,
116 amount,
117 decimals,
118 }
119 }
120
121 #[inline(always)]
122 pub fn invoke(&self) -> ProgramResult {
123 self.invoke_signed(&[])
124 }
125
126 #[inline(always)]
127 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
128 if self.multisig_signers.len() > MAX_MULTISIG_SIGNERS {
129 Err(ProgramError::InvalidArgument)?;
130 }
131
132 let mut instruction_accounts =
133 [UNINIT_INSTRUCTION_ACCOUNT; TransferChecked::MAX_ACCOUNTS_LEN];
134 let written_instruction_accounts =
135 self.write_instruction_accounts(&mut instruction_accounts)?;
136
137 let mut accounts = [UNINIT_CPI_ACCOUNT; TransferChecked::MAX_ACCOUNTS_LEN];
138 let written_accounts = self.write_accounts(&mut accounts)?;
139
140 let mut instruction_data = [UNINIT_BYTE; TransferChecked::DATA_LEN];
141 let written_instruction_data = self.write_instruction_data(&mut instruction_data)?;
142
143 unsafe {
144 invoke_signed_unchecked(
145 &InstructionView {
146 program_id: &crate::ID,
147 accounts: from_raw_parts(
148 instruction_accounts.as_ptr() as _,
149 written_instruction_accounts,
150 ),
151 data: from_raw_parts(instruction_data.as_ptr() as _, written_instruction_data),
152 },
153 from_raw_parts(accounts.as_ptr() as _, written_accounts),
154 signers,
155 );
156 }
157
158 Ok(())
159 }
160}
161
162impl<MultisigSigner: AsRef<AccountView>> CpiWriter for TransferChecked<'_, '_, MultisigSigner> {
163 #[inline(always)]
164 fn write_accounts<'cpi>(
165 &self,
166 accounts: &mut [MaybeUninit<CpiAccount<'cpi>>],
167 ) -> Result<usize, ProgramError>
168 where
169 Self: 'cpi,
170 {
171 write_accounts(
172 self.from,
173 self.mint,
174 self.to,
175 self.authority,
176 self.multisig_signers,
177 accounts,
178 )
179 }
180
181 #[inline(always)]
182 fn write_instruction_accounts<'cpi>(
183 &self,
184 accounts: &mut [MaybeUninit<InstructionAccount<'cpi>>],
185 ) -> Result<usize, ProgramError>
186 where
187 Self: 'cpi,
188 {
189 write_instruction_accounts(
190 self.from,
191 self.mint,
192 self.to,
193 self.authority,
194 self.multisig_signers,
195 accounts,
196 )
197 }
198
199 #[inline(always)]
200 fn write_instruction_data(&self, data: &mut [MaybeUninit<u8>]) -> Result<usize, ProgramError> {
201 write_instruction_data(self.amount, self.decimals, data)
202 }
203}
204
205impl<MultisigSigner: AsRef<AccountView>> super::IntoBatch
206 for TransferChecked<'_, '_, MultisigSigner>
207{
208 #[inline(always)]
209 fn into_batch<'account, 'state>(
210 self,
211 batch: &mut super::Batch<'account, 'state>,
212 ) -> ProgramResult
213 where
214 Self: 'account + 'state,
215 {
216 batch.push(
217 |accounts| {
218 write_accounts(
219 self.from,
220 self.mint,
221 self.to,
222 self.authority,
223 self.multisig_signers,
224 accounts,
225 )
226 },
227 |accounts| {
228 write_instruction_accounts(
229 self.from,
230 self.mint,
231 self.to,
232 self.authority,
233 self.multisig_signers,
234 accounts,
235 )
236 },
237 |data| write_instruction_data(self.amount, self.decimals, data),
238 )
239 }
240}
241
242#[inline(always)]
243fn write_accounts<'account, 'multisig, 'out, MultisigSigner: AsRef<AccountView>>(
244 from: &'account AccountView,
245 mint: &'account AccountView,
246 to: &'account AccountView,
247 authority: &'account AccountView,
248 multisig_signers: &'multisig [MultisigSigner],
249 accounts: &mut [MaybeUninit<CpiAccount<'out>>],
250) -> Result<usize, ProgramError>
251where
252 'account: 'out,
253 'multisig: 'out,
254{
255 let expected_accounts = 4 + multisig_signers.len();
256
257 if expected_accounts > accounts.len() {
258 return Err(invalid_argument_error());
259 }
260
261 if from.is_borrowed() | to.is_borrowed() {
262 return Err(account_borrow_failed_error());
263 }
264
265 CpiAccount::init_from_account_view(from, &mut accounts[0]);
266
267 CpiAccount::init_from_account_view(mint, &mut accounts[1]);
268
269 CpiAccount::init_from_account_view(to, &mut accounts[2]);
270
271 CpiAccount::init_from_account_view(authority, &mut accounts[3]);
272
273 for (account, signer) in accounts[4..expected_accounts]
274 .iter_mut()
275 .zip(multisig_signers.iter())
276 {
277 CpiAccount::init_from_account_view(signer.as_ref(), account);
278 }
279
280 Ok(expected_accounts)
281}
282
283#[inline(always)]
284fn write_instruction_accounts<'account, 'multisig, 'out, MultisigSigner: AsRef<AccountView>>(
285 from: &'account AccountView,
286 mint: &'account AccountView,
287 to: &'account AccountView,
288 authority: &'account AccountView,
289 multisig_signers: &'multisig [MultisigSigner],
290 accounts: &mut [MaybeUninit<InstructionAccount<'out>>],
291) -> Result<usize, ProgramError>
292where
293 'account: 'out,
294 'multisig: 'out,
295{
296 let expected_accounts = 4 + multisig_signers.len();
297
298 if expected_accounts > accounts.len() {
299 return Err(invalid_argument_error());
300 }
301
302 accounts[0].write(InstructionAccount::writable(from.address()));
303
304 accounts[1].write(InstructionAccount::readonly(mint.address()));
305
306 accounts[2].write(InstructionAccount::writable(to.address()));
307
308 accounts[3].write(InstructionAccount::new(
309 authority.address(),
310 false,
311 multisig_signers.is_empty(),
312 ));
313
314 for (account, signer) in accounts[4..expected_accounts]
315 .iter_mut()
316 .zip(multisig_signers.iter())
317 {
318 account.write(InstructionAccount::readonly_signer(
319 signer.as_ref().address(),
320 ));
321 }
322
323 Ok(expected_accounts)
324}
325
326#[inline(always)]
327fn write_instruction_data(
328 amount: u64,
329 decimals: u8,
330 data: &mut [MaybeUninit<u8>],
331) -> Result<usize, ProgramError> {
332 if data.len() < TransferChecked::DATA_LEN {
333 return Err(invalid_argument_error());
334 }
335
336 data[0].write(TransferChecked::DISCRIMINATOR);
337
338 write_bytes(&mut data[1..9], &amount.to_le_bytes());
339
340 data[9].write(decimals);
341
342 Ok(TransferChecked::DATA_LEN)
343}