Skip to main content

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