5
use anchor_lang::prelude::*;
use anchor_spl::token::{Mint, Token, TokenAccount, Transfer};

declare_id!("5L3ky71YgB1rRMbJMxw9iH2vw9A59wZ2krWW7vdwfn61");

#[program]
pub mod hodl_token_lock {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        require!(!counter.initialized, ErrorCode::CounterAlreadyInitialized);
        counter.count = 0;
        counter.initialized = true;
        Ok(())
    }

    pub fn deposit_tokens(ctx: Context<Deposit>, amount: u64, duration: i64) -> Result<()> {
        let hodl_account = &mut ctx.accounts.hodl_account;
        let counter = &mut ctx.accounts.counter;

        counter.count += 1;

        hodl_account.id = counter.count;
        hodl_account.mint = *ctx.accounts.hodl_token_account.to_account_info().key;
        hodl_account.owner = *ctx.accounts.owner.key;
        hodl_account.owner_token_account = *ctx.accounts.from.to_account_info().key;
        hodl_account.hodl_token_account = *ctx.accounts.hodl_token_account.to_account_info().key;
        hodl_account.amount = amount;
        hodl_account.locked_time = Clock::get()?.unix_timestamp;
        hodl_account.unlock_time = Clock::get()?.unix_timestamp + duration;
        hodl_account.withdrawn = false;

        let cpi_accounts = Transfer {
            from: ctx.accounts.from.to_account_info(),
            to: ctx.accounts.hodl_token_account.to_account_info(),
            authority: ctx.accounts.owner.to_account_info(),
        };
        let cpi_program = ctx.accounts.token_program.to_account_info();
        let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);

        anchor_spl::token::transfer(cpi_ctx, amount)?;

        Ok(())
    }

    pub fn withdraw_tokens(ctx: Context<Withdraw>) -> Result<()> {
        let hodl_account = &mut ctx.accounts.hodl_account;
        let clock = Clock::get()?;

        require!(
            clock.unix_timestamp >= hodl_account.unlock_time,
            ErrorCode::UnlockTimeNotCompleted
        );

        require!(!hodl_account.withdrawn, ErrorCode::AlreadyWithdrawn);

        let amount = hodl_account.amount;
        hodl_account.withdrawn = true;

        let cpi_accounts = Transfer {
            from: ctx.accounts.hodl_token_account.to_account_info(),
            to: ctx.accounts.owner_token_account.to_account_info(),
            authority: ctx.accounts.hodl_account.to_account_info(), // Use hodl_account as authority
        };
        
     


        let cpi_program = ctx.accounts.token_program.to_account_info();


        let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);

        anchor_spl::token::transfer(cpi_ctx, amount)?;

        Ok(())

    }
}

#[account]
pub struct HodlDetailsAccount {
    pub id: u64,
    pub owner: Pubkey,
    pub mint: Pubkey,
    pub owner_token_account: Pubkey,
    pub hodl_token_account: Pubkey,
    pub amount: u64,
    pub locked_time: i64,
    pub unlock_time: i64,
    pub withdrawn: bool,
}

#[account]
pub struct Counter {
    pub count: u64,
    pub initialized: bool,
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = initializer, space = 8 + 8 + 1, seeds = [b"counter"], bump)]
    pub counter: Account<'info, Counter>,
    #[account(mut)]
    pub initializer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Deposit<'info> {
    #[account(mut, seeds = [b"counter"], bump)]
    pub counter: Account<'info, Counter>,

    #[account(init, payer = owner, space = 8 + 8 + 32 + 32 + 32 + 32+ 8 + 8 + 8 + 8 + 1)]
    pub hodl_account: Account<'info, HodlDetailsAccount>,

    #[account(mut)]
    pub hodl_token_account: Account<'info, TokenAccount>,
    pub token_program: Program<'info, Token>,

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

    #[account(mut)]
    pub owner: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Withdraw<'info> {
    #[account(mut)]
    pub hodl_account: Account<'info, HodlDetailsAccount>,

    #[account(mut, address = hodl_account.owner_token_account)]
    pub owner_token_account: Account<'info, TokenAccount>,

    #[account(mut, address = hodl_account.hodl_token_account)]
    pub hodl_token_account: Account<'info, TokenAccount>,

    #[account(mut)]
    pub mint: Account<'info, Mint>,
   
    pub token_program: Program<'info, Token>,
}

#[error_code]
pub enum ErrorCode {
    #[msg("The counter has already been initialized.")]
    CounterAlreadyInitialized,
    #[msg("You can't withdraw tokens before unlock time.")]
    UnlockTimeNotCompleted,
    #[msg("Tokens have already been withdrawn.")]
    AlreadyWithdrawn,
}

test cases

  it("deposit", async () => {
    const provider = Provider.local();

    const counterAccount = web3.PublicKey.findProgramAddressSync(
      [Buffer.from("counter")],
      pg.PROGRAM_ID
    )[0];

    const hodlAccount = Keypair.generate();
    const mintAddress = new PublicKey(
      "4sbjDboGaD7vYMx2wqc1HwJvN5h2Yq6bzVJ7Prut3hSL"
    );

    const myTokenAccount = await anchor.utils.token.associatedAddress({
      mint: mintAddress,
      owner: pg.wallet.publicKey,
    });

    const myHodlTokenAccount = await anchor.utils.token.associatedAddress({
      mint: mintAddress,
      owner: hodlAccount.publicKey,
    });
    console.log("okkk-1");
    // Ensure the token accounts are created
    try {
      const tx1 = new web3.Transaction().add(
        createAssociatedTokenAccountInstruction(
          provider.wallet.publicKey, // payer
          myTokenAccount,
          provider.wallet.publicKey,
          mintAddress
        )
      );
      await anchor.Provider.env().sendAndConfirm(tx1, []);

      console.log("Token account for wallet created");
    } catch (e) {
      console.log(
        "Token account for wallet already exists or failed to create:",
        e.message
      );
    }

    try {
      const tx2 = new web3.Transaction().add(
        createAssociatedTokenAccountInstruction(
          provider.wallet.publicKey, // payer
          myHodlTokenAccount,
          hodlAccount.publicKey,
          mintAddress
        )
      );
      await anchor.Provider.env().sendAndConfirm(tx2, []);

      console.log("Token account for HODL account created");
    } catch (e) {
      console.log(
        "Token account for HODL account already exists or failed to create:",
        e.message
      );
    }
    console.log("okkk-3");
    const tx = await pg.program.methods
      .depositTokens(new BN(1000000000000), new BN(600))
      .accounts({
        counter: counterAccount,
        hodlAccount: hodlAccount.publicKey,
        tokenProgram: TOKEN_PROGRAM_ID,
        hodlTokenAccount: myHodlTokenAccount,
        from: myTokenAccount,
        owner: pg.wallet.publicKey,
        systemProgram: SystemProgram.programId,
      })
      .signers([hodlAccount])
      .rpc({ skipPreflight: true });

    console.log(`Use 'solana confirm -v ${tx}' to see the logs`);
  });
it("withdraw", async () => {
    const hodlAccount = new web3.PublicKey(
      "7XUvNJWb57UTw3wcCTUEwZehjdSDcSjMcCeMyFNqdK2A"
    );

    const mintAddress = new PublicKey(
      "4sbjDboGaD7vYMx2wqc1HwJvN5h2Yq6bzVJ7Prut3hSL"
    );

    const ownerTokenAccount = await anchor.utils.token.associatedAddress({
      mint: mintAddress,
      owner: pg.wallet.publicKey,
    });

    const hodlTokenAccount = await anchor.utils.token.associatedAddress({
      mint: mintAddress,
      owner: hodlAccount,
    });

    const tx =await pg.program.methods
      .withdrawTokens()
      .accounts({
        hodlAccount: hodlAccount,
        ownerTokenAccount: ownerTokenAccount,
        hodlTokenAccount: hodlTokenAccount,
        tokenProgram: TOKEN_PROGRAM_ID,
        mint: mintAddress,
      })
      .signers([])
      .rpc({ skipPreflight: true });
    console.log(`Use 'solana confirm -v ${tx}' to see the logs`);
  });

i have written solana program for staking solana token on call depositToken i have deposited one solana fungible token to hodlAccount ,but when i am tring to get it back to my account it is generating issue privilege escalation

1 Answer 1

2

Try this in withdraw_tokens function:-

    let bump = &[ctx.bumps.pda_signer];
    let binding = ctx.accounts.mint.key();
    let signer_seeds = &[&[b"sale", binding.as_ref(), bump][..]];

        let cpi_program = ctx.accounts.token_program.to_account_info();
        let cpi_accounts = Transfer {
                    from: ctx.accounts.pda_token_account.to_account_info(),
                    to: ctx.accounts. owner_token_account.to_account_info(),
                    authority: ctx.accounts. hodl_account.to_account_info(),
        };

        let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds);
3
  • i have added testcases for deposit as well , i am generating random keypair for hodl_account Commented Jun 7 at 11:33
  • Yes but main issue comes in the anchor code in withdraw function. When you transfer tokens you need the authority. Here your pda needs the authority to transfer the token.
    – Aditya
    Commented Jun 7 at 11:43
  • thanks for you response ,could you please share some resources from where i can check for authority initilization to transfer token Commented Jun 7 at 12:30

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.