From f41e9d8710367eb2b8ef2f4c127f6da870560191 Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 18 Oct 2023 15:37:21 +0200 Subject: [PATCH 1/3] token 2022: add `InitializeGroup` instruction from SPL Token Group interface --- Cargo.lock | 3 + token/client/Cargo.toml | 1 + token/client/src/token.rs | 72 ++++- token/program-2022-test/Cargo.toml | 1 + .../tests/token_group_initialize.rs | 271 ++++++++++++++++++ token/program-2022/Cargo.toml | 1 + token/program-2022/src/extension/mod.rs | 9 +- .../src/extension/token_group/mod.rs | 11 + .../src/extension/token_group/processor.rs | 107 +++++++ token/program-2022/src/processor.rs | 5 +- 10 files changed, 478 insertions(+), 3 deletions(-) create mode 100644 token/program-2022-test/tests/token_group_initialize.rs 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 b5bb3fac752..cc83b39acf3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7232,6 +7232,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", @@ -7276,6 +7277,7 @@ dependencies = [ "spl-pod 0.1.0", "spl-token-2022 0.9.0", "spl-token-client", + "spl-token-group-interface", "spl-token-metadata-interface 0.2.0", "spl-transfer-hook-example", "spl-transfer-hook-interface 0.3.0", @@ -7337,6 +7339,7 @@ dependencies = [ "spl-memo 4.0.0", "spl-token 4.0.0", "spl-token-2022 0.9.0", + "spl-token-group-interface", "spl-token-metadata-interface 0.2.0", "spl-transfer-hook-interface 0.3.0", "thiserror", diff --git a/token/client/Cargo.toml b/token/client/Cargo.toml index 3d8975a582e..79a39e63545 100644 --- a/token/client/Cargo.toml +++ b/token/client/Cargo.toml @@ -24,6 +24,7 @@ spl-associated-token-account = { version = "2.0", path = "../../associated-token spl-memo = { version = "4.0.0", path = "../../memo/program", features = ["no-entrypoint"] } spl-token = { version = "4.0", path="../program", features = [ "no-entrypoint" ] } spl-token-2022 = { version = "0.9", path="../program-2022" } +spl-token-group-interface = { version = "0.1", path="../../token-group/interface" } spl-token-metadata-interface = { version = "0.2", path="../../token-metadata/interface" } spl-transfer-hook-interface = { version = "0.3", path="../transfer-hook/interface" } thiserror = "1.0" diff --git a/token/client/src/token.rs b/token/client/src/token.rs index b039d2306ce..44536c0aecc 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -44,7 +44,7 @@ use { }, cpi_guard, default_account_state, group_pointer, interest_bearing_mint, memo_transfer, metadata_pointer, transfer_fee, transfer_hook, BaseStateWithExtensions, ExtensionType, - StateWithExtensionsOwned, + StateWithExtensions, StateWithExtensionsOwned, }, instruction, offchain, proof::ProofLocation, @@ -61,6 +61,7 @@ use { }, state::{Account, AccountState, Mint, Multisig}, }, + spl_token_group_interface::state::TokenGroup, spl_token_metadata_interface::state::{Field, TokenMetadata}, std::{ fmt, io, @@ -3649,4 +3650,73 @@ where ) .await } + + /// Initialize token-group on a mint + pub async fn token_group_initialize( + &self, + mint_authority: &Pubkey, + update_authority: &Pubkey, + max_size: u32, + signing_keypairs: &S, + ) -> TokenResult { + self.process_ixs( + &[spl_token_group_interface::instruction::initialize_group( + &self.program_id, + &self.pubkey, + &self.pubkey, + mint_authority, + Some(*update_authority), + max_size, + )], + signing_keypairs, + ) + .await + } + + async fn get_additional_rent_for_fixed_len_extension( + &self, + ) -> TokenResult { + let account = self.get_account(self.pubkey).await?; + let account_lamports = account.lamports; + let mint_state = StateWithExtensions::::unpack(&account.data)?; + let new_account_len = mint_state.try_get_new_account_len::(size_of::())?; + let new_rent_exempt_minimum = self + .client + .get_minimum_balance_for_rent_exemption(new_account_len) + .await + .map_err(TokenError::Client)?; + Ok(new_rent_exempt_minimum.saturating_sub(account_lamports)) + } + + /// Initialize token-group on a mint + #[allow(clippy::too_many_arguments)] + pub async fn token_group_initialize_with_rent_transfer( + &self, + payer: &Pubkey, + mint_authority: &Pubkey, + update_authority: &Pubkey, + max_size: u32, + signing_keypairs: &S, + ) -> TokenResult { + let additional_lamports = self + .get_additional_rent_for_fixed_len_extension::() + .await?; + let mut instructions = vec![]; + if additional_lamports > 0 { + instructions.push(system_instruction::transfer( + payer, + &self.pubkey, + additional_lamports, + )); + } + instructions.push(spl_token_group_interface::instruction::initialize_group( + &self.program_id, + &self.pubkey, + &self.pubkey, + mint_authority, + Some(*update_authority), + max_size, + )); + self.process_ixs(&instructions, signing_keypairs).await + } } diff --git a/token/program-2022-test/Cargo.toml b/token/program-2022-test/Cargo.toml index ec702843feb..809b2e516b2 100644 --- a/token/program-2022-test/Cargo.toml +++ b/token/program-2022-test/Cargo.toml @@ -29,6 +29,7 @@ spl-pod = { version = "0.1.0", path = "../../libraries/pod" } spl-token-2022 = { version = "0.9", path="../program-2022", features = ["no-entrypoint"] } spl-instruction-padding = { version = "0.1.0", path="../../instruction-padding/program", features = ["no-entrypoint"] } spl-token-client = { version = "0.8", path = "../client" } +spl-token-group-interface = { version = "0.1", path = "../../token-group/interface" } spl-token-metadata-interface = { version = "0.2", path = "../../token-metadata/interface" } spl-transfer-hook-example = { version = "0.3", path="../transfer-hook/example", features = ["no-entrypoint"] } spl-transfer-hook-interface = { version = "0.3", path="../transfer-hook/interface" } diff --git a/token/program-2022-test/tests/token_group_initialize.rs b/token/program-2022-test/tests/token_group_initialize.rs new file mode 100644 index 00000000000..54ecf3c5dab --- /dev/null +++ b/token/program-2022-test/tests/token_group_initialize.rs @@ -0,0 +1,271 @@ +#![cfg(feature = "test-sbf")] + +mod program_test; +use { + program_test::TestContext, + solana_program_test::{processor, tokio, ProgramTest}, + solana_sdk::{ + instruction::InstructionError, pubkey::Pubkey, signature::Signer, signer::keypair::Keypair, + transaction::TransactionError, transport::TransportError, + }, + spl_pod::bytemuck::pod_from_bytes, + spl_token_2022::{error::TokenError, extension::BaseStateWithExtensions, processor::Processor}, + spl_token_client::token::{ExtensionInitializationParams, TokenError as TokenClientError}, + spl_token_group_interface::{error::TokenGroupError, state::TokenGroup}, + std::{convert::TryInto, sync::Arc}, +}; + +fn setup_program_test() -> ProgramTest { + let mut program_test = ProgramTest::default(); + program_test.add_program( + "spl_token_2022", + spl_token_2022::id(), + processor!(Processor::process), + ); + program_test +} + +async fn setup(mint: Keypair, authority: &Pubkey) -> TestContext { + let program_test = setup_program_test(); + + let context = program_test.start_with_context().await; + let context = Arc::new(tokio::sync::Mutex::new(context)); + let mut context = TestContext { + context, + token_context: None, + }; + let group_address = Some(mint.pubkey()); + context + .init_token_with_mint_keypair_and_freeze_authority( + mint, + vec![ExtensionInitializationParams::GroupPointer { + authority: Some(*authority), + group_address, + }], + None, + ) + .await + .unwrap(); + context +} + +#[tokio::test] +async fn success_initialize() { + let authority = Pubkey::new_unique(); + let mint_keypair = Keypair::new(); + let mut test_context = setup(mint_keypair, &authority).await; + let payer_pubkey = test_context.context.lock().await.payer.pubkey(); + let token_context = test_context.token_context.take().unwrap(); + + let update_authority = Pubkey::new_unique(); + let max_size = 10; + let token_group = TokenGroup::new( + token_context.token.get_address(), + Some(update_authority).try_into().unwrap(), + max_size, + ); + + // fails without more lamports for new rent-exemption + let error = token_context + .token + .token_group_initialize( + &token_context.mint_authority.pubkey(), + &update_authority, + max_size, + &[&token_context.mint_authority], + ) + .await + .unwrap_err(); + assert_eq!( + error, + TokenClientError::Client(Box::new(TransportError::TransactionError( + TransactionError::InsufficientFundsForRent { account_index: 2 } + ))) + ); + + // fail wrong signer + let not_mint_authority = Keypair::new(); + let error = token_context + .token + .token_group_initialize_with_rent_transfer( + &payer_pubkey, + ¬_mint_authority.pubkey(), + &update_authority, + max_size, + &[¬_mint_authority], + ) + .await + .unwrap_err(); + assert_eq!( + error, + TokenClientError::Client(Box::new(TransportError::TransactionError( + TransactionError::InstructionError( + 1, + InstructionError::Custom(TokenGroupError::IncorrectMintAuthority as u32) + ) + ))) + ); + + token_context + .token + .token_group_initialize_with_rent_transfer( + &payer_pubkey, + &token_context.mint_authority.pubkey(), + &update_authority, + max_size, + &[&token_context.mint_authority], + ) + .await + .unwrap(); + + // check that the data is correct + let mint_info = token_context.token.get_mint_info().await.unwrap(); + let group_bytes = mint_info.get_extension_bytes::().unwrap(); + let fetched_group = pod_from_bytes::(group_bytes).unwrap(); + assert_eq!(fetched_group, &token_group); + + // fail double-init + let error = token_context + .token + .token_group_initialize_with_rent_transfer( + &payer_pubkey, + &token_context.mint_authority.pubkey(), + &update_authority, + max_size, + &[&token_context.mint_authority], + ) + .await + .unwrap_err(); + assert_eq!( + error, + TokenClientError::Client(Box::new(TransportError::TransactionError( + TransactionError::InstructionError( + 1, + InstructionError::Custom(TokenError::ExtensionAlreadyInitialized as u32) + ) + ))) + ); +} + +#[tokio::test] +async fn fail_without_group_pointer() { + let mut test_context = { + let mint_keypair = Keypair::new(); + let program_test = setup_program_test(); + let context = program_test.start_with_context().await; + let context = Arc::new(tokio::sync::Mutex::new(context)); + let mut context = TestContext { + context, + token_context: None, + }; + context + .init_token_with_mint_keypair_and_freeze_authority(mint_keypair, vec![], None) + .await + .unwrap(); + context + }; + + let payer_pubkey = test_context.context.lock().await.payer.pubkey(); + let token_context = test_context.token_context.take().unwrap(); + + let error = token_context + .token + .token_group_initialize_with_rent_transfer( + &payer_pubkey, + &token_context.mint_authority.pubkey(), + &Pubkey::new_unique(), + 5, + &[&token_context.mint_authority], + ) + .await + .unwrap_err(); + assert_eq!( + error, + TokenClientError::Client(Box::new(TransportError::TransactionError( + TransactionError::InstructionError( + 1, + InstructionError::Custom(TokenError::InvalidExtensionCombination as u32) + ) + ))) + ); +} + +#[tokio::test] +async fn fail_init_in_another_mint() { + let authority = Pubkey::new_unique(); + let first_mint_keypair = Keypair::new(); + let first_mint = first_mint_keypair.pubkey(); + let mut test_context = setup(first_mint_keypair, &authority).await; + let second_mint_keypair = Keypair::new(); + let second_mint = second_mint_keypair.pubkey(); + test_context + .init_token_with_mint_keypair_and_freeze_authority( + second_mint_keypair, + vec![ExtensionInitializationParams::GroupPointer { + authority: Some(authority), + group_address: Some(second_mint), + }], + None, + ) + .await + .unwrap(); + + let token_context = test_context.token_context.take().unwrap(); + + let error = token_context + .token + .process_ixs( + &[spl_token_group_interface::instruction::initialize_group( + &spl_token_2022::id(), + &first_mint, + token_context.token.get_address(), + &token_context.mint_authority.pubkey(), + Some(Pubkey::new_unique()), + 5, + )], + &[&token_context.mint_authority], + ) + .await + .unwrap_err(); + + assert_eq!( + error, + TokenClientError::Client(Box::new(TransportError::TransactionError( + TransactionError::InstructionError( + 0, + InstructionError::Custom(TokenError::MintMismatch as u32) + ) + ))) + ); +} + +#[tokio::test] +async fn fail_without_signature() { + let authority = Pubkey::new_unique(); + let mint_keypair = Keypair::new(); + let mut test_context = setup(mint_keypair, &authority).await; + + let token_context = test_context.token_context.take().unwrap(); + + let mut instruction = spl_token_group_interface::instruction::initialize_group( + &spl_token_2022::id(), + token_context.token.get_address(), + token_context.token.get_address(), + &token_context.mint_authority.pubkey(), + Some(Pubkey::new_unique()), + 5, + ); + instruction.accounts[2].is_signer = false; + let error = token_context + .token + .process_ixs(&[instruction], &[] as &[&dyn Signer; 0]) // yuck, but the compiler needs it + .await + .unwrap_err(); + + assert_eq!( + error, + TokenClientError::Client(Box::new(TransportError::TransactionError( + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature) + ))) + ); +} diff --git a/token/program-2022/Cargo.toml b/token/program-2022/Cargo.toml index 7d9c2a1be07..8376cf835d7 100644 --- a/token/program-2022/Cargo.toml +++ b/token/program-2022/Cargo.toml @@ -27,6 +27,7 @@ solana-program = "1.17.2" solana-zk-token-sdk = "1.17.2" 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 9c050649c8c..7361d54d62d 100644 --- a/token/program-2022/src/extension/mod.rs +++ b/token/program-2022/src/extension/mod.rs @@ -36,6 +36,7 @@ use { bytemuck::{pod_from_bytes, pod_from_bytes_mut, pod_get_packed_len}, primitives::PodU16, }, + spl_token_group_interface::state::TokenGroup, spl_type_length_value::variable_len_pack::VariableLenPack, std::{ cmp::Ordering, @@ -70,6 +71,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 @@ -948,6 +951,8 @@ pub enum ExtensionType { /// Mint contains a pointer to another account (or the same account) that /// holds group configurations GroupPointer, + /// Mint contains token group configurations + TokenGroup, /// Test variable-length mint extension #[cfg(test)] VariableLenMintTest = u16::MAX - 2, @@ -1025,6 +1030,7 @@ impl ExtensionType { ExtensionType::MetadataPointer => pod_get_packed_len::(), ExtensionType::TokenMetadata => unreachable!(), ExtensionType::GroupPointer => pod_get_packed_len::(), + ExtensionType::TokenGroup => pod_get_packed_len::(), #[cfg(test)] ExtensionType::AccountPaddingTest => pod_get_packed_len::(), #[cfg(test)] @@ -1085,7 +1091,8 @@ impl ExtensionType { | ExtensionType::ConfidentialTransferFeeConfig | ExtensionType::MetadataPointer | ExtensionType::TokenMetadata - | ExtensionType::GroupPointer => AccountType::Mint, + | ExtensionType::GroupPointer + | ExtensionType::TokenGroup => AccountType::Mint, ExtensionType::ImmutableOwner | ExtensionType::TransferFeeAmount | ExtensionType::ConfidentialTransferAccount 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..cd00b5a01a9 --- /dev/null +++ b/token/program-2022/src/extension/token_group/processor.rs @@ -0,0 +1,107 @@ +//! Token-group processor + +use { + crate::{ + check_program_account, + error::TokenError, + extension::{ + group_pointer::GroupPointer, BaseStateWithExtensions, ExtensionType, + StateWithExtensions, StateWithExtensionsMut, + }, + 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::{ + error::TokenGroupError, + instruction::{InitializeGroup, TokenGroupInstruction}, + state::TokenGroup, + }, +}; + +fn realloc_mint(mint_info: &AccountInfo, extension: ExtensionType) -> Result<(), ProgramError> { + let extension_len = extension.try_get_tlv_len()?; + let new_account_len = mint_info + .data_len() + .checked_add(extension_len) + .ok_or::(TokenError::Overflow.into())?; + mint_info.realloc(new_account_len, false)?; + Ok(()) +} + +/// 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(TokenGroupError::IncorrectMintAuthority.into()); + } + + if mint.get_extension::().is_err() { + msg!( + "A mint with group configurations must have the group-pointer extension \ + initialized" + ); + return Err(TokenError::InvalidExtensionCombination.into()); + } + } + + // Reallocate the mint for the new extension + realloc_mint(mint_info, ExtensionType::TokenGroup)?; + + // Allocate a TLV entry for the space and write it in + // Assumes that there's enough SOL for the new rent-exemption + let mut mint_data = mint_info.try_borrow_mut_data()?; + let mut mint = StateWithExtensionsMut::::unpack(&mut mint_data)?; + let group = mint.init_extension::(false)?; + *group = TokenGroup::new(mint_info.key, 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 65f44558b83..95068c3a0ba 100644 --- a/token/program-2022/src/processor.rs +++ b/token/program-2022/src/processor.rs @@ -19,7 +19,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, @@ -42,6 +42,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}, }; @@ -1690,6 +1691,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()) } From 3a0de2ee6b403672f46e83b7887c94445ea8a24e Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 25 Oct 2023 14:22:07 +0200 Subject: [PATCH 2/3] feedback: rework rent check and test tx dedupe --- token/client/src/token.rs | 24 +++++++++++-------- .../tests/token_group_initialize.rs | 4 ++-- .../src/extension/token_group/processor.rs | 23 ++++-------------- 3 files changed, 20 insertions(+), 31 deletions(-) diff --git a/token/client/src/token.rs b/token/client/src/token.rs index 44536c0aecc..5069ad4c187 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -43,8 +43,8 @@ use { ConfidentialTransferFeeConfig, }, cpi_guard, default_account_state, group_pointer, interest_bearing_mint, memo_transfer, - metadata_pointer, transfer_fee, transfer_hook, BaseStateWithExtensions, ExtensionType, - StateWithExtensions, StateWithExtensionsOwned, + metadata_pointer, transfer_fee, transfer_hook, BaseStateWithExtensions, Extension, + ExtensionType, StateWithExtensionsOwned, }, instruction, offchain, proof::ProofLocation, @@ -3678,14 +3678,18 @@ where ) -> TokenResult { let account = self.get_account(self.pubkey).await?; let account_lamports = account.lamports; - let mint_state = StateWithExtensions::::unpack(&account.data)?; - let new_account_len = mint_state.try_get_new_account_len::(size_of::())?; - let new_rent_exempt_minimum = self - .client - .get_minimum_balance_for_rent_exemption(new_account_len) - .await - .map_err(TokenError::Client)?; - Ok(new_rent_exempt_minimum.saturating_sub(account_lamports)) + let mint_state = self.unpack_mint_info(account)?; + if mint_state.get_extension::().is_ok() { + Ok(0) + } else { + let new_account_len = mint_state.try_get_new_account_len::()?; + let new_rent_exempt_minimum = self + .client + .get_minimum_balance_for_rent_exemption(new_account_len) + .await + .map_err(TokenError::Client)?; + Ok(new_rent_exempt_minimum.saturating_sub(account_lamports)) + } } /// Initialize token-group on a mint diff --git a/token/program-2022-test/tests/token_group_initialize.rs b/token/program-2022-test/tests/token_group_initialize.rs index 54ecf3c5dab..e9d1296c31f 100644 --- a/token/program-2022-test/tests/token_group_initialize.rs +++ b/token/program-2022-test/tests/token_group_initialize.rs @@ -131,7 +131,7 @@ async fn success_initialize() { &payer_pubkey, &token_context.mint_authority.pubkey(), &update_authority, - max_size, + 12, // Change so we get a different transaction &[&token_context.mint_authority], ) .await @@ -140,7 +140,7 @@ async fn success_initialize() { error, TokenClientError::Client(Box::new(TransportError::TransactionError( TransactionError::InstructionError( - 1, + 0, // No additional rent InstructionError::Custom(TokenError::ExtensionAlreadyInitialized as u32) ) ))) diff --git a/token/program-2022/src/extension/token_group/processor.rs b/token/program-2022/src/extension/token_group/processor.rs index cd00b5a01a9..9966f02ff5e 100644 --- a/token/program-2022/src/extension/token_group/processor.rs +++ b/token/program-2022/src/extension/token_group/processor.rs @@ -5,8 +5,8 @@ use { check_program_account, error::TokenError, extension::{ - group_pointer::GroupPointer, BaseStateWithExtensions, ExtensionType, - StateWithExtensions, StateWithExtensionsMut, + alloc_and_serialize, group_pointer::GroupPointer, BaseStateWithExtensions, + StateWithExtensions, }, state::Mint, }, @@ -25,16 +25,6 @@ use { }, }; -fn realloc_mint(mint_info: &AccountInfo, extension: ExtensionType) -> Result<(), ProgramError> { - let extension_len = extension.try_get_tlv_len()?; - let new_account_len = mint_info - .data_len() - .checked_add(extension_len) - .ok_or::(TokenError::Overflow.into())?; - mint_info.realloc(new_account_len, false)?; - Ok(()) -} - /// Processes a [InitializeGroup](enum.TokenGroupInstruction.html) instruction. pub fn process_initialize_group( _program_id: &Pubkey, @@ -78,15 +68,10 @@ pub fn process_initialize_group( } } - // Reallocate the mint for the new extension - realloc_mint(mint_info, ExtensionType::TokenGroup)?; - // Allocate a TLV entry for the space and write it in // Assumes that there's enough SOL for the new rent-exemption - let mut mint_data = mint_info.try_borrow_mut_data()?; - let mut mint = StateWithExtensionsMut::::unpack(&mut mint_data)?; - let group = mint.init_extension::(false)?; - *group = TokenGroup::new(mint_info.key, data.update_authority, data.max_size.into()); + let group = TokenGroup::new(mint_info.key, data.update_authority, data.max_size.into()); + alloc_and_serialize::(group_info, &group, false)?; Ok(()) } From 74a835916d74bc666e7ab8d1767d05f21c427e9f Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 9 Nov 2023 12:08:37 +0000 Subject: [PATCH 3/3] clip clip clippy --- token/client/src/token.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/token/client/src/token.rs b/token/client/src/token.rs index 5069ad4c187..4af5eaa4607 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -3693,7 +3693,6 @@ where } /// Initialize token-group on a mint - #[allow(clippy::too_many_arguments)] pub async fn token_group_initialize_with_rent_transfer( &self, payer: &Pubkey,