Skip to main content

pinocchio_token/instructions/
sync_native.rs

1use {
2    crate::{
3        instructions::{account_borrow_failed_error, invalid_argument_error, CpiWriter},
4        UNINIT_BYTE, UNINIT_CPI_ACCOUNT, UNINIT_INSTRUCTION_ACCOUNT,
5    },
6    core::{mem::MaybeUninit, slice::from_raw_parts},
7    solana_account_view::AccountView,
8    solana_instruction_view::{
9        cpi::{invoke_unchecked, CpiAccount},
10        InstructionAccount, InstructionView,
11    },
12    solana_program_error::{ProgramError, ProgramResult},
13};
14
15/// Given a wrapped / native token account (a token account containing SOL)
16/// updates its amount field based on the account's underlying `lamports`.
17/// This is useful if a non-wrapped SOL account uses
18/// `system_instruction::transfer` to move lamports to a wrapped token
19/// account, and needs to have its token `amount` field updated.
20///
21/// Accounts expected by this instruction:
22///
23///   * Using runtime Rent sysvar
24///   0. `[writable]`  The native token account to sync with its underlying
25///      lamports.
26///
27///   * Using Rent sysvar account
28///   0. `[writable]`  The native token account to sync with its underlying
29///      lamports.
30///   1. `[]` Rent sysvar.
31pub struct SyncNative<'account> {
32    /// Native Token Account
33    pub native_token: &'account AccountView,
34
35    pub rent_sysvar: Option<&'account AccountView>,
36}
37
38impl<'account> SyncNative<'account> {
39    pub const DISCRIMINATOR: u8 = 17;
40
41    /// Maximum number of accounts expected by this instruction.
42    ///
43    /// The required number of accounts will depend whether the
44    /// source account has a single owner or a multisignature
45    /// owner.
46    pub const MAX_ACCOUNTS_LEN: usize = 2;
47
48    /// Instruction data length:
49    ///   - discriminator (1 byte)
50    pub const DATA_LEN: usize = 1;
51
52    #[inline(always)]
53    pub fn new(
54        native_token: &'account AccountView,
55        rent_sysvar: Option<&'account AccountView>,
56    ) -> Self {
57        Self {
58            native_token,
59            rent_sysvar,
60        }
61    }
62
63    #[inline(always)]
64    pub fn invoke(&self) -> ProgramResult {
65        let mut instruction_accounts = [UNINIT_INSTRUCTION_ACCOUNT; SyncNative::MAX_ACCOUNTS_LEN];
66        let written_instruction_accounts =
67            self.write_instruction_accounts(&mut instruction_accounts)?;
68
69        let mut accounts = [UNINIT_CPI_ACCOUNT; Self::MAX_ACCOUNTS_LEN];
70        let written_accounts = self.write_accounts(&mut accounts)?;
71
72        let mut instruction_data = [UNINIT_BYTE; Self::DATA_LEN];
73        let written_instruction_data = self.write_instruction_data(&mut instruction_data)?;
74
75        unsafe {
76            invoke_unchecked(
77                &InstructionView {
78                    program_id: &crate::ID,
79                    accounts: from_raw_parts(
80                        instruction_accounts.as_ptr() as _,
81                        written_instruction_accounts,
82                    ),
83                    data: from_raw_parts(instruction_data.as_ptr() as _, written_instruction_data),
84                },
85                from_raw_parts(accounts.as_ptr() as _, written_accounts),
86            );
87        }
88
89        Ok(())
90    }
91}
92
93impl CpiWriter for SyncNative<'_> {
94    #[inline(always)]
95    fn write_accounts<'cpi>(
96        &self,
97        accounts: &mut [MaybeUninit<CpiAccount<'cpi>>],
98    ) -> Result<usize, ProgramError>
99    where
100        Self: 'cpi,
101    {
102        write_accounts(self.native_token, self.rent_sysvar, accounts)
103    }
104
105    #[inline(always)]
106    fn write_instruction_accounts<'cpi>(
107        &self,
108        accounts: &mut [MaybeUninit<InstructionAccount<'cpi>>],
109    ) -> Result<usize, ProgramError>
110    where
111        Self: 'cpi,
112    {
113        write_instruction_accounts(self.native_token, self.rent_sysvar, accounts)
114    }
115
116    #[inline(always)]
117    fn write_instruction_data(&self, data: &mut [MaybeUninit<u8>]) -> Result<usize, ProgramError> {
118        write_instruction_data(data)
119    }
120}
121
122impl super::IntoBatch for SyncNative<'_> {
123    #[inline(always)]
124    fn into_batch<'account, 'state>(
125        self,
126        batch: &mut super::Batch<'account, 'state>,
127    ) -> ProgramResult
128    where
129        Self: 'account + 'state,
130    {
131        batch.push(
132            |accounts| write_accounts(self.native_token, self.rent_sysvar, accounts),
133            |accounts| write_instruction_accounts(self.native_token, self.rent_sysvar, accounts),
134            write_instruction_data,
135        )
136    }
137}
138
139#[inline(always)]
140fn write_accounts<'account, 'out>(
141    native_token: &'account AccountView,
142    rent_sysvar: Option<&'account AccountView>,
143    accounts: &mut [MaybeUninit<CpiAccount<'out>>],
144) -> Result<usize, ProgramError>
145where
146    'account: 'out,
147{
148    if accounts.len() < SyncNative::MAX_ACCOUNTS_LEN {
149        return Err(invalid_argument_error());
150    }
151
152    if native_token.is_borrowed() {
153        return Err(account_borrow_failed_error());
154    }
155
156    CpiAccount::init_from_account_view(native_token, &mut accounts[0]);
157
158    if let Some(rent_sysvar) = rent_sysvar {
159        CpiAccount::init_from_account_view(rent_sysvar, &mut accounts[1]);
160        Ok(SyncNative::MAX_ACCOUNTS_LEN)
161    } else {
162        Ok(1)
163    }
164}
165
166#[inline(always)]
167fn write_instruction_accounts<'account, 'out>(
168    native_token: &'account AccountView,
169    rent_sysvar: Option<&'account AccountView>,
170    accounts: &mut [MaybeUninit<InstructionAccount<'out>>],
171) -> Result<usize, ProgramError>
172where
173    'account: 'out,
174{
175    if accounts.len() < SyncNative::MAX_ACCOUNTS_LEN {
176        return Err(invalid_argument_error());
177    }
178
179    accounts[0].write(InstructionAccount::writable(native_token.address()));
180
181    if let Some(rent_sysvar) = rent_sysvar {
182        accounts[1].write(InstructionAccount::readonly(rent_sysvar.address()));
183        Ok(SyncNative::MAX_ACCOUNTS_LEN)
184    } else {
185        Ok(1)
186    }
187}
188
189#[inline(always)]
190fn write_instruction_data(data: &mut [MaybeUninit<u8>]) -> Result<usize, ProgramError> {
191    if data.len() < SyncNative::DATA_LEN {
192        return Err(invalid_argument_error());
193    }
194
195    data[0].write(SyncNative::DISCRIMINATOR);
196
197    Ok(SyncNative::DATA_LEN)
198}