metaplex-foundation / shank

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

[feat]: Capture PDA knowledge #23

Open lorisleiva opened 2 years ago

lorisleiva commented 2 years ago

It would be great if Shank could capture all the PDAs of a program in the IRL so they can be auto-generated by Solita (See https://github.com/metaplex-foundation/metaplex-program-library/pull/420).

I'm not sure if something like this is feasible with Rust macro, but this is what I have in mind.

  #[repr(C)]
- #[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, ShankAccount)]
+ #[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, ShankPda)]
+ #[seed(0, literal="metadata")]
+ #[seed(1, programId)]
+ #[seed(2, name="mint", desc="The public key of the mint account")]
+ #[seed(3, literal="edition")]
  pub struct MasterEditionV2 {
      pub key: Key,

      pub supply: u64,

      pub max_supply: Option<u64>,
  }

Where a seed can be one of three types:

stegaBOB commented 2 years ago

Following up on this after just fixing the seed parsing for anchor IDL generation (it currently generates seeds as well). We can do something similar to what loris is saying but for instructions too! From there it can be parsed into the same format that anchor generates:

(For the accounts part of the instruction definition)

{
  "name": "accountName",
  "isMut": true,
  "isSigner": false,
  "pda": {
    "seeds": [
      {
        "kind": "const",
        "type": "string", // any valid type
        "value": "the string value"
      },
      {
        "kind": "arg", // meaning one of the ix args
        "type": "u64", // again, can be any valid type here
        "path": "amount" // the name of the argument
      },
      {
        "kind": "account", // referring to one of the accounts in the ix
        "type": "publicKey",
        "path": "authority" // the account name
      }
    ]
  }
},

The anchor TS client uses this to autopopulate PDAs (i.e. they arent required to be passed in in the accounts struct). We should be able to replicate this behavior in Solita.

Anchor PR for reference: https://github.com/coral-xyz/anchor/pull/2125

thlorenz commented 1 year ago

After experimenting a bit with solutions to efficiently providing seeds I came up with an easily parseable (by humans and proc_macros) way to provide seeds.

Using the example above it'd be as follows:

#[derive(ShankAccount)]
#[seeds("prefix", program_id, something("description of something"), "postfix")]
struct MyAccount { .. }

Which would result in seeds + pda helper impl methods similar to the below:

impl MyAccount {
    /// @param program_id the id of the program to generate the PDA for
    /// @param something description of something
    pub fn get_seeds<'a>(program_id: &'a Pubkey, something: &'a Pubkey) -> [&'a [u8]; 4] {
        [
            "prefix".as_bytes(),
            program_id.as_ref(),
            something.as_ref(),
            "postfix".as_bytes(),
        ]
    }

    pub fn get_pda<'a>(program_id: &'a Pubkey, something: &'a Pubkey) -> (Pubkey, u8) {
        let seeds = get_seeds(program_id, something);
        let (key, bump) = Pubkey::find_program_address(&seeds, program_id);
        (key, bump)
    }
}

Additionally the relevant info to generate similar methods in TypeScript will be added to the IDL. My assumption here is that we only use either literal strings or public keys in order to derive seeds. If we need other types of data we could express this as something_else("desc of else", u8) (defaulting to Pubkey if the second param is not provided).

Waiting for approval/suggestions while I'm working on something along those lines.

thlorenz commented 1 year ago

@stegaBOB reading your comment from above, the above should cover this. However, given that those consts can just be hard-coded in the get_seeds method I'm not sure the below is needed.

{
      "kind": "const",
      "type": "string", // any valid type
      "value": "the string value"
    },

Could you clarify why this still would be useful to include in the IDL and/or why I'm incorrect?