Gno: Examples and Comparisons

Nebular, 13 Jul 2024, Brussels

Manfred Touron

VP Eng., Gno.land

Bonjour, Nebular!

package main

import "fmt"

func main() {
    fmt.Println("Hello, Nebular! It's Manfred.")
}
2

What is Gno?

3

Gno - Features and Limitations

4

gno hello world

package hello

func Hello() string {
    return "hello world"
}
5

counter.gno - persistency and Render()

package counter

var Counter int

func Inc() {
    Counter += 1
}

func Render(path string) string {
    return "My Super Counter: " + Counter
}
6

counter.gno - private helpers

package counter

import "std"

var (
    Counter    int
    LastCaller std.Address
)

func Inc() int {
    return addToCounter(amount)
}

func Add(amount int) int {
    return addToCounter(amount)
}

func addToCounter(amount int) int {
    Counter += amount
    LastCaller = std.GetOrigCaller()
    return Counter
}
7

gno guest book

package guest

import "std"

var messages avl.Tree // std.Address -> string (message)

func AddMessage(message string) {
    caller := std.GetOrigCaller()
    if _, ok := messages.Get(caller); ok {
        panic("this user already post a message")
    }
    messages.Set(caller, message) // add message to our messages list
}

func Render(path string) string {
    var view string
    for _, message := range messages {
        view = view + "\n" + message // add message to the render
    }
    return view
}
8

import "std"

package std // import "std"

func AssertOriginCall()
func Emit(typ string, attrs ...string)
func GetChainID() string
func GetHeight() int64
func IsOriginCall() bool
type Address string
    func DerivePkgAddr(pkgPath string) Address
    func GetOrigCaller() Address
type Banker interface{ ... }
    func GetBanker(bt BankerType) Banker
type Coin struct{ ... }
    func NewCoin(denom string, amount int64) Coin
type Coins []Coin
    func GetOrigSend() Coins
    func NewCoins(coins ...Coin) Coins
type Realm struct{ ... }
    func CurrentRealm() Realm
    func PrevRealm() Realm
9

Why Gno?

The GnoVM enables

seamless interoperability of

untrusted user programs

written in a good language.

10

alice.gno, bob.gno

package alice

var x int

func GetX() int {
    return x
}

func SetX(n int) {
    x = n
}
package bob

import "alice"

func IncrAlice() {
    x := alice.GetX()
    alice.SetX(x + 1)
}
11

users.gno - register

package users

var (
    addressToUsers avl.Tree // address -> user
    usersToAddress avl.Tree // user -> address
)

func Register(name string) {
    caller := std.GetOrigCaller()
    if usersToAddress.Has(name) || addressToUsers.Has(caller) {
        panic("address/name already registered")
    }

    usersToAddress.Set(name, std.GetOrignCaller())
    addressToUsers.Set(std.GetOrignCaller(), name)
}
12

users.gno - admin invite

package users

var admin = "g1xxxxx..."

var invites avl.Tree // std.Address -> true

func AdminSendInvite(address std.Address) {
    if caller := std.GetOrigCaller(); caller != admin {
        panic("unauthorized")
    }
    invites.Set(address, true)
}

func Register() {
    caller := std.GetOrigCaller()
    if !invites.Has(caller) {
        panic("caller hasn't been invited")
    }
    // ... register caller
    invites.Remove(address)
}
13

tamagotchi.gno - struct

package tamagochi

// Tamagotchi structure
type Tamagotchi struct {
    hunger    int
    happiness int
    health    int
}

func (t *Tamagotchi) Feed() {
    t.updateStats()
    if t.dead() {
        return
    }
    t.hunger = bound(t.hunger-10, 0, 100)
}

func (t *Tamagotchi) Heal() { /* ...  */ }

func (t *Tamagotchi) Play() { /* ...  */ }
14

tamagotchi.gno - render

package tamagochi

var tam Tamagochi

// Interaction function ...

func Render(path string) string {
    switch {
    case tam.Health() == 0:
        return "😵" // dead face
    case tam.Health() < 30:
        return "😷" // sick face
    case tam.Happiness() < 30:
        return "😢" // sad face
    case tam.Hunger() > 70:
        return "😫" // hungry face
    default:
        return "😃" // happy face
    }
}
15

grc20/types.gno

Implement a GRC20 token standard similar to ERC20 in Solidity.

// gno.land/p/demo/grc/grc20
package grc20

import (
    "std"

    "gno.land/p/demo/grc/exts"
)

type GRC20 interface {
    exts.TokenMetadata
    TotalSupply() uint64
    BalanceOf(account std.Address) uint64
    Transfer(to std.Address, amount uint64) error
    Allowance(owner, spender std.Address) uint64
    Approve(spender std.Address, amount uint64) error
    TransferFrom(from, to std.Address, amount uint64) error
}
16

bar20.gno

package bar20

import "gno.land/r/demo/grc20reg"

var Bank, adm = grc20.NewBank("Bar", "BAR", 4)

var UserBanker = grc20.PrevRealmBanker(Bank)

func init() {
    grc20reg.Register(Bank, "")
}

func Faucet() string {
    caller := std.GetOrignCaller()
    if err := adm.Mint(caller, 1_000_000); err != nil {
        return "error: " + err.Error()
    }
    return "OK"
}
17

grc20registry

package grc20reg

import (
    "std"

    "gno.land/p/demo/grc/grc20"
)

var registry = avl.NewTree() // rlmPath[.slug] -> *grc20.Bank

func Register(bank *grc20.Bank, slug string) {
    rlmPath := std.PrevRealm().PkgPath()
    key := fqname.Construct(rlmPath, slug)
    registry.Set(key, bank)
    std.Emit(registerEvent, "pkgpath", rlmPath, "slug", slug)
}

func Get(key string) *grc20.Bank {
    bank, ok := registry.Get(key)
    if !ok {
        return nil
    }
    return bank.(*grc20.Bank)
}
18

wugnot.gno

package wugnot

import (
    "std"
    "strings"

    "gno.land/p/demo/grc/grc20"
    "gno.land/p/demo/ufmt"
    pusers "gno.land/p/demo/users"
    "gno.land/r/demo/grc20reg"
    "gno.land/r/demo/users"
)

var Bank, adm = grc20.NewBank("wrapped GNOT", "wugnot", 0)

func Deposit() {
    caller := std.PrevRealm().Addr()
    sent := std.GetOrigSend()
    amount := sent.AmountOf("ugnot")
    require(uint64(amount) >= ugnotMinDeposit, ufmt.Sprintf("Deposit below minimum: %d/%d ugnot.", amount, ugnotMinDeposit))
    checkErr(adm.Mint(caller, uint64(amount)))
}

// Withdraw...
19

grc20factory.gno

package foo20

import (
    "std"
    "strings"

    "gno.land/p/demo/grc/grc20"
    "gno.land/r/demo/grc20reg"
)

var instances avl.Tree // symbol -> instance

func New(name, symbol string, decimals uint, initialMint, faucet uint64) {
    admin := std.PrevRealm().Addr()
    bank := grc20.NewBank(name, symbol, decimals)
    bank.Mint(admin, initialMint)
    inst := instance{bank: bank, admin: ownable.NewWithAddress(admin), faucet: faucet}
    instances.Set(symbol, &inst)
    grc20reg.Register(bank, symbol)
}

func Bank(symbol string) *grc20.Bank {
    return mustGetInstance(symbol).bank
}
20

minidex - PlaceOrder

package dex

func (dex *DEX) PlaceOrder(tokenFrom, tokenTo *grc20.Bank, amount uint64, isBuy bool) int {
    trader, contract := std.PrevRealm().Addr(), std.CurrentRealm().Addr()
    userBanker := grc20.AccountBanker(tokenFrom, "")

    allowance := userBanker.Allowance(trader, contract)
    require(allowance >= amount, "insufficient allowance")
    err := userBanker.TransferFrom(trader, contract, amount)
    checkErr(err, "cannot retrieve tokens from allowance")

    order := &Order{trader: trader, tokenFrom: tokenFrom, tokenTo: tokenTo, amount: amount, isBuy: isBuy}
    dex.Append(order)
    std.Emit(
        "order_placed",
        "trader", trader.String(),
        "tokenFrom", tokenFrom.GetName(),
        "tokenTo", tokenTo.GetName(),
        "amount", ufmt.Sprintf("%d", amount),
    )

    return dex.matchPairOrders(tokenFrom, tokenTo)
}
21

minidex - MatchOrders

package dex

func (dex *DEX) matchPairOrders(tokenFrom, tokenTo *grc20.Bank) int {
    matched := 0
    orders.Iterate("", "", func(key1 string, value interface{}) bool {
        orders.Iterate("", "", func(key2 string, value2 interface{}) bool {
            if order1.isBuy != order2.isBuy && order1.tokenFrom == order2.tokenTo && order1.tokenTo == order2.tokenFrom {
                amount := min(order1.amount, order2.amount)
                order1.amount -= amount
                order2.amount -= amount
                banker1 := grc20.AccountBanker(order1.tokenFrom, "")
                banker2 := grc20.AccountBanker(order2.tokenFrom, "")
                banker1.Transfer(order2.trader, amount)
                banker2.Transfer(order1.trader, amount)
                matched++
                std.Emit("trade_executed" /*...*/)
            }
        })
    })
    return matched
}
22

dao.gno

package dao

// Voter defines the needed methods for a voting system
type Voter interface {
    // IsAccepted indicates if the voting process had been accepted
    IsAccepted(voters []std.Address) bool

    // IsFinished indicates if the voting process is finished
    IsFinished(voters []std.Address) bool

    // Vote adds a new vote to the voting system
    Vote(voters []std.Address, caller std.Address, flag string)

    // Status returns a human friendly string describing how the voting process is going
    Status(voters []std.Address) string
}
23

Boards Post

package board

type Post struct {
    id       int
    Author   std.Address
    Name     string
    Comments []Post
}

type Board struct {
    posts []Post
}

func (b *Board) Post(name, body string) Post {
    caller := std.GetOrigCaller()
    post := Post{len(b.posts), caller, name, body}
    b.posts = append(b.posts, post)
    return post
}
24

Board - comment

package board

type Post struct{}

type Board struct{}

func (b *Board) Post(name, body string) Post

func (b *Board) Comment(id string, body string) {
    if id < 0 || id >= len(b.posts) {
        panic("invalid id")
    }
    caller := std.GetOrigCaller()

    post := &b.posts[id]
    comment := Post{len(post.Comment), caller, "", body}
    post.Comment = append(post.Comment, post)
}
25

Blog - Post

package blog

import "time"

type Post struct {
    id       int
    Date     time.Time
    Name     string
    Comments []Post
}

var postid int

var posts []Post

func Post(name, body string) Post {
    post := Post{postid, time.Now(), name, body}
    posts = append(posts, post)
    postid++
    return post
}
26

Blog - Admin

package blog

type Post struct{}

var (
    adminAuthor string = std.GetOrginCaller() // Set deployer as admin
    postid      int
    posts       []Post
)

func Post(name, body string) Post {
    // Assert caller is admin
    caller := std.GetOrigCaller()
    if caller != adminAuthor {
        panic("unauthorized")
    }

    // Add post ...
}
27

Thank you

Manfred Touron

VP Eng., Gno.land

Use the left and right arrow keys or click the left and right edges of the page to navigate between slides.
(Press 'H' or navigate to hide this message.)