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: API for creating program derived addresses with instruction data #150

Closed
armaniferrante opened this issue Apr 6, 2021 · 8 comments

Comments

@armaniferrante
Copy link
Member

armaniferrante commented Apr 6, 2021

Using program derived addresses for account data is cumbersome. One has to

  • allocate space for the account, manually adding 8+ for the account discriminator
  • invoke the system program's create account method
  • deserialize the data after the account has been created
  • manually persist the newly created account (after initializing it)

An example of this taken from https://github.com/project-serum/anchor/pull/86/files#diff-20d471b8d1707e332b0dd96fe3ef98601ff2511362e4eeb3a94ff3c921a797edR167:

        // Create account manually.
        let mut vote: ProgramAccount<Vote> = {
            // First create the vote account.
            //
            // Add 8 for the account discriminator.
            let space = 8 + Vote::default().try_to_vec().unwrap().len();
            let lamports = ctx.accounts.rent.minimum_balance(space);
            let ix = solana_program::system_instruction::create_account(
                ctx.accounts.stake.beneficiary.key,
                ctx.accounts.vote.key,
                lamports,
                space as u64,
                ctx.program_id,
            );
            let seeds = [
                ctx.accounts.poll.to_account_info().key.as_ref(),
                ctx.accounts.stake.member.to_account_info().key.as_ref(),
                &[nonce],
            ];
            let signer = &[&seeds[..]];
            solana_program::program::invoke_signed(
                &ix,
                &[
                    ctx.accounts.stake.beneficiary.clone(),
                    ctx.accounts.vote.clone(),
                    ctx.accounts.system_program.clone(),
                ],
                signer,
            )?;
            // Deserialize the newly created account into an object.
            ProgramAccount::try_from_init(&ctx.accounts.vote)?
        };

        // Do any initialization here.
        ...

        // Manually persist changes since we manually created the account.
        vote.exit(ctx.program_id)?;

There should be an api or some codegen to make this more friendly.

For example

// The seeds can be taken from any of the input parameters to `create_my_account`.
#[program_derived_address([seeds, go, here])]
fn create_my_account(ctx: Context<MyAccounts>, uninitalized_account: ProgramAccount<Vote>) {
   // Initialization here.
}
@armaniferrante armaniferrante changed the title lang: API for easily creating program derived addresses lang: API for creating program derived addresses with account data Apr 6, 2021
@NorbertBodziony
Copy link
Contributor

We can expand already existing init flag by associated that will indicate that this account will be created from seed.
e.g.

pub struct CreateExchangeAccount<'info> {
    #[account(init,associated)]
    pub exchange_account: ProgramAccount<'info, ExchangeAccount>,
    pub rent: Sysvar<'info, Rent>,
}

@Standaa
Copy link
Contributor

Standaa commented Apr 6, 2021

I agree with Norbert. I think expanding the init flags shows intent with more clarity and is more in-line with the existing patterns.

@armaniferrante
Copy link
Member Author

armaniferrante commented Apr 6, 2021

I like the idea of an associated attribute. One limitation is that the attribute inside the #[derive(Accounts)] struct doesn't have access to the instruction input parameters, which one might want to use as seeds.

However, we can limit the scope of associated to be more inline with the associated token program. I.e., associated means account that's a function of program_id + owner. Edit. My comment here is too narrow. Associated, when used here, doesn't necessarily have to just be program_id + owner. It applies more broadly to any deterministic address (where non-account input parameters are not used considered).

So the attribute becomes

pub struct CreateExchangeAccount<'info> {
    #[account(init, associated = authority)]
    pub exchange_account: ProgramAccount<'info, ExchangeAccount>,
    #[account(signer)]
    pub authority: AccountInfo<'info>
    pub rent: Sysvar<'info, Rent>,
}

@armaniferrante
Copy link
Member Author

Moving the associated attribute to a separate issue #185.

@armaniferrante armaniferrante changed the title lang: API for creating program derived addresses with account data lang: API for creating program derived addresses instruction data arguments Apr 14, 2021
@armaniferrante
Copy link
Member Author

armaniferrante commented Apr 14, 2021

Perhaps a more elegant solution to the one originally proposed here is to extend + merge both the #[state] and #[associated] concepts to generate arbitrary program derived addresses.

We can do this with a new #[pda] (program-derived-address) attribute, where the constructor returns two things: 1) the initial state of the account, and 2) the seeds for the program derived address. This will allow one to generate pda accounts where seeds can also be function arguments (one of the limitations of the current associated attribute).

#[pda]
pub struct MyProgramDerivedAddress {
 ....
}

impl MyProgramDerivedAddress {
  pub fn new(ctx: Context<New>, input_data: &u8) -> Result<(Self, &[&[&[u8]]])> {
     //
  }
}

Open questions

Do we want to change the #[state] attribute to use the above api, or do we want separate attributes?. If they were the same, then one would have to define the canonical state by returning empty seeds &[].

Edit. The current consensus is to make a new macro. The #[state] attribute provides a convenient abstraction for newcomers to solana. The new #[pda] attribute can be a separate, advanced feature (even if it uses the same machinery under the hood).

Implementation details

One annoying thing here is how to deal with the seeds. They must be a &[&[&[u8]]], which means the input args probably have to be references to avoid lifetime issues. This can be inferred by the macro, but means that if a user wants to use a seed as an input argument, there may be an unnecessary clone involved to save that seed into the struct (MyProgramDerivedAddress).

@armaniferrante armaniferrante changed the title lang: API for creating program derived addresses instruction data arguments lang: API for creating program derived addresses with instruction data arguments Apr 14, 2021
@armaniferrante
Copy link
Member Author

An additional feature mentioned a couples times on discord, is that it would be desireable for one to specify static strings in the pda seeds.

@armaniferrante
Copy link
Member Author

armaniferrante commented Jun 15, 2021

The API that I've landed on for the first version of this feature will look like this.

#[derive(Accounts)]
#[instruction(domain: String)]
pub struct TestPdaInit<'info> {
    #[account(init, seeds = [b"my-seed", domain.as_bytes()], payer = my_payer)]
    my_pda: ProgramAccount<'info, DataU16>,
    my_payer: AccountInfo<'info>,
    rent: Sysvar<'info, Rent>,
    system_program: AccountInfo<'info>,
}

Where we extend the seeds attribute to create a program derived address if the init argument is present. The payer must specified since initializing a PDA requires one to pay lamports via the system program instruction. Similarly, the rent and system_program accounts must be specified.

Note that one must redeclare the instruction api via #[instruction(<args>)] to access the instruction arguments in the scope of the Accounts derive. These arguments can be used for any accounts constraint checks.

@armaniferrante armaniferrante changed the title lang: API for creating program derived addresses with instruction data arguments lang: API for creating program derived addresses with instruction data Jun 15, 2021
@armaniferrante
Copy link
Member Author

Done by #386.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants