1

I want to close the PDA account only on specif condition. Macro [account(mut, close = <target_account>)] does not work in this case because it will always be triggered after instruction completion. Also security should also be taken into account in order to avoid sealevel-attacks. I followed the instruction provided here but I am not sure what is the purpose of force_defund instruction and how to integrate it with my code:

#[derive(Accounts)]
pub struct Debt<'info> {
    pub user: SystemAccount<'info>,
    #[account(
        mut,
        seeds = [b"vault".as_ref(), player.key().as_ref()],
        bump = vault.bump
    )]
    pub user_vault: Account<'info, Vault>,
    #[account(mut)]
    pub vault: SystemAccount<'info>,
    pub system_program: Program<'info, System>,
}

impl Debt<'_> {
    pub fn debt(ctx: Context<Self>) -> Result<()> {
        ctx.accounts.user_vault.calculate_debt();

        if ctx.accounts.user_vault.result == Status::Inactive {
            // CLOSING SECURELY THE USER VAULT PDA ACCOUNT

            **ctx.accounts.user.to_account_info().try_borrow_mut_lamports()? +=
                **ctx.accounts.user_vault.to_account_info().try_borrow_lamports()?;
            **ctx.accounts.user_vault.to_account_info().try_borrow_mut_lamports()? = 0;

            let user_vault_account = ctx.accounts.user_vault.to_account_info();
            let mut data = user_vault_account.try_borrow_mut_data()?;
            for byte in data.deref_mut().iter_mut() {
                *byte = 0;
            }

            let dst: &mut [u8] = &mut data;
            let mut cursor = Cursor::new(dst);
            cursor.write_all(&CLOSED_ACCOUNT_DISCRIMINATOR).unwrap();
        }

        // DO SOME OTHER STUFF

        Ok(())
    }
}

2 Answers 2

2

I found the source code for close macro (docs) which has a lot of similarities with my code:

use crate::bpf_writer::BpfWriter;
use crate::error::ErrorCode;
use crate::prelude::error;
use crate::Result;
use solana_program::account_info::AccountInfo;
use std::io::Write;

pub fn close<'info>(info: AccountInfo<'info>, sol_destination: AccountInfo<'info>) -> Result<()> {
    // Transfer tokens from the account to the sol_destination.
    let dest_starting_lamports = sol_destination.lamports();
    **sol_destination.lamports.borrow_mut() =
        dest_starting_lamports.checked_add(info.lamports()).unwrap();
    **info.lamports.borrow_mut() = 0;

    // Mark the account discriminator as closed.
    let mut data = info.try_borrow_mut_data()?;
    let dst: &mut [u8] = &mut data;
    let mut writer = BpfWriter::new(dst);
    writer
        .write_all(&crate::__private::CLOSED_ACCOUNT_DISCRIMINATOR)
        .map_err(|_| error!(ErrorCode::AccountDidNotSerialize))
}

So my final version of code is:

#[derive(Accounts)]
pub struct Debt<'info> {
    pub user: SystemAccount<'info>,
    #[account(
        mut,
        seeds = [b"vault".as_ref(), player.key().as_ref()],
        bump = vault.bump
    )]
    pub user_vault: Account<'info, Vault>,
    #[account(mut)]
    pub vault: SystemAccount<'info>,
    pub system_program: Program<'info, System>,
}

impl Debt<'_> {
    pub fn debt(ctx: Context<Self>) -> Result<()> {
        ctx.accounts.user_vault.calculate_debt();

        if ctx.accounts.user_vault.result == Status::Inactive {
            // CLOSING SECURELY THE USER VAULT PDA ACCOUNT

            **ctx.accounts.user.to_account_info().try_borrow_mut_lamports()? +=
                **ctx.accounts.user_vault.to_account_info().try_borrow_lamports()?;
            **ctx.accounts.user_vault.to_account_info().try_borrow_mut_lamports()? = 0;

            let user_vault_account = ctx.accounts.user_vault.to_account_info();
            let mut data = user_vault_account.try_borrow_mut_data()?;
            for byte in data.deref_mut().iter_mut() {
                *byte = 0;
            }

            let dst: &mut [u8] = &mut data;
            let mut cursor = Cursor::new(dst);
            cursor.write_all(&CLOSED_ACCOUNT_DISCRIMINATOR)
                  .map_err(|_| error!(ErrorCode::AccountDidNotSerialize))
                  .unwrap();
        }

        // DO SOME OTHER STUFF

        Ok(())
    }
}
3
  • 1
    If you do it this way exit() will overwrite the output and actually reopen the account, hence it's highly recommended to not do it this way. There's actually a newer and more secure way to close accounts in the anchor repo which reassign's ownership to the system program immediately.
    – Henry E
    Commented Oct 18, 2022 at 18:04
  • @HentryE I noticed that close is done after exit, thanks for pointing it out. Can you provide a example how to close in secure manner.
    – Void
    Commented Oct 18, 2022 at 18:10
  • 1
    For now, there's no actual way to safely close an account outside of the close constraint, unless the account is passed in as an UncheckedAccount or within the remaining accounts. There's currently a PR open to fix this: github.com/coral-xyz/anchor/pull/2209.
    – Sammy
    Commented Oct 18, 2022 at 20:41
0

For now, there's no actual way to safely close an account outside of the close constraint, unless the account is passed in as an UncheckedAccount or within the remaining accounts. There's currently a PR open to fix this: github.com/coral-xyz/anchor/pull/2209.

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.