Skip to content

Commit

Permalink
token 2022: add UpdateGroupAuthority instruction from SPL Token Gro…
Browse files Browse the repository at this point in the history
…up interface
  • Loading branch information
buffalojoec committed Oct 22, 2023
1 parent 0b2bfa8 commit 2c99ff5
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 1 deletion.
21 changes: 21 additions & 0 deletions token/client/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3939,4 +3939,25 @@ where
)
.await
}

/// Update the token-group authority in a mint
pub async fn token_group_update_authority<S: Signers>(
&self,
current_authority: &Pubkey,
new_authority: Option<Pubkey>,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
self.process_ixs(
&[
spl_token_group_interface::instruction::update_group_authority(
&self.program_id,
&self.pubkey,
current_authority,
new_authority,
),
],
signing_keypairs,
)
.await
}
}
202 changes: 202 additions & 0 deletions token/program-2022-test/tests/token_group_update_authority.rs
Original file line number Diff line number Diff line change
@@ -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::<TokenGroup>().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::<TokenGroup>().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,)
);
}
31 changes: 30 additions & 1 deletion token/program-2022/src/extension/token_group/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};
Expand Down Expand Up @@ -125,6 +127,29 @@ 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::<Mint>::unpack(&mut buffer)?;
let group = state.get_extension_mut::<TokenGroup>()?;

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,
Expand All @@ -140,6 +165,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),
}
}

0 comments on commit 2c99ff5

Please sign in to comment.