Browse Source

docs, examples: Associated tutorial

armaniferrante 4 years ago
parent
commit
d0a3f0faba

+ 1 - 0
.travis.yml

@@ -67,3 +67,4 @@ jobs:
         - pushd examples/tutorial/basic-2 && anchor test && popd
         - pushd examples/tutorial/basic-3 && anchor test && popd
         - pushd examples/tutorial/basic-4 && anchor test && popd
+        - pushd examples/tutorial/basic-5 && anchor test && popd

+ 1 - 0
docs/src/.vuepress/config.js

@@ -56,6 +56,7 @@ module.exports = {
           "/tutorials/tutorial-3",
           "/tutorials/tutorial-4",
           "/tutorials/tutorial-5",
+          "/tutorials/tutorial-6",
         ],
       },
       {

+ 9 - 0
docs/src/tutorials/tutorial-4.md

@@ -64,6 +64,15 @@ To invoke an instruction,
 
 <<< @/../examples/tutorial/basic-4/tests/basic-4.js#instruction
 
+## CPI
+
+Performing CPI from one Anchor program to another's state methods is very similar to performing CPI to normal Anchor instructions, except for two differences:
+
+1. All the generated instructions are located under the `<my_program>::cpi::state` module.
+2. One must use a [CpiStateContext](https://docs.rs/anchor-lang/latest/anchor_lang/struct.CpiStateContext.html), instead of a `[CpiContext](https://docs.rs/anchor-lang/latest/anchor_lang/struct.CpiContext.html).
+
+For a full example, see the `test_state_cpi` instruction, [here](https://github.com/project-serum/anchor/blob/master/examples/misc/programs/misc/src/lib.rs#L39).
+
 ## Conclusion
 
 Using state structs is intuitive. However, due to the fact that accounts

+ 85 - 0
docs/src/tutorials/tutorial-6.md

@@ -0,0 +1,85 @@
+# Associated Accounts
+
+No Solana program should be written without understanding associated accounts.
+Every program using an account in a way that maps to a user, which is almost all
+programs, should use them.
+
+The TLDR. Associated accounts are accounts whose address are deterministically defined by
+a program and some associated data. Usually that data is both a user wallet and some account
+instance, for example, a token mint.
+
+Why should you care? UX.
+
+Consider a wallet. Would you rather have a wallet with a single SOL address, which you
+can use to receive *all* SPL tokens, or would you rather have a wallet with a different
+address for every SPL token. Now generalize this. For everyone program you use, do you
+want a single account, i.e. your SOL wallet, to define your application state? Or do
+you want to keep track of all your account addresses for every program in existance?
+
+Associated accounts allow you to easily calculate addresses, and thus find,
+accounts for any program of any type. A huge improvement on the account model introduced
+thus far.
+
+Luckily, Anchor provides the ability to easily created associated program accounts for your program.
+
+::: details
+If you've explored Solana, you may have come across the [Associated Token Account Program](https://spl.solana.com/associated-token-account) which uniquely and deterministically defines
+a token account for a given wallet and a given mint. That is, if you have a SOL address,
+then you will have, at most, a single "token account" for every SPL mint in existence
+if you only use associated token addresses.
+
+Unfortunately, the SPL token program doesn't do this, strictly. It was built *before* the existance
+of associated token accounts (associated token accounts were built as an add-on).
+So in reality, there are non associated token accounts floating around Solanaland.
+However, for new programs, this isn't necessary, and it's recommend to only use associated
+accounts, when creating accounts on behalf of users, for example, a token account.
+:::
+
+## Clone the Repo
+
+To get started, clone the repo.
+
+```bash
+git clone https://github.com/project-serum/anchor
+```
+
+And change directories to the [example](https://github.com/project-serum/anchor/tree/master/examples/tutorial/basic-5).
+
+```bash
+cd anchor/examples/tutorial/basic-5
+```
+
+## Defining a Program to Create Associated Accounts
+
+The following program is an *extremely* simplified version of the SPL token program that
+does nothing other than create a mint and *associated* token account.
+
+<<< @/../examples/tutorial/basic-5/programs/basic-5/src/lib.rs#code
+
+Two new keywords are introduced to the `CreateToken` account context:
+
+* `associated = <target>`
+* `with = <target>`
+
+Both of these allow one to define input "seeds" that
+uniquely define the associated account. By convention, `associated` is used to define
+the main address to associate, i.e., the wallet, while `with` is used to define an
+auxilliary piece of metadata which has the effect of namespacing the associated account.
+This can be used, for example, to create multiple different associated accounts, each of
+which is associated *with* a new piece of metadata. In the token program, these pieces
+of metadata are mints, i.e., different token types.
+
+## Using the Client
+
+The client can be used similarly to all other examples. Additionally, we introduce
+two new apis.
+
+* `<program>.account.<account-name>.associatedAddress` returns an associated token address, given seeds.
+* `<program>.account.<account-name>.associated` returns the deserialized associated account, given seeds.
+
+
+We can use them with the example above as follows
+
+<<< @/../examples/tutorial/basic-5/tests/basic-5.js#test
+
+Notice that, in both apis, the "seeds" given match what is expected by the `#[account(associated = <target, with = <target>)]` attribute, where order matters. The `associated` target must come before the `with` target.

+ 9 - 0
examples/tutorial/basic-5/.gitignore

@@ -0,0 +1,9 @@
+# ignore Mac OS noise
+.DS_Store
+
+# ignore the build directory for Rust/Anchor
+target
+
+# Ignore backup files creates by cargo fmt.
+**/*.rs.bk
+    

+ 2 - 0
examples/tutorial/basic-5/Anchor.toml

@@ -0,0 +1,2 @@
+cluster = "localnet"
+wallet = "/home/armaniferrante/.config/solana/id.json"

+ 4 - 0
examples/tutorial/basic-5/Cargo.toml

@@ -0,0 +1,4 @@
+[workspace]
+members = [
+    "programs/*"
+]

+ 13 - 0
examples/tutorial/basic-5/migrations/deploy.js

@@ -0,0 +1,13 @@
+
+// Migrations are an early feature. Currently, they're nothing more than this
+// single deploy script that's invoked from the CLI, injecting a provider
+// configured from the workspace's Anchor.toml.
+
+const anchor = require("@project-serum/anchor");
+
+module.exports = async function (provider) {
+  // Configure client to use the provider.
+  anchor.setProvider(provider);
+
+  // Add your deploy script here.
+}

+ 18 - 0
examples/tutorial/basic-5/programs/basic-5/Cargo.toml

@@ -0,0 +1,18 @@
+[package]
+name = "basic-5"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "basic_5"
+
+[features]
+no-entrypoint = []
+no-idl = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = { path = "../../../../../lang" }

+ 2 - 0
examples/tutorial/basic-5/programs/basic-5/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 57 - 0
examples/tutorial/basic-5/programs/basic-5/src/lib.rs

@@ -0,0 +1,57 @@
+//! This example displays the use of associated program accounts.
+//!
+//! This program is an *extremely* simplified version of the SPL token program
+//! that does nothing other than create mint and token accounts, where all token
+//! accounts are associated accounts.
+
+// #region code
+use anchor_lang::prelude::*;
+
+#[program]
+pub mod basic_5 {
+    use super::*;
+
+    pub fn create_mint(ctx: Context<CreateMint>) -> ProgramResult {
+        ctx.accounts.mint.supply = 0;
+        Ok(())
+    }
+
+    pub fn create_token(ctx: Context<CreateToken>) -> ProgramResult {
+        let token = &mut ctx.accounts.token;
+        token.amount = 0;
+        token.authority = *ctx.accounts.authority.key;
+        token.mint = *ctx.accounts.mint.to_account_info().key;
+        Ok(())
+    }
+}
+
+#[derive(Accounts)]
+pub struct CreateMint<'info> {
+    #[account(init)]
+    mint: ProgramAccount<'info, Mint>,
+    rent: Sysvar<'info, Rent>,
+}
+
+#[derive(Accounts)]
+pub struct CreateToken<'info> {
+    #[account(associated = authority, with = mint)]
+    token: ProgramAccount<'info, Token>,
+    #[account(mut, signer)]
+    authority: AccountInfo<'info>,
+    mint: ProgramAccount<'info, Mint>,
+    rent: Sysvar<'info, Rent>,
+    system_program: AccountInfo<'info>,
+}
+
+#[account]
+pub struct Mint {
+    pub supply: u32,
+}
+
+#[associated]
+pub struct Token {
+    pub amount: u32,
+    pub authority: Pubkey,
+    pub mint: Pubkey,
+}
+// #endregion code

+ 57 - 0
examples/tutorial/basic-5/tests/basic-5.js

@@ -0,0 +1,57 @@
+const anchor = require("@project-serum/anchor");
+const assert = require("assert");
+
+describe("basic-5", () => {
+  // Configure the client to use the local cluster.
+  anchor.setProvider(anchor.Provider.env());
+
+  const program = anchor.workspace.Basic5;
+
+  const mint = new anchor.web3.Account();
+
+  // Setup. Not important for the point of the example.
+  it("Sets up the test", async () => {
+    // Create the mint account.
+    await program.rpc.createMint({
+      accounts: {
+        mint: mint.publicKey,
+        rent: anchor.web3.SYSVAR_RENT_PUBKEY,
+      },
+      instructions: [await program.account.mint.createInstruction(mint)],
+      signers: [mint],
+    });
+  });
+
+  it("Creates an associated token account", async () => {
+    // #region test
+    // Calculate the associated token address.
+    const authority = program.provider.wallet.publicKey;
+    const associatedToken = await program.account.token.associatedAddress(
+      authority,
+      mint.publicKey
+    );
+
+    // Execute the transaction to create the associated token account.
+    await program.rpc.createToken({
+      accounts: {
+        token: associatedToken,
+        authority,
+        mint: mint.publicKey,
+        rent: anchor.web3.SYSVAR_RENT_PUBKEY,
+        systemProgram: anchor.web3.SystemProgram.programId,
+      },
+    });
+
+    // Fetch the new associated account.
+    const account = await program.account.token.associated(
+      authority,
+      mint.publicKey
+    );
+    // #endregion test
+
+    // Check it was created correctly.
+    assert.ok(account.amount === 0);
+    assert.ok(account.authority.equals(authority));
+    assert.ok(account.mint.equals(mint.publicKey));
+  });
+});