3

I have been getting a seed constraint error when calling the destake function. The stake function works fine. There is a modification for the destake function, it can only be called by the admin, so in order to get the correct stakeInfo PDA,I am passing the account for which to destake as an argument. But then testing on client gives me the seed constraint error. Any ideas?

Error

Uncaught error: AnchorError caused by account: stake_info. Error Code: ConstraintSeeds. Error Number: 2006. Error Message: A seeds constraint was violated.
Program log: Left:
Program log: 9eJ1HWTnajNYD7ZjJTJ2JzRCL4Fk6RgWnZi97YNmvBEq
Program log: Right:
Program log: 84MaL6o53NrPJk3qP8EEwbUAJhhcZmqoi1N3cKXEfhLL

Anchor Program

use anchor_lang::prelude::*;
use anchor_spl::token::{transfer, Mint, Token, TokenAccount, Transfer};
use solana_program::{pubkey, pubkey::Pubkey};

declare_id!("5EYboEs4GWbBbkdimV7VngifbtUXmE58qaKEkcERxRZE");
pub const ADMIN: Pubkey = pubkey!("E6q6HeKopLXp447tcqScBFeiFDScHakK26TrTxqHbncE");

#[program]
mod token_vault {
    use super::*;
    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
        Ok(())
    }

    pub fn stake(ctx: Context<Stake>, amount: u64) -> Result<()> {
        msg!("Stake amount: {}!", amount);

        let stake_info = &mut ctx.accounts.stake_info;
        stake_info.amount = amount;
        stake_info.staker = ctx.accounts.signer.key();

        let transfer_instruction = Transfer {
            from: ctx.accounts.sender_token_account.to_account_info(),
            to: ctx.accounts.vault_token_account.to_account_info(),
            authority: ctx.accounts.signer.to_account_info(),
        };

        let cpi_ctx = CpiContext::new(
            ctx.accounts.token_program.to_account_info(),
            transfer_instruction,
        );

        transfer(cpi_ctx, amount)?;

        Ok(())
    }

    pub fn destake(ctx: Context<DeStake>, amount: u64, _staker_address: Pubkey) -> Result<()> {
        msg!("Destake amount: {}!", amount);

        let stake_info = &mut ctx.accounts.stake_info;

        if amount == 0 {
            return Err(error!(ErrorCode::AmountZero));
        }

        if amount > stake_info.amount {
            return Err(error!(ErrorCode::WithdrawAmountGreater));
        }

        if stake_info.amount == 0 {
            return Err(error!(ErrorCode::StakeAmountZero));
        }

        let updated_amount = stake_info.amount.checked_sub(amount).unwrap();
        ctx.accounts.stake_info.amount = updated_amount;

        let transfer_instruction = Transfer {
            from: ctx.accounts.vault_token_account.to_account_info(),
            to: ctx.accounts.sender_token_account.to_account_info(),
            authority: ctx.accounts.token_account_owner_pda.to_account_info(),
        };

        let bump = ctx.bumps.token_account_owner_pda;
        let seeds = &[b"token_account_owner_pda".as_ref(), &[bump]];
        let signer = &[&seeds[..]];

        let cpi_ctx = CpiContext::new_with_signer(
            ctx.accounts.token_program.to_account_info(),
            transfer_instruction,
            signer,
        );

        transfer(cpi_ctx, amount)?;

        Ok(())
    }

    pub fn delete_stake_acc(_ctx: Context<DeleteStakeAcc>, _staker_address: Pubkey) -> Result<()> {
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    // Derived PDAs
    #[account(
        init,
        payer = signer,
        seeds=[b"token_account_owner_pda".as_ref()],
        bump,
        space = 8
    )]
    token_account_owner_pda: Account<'info, TokenOwnerAccount>,

    #[account(
        init,
        payer = signer,
        seeds=[b"token_vault".as_ref(), mint_of_token_being_sent.key().as_ref()],
        token::mint=mint_of_token_being_sent,
        token::authority=token_account_owner_pda,
        bump
    )]
    vault_token_account: Account<'info, TokenAccount>,

    mint_of_token_being_sent: Account<'info, Mint>,

    #[account(mut)]
    signer: Signer<'info>,
    system_program: Program<'info, System>,
    token_program: Program<'info, Token>,
    rent: Sysvar<'info, Rent>,
}

#[derive(Accounts)]
pub struct Stake<'info> {
    // Derived PDAs
    #[account(
        init,
        payer = signer,
        seeds=[b"stake_info".as_ref(), signer.key().as_ref()],
        bump,
        space = 8 + StakeInfo::INIT_SPACE
    )]
    pub stake_info: Account<'info, StakeInfo>,

    #[account(
        mut,
        seeds=[b"token_account_owner_pda"],
        bump
    )]
    token_account_owner_pda: Account<'info, TokenOwnerAccount>,

    #[account(mut,
        seeds=[b"token_vault", mint_of_token_being_sent.key().as_ref()],
        bump,
        token::mint=mint_of_token_being_sent,
        token::authority=token_account_owner_pda,
    )]
    vault_token_account: Account<'info, TokenAccount>,

    #[account(mut)]
    sender_token_account: Account<'info, TokenAccount>,

    mint_of_token_being_sent: Account<'info, Mint>,

    #[account(mut)]
    signer: Signer<'info>,
    system_program: Program<'info, System>,
    token_program: Program<'info, Token>,
    rent: Sysvar<'info, Rent>,
}

#[derive(Accounts)]
#[instruction(staker_address: Pubkey)]
pub struct DeStake<'info> {
    // Derived PDAs
    #[account(
        mut,
        seeds=[b"stake_info".as_ref(), staker_address.key().as_ref()],
        bump,
    )]
    pub stake_info: Account<'info, StakeInfo>,

    #[account(mut,
        seeds=[b"token_account_owner_pda"],
        bump
    )]
    token_account_owner_pda: Account<'info, TokenOwnerAccount>,

    #[account(mut,
        seeds=[b"token_vault", mint_of_token_being_sent.key().as_ref()],
        bump,
        token::mint=mint_of_token_being_sent,
        token::authority=token_account_owner_pda,
    )]
    vault_token_account: Account<'info, TokenAccount>,

    // #[account(mut)]
    sender_token_account: Account<'info, TokenAccount>,
    mint_of_token_being_sent: Account<'info, Mint>,

    #[account(mut, address = ADMIN)]
    signer: Signer<'info>,
    system_program: Program<'info, System>,
    token_program: Program<'info, Token>,
    rent: Sysvar<'info, Rent>,
}

#[derive(Accounts)]
#[instruction(staker_address: Pubkey)]
pub struct DeleteStakeAcc<'info> {
    #[account(
        mut,
        seeds=[b"stake_info", staker_address.key().as_ref()],
        bump,
        close = signer
    )]
    pub stake_info: Account<'info, StakeInfo>,

    #[account(mut, address = ADMIN)]
    signer: Signer<'info>,
}

#[account]
#[derive(InitSpace)]
pub struct StakeInfo {
    pub staker: Pubkey,
    pub amount: u64,
}

#[account]
pub struct TokenOwnerAccount {}

#[error_code]
pub enum ErrorCode {
    #[msg("Amount must be greater than zero")]
    AmountZero,

    #[msg("Withdraw amount must be less than the deposit")]
    WithdrawAmountGreater,

    #[msg("Deposit amount is zero")]
    StakeAmountZero,
}

Client Program

import {
  getOrCreateAssociatedTokenAccount,
  getAccount,
} from "@solana/spl-token";
import { PublicKey } from "@solana/web3.js";

const DUMMY_TOKEN_ADDRESS = new PublicKey(
  "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr"
);
const payer = pg.wallet.keypair;
let mint = await pg.connection.getParsedAccountInfo(DUMMY_TOKEN_ADDRESS);
const decimals = mint.value.data.parsed.info.decimals;
const mintDecimals = Math.pow(10, decimals);
const tokenAccount = await getOrCreateAssociatedTokenAccount(
  pg.connection,
  payer,
  DUMMY_TOKEN_ADDRESS,
  payer.publicKey
);
let tokenAccountInfo = await getAccount(pg.connection, tokenAccount.address);
console.log(
  "Token Account Info",
  tokenAccountInfo.amount / BigInt(mintDecimals)
);

let confirmOptions = {
  skipPreflight: true,
};

let [tokenAccountOwnerPda] = PublicKey.findProgramAddressSync(
  [Buffer.from("token_account_owner_pda")],
  pg.PROGRAM_ID
);

let [tokenVault] = PublicKey.findProgramAddressSync(
  [Buffer.from("token_vault"), DUMMY_TOKEN_ADDRESS.toBuffer()],
  pg.PROGRAM_ID
);

let txHash;

// Creating Initiaize Transaction
// txHash = await pg.program.methods
//   .initialize()
//   .accounts({
//     tokenAccountOwnerPda: tokenAccountOwnerPda,
//     vaultTokenAccount: tokenVault,
//     mintOfTokenBeingSent: DUMMY_TOKEN_ADDRESS,
//     signer: payer.publicKey,
//   })
//   .rpc(confirmOptions);

// console.log(`Initialize`);
// await logTransaction(txHash);

// console.log(`Vault initialized.`);

// Creating Stake Transaction
const [stakeInfo] = await PublicKey.findProgramAddressSync(
  [Buffer.from("stake_info"), payer.publicKey.toBuffer()],
  pg.PROGRAM_ID
);
// txHash = await pg.program.methods
//   .stake(new anchor.BN(10 * mintDecimals))
//   .accounts({
//     tokenAccountOwnerPda: tokenAccountOwnerPda,
//     stakeInfo: stakeInfo,
//     mintOfTokenBeingSent: DUMMY_TOKEN_ADDRESS,
//     senderTokenAccount: tokenAccount.address,
//     signer: payer.publicKey,
//     vaultTokenAccount: tokenVault,
//   })
//   .signers([payer])
//   .rpc(confirmOptions);

// console.log(`Stake`);
// await logTransaction(txHash);

// console.log(`Stake Done`);

tokenAccountInfo = await getAccount(pg.connection, tokenAccount.address);
console.log(
  "Owned token amount: " + tokenAccountInfo.amount / BigInt(mintDecimals)
);

tokenAccountInfo = await getAccount(pg.connection, tokenVault);
console.log(
  "Vault token amount: " + tokenAccountInfo.amount / BigInt(mintDecimals)
);

// Read Account Data
const data = await pg.program.account.stakeInfo.fetch(stakeInfo);
console.log(`Staking Account Staker`, data.staker.toString());
console.log(
  `Staking Account Amount`,
  data.amount.divn(mintDecimals).toString()
);

// Destake Transaction
// Note: Destake function can only be called by the admin address
txHash = await pg.program.methods
  .destake(new anchor.BN(5 * mintDecimals), payer.publicKey)
  .accounts({
    stakeInfo: stakeInfo,
    tokenAccountOwnerPda: tokenAccountOwnerPda,
    vaultTokenAccount: tokenVault,
    mintOfTokenBeingSent: DUMMY_TOKEN_ADDRESS,
    signer: payer.publicKey,
    senderTokenAccount: tokenAccount.address,
  })
  .signers([payer])
  .rpc(confirmOptions);

tokenAccountInfo = await getAccount(pg.connection, tokenAccount.address);
console.log(
  "Owned token amount: " + tokenAccountInfo.amount / BigInt(mintDecimals)
);

tokenAccountInfo = await getAccount(pg.connection, tokenVault);
console.log(
  "Vault token amount: " + tokenAccountInfo.amount / BigInt(mintDecimals)
);

async function logTransaction(txHash) {
  const { blockhash, lastValidBlockHeight } =
    await pg.connection.getLatestBlockhash();

  await pg.connection.confirmTransaction({
    blockhash,
    lastValidBlockHeight,
    signature: txHash,
  });

  console.log(
    `Solana Explorer: https://explorer.solana.com/tx/${txHash}?cluster=devnet`
  );
}

3 Answers 3

2

The error suggests that you might be driving the PDA wrongly.

Try removing the as_ref() method from your seeds since the b"" token, already converts the string slice to a byte sequence &[].

#[account(
        mut,
        seeds=[b"stake_info", staker_address.key().as_ref()],
        bump,
  )]
  pub stake_info: Account<'info, StakeInfo>,
5
  • I've tried this but still the same error.
    – Anas
    Commented Jun 10 at 16:29
  • Is there anyway to debug these seed errors?
    – Anas
    Commented Jun 10 at 16:31
  • which instruction are you calling that causing it to fail? github.com/0xdeepmehta/fuck-anchor/blob/master/src/errors.rs contains the error codes for anchor
    – Jimii
    Commented Jun 10 at 19:40
  • This is the instruction that causes it to fail.
    – Anas
    Commented Jun 11 at 6:44
  • txHash = await pg.program.methods .destake(new anchor.BN(5 * mintDecimals), payer.publicKey) .accounts({ stakeInfo: stakeInfo, tokenAccountOwnerPda: tokenAccountOwnerPda, vaultTokenAccount: tokenVault, mintOfTokenBeingSent: DUMMY_TOKEN_ADDRESS, signer: payer.publicKey, senderTokenAccount: tokenAccount.address, }) .signers([payer]) .rpc(confirmOptions);
    – Anas
    Commented Jun 11 at 6:45
0

I think I found where the problem is :

#[instruction(staker_address: Pubkey)]

the above macro does not really read the name of the arguments in the instruction to fetch the values but their order. And since you've got another argument before the staker_address, you might need to de-structure it in the macro too.

pub fn destake(ctx: Context<DeStake>, amount: u64, _staker_address: Pubkey) -> Result<()> {

updated code

#[instruction(amount: u64, staker_address: Pubkey)]
0

A bit late but I was able to find the solution. I had a friend with the same error and was able to figure it out.

The problem is when you pass the instruction arguments to the derive(Accounts). They need to be in the same order. You can only omit the arguments that comes after the one you want to use and not the ones before.

These are your arguments:

pub fn destake(ctx: Context<DeStake>, amount: u64, _staker_address: Pubkey) 

So this needs to be like this:

#[derive(Accounts)]
#[instruction(amount: u64, staker_address: Pubkey)]
pub struct DeStake<'info> {
    // Derived PDAs
    #[account(
        mut,
        seeds=[b"stake_info".as_ref(), staker_address.key().as_ref()],
        bump,
    )]
    pub stake_info: Account<'info, StakeInfo>,

    #[account(mut,
        seeds=[b"token_account_owner_pda"],
        bump
    )]
    token_account_owner_pda: Account<'info, TokenOwnerAccount>,

    #[account(mut,
        seeds=[b"token_vault", mint_of_token_being_sent.key().as_ref()],
        bump,
        token::mint=mint_of_token_being_sent,
        token::authority=token_account_owner_pda,
    )]
    vault_token_account: Account<'info, TokenAccount>,

    // #[account(mut)]
    sender_token_account: Account<'info, TokenAccount>,
    mint_of_token_being_sent: Account<'info, Mint>,

    #[account(mut, address = ADMIN)]
    signer: Signer<'info>,
    system_program: Program<'info, System>,
    token_program: Program<'info, Token>,
    rent: Sysvar<'info, Rent>,
}

As per Solana Docs: "Let's briefly look at the #[instruction(...)] attribute macro before moving on. When using #[instruction(...)], the instruction data you provide in the list of arguments must match and be in the same order as the instruction arguments. You can omit unused arguments at the end of the list, but you must include all arguments up until the last one you will be using. For example, imagine an instruction has arguments input_one, input_two, and input_three. If your account constraints need to reference input_one and input_three, you need to list all three arguments in the #[instruction(...)] attribute macro. However, if your constraints only reference input_one and input_two, you can omit input_three."

Ref: https://solana.com/developers/courses/onchain-development/anchor-pdas

Hope this helps someone else.

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.