2

I have the following MRE, where I create two PDAs and realloc them "in opposite directions" i.e. I make one account 1 byte larger and the other 1 byte smaller. Calling this, yields sum of account balances before and after instruction do not match.. Only reallocing one account in either direction makes this error go away. Reallocing both accounts in the same direction i.e. making both bigger/smaller also makes the error go away. What am I doing wrong here? I'm using anchor v0.30.1.

MRE Program code:

use anchor_lang::prelude::*;

declare_id!("8owLmone5oQWwAZPKeYEmMciBg8Q533UcHBjcU6niV9E");

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

    pub fn init(_ctx: Context<Initialize>) -> Result<()> {
        Ok(())
    }

    pub fn demo(_ctx: Context<Demo>) -> Result<()> {
        Ok(())
    }    
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        init,
        payer = signer,
        seeds = [b"Acc1"],
        space = 9,
        bump
    )]
    pub acc1: Account<'info, DemoAcc>,
    #[account(
        init,
        payer = signer,
        seeds = [b"Acc2"],
        space = 9,
        bump
    )]
    pub acc2: Account<'info, DemoAcc>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Demo<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        mut,
        realloc = 8,
        realloc::payer = signer,
        realloc::zero = false,
        seeds = [b"Acc1"],
        bump
    )]
    pub acc1: Account<'info, DemoAcc>,
    #[account(
        mut,
        realloc = 10,
        realloc::payer = signer,
        realloc::zero = false,
        seeds = [b"Acc2"],
        bump 
    )]
    pub acc2: Account<'info, DemoAcc>,
    pub system_program: Program<'info, System>,
}

#[account]
pub struct DemoAcc {}

MRE client typescript test code:

import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { ReallocDemo } from "../target/types/realloc_demo";

describe("example", () => {
  const provider = anchor.AnchorProvider.env();
  anchor.setProvider(provider);
  const program = anchor.workspace.ReallocDemo as Program<ReallocDemo>;
  it("Init", async () => {
    const transactionSignature = await program.methods.init().rpc();
    console.log("Your transaction signature", transactionSignature);
  });

  it("Realloc (fails)", async () => {
    const sig = await program.methods.demo().rpc();
    console.log("Your transaction signature", sig);
  });
});

1 Answer 1

1

When anchor processes reallocs, it turns each realloc constraint into code that looks like this:

        let __anchor_rent = anchor_lang::prelude::Rent::get()?;
        let __field_info = acc1.to_account_info();
        let __new_rent_minimum = __anchor_rent.minimum_balance(8);
        let __delta_space = (::std::convert::TryInto::<isize>::try_into(8).unwrap())
            .checked_sub(
                ::std::convert::TryInto::try_into(__field_info.data_len()).unwrap(),
            )
            .unwrap();
        if __delta_space != 0 {
            if __delta_space > 0 {
                if ::std::convert::TryInto::<usize>::try_into(__delta_space).unwrap()
                    > anchor_lang::solana_program::entrypoint::MAX_PERMITTED_DATA_INCREASE
                {
                    return Err(
                        anchor_lang::error::Error::from(
                                anchor_lang::error::ErrorCode::AccountReallocExceedsLimit,
                            )
                            .with_account_name("acc1"),
                    );
                }
                if __new_rent_minimum > __field_info.lamports() {
                    anchor_lang::system_program::transfer(
                        anchor_lang::context::CpiContext::new(
                            system_program.to_account_info(),
                            anchor_lang::system_program::Transfer {
                                from: signer.to_account_info(),
                                to: __field_info.clone(),
                            },
                        ),
                        __new_rent_minimum.checked_sub(__field_info.lamports()).unwrap(),
                    )?;
                }
            } else {
                let __lamport_amt = __field_info
                    .lamports()
                    .checked_sub(__new_rent_minimum)
                    .unwrap();
                **signer.to_account_info().lamports.borrow_mut() = signer
                    .to_account_info()
                    .lamports()
                    .checked_add(__lamport_amt)
                    .unwrap();
                **__field_info.lamports.borrow_mut() = __field_info
                    .lamports()
                    .checked_sub(__lamport_amt)
                    .unwrap();
            }
            __field_info.realloc(8, false)?;
            __reallocs.insert(acc1.key());
        }

The important thing to notice here, is that if we're resizing our account to be bigger (__delta_space > 0) this code will use system_program::transfer to transfer the needed lamports for this from the payer to the account but if we're resizing to be smaller it uses lamports().checked_add to modify the lamport number directly. This difference is because reducing the balance of an account can only be done by the owner of an account, so in the case of the payer that's the system program and in the case of the PDA that's our program. This has well-known limitations: All CPIs have to be done before manually modifying the lamports, which means the fix is as simple as:

Works:

#[derive(Accounts)]
pub struct Demo<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        mut,
        realloc = 10,
        realloc::payer = signer,
        realloc::zero = false,
        seeds = [b"Acc1"],
        bump
    )]
    pub acc1: Account<'info, DemoAcc>,
    #[account(
        mut,
        realloc = 8,
        realloc::payer = signer,
        realloc::zero = false,
        seeds = [b"Acc2"],
        bump 
    )]
    pub acc2: Account<'info, DemoAcc>,
    pub system_program: Program<'info, System>,
}

Doesn't work (acc1 gets smaller):

#[derive(Accounts)]
pub struct Demo<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        mut,
        realloc = 8,
        realloc::payer = signer,
        realloc::zero = false,
        seeds = [b"Acc1"],
        bump
    )]
    pub acc1: Account<'info, DemoAcc>,
    #[account(
        mut,
        realloc = 10,
        realloc::payer = signer,
        realloc::zero = false,
        seeds = [b"Acc2"],
        bump 
    )]
    pub acc2: Account<'info, DemoAcc>,
    pub system_program: Program<'info, System>,
}

TL;DR: Always put all accounts that get bigger FIRST and all accounts that get smaller LAST.

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.