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

Commit 480dc68

Browse files
authored
token-2022: Add init mint close authority (#2744)
1 parent f3704db commit 480dc68

File tree

8 files changed

+596
-103
lines changed

8 files changed

+596
-103
lines changed

token/program-2022/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ pub enum TokenError {
102102
/// Available balance mismatch
103103
#[error("Available balance mismatch")]
104104
ConfidentialTransferAvailableBalanceMismatch,
105+
/// Mint has non-zero supply. Burn all tokens before closing the mint.
106+
#[error("Mint has non-zero supply. Burn all tokens before closing the mint")]
107+
MintHasSupply,
105108
}
106109
impl From<TokenError> for ProgramError {
107110
fn from(e: TokenError) -> Self {

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

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,61 @@ fn type_and_tlv_indices<S: BaseState>(
193193
}
194194
}
195195

196+
fn get_extension<S: BaseState, V: Extension>(tlv_data: &[u8]) -> Result<&V, ProgramError> {
197+
if V::TYPE.get_account_type() != S::ACCOUNT_TYPE {
198+
return Err(ProgramError::InvalidAccountData);
199+
}
200+
let TlvIndices {
201+
type_start: _,
202+
length_start,
203+
value_start,
204+
} = get_extension_indices::<V>(tlv_data, false)?;
205+
let length = pod_from_bytes::<Length>(&tlv_data[length_start..value_start])?;
206+
let value_end = value_start.saturating_add(usize::from(*length));
207+
pod_from_bytes::<V>(&tlv_data[value_start..value_end])
208+
}
209+
210+
/// Encapsulates owned immutable base state data (mint or account) with possible extensions
211+
#[derive(Debug, PartialEq)]
212+
pub struct StateWithExtensionsOwned<S: BaseState> {
213+
/// Unpacked base data
214+
pub base: S,
215+
/// Raw TLV data, deserialized on demand
216+
tlv_data: Vec<u8>,
217+
}
218+
impl<S: BaseState> StateWithExtensionsOwned<S> {
219+
/// Unpack base state, leaving the extension data as a slice
220+
///
221+
/// Fails if the base state is not initialized.
222+
pub fn unpack(mut input: Vec<u8>) -> Result<Self, ProgramError> {
223+
check_min_len_and_not_multisig(&input, S::LEN)?;
224+
let mut rest = input.split_off(S::LEN);
225+
let base = S::unpack(&input)?;
226+
if let Some((account_type_index, tlv_start_index)) = type_and_tlv_indices::<S>(&rest)? {
227+
let account_type = AccountType::try_from(rest[account_type_index])
228+
.map_err(|_| ProgramError::InvalidAccountData)?;
229+
check_account_type::<S>(account_type)?;
230+
let tlv_data = rest.split_off(tlv_start_index);
231+
Ok(Self { base, tlv_data })
232+
} else {
233+
Ok(Self {
234+
base,
235+
tlv_data: vec![],
236+
})
237+
}
238+
}
239+
240+
/// Unpack a portion of the TLV data as the desired type
241+
pub fn get_extension<V: Extension>(&self) -> Result<&V, ProgramError> {
242+
get_extension::<S, V>(&self.tlv_data)
243+
}
244+
245+
/// Iterates through the TLV entries, returning only the types
246+
pub fn get_extension_types(&self) -> Result<Vec<ExtensionType>, ProgramError> {
247+
get_extension_types(&self.tlv_data)
248+
}
249+
}
250+
196251
/// Encapsulates immutable base state data (mint or account) with possible extensions
197252
#[derive(Debug, PartialEq)]
198253
pub struct StateWithExtensions<'data, S: BaseState> {
@@ -227,17 +282,7 @@ impl<'data, S: BaseState> StateWithExtensions<'data, S> {
227282

228283
/// Unpack a portion of the TLV data as the desired type
229284
pub fn get_extension<V: Extension>(&self) -> Result<&V, ProgramError> {
230-
if V::TYPE.get_account_type() != S::ACCOUNT_TYPE {
231-
return Err(ProgramError::InvalidAccountData);
232-
}
233-
let TlvIndices {
234-
type_start: _,
235-
length_start,
236-
value_start,
237-
} = get_extension_indices::<V>(self.tlv_data, false)?;
238-
let length = pod_from_bytes::<Length>(&self.tlv_data[length_start..value_start])?;
239-
let value_end = value_start.saturating_add(usize::from(*length));
240-
pod_from_bytes::<V>(&self.tlv_data[value_start..value_end])
285+
get_extension::<S, V>(self.tlv_data)
241286
}
242287

243288
/// Iterates through the TLV entries, returning only the types

token/program-2022/src/instruction.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,6 +1418,20 @@ pub fn get_account_data_size(
14181418
})
14191419
}
14201420

1421+
/// Creates an `InitializeMintCloseAuthority` instruction
1422+
pub fn initialize_mint_close_authority(
1423+
token_program_id: &Pubkey,
1424+
mint_pubkey: &Pubkey,
1425+
close_authority: COption<Pubkey>,
1426+
) -> Result<Instruction, ProgramError> {
1427+
check_program_account(token_program_id)?;
1428+
Ok(Instruction {
1429+
program_id: *token_program_id,
1430+
accounts: vec![AccountMeta::new(*mint_pubkey, false)],
1431+
data: TokenInstruction::InitializeMintCloseAuthority { close_authority }.pack(),
1432+
})
1433+
}
1434+
14211435
/// Utility function that checks index is between MIN_SIGNERS and MAX_SIGNERS
14221436
pub fn is_valid_signer_index(index: usize) -> bool {
14231437
(MIN_SIGNERS..=MAX_SIGNERS).contains(&index)

token/program-2022/src/processor.rs

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::{
55
error::TokenError,
66
extension::{
77
confidential_transfer::{self, ConfidentialTransferAccount},
8+
mint_close_authority::MintCloseAuthority,
89
transfer_fee, ExtensionType, StateWithExtensions, StateWithExtensionsMut,
910
},
1011
instruction::{is_valid_signer_index, AuthorityType, TokenInstruction, MAX_SIGNERS},
@@ -23,6 +24,7 @@ use solana_program::{
2324
pubkey::Pubkey,
2425
sysvar::{rent::Rent, Sysvar},
2526
};
27+
use std::convert::TryInto;
2628

2729
/// Program state handler.
2830
pub struct Processor {}
@@ -37,27 +39,33 @@ impl Processor {
3739
let account_info_iter = &mut accounts.iter();
3840
let mint_info = next_account_info(account_info_iter)?;
3941
let mint_data_len = mint_info.data_len();
42+
let mut mint_data = mint_info.data.borrow_mut();
4043
let rent = if rent_sysvar_account {
4144
Rent::from_account_info(next_account_info(account_info_iter)?)?
4245
} else {
4346
Rent::get()?
4447
};
4548

46-
let mut mint = Mint::unpack_unchecked(&mint_info.data.borrow())?;
47-
if mint.is_initialized {
48-
return Err(TokenError::AlreadyInUse.into());
49-
}
50-
5149
if !rent.is_exempt(mint_info.lamports(), mint_data_len) {
5250
return Err(TokenError::NotRentExempt.into());
5351
}
5452

55-
mint.mint_authority = COption::Some(mint_authority);
56-
mint.decimals = decimals;
57-
mint.is_initialized = true;
58-
mint.freeze_authority = freeze_authority;
53+
let mut mint = StateWithExtensionsMut::<Mint>::unpack_uninitialized(&mut mint_data)?;
54+
if mint.base.is_initialized {
55+
return Err(TokenError::AlreadyInUse.into());
56+
}
5957

60-
Mint::pack(mint, &mut mint_info.data.borrow_mut())?;
58+
let extension_types = mint.get_extension_types()?;
59+
if ExtensionType::get_account_len::<Mint>(&extension_types) != mint_data_len {
60+
return Err(ProgramError::InvalidAccountData);
61+
}
62+
63+
mint.base.mint_authority = COption::Some(mint_authority);
64+
mint.base.decimals = decimals;
65+
mint.base.is_initialized = true;
66+
mint.base.freeze_authority = freeze_authority;
67+
mint.pack_base();
68+
mint.init_account_type()?;
6169

6270
Ok(())
6371
}
@@ -763,10 +771,18 @@ impl Processor {
763771

764772
/// Processes an [InitializeMintCloseAuthority](enum.TokenInstruction.html) instruction
765773
pub fn process_initialize_mint_close_authority(
766-
_accounts: &[AccountInfo],
767-
_close_authority: COption<Pubkey>,
774+
accounts: &[AccountInfo],
775+
close_authority: COption<Pubkey>,
768776
) -> ProgramResult {
769-
unimplemented!();
777+
let account_info_iter = &mut accounts.iter();
778+
let mint_account_info = next_account_info(account_info_iter)?;
779+
780+
let mut mint_data = mint_account_info.data.borrow_mut();
781+
let mut mint = StateWithExtensionsMut::<Mint>::unpack_uninitialized(&mut mint_data)?;
782+
let extension = mint.init_extension::<MintCloseAuthority>()?;
783+
extension.close_authority = close_authority.try_into()?;
784+
785+
Ok(())
770786
}
771787

772788
/// Processes a [GetAccountDataSize](enum.TokenInstruction.html) instruction
@@ -1027,6 +1043,9 @@ impl PrintProgramError for TokenError {
10271043
TokenError::ConfidentialTransferAvailableBalanceMismatch => {
10281044
msg!("Error: Available balance mismatch")
10291045
}
1046+
TokenError::MintHasSupply => {
1047+
msg!("Error: Mint has non-zero supply. Burn all tokens before closing the mint")
1048+
}
10301049
}
10311050
}
10321051
}

0 commit comments

Comments
 (0)