Solana Programming Primer. Solana meets Rust (without Anchor) | by Alec Chen | Apr, 2022

Photograph by Shubham Dhage on Unsplash

I wrote this in February 2022 form of final minute to offer further steerage/assets throughout Illini Blockchain’s Solana Onboarding.

It accompanied the echo project from the Bounce Crypto x Solana Labs x Pyth Bootcamp I attended in January 2022.

Figured I’d put this on the market as a public report. This outlines the fundamentals of Solana program improvement written in Rust, with out the favored framework Anchor.

Fundamental understanding of the Solana Programming Model.
Fundamental Rust (by example).

This can be a fundamental instance of what a Solana venture would come with.

venture/
js/
program/
src/
entrypoint.rs
error.rs
instruction.rs
lib.rs
processor.rs
state.rs
exams/
Cargo.toml

To briefly clarify the primary parts:

  • js/ — consumer scripts for testing.
  • entrypoint.rs — wrapper on processor.rs offering an entry level on the blockchain.
  • error.rs — customized error definitions.
  • instruction.rs — enum definitions denoting completely different directions and their parameters.
  • lib.rs — exports modules for simple entry in different crates.
  • processor.rs — definition of precise operating code.
  • state.rs — struct definitions for account information serialization.
  • Cargo.toml — outline crate metadata and dependencies.
  1. Modify program and deploy.
cargo build-bpf // builds your program - be sure to're in your program's root listingsolana program deploy [PATH_TO_DOT_SO_FILE]// instance: solana program deploy /Customers/alecchen/Paperwork/Code/solana-onboarding/echo-skeleton/program/goal/deploy/echo.so

2. Run consumer script to check.

// javascript
node [SCRIPT_FILENAME]
// instance: node index.js

3. Shopper will both succeed and print a hyperlink to the transaction on a block explorer, or it would spit out the hash of the failed transaction and also you copy and paste it right into a block explorer.

// instance success output:
<https://explorer.solana.com/tx/85CVVw4CrmNbzqCXEmRDF3LEUFEymc4s1pUtDK1urcV6R3jbRjdJNWGjb24Trbt8bZjN5fJkqA55GjNvoBLubfE?cluster=devnet>
Echo Buffer Textual content: asdfff
Success

// instance fail output:
Error: Transaction 4MDQWBNkuRAf59gjqgoFBDLq76UPVS73LUktTP1TJ8op1Qtvw2UsxcRw6tMHKo5mjsWEaZWC5CLePtV44YTFsUA3 failed ("err":"InstructionError":[1,"InvalidInstructionData"])
at sendAndConfirmTransaction (/Customers/alecchen/Paperwork/Code/solana-onboarding/echo-skeleton/js/node_modules/@solana/web3.js/lib/index.cjs.js:2981:11)
at processTicksAndRejections (node:inner/course of/task_queues:96:5)
at async foremost (/Customers/alecchen/Paperwork/Code/solana-onboarding/echo-skeleton/js/index.js:75:14)
// copy 4MDQWBNkuRAf59gjqgoFBDLq76UPVS73LUktTP1TJ8op1Qtvw2UsxcRw6t right into a block explorer (be sure to're on devnet!)

Solana’s block explorers are extremely useful for debugging! Spend a while exploring every thing that’s outputted. It is going to present a number of useful info, together with account inputs, their writable/signer flags, SOL and spl-token stability modifications, directions, and sub-instructions referred to as, in addition to program logs the place you’ll discover print statements and error messages.

4. Repeat steps 1–3.

While you implement Solana program directions, you’re code will sometimes compose of three foremost elements:

  1. Getting accounts and deserialization
  2. Account validation
  3. Logic and performance

Accounts are handed in as an array of AccountInfos. To work with these you’ll outline an iterator and retrieve references to the accounts.

However how are you aware which accounts are which? You’ll want to specify someplace what accounts you anticipate, after which 1) it’s as much as the consumer to move in the right amount and order of accounts and a couple of) it’s as much as you as this system developer to make sure that your program solely runs in case you obtain the right accounts (this half is the account validation portion). You must discover within the echo skeleton that the accounts handed in throughout the instruction definition on the consumer match these anticipated/outlined in instruction.rs.

Anyway, onto deserialization. By default, account information is simply an array of bytes. When you anticipate an account’s information to reach in a sure construction, you may deserialize it to entry that information in an organized manner. You have to to outline that construction beforehand, and embrace a way of deserializing bytes into it (these strategies are often outlined as traits in Rust). SPL packages typically have deserialization strategies utilizing the Pack trait. In case you are defining your individual structs, Borsh is a library that has predefined strategies for widespread information sorts.

Instance with an spl-token program mint account:

Instance with our personal outlined struct:

As talked about beforehand, you might be counting on shoppers to move in accounts matching what you anticipate. This opens the chance for shoppers to move in accounts which may mess up the performance of your code.

For instance, for instance you will have a fundamental swap program, the place this system expects these 4 accounts (amongst others): your vault for token a, your vault for token b, the swap program’s vault for token a, and the swap program’s vault for token b. It takes some quantity of token a out of your vault, deposits in its personal vault, then takes some quantity of token b from its personal vault and deposits it into your vault. Now, what in case you offered your individual token a vault once more instead of the swap program’s vault? If it didn’t validate the accounts, then it might deposit token a into your vault, and also you’d get token b without cost! An instance in actual Solana packages: the Wormhole exploit in early February was brought on by means of a deprecated helper operate that didn’t validate accounts correctly.

The commonest conditions the place you’ll be validating accounts is invalidating accounts together with sure information, guaranteeing an account is a sure PDA and checking {that a} program is a sure program.

Validating account information

For validating information, there are two elements:

  1. ensuring an account could be deserialized correctly
  2. guaranteeing that information matches sure constraints.

An instance of this might be ensuring the person sending the transaction is definitely the proprietor of a token account that they wish to switch tokens from.

From right here on out I’ll omit the imports and getting accounts sections to make issues much less cluttered.

Validating PDAs

One other widespread technique to validate accounts is with PDAs. If an account is a PDA, you may guarantee it’s the fitting account by discovering the PDA your self and checking if the keys match. You are able to do this in two methods: 1) in case you solely have the seeds and never the bump, find_program_address and a couple of) in case you have each the bump and the seeds, create_program_address.

Checking program ids

All packages are accounts, and as , in case you’re program is interacting with a sure account, it have to be handed into the instruction. So in case your program calls one other program, shoppers should move that program account in.

This opens up a safety difficulty. Now a consumer can move in a program that implements the identical instruction that your program is making an attempt to entry, however as an alternative appends some malicious habits. For that reason, we have now to verify that this system accounts handed in are the right ones.

Usually you’ll be working with well-known packages, and they’re going to have public Rust crates that expose some form of id() operate that permits you to simply verify, i.e. Solana native packages, SPL packages. Different instances it’s possible you’ll simply should onerous code this system id.

assert_msg helper

A pleasant helper operate I used to be launched to on the bootcamp is assert_msg. While you throw an error, it’ll solely print out the message related to that kind of error. You would possibly use the identical error in numerous contexts, by which a further message could be useful for debugging. assert_msg does precisely that.

After you’ve ensured your accounts are all right, you may really write code that does stuff. Right here you are able to do something your coronary heart wishes, however a method or one other you’ll probably be doing both/each of the next two issues:

  1. Modifying account information
  2. Calling different packages

Modifying account information

Two methods to switch an accounts information: 1) modify the bytes instantly, and a couple of) outline a struct and serialize the info into the accounts array of bytes.

Modifying instantly:

Serializing information (with Borsh):

Cross-program invocations

Usually in packages, you write you’ll should name different packages on the blockchain. For these you’ll primarily use two capabilities: [invoke](<https://docs.rs/solana-program/1.6.4/solana_program/program/fn.invoke.html>) for normal cross-program calls, and [invoke_signed](<https://docs.rs/solana-program/1.6.4/solana_program/program/fn.invoke_signed.html>) for cross-program calls the place PDAs must signal.

They’re primarily the identical apart from invoke_signed you move within the seeds (with the bump appended) for the PDA.

Examples of the 2 can be found here.

You’ll discover the primary argument takes an Instruction kind. Like all directions, it consists of accounts, a program id, and enter information.

Nevertheless, as you see within the examples, typically packages could have wrapper capabilities for creating directions which makes it a lot simpler for us to take action. You’ll be able to see find out how to create a uncooked directions by inspecting their supply code.

More Posts