coral-xyz / anchor

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

lang: Associated account attribute #185

Closed armaniferrante closed 3 years ago

armaniferrante commented 3 years ago

An associated account attribute should be provided to encourage application developers to use deterministic addressing in their programs by default.

For example,

#[derive(Accounts)]
pub struct CreateToken<'info> {
    #[account(associated = authority, with = mint, payer = authority, space = "100")]
    pub token_account: ProgramAccount<'info, Account>,
    #[account(signer)]
    pub authority: AccountInfo<'info>
    pub mint: ProgramAccount<'info, Mint>,    
    pub rent: Sysvar<'info, Rent>,
    pub system_program: AccountInfo<'info>,
}

Should create the exchange_account located at a program-derived-address located at deterministic address as a function of the program id + the authority address.

Note that the payer = authority argument can also be provided, to specify the account paying for the account creation. If payer is not specified, the payer will default to the authority. Similarly, space can be provided to specify the size of the account. If none is provided, the length of the Default::default implementation for the account will be used. Lastly, with = <target> is an optional parameter to specify a seed, e.g., how the spl token program's associated token account is defined by both the user's sol address and the token mint. It would be more general to allow this to be an array of arbitrary seeds, but I found the with = <target> syntax to be more aesthetic. If there's demand for a more general array, we can change or add it, e.g., with_seeds = [...].

A simplified version of the above, using the defaults, would like like

#[derive(Accounts)]
pub struct CreateToken<'info> {
    #[account(associated = authority)]
    pub token_account: ProgramAccount<'info, Account>,
    #[account(signer)]
    pub authority: AccountInfo<'info>
    pub rent: Sysvar<'info, Rent>,
    pub system_program: AccountInfo<'info>,
}

Here, the authority would pay for the account creation and be used as the target for the associated address, which would apply globally to the program (since no with parameter is specified). The space sued would be the Account's Default::default implementation).

One wart here is the fact that the rent and system_program accounts are exposed. However, they are needed for creating the associated address, and the framework has precedent for using accounts like this (e.g., the init attribute also requires rent). In theory, we could remove the system_program and have it be transparently used by the program, but I'm happy to punt that for now, as it may create unforseen complications + corner cases for using features like remaining_accounts.

Originally suggested in the conversation here https://github.com/project-serum/anchor/issues/150.

Note: I suspect the current use of with = <target> is not ideal. Additionally, we might want to expand associated = <target> to just allow multiple instances to be specified, where the order is preserved when constructing seeds. with = <target>, if specified, can be at the end. Filing this feature under a separate issue.

armaniferrante commented 3 years ago

One challenge here is considering what to do with the "nonce" that comes with the program derived address.

Three different solutions:

  1. Add it to a header to the account. So the layout becomes: <header> || <discriminator> || <borsh-data>, where the header has a nonce and a boolean, determining if the account data is associated or not. This is nice because it hides this implementation detail. We'd probably need to add some accessors for the nonce on the ProgramAccount or on a new type, however.
  2. Force the user to have an explicit nonce field in the deserialized borsh account data. This solution is much easier from an implementation point of view.
  3. Add a new #[associated] attribute that is the same as #[account] but 1) adds a private nonce field and 2) implements a BumpSeed trait implementation.

I'm inclined to start with 2. And then move to 1 or 3 at some point in the future.

armaniferrante commented 3 years ago

Addressed by https://github.com/project-serum/anchor/pull/186. Still need to make some ts updates but one can find a working example there.