docs: Update tutorials for new init constraint (#670)

This commit is contained in:
Armani Ferrante 2021-09-02 16:29:25 -07:00 committed by GitHub
parent 4808c9ab6c
commit 2cc82bc11a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 42 additions and 133 deletions

View File

@ -31,27 +31,16 @@ Some new syntax elements are introduced here.
First, let's start with the initialize instruction. Notice the `data` argument passed into the program. This argument and any other valid
Rust types can be passed to the instruction to define inputs to the program.
::: tip
If you'd like to pass in your own type as an input to an instruction handler, then it must be
defined in the same `src/lib.rs` file as the `#[program]` module, so that the IDL parser can
pick it up.
:::
Additionally,
notice how we take a mutable reference to `my_account` and assign the `data` to it. This leads us to
the `Initialize` struct, deriving `Accounts`. There are three things to notice about `Initialize`.
the `Initialize` struct, deriving `Accounts`. There are two things to notice about `Initialize`.
1. The `my_account` field is of type `ProgramAccount<'info, MyAccount>`, telling the program it *must*
be **owned** by the currently executing program, and the deserialized data structure is `MyAccount`.
2. The `my_account` field is marked with the `#[account(init)]` attribute. This should be used
in one situation: when a given `ProgramAccount` is newly created and is being used by the program
for the first time (and thus its data field is all zero). If `#[account(init)]` is not used
when account data is zero initialized, the transaction will be rejected.
3. The `Rent` **sysvar** is required for the rent exemption check, which the framework enforces
by default for any account marked with `#[account(init)]`. To be more explicit about the check,
one can specify `#[account(init, rent_exempt = enforce)]`. To skip this check, (and thus
allowing you to omit the `Rent` acccount), you can specify
`#[account(init, rent_exempt = skip)]` on the account being initialized (here, `my_account`).
2. The `my_account` field is marked with the `init` attribute. This will create a new
account owned by the current program, zero initialized. When using `init`, one must also provider
`payer`, which will fund the account creation, `space`, which defines how large the account should be,
and the `system_program`, which is required by the runtime for creating the account.
::: details
All accounts created with Anchor are laid out as follows: `8-byte-discriminator || borsh
@ -81,15 +70,16 @@ for persisting changes.
## Creating and Initializing Accounts
For a moment, assume an account of type `MyAccount` was created on Solana, in which case,
we can invoke the above `initialize` instruction as follows.
We can interact with the program as follows.
<<< @/../examples/tutorial/basic-1/tests/basic-1.js#code-separated
<<< @/../examples/tutorial/basic-1/tests/basic-1.js#code-simplified
The last element passed into the method is common amongst all dynamically generated
methods on the `rpc` namespace, containing several options for a transaction. Here,
we specify the `accounts` field, an object of all the addresses the transaction
needs to touch.
needs to touch, and the `signers` array of all `Signer` objects needed to sign the
transaction. Because `myAccount` is being created, the Solana runtime requries it
to sign the transaction.
::: details
If you've developed on Solana before, you might notice two things 1) the ordering of the accounts doesn't
@ -98,22 +88,6 @@ options are not specified on the account anywhere. In both cases, the framework
of these details for you, by reading the IDL.
:::
However it's common--and sometimes necessary for security purposes--to batch
instructions together. We can extend the example above to both create an account
and initialize it in one atomic transaction.
<<< @/../examples/tutorial/basic-1/tests/basic-1.js#code
Here, notice the **two** fields introduced: `signers` and `instructions`. `signers`
is an array of all `Account` objects to sign the transaction and `instructions` is an
array of all instructions to run **before** the explicitly specified program instruction,
which in this case is `initialize`. Because we are creating `myAccount`, it needs to
sign the transaction, as required by the Solana runtime.
We can simplify this further.
<<< @/../examples/tutorial/basic-1/tests/basic-1.js#code-simplified
As before, we can run the example tests.
```

View File

@ -71,9 +71,8 @@ retrieve the return value. In future work, Anchor should do this transparently.
## Conclusion
Now that you can have your programs call other programs, you should be able to access all the work being done by other developers in your own applications!
Now that you can have your programs call other programs, you should be able to access all the work being done by other developers in your own applications!
## Next Steps
Up until now, we've treated programs on Solana as stateless. In the next tutorial we will learn how to add a global state to our program.

View File

@ -19,8 +19,10 @@ mod basic_1 {
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(zero)]
#[account(init, payer = user, space = 8 + 8)]
pub my_account: ProgramAccount<'info, MyAccount>,
pub user: AccountInfo<'info>,
pub system_program: AccountInfo<'info>,
}
#[derive(Accounts)]

View File

@ -1,5 +1,6 @@
const assert = require("assert");
const anchor = require("@project-serum/anchor");
const { SystemProgram } = anchor.web3;
describe("basic-1", () => {
// Use a local provider.
@ -8,102 +9,23 @@ describe("basic-1", () => {
// Configure the client to use the local cluster.
anchor.setProvider(provider);
it("Creates and initializes an account in two different transactions", async () => {
// The program owning the account to create.
const program = anchor.workspace.Basic1;
// The Account to create.
const myAccount = anchor.web3.Keypair.generate();
// Create account transaction.
const tx = new anchor.web3.Transaction();
tx.add(
anchor.web3.SystemProgram.createAccount({
fromPubkey: provider.wallet.publicKey,
newAccountPubkey: myAccount.publicKey,
space: 8 + 8,
lamports: await provider.connection.getMinimumBalanceForRentExemption(
8 + 8
),
programId: program.programId,
})
);
// Execute the transaction against the cluster.
await provider.send(tx, [myAccount]);
// Execute the RPC.
// #region code-separated
await program.rpc.initialize(new anchor.BN(1234), {
accounts: {
myAccount: myAccount.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
});
// #endregion code-separated
// Fetch the newly created account from the cluster.
const account = await program.account.myAccount.fetch(myAccount.publicKey);
// Check it's state was initialized.
assert.ok(account.data.eq(new anchor.BN(1234)));
});
// Reference to an account to use between multiple tests.
let _myAccount = undefined;
it("Creates and initializes an account in a single atomic transaction", async () => {
// The program to execute.
const program = anchor.workspace.Basic1;
// #region code
// The Account to create.
const myAccount = anchor.web3.Keypair.generate();
// Atomically create the new account and initialize it with the program.
await program.rpc.initialize(new anchor.BN(1234), {
accounts: {
myAccount: myAccount.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [myAccount],
instructions: [
anchor.web3.SystemProgram.createAccount({
fromPubkey: provider.wallet.publicKey,
newAccountPubkey: myAccount.publicKey,
space: 8 + 8, // Add 8 for the account discriminator.
lamports: await provider.connection.getMinimumBalanceForRentExemption(
8 + 8
),
programId: program.programId,
}),
],
});
// Fetch the newly created account from the cluster.
const account = await program.account.myAccount.fetch(myAccount.publicKey);
// Check it's state was initialized.
assert.ok(account.data.eq(new anchor.BN(1234)));
// #endregion code
});
it("Creates and initializes an account in a single atomic transaction (simplified)", async () => {
// #region code-simplified
// The program to execute.
const program = anchor.workspace.Basic1;
// The Account to create.
const myAccount = anchor.web3.Keypair.generate();
// Atomically create the new account and initialize it with the program.
// Create the new account and initialize it with the program.
// #region code-simplified
await program.rpc.initialize(new anchor.BN(1234), {
accounts: {
myAccount: myAccount.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
user: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
},
signers: [myAccount],
instructions: [await program.account.myAccount.createInstruction(myAccount)],
});
// #endregion code-simplified

View File

@ -1,4 +1,5 @@
use anchor_lang::prelude::*;
use anchor_lang::solana_program::system_program;
// Define the program's instruction handlers.
@ -24,8 +25,12 @@ mod basic_2 {
#[derive(Accounts)]
pub struct Create<'info> {
#[account(zero)]
#[account(init, payer = user, space = 8 + 40)]
pub counter: ProgramAccount<'info, Counter>,
#[account(signer)]
pub user: AccountInfo<'info>,
#[account(address = system_program::ID)]
pub system_program: AccountInfo<'info>,
}
#[derive(Accounts)]

View File

@ -1,5 +1,6 @@
const assert = require('assert');
const anchor = require('@project-serum/anchor');
const { SystemProgram } = anchor.web3;
describe('basic-2', () => {
const provider = anchor.Provider.local()
@ -17,10 +18,10 @@ describe('basic-2', () => {
await program.rpc.create(provider.wallet.publicKey, {
accounts: {
counter: counter.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
user: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
},
signers: [counter],
instructions: [await program.account.counter.createInstruction(counter)],
})
let counterAccount = await program.account.counter.fetch(counter.publicKey)

View File

@ -17,7 +17,7 @@ mod puppet_master {
#[derive(Accounts)]
pub struct PullStrings<'info> {
#[account(mut)]
#[account(mut, owner = puppet_program)]
pub puppet: CpiAccount<'info, Puppet>,
pub puppet_program: AccountInfo<'info>,
}

View File

@ -1,4 +1,5 @@
use anchor_lang::prelude::*;
use anchor_lang::solana_program::system_program;
#[program]
pub mod puppet {
@ -16,8 +17,12 @@ pub mod puppet {
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(zero)]
#[account(init, payer = user, space = 8 + 8)]
pub puppet: ProgramAccount<'info, Puppet>,
#[account(signer)]
pub user: AccountInfo<'info>,
#[account(address = system_program::ID)]
pub system_program: AccountInfo<'info>,
}
#[derive(Accounts)]

View File

@ -1,5 +1,6 @@
const assert = require("assert");
const anchor = require("@project-serum/anchor");
const { SystemProgram } = anchor.web3;
describe("basic-3", () => {
const provider = anchor.Provider.local();
@ -16,18 +17,18 @@ describe("basic-3", () => {
const tx = await puppet.rpc.initialize({
accounts: {
puppet: newPuppetAccount.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
user: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
},
signers: [newPuppetAccount],
instructions: [await puppet.account.puppet.createInstruction(newPuppetAccount)],
});
// Invoke the puppet master to perform a CPI to the puppet.
await puppetMaster.rpc.pullStrings(new anchor.BN(111), {
accounts: {
puppet: newPuppetAccount.publicKey,
puppetProgram: puppet.programId,
},
accounts: {
puppet: newPuppetAccount.publicKey,
puppetProgram: puppet.programId,
},
});
// Check the state updated.