Gno.land Challenge Series

Welcome to the Gno.land challenge series! Gno.land is a platform for writing smart contract based on Gno, an interpreted variation of Go. The challenges in this section sit at the intersection of Blockchain and Gno programming; though we've tried to condense most of what you should know to get started straight into this README.

We recommend you to get started acquiring the tools of the trade in the "Setting up" section. After that, we'll delve into a few of the key differences of Gno and Go programming. You'll then have a glossary of key terminology and concepts that are useful to know for these challenges; you don't need to take them in all at once, but they should help you get around if you're confused.

Most of what you need should be embedded into this document; though do check out our documentation if there's anything we missed. Finally, a Gno.land engineer will be at the challenge series booth at all times; so we're here to help if you're having trouble!

Setting up

Step 1: installing gno and gnokey

gno is the command-line tool to run, test, lint (etc.) Gno programs. gnokey is the command-line tool to interact with the Gno.land blockchain.

Currently, gno requires some source files from its repository, which are not shipped in the binary tarballs. So, you'll need to compile from source; but seeing as it's just a Go program, this shouldn't take too long :)

Step 1.1: Pre-requisites

export GOPATH=$HOME/go
export PATH=$GOPATH/bin:$PATH

Step 1.2: Cloning the repository

To get started with a local Gno.land development environment, you must clone the GitHub repository somewhere on disk:

git clone https://github.com/gnolang/gno.git

Step 1.3: Installing the required tools

To install gno and gnokey, run the following make command at the root directory of the repository:

make install.gno install.gnokey

To verify your installation, try running the following:

gno --help
gnokey --help

If you need any help, come ask at the Challenge Series area, where there'll be a Gno developer to help you troubleshoot any problems!

Step 2: setting up a key

Generate a mnemonic phrase using gnokey generate:

$ gnokey recover
yellow modify budget wagon mirror ...

This is a sequence of words that form the bits used to derive your private key. You can read more in the Terminology and concepts section.

Add your private key, using the menominc, with gnokey add:

gnokey add -recover <key-name>

You should now see your newly-added key with the command gnokey list:

$ gnokey list
0. mykey (local) - addr: g1r8rklrgcce0ma0jh2nnwdc2kzrrek3vhzkjw44 pub: gpub<trimmed>, path: <nil>

You can request funds from the faucet on the website. Simply paste your address (from the gnokey list result), and you should receive the funds in your account.

The faucet serves 25GNOT at a time, which can make up to 25 transactions. You can request more funds at the same URL, simply by making a request with the given address again.

https://web.gnoland.mentats.org/faucet

Verify your account is loaded with ugnot by querying your account, using your address (replace the address after auth/accounts/):

$ gnokey query -remote https://gnoland.mentats.org:443 auth/accounts/g1r8rklrgcce0ma0jh2nnwdc2kzrrek3vhzkjw44
height: 0
data: {
  "BaseAccount": {
    "address": "g1r8rklrgcce0ma0jh2nnwdc2kzrrek3vhzkjw44",
    "coins": "99000000ugnot",
    "public_key": null,
    "account_number": "0",
    "sequence": "0"
  }
}

Step 3: make a transaction

Let's try making a few transactions. Transactions allow you to interact with the blockchain and change its contents; we delve further into what a transaction is in the "Terminology and concepts" section.

Check out the "counter" realm. This realm has a simple counter variable in its state, which can by incremented by any user by making a transaction. When somebody increases it, their address is stored within the realm's state as well. But you don't have to take our word for it: the web interface has two important buttons in the top-right corner: [source], and [help].

[source] allows you to browse the realm's source. By clicking on counter.gno, you can see the short source code for this realm.

// Realm counter is a simple realm implementing a counter which can be
// arbitrarily increased by any user.
package counter

import (
    "gno.land/p/demo/ufmt"
    "std"
)

var (
    counter         uint64
    lastIncreasedBy std.Realm
)

// Increment increments the counter, and saves the "increaser" into lastIncreasedBy.
func Increment() string {
    counter++
    // We use PrevRealm to get the caller of this function. The caller may be an
    // end user (through a `call` transaction) or another code realm -- see the
    // `addpkg` example!
    lastIncreasedBy = std.PrevRealm()
    return Render("")
}

// Render creates the "render" function; this returns content for the page formatted as markdown;
// and it is shown when browsing the realm through the web interface.
func Render(string) string {
    return ufmt.Sprintf(
        "Counter: %d\n\nLast increased by: %s (pkgPath: %s)",
        counter, lastIncreasedBy.Addr(), lastIncreasedBy.PkgPath(),
    )
}

func init() {
    // Perform first increment when publishing the package!
    Increment()
}

Note: you may not be familiar with the packages being used here. That's because Gno has its own standard library (where the "std" package exists) and its own packages (these are not normal URLs, like Go, but rather paths on the blockchain). We don't currently have an equivalent to pkg.go.dev (we're working on it!) but we do have the gno doc command line tool; so you can inspect packages and symbols using commands like gno doc std, gno doc ufmt, and gno doc ufmt.Sprintf.

Let's take a look at how we can interact with this realm; and hack our way around it.

Step 3.1: calling a function with a call transaction

The first thing we're going to try is making a call transaction. If you click on the [help] button, you'll access an interactive page that generates the gnokey command you'll need.

Fill out the "My address" input with the address name you picked above, then copy the command (in the "INSECURE BUT QUICK" section) for the Increment(...) call. It should should look something like this (broken down for readability):

gnokey maketx call \
    -pkgpath "gno.land/r/demo/counter" \
    -func "Increment" \
    -gas-fee 1000000ugnot \
    -gas-wanted 2000000 \
    -send "" \
    -broadcast \
    -chainid "dev" \
    -remote "https://gnoland.mentats.org:443" \
    mykey

Here's everything that is going on:

After making this transaction, if you visit the realm's web page, you should see the counter increased, together with your address.

Step 3.2: executing a script with a run transaction

But let's say you wanted to execute Increment() many times, so that you could, for instance, increase the counter 100 times.

One way to do it is by making a call transaction. By all means, that is possible; however it will end up costing a lot in terms of transaction fees. This is why Gno.land has a nice tool up its sleeve, which will be helpful to let you solve many of the challenges you encounter!

Copy and paste the following file into a script.gno in your working directory:

package main

import (
    "gno.land/r/demo/counter"
)

func main() {
    for i := 0; i < 100; i++ {
        counter.Increment()
    }
}

Then, use maketx run to execute this script against the blockchain:

gnokey maketx run \
    -gas-fee 1000000ugnot \
    -gas-wanted 10_000_000 \
    -broadcast \
    -remote "https://gnoland.mentats.org:443" \
    mykey script.gno

This will be uploaded onto the blockchain and the main function will be executed as if you're running it with gno run (like go run).

Note how the realm gno.land/r/demo/counter can simply be imported and called like any import; it can act both as an end-user "RPC API", simply using its exported Gno functions, but it also works when imported by other code, and persists its state the same way!

Step 3.3: extra: adding a new package/realm with an addpkg transaction

You can skip this step if you want to jump straight to the challenges.

There's one more transaction that is useful to know: the addpkg transaction. This allows you to upload a directory to the chain, and add it as a new "pure" package or realm. (See below in "Gno programming" for the distinction)

Let's make the previous script into a callable realm. Make sure to adapt <CHANGE_ME> and the key name, as usual.

# Create the "myrealm" directory, and load the script to upload
mkdir myrealm
cat << EOF > myrealm/myrealm.gno
package main

import (
    "gno.land/r/demo/counter"
)

func IncrementBy(n int) {
    for i := 0; i < n; i++ {
        counter.Increment()
    }
}
EOF

# Add it to the chain. (Change <CHANGE_ME> to your name/online handle!)
gnokey maketx addpkg \
    -pkgpath gno.land/r/<CHANGE_ME>/myrealm \
    -pkgdir ./myrealm \
    -gas-fee 1000000ugnot \
    -gas-wanted 10_000_000 \
    -broadcast \
    -remote "https://gnoland.mentats.org:443" \
    mykey

You should now be able to browse to the /r/<CHANGE_ME>/myrealm path on the web interface (just change the URL manually). And you should be able to construct a maketx call to increment the counter by as much as you want! Well, not really. You'll still be constrained by your -gas-wanted; so in this following example, we bumped it up to 10M.

gnokey maketx call \
    -pkgpath "gno.land/r/<CHANGE_ME>/myrealm" \
    -func "IncrementBy" \
    -gas-fee 1000000ugnot \
    -gas-wanted 10000000 \
    -send "" \
    -broadcast \
    -chainid "dev" \
    -args "100" \
    -remote "https://gnoland.mentats.org:443" \
    cs

Note the addition of the -args flag - this is used to pass function arguments. On the web interface, you can modify it using the input field right next to "params".

One last note: if you visit the counter realm, you'll see the "last increased by" line will now contain the path of your realm, as well as a different realm:

Last increased by: g10jy763d3p0p27f9qcylmat07w4zd46swq79e48 (pkgPath: gno.land/r/<CHANGE_ME>/myrealm)

This is because std.PrevRealm, as previously mentioned, will return the code realm's information - its own address and import path. (The address is actually a "hash" of the pkgPath -- see gno doc std.DerivePkgAddr).

Step 4: time to solve some challenges!

A good place to start are the "basics" challenges. They'll get you some hands-on experience with some of the concepts we've talked about!

Gno programming

There is an Effective Gno document which delves into most of the good practices and differences between Go and Gno. Here we recap some of the most important:

Editor support

Still very work-in-progress. There is a vscode extension. There are some barebones set ups for vim and emacs on our CONTRIBUTING guide.

The Gno developer writing this guide generally works with Vim with the gofumpt setup, with a terminal always on the side to look up symbols using gno doc. If writing long programs, it can be useful to start off writing as a Go file (with all the Go tooling); then changing the extension to .gno and adapting it.

Playground and Gnodev

You can use the gno playground to quickly test, execute and share Gno programs.

If you're wishing to work on a realm with a quick "feedback loop", you can use gnodev (installable from the repository, using the top-level make command make install.gnodev). This offers a similar experience for realm development as the one in the front-end world is given by npm run dev (hot-reloading, state recovery...).

Terminology and concepts

You don't need much more than Go knowledge to solve most of the challenges, but when using the gnokey command (see Setting Up) to interact with the blockchain, you may wonder what all these flags are doing. It is recommended you skim through this list and use it as a reference to look up concepts when you need them when solving the challenges.

[^1]: A realm is actually a wider concept, and this is a simplification; however this is what we generally mean when using the word for the purpose of the challenge series. [^2]: Often, coins are also called "tokens". In Gno.land, we distinguish between coins and tokens; read more in the docs.