metaplex-foundation / shank

Extracts IDL from Solana Rust contracts
https://docs.rs/crate/shank_macro/latest
130 stars 26 forks source link

feat: generate PDA/Seed implementations #46

Closed thlorenz closed 1 year ago

thlorenz commented 1 year ago

Summary

The ShankAccount derive macro now supports the seed attribute which allows to specify the seeds for the PDA of accounts derived for the program.

When it is provided shank renders an impl block with seed/pda helper methods.

Example

#[derive(ShankAccount)]
#[seeds(
    /* literal    */ "lit:prefix",
    /* program_id */ program_id,
    /* pubkey     */ some_pubkey("description of some pubkey"),
    /* byte       */ some_byte("description of byte", u8),
)]
struct AccountStructWithSeed {
    count: u8,
}

Generates the following impl block:

impl AccountStructWithSeed {
    /// Derives the seeds for this account.
    ///
    /// * **program_id**: The id of the program | [Pubkey]
    /// * **some_pubkey**: description of some pubkey | [Pubkey]
    /// * **some_byte**: description of byte | [u8]
    #[allow(unused, clippy::needless_lifetimes)]
    pub fn shank_seeds<'a>(
        program_id: &'a ::solana_program::pubkey::Pubkey,
        some_pubkey: &'a ::solana_program::pubkey::Pubkey,
        some_byte: &'a [u8; 1usize],
    ) -> [&'a [u8]; 4usize] {
        [b"lit:prefix", program_id.as_ref(), some_pubkey.as_ref(), some_byte]
    }
    /// Derives the seeds for this account allowing to provide a bump seed.
    ///
    /// * **program_id**: The id of the program | [Pubkey]
    /// * **some_pubkey**: description of some pubkey | [Pubkey]
    /// * **some_byte**: description of byte | [u8]
    /// * **bump**: the bump seed to pass when deriving the PDA
    #[allow(unused, clippy::needless_lifetimes)]
    pub fn shank_seeds_with_bump<'a>(
        program_id: &'a ::solana_program::pubkey::Pubkey,
        some_pubkey: &'a ::solana_program::pubkey::Pubkey,
        some_byte: &'a [u8; 1usize],
        bump: &'a [u8; 1],
    ) -> [&'a [u8]; 5usize] {
        [b"lit:prefix", program_id.as_ref(), some_pubkey.as_ref(), some_byte, bump]
    }
    /// Derives the PDA for this account.
    ///
    /// * **program_id**: The id of the program
    /// * **some_pubkey**: description of some pubkey | [Pubkey]
    /// * **some_byte**: description of byte | [u8]
    #[allow(unused)]
    pub fn shank_pda(
        program_id: &::solana_program::pubkey::Pubkey,
        some_pubkey: &::solana_program::pubkey::Pubkey,
        some_byte: u8,
    ) -> (::solana_program::pubkey::Pubkey, u8) {
        let some_byte_arg = &[some_byte];
        let seeds = Self::shank_seeds(program_id, some_pubkey, some_byte_arg);
        ::solana_program::pubkey::Pubkey::find_program_address(&seeds, program_id)
    }
    /// Derives the PDA for this account allowing to provide a bump seed.
    ///
    /// * **program_id**: The id of the program
    /// * **some_pubkey**: description of some pubkey | [Pubkey]
    /// * **some_byte**: description of byte | [u8]
    /// * **bump**: the bump seed to pass when deriving the PDA
    #[allow(unused)]
    pub fn shank_pda_with_bump(
        program_id: &::solana_program::pubkey::Pubkey,
        some_pubkey: &::solana_program::pubkey::Pubkey,
        some_byte: u8,
        bump: u8,
    ) -> (::solana_program::pubkey::Pubkey, u8) {
        let some_byte_arg = &[some_byte];
        let bump_arg = &[bump];
        let seeds = Self::shank_seeds_with_bump(
            program_id,
            some_pubkey,
            some_byte_arg,
            bump_arg,
        );
        ::solana_program::pubkey::Pubkey::find_program_address(&seeds, program_id)
    }
}

Chore

I ran cargo clippy in all lower level crates and fixed any issues.

Crates

A new crate shank-render was introduced an needs to be published to crates.io after mergint this PR.

Docs

Documentation on the ShankAccount macro was updated in order to explain seeds.

A diagnostics folder was added which at this point documents the code size benchmarks I made related to the newly rendered impl blocks (up until now shank did not render any code).

Compatibility

When the seeds attribute is not used no extra code is rendered and thus existing contracts should see no change until they use it.

Remaining Work

At this point shank does not pull that seed info into the IDL yet. This will be part of a follow up PR as I needed to wrap this up first since it started to grow a lot.

Additionally I made changes/additions to the RustType functionality, but only the ones that were needed for this feature. Thus a lot of code paths have todos at this point (for types we don't support for PDA code generation).

Errors for invalid types could also be improved. These places were all marked with TODOs.

Most examples that are now supported came from Metaplex contracts known to me. Thus I'm pretty sure others will run into unsupported cases and we need to support them as they are encountered.