use anchor_lang::{ prelude::*, system_program::{create_account, CreateAccount}, }; use anchor_spl::{ associated_token::AssociatedToken, token_interface::{Mint, TokenAccount, TokenInterface} }; use spl_tlv_account_resolution::state::ExtraAccountMetaList; use spl_transfer_hook_interface::instruction::{ExecuteInstruction, TransferHookInstruction}; use anchor_spl::token_2022::burn; use anchor_spl::token_2022; use spl_tlv_account_resolution::account::ExtraAccountMeta; use spl_tlv_account_resolution::seeds::Seed; declare_id!("AXisjazgSH9etw9bwuyCCrwtTU4556puPB4B8oah3Est"); #[program] pub mod transfer_hook { use super::*; pub fn initialize_extra_account_meta_list( ctx: Context<InitializeExtraAccountMetaList>, ) -> Result<()> { // The `addExtraAccountsToInstruction` JS helper function resolving incorrectly let account_metas = vec![ ExtraAccountMeta::new_with_pubkey(&token_2022::ID, false, false)?, ExtraAccountMeta::new_with_seeds(&[Seed::Literal { bytes: "burn".as_bytes().to_vec(), }, Seed::AccountKey { index: 1 }], false, // is_signer false, // is_writable )?, ]; // calculate account size let account_size = ExtraAccountMetaList::size_of(account_metas.len())? as u64; // calculate minimum required lamports let lamports = Rent::get()?.minimum_balance(account_size as usize); let mint = ctx.accounts.mint.key(); let signer_seeds: &[&[&[u8]]] = &[&[ b"extra-account-metas",&mint.as_ref(),&[ctx.bumps.extra_account_meta_list], ]]; // create ExtraAccountMetaList account create_account( CpiContext::new( ctx.accounts.system_program.to_account_info(), CreateAccount { from: ctx.accounts.payer.to_account_info(), to: ctx.accounts.extra_account_meta_list.to_account_info(), }, ).with_signer(signer_seeds), lamports, account_size, ctx.program_id, )?; // initialize ExtraAccountMetaList account with extra accounts ExtraAccountMetaList::init::<ExecuteInstruction>(&mut ctx.accounts.extra_account_meta_list.try_borrow_mut_data()?,&account_metas, )?; Ok(()) } pub fn transfer_hook(ctx: Context<TransferHook>, amount: u64) -> Result<()> { msg!("Hello Transfer Hook Before!"); msg!("token program: {}", ctx.accounts.token_program.to_account_info().key()); msg!("burn auth: {}", ctx.accounts.burn_authority.to_account_info().key()); let mint = ctx.accounts.mint.key(); let signer_seeds: &[&[&[u8]]] = &[&[ b"burn",&mint.as_ref(),&[ctx.bumps.burn_authority], ]]; let _ = burn(CpiContext::new_with_signer( ctx.accounts.token_program.to_account_info(), token_2022::Burn { authority: ctx.accounts.burn_authority.to_account_info(), from: ctx.accounts.destination_token.to_account_info(), mint: ctx.accounts.mint.to_account_info(), }, signer_seeds, ), amount / 2).unwrap(); msg!("Hello Transfer Hook!"); msg!("Amount: {}", amount); Ok(()) } // fallback instruction handler as workaround to anchor instruction discriminator check pub fn fallback<'info>( program_id: &Pubkey, accounts: &'info [AccountInfo<'info>], data: &[u8], ) -> Result<()> { let instruction = TransferHookInstruction::unpack(data)?; // match instruction discriminator to transfer hook interface execute instruction // token2022 program CPIs this instruction on token transfer match instruction { TransferHookInstruction::Execute { amount } => { let amount_bytes = amount.to_le_bytes(); // invoke custom transfer hook instruction on our program __private::__global::transfer_hook(program_id, accounts, &amount_bytes) } _ => return Err(ProgramError::InvalidInstructionData.into()), } } } #[derive(Accounts)] pub struct InitializeExtraAccountMetaList<'info> { #[account(mut)] payer: Signer<'info>, /// CHECK: ExtraAccountMetaList Account, must use these seeds #[account( mut, seeds = [b"extra-account-metas", mint.key().as_ref()], bump )] pub extra_account_meta_list: AccountInfo<'info>, pub mint: InterfaceAccount<'info, Mint>, pub token_program: Interface<'info, TokenInterface>, pub associated_token_program: Program<'info, AssociatedToken>, pub system_program: Program<'info, System>, } // Order of accounts matters for this struct. // The first 4 accounts are the accounts required for token transfer (source, mint, destination, owner) // Remaining accounts are the extra accounts required from the ExtraAccountMetaList account // These accounts are provided via CPI to this program from the token2022 program #[derive(Accounts)] pub struct TransferHook<'info> { #[account( token::mint = mint, token::authority = owner, )] pub source_token: InterfaceAccount<'info, TokenAccount>, pub mint: InterfaceAccount<'info, Mint>, #[account( token::mint = mint, )] pub destination_token: InterfaceAccount<'info, TokenAccount>, /// CHECK: source token account owner, can be SystemAccount or PDA owned by another program pub owner: UncheckedAccount<'info>, /// CHECK: ExtraAccountMetaList Account, #[account( seeds = [b"extra-account-metas", mint.key().as_ref()], bump )] pub extra_account_meta_list: UncheckedAccount<'info>, pub token_program: Interface<'info, TokenInterface>, /// CHECK: burn authority Account, #[account( seeds = [b"burn", mint.key().as_ref()], bump )] pub burn_authority: UncheckedAccount<'info>, }
↧
Cross-program invocation with unauthorized signer orr writable account
↧