Add some more tutorial documentation (#60)
This commit is contained in:
parent
24e92f29d7
commit
d36ebd640a
|
@ -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",
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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).
|
|
@ -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")]
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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)));
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue