Photo by Kaleidico on Unsplash

Solana Development (6): Understanding How Smart Contract Works in Solana

By Brochain | Solana Development | 25 Apr 2022


I suppose you have successfully deploy your first Smart Contract following previous post. So, now, let’s understand how things work underhook. In this article, we would first talk about some concept of account in Solana then we will start reviewing the code of Hello-world and how it works. So, let’s begin.

Solana Account

1. What is an Account?

In Solana, everything is an account. Accounts in Solana are like files. It can be used to store data. Also, it can be read-only or writable. If you go to the documentation of solana-program about account info here, you would find an account of Solana looks like this:

pub struct AccountInfo<'a> {
pub key: &'a Pubkey,
pub is_signer: bool,
pub is_writable: bool,
pub lamports: Rc<RefCell<&'a mut u64>>,
pub data: Rc<RefCell<&'a mut [u8]>>,
pub owner: &'a Pubkey,
pub executable: bool,
pub rent_epoch: Epoch,
}

So, let’s go through the above parameters one-by-one:

  1. key: Public key of this account;
  2. is_signer: This is used to check whether a transaction is signed by this account;
  3. is_writeable: Is this account writeable?
  4. lamports: Return lamports stored in this account. Lamport is the smallest unit of SOL. You can think it as Wei in Ethereum.
  5. data: Return the data that is stored in this account;
  6. owner: public key of the owner of this account;
  7. Executable: If the account is set to executable, it is treated as on-chain program;
  8. rent_epoch: The epoch at which this account will next owe rent. Epoch is kind of a time unit in Solana. So, it basically means when does this account need to pay rent. We will discuss a bit more about rent concept in latter part.
2. Owner vs Holder

In parameters above, there is a term called owner. However, this owner does not mean the person who own the private key of the account. In Solana, the person who owns the private key of the account is called holder, who has the right to operate the account (i.e. transfer fund to another account etc.). Owner is a different concept. Owner in Solana means the program that has right to amend the data of any account. In Solana, system program is set to be the owner of each account by default. Let me give you an example, if you create an account in Solana just simply store some SOL you own, it makes you the holder of the account and system program would be set as owner by default. So, you have the right to move the SOL out from your account. However, you cannot amend the SOL balance in your account at will. But system program does. System program would verify if a transaction is signed by appropriate private key and amend SOL balance of relevant accounts accordingly.

3. Program

As mentioned earlier, Smart Contract are called onchain-program in Solana. It is just an executable account in Solana (i.e. its is_executable set to True). According to Solana Runtime Account Role, all executable programs are immutable. That means you cannot change the code once its deployed. Also, all executable programs are stateless. That means data in it does not store any state of the program like Smart Contract in Ethereum. So, what if you need to store data or write data of this program? In this case, you need to create another non-executable accounts and set the program as the owner. So that, program can update data of this storage account from state to state.

4. Calling Account Info

In Solana, account info cannot read directly by an executable program. Instead, it has to be read by a client then pass the account info parameter to the program. We will discuss more about it when it comes to code review.

5. Rent

In Solana, all data storage in account would incur rent. It would be deducted from the lamport in that account. Once all lamport has been deducted, the account would be closed automatically. In such case, Solana would not store any abondaned data and keep it as light weight as possible.

Hello World Code Review

So, I guess most of you have grabbed some basic concepts of Solana’s account. Now, let’s start reading Hello World code. If you have play around with the program according to previous post, you would notice that the program simply return number of hellos your account sent to the program. It is a very simple program. You may find main logic code of the program here. In below, we would go through hello world code section by section.

use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint,
entrypoint::ProgramResult,
msg,
program_error::ProgramError,
pubkey::Pubkey,
};

In the first section, the program imports some other codes. From code above, we can see that the program mainly use borsh and solana_program. Borsh is a tool used to serialize and deserialize data. Solana_program is a tool for program to interact with Solana chain. In Rust, these libraries are uploaded to crate. To import them, you just simply including these in cargo.toml as dependencies. Then, it can be installed through cargo command. Then, you can just simply import them as code above.

/// Define the type of state stored in accounts
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct GreetingAccount {
/// number of greetings
pub counter: u32,
}

Now, the program defines a struct called GreetingAccount. This is used to store a public variable called counter, which is a variable used to trace number of hellos a client has sent to this program. Public variable means it can be publicly accessed by simply calling it. This struct has inherited the functions of BorshSerailize, BorshDeserialize and Debug. It means any functions of these 3 can also be used by GreetingAccount.

In Solana, this kind of changing variables need to be stored in a separate account. So, a user needs to provide a storage account for the program to store these data into. We would get into this shortly.

Now, let’s move to the main logic of the contract.

// Declare and export the program's entrypointentrypoint!(process_instruction); 
// Program entrypoint's implementation

pub fn process_instruction(
program_id: &Pubkey, // Public key of the account the hello world program was loaded into
accounts: &[AccountInfo], // The account to say hello to
_instruction_data: &[u8], // Ignored, all helloworld instructions are hellos
)

In Solana, every program needs to define an entry point. All Solana program takes 3 parameters in entry point: (1) public key of the program, (2) a list of accounts that the program can write to and (3) instruction data.

(1) above is easy to understand. It basically is the program id of your program. (2) is a bit confusing. So, as mentioned above, your program needs some accounts for them to keep track of some changing data. In this case, the changing data is the counter variable. If you need more than one accounts, the account info would be put in as an array and pass to the program. For (3), we may ignore instruction data as all instructions are just simply hello. Now, let’s move to ProgramResult:

-> ProgramResult{msg!("Hello World Rust program entrypoint");     // Iterating accounts is safer then indexing
let accounts_iter = &mut accounts.iter(); // Get the account to say hello to
let account = next_account_info(accounts_iter)?;

Then, the program result would be the major logic. After the welcome message, the first thing the program does is to make the accounts array iterable. So that the program can go through the array by using next_account_info() function. Again, this program only needs one account. So, we just need the first element of the account info array.

// The account must be owned by the program in order to modify its data
if account.owner != program_id {
msg!("Greeted account does not have the correct program id");
return Err(ProgramError::IncorrectProgramId);
}

As mentioned earlier, the program has to be owner of the account in order to amend any data in it. So, you need to check if the account owner points to the program. Otherwise, return an error message.

// Increment and store the number of times the account has been greeted
let mut greeting_account = GreetingAccount::try_from_slice(&account.data.borrow())?;
greeting_account.counter += 1;
greeting_account.serialize(&mut &mut account.data.borrow_mut()[..])?;
msg!("Greeted {} time(s)!", greeting_account.counter);
Ok(())
}

Now, grab the data from the account provided by the user and use try_from_slice() to deserialize it. try_from_slice() is a function defined in BorshDeserialize. Now, increment the counter variable from the account by 1. Then, serialize the updated counter variable and put it back to the account. Lastly, the program call the updated counter variable from the account and print it out.

Conclusion

So, now this is basically what a program in Solana looks like. In next article, we will go through the client program provided by the example to understand how it works on client-side. So, stay tuned and see you soon!

How do you rate this article?


22

0

Brochain
Brochain

Web3 Developer. The best way to learn is to share what you learnt.


Solana Development
Solana Development

Welcome to my blog. This blog would be about Solana Development. So, if you are interested to know how to write smart contract, web3 programs in Solana, stay tuned! Cheers!

Send a $0.01 microtip in crypto to the author, and earn yourself as you read!

20% to author / 80% to me.
We pay the tips from our rewards pool.