DEV Community

Cover image for Soroban Quest - Hello World
Altuğ Bakan
Altuğ Bakan

Posted on • Updated on

Soroban Quest - Hello World

On the last post, we booted up our GitPod environment and now are ready to tackle the first Soroban Quest: Hello World!

Understanding README.md

Let's begin questing by opening the quests folder in our Explorer. We can see multiple quests existing here. Since we will tackle the first quest right now, let's open the 1-hello-world folder by clicking on it. Then, select README.md for more information about what to do on this quest.

Hello World README

We can see that we have to use our Stellar Quest account to deploy and invoke the 1 Hello World contract on the Stellar Futurenet. What does any of these words mean? Let's check them out.

Setting Up Our Stellar Quest Account

If we missed it, or are using another workspace, we should use sq login to login to our Stellar Quest account. This way, our progress can be saved to our account, and we can get sweet NFTs to our connected wallet. If the 1-hello-world folder is missing, we can use sq pull to get the newest copy of the repository from GitHub.

Pull? From where?

sq pull uses git underneath to "pull" the newest changes from the soroban quest repository to our local workspace. git is a version control program with excellent remote and local repository synchronization support, so that's what is happening here. sq pull accomplishes it in three steps:

  1. Uses git stash to save our changes into a stash, so that collisions between the remote repository and our repository do not occur.
  2. Uses git pull to pull the changes from the soroban quest repository.
  3. Uses git stash pop to reapply the saved changes to our workspace.

It is pretty useful for saving our work on the workspace, while having the ability to add new stuff!

After setting up our account and everything, we can use sq play 1 to start questing! We should accept the funding by the prompt, as we will need some Futurenet Lumens for transacting. If you accidentally pressed no, you can use sq fund [account], with account being the public key given to you for the quest. Don't forget to remove the brackets!

Examining the Contract

On the src folder, we can see two files: lib.rs and test.rs. lib.rs contains the source code of the Soroban contract given on this quest. Let's begin checking out the code.

// We don't include the standard library to minimize compiled
// size. We also import a few macros and types we need from the
// `soroban_sdk`.
#![no_std]
use soroban_sdk::{contractimpl, symbol, vec, Env, Symbol, Vec};

pub struct HelloContract;

// Our `HelloContract` implementation contains only one function,
// `hello()`. This function will receive a `to` argument, and
// return a Vec made up of "Hello" and the supplied `to` value.
#[contractimpl]
impl HelloContract {
    pub fn hello(env: Env, to: Symbol) -> Vec<Symbol> {
        vec![&env, symbol!("Hello"), to]
    }
}

// This `mod` declaration inserts the contents of `test.rs` into
// this file.
mod test;
Enter fullscreen mode Exit fullscreen mode

The Soroban contracts are written in Rust, which is the hottest programming language at the moment. If you are not familiar with Rust, no worries! The contracts are extremely well commented, and it is quite understandable if you are experienced with another programming language. Let's dive deeper into the contract's code.

What is Rust and Why is it Everywhere?

Rust is a systems programming language, which is known by it's memory safety. In addition to being memory safe, it is also blazingly fast, which makes it a popular programming language amongst programmers. The memory safety aspect is important, since 70% of the CVEs are caused by memory safety issues. So, to write a fast and secure code, a different approach than C and C++, must be employed. That's why Rust is everywhere!

#![no_std]
use soroban_sdk::{contractimpl, symbol, vec, Env, Symbol, Vec};

pub struct HelloContract;
Enter fullscreen mode Exit fullscreen mode

The #![no_std] line tells the Rust compiler to not include the standard library, as it is quite large and is not fully supported by the Soroban environment. The useful components are already implemented in the soroban_sdk, so we don't need to include the standard library! The second line tells the compiler to use the implementations of contractimpl, symbol, vec, Env, Symbol, and Vec from the soroban_sdk library. We will use these lines almost every time when we are developing a Soroban contract. The next line tells the Soroban Runtime that our contract's name is HelloContract. After these definitions, we can check the implementation of the contract.

#[contractimpl]
impl HelloContract {
    pub fn hello(env: Env, to: Symbol) -> Vec<Symbol> {
        vec![&env, symbol!("Hello"), to]
    }
}
Enter fullscreen mode Exit fullscreen mode

The implementations of Soroban contracts are marked with #[contractimpl] to tell the compiler to do the right thing. In our contract implementation of the HelloContract, we can see that we have a public function, denoted with pub fn. Public functions are functions on a contract which are invokable by everyone using the Soroban network. The hello function has two arguments, and returns a Vector of Symbols. A Symbol is a string-like value, which consists of at most 10 characters. A Vector can be defined as an array with the capacity to grow indefinitely.

The first input argument is env of type Env. This parameter can be found on every functiion that deals with the state of the Soroban network. It is generally used in each function, as it supplies some additional functionality. The env argument is not supplied by the invoker of the function, and is added automatically on function invocation.

The second input argument is to of type Symbol. Symbols can be assumed as strings of a maximum length of 10. They provide cost and space efficiency, and are widely used for small data.

The function returns a Vector, using the macro vec!. It returns a vector with three elements, which are &env, the symbol "Hello", and the symbol to, which is supplied by the invoker of the function. The &env parameter is the reference of the Soroban environment, and is not explicitly returned to the user.

mod test;
Enter fullscreen mode Exit fullscreen mode

The last line inserts the code content of the test.rs file to the lib.rs file.

Examining the Test

Now, let's examine the test.rs file. Examining the tests are important to see how the contract operates. A well tested contract is more secure than a non-tested contract, and security is one of the most important concerns when dealing with contracts on a blockchain.

#![cfg(test)]

use super::*;
use soroban_sdk::{symbol, vec, Env};

// The purpose of this file is to run automated tests on the 
// contract code we've written in `lib.rs`. Writing tests can be
// quite a big topic, and we'll dive in further in a future quest. 
// Just you wait!
#[test]
fn test() {
    // we register the contract in a Soroban environment, and
    // build a client we can use to invoke the contract
    let env = Env::default();
    let contract_id = env.register_contract(None, HelloContract);
    let client = HelloContractClient::new(&env, &contract_id);

    // Next, we call `client.hello()`, supplying "Dev" as our `to`
    // argument.
    let words = client.hello(&symbol!("Dev"));

    // We assert the contract must return a Vec that matches what 
    // we would expect to receive from our contract: 
    // [Symbol("Hello"), Symbol("Dev")]
    assert_eq!(words, vec![&env, symbol!("Hello"), symbol!("Dev"),]);
}
Enter fullscreen mode Exit fullscreen mode

The test is also written in Rust, and is used to test the functionality of the HelloContract on a local environment.

#![cfg(test)]

use super::*;
use soroban_sdk::{symbol, vec, Env};
Enter fullscreen mode Exit fullscreen mode

The first line configures the file as a test environment. The second line, use super::*;, imports everything from the parent module, which is the HelloContract file here. As before, the third line imports some implementations from the soroban_sdk.

#[test]
fn test() {
    let env = Env::default();
    let contract_id = env.register_contract(None, HelloContract);
    let client = HelloContractClient::new(&env, &contract_id);

    let words = client.hello(&symbol!("Dev"));

    assert_eq!(words, vec![&env, symbol!("Hello"), symbol!("Dev"),]);
}
Enter fullscreen mode Exit fullscreen mode

The test implementations are marked with #[test], and allows the Rust compiler to understand that these are testing functions. We start with creating a constant, env, and initializing with the default value. Next, we are registering our HelloContract to the environment. Now, we have a local Soroban sandbox with only HelloContract existing. We then create a client to interact with the contract of ours for testing.

We invoke the function hello on our HelloContract by using the hello method of our client using the "Dev" argument. See that we are not supplying the env parameter, as it is implicitly included already. We then set the reply of the function to the words constant.

After doing our invocation, we assert the contract must return a Vec, which consists of the elements &env, "Hello", and our input argument, "Dev". If that is the case, the test ends without an error, which means that our implementation is correct.

Testing Tips

When testing software, we use the AAA (Arrange, Act, and Assert) principle. First, we "Arrange" the environment, which we did here using the first three lines. Then, we "Act" on our source code, which corresponds to invoking our hello function on our contract. Finally, we "Assert" that everything is correct by comparing the actual result to the expected result. This way, testing software can be done systematically.

Compiling, Testing, Deploying

We can compile our code using cargo build to compile our rust codes into .wasm files. If we want to build only a specific quest's contract, the usage is

cargo build --package [package-name] --target wasm32-unknown-unknown --release
Enter fullscreen mode Exit fullscreen mode

where the package name of a quest can be found in the Cargo.toml file on the quest's folder. For example, the Hello World quest's package name is soroban-hello-world-contract, so we can build it using

cargo build --package soroban-hello-world-contract --target wasm32-unknown-unknown --release
Enter fullscreen mode Exit fullscreen mode

The created object can be found in target/wasm32-unknown-unknown/release/soroban_hello_world_contract.wasm file. We can also use cargo test, to see if our implementations contains any error.

Testing Hello World

Everything looks great! We now can deploy our Soroban contract to the Futurenet using soroban deploy command. Please be careful that you are using the CLI - Futurenet: bash terminal to successfully accomplish this task. The full command is

soroban deploy --wasm target/wasm32-unknown-unknown/release/soroban_hello_world_contract.wasm
Enter fullscreen mode Exit fullscreen mode

We should see a success message with the contract's ID. If you are getting errors when deploying, wait for the Soroban client to synchronize to the Futurenet, which can take some time. If you are still getting errors, try using the handy command sq rpc -c to change the default RPC endpoint to an official one.

Deploying Soroban Contract

Great! We will need to supply this ID to interact with our deployed contract. We can use a different contract ID to interact with another contract. The possibilities are endless!

We can now invoke the hello function using soroban invoke to get a reply from our contract. We should again use the Futurenet CLI for this purpose.

soroban invoke --id [your contract id] --fn hello --arg [any string argument shorter than 10 characters]
Enter fullscreen mode Exit fullscreen mode

The invocation should return a success and the Vector that contains the "Hello" word followed by the argument we supplied.

Soroban Contract Invocation

Everything works perfectly, and we have completed the first quest! Well done! Use sq check 1 to claim your reward 🎉.

How Long Will My Contract Live?

Forever! That's the beauty of the blockchains. The immutability and the censorship resistance are what makes blockchains great. This power is a double edged sword though, if we make any mistakes, or have any vulnerabilities in our contract, there's nothing we can do! That's why testing and auditing smart contracts are an important business.

Note that this is the Futurenet, which is a test server for Soroban contracts. It often gets reset, so your Soroban contract will cease to exist. This will not happen when Soroban hits the mainnet!

Top comments (3)

Collapse
 
altug profile image
Altuğ Bakan • Edited

Hope you enjoyed this post!

If you want to support my work, you can send me some XLM at

GB2M73QXDA7NTXEAQ4T2EGBE7IYMZVUX4GZEOFQKRWJEMC2AVHRYL55V
Stellar Wallet

Thanks for all your support!

Collapse
 
tyvdh profile image
Tyler van der Hoeven

This is a really great write up. Well done! 👏

(I did notice a little miss on a link at the top though)
[git](https://git-scm.com/)

Collapse
 
altug profile image
Altuğ Bakan

Thanks!

Also thanks for the heads up on the error, I fixed it!