spl_token_2022/extension/
reallocate.rs

1use {
2    crate::{
3        error::TokenError,
4        extension::{
5            set_account_type, AccountType, BaseStateWithExtensions, ExtensionType,
6            StateWithExtensions, StateWithExtensionsMut,
7        },
8        processor::Processor,
9        state::Account,
10    },
11    solana_account_info::{next_account_info, AccountInfo},
12    solana_cpi::invoke,
13    solana_msg::msg,
14    solana_program_error::ProgramResult,
15    solana_program_option::COption,
16    solana_pubkey::Pubkey,
17    solana_rent::Rent,
18    solana_system_interface::instruction as system_instruction,
19    solana_sysvar::Sysvar,
20};
21
22/// Processes a [Reallocate](enum.TokenInstruction.html) instruction
23pub fn process_reallocate(
24    program_id: &Pubkey,
25    accounts: &[AccountInfo],
26    new_extension_types: Vec<ExtensionType>,
27) -> ProgramResult {
28    let account_info_iter = &mut accounts.iter();
29    let token_account_info = next_account_info(account_info_iter)?;
30    let payer_info = next_account_info(account_info_iter)?;
31    let system_program_info = next_account_info(account_info_iter)?;
32    let authority_info = next_account_info(account_info_iter)?;
33    let authority_info_data_len = authority_info.data_len();
34
35    // check that account is the right type and validate owner
36    let (mut current_extension_types, native_token_amount) = {
37        let token_account = token_account_info.data.borrow();
38        let account = StateWithExtensions::<Account>::unpack(&token_account)?;
39        Processor::validate_owner(
40            program_id,
41            &account.base.owner,
42            authority_info,
43            authority_info_data_len,
44            account_info_iter.as_slice(),
45        )?;
46        let native_token_amount = account.base.is_native().then_some(account.base.amount);
47        (account.get_extension_types()?, native_token_amount)
48    };
49
50    // check that all desired extensions are for the right account type
51    if new_extension_types
52        .iter()
53        .any(|extension_type| extension_type.get_account_type() != AccountType::Account)
54    {
55        return Err(TokenError::InvalidState.into());
56    }
57    // ExtensionType::try_calculate_account_len() dedupes types, so just a dumb
58    // concatenation is fine here
59    current_extension_types.extend_from_slice(&new_extension_types);
60    let needed_account_len =
61        ExtensionType::try_calculate_account_len::<Account>(&current_extension_types)?;
62
63    // if account is already large enough, return early
64    if token_account_info.data_len() >= needed_account_len {
65        return Ok(());
66    }
67
68    // reallocate
69    msg!(
70        "account needs realloc, +{:?} bytes",
71        needed_account_len - token_account_info.data_len()
72    );
73    token_account_info.realloc(needed_account_len, false)?;
74
75    // if additional lamports needed to remain rent-exempt, transfer them
76    let rent = Rent::get()?;
77    let new_rent_exempt_reserve = rent.minimum_balance(needed_account_len);
78
79    let current_lamport_reserve = token_account_info
80        .lamports()
81        .checked_sub(native_token_amount.unwrap_or(0))
82        .ok_or(TokenError::Overflow)?;
83    let lamports_diff = new_rent_exempt_reserve.saturating_sub(current_lamport_reserve);
84    if lamports_diff > 0 {
85        invoke(
86            &system_instruction::transfer(payer_info.key, token_account_info.key, lamports_diff),
87            &[
88                payer_info.clone(),
89                token_account_info.clone(),
90                system_program_info.clone(),
91            ],
92        )?;
93    }
94
95    // set account_type, if needed
96    let mut token_account_data = token_account_info.data.borrow_mut();
97    set_account_type::<Account>(&mut token_account_data)?;
98
99    // sync the rent exempt reserve for native accounts
100    if let Some(native_token_amount) = native_token_amount {
101        let mut token_account = StateWithExtensionsMut::<Account>::unpack(&mut token_account_data)?;
102        // sanity check that there are enough lamports to cover the token amount
103        // and the rent exempt reserve
104        let minimum_lamports = new_rent_exempt_reserve
105            .checked_add(native_token_amount)
106            .ok_or(TokenError::Overflow)?;
107        if token_account_info.lamports() < minimum_lamports {
108            return Err(TokenError::InvalidState.into());
109        }
110        token_account.base.is_native = COption::Some(new_rent_exempt_reserve);
111        token_account.pack_base();
112    }
113
114    Ok(())
115}