use anchor_lang::prelude::*;
declare_id!("PUBLIC_KEY_HERE");
#[program]
pub mod counter_contract {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let account = &ctx.accounts.counter_account;
msg!("Counter account has been initialised! Current count: {}", account.count);
Ok(())
}
pub fn update(ctx: Context<Update>, count: u64) -> Result<()> {
let account = &mut ctx.accounts.counter_account;
account.count = counter.count + 1;
msg!("Counter has been updated! Current count: {}", account.count);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(
init,
payer = user,
space = 8 + CounterAccount::INIT_SPACE)
)]
pub counter_account: Account<'info, CounterAccount>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Update<'info> {
#[account(mut)]
pub counter_account: Account<'info, CounterAccount>,
}
#[account]
pub struct CounterAccount {
count: u64,
}
Congratulations on starting your journey into smart contract development! This is Level 1, and we're excited to have you. By the end of this level, you'll be able to build rudimentary contracts on your own. Let's dive in!
So the idea of this smart contract is to create a counter application but decentralised meaning the actual value will be stored on-chain. Excited?
For any smart contract, you need two key components: an account (the state that holds all the values) and functions (that perform mutations on the state).
To use an analogy, an architect has a blueprint for a project and they use the blueprint to create multiple replicas. In a similar way, the account defined in the contract acts as a blueprint, allowing you to create multiple instances, each with different values.
In the editor, focus on the bottom-most snippet of code. That's the "account" we've been discussing, which is nothing but a structure. As you can see, it defines the data that your contract account will hold. In this case, the counter value.
Now, let's look at the top of the code, where the initialise and update functions are defined (we'll discuss derived accounts shortly).
The first function is the initialization function, which, as the name suggests, initializes an account.
The second function, update, increments the counter each time it is called. For now, let's focus on the initialise function.
As you may have noticed, the first argument in this function is the Context variable. The ctx parameter includes all the accounts and program state necessary for the instruction to execute correctly. This context provides access to a special property called accounts, which, as you might have guessed, gives access to the account object.
Another thing to be noted here is that the context argument is being passed an Initialise variable. Now this is used to enable type-checking so that you only access the data defined in our account. This Initialise variable is coming from our derived accounts! Let's explore that!
If you scroll down to the derived accounts snippet, you can very well see the Initialise struct (structure or blueprint). Now, let's break each component one by one.
Whoa! That was a lot to take in. Take a breather, and feel free to come back to it when you're ready.
Alright, let's wrap things up for the initialise function. In the first line, we initialize the account, followed by printing a message that includes the count data associated with the account. In this case, would be zero.
Now, let's move on to the update function. This time, we are accessing the counter_account instead of initializing it. Rust knows we're here to modify the account because, if you look back at the Initialise and Updatederived accounts, you'll notice that one uses init for the account while the other uses mut. This distinction allows Rust to understand whether we are initializing or mutating an object.
In the second line, we are simply incrementing the count by one. Although this is a fairly risky way, as there's a possibility that the count is at the MAX VALUE, in which case this will throw an error. But for the sake of simplicity, we can use this snippet. After incrementing, we print a message displaying the updated count!
Some axuillary yet important stuff includes the name of your smart contract which is counter_contract defined in the 4th line. mod keyword stands for modules which means from Rust's perspective you are just creating modules.
Additionally, note the declare_id macro (which differs slightly from functions) provided by Anchor. It is used to define the program's public key. The public key is crucial because it uniquely identifies your Solana program on the blockchain.
And voila! You can now call yourself a beginner smart contract developer 😄. However, this was just the tip of the iceberg and there are much more complex things you can build with a smart contract, which we will expore in the upcoming levels.