docs: add book (#1302)

This commit is contained in:
Paul 2022-01-12 16:13:32 +01:00 committed by GitHub
parent 77043131c2
commit 9f46b30bc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1178 additions and 0 deletions

33
.github/workflows/deploy_book.yaml vendored Normal file
View File

@ -0,0 +1,33 @@
name: Deploy
on:
push:
branches:
- master
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Install mdbook
run: |
mkdir mdbook
curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.14/mdbook-v0.4.14-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook
echo `pwd`/mdbook >> $GITHUB_PATH
- name: Deploy GitHub Pages
run: |
cd book
mdbook build
git worktree add gh-pages gh-pages
git config user.name "Deploy from CI"
git config user.email ""
cd gh-pages
# Delete the ref to avoid keeping history.
git update-ref -d refs/heads/gh-pages
rm -rf *
mv ../book/* .
git add .
git commit -m "Deploy $GITHUB_SHA to gh-pages"
git push --force

1
book/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
book

6
book/book.toml Normal file
View File

@ -0,0 +1,6 @@
[book]
title = "The Anchor Book"
authors = []
language = "en"
multilingual = false
src = "src"

21
book/src/SUMMARY.md Normal file
View File

@ -0,0 +1,21 @@
# Summary
- [Introduction](./chapter_1/introduction.md)
- [What is Anchor](./chapter_1/what_is_anchor.md)
- [Anchor Documentation](./chapter_1/anchor_documentation.md)
- [Prerequisites](./chapter_1/prerequisites.md)
- [Getting Started](./chapter_2/getting_started.md)
- [Installation](./chapter_2/installation.md)
- [Hello, Anchor!](./chapter_2/hello_anchor.md)
- [Anchor Programs In-Depth](./chapter_3/anchor_programs_in-depth.md)
- [Essentials](./chapter_3/essentials.md)
- [High-level Overview](./chapter_3/high-level_overview.md)
- [The Accounts Struct](./chapter_3/the_accounts_struct.md)
- [The Program Module](./chapter_3/the_program_module.md)
- [Errors](./chapter_3/errors.md)
- [Milestone Project - Tic-Tac-Toe](./chapter_3/milestone_project_tic-tac-toe.md)
- [Intermediate]()
- [Anchor Periphery](./chapter_4/anchor_periphery.md)
- [CLI](./chapter_4/cli.md)
- [IDL]()
- [Anchor BTS]()

View File

@ -0,0 +1,6 @@
# Anchor Documentation
Anchor's official documentation is split up into multiple parts, namely the guide, which is what you are reading right now and the references.
There are three references. One for the [core library](https://docs.rs/anchor-lang/latest/anchor_lang/) and one for each official client library ([typescript](https://project-serum.github.io/anchor/ts/index.html) and [rust](https://docs.rs/anchor-client/latest/anchor_client/)). These references are close to the code and detailed. If you know what you are looking for and want to understand how it works more deeply, you'll find explanations there.
However, if you're new to anchor, you need to know what anchor has to offer before you can even try to understand it more deeply. That's what this guide is for. Its purpose is to introduce you to anchor, to help you become familiar with it. It teaches you what features are available in Anchor so you can explore them yourself in detail using the references.

View File

@ -0,0 +1,5 @@
# Introduction
Welcome to The Anchor Book! ⚓
This chapter covers what anchor is, how its documentation is structured, and what you should know to have a good time with this guide.

View File

@ -0,0 +1,4 @@
# Prerequisites
This guide (for now) assumes that you already have some knowledge of Solana programs and basic Rust. Ideally, you have already written a basic program without anchor. To make it through the essentials section, you should at least understand [Solana's programming model](https://docs.solana.com/developing/programming-model/overview). Additionally, you should've read chapter 1-9 of the [Rust book](https://doc.rust-lang.org/book/title-page.html) which cover the basics of using Rust (Most of the time you don't need advanced Rust to write anchor programs).
If you're not familiar with Solana at all, the official [Solana developers page](https://solana.com/developers) is a good starting point.

View File

@ -0,0 +1,8 @@
# What is Anchor
Anchor is a framework for quickly building secure Solana programs.
With Anchor you can build programs quickly because it writes various boilerplate for you such as (de)serialization of accounts and instruction data.
You can build secure programs more easily because Anchor handles certain security checks for you. On top of that, it allows you to succinctly define additional checks and keep them separate from your business logic.
Both of these aspects mean that instead of working on the tedious parts of raw Solana programs, you can spend more time working on what matters most, your product.

View File

@ -0,0 +1,2 @@
# Getting Started
This chapter walks you through the installation process and the folder structure of an anchor workspace.

View File

@ -0,0 +1,19 @@
# Hello, Anchor!
To initialize a new project, simply run:
```
anchor init <new-workspace-name>
```
This creates a new anchor workspace you can move into. The following are some of the important files in the folder:
- The `.anchor` folder: It includes the most recent program logs and a local ledger that is used for testing
- The `app` folder: An empty folder that you can use to hold your frontend if you use a monorepo
- The `programs` folder: This folder contains your programs. It can contain multiple but initially only contains a program with the same name as `<new-workspace-name>`. This program already contains a `lib.rs` file with some sample code.
- The `tests` folder: The folder that contains your E2E tests. It will already include a file that tests the sample code in the `programs/<new-workspace-name>`.
- The `migrations` folder: In this folder you can save your deploy and migration scripts for your programs.
- The `Anchor.toml` file: This file configures workspace wide settings for your programs. Initially, it configures
- The addresses of your programs on localnet (`[programs.localnet]`)
- A registry your program can be pushed to (`[registry]`)
- A provider which can be used in your tests (`[provider]`)
- Scripts that Anchor executes for you (`[scripts]`). The `test` script is run when running `anchor test`. You can run your own scripts with `anchor run <script_name>`.

View File

@ -0,0 +1,38 @@
# Installation
## Rust
Go [here](https://www.rust-lang.org/tools/install) to install Rust.
## Solana
Go [here](https://docs.solana.com/cli/install-solana-cli-tools) to install Solana.
## Yarn
Go [here](https://yarnpkg.com/getting-started/install) to install Yarn.
## Anchor
### Install using pre-build binary on x86_64 Linux
Anchor binaries are available via an NPM package [`@project-serum/anchor-cli`](https://www.npmjs.com/package/@project-serum/anchor-cli). Only `x86_64` Linux is supported currently, you must build from source for other OS'.
### Build from source for other operating systems
For now, we can use Cargo to install the CLI.
```
cargo install --git https://github.com/project-serum/anchor --tag v0.20.1 anchor-cli --locked
```
On Linux systems you may need to install additional dependencies if cargo install fails. On Ubuntu,
```
sudo apt-get update && sudo apt-get upgrade && sudo apt-get install -y pkg-config build-essential libudev-dev
```
Now verify the CLI is installed properly.
```
anchor --version
```

View File

@ -0,0 +1,7 @@
# Anchor Programs In-Depth
This section explains how you can use Anchor to build Solana programs. Each section includes code examples, so it is recommended that you start up a new Anchor project before you proceed so you can play around with the code yourself while reading. Call it `hello-anchor`.
```
anchor init hello-anchor
```
This sections begins with the essentials and then explains more intermediate content afterwards.

View File

@ -0,0 +1,59 @@
# Errors
There are three types of errors in anchor programs. Anchor Internal Errors, Custom Errors, and non-anchor errors.
The autogenerated clients can automatically parse Anchor Internal Errors and Custom Errors so they can display the error code and error message. This is not possible for non-anchor errors where clients just return the raw error returned by the underlying solana client libraries.
> (Ultimately, all programs return the same Error: The [`ProgramError`](https://docs.rs/solana-program/latest/solana_program/program_error/enum.ProgramError.html). This Error has a field for a custom error number. This is where Anchor puts its internal and custom error codes. The autogenerated clients read this number and read the IDL (where custom errors' numbers are mapped to their messages) to display the correct error messages (The Anchor internal error number=>message mapping is hardcoded in the clients). Doing it this way means that there is no way to display dynamic custom error messages because all error messages are hardcoded in the IDL. Very soon, anchor will use logs instead of relying only on the returned error code number to emit errors. These logs can also be read by the client and allow dynamic content.)
## Anchor Internal Errors
> [Anchor Internal Error Code Reference](https://docs.rs/anchor-lang/latest/anchor_lang/__private/enum.ErrorCode.html)
Anchor has many different internal error codes. These are not meant to be used by users, but it's useful to study the reference to learn about the mappings between codes and their causes. They are, for example, thrown when a constraint has been violated, e.g. when an account is marked with `mut` but its `is_writable` property is `false`.
## Custom Errors
You can add errors that are unique to your program by using the error attribute.
Simply add it to an enum with a name of your choice. You can then use the variants of the enum as errors in your program. Additionally, you can add a message attribute to the individual variants. Clients will then display this error message if the error occurs. Custom Error code numbers start at the [custom error offset](https://docs.rs/anchor-lang/latest/anchor_lang/__private/constant.ERROR_CODE_OFFSET.html).
```rust,ignore
#[program]
mod hello_anchor {
use super::*;
pub fn set_data(ctx: Context<SetData>, data: MyAccount) -> ProgramResult {
if data.data >= 100 {
return Err(MyError::DataTooLarge.into());
}
ctx.accounts.my_account.set_inner(data);
Ok(())
}
}
#[error]
pub enum MyError {
#[msg("MyAccount may only hold data below 100")]
DataTooLarge
}
```
You can use the [`require`](https://docs.rs/anchor-lang/latest/anchor_lang/macro.require.html) macro to simplify writing errors. The code above can be simplified to this (Note that the `>=` flips to `<`):
```rust,ignore
#[program]
mod hello_anchor {
use super::*;
pub fn set_data(ctx: Context<SetData>, data: MyAccount) -> ProgramResult {
require!(data.data < 100, MyError::DataTooLarge);
ctx.accounts.my_account.set_inner(data);
Ok(())
}
}
#[error]
pub enum MyError {
#[msg("MyAccount may only hold data below 100")]
DataTooLarge
}
```

View File

@ -0,0 +1,2 @@
# Essentials
This chapter teaches you Anchor essentials and includes a milestone project with which you can test your understanding.

View File

@ -0,0 +1,26 @@
# High-level Overview
An Anchor program consists of three parts. The `program` module, the Accounts structs which are marked with `#[derive(Accounts)]`, and the `declareId` macro. The `program` module is where you write your business logic. The Accounts structs is where you validate accounts. The`declareId` macro creates an `ID` field that stores the address of your program.
When you start up a new Anchor project, you'll see the following:
```rust,ignore
// use this import to gain access to common anchor features
use anchor_lang::prelude::*;
// declare an id for your program
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
// write your business logic here
#[program]
mod hello_anchor {
use super::*;
pub fn initialize(_ctx: Context<Initialize>) -> ProgramResult {
Ok(())
}
}
// validate incoming accounts here
#[derive(Accounts)]
pub struct Initialize {}
```
We'll go into more detail in the next sections but for now, note that the way an endpoint is connected to its corresponding Accounts struct is the `ctx` argument in the endpoint. The argument is of type `Context` which is generic over an Accounts struct, i.e. this is where you put the name of your account validation struct. In this example, it's `Initialize`.

View File

@ -0,0 +1,490 @@
# Milestone Project - Tic-Tac-Toe
You're now ready to build your first anchor project. Create a new anchor workspace with
```
anchor init tic-tac-toe
```
The program will have 2 instructions. First, we need to setup the game. We need to save who is playing it and create a board to play on. Then, the player take turns until there is a winner or a tie.
## Setting up the game
### State
Let's begin by thinking about which data we should store. Each game has players, turns, a board, and a game state. This game state describes whether the game is active, tied, or one of the two players won. We can save all this data in an account. This means that each new game will have its own account. Add the following to the bottom of the `lib.rs` file:
```rust,ignore
#[account]
pub struct Game {
players: [Pubkey; 2], // 64
turn: u8, // 1
board: [[Option<Sign>; 3]; 3], // 9 * (1 + 1) = 18
state: GameState, // 32 + 1
}
```
This is the game account. Next to the field definitions, you can see how many bytes each field requires. This will be very important later. Let's also add the `Sign` and the `GameState` type.
```rust,ignore
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Eq)]
pub enum GameState {
Active,
Tie,
Won { winner: Pubkey },
}
#[derive(
AnchorSerialize,
AnchorDeserialize,
FromPrimitive,
ToPrimitive,
Copy,
Clone,
PartialEq,
Eq
)]
pub enum Sign {
X,
O,
}
```
Both `GameState` and `Sign` derive some traits. `AnchorSerialize` and `AnchorDeserialize` are the crucial ones. All types that are used in types that are marked with `#[account]` must implement these two traits (or be marked with `#[account]` themselves). All other traits are important to our game logic and we are going to use them later. Generally, it is good practice to derive even more traits to make the life of others trying to interface with your program easier (see [Rust's API guidelines](https://rust-lang.github.io/api-guidelines/interoperability.html#types-eagerly-implement-common-traits-c-common-traits)) but for brevity's sake, we are not going to do that in this guide.
This won't quite work yet because `FromPrimitive` and `ToPrimitive` are unknown. Go to the `Cargo.toml` file right outside `src` (not the one at the root of the workspace) and add these two dependencies:
```toml
num-traits = "0.2"
num-derive = "0.3"
```
Then, import them at the top of `lib.rs`:
```rust,ignore
use num_derive::*;
use num_traits::*;
```
### The Setup Instruction
Before we write any game logic, we can add the instruction that will set up the game in its initial state. Rename the already existing instruction function and accounts struct to `setup_game` and `SetupGame` respectively. Now think about which accounts are needed to set up the game. Clearly, we need the game account. Before we can fill it with values, we need to create it. For that, we use the `init` constraint.
```rust,ignore
#[derive(Accounts)]
pub struct SetupGame<'info> {
#[account(init)]
pub game: Account<'info, Game>
}
```
`init` immediately shouts at us and tells us to add a payer. Why do we need it? Because `init` creates `rent-exempt` accounts and someone has to pay for that. Naturally, if we want to take money from someone, we should make them sign as well as mark their account as mutable.
```rust,ignore
#[derive(Accounts)]
pub struct SetupGame<'info> {
#[account(init, payer = player_one)]
pub game: Account<'info, Game>,
#[account(mut)]
pub player_one: Signer<'info>
}
```
`init` is not happy yet. It wants the system program to be inside the struct because `init` creates the game account by making a call to that program. So let's add it.
```rust,ignore
#[derive(Accounts)]
pub struct SetupGame<'info> {
#[account(init, payer = player_one)]
pub game: Account<'info, Game>,
#[account(mut)]
pub player_one: Signer<'info>,
pub system_program: Program<'info, System>
}
```
There's one more thing to do to complete `SetupGame`. Every account is created with a fixed amount of space. `init` can try to infer how much space an account needs if it derives `Default`. So let's implement `Default` for `Game`
```rust,ignore
#[account]
#[derive(Default)] <-- add this
pub struct Game {...
```
and `GameState`.
```rust,ignore
impl Default for GameState {
fn default() -> Self {
Self::Active
}
}
```
And with this, `SetupGame` is complete and we can move on to the `setup_game` function. (If you like playing detective, you can pause here and try to figure out why what we just did will not work. Hint: Have a look at the [specification](https://borsh.io/) of the serialization library Anchor uses. If you cannot figure it out, don't worry. We are going to fix it very soon, together.)
Let's start by adding an argument to the `setup_game` function.
```rust,ignore
pub fn setup_game(ctx: Context<SetupGame>, player_two: Pubkey) -> ProgramResult {
Ok(())
}
```
Why didn't we just add `player_two` as an account in the accounts struct? There are two reasons for this.First, adding it there requires a little more space in the transaction that saves whether the account is writable and whether it's a signer. But we care about neither of that, we just want the address. This brings us to the second and more important reason. A transaction can have effects on other transaction that are processed at the same time in the network if they share accounts. For example, if we add `player_two` to the accounts struct, during our transaction, no other transaction can edit `player_two`'s account. Therefore, we block all other transactions that want to edit `player_two`'s account, even though we neither want to read from nor write to the account. We just care about its address!
Finish the instruction function by setting the game to its initial values:
```rust,ignore
pub fn setup_game(ctx: Context<SetupGame>, player_two: Pubkey) -> ProgramResult {
let game = &mut ctx.accounts.game;
game.players = [ctx.accounts.player_one.key(), player_two];
game.turn = 1;
Ok(())
}
```
Now, run `anchor build`. On top of compiling your program, this command creates an [IDL](https://en.wikipedia.org/wiki/Interface_description_language) for your program. You can find it in `target/idl`. The anchor typescript client can automatically parse this IDL and generate functions based on it. What this means is that each anchor program gets its own typescript client for free! (Technically, you don't have to call anchor build before testing. `anchor test` will do it for you.)
### Testing the Setup Instruction
Time to test our code! Head over into the `tests` folder in the root directory. Open the `tic-tac-toe.ts` file and remove the existing `it` test. Then, put the following into the `describe` section:
```typescript
it('setup game!', async() => {
const gameKeypair = anchor.web3.Keypair.generate();
const playerOne = program.provider.wallet;
const playerTwo = anchor.web3.Keypair.generate();
await program.rpc.setupGame(playerTwo.publicKey, {
accounts: {
game: gameKeypair.publicKey,
playerOne: playerOne.publicKey,
systemProgram: anchor.web3.SystemProgram.programId
},
signers: [gameKeypair]
});
let gameState = await program.account.game.fetch(gameKeypair.publicKey);
expect(gameState.turn).to.equal(1);
expect(gameState.players)
.to
.eql([playerOne.publicKey, playerTwo.publicKey]);
expect(gameState.state).to.eql({ active: {} });
expect(gameState.board)
.to
.eql([[null,null,null],[null,null,null],[null,null,null]]);
});
```
and add this to the top of your file:
```typescript
import { expect } from 'chai';
```
The test begins by creating some keypairs. Importantly, `playerOne` is not a keypair but the wallet of the program's provider. The provider details are defined in the `Anchor.toml` file in the root of the project.
Then, we send the transaction. Because the anchor typescript client has parsed the IDL, all transaction inputs have types. If you remove one of the accounts for example, typescript will complain.
The structure of the transaction function is as follows. First come the instruction arguments. For this function, the public key of the second player. Then come the accounts. Lastly, we add a signers array. We have to add the `gameKeypair` here because whenever an account gets created, it has to sign its creation transaction. We don't have to add `playerOne` even though we gave it the `Signer` type in the program because it is the program provider and therefore signs the transaction by default.
After the transaction returns, we can fetch the state of the game account. You can fetch account state using the `program.account` namespace.
Finally, we verify the game has been set up properly. Anchor's typescript client deserializes rust enums like this: `{ active: {}}` for a fieldless variant and `{ won: { winner: Pubkey }}` for a variant with fields. The `None` variant of `Option` becomes `null`. The `Some(x)` variant becomes whatever `x` deserializes to.
Now, run `anchor test`. This starts up (and subsequently shuts down) a local validator (make sure you don't have one running) and runs your tests using the test script defined in `Anchor.toml`.
## Playing the game
### The Play Instruction
The `Play` accounts struct is straightforward. We need the game and a player:
```rust,ignore
#[derive(Accounts)]
pub struct Play<'info> {
#[account(mut)]
pub game: Account<'info, Game>,
pub player: Signer<'info>,
}
```
`player` needs to sign or someone else could play for the player.
Next, add the game logic:
```rust,ignore
impl Game {
pub fn is_active(&self) -> bool {
self.state == GameState::Active
}
fn current_player_index(&self) -> usize {
((self.turn - 1) % 2) as usize
}
pub fn current_player(&self) -> Pubkey {
self.players[self.current_player_index()]
}
pub fn play(&mut self, tile: &Tile) -> ProgramResult {
if !self.is_active() {
return Err(TicTacToeError::GameAlreadyOver.into());
}
match tile {
tile
@ Tile {
row: 0..=2,
column: 0..=2,
} => match self.board[tile.row as usize][tile.column as usize] {
Some(_) => return Err(TicTacToeError::TileAlreadySet.into()),
None => {
self.board[tile.row as usize][tile.column as usize] =
Some(Sign::from_usize(self.current_player_index()).unwrap());
}
},
_ => return Err(TicTacToeError::TileOutOfBounds.into()),
}
self.update_state();
if let GameState::Active = self.state {
self.turn += 1;
}
Ok(())
}
fn is_winning_trio(&self, trio: [(usize, usize); 3]) -> bool {
let [first, second, third] = trio;
self.board[first.0][first.1].is_some()
&& self.board[first.0][first.1] == self.board[second.0][second.1]
&& self.board[first.0][first.1] == self.board[third.0][third.1]
}
fn update_state(&mut self) {
for i in 0..=2 {
// three of the same in one row
if self.is_winning_trio([(i, 0), (i, 1), (i, 2)]) {
self.state = GameState::Won {
winner: self.current_player(),
};
return;
}
// three of the same in one column
if self.is_winning_trio([(0, i), (1, i), (2, i)]) {
self.state = GameState::Won {
winner: self.current_player(),
};
return;
}
}
// three of the same in one diagonal
if self.is_winning_trio([(0, 0), (1, 1), (2, 2)])
|| self.is_winning_trio([(0, 2), (1, 1), (2, 0)])
{
self.state = GameState::Won {
winner: self.current_player(),
};
return;
}
// reaching this code means the game has not been won,
// so if there are unfilled tiles left, it's still active
for row in 0..=2 {
for column in 0..=2 {
if self.board[row][column].is_none() {
return;
}
}
}
// game has not been won
// game has no more free tiles
// -> game ends in a tie
self.state = GameState::Tie;
}
}
```
We are not going to explore this code in detail together because it's rather simple rust code. It's just tic-tac-toe after all! Roughly, what happens when `play` is called:
1. return error if game is over or
return error if given row or column are outside the 3x3 board or
return error if tile on board is already set
2. determine current player and set tile to X or O
3. update game state
4. if game is still active, increase the turn
Currently, the code doesn't compile because we need to add the `Tile`
```rust,ignore
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct Tile {
row: u8,
column: u8,
}
```
and the `TicTacToeError` type.
```rust,ignore
#[error]
pub enum TicTacToeError {
TileOutOfBounds,
TileAlreadySet,
GameAlreadyOver,
NotPlayersTurn,
}
```
Finally, we can add the `play` function inside the program module.
```rust,ignore
pub fn play(ctx: Context<Play>, tile: Tile) -> ProgramResult {
let game = &mut ctx.accounts.game;
require!(
game.current_player() == ctx.accounts.player.key(),
TicTacToeError::NotPlayersTurn
);
game.play(&tile)
}
```
We've checked in the accounts struct that the `player` account has signed the transaction, but we do not check that it is the `player` we expect. That's what the `require` check in `play` is for.
### Testing the Play Instruction
Testing the `play` instruction works the exact same way. To avoid repeating yourself, create a helper function at the top of the test file:
```typescript
async function play(program, game, player,
tile, expectedTurn, expectedGameState, expectedBoard) {
await program.rpc.play(tile, {
accounts: {
player: player.publicKey,
game
},
signers: player instanceof (anchor.Wallet as any) ? [] : [player]
});
const gameState = await program.account.game.fetch(game);
expect(gameState.turn).to.equal(expectedTurn);
expect(gameState.state).to.eql(expectedGameState);
expect(gameState.board)
.to
.eql(expectedBoard);
}
```
You can create then a new `it` test, setup the game like in the previous test, but then keep calling the `play` function you just added to simulate a complete run of the game. Let's begin with the first turn:
```typescript
it('player one wins', async() => {
const gameKeypair = anchor.web3.Keypair.generate();
const playerOne = program.provider.wallet;
const playerTwo = anchor.web3.Keypair.generate();
await program.rpc.setupGame(playerTwo.publicKey, {
accounts: {
game: gameKeypair.publicKey,
playerOne: playerOne.publicKey,
systemProgram: anchor.web3.SystemProgram.programId
},
signers: [gameKeypair]
});
let gameState = await program.account.game.fetch(gameKeypair.publicKey);
expect(gameState.turn).to.equal(1);
expect(gameState.players)
.to
.eql([playerOne.publicKey, playerTwo.publicKey]);
expect(gameState.state).to.eql({ active: {} });
expect(gameState.board)
.to
.eql([[null,null,null],[null,null,null],[null,null,null]]);
await play(
program,
gameKeypair.publicKey,
playerOne,
{row: 0, column: 0},
2,
{ active: {}, },
[
[{x:{}},null,null],
[null,null,null],
[null,null,null]
]
);
});
```
Now run `anchor test` again and you will be greeted with an error:
```
Error: 3004: Failed to serialize the account
```
What to do? We know that it happens during play, because our `setupGame` test runs fine.
Also, it says `serialize`, not `deserialize`. So after our logic runs and anchor tries to save all the data, there is an error.
What this means most of the time is that the account is too small to hold all its data and this is also the problem here.
Let's have a look at the `Game` struct again and the way we created it:
```rust,ignore
#[account]
#[derive(Default)]
pub struct Game {
players: [Pubkey; 2], // 64
turn: u8, // 1
board: [[Option<Sign>; 3]; 3], // 9 * (1 + 1) = 18
state: GameState, // 32 + 1
}
...
#[account(init, payer = player_one)]
pub game: Account<'info, Game>,
...
```
Remember that we implemented `Default` for `Game` because `init` can try to infer the correct space requirements based on `Default`, "try" being the operative word. What happens if we don't specify an explicit `space` requirement for the account is that anchor will call `default` on the account and convert it to a vector using borsh. It then uses the length of that vector as the space for the account.
Let's walk through our example step by step using the [borsh specification](https://borsh.io/). The comments show us the space requirements that we must get, that is, the largest the given type can become.
- Pubkey as a vector has a length of `32` so `2*32 = 64`
- u8 as a vector has a length of `1` so `1 = 1`
- board's default (`9 * None`) as a vector has a length of `9 != 18`
- state's default as a vector is `1 != 33`
We have found out that `init` currently only allocates 75 bytes for our account data but the account can grow to (64 + 1 + 18 + 33) = 116 bytes.
We can add this number to our Game impl like this:
```rust,ignore
impl Game {
const MAXIMUM_SIZE: usize = 116;
... // other functions
}
```
```rust,ignore
...
#[account(init, payer = player_one, space = Game::MAXIMUM_SIZE + 8)]
pub game: Account<'info, Game>,
...
```
In addition to the game's size, we have to add another `8` to the space. This is space for the `discriminator` which anchor sets automatically. In short, the discriminator is how anchor can differentiate between different accounts of the same program.
> (What about using `mem::size_of<Game>()`? This almost works but not quite. The issue is that borsh will always serialize an option as 1 byte for the variant identifier and then additional x bytes for the content if it's Some. Rust uses null-pointer optimization to make Option's variant identifier 0 bytes when it can, so an option is sometimes just as big as its contents. This is the case with `Sign`. This means the `MAXIMUM_SIZE` could be expressed as `mem::size_of<Game>() + 9`.)
Running `anchor test` should work now. You can finish writing the test by yourself. Try to simulate a win and a tie!
Proper testing also includes tests that try to exploit the contract. You can check whether you've protected yourself properly by calling `play` with unexpected parameters. For example:
```typescript
try {
await play(
program,
gameKeypair.publicKey,
playerTwo,
{row: 5, column: 1}, // out of bounds row
4,
{ active: {}, },
[
[{x:{}},{x: {}},null],
[{o:{}},null,null],
[null,null,null]
]
);
// we use this to make sure we definitely throw an error
chai.assert(false, "should've failed but didn't ");
} catch (error) {
expect(error.code).to.equal(6000);
}
```
## Deployment
Solana has three main clusters: `mainnet-beta`, `devnet`, and `testnet`.
For developers, `devnet` and `mainnet-beta` are the most interesting. `devnet` is where you test your application in a more realistic environment than `localnet`. `testnet` is mostly for validators.
We are going to deploy on `devnet`.
Here is your deployment checklist 🚀
1. run `anchor build`. Your program keypair is now in `target/deploy`. Keep this secret. You can reuse it on all clusters.
2. run `solana address -k target/deploy/tic_tac_toe-keypair.json` and copy the address into your `declare_id!` macro at the top of `lib.rs`.
3. run `anchor build` again. This step is is necessary to include our new program id in the binary.
4. change the `provider.cluster` variable in `Anchor.toml` to `devnet`.
5. run `anchor deploy`
6. run `anchor test`
There is more to deployments than this e.g. understanding how the BPFLoader works, how to manage keys, how to upgrade your programs and more. Keep reading to learn more!
Well done! You've finished the essentials section. You can now move on to the more advanced parts of Anchor.

View File

@ -0,0 +1,127 @@
# The Accounts Struct
The Accounts struct is where you define which accounts your instruction expects and which constraints these accounts should adhere to. You do this via two constructs: Types and constraints.
## Types
> [Account Types Reference](https://docs.rs/anchor-lang/latest/anchor_lang/accounts/index.html)
Each type has a specific use case in mind. Detailed explanations for the types can be found in the [reference](https://docs.rs/anchor-lang/latest/anchor_lang/accounts/index.html). We will briefly explain the most important type here, the `Account` type.
### The Account Type
> [Account Reference](https://docs.rs/anchor-lang/latest/anchor_lang/accounts/account/struct.Account.html)
The `Account` type is used when an instruction is interested in the deserialized data of the account. Consider the following example where we set some data in an account:
```rust,ignore
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod hello_anchor {
use super::*;
pub fn set_data(ctx: Context<SetData>, data: u64) -> ProgramResult {
ctx.accounts.my_account.data = data;
Ok(())
}
}
#[account]
#[derive(Default)]
pub struct MyAccount {
data: u64
}
#[derive(Accounts)]
pub struct SetData<'info> {
#[account(mut)]
pub my_account: Account<'info, MyAccount>
}
```
`Account` is generic over `T`. This `T` is a type you can create yourself to store data. In this example, we have created a struct `MyAccount` with a single `data` field to store a `u64`. Account requires `T` to implement certain functions (e.g. functions that (de)serialize `T`). Most of the time, you can use the `#[account]` attribute to add these functions to your data, as is done in the example.
Most importantly, the `#[account]` attribute sets the owner of that data to the `ID` (the one we created earlier with `declareId`) of the crate `#[account]` is used in. The Account type can then check for you that the `AccountInfo` passed into your instruction has its `owner` field set to the correct program. In this example, `MyAccount` is declared in our own crate so `Account` will verify that the owner of `my_account` equals the address we declared with `declareId`.
#### Using `Account<'a, T>` with non-anchor program accounts
There may be cases where you want your program to interact with a non-Anchor program. You can still get all the benefits of `Account` but you have to write a custom wrapper type instead of using `#[account]`. For instance, Anchor provides wrapper types for the token program accounts so they can be used with `Account`.
```rust,ignore
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod hello_anchor {
use super::*;
pub fn set_data(ctx: Context<SetData>, data: u64) -> ProgramResult {
if ctx.accounts.token_account.amount > 0 {
ctx.accounts.my_account.data = data;
}
Ok(())
}
}
#[account]
#[derive(Default)]
pub struct MyAccount {
data: u64,
mint: Pubkey
}
#[derive(Accounts)]
pub struct SetData<'info> {
#[account(mut)]
pub my_account: Account<'info, MyAccount>,
#[account(
constraint = my_account.mint == token_account.mint,
has_one = owner
)]
pub token_account: Account<'info, TokenAccount>,
pub owner: Signer<'info>
}
```
In this example, we set the `data` field of an account if the caller has admin rights. We decide whether the caller is an admin by checking whether they own admin tokens for the account they want to change. We do most of this via constraints which we will look at in the next section.
The important thing to take away is that we use the `TokenAccount` type (that wraps around the token program's `Account` struct and adds the required functions) to make anchor ensure that the incoming account is owned by the token program and to make anchor deserialize it. This means we can use the `TokenAccount` properties inside our constraints (e.g. `token_account.mint`) as well as in the instruction function.
Check out the [reference for the Account type](https://docs.rs/anchor-lang/latest/anchor_lang/struct.Account.html) to learn how to implement your own wrapper types for non-anchor programs.
## Constraints
> [Constraints reference](https://docs.rs/anchor-lang/latest/anchor_lang/derive.Accounts.html)
Account types can do a lot of work for you but they're not dynamic enough to handle all the security checks a secure program requires.
Add constraints to an account with the following format:
```rust,ignore
#[account(<constraints>)]
pub account: AccountType
```
Some constraints support custom Errors (we will explore errors [later](./errors.md)):
```rust,ignore
#[account(...,<constraint> @ MyError::MyErrorVariant, ...)]
pub account: AccountType
```
For example, in the examples above, we used the `mut` constraint to indicate that `my_account` should be mutable. We used `has_one` to check that `token_account.owner == owner.key()`. And finally we used `constraint` to check an arbitrary expression; in this case, whether the incoming `TokenAccount` belongs to the admin mint.
```rust,ignore
#[derive(Accounts)]
pub struct SetData<'info> {
#[account(mut)]
pub my_account: Account<'info, MyAccount>,
#[account(
constraint = my_account.mint == token_account.mint,
has_one = owner
)]
pub token_account: Account<'info, TokenAccount>,
pub owner: Signer<'info>
}
```
You can find information about all constraints in the reference. We will cover some of the most important ones in the milestone project at the end of the Essentials section.

View File

@ -0,0 +1,78 @@
# The Program Module
The program module is where you define your business logic. You do so by writing functions which can be called by clients or other programs. You've already seen one example of such a function, the `set_data` function from the previous section.
```rust,ignore
#[program]
mod hello_anchor {
use super::*;
pub fn set_data(ctx: Context<SetData>, data: u64) -> ProgramResult {
if ctx.accounts.token_account.amount > 0 {
ctx.accounts.my_account.data = data;
}
Ok(())
}
}
```
## Context
> [Context Reference](https://docs.rs/anchor-lang/latest/anchor_lang/context/index.html)
Each endpoint function takes a `Context` type as its first argument. Through this context argument it can access the accounts (`ctx.accounts`), the program id (`ctx.program_id`) of the executing program, and the remaining accounts (`ctx.remaining_accounts`). `remaining_accounts` is a vector that contains all accounts that were passed into the instruction but are not declared in the `Accounts` struct. This is useful when you want your function to handle a variable amount of accounts, e.g. when initializing a game with a variable number of players.
## Instruction Data
If your function requires instruction data, you can add it by adding arguments to the function after the context argument. Anchor will then automatically deserialize the instruction data into the arguments. You can have as many as you like. You can even pass in your own types as long as you use`#[derive(AnchorDeserialize)]` on them or implement `AnchorDeserialize` for them yourself. Here's an example with a custom type used as an instruction data arg:
```rust,ignore
...
#[program]
mod hello_anchor {
use super::*;
pub fn set_data(ctx: Context<SetData>, data: Data) -> ProgramResult {
ctx.accounts.my_account.data = init_data.data;
ctx.accounts.my_account.age = init_data.age;
Ok(())
}
}
#[account]
#[derive(Default)]
pub struct MyAccount {
pub data: u64,
pub age: u8
}
#[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Copy, Debug)]
pub struct Data {
pub data: u64,
pub age: u8
}
...
```
Conveniently, `#[account]` implements `Anchor(De)Serialize` for `MyAccount`, so the example above can be simplified.
```rust,ignore
...
#[program]
mod hello_anchor {
use super::*;
pub fn set_data(ctx: Context<SetData>, data: MyAccount) -> ProgramResult {
ctx.accounts.my_account.set_inner(data);
Ok(())
}
}
#[account]
#[derive(Default)]
pub struct MyAccount {
pub data: u64,
pub age: u8
}
...
```

View File

@ -0,0 +1,2 @@
# Anchor Periphery
This chapter explores Anchor's periphery.

244
book/src/chapter_4/cli.md Normal file
View File

@ -0,0 +1,244 @@
# CLI
A CLI is provided to support building and managing an Anchor workspace.
For a comprehensive list of commands and options, run `anchor -h` on any
of the following subcommands.
```
anchor-cli
USAGE:
anchor <SUBCOMMAND>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
SUBCOMMANDS:
build Builds the workspace
cluster Cluster commands
deploy Deploys each program in the workspace
expand Expands the macros of a program or the workspace
help Prints this message or the help of the given subcommand(s)
idl Commands for interacting with interface definitions
init Initializes a workspace
migrate Runs the deploy migration script
new Creates a new program
test Runs integration tests against a localnetwork
upgrade Upgrades a single program. The configured wallet must be the upgrade authority
verify Verifies the on-chain bytecode matches the locally compiled artifact. Run this
command inside a program subdirectory, i.e., in the dir containing the program's
Cargo.toml
```
## Build
```
anchor build
```
Builds programs in the workspace targeting Solana's BPF runtime and emitting IDLs in the `target/idl` directory.
```
anchor build --verifiable
```
Runs the build inside a docker image so that the output binary is deterministic (assuming a Cargo.lock file is used). This command must be run from within a single crate subdirectory within the workspace. For example, `programs/<my-program>/`.
## Cluster
### Cluster list
```
anchor cluster list
```
This lists cluster endpoints:
```
Cluster Endpoints:
* Mainnet - https://solana-api.projectserum.com
* Mainnet - https://api.mainnet-beta.solana.com
* Devnet - https://api.devnet.solana.com
* Testnet - https://api.testnet.solana.com
```
## Deploy
```
anchor deploy
```
Deploys all programs in the workspace to the configured cluster.
::: tip Note
This is different from the `solana program deploy` command, because everytime it's run
it will generate a *new* program address.
:::
## Expand
```
anchor expand
```
If run inside a program folder, expands the macros of the program.
If run in the workspace but outside a program folder, expands the macros of the workspace.
If run with the `--program-name` option, expand only the given program.
## Idl
The `idl` subcommand provides commands for interacting with interface definition files.
It's recommended to use these commands to store an IDL on chain, at a deterministic
address, as a function of nothing but the the program's ID. This
allows us to generate clients for a program using nothing but the program ID.
### Idl Init
```
anchor idl init -f <target/idl/program.json> <program-id>
```
Creates an idl account, writing the given `<target/idl/program.json>` file into a program owned account. By default, the size of the account is double the size of the IDL,
allowing room for growth in case the idl needs to be upgraded in the future.
### Idl Fetch
```
anchor idl fetch -o <out-file.json> <program-id>
```
Fetches an IDL from the configured blockchain. For example, make sure
your `Anchor.toml` is pointing to the `mainnet` cluster and run
```
anchor idl fetch GrAkKfEpTKQuVHG2Y97Y2FF4i7y7Q5AHLK94JBy7Y5yv
```
### Idl Authority
```
anchor idl authority <program-id>
```
Outputs the IDL account's authority. This is the wallet that has the ability to
update the IDL.
### Idl Erase Authority
```
anchor idl erase-authority -p <program-id>
```
Erases the IDL account's authority so that upgrades can no longer occur. The
configured wallet must be the current authority.
### Idl Upgrade
```
anchor idl upgrade <program-id> -f <target/idl/program.json>
```
Upgrades the IDL file on chain to the new `target/idl/program.json` idl.
The configured wallet must be the current authority.
```
anchor idl set-authority -n <new-authority> -p <program-id>
```
Sets a new authority on the IDL account. Both the `new-authority` and `program-id`
must be encoded in base 58.
## Init
```
anchor init
```
Initializes a project workspace with the following structure.
* `Anchor.toml`: Anchor configuration file.
* `Cargo.toml`: Rust workspace configuration file.
* `package.json`: JavaScript dependencies file.
* `programs/`: Directory for Solana program crates.
* `app/`: Directory for your application frontend.
* `tests/`: Directory for JavaScript integration tests.
* `migrations/deploy.js`: Deploy script.
## Migrate
```
anchor migrate
```
Runs the deploy script located at `migrations/deploy.js`, injecting a provider configured
from the workspace's `Anchor.toml`. For example,
```javascript
// File: migrations/deploys.js
const anchor = require("@project-serum/anchor");
module.exports = async function (provider) {
anchor.setProvider(provider);
// Add your deploy script here.
}
```
Migrations are a new feature
and only support this simple deploy script at the moment.
## New
```
anchor new <program-name>
```
Creates a new program in the workspace's `programs/` directory initialized with boilerplate.
## Test
```
anchor test
```
Run an integration test suit against the configured cluster, deploying new versions
of all workspace programs before running them.
If the configured network is a localnet, then automatically starts the localnetwork and runs
the test.
::: tip Note
Be sure to shutdown any other local validators, otherwise `anchor test` will fail to run.
If you'd prefer to run the program against your local validator use `anchor test --skip-local-validator`.
:::
When running tests we stream program logs to `.anchor/program-logs/<address>.<program-name>.log`
::: tip Note
The Anchor workflow [recommends](https://www.parity.io/paritys-checklist-for-secure-smart-contract-development/)
to test your program using integration tests in a language other
than Rust to make sure that bugs related to syntax misunderstandings
are coverable with tests and not just replicated in tests.
:::
## Upgrade
```
anchor upgrade <target/deploy/program.so> --program-id <program-id>
```
Uses Solana's upgradeable BPF loader to upgrade the on chain program code.
## Verify
```
anchor verify <program-id>
```
Verifies the on-chain bytecode matches the locally compiled artifact.