coral-xyz / anchor

⚓ Solana Sealevel Framework
https://anchor-lang.com
Apache License 2.0
3.66k stars 1.34k forks source link

Stack Usage Limitation in Anchor v29 Upgrade #2718

Open perlish opened 11 months ago

perlish commented 11 months ago

I recently attempted to upgrade contract from version 26 to version 29. However, after upgrade I encountered a stack usage limitation issue that I believe needs attention and resolution.

same codebase works fine with 0.26, after upgrade to 0.29.0 one of Instruction return error the error is Access violation in stack frame

solana-cli 1.16.13 anchor 0.29.0 rustc 1.74.0

acheroncrypto commented 11 months ago

There are a few more bytes allocated to the stack for the bumps so it could cause problems if your instruction in 0.26 was on the edge of maxing out stack memory. You get a lot more heap space with 0.29 so I'd try heap allocating some parts e.g. acounts.

0xdal commented 9 months ago

+1 same problem here

acheroncrypto commented 9 months ago

Do you have an example instruction that go over the limit compared to previous Anchor version? I can't think of anything else other than the bump change which would add about 8 bytes but we also have a lot of reductions in other places.

0xdal commented 9 months ago

Do you have an example instruction that go over the limit compared to previous Anchor version? I can't think of anything else other than the bump change which would add about 8 bytes but we also have a lot of reductions in other places.

Sure! it's a instruction with lots of initializations, was working fine on 0.27, but it breaks the stack limit on 0.29.

I'm changing the accounts to use zero_copy but the main issue are the bumps. Maybe it can be disabled? I could pass them as arguments.

Error: Function _ZN221_$LT$bliv..instructions..initialize_fixed_maturity_market..InitializeFixedMaturityMarket$u20$as$u20$anchor_lang..Accounts$LT$bliv..instructions..initialize_fixed_maturity_market..InitializeFixedMaturityMarketBumps$GT$$GT$12try_accounts17he2ac8ac050d3592aE Stack offset of 5728 exceeded max offset of 4096 by 1632 bytes, please minimize large stack variables
#[derive(Accounts)]
#[instruction(
    maturity: u64,
)]
pub struct InitializeFixedMaturityMarket<'info> {
    #[account(mut, constraint = market.admin == admin.key())]
    pub admin: Signer<'info>,

    #[account(
        mut,
        seeds = [
            b"market",
            market_token_mint.key().as_ref(),
        ],
        bump = market.bump[0],
        constraint = market.admin == admin.key(),
        constraint = market.market_token_mint == market_token_mint.key()
    )]
    pub market: Box<Account<'info, Market>>,

    #[account(
        constraint = market.market_token_mint == market_token_mint.key()
    )]
    pub market_token_mint: Box<Account<'info, Mint>>,

    #[account(
        init,
        payer = admin,
        seeds = [
            b"fixed_maturity_market",
            market.key().as_ref(),
            principal_token_mint.key().as_ref(),
            yield_token_mint.key().as_ref(),
        ],
        bump,
        space = FixedMaturityMarket::LEN + 8,
    )]
    pub fixed_maturity_market: Box<Account<'info, FixedMaturityMarket>>,

    #[account(
        mut,
        seeds = [
            b"bliv_token_mint",
            market.market_token_mint.key().as_ref()
        ],
        bump = market.bliv_token_mint_bump[0],
    )]
    pub bliv_token_mint: Box<Account<'info, Mint>>,

    #[account(
        init,
        payer = admin,
        token::mint = bliv_token_mint,
        token::authority = fixed_maturity_market,
    )]
    pub bliv_token_vault: Box<Account<'info, TokenAccount>>,

    #[account(
        init,
        payer = admin,
        seeds = [
            b"principal_token_mint",
            market.key().as_ref(),
            maturity.to_le_bytes().as_ref(),
        ],
        bump,
        mint::decimals = market_token_mint.decimals,
        mint::authority = fixed_maturity_market,
    )]
    pub principal_token_mint: Box<Account<'info, Mint>>,

    /// CHECK: checked via the Metadata CPI call
    #[account(mut)]
    pub principal_token_metadata: UncheckedAccount<'info>,

    #[account(
        init,
        payer = admin,
        token::mint = principal_token_mint,
        token::authority = fixed_maturity_market,
    )]
    pub principal_token_vault: Box<Account<'info, TokenAccount>>,

    #[account(
        init,
        payer = admin,
        seeds = [
            b"yield_token_mint",
            market.key().as_ref(),
            maturity.to_le_bytes().as_ref(),
        ],
        bump,
        mint::decimals = market_token_mint.decimals,
        mint::authority = fixed_maturity_market,
    )]
    pub yield_token_mint: Box<Account<'info, Mint>>,

    /// CHECK: checked via the Metadata CPI call
    #[account(mut)]
    pub yield_token_metadata: UncheckedAccount<'info>,

    #[account(
        init,
        payer = admin,
        token::mint = yield_token_mint,
        token::authority = fixed_maturity_market,
    )]
    pub yield_token_vault: Box<Account<'info, TokenAccount>>,

    /// CHECK: checked via account constraints
    #[account(address = mpl_token_metadata::ID)]
    pub metadata_program: UncheckedAccount<'info>,

    pub system_program: Program<'info, System>,
    pub token_program: Program<'info, Token>,
    pub rent: Sysvar<'info, Rent>,
}
acheroncrypto commented 9 months ago

Thank you for sharing. I think you should be able to fix this by removing the rent account and using:

let rent = Rent::get()?;

I'm changing the accounts to use zero_copy but the main issue are the bumps. Maybe it can be disabled? I could pass them as arguments.

5 bumps is bytes(8 because of alignment) so it's pretty small but I agree, it might be good idea to add ability to disable caching bumps.

0xdal commented 9 months ago

@acheroncrypto removing rent from instruction only saved 32 bytes :/

It complaints about the bumps on try_accounts: InitializeFixedMaturityMarketBumps$GT$$GT$12try_accounts17he2ac8ac050d3592aE

I changed my big accounts to use zero copy, but it still is exceeding the stack, now by only 136 bytes.

Interesting enough, removing only 1 TokenAccount initialization, it reduces 464 bytes! So with 6 initializations it adds ~2784 to the stack. Looks like there's really something odd in when using lots of inits and bumps

I guess I'll need to move some initializations to other instruction as a workaround. Any other idea?

Appreciate the help.

acheroncrypto commented 9 months ago

I changed my big accounts to use zero copy, but it still is exceeding the stack, now by only 136 bytes.

I think something is off here because there are 5 bumps in your instruction and each bump is 1 byte so it should never add hundreds of bytes to the stack even if some of them gets copied.

Can you try using

anchor-lang = { git = "https://github.com/febo/anchor", branch = "febo/boxed-bumps" }

and let me know whether it fixes your issue?