Skip to content

Commit

Permalink
token 2022: add UpdateGroupMaxSize instruction from SPL Token Group…
Browse files Browse the repository at this point in the history
… interface
  • Loading branch information
buffalojoec committed Nov 14, 2023
1 parent a1404b3 commit 221ce2d
Show file tree
Hide file tree
Showing 3 changed files with 316 additions and 2 deletions.
21 changes: 21 additions & 0 deletions token/client/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3722,4 +3722,25 @@ where
));
self.process_ixs(&instructions, signing_keypairs).await
}

/// Update a token-group max size on a mint
pub async fn token_group_update_max_size<S: Signers>(
&self,
update_authority: &Pubkey,
new_max_size: u32,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
self.process_ixs(
&[
spl_token_group_interface::instruction::update_group_max_size(
&self.program_id,
&self.pubkey,
update_authority,
new_max_size,
),
],
signing_keypairs,
)
.await
}
}
249 changes: 249 additions & 0 deletions token/program-2022-test/tests/token_group_update_max_size.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
#![cfg(feature = "test-sbf")]
#![allow(clippy::items_after_test_module)]

mod program_test;
use {
program_test::TestContext,
solana_program_test::{processor, tokio, ProgramTest},
solana_sdk::{
account::Account as SolanaAccount, instruction::InstructionError, pubkey::Pubkey,
signature::Signer, signer::keypair::Keypair, transaction::TransactionError,
transport::TransportError,
},
spl_token_2022::{extension::BaseStateWithExtensions, processor::Processor},
spl_token_client::token::{ExtensionInitializationParams, TokenError as TokenClientError},
spl_token_group_interface::{
error::TokenGroupError, instruction::update_group_max_size, state::TokenGroup,
},
std::{convert::TryInto, sync::Arc},
test_case::test_case,
};

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
}

// Successful attempts to set higher than size
#[test_case(0, 0, 10)]
#[test_case(5, 0, 10)]
#[test_case(50, 0, 200_000)]
#[test_case(100_000, 100_000, 200_000)]
#[test_case(50, 0, 300_000_000)]
#[test_case(100_000, 100_000, 300_000_000)]
#[test_case(100_000_000, 100_000_000, 300_000_000)]
#[test_case(0, 0, u32::MAX)]
#[test_case(200_000, 200_000, u32::MAX)]
#[test_case(300_000_000, 300_000_000, u32::MAX)]
// Attempts to set lower than size
#[test_case(5, 5, 4)]
#[test_case(200_000, 200_000, 50)]
#[test_case(200_000, 200_000, 100_000)]
#[test_case(300_000_000, 300_000_000, 50)]
#[test_case(u32::MAX, u32::MAX, 0)]
#[tokio::test]
async fn test_update_group_max_size(max_size: u32, size: u32, new_max_size: u32) {
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(),
max_size,
);

token_context
.token
.token_group_initialize_with_rent_transfer(
&payer_pubkey,
&token_context.mint_authority.pubkey(),
&update_authority.pubkey(),
max_size,
&[&token_context.mint_authority],
)
.await
.unwrap();

{
// Update the group's size manually
let mut context = test_context.context.lock().await;

let group_mint_account = context
.banks_client
.get_account(mint_keypair.pubkey())
.await
.unwrap()
.unwrap();

let old_data = context
.banks_client
.get_account(mint_keypair.pubkey())
.await
.unwrap()
.unwrap()
.data;

let data = {
// 0....81: mint
// 82...164: padding
// 165..166: account type
// 167..170: extension discriminator (GroupPointer)
// 171..202: authority
// 203..234: group pointer
// 235..238: extension discriminator (TokenGroup)
// 239..270: mint
// 271..302: update_authority
// 303..306: size
// 307..310: max_size
let (front, back) = old_data.split_at(302);
let (_, back) = back.split_at(3);
let size_bytes = size.to_le_bytes();
let mut bytes = vec![];
bytes.extend_from_slice(front);
bytes.extend_from_slice(&size_bytes);
bytes.extend_from_slice(back);
bytes
};

context.set_account(
&mint_keypair.pubkey(),
&SolanaAccount {
data,
..group_mint_account
}
.into(),
);

token_group.size = size.into();
}

token_group.max_size = new_max_size.into();

if new_max_size < size {
let error = token_context
.token
.token_group_update_max_size(
&update_authority.pubkey(),
new_max_size,
&[&update_authority],
)
.await
.unwrap_err();
assert_eq!(
error,
TokenClientError::Client(Box::new(TransportError::TransactionError(
TransactionError::InstructionError(
0,
InstructionError::Custom(TokenGroupError::SizeExceedsNewMaxSize as u32)
)
))),
);
} else {
token_context
.token
.token_group_update_max_size(
&update_authority.pubkey(),
new_max_size,
&[&update_authority],
)
.await
.unwrap();

let mint_info = token_context.token.get_mint_info().await.unwrap();
let fetched_group = mint_info.get_extension::<TokenGroup>().unwrap();
assert_eq!(fetched_group, &token_group);
}
}

#[tokio::test]
async fn fail_authority_checks() {
let authority = Keypair::new();
let mint_keypair = Keypair::new();
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();

// no signature
let mut instruction = update_group_max_size(
&spl_token_2022::id(),
token_context.token.get_address(),
&update_authority.pubkey(),
20,
);
instruction.accounts[1].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)
)))
);

// wrong authority
let wrong_authority = Keypair::new();
let error = token_context
.token
.token_group_update_max_size(&wrong_authority.pubkey(), 20, &[&wrong_authority])
.await
.unwrap_err();
assert_eq!(
error,
TokenClientError::Client(Box::new(TransportError::TransactionError(
TransactionError::InstructionError(
0,
InstructionError::Custom(TokenGroupError::IncorrectUpdateAuthority as u32)
)
)))
);
}
48 changes: 46 additions & 2 deletions token/program-2022/src/extension/token_group/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use {
error::TokenError,
extension::{
alloc_and_serialize, group_pointer::GroupPointer, BaseStateWithExtensions,
StateWithExtensions,
StateWithExtensions, StateWithExtensionsMut,
},
state::Mint,
},
Expand All @@ -18,13 +18,29 @@ use {
program_option::COption,
pubkey::Pubkey,
},
spl_pod::optional_keys::OptionalNonZeroPubkey,
spl_token_group_interface::{
error::TokenGroupError,
instruction::{InitializeGroup, TokenGroupInstruction},
instruction::{InitializeGroup, TokenGroupInstruction, UpdateGroupMaxSize},
state::TokenGroup,
},
};

fn check_update_authority(
update_authority_info: &AccountInfo,
expected_update_authority: &OptionalNonZeroPubkey,
) -> Result<(), ProgramError> {
if !update_authority_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
let update_authority = Option::<Pubkey>::from(*expected_update_authority)
.ok_or(TokenGroupError::ImmutableGroup)?;
if update_authority != *update_authority_info.key {
return Err(TokenGroupError::IncorrectUpdateAuthority.into());
}
Ok(())
}

/// Processes a [InitializeGroup](enum.TokenGroupInstruction.html) instruction.
pub fn process_initialize_group(
_program_id: &Pubkey,
Expand Down Expand Up @@ -76,6 +92,30 @@ pub fn process_initialize_group(
Ok(())
}

/// Processes an
/// [UpdateGroupMaxSize](enum.GroupInterfaceInstruction.html)
/// instruction
pub fn process_update_group_max_size(
_program_id: &Pubkey,
accounts: &[AccountInfo],
data: UpdateGroupMaxSize,
) -> 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_max_size(data.max_size.into())?;

Ok(())
}

/// Processes an [Instruction](enum.Instruction.html).
pub fn process_instruction(
program_id: &Pubkey,
Expand All @@ -87,6 +127,10 @@ pub fn process_instruction(
msg!("TokenGroupInstruction: InitializeGroup");
process_initialize_group(program_id, accounts, data)
}
TokenGroupInstruction::UpdateGroupMaxSize(data) => {
msg!("TokenGroupInstruction: UpdateGroupMaxSize");
process_update_group_max_size(program_id, accounts, data)
}
_ => Err(ProgramError::InvalidInstructionData),
}
}

0 comments on commit 221ce2d

Please sign in to comment.