Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Counter PDA Tutorial - Easy #71

Merged
merged 12 commits into from
Oct 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file added client/public/tutorials/counter-easy/counter.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion client/src/tutorials/Template/files/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
// Implement program...
// Implement program...
29 changes: 29 additions & 0 deletions client/src/tutorials/Template_counter/Template.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Tutorial } from "../../components/Tutorial";

const Template = () => (
<Tutorial
// About section that will be shown under the description of the tutorial page
about={require("./about.md")}
// Actual tutorial pages to show next to the editor
pages={[
{ content: require("./pages/1.md"), title: "1/10" },
{ content: require("./pages/2.md"), title: "2/10" },
{ content: require("./pages/3.md"), title: "3/10" },
{ content: require("./pages/4.md"), title: "4/10" },
{ content: require("./pages/5.md"), title: "5/10" },
{ content: require("./pages/6.md"), title: "6/10" },
{ content: require("./pages/7.md"), title: "7/10" },
{ content: require("./pages/8.md"), title: "8/10" },
{ content: require("./pages/9.md"), title: "9/10" },
{ content: require("./pages/10.md"), title: "10/10" },
]}
// Initial files to have at the beginning of the tutorial
files={[
["src/lib.rs", require("./files/lib.rs")],
["client/client.ts", require("./files/client.ts.raw")],
["tests/index.test.ts", require("./files/anchor.test.ts.raw")],
]}
/>
);

export default Template;
7 changes: 7 additions & 0 deletions client/src/tutorials/Template_counter/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## What you will learn

- How to program a basic Solana Anchor program
- How to build and deploy a Solana Anchor program
- How to store information using PDAs
- Basic on-chain adding operation
- How to interact with your on-chain data accounts
55 changes: 55 additions & 0 deletions client/src/tutorials/Template_counter/files/anchor.test.ts.raw
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
describe("counter", () => {
// Configure the client to use the local cluster.

const systemProgram = anchor.web3.SystemProgram;

it("Create Counter!", async () => {
// Keypair = account
const [counter, _counterBump] =
await anchor.web3.PublicKey.findProgramAddress(
[pg.wallet.publicKey.toBytes()],
pg.program.programId
);
console.log("Your counter address", counter.toString());
const tx = await pg.program.methods
.createCounter()
.accounts({
authority: pg.wallet.publicKey,
counter: counter,
systemProgram: systemProgram.programId,
})
.rpc();
console.log("Your transaction signature", tx);
});

it("Fetch a counter!", async () => {
// Keypair = account
const [counterPubkey, _] = await anchor.web3.PublicKey.findProgramAddress(
[pg.wallet.publicKey.toBytes()],
pg.program.programId
);
console.log("Your counter address", counterPubkey.toString());
const counter = await pg.program.account.counter.fetch(counterPubkey);
console.log("Your counter", counter);
});

it("Update a counter!", async () => {
// Keypair = account
const [counterPubkey, _] = await anchor.web3.PublicKey.findProgramAddress(
[pg.wallet.publicKey.toBytes()],
pg.program.programId
);
console.log("Your counter address", counterPubkey.toString());
const counter = await pg.program.account.counter.fetch(counterPubkey);
console.log("Your counter", counter);
const tx = await pg.program.methods
.updateCounter()
.accounts({
counter: counterPubkey,
})
.rpc();
console.log("Your transaction signature", tx);
const counterUpdated = await pg.program.account.counter.fetch(counterPubkey);
console.log("Your counter count is: ", counterUpdated.count.toNumber());
});
});
2 changes: 2 additions & 0 deletions client/src/tutorials/Template_counter/files/client.ts.raw
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Client code...
console.log("client side");
58 changes: 58 additions & 0 deletions client/src/tutorials/Template_counter/files/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@

use anchor_lang::prelude::*;

declare_id!("9MGFR88uofDVcjHx3hoy2jmRtcnfMdQUbYRHCFFJFuXC");

#[program]
// Smart contract functions
pub mod counter {
use super::*;

pub fn create_counter(ctx: Context<CreateCounter>) -> Result<()> {
msg!("Creating a Counter!!");

// The creation of the counter must be here


msg!("Current count is {}", counter.count);
msg!("The Admin PubKey is: {} ", counter.authority);

Ok(())
}

pub fn update_counter(ctx: Context<UpdateCounter>) -> Result<()> {
msg!("Adding 1 to the counter!!");

// Updating the counter must be here

msg!("Current count is {}", counter.count);
msg!("{} remaining to reach 1000 ", 1000 - counter.count);

Ok(())
}

}

// Data validators
#[derive(Accounts)]
pub struct CreateCounter<'info> {
#[account(mut)]
authority: Signer<'info>,
#[account(init, seeds=[authority.key().as_ref()], bump, payer=authority, space=100)]
counter: Account<'info, Counter>,
system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct UpdateCounter<'info> {
authority: Signer<'info>,
#[account(mut, has_one = authority)]
counter: Account<'info, Counter>,
}

// Data structures
#[account]
pub struct Counter {
authority: Pubkey,
count: u64,
}
1 change: 1 addition & 0 deletions client/src/tutorials/Template_counter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./Template";
13 changes: 13 additions & 0 deletions client/src/tutorials/Template_counter/pages/1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Create a Counter! 📝

Hey!

In this tutorial you will learn how to store basic data using a Program Derivated Address(PDA).

For doing that, we will create a simple Counter. By default it will start with a value of 0 and we will have to add +1 every time we call it.

![](/tutorials/counter-easy/counter.jpg)

This tutorial is pretty simple, but will be essential in order to get a better understanding on how Solana works.

Are you ready?? Let's go then 💻 ⚡️
7 changes: 7 additions & 0 deletions client/src/tutorials/Template_counter/pages/10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Congratss!!! You have reached the end of this tutorial

🥳 🥳 🕺 🕺 🥳 🕺

![](/tutorials/counter-easy/kid-dance.gif)

Advanced counter tutorial is waiting for you..
19 changes: 19 additions & 0 deletions client/src/tutorials/Template_counter/pages/2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Structure of the Program

As you probably have seen, our initial program is divided in 3 main sections: Functions, Data Validators and Data Structures.

- **Smart Contract Functions**: a section with the operations and logic of the program.

- **Data Validators**: a section for all the accounts that we will have to create and declare. As you can see, the _Context_<Name> from the functions is actually the same as the Data validators.

- **Data Structures**: the structure of the accounts / rust structs that we will use to store info! In the program is pretty clear, the Counter account will store 2 things:

```rust
#[account]
pub struct Counter {
authority: Pubkey,
count: u64,
}
```

So if we want to call the Count of the counter we will just have to do -> counter.count
38 changes: 38 additions & 0 deletions client/src/tutorials/Template_counter/pages/3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Create the counter: Initialize the counter

Let's check the function that is called `create_counter`. In this function there is an empty space that we should complete to actually obtain our Counter Account.

```rust
let counter = &mut ctx.accounts.counter;
```

Now let's set our initial default parameters to our counter:

- The counter will start at 0
- Setting the authority to the person that is creating the Counter.

```rust
counter.authority = ctx.accounts.authority.key();
counter.count = 0;
```

Nice! we have finished completing our first function.

**_Function completed_**

```rust

pub fn create_counter(ctx: Context<CreateCounter>) -> Result<()> {

msg!("Creating a Counter!!");

let counter = &mut ctx.accounts.counter;
counter.authority = ctx.accounts.authority.key();
counter.count = 0;

msg!("Current count is {}", counter.count);
msg!("The Admin PubKey is: {} ", counter.authority);

Ok(())
}
```
23 changes: 23 additions & 0 deletions client/src/tutorials/Template_counter/pages/4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Create the counter: Data Validator

In this section you don't have to write anything but It will be very important to look carefully the CreateCounter Data Validator because is relevant.

Previously we have called the ctx.accounts.counter, that is basically this account :

```rust
#[account(init, seeds=[authority.key().as_ref()], bump, payer=authority, space=100)]
counter: Account<'info, Counter>,
```

This account is a PDA, you can obtain more info about PDAs in the [Solana Cookbook](https://solanacookbook.com/core-concepts/pdas.html#facts).

But the important concepts to know about it, is that it must the initialized and must contain a seed, a bump and some required space.

In our case we used the authority key as the seed, we introduced a bump, we have set the authority to the payer(payer=person who is creating this PDA) and we have set space to 100, that is actually more than we need.

Finally, we also declared the Authority Account. This account has been set to the Signer address.

```rust
#[account(mut)]
authority: Signer<'info>,
```
51 changes: 51 additions & 0 deletions client/src/tutorials/Template_counter/pages/5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Update the counter ✍️

Let's check the function `update_counter`. This function must add +1 to the counter count every time is called.

I recommend you to try it by yourself instead of following the tutorial :) , using the concepts learned from the previous function!

.

.

.

.

.

.

.

.

Ok, so the solution is quite simple.

We will have to declare again the counter account from the Data Validator as we did before:

```rust
let counter = &mut ctx.accounts.counter;
```

And we will have to call the count property from counter and add +1

```rust
counter.count += 1 ;
```

**Function Completed** :

```rust
pub fn update_counter(ctx: Context<UpdateCounter>) -> Result<()> {

msg!("Adding 1 to the counter!!");
let counter = &mut ctx.accounts.counter;
counter.count += 1 ;
msg!("Current count is {}", counter.count);
msg!("{} remaining to reach 1000 ", 1000 - counter.count);

Ok(())
}

```
9 changes: 9 additions & 0 deletions client/src/tutorials/Template_counter/pages/6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Build the Counter Program

Congrats! You have almost reached the end!!

Now we will have to build this program, to make sure everything works fine :)

Let's build it then!

![](/tutorials/counter-easy/counter_anchorbuild.png)
11 changes: 11 additions & 0 deletions client/src/tutorials/Template_counter/pages/7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Deploy the Counter Program

In order to deploy the program to the Devnet network, we will have to make sure we have connected a wallet to Solana Playground and also having enough SOL on it!

To request SOL, type in the console:

```bash
solana airdrop 2
```

After that, deploy your smart contract to the Blockchain via the deploy button.
5 changes: 5 additions & 0 deletions client/src/tutorials/Template_counter/pages/8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Testing the Counter Program

Time to test our Counter Program!

For doing so, we will only have to click on Test inside the Explorer!
16 changes: 16 additions & 0 deletions client/src/tutorials/Template_counter/pages/9.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Testing the Counter: Fetching Data

Solana Playground is a powerful tool that can detect the accounts and data structs in our programs.

Because of that, we are able to fetch all our on-chain data just clicking buttons!

The testing script we ran before is designed to:

- Call Create Counter 1 time
- Call Update Counter 1 time

**_That's why if you test 5 times, you will fail in Create Counter, because the counter is already created!_**

So if you click test 5 times, you will be updating the counter 5 times with a total count of 5. Fetch counter data after that and see if it worked!

![](/tutorials/counter-easy/counter_testing_5times.png)
16 changes: 16 additions & 0 deletions client/src/tutorials/tutorials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,20 @@ export const TUTORIALS: TutorialData[] = [
categories: [TutorialCategory.OTHER],
elementImport: () => import("./Template"),
},

{
name: "Counter PDA Tutorial",
description:
"Create a simple counter that will store the number of times is called.",
imageSrc: getTutorialImgSrc("counter-easy/counter.jpg"),
authors: [
{
name: "cleon",
link: "https://twitter.com/0xCleon",
},
],
level: TutorialLevel.BEGINNER,
categories: [TutorialCategory.OTHER],
elementImport: () => import("./Template_counter"),
},
];