Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit c7ec442

Browse files
author
Tyera Eulberg
authored
token-2022: Add Reallocate instruction (#2864)
* Pass by ref * Dedupe ExtensionTypes in length apis * Depend on deduplication in ExtensionType::get_account_len * Add Reallocate instruction * Remove unneeded api * Add set_account_type helper and remove unneeded StateWithExtensionsMut api
1 parent 129b356 commit c7ec442

File tree

8 files changed

+450
-311
lines changed

8 files changed

+450
-311
lines changed

associated-token-account/program/src/processor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ pub fn process_create_associated_token_account(
7979
let account_len = get_account_len(
8080
spl_token_mint_info,
8181
spl_token_program_info,
82-
vec![spl_token::extension::ExtensionType::ImmutableOwner],
82+
&[spl_token::extension::ExtensionType::ImmutableOwner],
8383
)?;
8484

8585
create_pda_account(

associated-token-account/program/src/tools/account.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ pub fn create_pda_account<'a>(
7676
pub fn get_account_len<'a>(
7777
mint: &AccountInfo<'a>,
7878
spl_token_program: &AccountInfo<'a>,
79-
extension_types: Vec<ExtensionType>,
79+
extension_types: &[ExtensionType],
8080
) -> Result<usize, ProgramError> {
8181
invoke(
8282
&spl_token::instruction::get_account_data_size(

token/program-2022/src/extension/mod.rs

Lines changed: 83 additions & 291 deletions
Large diffs are not rendered by default.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use {
2+
crate::{
3+
error::TokenError,
4+
extension::{set_account_type, AccountType, ExtensionType, StateWithExtensions},
5+
processor::Processor,
6+
state::Account,
7+
},
8+
solana_program::{
9+
account_info::{next_account_info, AccountInfo},
10+
entrypoint::ProgramResult,
11+
msg,
12+
program::invoke,
13+
pubkey::Pubkey,
14+
system_instruction,
15+
sysvar::{rent::Rent, Sysvar},
16+
},
17+
};
18+
19+
/// Processes a [Reallocate](enum.TokenInstruction.html) instruction
20+
pub fn process_reallocate(
21+
program_id: &Pubkey,
22+
accounts: &[AccountInfo],
23+
new_extension_types: Vec<ExtensionType>,
24+
) -> ProgramResult {
25+
let account_info_iter = &mut accounts.iter();
26+
let token_account_info = next_account_info(account_info_iter)?;
27+
let payer_info = next_account_info(account_info_iter)?;
28+
let system_program_info = next_account_info(account_info_iter)?;
29+
let authority_info = next_account_info(account_info_iter)?;
30+
let authority_info_data_len = authority_info.data_len();
31+
32+
// check that account is the right type and validate owner
33+
let mut current_extension_types = {
34+
let token_account = token_account_info.data.borrow();
35+
let account = StateWithExtensions::<Account>::unpack(&token_account)?;
36+
Processor::validate_owner(
37+
program_id,
38+
&account.base.owner,
39+
authority_info,
40+
authority_info_data_len,
41+
account_info_iter.as_slice(),
42+
)?;
43+
account.get_extension_types()?
44+
};
45+
46+
// check that all desired extensions are for the right account type
47+
if new_extension_types
48+
.iter()
49+
.any(|extension_type| extension_type.get_account_type() != AccountType::Account)
50+
{
51+
return Err(TokenError::InvalidState.into());
52+
}
53+
// ExtensionType::get_account_len() dedupes types, so just a dumb concatenation is fine here
54+
current_extension_types.extend_from_slice(&new_extension_types);
55+
let needed_account_len = ExtensionType::get_account_len::<Account>(&current_extension_types);
56+
57+
// if account is already large enough, return early
58+
if token_account_info.data_len() >= needed_account_len {
59+
return Ok(());
60+
}
61+
62+
// reallocate
63+
msg!(
64+
"account needs realloc, +{:?} bytes",
65+
needed_account_len - token_account_info.data_len()
66+
);
67+
token_account_info.realloc(needed_account_len, false)?;
68+
69+
// if additional lamports needed to remain rent-exempt, transfer them
70+
let rent = Rent::get()?;
71+
let new_minimum_balance = rent.minimum_balance(needed_account_len);
72+
let lamports_diff = new_minimum_balance.saturating_sub(token_account_info.lamports());
73+
invoke(
74+
&system_instruction::transfer(payer_info.key, token_account_info.key, lamports_diff),
75+
&[
76+
payer_info.clone(),
77+
token_account_info.clone(),
78+
system_program_info.clone(),
79+
],
80+
)?;
81+
82+
// unpack to set account_type, if needed
83+
let mut token_account = token_account_info.data.borrow_mut();
84+
set_account_type::<Account>(&mut token_account)?;
85+
86+
Ok(())
87+
}

token/program-2022/src/instruction.rs

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use {
1111
program_error::ProgramError,
1212
program_option::COption,
1313
pubkey::{Pubkey, PUBKEY_BYTES},
14-
sysvar,
14+
system_program, sysvar,
1515
},
1616
std::{convert::TryInto, mem::size_of},
1717
};
@@ -495,11 +495,33 @@ pub enum TokenInstruction {
495495
/// Accounts expected by this instruction:
496496
///
497497
/// 0. `[writable]` The account to initialize.
498-
//
498+
///
499499
/// Data expected by this instruction:
500500
/// None
501501
///
502502
InitializeImmutableOwner,
503+
/// Check to see if a token account is large enough for a list of ExtensionTypes, and if not,
504+
/// use reallocation to increase the data size.
505+
///
506+
/// Accounts expected by this instruction:
507+
///
508+
/// * Single owner
509+
/// 0. `[writable]` The account to reallocate.
510+
/// 1. `[signer, writable]` The payer account to fund reallocation
511+
/// 2. `[]` System program for reallocation funding
512+
/// 3. `[signer]` The account's owner.
513+
///
514+
/// * Multisignature owner
515+
/// 0. `[writable]` The account to reallocate.
516+
/// 1. `[signer, writable]` The payer account to fund reallocation
517+
/// 2. `[]` System program for reallocation funding
518+
/// 3. `[]` The account's multisignature owner/delegate.
519+
/// 4. ..4+M `[signer]` M signer accounts.
520+
///
521+
Reallocate {
522+
/// New extension types to include in the reallocated account
523+
extension_types: Vec<ExtensionType>,
524+
},
503525
}
504526
impl TokenInstruction {
505527
/// Unpacks a byte buffer into a [TokenInstruction](enum.TokenInstruction.html).
@@ -611,6 +633,13 @@ impl TokenInstruction {
611633
24 => Self::ConfidentialTransferExtension,
612634
25 => Self::DefaultAccountStateExtension,
613635
26 => Self::InitializeImmutableOwner,
636+
27 => {
637+
let mut extension_types = vec![];
638+
for chunk in rest.chunks(size_of::<ExtensionType>()) {
639+
extension_types.push(chunk.try_into()?);
640+
}
641+
Self::Reallocate { extension_types }
642+
}
614643
_ => return Err(TokenError::InvalidInstruction.into()),
615644
})
616645
}
@@ -735,6 +764,14 @@ impl TokenInstruction {
735764
&Self::InitializeImmutableOwner => {
736765
buf.push(26);
737766
}
767+
&Self::Reallocate {
768+
ref extension_types,
769+
} => {
770+
buf.push(27);
771+
for extension_type in extension_types {
772+
buf.extend_from_slice(&<[u8; 2]>::from(*extension_type));
773+
}
774+
}
738775
};
739776
buf
740777
}
@@ -1448,13 +1485,16 @@ pub fn sync_native(
14481485
pub fn get_account_data_size(
14491486
token_program_id: &Pubkey,
14501487
mint_pubkey: &Pubkey,
1451-
extension_types: Vec<ExtensionType>,
1488+
extension_types: &[ExtensionType],
14521489
) -> Result<Instruction, ProgramError> {
14531490
check_program_account(token_program_id)?;
14541491
Ok(Instruction {
14551492
program_id: *token_program_id,
14561493
accounts: vec![AccountMeta::new_readonly(*mint_pubkey, false)],
1457-
data: TokenInstruction::GetAccountDataSize { extension_types }.pack(),
1494+
data: TokenInstruction::GetAccountDataSize {
1495+
extension_types: extension_types.to_vec(),
1496+
}
1497+
.pack(),
14581498
})
14591499
}
14601500

@@ -1486,6 +1526,39 @@ pub fn initialize_immutable_owner(
14861526
})
14871527
}
14881528

1529+
/// Creates a `Reallocate` instruction
1530+
pub fn reallocate(
1531+
token_program_id: &Pubkey,
1532+
account_pubkey: &Pubkey,
1533+
payer: &Pubkey,
1534+
owner_pubkey: &Pubkey,
1535+
signer_pubkeys: &[&Pubkey],
1536+
extension_types: &[ExtensionType],
1537+
) -> Result<Instruction, ProgramError> {
1538+
check_program_account(token_program_id)?;
1539+
1540+
let mut accounts = Vec::with_capacity(4 + signer_pubkeys.len());
1541+
accounts.push(AccountMeta::new(*account_pubkey, false));
1542+
accounts.push(AccountMeta::new(*payer, true));
1543+
accounts.push(AccountMeta::new_readonly(system_program::id(), false));
1544+
accounts.push(AccountMeta::new_readonly(
1545+
*owner_pubkey,
1546+
signer_pubkeys.is_empty(),
1547+
));
1548+
for signer_pubkey in signer_pubkeys.iter() {
1549+
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
1550+
}
1551+
1552+
Ok(Instruction {
1553+
program_id: *token_program_id,
1554+
accounts,
1555+
data: TokenInstruction::Reallocate {
1556+
extension_types: extension_types.to_vec(),
1557+
}
1558+
.pack(),
1559+
})
1560+
}
1561+
14891562
/// Utility function that checks index is between MIN_SIGNERS and MAX_SIGNERS
14901563
pub fn is_valid_signer_index(index: usize) -> bool {
14911564
(MIN_SIGNERS..=MAX_SIGNERS).contains(&index)

token/program-2022/src/processor.rs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use {
99
default_account_state::{self, DefaultAccountState},
1010
immutable_owner::ImmutableOwner,
1111
mint_close_authority::MintCloseAuthority,
12+
reallocate,
1213
transfer_fee::{self, TransferFeeAmount, TransferFeeConfig},
1314
ExtensionType, StateWithExtensions, StateWithExtensionsMut,
1415
},
@@ -992,18 +993,15 @@ impl Processor {
992993
/// Processes a [GetAccountDataSize](enum.TokenInstruction.html) instruction
993994
pub fn process_get_account_data_size(
994995
accounts: &[AccountInfo],
995-
extension_types: Vec<ExtensionType>,
996+
new_extension_types: Vec<ExtensionType>,
996997
) -> ProgramResult {
997998
let account_info_iter = &mut accounts.iter();
998999
let mint_account_info = next_account_info(account_info_iter)?;
9991000

10001001
let mut account_extensions = Self::get_required_account_extensions(mint_account_info)?;
1001-
1002-
for extension_type in extension_types {
1003-
if !account_extensions.contains(&extension_type) {
1004-
account_extensions.push(extension_type);
1005-
}
1006-
}
1002+
// ExtensionType::get_account_len() dedupes types, so just a dumb concatenation is fine
1003+
// here
1004+
account_extensions.extend_from_slice(&new_extension_types);
10071005

10081006
let account_len = ExtensionType::get_account_len::<Account>(&account_extensions);
10091007
set_return_data(&account_len.to_le_bytes());
@@ -1151,6 +1149,10 @@ impl Processor {
11511149
msg!("Instruction: InitializeImmutableOwner");
11521150
Self::process_initialize_immutable_owner(accounts)
11531151
}
1152+
TokenInstruction::Reallocate { extension_types } => {
1153+
msg!("Instruction: Reallocate");
1154+
reallocate::process_reallocate(program_id, accounts, extension_types)
1155+
}
11541156
}
11551157
}
11561158

@@ -6919,7 +6921,7 @@ mod tests {
69196921
.to_vec(),
69206922
);
69216923
do_process_instruction(
6922-
get_account_data_size(&program_id, &mint_key, vec![]).unwrap(),
6924+
get_account_data_size(&program_id, &mint_key, &[]).unwrap(),
69236925
vec![&mut mint_account],
69246926
)
69256927
.unwrap();
@@ -6933,7 +6935,7 @@ mod tests {
69336935
get_account_data_size(
69346936
&program_id,
69356937
&mint_key,
6936-
vec![
6938+
&[
69376939
ExtensionType::TransferFeeAmount,
69386940
ExtensionType::TransferFeeAmount, // Duplicate user input ignored...
69396941
],
@@ -6951,7 +6953,7 @@ mod tests {
69516953
.to_vec(),
69526954
);
69536955
do_process_instruction(
6954-
get_account_data_size(&program_id, &mint_key, vec![]).unwrap(),
6956+
get_account_data_size(&program_id, &mint_key, &[]).unwrap(),
69556957
vec![&mut mint_account],
69566958
)
69576959
.unwrap();
@@ -6982,7 +6984,7 @@ mod tests {
69826984
.to_vec(),
69836985
);
69846986
do_process_instruction(
6985-
get_account_data_size(&program_id, &mint_key, vec![]).unwrap(),
6987+
get_account_data_size(&program_id, &mint_key, &[]).unwrap(),
69866988
vec![&mut extended_mint_account],
69876989
)
69886990
.unwrap();
@@ -6991,7 +6993,7 @@ mod tests {
69916993
get_account_data_size(
69926994
&program_id,
69936995
&mint_key,
6994-
vec![ExtensionType::TransferFeeAmount], // User extension that's also added by the mint ignored...
6996+
&[ExtensionType::TransferFeeAmount], // User extension that's also added by the mint ignored...
69956997
)
69966998
.unwrap(),
69976999
vec![&mut extended_mint_account],
@@ -7018,7 +7020,7 @@ mod tests {
70187020

70197021
assert_eq!(
70207022
do_process_instruction(
7021-
get_account_data_size(&program_id, &invalid_mint_key, vec![]).unwrap(),
7023+
get_account_data_size(&program_id, &invalid_mint_key, &[]).unwrap(),
70227024
vec![&mut invalid_mint_account],
70237025
),
70247026
Err(TokenError::InvalidMint.into())
@@ -7043,7 +7045,7 @@ mod tests {
70437045

70447046
assert_eq!(
70457047
do_process_instruction(
7046-
get_account_data_size(&program_id, &invalid_mint_key, vec![]).unwrap(),
7048+
get_account_data_size(&program_id, &invalid_mint_key, &[]).unwrap(),
70477049
vec![&mut invalid_mint_account],
70487050
),
70497051
Err(ProgramError::IncorrectProgramId)

0 commit comments

Comments
 (0)