Under the risks of accounts of the wrong type being used in instruction, and use account type checks to mitigate them.
Discriminator
trait which creates an 8 byte unique identifier for a type
Account<'info, T>
type to automatically check the discriminator
of the account when deserializing the account data
AdminConfig
and UserConfig
account types
store a single public key. The admin_instruction
instruction deserializes the
admin_config
account as an AdminConfig
type and then performs a owner check
and data validation check.
However, the AdminConfig
and UserConfig
account types have the same data
structure. This means a UserConfig
account type could be passed in as the
admin_config
account. As long as the public key stored on the account data
matches the admin
signing the transaction, the admin_instruction
instruction
would continue to process, even if the signer isn’t actually an admin.
Note that the names of the fields stored on the account types (admin
and
user
) make no difference when deserializing account data. The data is
serialized and deserialized based on the order of fields rather than their
names.
AdminConfig
and UserConfig
account types with
a discriminant
field. The admin_instruction
instruction includes an
additional data validation check for the discriminant
field.
discriminant
field of the account passed into the instruction as the
admin_config
account does not match the expected AccountDiscriminant
, then
the transaction will fail. Simply make sure to set the appropriate value for
discriminant
when you initialize each account (not shown in the example), and
then you can include these discriminant checks in every subsequent instruction.
Account
wrapper#[account]
attribute macro for
automatically implementing traits that every account should have.
Structs marked with #[account]
can then be used with Account
to validate
that the passed in account is indeed the type you expect it to be. When
initializing an account whose struct representation has the #[account]
attribute, the first 8 bytes are automatically reserved for a discriminator
unique to the account type. When deserializing the account data, Anchor will
automatically check if the discriminator on the account matches the expected
account type and throw and error if it does not match.
In the example below, Account<'info, AdminConfig>
specifies that the
admin_config
account should be of type AdminConfig
. Anchor then
automatically checks that the first 8 bytes of account data match the
discriminator of the AdminConfig
type.
The data validation check for the admin
field is also moved from the
instruction logic to the account validation struct using the has_one
constraint. #[account(has_one = admin)]
specifies that the admin_config
account’s admin
field must match the admin
account passed into the
instruction. Note that for the has_one
constraint to work, the naming of the
account in the struct must match the naming of field on the account you are
validating.
init
constraint which automatically sets an account discriminatorstarter
branch of
this repository. The
starter code includes a program with three instructions and some tests.
The three instructions are:
initialize_admin
- initializes an admin account and sets the admin
authority of the programinitialize_user
- intializes a standard user accountupdate_admin
- allows the existing admin to update the admin authority of
the programlib.rs
file. The last
instruction should only be callable by the account matching the admin
field on
the admin account initialized using the initialize_admin
instruction.
update_admin
instructionUser
account in place of the
admin
account in the update_admin
instruction, thereby bypassing the
requirement that one be an admin to call this instruction.
Take a look at the solana-type-cosplay.ts
file in the tests
directory. It
contains some basic setup and two tests. One test initializes a user account,
and the other invokes update_admin
and passes in the user account in place of
an admin account.
Run anchor test
to see that invoking update_admin
will complete
successfully.
type-checked
programtype-checked
by running
anchor new type-checked
from the root of the existing anchor program.
Now in your programs
folder you will have two programs. Run anchor keys list
and you should see the program ID for the new program. Add it to the lib.rs
file of the type-checked
program and to the type_checked
program in the
Anchor.toml
file.
Next, update the test file’s setup to include the new program and two new
keypairs for the accounts we’ll be initializing for the new program.
type-checked
programtype_checked
program, add two instructions using the init
constraint
to initialize an AdminConfig
account and a User
account. When using the
init
constraint to initialize new program accounts, Anchor will automatically
set the first 8 bytes of account data as a unique discriminator for the account
type.
We’ll also add an update_admin
instruction that validates the admin_config
account as a AdminConfig
account type using Anchor’s Account
wrapper. For
any account passed in as the admin_config
account, Anchor will automatically
check that the account discriminator matches the expected account type.
update_admin
instructionAdminConfig
account and a User
account
from the type_checked
program. Then we’ll invoke the updateAdmin
instruction
twice passing in the newly created accounts.
anchor test
. For the transaction where we pass in the User
account type,
we expect the instruction and return an Anchor Error for the account not being
of type AdminConfig
.
#[account]
attribute when
creating account structs, use the init
constraint when initializing accounts,
and use the Account
type in your account validation structs.
If you want to take a look at the final solution code you can find it on the
solution
branch of
the repository.