Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lang: proposal: Type safe bumps in context #1824

Closed
cyphersnake opened this issue Apr 22, 2022 · 5 comments
Closed

lang: proposal: Type safe bumps in context #1824

cyphersnake opened this issue Apr 22, 2022 · 5 comments
Labels
help wanted Extra attention is needed lang

Comments

@cyphersnake
Copy link
Contributor

Current design:

#[program]
pub mod sandbox {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        msg!("{}", *ctx.bumps.get("initializing_account").unwrap());
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        seeds = [b"seed"],
        bump,
    )]
    initializing_account: Account<'info, Foo>,
    ...
}

Proposed:

#[program]
pub mod sandbox {
    use super::*;
    pub fn initialize<'info>(ctx: Context<Initialize<'info>>) -> Result<()> {
        msg!("{}", ctx.get_bump(Initialize::Bumps::InitializingAccount));
        Ok(())
    }
}

Under the hood implement it like this:

/// Information about bumps inside `Accounts` impl type
pub trait BumpsMetadata {
    /// Enumerate type with all accounts with calculated bumps 
    type Bumps;
    ///  The number of these accounts. We an also calculate with a macro from the enum type, but for simplicity we will leave the constants
    const BUMPS_ARRAYS_SIZE: usize;
}

/// Auto-generated type for `Initialize`
pub enum InitializeBumps {
    InitializingAccount,
}
impl<'info> BumpsMetadata for Initialize<'info> {
    type Bumps = InitializeBumps;
    const BUMPS_ARRAYS_SIZE: usize = 1;
}

pub struct Context<'a, 'b, 'c, 'info, T: BumpsMetadata> {
    /// Currently executing program id.
    pub program_id: &'a Pubkey,
    /// Deserialized accounts.
    pub accounts: &'b mut T,
    /// Remaining accounts given but not deserialized or validated.
    /// Be very careful when using this directly.
    pub remaining_accounts: &'c [AccountInfo<'info>],
    /// Bump seeds found during constraint validation. This is provided as a
    /// convenience so that handlers don't have to recalculate bump seeds or
    /// pass them in as arguments.
    bumps: [u8, T::BUMPS_ARRAYS_SIZE]
}
impl<'a, 'b', 'c, 'info, T> Context<'a, 'b, 'c,' info, T> {
    fn get_bump(&self, account: T::Bumps) -> &u8 {
        &self.bumps[account as usize]
    } 
}

The main idea of course is not to query accounts by name from an arbitrary map, this is very unsafe and difficult to check. A changed account or a typo will not be caught by the compiler and should be covered by tests. At the same time, there is room for maneuver, because. generating a enum and requesting by it from an array (and without an option on the output) shouldn't be very difficult.

@cyphersnake cyphersnake changed the title Proposal: Type safe bumps in context lang: proposal: Type safe bumps in context Apr 25, 2022
@Juici
Copy link
Contributor

Juici commented Apr 25, 2022

The non-typesafe bump seeds are one of the largest blockers that is keeping our projects on version 0.20.1.

@cryptoboole
Copy link

Is there merit to this issue? I understand the concerns but I am not understanding why it's not getting more attention

@armaniferrante armaniferrante added help wanted Extra attention is needed lang labels Jun 7, 2022
@armaniferrante
Copy link
Member

The proposed solutions sounds good to me!

@Juici
Copy link
Contributor

Juici commented Jul 28, 2022

The easiest and probably the most developer friendly solution would probably be to generate a struct to hold the bump seeds. The generated struct would have a field for each account that has a seeds constraint. This would be type-safe and trivial to use.

Library changes:

/// Associated bump seeds for `Accounts`.
pub trait Bumps {
    /// Struct to hold account bump seeds.
    type Bumps: Sized;
}

pub struct Context<'a, 'b, 'c, 'info, T: Bumps> {
    /// Currently executing program id.
    pub program_id: &'a Pubkey,
    /// Deserialized accounts.
    pub accounts: &'b mut T,
    /// Remaining accounts given but not deserialized or validated.
    /// Be very careful when using this directly.
    pub remaining_accounts: &'c [AccountInfo<'info>],
    /// Bump seeds found during constraint validation. This is provided as a
    /// convenience so that handlers don't have to recalculate bump seeds or
    /// pass them in as arguments.
    pub bumps: T::Bumps,
}

Code generation:

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, seeds = [b"foo"], bump)]
    foo: AccountInfo<'info>,
    bar: AccountInfo<'info>,
    #[account(seeds = [b"baz"], bump)]
    baz: AccountInfo<'info>,
    ...
}

// Auto-generated for `Initialize`.
pub struct InitializeBumps {
    foo: u8,
    baz: u8,
}

// Auto-generated for `Initialize`.
impl<'info> Bumps for Initialize<'info> {
    type Bumps = InitializeBumps;
}

Usage:

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

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        msg!("foo: {}", ctx.bumps.foo);
        msg!("baz: {}", ctx.bumps.baz);

        Ok(())
    }
}

@acheroncrypto
Copy link
Collaborator

Added in #2542.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed lang
Projects
None yet
Development

No branches or pull requests

5 participants