docs: Update tutorials for new init constraint (#670)
This commit is contained in:
parent
4808c9ab6c
commit
2cc82bc11a
|
@ -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.
|
||||
|
||||
```
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue