2

Is anybody familiar with this error

    Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 15038 of 88944 compute units
    Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb failed: Cross-program invocation reentrancy not allowed for this instruction
    Program 6rxK5YmZVX5oahY8KSDAVNJu59mP21TJAQqymiwLQEbW consumed 126094 of 200000 compute units
    Program 6rxK5YmZVX5oahY8KSDAVNJu59mP21TJAQqymiwLQEbW failed: Cross-program invocation reentrancy not allowed for this instruction

i have made royalty enforced transfer hook, which im trying to invoke from the same program itself, through this helper anchor_spl::token_2022::spl_token_2022::onchain::invoke_transfer_checked but it seems the hook is not getting invoked in the first place, invoke_transfer_checked is failing, with the error i mentioned above, tried a lot of ways, can someone please help me here. Thank You.

let additional_accounts = &mut vec![
            self.transfer_hook_program.to_account_info(),
            self.extra_metas_account.to_account_info()
        ];
        invoke_transfer_checked(
            &Token2022::id(),
            self.seller_token_account.to_account_info(),
            self.mint.to_account_info(),
            self.buyer_token_account.to_account_info(),
            self.listed_nft_pda.to_account_info(),
            additional_accounts,
            1,
            0,
            signer_seeds
        )?;

Initialize account metas instruction is working,

impl<'info> AddRoyalties<'info> {
pub fn account_metas(mint: Pubkey) -> Result<Vec<ExtraAccountMeta>> {
        let meta_accounts: Vec<ExtraAccountMeta> = vec![];
        Ok(meta_accounts)
    }
    pub fn init_account_metas(&mut self) -> ProgramResult {
        ExtraAccountMetaList::init::<ExecuteInstruction>(
            &mut self.extra_metas_account.try_borrow_mut_data()?,
            AddRoyalties::account_metas(self.mint.key())?.as_ref()
        )?;
        Ok(())
    }
}

Here is complete instruction logic if need be, its just failing at the end while invoke transfer checked.


use super::{ get_metadata, CreatorWithShare };

pub fn buy_nft(ctx: Context<BuyNft>) -> Result<()> {
    let selling_price = ctx.accounts.listed_nft_pda.price;
    let delegate_bump = ctx.bumps.listed_nft_pda;
    let mint = ctx.accounts.mint.to_account_info();
    let royalty_amount = calculate_royalties(&mint, selling_price)?;
    let payment_amount = selling_price
        .checked_sub(royalty_amount)
        .ok_or(MarketplaceError::ArithmeticError)?;
    ctx.accounts.transfer_royalty_to_distribution_account(royalty_amount)?; // Send sol to distribution account, which can be claimed after distributing royalties.
    ctx.accounts.distribute_royalty(royalty_amount)?; // Distribute royalties
    ctx.accounts.set_enforcement()?; // Ensures transfer_hook works by requiring a valid account slot, enforced and validated at hook CPI.
    ctx.accounts.pay_and_invoke_nft_transfer(payment_amount, delegate_bump)?; // Send sol to seller, thaw and trasfer the nft, which invokes transfer hook.
    // ctx.accounts.listed_nft_pda.close(ctx.accounts.seller.to_account_info())?; // Remove from the listing.
    Ok(())
}

#[derive(Accounts)]
pub struct BuyNft<'info> {
    #[account(mut)]
    pub buyer: Signer<'info>,
    /// CHECK: validated with constraints below
    #[account(mut)]
    pub seller: SystemAccount<'info>,
    #[account(
        mut,
        associated_token::token_program = token_program,
        associated_token::mint = mint,
        associated_token::authority = seller
    )]
    pub seller_token_account: InterfaceAccount<'info, TokenAccount>,

    #[account(
        init_if_needed,
        payer = buyer,
        associated_token::token_program = token_program,
        associated_token::mint = mint,
        associated_token::authority = buyer
    )]
    pub buyer_token_account: InterfaceAccount<'info, TokenAccount>,
    #[account(
        mut,
        seeds = [b"listed_nft_pda", seller.key().as_ref(), mint.key().as_ref()],
        bump,
        constraint = mint.key() == listed_nft_pda.mint && 
        seller.key() == listed_nft_pda.seller && 
        seller_token_account.key() == listed_nft_pda.seller_ata
    )]
    pub listed_nft_pda: Account<'info, ListedNftPda>,
    #[account(
        mut,
        constraint = mint.mint_authority == COption::None &&
        mint.decimals == 0 &&
        mint.supply == 1
    )]
    pub mint: Box<InterfaceAccount<'info, Mint>>,
    /// CHECK: Checked inside Token extensions program
    transfer_hook_program: UncheckedAccount<'info>,
    /// CHECK: Checked inside Token extensions program
    #[account(seeds = [b"extra-account-metas", mint.key().as_ref()], bump)]
    extra_metas_account: UncheckedAccount<'info>,
    #[account(
        init_if_needed,
        seeds = [b"enforcing_account", mint.key().as_ref()],
        bump,
        payer = buyer,
        space = 8 + EnforcingAccount::INIT_SPACE
    )]
    pub enforcing_account: Account<'info, EnforcingAccount>,
    #[account(
        mut,
        seeds = [b"distribution_account", mint.key().as_ref()],
        bump,
    )]
    pub distribution_account: Account<'info, DistributionAccount>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub token_program: Program<'info, Token2022>,
    pub system_program: Program<'info, System>,
}

impl<'info> BuyNft<'info> {
    pub fn set_enforcement(&mut self) -> Result<()> {
        let clock = Clock::get()?;
        self.enforcing_account.slot = clock.slot;
        Ok(())
    }
    pub fn thaw_account(&self) -> Result<()> {
        thaw_account(
            CpiContext::new(self.token_program.to_account_info(), ThawAccount {
                account: self.seller_token_account.to_account_info(),
                authority: self.seller.to_account_info(),
                mint: self.mint.to_account_info(),
            })
        )?;
        Ok(())
    }
    pub fn transfer_royalty_to_distribution_account(&self, royalty_amount: u64) -> Result<()> {
        transfer(
            CpiContext::new(self.system_program.to_account_info(), Transfer {
                from: self.buyer.to_account_info(),
                to: self.distribution_account.to_account_info(),
            }),
            royalty_amount
        )
    }
    pub fn distribute_royalty(&mut self, royalty_amount: u64) -> Result<()> {
        let metadata = get_metadata(self.mint.to_account_info().as_ref())?;
        if royalty_amount == 0 {
            return Ok(());
        }

        // get all creators from metadata Vec(String, String), only royalty_basis_points needs to be removed
        let creators = metadata.additional_metadata
            .iter()
            .filter(|(key, _)| key != "royalty_basis_points")
            .filter_map(|(key, value)| {
                match Pubkey::from_str(key) {
                    Ok(pubkey) =>
                        Some(CreatorWithShare {
                            address: pubkey,
                            share: value.parse::<u8>().unwrap(),
                        }),
                    Err(_) => None,
                }
            })
            .collect::<Vec<CreatorWithShare>>();

        // update creator amounts in distribution account. add creator if not present, else update amount (amount * pct / 100)
        let creators_already_claimer = &mut self.distribution_account.creators_claims;
        for creator in creators {
            let is_creator_already_claimer = creators_already_claimer
                .iter()
                .position(|cc| creator.address == cc.address);
            match is_creator_already_claimer {
                Some(creator_index) => {
                    // Calculate and add more claim to previous claim amount
                    creators_already_claimer
                        .get_mut(creator_index)
                        .unwrap()
                        .claim_amount.checked_add(
                            royalty_amount
                                .checked_mul(creator.share as u64)
                                .ok_or(MarketplaceError::ArithmeticError)?
                                .checked_div(100)
                                .ok_or(MarketplaceError::ArithmeticError)?
                        )
                        .ok_or(MarketplaceError::ArithmeticError)?;
                }
                None => {
                    // Add a new claimer
                    creators_already_claimer.push(Creator {
                        address: creator.address,
                        claim_amount: royalty_amount
                            .checked_mul(creator.share as u64)
                            .ok_or(MarketplaceError::ArithmeticError)?
                            .checked_div(100)
                            .ok_or(MarketplaceError::ArithmeticError)?,
                    });
                }
            }
        }
        let new_creator_size = std::cmp::max(
            creators_already_claimer.len() * Creator::INIT_SPACE,
            Creator::INIT_SPACE
        );
        let realloc_size = CLAIM_DATA_OFFSET + new_creator_size;
        self.distribution_account.to_account_info().realloc(realloc_size, false)?;
        Ok(())
    }
    pub fn pay_and_invoke_nft_transfer(
        &self,
        payment_amount: u64,
        delegate_bump: u8
    ) -> Result<()> {
        // transfer payment_amount to seller
        transfer(
            CpiContext::new(self.system_program.to_account_info(), Transfer {
                from: self.buyer.to_account_info(),
                to: self.seller.to_account_info(),
            }),
            payment_amount
        )?;
        // Unfreeze and allow for transfer
        self.thaw_account()?;
        let listed_nft_pda = &self.listed_nft_pda;
        let signer_seeds: &[&[&[u8]]] = &[
            &[
                b"listed_nft_pda".as_ref(),
                listed_nft_pda.seller.as_ref(),
                listed_nft_pda.mint.as_ref(),
                &[delegate_bump],
            ],
        ];
        let additional_accounts = &mut vec![
            // self.enforcing_account.to_account_info(),
            self.transfer_hook_program.to_account_info(),
            self.extra_metas_account.to_account_info()
        ];
        invoke_transfer_checked(
            &Token2022::id(),
            self.seller_token_account.to_account_info(),
            self.mint.to_account_info(),
            self.buyer_token_account.to_account_info(),
            self.listed_nft_pda.to_account_info(),
            additional_accounts,
            1,
            0,
            signer_seeds
        )?;
        Ok(())
    }
}
6
  • Is the transfer hook program the same as the program that mints the nft? That would probably not work because you call the program and then the tokenhook program would try to call the transfer hook. If thats the same program it would be a reentrance.
    – Jonas H.
    Commented Jul 24 at 12:00
  • I made a seperate program for initializing meta and execute hook, but i still get this error, i have check everything but idk what's the issue, i am sending no extra accounts when initializing the extra_metas, and this is the additional accounts i am sending in the addtional accounts field in invoke_transfer_hook let additional_accounts = &mut vec![ self.transfer_hook_program.to_account_info(), self.extra_metas_account.to_account_info(), ]; but i keep getting this Error Number: 2110272652. Error Message: Custom program error: 0x7dc8348c. @JonasH. Commented Jul 24 at 15:01
  • So now you are getting another error? This looks like you are passing in a wrong account: solana.stackexchange.com/questions/9442/…
    – Jonas H.
    Commented Jul 24 at 15:15
  • Yeah i also think so, but can you point me out what wrong account am i passing, looking at pay_and_invoke_transfer fn above, i had delegated the listed_nft_pda account to make the transfer and other that from that here's i made a branch if you want to look at the complete code github.com/me-imfhd/solana-contracts/tree/nft-marketplace arhh, i am stuck here with transfer hooks for a week Commented Jul 24 at 15:57
  • 1
    I figured it out. I was using a non-strict feature in Anchor 0.31.0 typescript client that automatically derives PDAs. The issue arose from ownership mismatches between the transfer hook program (creating the extra-account-metas PDA) and the marketplace program (using it, which derived it incorrectly). Using findProgramAddress with the transfer hook program correctly derived the account, resolving the issue. The self-deriving PDA feature is great, but I've shot myself in the foot with it more than once because of it 😭, i'll be more aware now. Thanks for helping me as always @JonasH. 😇 Commented Jul 26 at 13:18

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.