coral-xyz / anchor

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

Account discriminators and `#[account(init)]` #10

Closed armaniferrante closed 3 years ago

armaniferrante commented 3 years ago

Proposal

Similar to what the dex does with AccountFlags, we should transparently add a discriminator field, which uniquely identifies the account type, in the first 8 bytes of every program-owned account.

If the account discriminator doesn't match when the Accounts struct being deserialized, then the instruction should error out.

We achieve this with a new attribute #[account], which implements four traits:

  1. BorshSerialize
  2. BorshDeserialize
  3. AccountSerialize
  4. AccountDeserialize

Where the implementations of AccountSerialize and AccountDeserialize do the discriminator check on the first 8 bytes and Borsh ser/deser of the rest of the account data.

Concretely, this looks like.

#[account] 
pub struct Bar {
   ...
}

When checking the account discriminator, the one exception would be if the account is marked with a newly proposed attribute, #[account(init)], in which case, the discriminator should be zero, because an account is being initialized for the first time (and the discriminator will be set on the instruction execution).

#[derive(Accounts)]
pub struct MyInstruction<'info> {
  #[account(init)]
  pub foo: ProgramAccount<'info, Bar>,
}

Two Discriminator Options

Using a 1-indexed discriminator means all accounts need to be defined inside the #[program] decorated mod, so that the macro knows what index to assign a given Account. Alternatively, instead of incrementing a 1-indexed discriminator, we could just hash (SHA256) the name of the account and take the first 8 bytes to create the discriminator, in which case, the Accounts can be defined outside (or inside) of the #[program] mod.

Both for aesthetic, flexibility, and immediate implementation reasons, I prefer hashing the account name. Note that we can define the hashed discriminator at compile time (in the macro), so there is no runtime cost vs a 1-indexed counter. Aesthetically, it means only instruction handlers need to be inside the #[program] module. In terms of implementation, it allows all account related serialization to be defined completely within the derive macro. Otherwise, the #[program] macro needs to handle this logic.

(A third option would be to have the programmer manually specify the discriminator. The developer could do this by just manually implementing the AccountSerialize and AccountDeserialize traits instead of using the #[account] macro.)

armaniferrante commented 3 years ago

Addressed by #14.

johnzhu0907 commented 2 years ago

What's the benefit of using a discriminator? Are there any security considerations?