Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

token 2022: add UpdateGroupMaxSize instruction from SPL Token Group interface #5626

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
buffalojoec marked this conversation as resolved.
Show resolved Hide resolved
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),
}
}
Loading