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`
);
}