From dbd9ef8c62adcdd9c91a0317b2b14c2bfe05f373 Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 18 Oct 2023 15:37:21 +0200 Subject: [PATCH] token 2022: add `InitializeGroup` instruction from SPL Token Group interface --- Cargo.lock | 1 + token/program-2022/Cargo.toml | 1 + token/program-2022/src/extension/mod.rs | 7 ++ .../src/extension/token_group/mod.rs | 11 +++ .../src/extension/token_group/processor.rs | 87 +++++++++++++++++++ token/program-2022/src/processor.rs | 5 +- 6 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 token/program-2022/src/extension/token_group/mod.rs create mode 100644 token/program-2022/src/extension/token_group/processor.rs diff --git a/Cargo.lock b/Cargo.lock index 4019f31f9e4..ac0ad88f51b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7067,6 +7067,7 @@ dependencies = [ "spl-memo 4.0.0", "spl-pod 0.1.0", "spl-token 4.0.0", + "spl-token-group-interface", "spl-token-metadata-interface 0.2.0", "spl-transfer-hook-interface 0.3.0", "spl-type-length-value 0.3.0", diff --git a/token/program-2022/Cargo.toml b/token/program-2022/Cargo.toml index d1aa8e58dbe..badd7929cdb 100644 --- a/token/program-2022/Cargo.toml +++ b/token/program-2022/Cargo.toml @@ -27,6 +27,7 @@ solana-program = "1.16.16" solana-zk-token-sdk = "1.16.16" spl-memo = { version = "4.0.0", path = "../../memo/program", features = [ "no-entrypoint" ] } spl-token = { version = "4.0", path = "../program", features = ["no-entrypoint"] } +spl-token-group-interface = { version = "0.1.0", path = "../../token-group/interface" } spl-token-metadata-interface = { version = "0.2.0", path = "../../token-metadata/interface" } spl-transfer-hook-interface = { version = "0.3.0", path = "../transfer-hook-interface" } spl-type-length-value = { version = "0.3.0", path = "../../libraries/type-length-value" } diff --git a/token/program-2022/src/extension/mod.rs b/token/program-2022/src/extension/mod.rs index 23afe0a3694..4c9d43ef8ef 100644 --- a/token/program-2022/src/extension/mod.rs +++ b/token/program-2022/src/extension/mod.rs @@ -29,6 +29,7 @@ use { program_error::ProgramError, program_pack::{IsInitialized, Pack}, }, + spl_token_group_interface::state::TokenGroup, spl_pod::{ bytemuck::{pod_from_bytes, pod_from_bytes_mut, pod_get_packed_len}, primitives::PodU16, @@ -68,6 +69,8 @@ pub mod non_transferable; pub mod permanent_delegate; /// Utility to reallocate token accounts pub mod reallocate; +/// Token-group extension +pub mod token_group; /// Token-metadata extension pub mod token_metadata; /// Transfer Fee extension @@ -904,6 +907,8 @@ pub enum ExtensionType { ConfidentialTransferFeeAmount, /// Mint contains a pointer to another account (or the same account) that holds metadata MetadataPointer, + /// Mint contains token group configurations + TokenGroup, /// Mint contains token-metadata TokenMetadata, /// Test variable-length mint extension @@ -979,6 +984,7 @@ impl ExtensionType { pod_get_packed_len::() } ExtensionType::MetadataPointer => pod_get_packed_len::(), + ExtensionType::TokenGroup => pod_get_packed_len::(), ExtensionType::TokenMetadata => unreachable!(), #[cfg(test)] ExtensionType::AccountPaddingTest => pod_get_packed_len::(), @@ -1039,6 +1045,7 @@ impl ExtensionType { | ExtensionType::TransferHook | ExtensionType::ConfidentialTransferFeeConfig | ExtensionType::MetadataPointer + | ExtensionType::TokenGroup | ExtensionType::TokenMetadata => AccountType::Mint, ExtensionType::ImmutableOwner | ExtensionType::TransferFeeAmount diff --git a/token/program-2022/src/extension/token_group/mod.rs b/token/program-2022/src/extension/token_group/mod.rs new file mode 100644 index 00000000000..986bda89999 --- /dev/null +++ b/token/program-2022/src/extension/token_group/mod.rs @@ -0,0 +1,11 @@ +use { + crate::extension::{Extension, ExtensionType}, + spl_token_group_interface::state::TokenGroup, +}; + +/// Instruction processor for the TokenGroup extensions +pub mod processor; + +impl Extension for TokenGroup { + const TYPE: ExtensionType = ExtensionType::TokenGroup; +} diff --git a/token/program-2022/src/extension/token_group/processor.rs b/token/program-2022/src/extension/token_group/processor.rs new file mode 100644 index 00000000000..fec911afe9f --- /dev/null +++ b/token/program-2022/src/extension/token_group/processor.rs @@ -0,0 +1,87 @@ +//! Token-group processor + +use { + crate::{ + check_program_account, + error::TokenError, + extension::StateWithExtensions, + state::Mint, + }, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + msg, + program_error::ProgramError, + program_option::COption, + pubkey::Pubkey, + }, + spl_token_group_interface::{ + instruction::{ + InitializeGroup, TokenGroupInstruction, + }, + state::TokenGroup, + }, + spl_type_length_value::state::TlvStateMut, +}; + +/// Processes a [InitializeGroup](enum.TokenGroupInstruction.html) instruction. +pub fn process_initialize_group( + _program_id: &Pubkey, + accounts: &[AccountInfo], + data: InitializeGroup, +) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + + let group_info = next_account_info(account_info_iter)?; + let mint_info = next_account_info(account_info_iter)?; + let mint_authority_info = next_account_info(account_info_iter)?; + + // check that the mint and group accounts are the same, since the group + // extension should only describe itself + if group_info.key != mint_info.key { + msg!("Group configurations for a mint must be initialized in the mint itself."); + return Err(TokenError::MintMismatch.into()); + } + + // scope the mint authority check, since the mint is in the same account! + { + // This check isn't really needed since we'll be writing into the account, + // but auditors like it + check_program_account(mint_info.owner)?; + let mint_data = mint_info.try_borrow_data()?; + let mint = StateWithExtensions::::unpack(&mint_data)?; + + if !mint_authority_info.is_signer { + return Err(ProgramError::MissingRequiredSignature); + } + if mint.base.mint_authority.as_ref() != COption::Some(mint_authority_info.key) { + return Err(TokenError::IncorrectMintAuthority.into()); + } + } + + // Allocate a TLV entry for the space and write it in + // Assumes that there's enough SOL for the new rent-exemption + let mut buffer = group_info.try_borrow_mut_data()?; + let mut state = TlvStateMut::unpack(&mut buffer)?; + let (group, _) = state.init_value::(false)?; + *group = TokenGroup::new(data.update_authority, data.max_size.into()); + + Ok(()) +} + +/// Processes an [Instruction](enum.Instruction.html). +pub fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction: TokenGroupInstruction, +) -> ProgramResult { + match instruction { + TokenGroupInstruction::InitializeGroup(data) => { + msg!("TokenGroupInstruction: InitializeGroup"); + process_initialize_group(program_id, accounts, data) + } + _ => { + Err(ProgramError::InvalidInstructionData) + } + } +} diff --git a/token/program-2022/src/processor.rs b/token/program-2022/src/processor.rs index 27b58ec3a88..f023d927b3c 100644 --- a/token/program-2022/src/processor.rs +++ b/token/program-2022/src/processor.rs @@ -18,7 +18,7 @@ use { mint_close_authority::MintCloseAuthority, non_transferable::{NonTransferable, NonTransferableAccount}, permanent_delegate::{get_permanent_delegate, PermanentDelegate}, - reallocate, token_metadata, + reallocate, token_group, token_metadata, transfer_fee::{self, TransferFeeAmount, TransferFeeConfig}, transfer_hook::{self, TransferHook, TransferHookAccount}, AccountType, BaseStateWithExtensions, ExtensionType, StateWithExtensions, @@ -41,6 +41,7 @@ use { system_instruction, system_program, sysvar::{rent::Rent, Sysvar}, }, + spl_token_group_interface::instruction::TokenGroupInstruction, spl_token_metadata_interface::instruction::TokenMetadataInstruction, std::convert::{TryFrom, TryInto}, }; @@ -1664,6 +1665,8 @@ impl Processor { } } else if let Ok(instruction) = TokenMetadataInstruction::unpack(input) { token_metadata::processor::process_instruction(program_id, accounts, instruction) + } else if let Ok(instruction) = TokenGroupInstruction::unpack(input) { + token_group::processor::process_instruction(program_id, accounts, instruction) } else { Err(TokenError::InvalidInstruction.into()) }