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
- Git
make
(for running Makefiles)- Go 1.21+
- Go Environment Setup:
- Make sure
$GOPATH
is well-defined, and$GOPATH/bin
is added to your$PATH
variable. - To do this, you can add the following line to your
.bashrc
,.zshrc
or other config file:
- Make sure
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.
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 topkg.go.dev
(we're working on it!) but we do have thegno doc
command line tool; so you can inspect packages and symbols using commands likegno doc std
,gno doc ufmt
, andgno 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:
-pkgpath
and-func
make up together the function we're actually calling. You can read more in the below "Gno programming" section on how import paths (often referred to as "pkg paths") are constructed in Gno.-gas-fee
and-gas-wanted
specify the transaction fee and the gas we need to perform the transaction. These are explained in our terminology; generally, you'll only need to modify the-gas-wanted
if you get an error message saying you are out of gas. You can bump it up to 100 million; you can also use dashes for readability:2_000_000
.-send
allows you to make a "deposit" to a realm; this is generally used for functions which require a "fee" to prevent abuse. You won't need to change this, and can omit this flag normally.-broadcast
performs automatic signing and broadcasting of a transaction. Transactions can be performed in a three-stage process, allowing users to separate the machine on which they create and sign the transaction (which may be offline) and the one where it is sent to the chain. Without the flag, the transaction JSON object is simply generated and printed on stdout. (Note that this is why the command falls under the "INSECURE BUT QUICK" section; the most secure way to generate a transaction is generating it and signing it on an offline machine, then transferring it to an online machine to broadcast it).-chainid
is a security mechanism. It exists so that a message by a user on one blockchain cannot be replayed on another one (which will have a different chain ID). This is always the same, and for convenience if omitted the default will always be "dev" (so, you can generally omit it).-remote
specifies the RPC endpoint of the validator (explained below). This is the server that will be propagating your transaction to the rest of the chain and adding it to its own set of transactions.
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:
- There is a difference between "realm" packages and "pure" packages. The first
have a import path starting with
gno.land/r/
; the second start withgno.land/p/
.- If a realm declares a global variable, it is persistent, as you have seen in the previous "counter" example.
- A pure package cannot persist data after intialization, and it is most useful for code that serves as a library for building other libraries or realms.
- Only realms can be called using
maketx call
, thus they are the most useful for "application logic". - Standard libraries are pure packages (as such, their state cannot be modified by transactions).
- Realms act as their own "entities"; as such, they have their own "banker"
interface (which allows them to manage their coins), and change the
value of
std.PrevRealm
if they call an external realm. - Pure packages cannot import realms.
- Gno lacks the following Go features:
- Goroutines and channels.
- Complex data types (
complex64
andcomplex128
). - Pointer types (
uintptr
andunsafe.Pointer
). - Generics, and some Go 1.18+ features (like
any
, andmin
/max
). - Many standard libraries; most notably
unsafe
,reflect
,os
,runtime
, and those that depend onreflect
likefmt
andencoding/json
. Here is a full list
- Gno's
int
anduint
are always 64-bit, independent of the running machine's bit size. - A
panic
in a realm rolls back the transaction. Any changes to the state are reverted. Thus, panics are much more common in Gno than Go. avl.Tree
is an implementation of a binary tree; it scales better when storing millions of entries and loading them at run-time (ie. using less "gas").
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.
- Blockchain: really, just a distributed database, where the operations that
can be performed are clearly defined by the blockchain nodes' source code.
Every stateful action on the blockchain is a transaction, which may be
financial (ie. Alice sends 100 coins to Bob); or calling a function of
a realm (ie. executing code).
- Block: a collection of transactions, performed at a single point in time. Once a validator creates a new block, all other validators check the signatures and correctness of all the transactions; and if correct, the block is officially part of the "block chain". It contains the hash of the previous block's data, hence requiring a strict order. A new block is created within regular time intervals; in Gno.land's case, it's 5 seconds (compare this to Bitcoin, which is 10 minutes on average).
- Transaction: a transaction is a stateful operation on the blockchain. It
always involves some form of transaction fees to perform the
operation.
- Transaction fees: these are distributed to the validators and, in Gno.land's case, also to the chain's developers (this is what we call "Proof of Contribution", as opposed to the more widely-known Proof of Stake and Proof of Work, what Ethereum and Bitcoin use, respectively). The fees will be higher for transactions which take more computational time or storage.
- Gas: gas is a measure of the "cost" of a transaction. In Gno.land's case, it is a combination of the Virtual Machine cycles and any read/write operations to the database. The higher the operation count, the higher the gas fee. Gas is the baseline factor in determining the transaction fees.
- Call transaction: this is a transaction to make a function call on-chain. From the web interface, you can use the "help" page to browse a realm's exported functions and construct a call transaction.
- Run transaction: this is a transaction that takes in a Gno file and
executes the
main()
function definied within the file; similar togo run
(andgno run
). - Send transaction: this transaction allows you to send coins to another address.
- Coin:[^2] a quantifiable asset that is associated with an address on the
blockchain. For instance, the blockchain knows that Alice and Bob have
each 100 GNOT; so it will allow Alice to send to Bob up to 100 GNOT, but not
any more, as it would exceed what she holds.
- GNOT: this is Gno.land's "native coin", and it is used to pay for the transaction fees; it is also what is re-distributed to the validators and to the developers, consequently. There can be other coins on the chain, named differently.
- Subdivisions: in code, we actually talk about a single, indivisible
unit: the
ugnot
(ie. µgnot). As the µ prefix suggests, 1 GNOT = 1,000,000 UGNOT. Subdivisions exist also in Bitcoin and Ethereum, where they're known as "Satoshi" and "Gwei" respectively.
- Faucet: when testing and working in development contexts in general, we generally set up what are known as "faucets"; these are websites from which you can request tokens to be sent to a specified address. They are essentially very simple: they just make a send transaction to a specified address, using a pre-funded wallet with a lot of GNOT.
- Key pair: to access the Gno.land chain, you will need to set up a key pair
in
gnokey
, as we'll explain below. This is a cryptographic private key derived from a mnemonic, which in turn has a public key and an address.- Mnemonic: this is a sequence of words, generally 12 or 24, which are used to derive private keys. This is randomly generated, and you generally only need to use one to generate private keys.
- Address: your public key generates an address which uniquely identifies your key pair in the blockchain. When performing a send transaction, you're specifying the destination using their address.
- Realm: on other blockchains, these are generally known as "smart
contracts".[^1] They are programs running on the blockchain. They generally have
restricted capabilities compared to normal executable programs.
- Determinism: given the same inputs, the program will yield always the
same output. This is a characteristic that is necessary for blockchain
programming, as the result of a transaction cannot be different from
one validator to the next. For this reason, filesystem access and
network access is not allowed; nor is access to system time and
packages like
crypto/rand
. - State persistance: because of determinism, we cannot access the filesystem. To allow the creation of complex programs, Gno.land will save the value of a realm's global variables into its database and recover it when a new function of the realm is called.
- Source code: to execute the programs on-chain, at least a compiled bytecode must be uploaded on-chain, so that all the validators know what to run. Gno.land takes this a step further by only accepting Gno programs in the form of raw source code, allowing users to inspect the code of everything that exists on-chain.
- Determinism: given the same inputs, the program will yield always the
same output. This is a characteristic that is necessary for blockchain
programming, as the result of a transaction cannot be different from
one validator to the next. For this reason, filesystem access and
network access is not allowed; nor is access to system time and
packages like
- Validators: these are nodes of the network which have the job to
"validate" transactions. On Bitcoin, a similar concept is known as a
"miner" (but the word only makes sense in Proof of Work systems). A validator is
capable of receiving transactions and sending them to the other validators;
it takes turns with other validators to create the new blocks in the
blockchain. Validation here refers to validating the transaction signatures,
and ensuring that the transaction is correct and can be performed (for
instance, that Alice does have the coins she wants to send to Bob).
- RPC interface: a validator communicates with the outside world using
an RPC interface. This is what we'll be using today to use
gnokey
to interact with the blockchain; under the hood, it's a simple HTTP/JSON-RPC API. URL: https://gnoland.mentats.org - Web interface: on top of the RPC interface, web interfaces can be made. Blockchain information can be publicly read and inspected without incurring in any transaction fees; so for solving the challenges, we suggest you use the web interface from time to time to aid you. URL: https://web.gnoland.mentats.org
- Render functions: if a realm defines a
func Render(string) string
, then this function can be used to render content on the web interface. The input is a path provided in the URL; and the output is then formatted as markdown in the web render.
- RPC interface: a validator communicates with the outside world using
an RPC interface. This is what we'll be using today to use
[^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.