anchor/docs/src/tutorials/tutorial-1.md

5.1 KiB

Tutorial 1: Arguments and Accounts

This tutorial covers the basics of creating and mutating accounts using Anchor. It's recommended to read Tutorial 0 first, as this tutorial will build on top of it.

Clone the Repo

To get started, clone the repo.

git clone https://github.com/project-serum/anchor

And change directories to the example.

cd anchor/examples/tutorial/basic-1

Defining a Program

We define our program as follows

<<< @/../examples/tutorial/basic-1/programs/basic-1/src/lib.rs

Some new syntax elements are introduced here.

initialize instruction

First, let's start with the initialize instruction. Notice the data argument passed into the program. This argument and any other valid Rust types can be passed to the instruction to define inputs to the program.

::: tip If you'd like to pass in your own type as an input to an instruction handler, then it must be defined in the same src/lib.rs file as the #[program] module, so that the IDL parser can pick it up. :::

Additionally, notice how we take a mutable reference to my_account and assign the data to it. This leads us the Initialize struct, deriving Accounts. There are two things to notice about Initialize.

  1. The my_account field is of type ProgramAccount<'info, MyAccount>, telling the program it must be owned by the currently executing program, and the deserialized data structure is MyAccount.
  2. The my_account field is marked with the #[account(init)] attribute. This should be used in one situation: when a given ProgramAccount is newly created and is being used by the program for the first time (and thus it's data field is all zero). If #[account(init)] is not used when account data is zero initialized, the transaction will be rejected.

::: details All accounts created with Anchor are laid out as follows: 8-byte-discriminator || borsh serialized data. The 8-byte-discriminator is created from the first 8 bytes of the Sha256 hash of the account's type--using the example above, sha256("MyAccount")[..8].

Importantly, this allows a program to know for certain an account is indeed of a given type. Without it, a program would be vulnerable to account injection attacks, where a malicious user specifies an account of an unexpected type, causing the program to do unexpected things.

On account creation, this 8-byte discriminator doesn't exist, since the account storage is zeroed. The first time an Anchor program mutates an account, this discriminator is prepended to the account storage array and all subsequent accesses to the account (not decorated with #[account(init)]) will check for this discriminator. :::

update instruction

Similarly, the Update accounts struct is marked with the #[account(mut)] attribute. Marking an account as mut persists any changes made upon exiting the program.

Here we've covered the basics of how to interact with accounts. In a later tutorial, we'll delve more deeply into deriving Accounts, but for now, just know one must mark their accounts init when using an account for the first time and mut for persisting changes.

Creating and Initializing Accounts

For a moment, assume an account of type MyAccount was created on Solana, in which case, we can invoke the above initialize instruction as follows.

<<< @/../examples/tutorial/basic-1/tests/basic-1.js#code-separated

The last element passed into the method is common amongst all dynamically generated methods on the rpc namespace, containing several options for a transaction. Here, we specify the accounts field, an object of all the addresses the transaction needs to touch.

::: details If you've developed on Solana before, you might notice two things 1) the ordering of the accounts doesn't matter and 2) the isWritable and isSigner options are not specified on the account anywhere. In both cases, the framework takes care of these details for you, by reading the IDL. :::

However it's common--and sometimes necessary for security purposes--to batch instructions together. We can extend the example above to both create an account and initialize it in one atomic transaction.

<<< @/../examples/tutorial/basic-1/tests/basic-1.js#code

Here, notice the two fields introduced: signers and instructions. signers is an array of all Account objects to sign the transaction and instructions is an array of all instructions to run before the explicitly specified program instruction, which in this case is initialize. Because we are creating myAccount, it needs to sign the transaction, as required by the Solana runtime.

::: details In future work, we can simplify this example further by using something like a Builder pattern for constructing common transactions like creating and then initializing an account. :::

As before, we can run the example tests.

anchor test

Next Steps

We've covered all the basics of developing applications using Anchor. However, we've left out one import aspect to ensure the security of our programs--validating input and access control. We'll cover that next.