-
Notifications
You must be signed in to change notification settings - Fork 124
Permissioned burn extension #818
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 7 commits
905f41e
f0175dc
ea637bd
d918f27
6a2e5bd
21b2e65
e5ca5d4
d533794
c22a5fe
f9133d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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, | ||
| }, | ||
| )) | ||
| } |
| 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; | ||
| } |
| 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::*; |
| 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; |
| 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) | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
|
|
@@ -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}, | ||
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seeing this, I think we should add a new variant to
It would be better to be clear, and have a new instruction which orders the accounts as:
We can add it as another instruction under the There was a problem hiding this comment. Choose a reason for hiding this commentThe 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>() { | ||
|
|
@@ -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) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.