From 9e3250a9ce686103c04c6f0a442f667bf9fb734f Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 18 Oct 2023 15:40:36 +0200 Subject: [PATCH] token 2022: add `UpdateGroupAuthority` instruction from SPL Token Group interface --- token/client/src/token.rs | 21 ++ .../tests/token_group_update_authority.rs | 202 ++++++++++++++++++ .../src/extension/token_group/processor.rs | 32 ++- 3 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 token/program-2022-test/tests/token_group_update_authority.rs diff --git a/token/client/src/token.rs b/token/client/src/token.rs index a1383cfb35e..78c29d17cda 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -3743,4 +3743,25 @@ where ) .await } + + /// Update the token-group authority in a mint + pub async fn token_group_update_authority( + &self, + current_authority: &Pubkey, + new_authority: Option, + signing_keypairs: &S, + ) -> TokenResult { + self.process_ixs( + &[ + spl_token_group_interface::instruction::update_group_authority( + &self.program_id, + &self.pubkey, + current_authority, + new_authority, + ), + ], + signing_keypairs, + ) + .await + } } diff --git a/token/program-2022-test/tests/token_group_update_authority.rs b/token/program-2022-test/tests/token_group_update_authority.rs new file mode 100644 index 00000000000..a6f047b2c2a --- /dev/null +++ b/token/program-2022-test/tests/token_group_update_authority.rs @@ -0,0 +1,202 @@ +#![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::{Transaction, TransactionError}, + transport::TransportError, + }, + spl_pod::optional_keys::OptionalNonZeroPubkey, + spl_token_2022::{extension::BaseStateWithExtensions, processor::Processor}, + spl_token_client::token::{ExtensionInitializationParams, TokenError as TokenClientError}, + spl_token_group_interface::{ + error::TokenGroupError, instruction::update_group_authority, 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_update() { + let authority = Keypair::new(); + let mint_keypair = Keypair::new(); + let mut test_context = setup(mint_keypair.insecure_clone(), &authority.pubkey()).await; + let payer_pubkey = test_context.context.lock().await.payer.pubkey(); + let token_context = test_context.token_context.take().unwrap(); + + let update_authority = Keypair::new(); + let mut token_group = TokenGroup::new( + &mint_keypair.pubkey(), + Some(update_authority.pubkey()).try_into().unwrap(), + 10, + ); + + token_context + .token + .token_group_initialize_with_rent_transfer( + &payer_pubkey, + &token_context.mint_authority.pubkey(), + &update_authority.pubkey(), + 10, + &[&token_context.mint_authority], + ) + .await + .unwrap(); + + let new_update_authority = Keypair::new(); + let new_update_authority_pubkey = + OptionalNonZeroPubkey::try_from(Some(new_update_authority.pubkey())).unwrap(); + token_group.update_authority = new_update_authority_pubkey; + + token_context + .token + .token_group_update_authority( + &update_authority.pubkey(), + Some(new_update_authority.pubkey()), + &[&update_authority], + ) + .await + .unwrap(); + + // check that the data is correct + let mint = token_context.token.get_mint_info().await.unwrap(); + let fetched_group = mint.get_extension::().unwrap(); + assert_eq!(fetched_group, &token_group); + + // unset + token_group.update_authority = None.try_into().unwrap(); + token_context + .token + .token_group_update_authority( + &new_update_authority.pubkey(), + None, + &[&new_update_authority], + ) + .await + .unwrap(); + + let mint = token_context.token.get_mint_info().await.unwrap(); + let fetched_group = mint.get_extension::().unwrap(); + assert_eq!(fetched_group, &token_group); + + // fail to update + let error = token_context + .token + .token_group_update_authority( + &new_update_authority.pubkey(), + Some(new_update_authority.pubkey()), + &[&new_update_authority], + ) + .await + .unwrap_err(); + assert_eq!( + error, + TokenClientError::Client(Box::new(TransportError::TransactionError( + TransactionError::InstructionError( + 0, + InstructionError::Custom(TokenGroupError::ImmutableGroup as u32) + ) + ))) + ); +} + +#[tokio::test] +async fn fail_authority_checks() { + let program_id = spl_token_2022::id(); + let authority = Keypair::new(); + let mint_keypair = Keypair::new(); + let mint_pubkey = mint_keypair.pubkey(); + let mut test_context = setup(mint_keypair, &authority.pubkey()).await; + let payer_pubkey = test_context.context.lock().await.payer.pubkey(); + let token_context = test_context.token_context.take().unwrap(); + + let update_authority = Keypair::new(); + token_context + .token + .token_group_initialize_with_rent_transfer( + &payer_pubkey, + &token_context.mint_authority.pubkey(), + &update_authority.pubkey(), + 10, + &[&token_context.mint_authority], + ) + .await + .unwrap(); + + // wrong authority + let error = token_context + .token + .token_group_update_authority(&payer_pubkey, None, &[] as &[&dyn Signer; 0]) + .await + .unwrap_err(); + assert_eq!( + error, + TokenClientError::Client(Box::new(TransportError::TransactionError( + TransactionError::InstructionError( + 0, + InstructionError::Custom(TokenGroupError::IncorrectUpdateAuthority as u32), + ) + ))) + ); + + // no signature + let mut context = test_context.context.lock().await; + let mut instruction = + update_group_authority(&program_id, &mint_pubkey, &authority.pubkey(), None); + instruction.accounts[1].is_signer = false; + let transaction = Transaction::new_signed_with_payer( + &[instruction], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + let error = context + .banks_client + .process_transaction(transaction) + .await + .unwrap_err() + .unwrap(); + assert_eq!( + error, + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature,) + ); +} diff --git a/token/program-2022/src/extension/token_group/processor.rs b/token/program-2022/src/extension/token_group/processor.rs index 3032e513b57..5fce2def9b8 100644 --- a/token/program-2022/src/extension/token_group/processor.rs +++ b/token/program-2022/src/extension/token_group/processor.rs @@ -21,7 +21,9 @@ use { spl_pod::optional_keys::OptionalNonZeroPubkey, spl_token_group_interface::{ error::TokenGroupError, - instruction::{InitializeGroup, TokenGroupInstruction, UpdateGroupMaxSize}, + instruction::{ + InitializeGroup, TokenGroupInstruction, UpdateGroupAuthority, UpdateGroupMaxSize, + }, state::TokenGroup, }, }; @@ -116,6 +118,30 @@ pub fn process_update_group_max_size( Ok(()) } +/// Processes an +/// [UpdateGroupAuthority](enum.GroupInterfaceInstruction.html) +/// instruction +pub fn process_update_group_authority( + _program_id: &Pubkey, + accounts: &[AccountInfo], + data: UpdateGroupAuthority, +) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + + let group_info = next_account_info(account_info_iter)?; + let update_authority_info = next_account_info(account_info_iter)?; + + let mut buffer = group_info.try_borrow_mut_data()?; + let mut state = StateWithExtensionsMut::::unpack(&mut buffer)?; + let group = state.get_extension_mut::()?; + + check_update_authority(update_authority_info, &group.update_authority)?; + + group.update_authority = data.new_authority; + + Ok(()) +} + /// Processes an [Instruction](enum.Instruction.html). pub fn process_instruction( program_id: &Pubkey, @@ -131,6 +157,10 @@ pub fn process_instruction( msg!("TokenGroupInstruction: UpdateGroupMaxSize"); process_update_group_max_size(program_id, accounts, data) } + TokenGroupInstruction::UpdateGroupAuthority(data) => { + msg!("TokenGroupInstruction: UpdateGroupAuthority"); + process_update_group_authority(program_id, accounts, data) + } _ => Err(ProgramError::InvalidInstructionData), } }