use {
crate::{
check_program_account,
error::TokenError,
extension::{
alloc_and_serialize, group_member_pointer::GroupMemberPointer,
group_pointer::GroupPointer, BaseStateWithExtensions, StateWithExtensions,
StateWithExtensionsMut,
},
state::Mint,
},
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_group_interface::{
error::TokenGroupError,
instruction::{
InitializeGroup, TokenGroupInstruction, UpdateGroupAuthority, UpdateGroupMaxSize,
},
state::{TokenGroup, TokenGroupMember},
},
};
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)?;
if group_info.key != mint_info.key {
msg!("Group configurations for a mint must be initialized in the mint itself.");
return Err(TokenError::MintMismatch.into());
}
{
check_program_account(mint_info.owner)?;
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());
}
if mint.get_extension::<GroupPointer>().is_err() {
msg!(
"A mint with group configurations must have the group-pointer extension \
initialized"
);
return Err(TokenError::InvalidExtensionCombination.into());
}
}
let group = TokenGroup::new(mint_info.key, data.update_authority, data.max_size.into());
alloc_and_serialize::<Mint, TokenGroup>(group_info, &group, false)?;
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 = 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(())
}
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(())
}
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)?;
if member_info.key != member_mint_info.key {
msg!("Group member configurations for a mint must be initialized in the mint itself.");
return Err(TokenError::MintMismatch.into());
}
{
check_program_account(member_mint_info.owner)?;
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_mint.get_extension::<GroupMemberPointer>().is_err() {
msg!(
"A mint with group member configurations must have the group-member-pointer \
extension initialized"
);
return Err(TokenError::InvalidExtensionCombination.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 = StateWithExtensionsMut::<Mint>::unpack(&mut buffer)?;
let group = state.get_extension_mut::<TokenGroup>()?;
check_update_authority(group_update_authority_info, &group.update_authority)?;
let member_number = group.increment_size()?;
let member = TokenGroupMember::new(member_mint_info.key, group_info.key, member_number);
alloc_and_serialize::<Mint, TokenGroupMember>(member_info, &member, false)?;
Ok(())
}
#[cfg(feature = "token-group")]
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction: TokenGroupInstruction,
) -> ProgramResult {
match instruction {
TokenGroupInstruction::InitializeGroup(data) => {
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)
}
TokenGroupInstruction::UpdateGroupAuthority(data) => {
msg!("TokenGroupInstruction: UpdateGroupAuthority");
process_update_group_authority(program_id, accounts, data)
}
TokenGroupInstruction::InitializeMember(_) => {
msg!("TokenGroupInstruction: InitializeMember");
process_initialize_member(program_id, accounts)
}
}
}
#[cfg(not(feature = "token-group"))]
pub fn process_instruction(
_program_id: &Pubkey,
_accounts: &[AccountInfo],
_instruction: TokenGroupInstruction,
) -> ProgramResult {
Err(TokenError::InvalidInstruction.into())
}