Add some more tutorial documentation (#60)

This commit is contained in:
Armani Ferrante 2021-02-03 00:35:18 -08:00 committed by GitHub
parent 24e92f29d7
commit d36ebd640a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 408 additions and 263 deletions

View File

@ -42,19 +42,26 @@ module.exports = {
title: "Getting Started",
children: [
"/getting-started/introduction",
"/getting-started/installation",
"/getting-started/quick-start",
"/getting-started/installation",
],
},
{
collapsable: false,
title: "Tutorials",
children: [
title: "Programs on Solana",
children: [
"/tutorials/tutorial-0",
"/tutorials/tutorial-1",
"/tutorials/tutorial-2",
"/tutorials/tutorial-3",
"/tutorials/tutorial-4",
"/tutorials/tutorial-5",
],
},
{
collapsable: false,
title: "CLI",
children: [
"/cli/commands",
],
},
],

195
docs/src/cli/commands.md Normal file
View File

@ -0,0 +1,195 @@
# Commands
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
deploy Deploys each program in the workspace
idl Commands for interacting with interface definitions
init Initializes a workspace
launch Deploys, initializes an IDL, and migrates all in one command
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
```
## Init
```
anchor init
```
Initializes a project workspace wit the following structure.
* `Anchor.toml`: Anchor configuration file.
* `programs/`: Directory for Solana program crates.
* `app/`: Directory for your application frontend.
* `tests/`: Directory for JavaScript integration tests.
* `migrations/deploy.js`: Deploy script.
## Build
```
anchor build
```
Builds programs in the workspace targeting Solana's BPF runtime and emitting IDLs in the `target/idl` directory.
## 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.
:::
## Upgrade
```
anchor upgrade <target/deplooy/program.so> --program-id <program-id>
```
Uses Solana's upgradeable BPF loader to upgrade the on chain program code.
## 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
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.
:::
## Migrate
```
anchor migrate
```
Runs the deploy script located at `migrations/deploy.js`, injecting a provider configured
form 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.
## 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
allow 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.
## Launch
```
anchor launch
```
Builds, deploys and migrates, all in one command. This is particularly
useful when simultaneously developing an app against a Localnet or Devnet. For mainnet, it's
recommended to run each command separately, since transactions can sometimes be
unreliable depending on the Solana RPC node being used.
## New
```
anchor new <program-name>
```
Creates a new program in the workspace's `programs/` directory initialized with boilerplate.

View File

@ -15,10 +15,10 @@ rustup component add rustfmt
## Install Solana
See the solana [docs](https://docs.solana.com/cli/install-solana-cli-tools) for installation instructions. Version 1.5.0 is required. On macOS and Linux,
See the solana [docs](https://docs.solana.com/cli/install-solana-cli-tools) for installation instructions. Version 1.5.5 is required. On macOS and Linux,
```bash
sh -c "$(curl -sSfL https://release.solana.com/v1.5.0/install)"
sh -c "$(curl -sSfL https://release.solana.com/v1.5.5/install)"
```
## Install Mocha

View File

@ -2,7 +2,7 @@
Anchor is a framework for Solana's [Sealevel](https://medium.com/solana-labs/sealevel-parallel-processing-thousands-of-smart-contracts-d814b378192) runtime providing several convenient developer tools.
- Rust crates and DSL for writing Solana programs
- Rust crates and eDSL for writing Solana programs
- [IDL](https://en.wikipedia.org/wiki/Interface_description_language) specification
- TypeScript package for generating clients from IDL
- CLI and workspace management for developing complete applications

View File

@ -1,63 +0,0 @@
# Quick Start
The quick start provides a whirlwind tour through creating, deploying, and testing a project
using Anchor. For an in depth guide building the Anchor workflow and DSL from the ground up,
see the subequent tutorials.
## Initialize a project
To initialize your project workspace, run
```bash
anchor init my-project && cd my-project
```
Your repo will be laid out with the following structure
* `Anchor.toml`: Anchor configuration file.
* `programs/`: Directory for Solana program crates.
* `app/`: Directory for your application frontend.
* `tests/`: Directory for JavaScript integration tests.
## Build
To build your program targeting Solana's BPF runtime and emit an IDL run
```bash
anchor build
```
You should see IDL files for all workspace programs in your `target/idl/` directory.
## Deploy
To deploy all programs in your workspace, run
```
anchor deploy
```
## Test
It's [recommended](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.
```
anchor test
```
Testing will build a program, deploy it to a local network, and
run integration tests all in one command.
## New
Anchor supports simulatenous development of multiple Solana programs.
To create a new program in your workspace, run
```
anchor new <program-name>
```
You should see a new program in the `programs/` directory initialized with boilerplate.

View File

@ -1,4 +1,4 @@
# Tutorial 0: A Minimal Example
# A Minimal Example
Here, we introduce Anchor's core syntax elements and project workflow. This tutorial assumes all
[prerequisites](../getting-started/installation.md) are installed.
@ -65,7 +65,7 @@ anchor build
The `build` command is a convenience combining two steps.
1) `cargo build-bpf`
2) `anchor idl -f program/src/lib.rs -o target/idl/basic_0.json`.
2) `anchor idl parse -f program/src/lib.rs -o target/idl/basic_0.json`.
:::
Once run, you should see your build artifacts, as usual, in your `target/` directory. Additionally,

View File

@ -1,4 +1,4 @@
# Tutorial 1: Arguments and Accounts
# Arguments and Accounts
This tutorial covers the basics of creating and mutating accounts using Anchor.
It's recommended to read [Tutorial 0](./tutorial-0.md) first, as this tutorial will

View File

@ -1,18 +1,22 @@
# Tutorial 2: Account Constraints and Access Control
# Account Constraints and Access Control
This tutorial covers how to specify constraints and access control on accounts.
This tutorial covers how to specify constraints and access control on accounts, a problem
somewhat unique to the parallel nature of Solana.
Because Solana programs are stateless, a transaction must specify accounts to be executed. And because an untrusted client specifies those accounts, a program must responsibily validate all input to the program to ensure it is what it claims to be--in addition to any instruction specific access control the program needs to do.
On Solana, a transaction must specify all accounts required for execution. And because an untrusted client specifies those accounts, a program must responsibly validate all such accounts are what the client claims they are--in addition to any instruction specific access control the program needs to do.
For example, one could imagine easily writing a faulty token program that forgets to check if the signer of a transaction claiming to be the owner of a token account actually matches the owner on the account. A simpler question that must be asked: what happens if the program expects a `Mint` account but a `Token` account is given instead?
For example, one could imagine easily writing a faulty token program that forgets to check if the **signer** of a transaction claiming to be the **owner** of a Token `Account` actually matches the **owner** on that account. Furthermore, imagine what might happen if the program expects a `Mint` account but a malicious user gives a token `Account`.
To address these problems, Anchor provides several types, traits, and macros. It's easiest to understand by seeing how they're used in an example, but a couple include
Doing these checks is particularly burdensome when there are lots of dependencies between
accounts, leading to repetitive [boilerplate](https://github.com/project-serum/serum-dex/blob/master/registry/src/access_control.rs)
code for account validation along with the ability to easily shoot oneself in the foot.
Instead, one can use the Anchor DSL to do these checks by specifying **constraints** when deriving
`Accounts`. We briefly touched on the most basic (and important) type of account constraint in the
[previous tutorial](./tutorial-1.md), the account discriminator. Here, we demonstrate others.
* [Accounts](https://docs.rs/anchor-lang/0.1.0/anchor_lang/derive.Accounts.html): derive macro implementing the `Accounts` [trait](https://docs.rs/anchor-lang/0.1.0/anchor_lang/trait.Accounts.html), allowing a struct to transform
from the untrusted `&[AccountInfo]` slice given to a Solana program into a validated struct
of deserialized account types.
* [#[account]](https://docs.rs/anchor-lang/0.1.0/anchor_lang/attr.account.html): attribute macro implementing [AccountSerialize](https://docs.rs/anchor-lang/0.1.0/anchor_lang/trait.AccountSerialize.html) and [AccountDeserialize](https://docs.rs/anchor-lang/0.1.0/anchor_lang/trait.AnchorDeserialize.html), automatically prepending a unique 8 byte discriminator to the account array. The discriminator is defined by the first 8 bytes of the `Sha256` hash of the account's Rust identifier--i.e., the struct type name--and ensures no account can be substituted for another.
* [ProgramAccount](https://docs.rs/anchor-lang/0.1.0/anchor_lang/struct.ProgramAccount.html): a wrapper type for a deserialized account implementing `AccountDeserialize`. Using this type within an `Accounts` struct will ensure the account is **owned** by the currently executing program.
With the above, we can define preconditions for our any instruction handler expecting a certain set of
accounts, allowing us to more easily reason about the security of our programs.
## Clone the Repo
@ -30,12 +34,37 @@ cd anchor/examples/tutorial/basic-2
## Defining a Program
For now see the [source](https://github.com/project-serum/anchor/tree/master/examples/basic-2).
Here we have a simple **Counter** program, where anyone can create a counter, but only the assigned
**authority** can increment it.
TODO
<<< @/../examples/tutorial/basic-2/programs/basic-2/src/lib.rs
If you've gone through the previous tutorials the `create` instruction should be straightforward.
Let's focus on the `increment` instruction, specifically the `Increment` struct deriving
`Accounts`.
```rust
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, has_one = authority)]
pub counter: ProgramAccount<'info, Counter>,
#[account(signer)]
pub authority: AccountInfo<'info>,
}
```
Here, several `#[account(..)]` attributes are used.
* `mut`: tells the program to persist all changes to the account.
* `has_one`: enforces the constraint that `Increment.counter.authority == Increment.authority.key`.
* `signer`: enforces the constraint that the `authority` account **signed** the transaction.
If any of these constraints do not hold, then the `increment` instruction will never be executed.
This allows us to completely separate account validation from our program's business logic, allowing us
to reason about each concern more easily. For more, see the full [list](https://github.com/project-serum/anchor#accounts-attribute-syntax) of account constraints.
## Next Steps
We've covered the basics for writing a single program using Anchor on Solana. But the power of
blockchains come not from a single program, but from combining multiple *composable* programs
(buzzword alert!). Next, we'll see how to call one program from another.
(buzzword...check). Next, we'll see how to call one program from another.

View File

@ -1,4 +1,4 @@
# Tutorial 3: Cross Program Invocations (CPI)
# Cross Program Invocations (CPI)
This tutorial covers how to call one program from another, a process known as
*cross-program-invocation* (CPI).

View File

@ -1,4 +1,4 @@
# Tutorial 4: State structs
# State structs
Up until now, we've treated programs on Solana as stateless, using accounts to persist
state between instruction invocations. In this tutorial, we'll give Solana programs the
@ -23,10 +23,53 @@ cd anchor/examples/tutorial/basic-4
<<< @/../examples/tutorial/basic-4/programs/basic-4/src/lib.rs#code
TODO: explain + add instructions (both manual instructions and instructions inside the impl block).
Unlike the previous examples, all the instructions here not only take in an `Accounts`
struct, but they also operate over a mutable, global account marked by the `#[state]`
attribute. Every instruction defined in the corresponding `impl` block will have access
to this account, making it a great place to store global program state.
### How it works
We are able to give a program the illusion of state by adopting conventions in the framework. When invoking the `new` constructor, Anchor will automatically create a
program-owned account inside the program itself, invoking the system program's [create_account_with_seed](https://docs.rs/solana-program/1.5.5/solana_program/system_instruction/fn.create_account_with_seed.html) instruction, using `Pubkey::find_program_address(&[], program_id)` as the **base** and a deterministic string as the **seed** (the string doesn't
matter, as long as the framework is consistent).
This all has the effect of
giving the `#[state]` account a deterministic address, and so as long as all clients
and programs adopt this convention, programs can have the illusion of state in addition
to the full power of the lower level Solana accounts API. Of course, Anchor will handle this all for you, so you never have to worry about these details.
## Using the client
<<< @/../examples/tutorial/basic-4/tests/basic-4.js#code
### Invoke the constructor
TODO explain.
To access the `#[state]` account and associated instructions, one can use the
`anchor.state` namespace on the client. For example, to invoke the constructor,
<<< @/../examples/tutorial/basic-4/tests/basic-4.js#ctor
Note that the constructor can only be invoked once per program. All subsequent calls
to it will fail, since, as explained above, an account at a deterministic address
will be created.
### Fetch the state
To fetch the state account,
<<< @/../examples/tutorial/basic-4/tests/basic-4.js#accessor
### Invoke an instruction
To invoke an instruction,
<<< @/../examples/tutorial/basic-4/tests/basic-4.js#instruction
## Conclusion
Using state structs is intuitive. However, due to the fact that accounts
on Solana have a fixed size, applications often need to use accounts
directly in addition to `#[state]` stucts.
## Next Steps
Next we'll discuss errors.

View File

@ -0,0 +1,61 @@
# Errors
If you've ever programmed on a blockchain, you've probably been frustrated by
either non existant or opaque error codes. Anchor attempts to address this by
providing the `#[error]` attribute, which can be used to create typed Errors with
descriptive messages that automatically propagate to the client.
## Defining a Program
For example,
```rust
use anchor_lang::prelude::*;
#[program]
mod errors {
use super::*;
pub fn hello(_ctx: Context<Hello>) -> Result<()> {
Err(ErrorCode::Hello.into())
}
}
#[derive(Accounts)]
pub struct Hello {}
#[error]
pub enum ErrorCode {
#[msg("This is an error message clients will automatically display")]
Hello,
}
```
Observe the [#[error]](https://docs.rs/anchor-lang/0.1.0/anchor_lang/attr.error.html) attribute on the `ErrorCode` enum. This macro generates two types: an `Error` and a `Result`, both of which can be used when returning from your program.
To use the `Error`, one can simply use the user defined `ErrorCode` with Rust's [From](https://doc.rust-lang.org/std/convert/trait.From.html) trait. If you're unfamiliar with `From`, no worries. Just know that you need to either call
`.into()` when using your `ErrorCode`. Or use Rust's `?` operator, when returning an error.
Both of these will automatically convert *into* the correct `Error`.
::: details
What's the deal with this From stuff? Well, because the Solana runtime expects a [ProgramError](https://docs.rs/solana-program/1.5.5/solana_program/program_error/enum.ProgramError.html) in the return value. The framework needs to wrap the user defined error code into a
`ProgramError::Code` variant, before returning. The alternative would be to use the
`ProgramError` directly.
:::
## Using the Client
When using the client, we get the error message.
```javascript
try {
const tx = await program.rpc.hello();
assert.ok(false);
} catch (err) {
const errMsg = "This is an error message clients will automatically display";
assert.equal(err.toString(), errMsg);
}
```
It's that easy. :)
To run the full example, go [here](https://github.com/project-serum/anchor/tree/master/examples/errors).

View File

@ -1,3 +1,6 @@
//! This example demonstrates how custom errors and associated error messsages
//! can be defined and transparently propagated to clients.
#![feature(proc_macro_hygiene)]
use anchor_lang::prelude::*;
@ -5,15 +8,15 @@ use anchor_lang::prelude::*;
#[program]
mod errors {
use super::*;
pub fn hello(ctx: Context<Hello>) -> Result<()> {
pub fn hello(_ctx: Context<Hello>) -> Result<()> {
Err(MyError::Hello.into())
}
pub fn hello_no_msg(ctx: Context<HelloNoMsg>) -> Result<()> {
pub fn hello_no_msg(_ctx: Context<Hello>) -> Result<()> {
Err(MyError::HelloNoMsg.into())
}
pub fn hello_next(ctx: Context<HelloNext>) -> Result<()> {
pub fn hello_next(_ctx: Context<Hello>) -> Result<()> {
Err(MyError::HelloNext.into())
}
}
@ -21,12 +24,6 @@ mod errors {
#[derive(Accounts)]
pub struct Hello {}
#[derive(Accounts)]
pub struct HelloNoMsg {}
#[derive(Accounts)]
pub struct HelloNext {}
#[error]
pub enum MyError {
#[msg("This is an error message clients will automatically display")]

View File

@ -8,43 +8,16 @@ use anchor_lang::prelude::*;
mod basic_2 {
use super::*;
pub fn create_author(
ctx: Context<CreateAuthor>,
authority: Pubkey,
name: String,
) -> ProgramResult {
let author = &mut ctx.accounts.author;
author.authority = authority;
author.name = name;
pub fn create(ctx: Context<Create>, authority: Pubkey) -> ProgramResult {
let counter = &mut ctx.accounts.counter;
counter.authority = authority;
counter.count = 0;
Ok(())
}
pub fn update_author(ctx: Context<UpdateAuthor>, name: String) -> ProgramResult {
let author = &mut ctx.accounts.author;
author.name = name;
Ok(())
}
pub fn create_book(ctx: Context<CreateBook>, title: String, pages: Vec<Page>) -> ProgramResult {
let book = &mut ctx.accounts.book;
book.author = *ctx.accounts.author.to_account_info().key;
book.title = title;
book.pages = pages;
Ok(())
}
pub fn update_book(
ctx: Context<UpdateBook>,
title: Option<String>,
pages: Option<Vec<Page>>,
) -> ProgramResult {
let book = &mut ctx.accounts.book;
if let Some(title) = title {
book.title = title;
}
if let Some(pages) = pages {
book.pages = pages;
}
pub fn increment(ctx: Context<Increment>) -> ProgramResult {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
Ok(())
}
}
@ -52,60 +25,24 @@ mod basic_2 {
// Define the validated accounts for each handler.
#[derive(Accounts)]
pub struct CreateAuthor<'info> {
pub struct Create<'info> {
#[account(init)]
pub author: ProgramAccount<'info, Author>,
pub counter: ProgramAccount<'info, Counter>,
pub rent: Sysvar<'info, Rent>,
}
#[derive(Accounts)]
pub struct UpdateAuthor<'info> {
pub struct Increment<'info> {
#[account(mut, has_one = authority)]
pub counter: ProgramAccount<'info, Counter>,
#[account(signer)]
pub authority: AccountInfo<'info>,
#[account(mut, "&author.authority == authority.key")]
pub author: ProgramAccount<'info, Author>,
}
#[derive(Accounts)]
pub struct CreateBook<'info> {
#[account(signer)]
pub authority: AccountInfo<'info>,
#[account("&author.authority == authority.key")]
pub author: ProgramAccount<'info, Author>,
#[account(init)]
pub book: ProgramAccount<'info, Book>,
pub rent: Sysvar<'info, Rent>,
}
#[derive(Accounts)]
pub struct UpdateBook<'info> {
#[account(signer)]
pub authority: AccountInfo<'info>,
#[account("&author.authority == authority.key")]
pub author: ProgramAccount<'info, Author>,
#[account(mut, belongs_to = author)]
pub book: ProgramAccount<'info, Book>,
}
// Define the program owned accounts.
#[account]
pub struct Author {
pub struct Counter {
pub authority: Pubkey,
pub name: String,
}
#[account]
pub struct Book {
pub author: Pubkey,
pub title: String,
pub pages: Vec<Page>,
}
// Define custom types.
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct Page {
pub content: String,
pub footnote: String,
pub count: u64,
}

View File

@ -7,107 +7,41 @@ describe("basic-2", () => {
// Configure the client to use the local cluster.
anchor.setProvider(provider);
// Author for the tests.
const author = new anchor.web3.Account();
// Counter for the tests.
const counter = new anchor.web3.Account();
// Program for the tests.
const program = anchor.workspace.Basic2;
it("Creates an author", async () => {
await program.rpc.createAuthor(provider.wallet.publicKey, "Ghost", {
it("Creates a counter", async () => {
await program.rpc.create(provider.wallet.publicKey, {
accounts: {
author: author.publicKey,
counter: counter.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [author],
instructions: [
anchor.web3.SystemProgram.createAccount({
fromPubkey: provider.wallet.publicKey,
newAccountPubkey: author.publicKey,
space: 8 + 1000,
lamports: await provider.connection.getMinimumBalanceForRentExemption(
8 + 1000
),
programId: program.programId,
}),
],
signers: [counter],
instructions: [
await program.account.counter.createInstruction(counter),
],
});
let authorAccount = await program.account.author(author.publicKey);
let counterAccount = await program.account.counter(counter.publicKey);
assert.ok(authorAccount.authority.equals(provider.wallet.publicKey));
assert.ok(authorAccount.name === "Ghost");
assert.ok(counterAccount.authority.equals(provider.wallet.publicKey));
assert.ok(counterAccount.count.toNumber() === 0);
});
it("Updates an author", async () => {
await program.rpc.updateAuthor("Updated author", {
it("Updates a counter", async () => {
await program.rpc.increment({
accounts: {
author: author.publicKey,
counter: counter.publicKey,
authority: provider.wallet.publicKey,
},
});
authorAccount = await program.account.author(author.publicKey);
counterAccount = await program.account.counter(counter.publicKey);
assert.ok(authorAccount.authority.equals(provider.wallet.publicKey));
assert.ok(authorAccount.name === "Updated author");
});
// Book params to use accross tests.
const book = new anchor.web3.Account();
const pages = [
{
content: "first page",
footnote: "first footnote",
},
{
content: "second page",
footnote: "second footnote",
},
];
it("Creates a book", async () => {
await program.rpc.createBook("Book title", pages, {
accounts: {
authority: provider.wallet.publicKey,
author: author.publicKey,
book: book.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [book],
instructions: [
anchor.web3.SystemProgram.createAccount({
fromPubkey: provider.wallet.publicKey,
newAccountPubkey: book.publicKey,
space: 8 + 1000,
lamports: await provider.connection.getMinimumBalanceForRentExemption(
8 + 1000
),
programId: program.programId,
}),
],
});
const bookAccount = await program.account.book(book.publicKey);
assert.ok(bookAccount.author.equals(author.publicKey));
assert.ok(bookAccount.title === "Book title");
assert.deepEqual(bookAccount.pages, pages);
});
it("Updates a book", async () => {
await program.rpc.updateBook("New book title", null, {
accounts: {
authority: provider.wallet.publicKey,
author: author.publicKey,
book: book.publicKey,
},
});
const bookAccount = await program.account.book(book.publicKey);
assert.ok(bookAccount.author.equals(author.publicKey));
assert.ok(bookAccount.title === "New book title");
assert.deepEqual(bookAccount.pages, pages);
assert.ok(counterAccount.authority.equals(provider.wallet.publicKey));
assert.ok(counterAccount.count.toNumber() == 1);
});
});

View File

@ -10,26 +10,31 @@ describe("basic-4", () => {
const program = anchor.workspace.Basic4;
it("Is runs the constructor", async () => {
// #region code
// #region ctor
// Initialize the program's state struct.
await program.state.rpc.new({
accounts: {
authority: provider.wallet.publicKey,
},
});
// #endregion ctor
// Fetch the state struct from the network.
// #region accessor
const state = await program.state();
// #endregion code
// #endregion accessor
assert.ok(state.count.eq(new anchor.BN(0)));
});
it("Executes a method on the program", async () => {
// #region instruction
await program.state.rpc.increment({
accounts: {
authority: provider.wallet.publicKey,
},
});
// #endregion instruction
const state = await program.state();
assert.ok(state.count.eq(new anchor.BN(1)));
});