Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions interface/src/extension/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use {
non_transferable::{NonTransferable, NonTransferableAccount},
pausable::{PausableAccount, PausableConfig},
permanent_delegate::PermanentDelegate,
permissioned_burn::PermissionedBurnConfig,
scaled_ui_amount::ScaledUiAmountConfig,
transfer_fee::{TransferFeeAmount, TransferFeeConfig},
transfer_hook::{TransferHook, TransferHookAccount},
Expand Down Expand Up @@ -76,6 +77,8 @@ pub mod non_transferable;
pub mod pausable;
/// Permanent Delegate extension
pub mod permanent_delegate;
/// Permissioned burn extension
pub mod permissioned_burn;
/// Scaled UI Amount extension
pub mod scaled_ui_amount;
/// Token-group extension
Expand Down Expand Up @@ -1119,6 +1122,8 @@ pub enum ExtensionType {
Pausable,
/// Indicates that the account belongs to a pausable mint
PausableAccount,
/// Tokens burning requires approval from authorirty.
PermissionedBurn,

/// Test variable-length mint extension
#[cfg(test)]
Expand Down Expand Up @@ -1204,6 +1209,7 @@ impl ExtensionType {
ExtensionType::ScaledUiAmount => pod_get_packed_len::<ScaledUiAmountConfig>(),
ExtensionType::Pausable => pod_get_packed_len::<PausableConfig>(),
ExtensionType::PausableAccount => pod_get_packed_len::<PausableAccount>(),
ExtensionType::PermissionedBurn => pod_get_packed_len::<PermissionedBurnConfig>(),
#[cfg(test)]
ExtensionType::AccountPaddingTest => pod_get_packed_len::<AccountPaddingTest>(),
#[cfg(test)]
Expand Down Expand Up @@ -1271,6 +1277,7 @@ impl ExtensionType {
| ExtensionType::TokenGroupMember
| ExtensionType::ScaledUiAmount
| ExtensionType::Pausable => AccountType::Mint,
ExtensionType::PermissionedBurn => AccountType::Mint,
ExtensionType::ImmutableOwner
| ExtensionType::TransferFeeAmount
| ExtensionType::ConfidentialTransferAccount
Expand Down
59 changes: 59 additions & 0 deletions interface/src/extension/permissioned_burn/instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use {
crate::{
check_program_account,
instruction::{encode_instruction, TokenInstruction},
},
bytemuck::{Pod, Zeroable},
num_enum::{IntoPrimitive, TryFromPrimitive},
solana_instruction::{AccountMeta, Instruction},
solana_program_error::ProgramError,
solana_pubkey::Pubkey,
};

/// Permissioned Burn extension instructions
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, PartialEq, IntoPrimitive, TryFromPrimitive)]
#[repr(u8)]
pub enum PermissionedBurnInstruction {
/// Require permissioned burn for the given mint account
///
/// Accounts expected by this instruction:
///
/// 0. `[writable]` The mint account to initialize.
///
/// Data expected by this instruction:
/// `crate::extension::permissioned_burn::instruction::InitializeInstructionData`
Initialize,
}

/// Data expected by `PermissionedBurnInstruction::Initialize`
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct InitializeInstructionData {
/// The public key for the account that is required for token burning.
pub authority: Pubkey,
}

/// Create an `Initialize` instruction
pub fn initialize(
token_program_id: &Pubkey,
mint: &Pubkey,
authority: &Pubkey,
) -> Result<Instruction, ProgramError> {
check_program_account(token_program_id)?;
let accounts = vec![AccountMeta::new(*mint, false)];
Ok(encode_instruction(
token_program_id,
accounts,
TokenInstruction::PermissionedBurnExtension,
PermissionedBurnInstruction::Initialize,
&InitializeInstructionData {
authority: *authority,
},
))
}
24 changes: 24 additions & 0 deletions interface/src/extension/permissioned_burn/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use {
crate::extension::{Extension, ExtensionType},
bytemuck::{Pod, Zeroable},
solana_pubkey::Pubkey,
};

/// Instruction types for the permissioned burn extension
pub mod instruction;

/// Indicates that the tokens from this mint require permissioned burn
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
#[repr(C)]
pub struct PermissionedBurnConfig {
/// Authority that is required for burning
pub authority: Pubkey,
}

impl Extension for PermissionedBurnConfig {
const TYPE: ExtensionType = ExtensionType::PermissionedBurn;
}
5 changes: 5 additions & 0 deletions interface/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,8 @@ pub enum TokenInstruction<'a> {
ScaledUiAmountExtension,
/// Instruction prefix for instructions to the pausable extension
PausableExtension,
/// Instruction prefix for instructions to the permissioned burn extension
PermissionedBurnExtension,
}
impl<'a> TokenInstruction<'a> {
/// Unpacks a byte buffer into a
Expand Down Expand Up @@ -1053,6 +1055,9 @@ impl<'a> TokenInstruction<'a> {
&Self::PausableExtension => {
buf.push(44);
}
&Self::PermissionedBurnExtension => {
buf.push(45);
}
};
buf
}
Expand Down
2 changes: 2 additions & 0 deletions program/src/extension/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub mod non_transferable;
pub mod pausable;
/// Permanent Delegate extension
pub mod permanent_delegate;
/// Permissioned burn extension
pub mod permissioned_burn;
/// Utility to reallocate token accounts
pub mod reallocate;
/// Scaled UI Amount extension
Expand Down
5 changes: 5 additions & 0 deletions program/src/extension/permissioned_burn/instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#![deprecated(
since = "9.1.0",
note = "Use spl_token_2022_interface instead and remove spl_token_2022 as a dependency"
)]
pub use spl_token_2022_interface::extension::permissioned_burn::instruction::*;
10 changes: 10 additions & 0 deletions program/src/extension/permissioned_burn/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// Instruction types for the permissioned burn extension
pub mod instruction;
/// Instruction processor for the permissioned burn extension
pub mod processor;

#[deprecated(
since = "9.1.0",
note = "Use spl_token_2022_interface instead and remove spl_token_2022 as a dependency"
)]
pub use spl_token_2022_interface::extension::permissioned_burn::PermissionedBurnConfig;
50 changes: 50 additions & 0 deletions program/src/extension/permissioned_burn/processor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use {
solana_account_info::{next_account_info, AccountInfo},
solana_msg::msg,
solana_program_error::ProgramResult,
solana_pubkey::Pubkey,
spl_token_2022_interface::{
check_program_account,
extension::{
permissioned_burn::{
instruction::{InitializeInstructionData, PermissionedBurnInstruction},
PermissionedBurnConfig,
},
BaseStateWithExtensionsMut, PodStateWithExtensionsMut,
},
instruction::{decode_instruction_data, decode_instruction_type},
pod::PodMint,
},
};

fn process_initialize(
_program_id: &Pubkey,
accounts: &[AccountInfo],
authority: &Pubkey,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let mint_account_info = next_account_info(account_info_iter)?;
let mut mint_data = mint_account_info.data.borrow_mut();
let mut mint = PodStateWithExtensionsMut::<PodMint>::unpack_uninitialized(&mut mint_data)?;

let extension = mint.init_extension::<PermissionedBurnConfig>(true)?;
extension.authority = *authority;

Ok(())
}

pub(crate) fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
input: &[u8],
) -> ProgramResult {
check_program_account(program_id)?;

match decode_instruction_type(input)? {
PermissionedBurnInstruction::Initialize => {
msg!("PermissionedBurnInstruction::Initialize");
let InitializeInstructionData { authority } = decode_instruction_data(input)?;
process_initialize(program_id, accounts, authority)
}
}
}
1 change: 1 addition & 0 deletions program/src/pod_instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ pub(crate) enum PodTokenInstruction {
ConfidentialMintBurnExtension,
ScaledUiAmountExtension,
PausableExtension,
PermissionedBurnExtension,
}

fn unpack_pubkey_option(input: &[u8]) -> Result<PodCOption<Pubkey>, ProgramError> {
Expand Down
26 changes: 24 additions & 2 deletions program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use {
cpi_guard::{self, in_cpi},
default_account_state, group_member_pointer, group_pointer, interest_bearing_mint,
memo_transfer::{self, check_previous_sibling_instruction_is_memo},
metadata_pointer, pausable, reallocate, scaled_ui_amount, token_group, token_metadata,
transfer_fee, transfer_hook,
metadata_pointer, pausable, permissioned_burn, reallocate, scaled_ui_amount,
token_group, token_metadata, transfer_fee, transfer_hook,
},
pod_instruction::{
decode_instruction_data_with_coption_pubkey, AmountCheckedData, AmountData,
Expand Down Expand Up @@ -51,6 +51,7 @@ use {
non_transferable::{NonTransferable, NonTransferableAccount},
pausable::{PausableAccount, PausableConfig},
permanent_delegate::{get_permanent_delegate, PermanentDelegate},
permissioned_burn::PermissionedBurnConfig,
scaled_ui_amount::ScaledUiAmountConfig,
transfer_fee::{TransferFeeAmount, TransferFeeConfig},
transfer_hook::{TransferHook, TransferHookAccount},
Expand Down Expand Up @@ -1109,6 +1110,19 @@ impl Processor {
return Err(TokenError::MintPaused.into());
}
}
if let Ok(ext) = mint.get_extension::<PermissionedBurnConfig>() {
// Pull the required extra signer from the accounts
let approver_ai = next_account_info(account_info_iter)?;
Comment on lines +1127 to +1128
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing this, I think we should add a new variant to Burn, like BurnCheckedWithPermission, which explicitly takes in the authority. Otherwise, the account order for multisig owners will break, since you'll need to do:

  • source account
  • mint account
  • account authority (non-signer)
  • permissioned burn authority (signer)
  • multisig signers (signer)

It would be better to be clear, and have a new instruction which orders the accounts as:

  • source
  • mint
  • permissioned burn authority
  • account authority
  • multisig signers

We can add it as another instruction under the PermissionedBurn extension.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense; definitely better than making a breaking change.


if !approver_ai.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}

if *approver_ai.key != ext.authority {
return Err(ProgramError::InvalidAccountData);
}
}

let maybe_permanent_delegate = get_permanent_delegate(&mint);

if let Ok(cpi_guard) = source_account.get_extension::<CpiGuard>() {
Expand Down Expand Up @@ -1942,6 +1956,14 @@ impl Processor {
msg!("Instruction: PausableExtension");
pausable::processor::process_instruction(program_id, accounts, &input[1..])
}
PodTokenInstruction::PermissionedBurnExtension => {
msg!("Instruction: PermissionedBurnExtension");
permissioned_burn::processor::process_instruction(
program_id,
accounts,
&input[1..],
)
}
}
} else if let Ok(instruction) = TokenMetadataInstruction::unpack(input) {
token_metadata::processor::process_instruction(program_id, accounts, instruction)
Expand Down