From 3655b0a6309c758cf42df95c209986b21e859859 Mon Sep 17 00:00:00 2001 From: 0xCipherCoder Date: Wed, 11 Sep 2024 09:27:20 +0530 Subject: [PATCH] Native onchain development - Updated hello world lesson (#401) * Fixed codes, mistakes, links and styling * Fixed grammatical mistakes * Fixed onchain * Removed smart quotes * Update content/courses/native-onchain-development/hello-world-program.md Co-authored-by: Mike MacCana * Update content/courses/native-onchain-development/hello-world-program.md Co-authored-by: Mike MacCana * Fixed linting * Update content/courses/native-onchain-development/hello-world-program.md Co-authored-by: Mike MacCana * Update content/courses/native-onchain-development/hello-world-program.md Co-authored-by: Mike MacCana * Fixed linting * Fixed suggestions * Added details on account info and entrypoint * Updated link to client --------- Co-authored-by: Mike MacCana --- .../hello-world-program.md | 324 +++++++++--------- 1 file changed, 160 insertions(+), 164 deletions(-) diff --git a/content/courses/native-onchain-development/hello-world-program.md b/content/courses/native-onchain-development/hello-world-program.md index b928a329f..d386491c6 100644 --- a/content/courses/native-onchain-development/hello-world-program.md +++ b/content/courses/native-onchain-development/hello-world-program.md @@ -13,74 +13,72 @@ description: ## Summary -- Native Solana programs have a single **entry point** to process instructions -- A program processes an instruction using the **program_id**, list of - **accounts**, and **instruction_data** included with the instruction +- Native Solana programs have a single **entry point** to process instructions. +- A program processes an instruction using the **program_id**, a list of + **accounts**, and **instruction_data** included with the instruction. ## Lesson -The following guide assumes you are familiar with Solana program basics - if not, check out [introduction to onchain programming](/developers/courses/onchain-development/intro-to-onchain). + +The following guide assumes you are familiar with Solana program basics. If not, +check out +[Introduction to Onchain Programming](/content/courses/onchain-development/intro-to-onchain.md). -This lesson will give you a basic introduction to writing and deploying a Solana -program using the Rust programming language, without any framework. This gives -you greater control, but also requires you to perform much of the basic work of +This lesson will introduce you to writing and deploying a Solana program using +the Rust programming language without any framework. This approach offers +greater control but requires you to handle much of the foundational work of creating an onchain program yourself. -To avoid the distraction of setting up a local development environment, we'll be -using a browser-based IDE called Solana Playground. +To avoid the distractions of setting up a local development environment, we'll +be using a browser-based IDE called Solana Playground. ### Rust Basics -Before we dive into the building our "Hello, world!" program, let’s first go -over some Rust basics. If you want to dig deeper into Rust, have a look at -the [Rust language book](https://doc.rust-lang.org/book/ch00-00-introduction.html). +Before diving into building our "Hello, world!" program, let's review some Rust +basics. For a deeper dive into Rust, check out the +[Rust Language Book](https://doc.rust-lang.org/book/ch00-00-introduction.html). #### Module System Rust organizes code using what is collectively referred to as the “module -system”. - -This includes: +system.” This includes: -- **Modules** - A module separates code into logical units to provide isolated - namespaces for organization, scope, and privacy of paths -- **Crates** - A crate is either a library or an executable program. The source - code for a crate is usually subdivided into multiple modules. -- **Packages** - A package contains a collection of crates as well as a manifest - file for specifying metadata and dependencies between packages +- **Modules**: Separates code into logical units to provide isolated namespaces + for organization, scope, and privacy of paths. +- **Crates**: Either a library or an executable program. The source code for a + crate is usually subdivided into multiple modules. +- **Packages**: A collection of crates along with a manifest file that specifies + metadata and dependencies between packages. -Throughout this lesson, we’ll focus on using crates and modules. +Throughout this lesson, we'll focus on using crates and modules. -#### Paths and scope +#### Paths and Scope -Crates in Rust contain modules that define functionality which can be shared -with multiple projects. If we want to access an item within a module, then we -need to know its "path" (like when we're navigating a filesystem). +Crates contain modules that can be shared across multiple projects. If we want +to access an item within a module, we need to know its "path," similar to +navigating a filesystem. Think of the crate structure as a tree where the crate is the base and modules -are branches, each of which can have submodules or items that are additional -branches. +are branches, each potentially having submodules or items as additional +branches. The path to a particular module or item is the name of each step from +the crate to that module, separated by `::`. -The path to a particular module or item is the name of each step from the crate -to that module where each is separated by `::`. As an example, let's look at the -following structure: +For example: -1. The base crate is `solana_program` -2. `solana_program` contains a module named `account_info` -3. `account_info` contains a struct named `AccountInfo` +1. The base crate is `solana_program`. +2. `solana_program` contains a module named `account_info`. +3. `account_info` contains a struct named `AccountInfo`. The path to `AccountInfo` would be `solana_program::account_info::AccountInfo`. -Absent of any other keywords, we would need to reference this entire path to use -`AccountInfo` in our code. - -However, with the +Absent any other keywords, you would need to reference this entire path to use +`AccountInfo` in your code. However, with the [`use`](https://doc.rust-lang.org/stable/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html) -keyword we can bring an item into scope so that it can be reused throughout a -file without specifying the full path each time. It's common to see a series of -`use` commands at the top of a Rust file. +keyword, you can bring an item into scope so it can be reused throughout a file +without specifying the full path each time. It's common to see a series of `use` +commands at the top of a Rust file. ```rust use solana_program::account_info::AccountInfo @@ -88,81 +86,79 @@ use solana_program::account_info::AccountInfo #### Declaring Functions in Rust -We define a function in Rust by using the `fn` keyword followed by a function +Functions in Rust are defined using the `fn` keyword, followed by a function name and a set of parentheses. ```rust fn process_instruction() ``` -We can then add arguments to our function by including variable names and -specifying its corresponding data type within the parentheses. +We can add arguments to our function by including variable names and specifying +their corresponding data types within the parentheses. -Rust is known as a ”statically typed” language and every value in Rust is of a -certain ”data type”. This meaning that Rust must know the types of all variables -at compile time. In cases when multiple types are possible, we must add a type -annotation to our variables. +Rust is a "statically typed" language, meaning every value in Rust has a +specific "data type" known at compile time. In cases where multiple types are +possible, we must add a type annotation to our variables. In the example below, we create a function named `process_instruction` that requires the following arguments: -- `program_id` - required to be type `&Pubkey` -- `accounts` - required to be type `&[AccountInfo]` -- `instruction_data` - required to be type `&[u8]` +- `program_id` - required to be of type `&Pubkey`. +- `accounts` - required to be of type `&[AccountInfo]`. +- `instruction_data` - required to be of type `&[u8]`. Note the `&` in front of the type for each argument listed in the -`process_instruction` function. In Rust, `&` represents a ”reference” to another -variable. This allows you to refer to some value without taking ownership of it. -The “reference” is guaranteed to point to a valid value of a particular type. -The action of creating a reference in Rust is called “borrowing”. +`process_instruction` function. In Rust, `&` represents a "reference" to another +variable, allowing you to refer to some value without taking ownership of it. +The reference is guaranteed to point to a valid value of a particular type. The +action of creating a reference in Rust is called “borrowing.” In this example, when the `process_instruction` function is called, a user must pass in values for the required arguments. The `process_instruction` function -then references the values passed in by the user, and guarantees that each value -is the correct data type specified in the `process_instruction` function. +then references the values passed in by the user, guaranteeing that each value +is the correct data type specified in the function. Additionally, note the brackets `[]` around `&[AccountInfo]` and `&[u8]`. This -means that the `accounts` and `instruction_data` arguments expect “slices” of -types `AccountInfo` and `u8`, respectively. A “slice” is similar to an array -(collection of objects of the same type), except the length is not known at +indicates that the `accounts` and `instruction_data` arguments expect "slices" +of types `AccountInfo` and `u8`, respectively. A “slice” is similar to an array +(a collection of objects of the same type), except the length is not known at compile time. In other words, the `accounts` and `instruction_data` arguments expect inputs of unknown length. ```rust -fn process_instruction( +pub fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8], ) ``` -We can then have our functions return values by declaring the return type using -an arrow `->` after the function. +We can also have our functions return values by declaring the return type using +an arrow -> after the function. In the example below, the `process_instruction` function will now return a value -of type `ProgramResult`. We will go over this in the next section. +of type `ProgramResult`. We'll go over this in the next section. ```rust -fn process_instruction( +pub fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult ``` -#### Result enum +#### Result Enum -`Result` is a standard library type that represents two discrete outcomes: -success (`Ok`) or failure (`Err`). We'll talk more about enums in a future -lesson, but you'll see `Ok` used later in this lesson so it's important to cover -the basics. +`Result` is a standard library type representing two discrete outcomes: success +(`Ok`) or failure (`Err`). We'll discuss enums more in a future lesson, but +you'll see `Ok` used later in this lesson, so it's important to cover the +basics. -When you use `Ok` or `Err`, you must include a value, the type of which is -determined by the context of the code. For example, a function that requires a -return value of type `Result` is saying that the function can -either return `Ok` with an embedded string value or `Err` with an embedded -integer. In this example, the integer is an error code that can be used to -appropriately handle the error. +When using `Ok` or `Err`, you must include a value, the type of which is +determined by the code's context. For example, a function that requires a return +value of type `Result` can either return `Ok` with an embedded +string value or `Err` with an embedded integer. In this example, the integer is +an error code that can be used to handle the error appropriately. To return a success case with a string value, you would do the following: @@ -179,21 +175,20 @@ Err(404); ### Solana Programs Recall that all data stored on the Solana network are contained in what are -referred to as accounts. Each account has its own unique address which is used -to identify and access the account data. Solana programs are just a particular -type of Solana account that store and execute instructions. +referred to as accounts. Each account has its own unique address, which is used +to identify and access the account data. Solana programs are a specific type of +Solana account that stores and executes instructions. #### Solana Program Crate -To write Solana programs with Rust, we use the `solana_program` library crate. -The `solana_program` crate acts as a standard library for Solana programs. This -standard library contains the modules and macros that we'll use to develop our -Solana programs. If you want to dig deeper into the `solana_program` crate, have -a look -[at the `solana_program` crate documentation](https://docs.rs/solana-program/latest/solana_program/index.html). +To write Solana programs with Rust, we use the solana_program library crate. The +solana_program crate acts as a standard library for Solana programs. This +standard library contains the modules and macros we'll use to develop our Solana +programs. For more details, check out the +[`solana_program` crate documentation](https://docs.rs/solana-program/latest/solana_program/index.html). -For a basic program we will need to bring into scope the following items from -the `solana_program` crate: +For a basic program, we need to bring the following items from the +`solana_program` crate into scope: ```rust use solana_program::{ @@ -205,31 +200,34 @@ use solana_program::{ }; ``` -- `AccountInfo` - a struct within the `account_info` module that allows us to - access account information -- `entrypoint` - a macro that declares the entry point of the program -- `ProgramResult` - a type within the `entrypoint` module that returns either - a `Result` or `ProgramError` -- `Pubkey` - a struct within the `pubkey` module that allows us to access - addresses as a public key -- `msg` - a macro that allows us to print messages to the program log +- `AccountInfo` - A struct that allows us to access account information like + account addresses, owners, lamport balances, data length, executable status, + rent epoch, and whether the account was signed or writable in the current + transaction. +- `entrypoint` - A macro that defines a function that receives incoming + instructions and routes them to the appropriate instruction handler. +- `ProgramResult` - A type within the `entrypoint` module that returns either a + `Result` or `ProgramError`. +- `Pubkey` - A struct within the `pubkey` module that allows us to access + addresses as public keys. +- `msg` - A macro that allows us to print messages to the program log. #### Solana Program Entry Point Solana programs require a single entry point to process program instructions. -The entry point is declared using the `entrypoint!` macro. +The entry point is declared using the `entrypoint!` macro. -The entry point to a Solana program requires a `process_instruction` function +The entry point to a Solana program requires a `process_instruction` function with the following arguments: -- `program_id` - the address of the account where the program is stored -- `accounts` - the list of accounts required to process the instruction -- `instruction_data` - the serialized, instruction-specific data +- `program_id` - The address of the account where the program is stored. +- `accounts` - The list of accounts required to process the instruction. +- `instruction_data` - The serialized, instruction-specific data. ```rust entrypoint!(process_instruction); -fn process_instruction( +pub fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8], @@ -237,40 +235,40 @@ fn process_instruction( ``` Recall that Solana program accounts only store the logic to process -instructions. This means program accounts are "read-only" and “stateless”. The +instructions. This means program accounts are "read-only" and “stateless.” The “state” (the set of data) that a program requires to process an instruction is -stored in data accounts (separate from the program account). +stored in data accounts separate from the program account. -To process an instruction, the data accounts that an instruction requires must -be explicitly passed into the program through the `accounts` argument. Any -additional inputs must be passed in through the `instruction_data` argument. +To process an instruction, the data accounts required by the instruction must be +explicitly passed into the program through the `accounts` argument. Any +additional inputs must be passed in through the `instruction_data` argument. Following program execution, the program must return a value of type `ProgramResult`. This type is a `Result` where the embedded value of a success case is `()` and the embedded value of a failure case is `ProgramError`. `()` is -an empty value and `ProgramError` is an error type defined in the +an empty value, and `ProgramError` is an error type defined in the `solana_program` crate. -...and there you have it - you now know all the things you need for the -foundations of creating a Solana program using Rust. Let’s practice what we’ve -learned so far! +...and there you have it—you now know the foundations of creating a Solana +program using Rust. Let's practice what we've learned so far! ## Lab We're going to build a "Hello, World!" program using Solana Playground. Solana -Playground is a tool that allows you to write and deploy Solana programs from -the browser. +Playground is a tool that allows you to write and deploy Solana programs +directly from your browser. -#### 1. Setup +### 1. Setup -Open the [Solana Playground](https://beta.solpg.io/). Next, go ahead and delete -everything in the default `lib.rs` file and create a Playground wallet. +First, open the [Solana Playground](https://beta.solpg.io/). Once you're in, +delete all the existing code in the `lib.rs` file. Then, create a new wallet +within the Playground. ![Gif Solana Playground Create Wallet](/public/assets/courses/unboxed/hello-world-create-wallet.gif) -#### 2. Solana Program Crate +### 2. Solana Program Crate -First, let's bring into scope everything we’ll need from the `solana_program` +We'll begin by importing the necessary components from the `solana_program` crate. ```rust @@ -283,27 +281,27 @@ use solana_program::{ }; ``` -Next, let's set up the entry point to our program using the `entrypoint!` macro -and create the `process_instruction` function. The `msg!` macro then allows us -to print “Hello, world!” to the program log when the program is invoked. +Next, we'll set up the entry point of our program using the `entrypoint!` macro +and define the `process_instruction` function. We'll use the `msg!` macro to +print “Hello, world!” to the program log when the program is invoked. -#### 3. Entry Point +### 3. Entry Point ```rust entrypoint!(process_instruction); pub fn process_instruction( - program_id: &Pubkey, - accounts: &[AccountInfo], - instruction_data: &[u8] -) -> ProgramResult{ + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { msg!("Hello, world!"); Ok(()) } ``` -All together, the “Hello, world!” program will look like this: +Putting it all together, our complete “Hello, world!” program looks like this: ```rust use solana_program::{ @@ -317,78 +315,76 @@ use solana_program::{ entrypoint!(process_instruction); pub fn process_instruction( - program_id: &Pubkey, - accounts: &[AccountInfo], - instruction_data: &[u8] -) -> ProgramResult{ + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { msg!("Hello, world!"); Ok(()) } ``` -#### 4. Build and Deploy +### 4. Build and Deploy -Now let's build and deploy our program using Solana Playground. +Now, let's build and deploy our program using Solana Playground. ![Gif Solana Playground Build and Deploy](/public/assets/courses/unboxed/hello-world-build-deploy.gif) -#### 5. Invoke Program +### 5. Invoke Program -Finally, let's invoke our program from the client side. The focus of this lesson -is to build our Solana program, so we’ve gone ahead and provided -[the client code to invoke our “Hello, world!” program](https://github.com/Unboxed-Software/solana-hello-world-client) +Finally, let's invoke our program from the client side. The main focus of this +lesson is building our Solana program, so we've provided +[the client code to invoke our “Hello, world!” program](https://github.com/solana-developers/hello-world-client) for you to download. -The code provided includes a `sayHello` helper function that builds and submits -our transaction. We then call `sayHello` in the main function and print a Solana -Explorer URL to view our transaction details in the browser. - -Open the `index.ts` file you should see a variable named `programId`. Go ahead -and update this with the program ID of the “Hello, world!" program you just -deployed using Solana Playground. +This code includes a `sayHello` helper function that constructs and submits the +transaction. In the `index.ts` file, you'll find a variable named `programId`. +Update this with the program ID of the “Hello, world!” program you just deployed +using Solana Playground. ```typescript let programId = new web3.PublicKey(""); ``` -You can locate the program ID on Solana Playground referencing the image below. +You can find the program ID on Solana Playground as shown below. ![Gif Solana Playground Program ID](/public/assets/courses/unboxed/hello-world-program-id.gif) -Next, install the Node modules with `npm i`. +Next, install the Node modules by running `npm i`. -Now, go ahead and run `npm start`. This command will: +Afterwards, execute `npm start`. This command will: -1. Generate a new keypair and create a `.env` file if one does not already exist -2. Airdrop devnet SOL -3. Invoke the “Hello, world!” program -4. Output the transaction URL to view on Solana Explorer +1. Generate a new keypair and create a `.env` file if it doesn't already exist. +2. Airdrop some SOL onto this account on devnet. +3. Invoke the “Hello, world!” program. +4. Output a transaction URL that you can view on Solana Explorer. -Copy the transaction URL printed in the console into your browser. Scroll down -to see “Hello, world!” under Program Instruction Logs. +Copy the transaction URL from the console into your browser. Scroll down to the +Program Instruction Logs section to see “Hello, world!” displayed. ![Screenshot Solana Explorer Program Log](/public/assets/courses/unboxed/hello-world-program-log.png) -Congratulations, you’ve just successfully built and deployed a Solana program! +Congratulations! You've successfully built and deployed a Solana program! ## Challenge -Now it’s your turn to build something independently. Because we're starting with -very simple programs, yours will look almost identical to what we just created. -It's useful to try and get to the point where you can write it from scratch -without referencing prior code, so try not to copy and paste here. +Now it's your turn to build something independently. Since we're starting with +very simple programs, your task will closely resemble what we've just created. +The goal is to practice writing the code from scratch without referencing prior +examples, so try to avoid copying and pasting. -1. Write a new program that uses the `msg!` macro to print your own message to - the program log. -2. Build and deploy your program like we did in the lab. -3. Invoke your newly deployed program and use Solana Explorer to check that your - message was printed in the program log. +1. Write a new program that uses the `msg!` macro to print your own custom + message to the program log. +2. Build and deploy your program just like we did in the lab. +3. Invoke your newly deployed program and use Solana Explorer to confirm that + your message was printed in the program log. -As always, get creative with these challenges and take them beyond the basic -instructions if you want - and have fun! +As always, feel free to get creative with these challenges and go beyond the +basic instructions if you want — most importantly, have fun! + Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=5b56c69c-1490-46e4-850f-a7e37bbd79c2)!