Skip to main content

Anatomy of a Gno package

In this tutorial, you will learn to make a simple Counter application in Gno. We will cover the basics of the Gno language which will help you get started writing smart contracts for Gno.land.

Package Types Overview

Gno.land supports three types of packages:

  • Realms (/r/): Stateful applications (smart contracts) that maintain persistent state between transactions
  • Pure Packages (/p/): Stateless libraries that provide reusable functionality
  • Ephemeral Packages (/e/): Temporary code execution environments created with gnokey maketx run. They are designed for executing complex logic or workflows (like maketx call on steroids) but exist only during their execution

Import Rules

The import rules between different package types are:

  • Pure packages (/p/) can be imported by everything: realms, other packages, and ephemeral realms
  • Realms (/r/) can only be imported by other realms or ephemeral realms (not by pure packages)
  • Ephemeral packages (/e/) cannot be imported by anything

When importing:

  • Importing a realm gives access to its exported functions and interacts with the realm's persistent state
  • Importing a pure package gives access to its exported functions without any state persistence

Language basics

Let's dive into the Counter example.

First, we need to declare a package name.

package counter

A package is an organizational unit of code; it can contain multiple files, and as mentioned in previous tutorials, it lives on a specific package path once deployed to the network.

Next, let us declare a top level variable of type int:

package counter

var count int

In Gno, all top-level variables will automatically be persisted to the network's state after a successful transaction modifying them. Here, you can define variables that will store your smart contract's data.

In our case, we have defined a variable which will store the counter's state.

Next, let's define functions that users will be able to call to change the state of the counter:

package counter

var count int

func Increment(change int) int {
count += change
return count
}

The Increment() function has a few important features:

  • When written with the first letter in uppercase, the function is exported. This means that calls to this function from outside the counter package are allowed - be it from off-chain clients or from other Gno programs
  • It takes an argument of type int, called change. This is how the caller will provide a specific number which will be used to increment the counter
  • Returns the value of count after a successful call

Next, to make our application more user-friendly, we should define a Render() function. This function will help users see the current state of the Counter application.

In our case, we can replace the argument string with a _, signifying an unused variable. Then, we can simply return a string telling us the current value of count. For converting count to a string, we can import the strconv package from the Gno standard library, as we do when writing Go code.

info

A valid Render() function needs to have the following signature:

func Render(path string) string {
...
}

Writing unit tests

Following best practices, developers should test their Gno applications to avoid bugs and other problems down the line.

Let's see how we can write a simple test for the Increment() function.

By using the testing package from the standard library, we can access the testing.T object that exposes methods which can help us terminate tests in specific cases.

info

Common testing patterns found in Go, such as TDT, can also be used for Gno. We recommend checking out some of the many examples found online.