use {
solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
msg,
program_error::ProgramError,
program_option::COption,
pubkey::Pubkey,
},
spl_pod::optional_keys::OptionalNonZeroPubkey,
spl_token_2022::{extension::StateWithExtensions, state::Mint},
spl_token_group_interface::{
error::TokenGroupError,
instruction::{
InitializeGroup, TokenGroupInstruction, UpdateGroupAuthority, UpdateGroupMaxSize,
},
state::{TokenGroup, TokenGroupMember},
},
spl_type_length_value::state::TlvStateMut,
};
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(())
}
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)?;
{
let mint_data = mint_info.try_borrow_data()?;
let mint = StateWithExtensions::<Mint>::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());
}
}
let mut buffer = group_info.try_borrow_mut_data()?;
let mut state = TlvStateMut::unpack(&mut buffer)?;
let (group, _) = state.init_value::<TokenGroup>(false)?;
*group = TokenGroup::new(mint_info.key, data.update_authority, data.max_size.into());
Ok(())
}
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 = TlvStateMut::unpack(&mut buffer)?;
let group = state.get_first_value_mut::<TokenGroup>()?;
check_update_authority(update_authority_info, &group.update_authority)?;
group.update_max_size(data.max_size.into())?;
Ok(())
}
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 = TlvStateMut::unpack(&mut buffer)?;
let group = state.get_first_value_mut::<TokenGroup>()?;
check_update_authority(update_authority_info, &group.update_authority)?;
group.update_authority = data.new_authority;
Ok(())
}
pub fn process_initialize_member(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let member_info = next_account_info(account_info_iter)?;
let member_mint_info = next_account_info(account_info_iter)?;
let member_mint_authority_info = next_account_info(account_info_iter)?;
let group_info = next_account_info(account_info_iter)?;
let group_update_authority_info = next_account_info(account_info_iter)?;
{
let member_mint_data = member_mint_info.try_borrow_data()?;
let member_mint = StateWithExtensions::<Mint>::unpack(&member_mint_data)?;
if !member_mint_authority_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
if member_mint.base.mint_authority.as_ref() != COption::Some(member_mint_authority_info.key)
{
return Err(TokenGroupError::IncorrectMintAuthority.into());
}
}
if member_info.key == group_info.key {
return Err(TokenGroupError::MemberAccountIsGroupAccount.into());
}
let mut buffer = group_info.try_borrow_mut_data()?;
let mut state = TlvStateMut::unpack(&mut buffer)?;
let group = state.get_first_value_mut::<TokenGroup>()?;
check_update_authority(group_update_authority_info, &group.update_authority)?;
let member_number = group.increment_size()?;
let mut buffer = member_info.try_borrow_mut_data()?;
let mut state = TlvStateMut::unpack(&mut buffer)?;
let (member, _) = state.init_value::<TokenGroupMember>(false)?;
*member = TokenGroupMember::new(member_mint_info.key, group_info.key, member_number);
Ok(())
}
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
let instruction = TokenGroupInstruction::unpack(input)?;
match instruction {
TokenGroupInstruction::InitializeGroup(data) => {
msg!("Instruction: InitializeGroup");
process_initialize_group(program_id, accounts, data)
}
TokenGroupInstruction::UpdateGroupMaxSize(data) => {
msg!("Instruction: UpdateGroupMaxSize");
process_update_group_max_size(program_id, accounts, data)
}
TokenGroupInstruction::UpdateGroupAuthority(data) => {
msg!("Instruction: UpdateGroupAuthority");
process_update_group_authority(program_id, accounts, data)
}
TokenGroupInstruction::InitializeMember(_) => {
msg!("Instruction: InitializeMember");
process_initialize_member(program_id, accounts)
}
}
}