Skip to main content

pinocchio_token/instructions/
transfer_checked.rs

1use {
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
17/// Transfers tokens from one account to another either directly or via a
18/// delegate.  If this account is associated with the native mint then equal
19/// amounts of SOL and Tokens will be transferred to the destination
20/// account.
21///
22/// This instruction differs from [`super::Transfer`] in that the token mint and
23/// decimals value is checked by the caller.  This may be useful when
24/// creating transactions offline or within a hardware wallet.
25///
26/// Accounts expected by this instruction:
27///
28///   * Single owner/delegate
29///   0. `[writable]` The source account.
30///   1. `[]` The token mint.
31///   2. `[writable]` The destination account.
32///   3. `[signer]` The source account's owner/delegate.
33///
34///   * Multisignature owner/delegate
35///   0. `[writable]` The source account.
36///   1. `[]` The token mint.
37///   2. `[writable]` The destination account.
38///   3. `[]` The source account's multisignature owner/delegate.
39///   4. `..+M` `[signer]` M signer accounts.
40pub struct TransferChecked<'account, 'multisig, MultisigSigner: AsRef<AccountView>> {
41    /// The source account.
42    pub from: &'account AccountView,
43
44    /// The token mint.
45    pub mint: &'account AccountView,
46
47    ///  The destination account.
48    pub to: &'account AccountView,
49
50    /// The source account's owner/delegate.
51    pub authority: &'account AccountView,
52
53    /// Multisignature signers.
54    pub multisig_signers: &'multisig [MultisigSigner],
55
56    /// The amount of tokens to transfer.
57    pub amount: u64,
58
59    /// Expected number of base 10 digits to the right of the decimal place.
60    pub decimals: u8,
61}
62
63impl<'account> TransferChecked<'account, '_, &'account AccountView> {
64    /// The instruction discriminator.
65    pub const DISCRIMINATOR: u8 = 12;
66
67    /// Maximum number of accounts expected by this instruction.
68    ///
69    /// The required number of accounts will depend whether the
70    /// source account has a single owner or a multisignature
71    /// owner.
72    pub const MAX_ACCOUNTS_LEN: usize = 4 + MAX_MULTISIG_SIGNERS;
73
74    /// Instruction data length:
75    ///   - discriminator (1 byte)
76    ///   - amount (8 bytes)
77    ///   - decimals (1 byte)
78    pub const DATA_LEN: usize = 10;
79
80    /// Creates a new `TransferChecked` instruction with a single
81    /// owner/delegate authority.
82    #[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    /// Creates a new `TransferChecked` instruction with a
99    /// multisignature owner/delegate authority and signer accounts.
100    #[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}