Skip to main content

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