Skip to main content

pinocchio_token/instructions/
approve_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/// Approves a delegate.  A delegate is given the authority over tokens on
18/// behalf of the source account's owner.
19///
20/// This instruction differs from Approve in that the token mint and
21/// decimals value is checked by the caller.  This may be useful when
22/// creating transactions offline or within a hardware wallet.
23///
24/// Accounts expected by this instruction:
25///
26///   * Single owner
27///   0. `[writable]` The source account.
28///   1. `[]` The token mint.
29///   2. `[]` The delegate.
30///   3. `[signer]` The source account owner.
31///
32///   * Multisignature owner
33///   0. `[writable]` The source account.
34///   1. `[]` The token mint.
35///   2. `[]` The delegate.
36///   3. `[]` The source account's multisignature owner.
37///   4. `..+M` `[signer]` M signer accounts.
38pub struct ApproveChecked<'account, 'multisig, MultisigSigner: AsRef<AccountView>> {
39    /// The source account.
40    pub source: &'account AccountView,
41
42    /// The token mint.
43    pub mint: &'account AccountView,
44
45    /// The delegate.
46    pub delegate: &'account AccountView,
47
48    /// The source account owner.
49    pub authority: &'account AccountView,
50
51    /// Multisignature signers.
52    pub multisig_signers: &'multisig [MultisigSigner],
53
54    /// The amount of tokens the delegate is approved for.
55    pub amount: u64,
56
57    /// Expected number of base 10 digits to the right of the decimal place.
58    pub decimals: u8,
59}
60
61impl<'account> ApproveChecked<'account, '_, &'account AccountView> {
62    /// The instruction discriminator.
63    pub const DISCRIMINATOR: u8 = 13;
64
65    /// Maximum number of accounts expected by this instruction.
66    ///
67    /// The required number of accounts will depend whether the
68    /// source account has a single owner or a multisignature
69    /// owner.
70    pub const MAX_ACCOUNTS_LEN: usize = 4 + MAX_MULTISIG_SIGNERS;
71
72    /// Instruction data length:
73    ///   - discriminator (1 byte)
74    ///   - amount (8 bytes)
75    ///   - decimals (1 byte)
76    pub const DATA_LEN: usize = 10;
77
78    /// Creates a new `ApproveChecked` instruction with a single owner
79    /// authority.
80    #[inline(always)]
81    pub fn new(
82        source: &'account AccountView,
83        mint: &'account AccountView,
84        delegate: &'account AccountView,
85        authority: &'account AccountView,
86        amount: u64,
87        decimals: u8,
88    ) -> Self {
89        Self::with_multisig_signers(source, mint, delegate, authority, amount, decimals, &[])
90    }
91}
92
93impl<'account, 'multisig, MultisigSigner: AsRef<AccountView>>
94    ApproveChecked<'account, 'multisig, MultisigSigner>
95{
96    /// Creates a new `ApproveChecked` instruction with a
97    /// multisignature owner authority and signer accounts.
98    #[inline(always)]
99    pub fn with_multisig_signers(
100        source: &'account AccountView,
101        mint: &'account AccountView,
102        delegate: &'account AccountView,
103        authority: &'account AccountView,
104        amount: u64,
105        decimals: u8,
106        multisig_signers: &'multisig [MultisigSigner],
107    ) -> Self {
108        Self {
109            source,
110            mint,
111            delegate,
112            authority,
113            multisig_signers,
114            amount,
115            decimals,
116        }
117    }
118
119    #[inline(always)]
120    pub fn invoke(&self) -> ProgramResult {
121        self.invoke_signed(&[])
122    }
123
124    #[inline(always)]
125    pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
126        if self.multisig_signers.len() > MAX_MULTISIG_SIGNERS {
127            Err(ProgramError::InvalidArgument)?;
128        }
129
130        let mut instruction_accounts =
131            [UNINIT_INSTRUCTION_ACCOUNT; ApproveChecked::MAX_ACCOUNTS_LEN];
132        let written_instruction_accounts =
133            self.write_instruction_accounts(&mut instruction_accounts)?;
134
135        let mut accounts = [UNINIT_CPI_ACCOUNT; ApproveChecked::MAX_ACCOUNTS_LEN];
136        let written_accounts = self.write_accounts(&mut accounts)?;
137
138        let mut instruction_data = [UNINIT_BYTE; ApproveChecked::DATA_LEN];
139        let written_instruction_data = self.write_instruction_data(&mut instruction_data)?;
140
141        unsafe {
142            invoke_signed_unchecked(
143                &InstructionView {
144                    program_id: &crate::ID,
145                    accounts: from_raw_parts(
146                        instruction_accounts.as_ptr() as _,
147                        written_instruction_accounts,
148                    ),
149                    data: from_raw_parts(instruction_data.as_ptr() as _, written_instruction_data),
150                },
151                from_raw_parts(accounts.as_ptr() as _, written_accounts),
152                signers,
153            );
154        }
155
156        Ok(())
157    }
158}
159
160impl<MultisigSigner: AsRef<AccountView>> CpiWriter for ApproveChecked<'_, '_, MultisigSigner> {
161    #[inline(always)]
162    fn write_accounts<'cpi>(
163        &self,
164        accounts: &mut [MaybeUninit<CpiAccount<'cpi>>],
165    ) -> Result<usize, ProgramError>
166    where
167        Self: 'cpi,
168    {
169        write_accounts(
170            self.source,
171            self.mint,
172            self.delegate,
173            self.authority,
174            self.multisig_signers,
175            accounts,
176        )
177    }
178
179    #[inline(always)]
180    fn write_instruction_accounts<'cpi>(
181        &self,
182        accounts: &mut [MaybeUninit<InstructionAccount<'cpi>>],
183    ) -> Result<usize, ProgramError>
184    where
185        Self: 'cpi,
186    {
187        write_instruction_accounts(
188            self.source,
189            self.mint,
190            self.delegate,
191            self.authority,
192            self.multisig_signers,
193            accounts,
194        )
195    }
196
197    #[inline(always)]
198    fn write_instruction_data(&self, data: &mut [MaybeUninit<u8>]) -> Result<usize, ProgramError> {
199        write_instruction_data(self.amount, self.decimals, data)
200    }
201}
202
203impl<MultisigSigner: AsRef<AccountView>> super::IntoBatch
204    for ApproveChecked<'_, '_, MultisigSigner>
205{
206    #[inline(always)]
207    fn into_batch<'account, 'state>(
208        self,
209        batch: &mut super::Batch<'account, 'state>,
210    ) -> ProgramResult
211    where
212        Self: 'account + 'state,
213    {
214        batch.push(
215            |accounts| {
216                write_accounts(
217                    self.source,
218                    self.mint,
219                    self.delegate,
220                    self.authority,
221                    self.multisig_signers,
222                    accounts,
223                )
224            },
225            |accounts| {
226                write_instruction_accounts(
227                    self.source,
228                    self.mint,
229                    self.delegate,
230                    self.authority,
231                    self.multisig_signers,
232                    accounts,
233                )
234            },
235            |data| write_instruction_data(self.amount, self.decimals, data),
236        )
237    }
238}
239
240#[inline(always)]
241fn write_accounts<'account, 'multisig, 'out, MultisigSigner: AsRef<AccountView>>(
242    source: &'account AccountView,
243    mint: &'account AccountView,
244    delegate: &'account AccountView,
245    authority: &'account AccountView,
246    multisig_signers: &'multisig [MultisigSigner],
247    accounts: &mut [MaybeUninit<CpiAccount<'out>>],
248) -> Result<usize, ProgramError>
249where
250    'account: 'out,
251    'multisig: 'out,
252{
253    let expected_accounts = 4 + multisig_signers.len();
254
255    if expected_accounts > accounts.len() {
256        return Err(invalid_argument_error());
257    }
258
259    if source.is_borrowed() {
260        return Err(account_borrow_failed_error());
261    }
262
263    CpiAccount::init_from_account_view(source, &mut accounts[0]);
264
265    CpiAccount::init_from_account_view(mint, &mut accounts[1]);
266
267    CpiAccount::init_from_account_view(delegate, &mut accounts[2]);
268
269    CpiAccount::init_from_account_view(authority, &mut accounts[3]);
270
271    for (account, signer) in accounts[4..expected_accounts]
272        .iter_mut()
273        .zip(multisig_signers.iter())
274    {
275        CpiAccount::init_from_account_view(signer.as_ref(), account);
276    }
277
278    Ok(expected_accounts)
279}
280
281#[inline(always)]
282fn write_instruction_accounts<'account, 'multisig, 'out, MultisigSigner: AsRef<AccountView>>(
283    source: &'account AccountView,
284    mint: &'account AccountView,
285    delegate: &'account AccountView,
286    authority: &'account AccountView,
287    multisig_signers: &'multisig [MultisigSigner],
288    accounts: &mut [MaybeUninit<InstructionAccount<'out>>],
289) -> Result<usize, ProgramError>
290where
291    'account: 'out,
292    'multisig: 'out,
293{
294    let expected_accounts = 4 + multisig_signers.len();
295
296    if expected_accounts > accounts.len() {
297        return Err(invalid_argument_error());
298    }
299
300    accounts[0].write(InstructionAccount::writable(source.address()));
301
302    accounts[1].write(InstructionAccount::readonly(mint.address()));
303
304    accounts[2].write(InstructionAccount::readonly(delegate.address()));
305
306    accounts[3].write(InstructionAccount::new(
307        authority.address(),
308        false,
309        multisig_signers.is_empty(),
310    ));
311
312    for (account, signer) in accounts[4..expected_accounts]
313        .iter_mut()
314        .zip(multisig_signers.iter())
315    {
316        account.write(InstructionAccount::readonly_signer(
317            signer.as_ref().address(),
318        ));
319    }
320
321    Ok(expected_accounts)
322}
323
324#[inline(always)]
325fn write_instruction_data(
326    amount: u64,
327    decimals: u8,
328    data: &mut [MaybeUninit<u8>],
329) -> Result<usize, ProgramError> {
330    if data.len() < ApproveChecked::DATA_LEN {
331        return Err(invalid_argument_error());
332    }
333
334    data[0].write(ApproveChecked::DISCRIMINATOR);
335
336    write_bytes(&mut data[1..9], &amount.to_le_bytes());
337
338    data[9].write(decimals);
339
340    Ok(ApproveChecked::DATA_LEN)
341}