use {
solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
msg,
program::invoke_signed,
program_error::ProgramError,
pubkey::Pubkey,
system_instruction,
},
spl_tlv_account_resolution::state::ExtraAccountMetas,
spl_token_2022::{
extension::{
transfer_hook::TransferHookAccount, BaseStateWithExtensions, StateWithExtensions,
},
state::{Account, Mint},
},
spl_transfer_hook_interface::{
collect_extra_account_metas_signer_seeds,
error::TransferHookError,
get_extra_account_metas_address, get_extra_account_metas_address_and_bump_seed,
instruction::{ExecuteInstruction, TransferHookInstruction},
},
spl_type_length_value::state::TlvStateBorrowed,
};
fn check_token_account_is_transferring(account_info: &AccountInfo) -> Result<(), ProgramError> {
let account_data = account_info.try_borrow_data()?;
let token_account = StateWithExtensions::<Account>::unpack(&account_data)?;
let extension = token_account.get_extension::<TransferHookAccount>()?;
if bool::from(extension.transferring) {
Ok(())
} else {
Err(TransferHookError::ProgramCalledOutsideOfTransfer.into())
}
}
pub fn process_execute(
program_id: &Pubkey,
accounts: &[AccountInfo],
_amount: u64,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let source_account_info = next_account_info(account_info_iter)?;
let mint_info = next_account_info(account_info_iter)?;
let destination_account_info = next_account_info(account_info_iter)?;
let _authority_info = next_account_info(account_info_iter)?;
let extra_account_metas_info = next_account_info(account_info_iter)?;
check_token_account_is_transferring(source_account_info)?;
check_token_account_is_transferring(destination_account_info)?;
let expected_validation_address = get_extra_account_metas_address(mint_info.key, program_id);
if expected_validation_address != *extra_account_metas_info.key {
return Err(ProgramError::InvalidSeeds);
}
let data = extra_account_metas_info.try_borrow_data()?;
let state = TlvStateBorrowed::unpack(&data).unwrap();
let extra_account_metas =
ExtraAccountMetas::unpack_with_tlv_state::<ExecuteInstruction>(&state)?;
let extra_account_infos = account_info_iter.as_slice();
let account_metas = extra_account_metas.data();
if extra_account_infos.len() != account_metas.len() {
return Err(TransferHookError::IncorrectAccount.into());
}
for (i, account_info) in extra_account_infos.iter().enumerate() {
if &account_metas[i] != account_info {
return Err(TransferHookError::IncorrectAccount.into());
}
}
Ok(())
}
pub fn process_initialize_extra_account_metas(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let extra_account_metas_info = next_account_info(account_info_iter)?;
let mint_info = next_account_info(account_info_iter)?;
let authority_info = next_account_info(account_info_iter)?;
let _system_program_info = next_account_info(account_info_iter)?;
let mint_data = mint_info.try_borrow_data()?;
let mint = StateWithExtensions::<Mint>::unpack(&mint_data)?;
let mint_authority = mint
.base
.mint_authority
.ok_or(TransferHookError::MintHasNoMintAuthority)?;
if !authority_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
if *authority_info.key != mint_authority {
return Err(TransferHookError::IncorrectMintAuthority.into());
}
let (expected_validation_address, bump_seed) =
get_extra_account_metas_address_and_bump_seed(mint_info.key, program_id);
if expected_validation_address != *extra_account_metas_info.key {
return Err(ProgramError::InvalidSeeds);
}
let bump_seed = [bump_seed];
let signer_seeds = collect_extra_account_metas_signer_seeds(mint_info.key, &bump_seed);
let extra_account_infos = account_info_iter.as_slice();
let length = extra_account_infos.len();
let account_size = ExtraAccountMetas::size_of(length)?;
invoke_signed(
&system_instruction::allocate(extra_account_metas_info.key, account_size as u64),
&[extra_account_metas_info.clone()],
&[&signer_seeds],
)?;
invoke_signed(
&system_instruction::assign(extra_account_metas_info.key, program_id),
&[extra_account_metas_info.clone()],
&[&signer_seeds],
)?;
let mut data = extra_account_metas_info.try_borrow_mut_data()?;
ExtraAccountMetas::init_with_account_infos::<ExecuteInstruction>(
&mut data,
extra_account_infos,
)?;
Ok(())
}
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
let instruction = TransferHookInstruction::unpack(input)?;
match instruction {
TransferHookInstruction::Execute { amount } => {
msg!("Instruction: Execute");
process_execute(program_id, accounts, amount)
}
TransferHookInstruction::InitializeExtraAccountMetas => {
msg!("Instruction: InitializeExtraAccountMetas");
process_initialize_extra_account_metas(program_id, accounts)
}
}
}