A variation of the solana helloworld program example with a client written in Rust instead of Typescript

Overview

Simple Solana Smart Contract Example

This repository demonstrates how to create and invoke a program on the Solana blockchain. In Solana the word program is used to describe what is often described as a smart contract in other contexts.

The Solana program provided here counts the number of times that it has been executed and stores that information on chain. It is functionally identical to Solana's hello world example.

This program differs from the Solana example code in that the client which invokes the Solana program is written in Rust. Contrary to what you might guess the most complicated part of writing a smart contract in Solana is the client.

Getting started

In order to run this example program you will need to install Rust and Solana. Information about installing Rust can be found here and information about installing Solana can be found here.

Once you've completed the Solana installation run the following commands to configure you machine for local development:

solana config set --url localhost
solana-keygen new

These two commands create Solana config files in ~/.config/solana/ which solana command line tools will read in to determine what cluster to connect to and what keypair to use.

Having done that run a local Solana validator by running:

solana-test-validator

This program must be left running in the background.

Deploying the Solana program

To deploy the Solana program in this repository to the Solana cluster that you have configured run:

./run.sh deploy

Running the client program

To run the client program you must have already deployed the Solana program. The client program sends a transaction to the Solana blockchain asking it to execute the deployed program and reports the results.

./run.sh client

Will build and execute the client program. You ought to see results that look something like this:

Connected to remote solana node running version (1.7.16).
(1418720) lamports are required for this transaction.
(499999997700801080) lamports are owned by player.
creating greeting account
(1) greetings have been sent.

On future executions you will see that the greetings counter increases.

(2) greetings have been sent.

How this works

This repository is divided into two parts. There is a program/ directory which contains the smart contract that is actually deployed to the Solana blockchain and a client/ program which handles collecting funds, creating accounts, and invoking the deployed program.

Details about how the deployed program works can be found here. The actually interesting part of this repository is the client which I'll discuss below.

The client program

In order to execute a program that has been deployed to the Solana blockchain we need the following things:

  1. A connection to a Solana cluster
  2. An account with a large enough balance to pay for the program's execution.
  3. An account that we will transfer to the program to store state for the program.

The third item on that list is confusing. In Solana programs are entirely stateless. Instead, they operate on accounts and store data in those accounts between executions as needed.

Accounts are a really bad name for files. You can read the technical details here.

In order for a program to modify the contents of an account that program must own the account. This is the reason for the "transfer" part of the third item above. Our client program will create an account to store its program state in and then transfer ownership of that account to the program. The client will then later read the data in the account to see the results of the program's execution.

Why does it have to be like this? This seems like pain. The reason is that storing data on the blockchain costs money. The user of the program is expected to pay for the cost of running the program and so they must pay to create the account.

The technical details

I'll now take some time to walk through the technical details of how the client collects what is needed and then submits the transaction to Solana.

Connection establishment

The function establish_connection in client/src/client.rs creates a RPC connection to Solana over which we'll do all of our communication with the blockchain. The URL that we use for connecting to the blockchain is read from the Solana config in ~/.config/solana/cli/config.yml. It can be changed by running solana config set --url .

The data that we will be storing

Our program that we will deploy tracks the number of times that a given user has said hello to it. This requires a small amount of state which we represent with the following Rust structure:

struct GreetingSchema {
    counter: u32,
}

In order to make sure that this data can be serialized and unserialized independently to how Rust lays out a struct like that we use a serialization protocol called borsh. We can determine the size of this after serialization by serializing an instance of this struct with borsh and then getting its length.

Determining the balance requirement

To determine how much the program invocation will cost we use the function get_balance_requirement located in client/src/client.rs. The total cost of the invocation will be the cost of submitting the invocation transaction and the cost of storing the program state on the blockchain.

On Solana the cost of storing data on the blockchain is zero if that data is inside an account with a balance greater than the cost of two years of rent. You can read more about that here.

It appears as if the standard is to load two years of rent into accounts so that is what we do. Source: the "programs and accounts" section of this writeup. We can determine what this "two years of rent" amount is by running connection.get_minimum_balance_for_rent_exemption(data_size) where data_size is the amount of data that we will be storing in bytes.

Determining the payer and their balance

In order to determine who will be paying for this transaction we once again consult the solana config in ~/.config/solana/cli/config.yml. In that file there is a keypair_path field and we read the keypair from where that points.

To determine the payer's balance we use our connection and run connection.get_balance(player_pubkey).

Increasing the balance if there are not enough funds

If there are not enough funds in the payer's account we will need to airdrop funds there. This is done via the request_airdrop function in client/src/client.rs. Airdrops are only available on test networks so this will not work on the mainnet.

Locating the program that we will execute

Both the payer and the program are accounts on Solana. The only difference is that the program account is executable.

Our client program takes a single argument which is the path to the keypair of the program that has been deployed to the blockchain. In the get_program function located in client/src/client.rs the keypair is loaded again and then it is verified that the specified program is executable.

Creating an account to store state

The function create_greeting_account in client/src/client.rs handles the creation of an account to store program state in. This is the most complicated part of our client program as the address of the account must be derived from the payer's public key, the program's public key, and a seed phrase.

The reason that we derive the address of the storage account like this is so that it can be located later without storing any state across invocations of the client. Solana supports this method of account creation with the create_account_with_seed system instruction.

Arguments to this instruction are poorly documented and different across the Typescript and Rust SDKs. Here are what they are and their meanings in the Rust SDK:

  • from_pubkey the public key of the creator of the new account. In our case this is the payer's public key.
  • to_pubkey the public key that the generated account will have. In our case this is the public key that we generate.
  • base the payer's public key as it is the "base" in the derivation of the generated account's public key. The other ingredients being the program's public key and the seed phrase.
  • seed the seed phrase that was used in the generation of the generated account's public key.
  • lamports the number of lamports to send to the generated account. In our case this is equal to the amount of lamports required to live on the chain rent free.
  • space the size of the data that will be stored in the generated account.
  • owner the owner of the generated account. In our case this is the program's public key.

You may ask yourself after reading this why we need to both provide all of the ingredients needed to generate the new accounts public key (also called its address) and the public key that we have generated with those ingredients. The Solana source code seems to suggest that this is some method for error checking but it seems slightly shitty to me.

Sending the "hello" transaction

Sending the hello transaction to the program is actually the easy part. It is done in the say_hello function in client/src/client.rs. This function just creates a new instruction with the generated storage account as an argument and sends it to the program that we deployed.

Querying the account that stores state

We can query the state of our generated account and thus determine the output of our program using the get_account method on our connection. This is done in the count_greetings function in client/src/client.rs.

You might also like...
Rust / C / Cgo example

whatever How to build (This has been tested on Linux & macOS) Build the Rust code: $ cargo build The library is in ./target/debug/libwhatever.a Build

Example of reading the BME280 sensor on an ESP32-C3 using Rust

Rust ESP32-C3 BME280 Example of reading the BME280 sensor on an ESP32-C3 using Rust Hardware: ESP32-C3 microcontroller BME280 sensor Breadboard Jump w

An example of Brainf*** JIT-compiler with only the standard library.

jit-compiler An example of Brainf*** JIT-compiler with only the standard library. Prerequisite Rust(1.56.0-nightly or later, but it must work kind of

A basic rp2040-hal project with blinky and rtt logging example code.

A basic rp2040-hal project with blinky and rtt logging example code. With this you can quickly get started on a new rp2040 project

Example of structuring a proc macro crate for testability

testing-proc-macros Example of structuring a proc macro crate for testability. See accompanying blog post for details. License Licensed under either o

A example bevy application using bevy-kajiya for its renderer
A example bevy application using bevy-kajiya for its renderer

☀️ bevy-kajiya playground A example bevy application using bevy-kajiya for its renderer NOTE: only tested on Windows. For more context, check out the

Yet another ROS2 client library written in Rust
Yet another ROS2 client library written in Rust

RclRust Target CI Status Document Foxy (Ubuntu 20.04) Introduction This is yet another ROS2 client library written in Rust. I have implemented it inde

Luno API Client written in Rust Language for Rustaceans :)

The Luno API provides developers with a wealth of financial information provided through the Luno platform

Rust library for program synthesis of string transformations from input-output examples 🔮

Synox implements program synthesis of string transformations from input-output examples. Perhaps the most well-known use of string program synthesis in end-user programs is the Flash Fill feature in Excel. These string transformations are learned from input-output examples.

Comments
  • Doesn't build

    Doesn't build

    $ ./run.sh deploy
    BPF SDK: /home/phil/Downloads/solana-1.10.8/bin/sdk/bpf
    cargo-build-bpf child: rustup toolchain list -v
    cargo-build-bpf child: cargo +bpf build --target bpfel-unknown-unknown --release
       Compiling getrandom v0.1.16
       Compiling rayon-core v1.9.3
       Compiling generic-array v0.14.5
       Compiling toml v0.5.9
       Compiling bv v0.11.1
       Compiling serde_bytes v0.11.6
       Compiling bincode v1.3.3
    error: target is not supported, for more information see: https://docs.rs/getrandom/#unsupported-targets
       --> /home/phil/.cargo/registry/src/github.com-1ecc6299db9ec823/getrandom-0.1.16/src/lib.rs:267:9
        |
    267 | /         compile_error!("\
    268 | |             target is not supported, for more information see: \
    269 | |             https://docs.rs/getrandom/#unsupported-targets\
    270 | |         ");
        | |___________^
    
    error[E0433]: failed to resolve: use of undeclared crate or module `imp`
       --> /home/phil/.cargo/registry/src/github.com-1ecc6299db9ec823/getrandom-0.1.16/src/lib.rs:291:5
        |
    291 |     imp::getrandom_inner(dest)
        |     ^^^ use of undeclared crate or module `imp`
    
    For more information about this error, try `rustc --explain E0433`.
    error: could not compile `getrandom` due to 2 previous errors
    warning: build failed, waiting for other jobs to finish...
    error[E0599]: no method named `lock` found for struct `Stderr` in the current scope
       --> /home/phil/.cargo/registry/src/github.com-1ecc6299db9ec823/rayon-core-1.9.3/src/log.rs:266:52
        |
    266 |             let mut writer = BufWriter::new(stderr.lock());
        |                                                    ^^^^ method not found in `Stderr`
    
    For more information about this error, try `rustc --explain E0599`.
    error: build failed
    Error: Unable to open program file: No such file or directory (os error 2)
    
    
    $ cargo build-bpf --manifest-path=program/Cargo.toml --bpf-out-dir=dist/program
    BPF SDK: /home/phil/Downloads/solana-1.10.8/bin/sdk/bpf
    cargo-build-bpf child: rustup toolchain list -v
    cargo-build-bpf child: cargo +bpf build --target bpfel-unknown-unknown --release
       Compiling getrandom v0.1.16
       Compiling serde v1.0.139
       Compiling rayon-core v1.9.3
       Compiling wasm-bindgen-macro-support v0.2.81
       Compiling thiserror v1.0.31
       Compiling solana-program v1.11.2
       Compiling solana-frozen-abi-macro v1.11.2
    error: target is not supported, for more information see: https://docs.rs/getrandom/#unsupported-targets
       --> /home/phil/.cargo/registry/src/github.com-1ecc6299db9ec823/getrandom-0.1.16/src/lib.rs:267:9
        |
    267 | /         compile_error!("\
    268 | |             target is not supported, for more information see: \
    269 | |             https://docs.rs/getrandom/#unsupported-targets\
    270 | |         ");
        | |___________^
    
       Compiling bytemuck v1.10.0
    error[E0433]: failed to resolve: use of undeclared crate or module `imp`
       --> /home/phil/.cargo/registry/src/github.com-1ecc6299db9ec823/getrandom-0.1.16/src/lib.rs:291:5
        |
    291 |     imp::getrandom_inner(dest)
        |     ^^^ use of undeclared crate or module `imp`
    
    For more information about this error, try `rustc --explain E0433`.
    error: could not compile `getrandom` due to 2 previous errors
    warning: build failed, waiting for other jobs to finish...
    error[E0599]: no method named `lock` found for struct `Stderr` in the current scope
       --> /home/phil/.cargo/registry/src/github.com-1ecc6299db9ec823/rayon-core-1.9.3/src/log.rs:266:52
        |
    266 |             let mut writer = BufWriter::new(stderr.lock());
        |                                                    ^^^^ method not found in `Stderr`
    
    For more information about this error, try `rustc --explain E0599`.
    error: build failed
    
    opened by 7-- 1
Owner
zeke
zeke
a simple compiled language i made in rust. it uses intermediate representation (IR) instead of an abstract syntax tree (AST).

a simple compiled language i made in rust. it uses intermediate representation (IR) instead of an abstract syntax tree (AST).

null 4 Oct 3, 2022
The Voting example based on MoonZoon and Solana.

Voting example The Rust-only Voting example based on MoonZoon and Solana. MoonZoon is a Rust Fullstack Framework. Solana is a decentralized blockchain

Martin Kavík 26 Dec 8, 2022
Solana Escrow Program

Environment Setup Install Rust from https://rustup.rs/ Install Solana from https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-to

Nathan Ramli 0 Nov 12, 2021
01 Solana program application interface.

01 abi The abi is a repository for interfacing with the 01 program either through a rust client or through CPIs from another Solana program. Program A

Zero One Global Foundation 19 Oct 16, 2022
Minimalistic solana minter program if you don't want to use Metaplex

Minimalistic solana minter program The objective of this repository is to be only a minimalistic template for NFTs projects on Solana, so you can buil

Gabriel 23 Sep 6, 2022
A powerful minecraft bedrock software written in Rust with a powerful Typescript plugin API.

Netrex A powerful minecraft bedrock software written in RustLang. Why Netrex? It's written in Rust. Unique and straight to the point. Typescript Plugi

Netrex 51 Dec 26, 2022
Meteor Client Installer - Installer to automate the install of Fabric and Meteor Client

This is an installer that automates the install of Meteor and Fabric

Jake Priddle 3 Jun 23, 2021
NFT Marketplace with Rust on Solana

NFT Marketplace with Rust on Solana Compile and Deploy Contracts make sure you have solana installed make sure you have rust installed install phantom

null 37 Dec 16, 2022
Solana NFT Rust

HI, There Compile and Deploy Contracts make sure you have solana installed make sure you have rust installed install phantom wallet on the browser or

gSofter 4 Mar 30, 2022
An example project demonstrating integration with Rust for the ESP32-S2 and ESP32-C3 microcontrollers.

Rust ESP32 Example An example project demonstrating integration with Rust for the ESP32-S2 and ESP32-C3 microcontrollers.

Espressif Systems 303 Jan 4, 2023