designing seamless interconnected dApps with gno

BUIDL Europe, 9 Jan 2025, Lisbon

Manfred Touron

VP Eng., gno.land

bonjour, BUIDL!

package main

func main() {
    println("Hello, BUIDL! It's Manfred.")
}
2

introduction

3

what is gno?

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(1)
}

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

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

guestbook.gno

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

why gno?

the gnovm enables:

seamless interoperability of

untrusted user programs

written in a good language

9

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)
}
10

packages and realms

11

gno grc20 package

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

type Teller interface {
    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
}

type Token struct {
    name, symbol string
    decimals     uint
    ledger       *PrivateLedger
}

type PrivateLedger struct {
    totalSupply          uint64
    balances, allowances avl.Tree
}

func NewToken(name, symbol string, decs uint) (*Token, *PrivateLedger) { /*...*/ }
12

gno buidl20 realm

// https://gno.land/r/moul/x/buidl20$source
package buidl20

import (
    "std"
    "gno.land/p/demo/grc/grc20"
)

// Token: public safe object for composability
// adm:   privileged object for minting
var Token, adm = grc20.NewToken("Buidl", "BDL", 4) 

// grc20 API for the caller
var UserTeller = Token.CallerTeller()

func init() {
     adm.Mint(1_000_000, std.GetOrigCaller()) // mint 1M to the contract deployer.  
}

// optional helpers
// [...]
13

gno safe objects

14

gno grc20reg realm

// gno.land/r/demo/grc20reg
package grc20reg 

var registry = avl.NewTree() // rlmPath -> TokenGetter

func Register(tokenGetter grc20.TokenGetter) {
    rlmPath := std.PrevRealm().PkgPath()
    registry.Set(rlmPath, tokenGetter)
}

func Get(key string) grc20.TokenGetter {
    tokenGetter, _ := registry.Get(key)
    return tokenGetter
}
15

gno grc20factory realm

// gno.land/r/demo/grc20factory
package grc20factory

var instances avl.Tree // symbol -> instance

type instance struct {
    token  *grc20.Token
    ledger *grc20.PrivateLedger
    admin  *ownable.Ownable
    faucet uint64 // per-request amount. disabled if 0.
}

func New(name, symbol string, decimals uint, initialMint, faucet uint64) {
    caller := std.PrevRealm().Addr()
    token, ledger := grc20.NewToken(name, symbol, decimals)
    if initialMint > 0 {
        ledger.Mint(admin, initialMint)
    }
    owner := ownable.NewWithAddress(admin)
    inst := instance{token: token, ledger: ledger, admin: owner, faucet: faucet}
    instances.Set(symbol, &inst)
    grc20reg.Register(token.Getter(), symbol)
}
16

gno full type safety

// Solidity example for ERC721Receiver check
interface IERC721Receiver {
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

// Solidity relies on checking bytes4 signature
require(
    contract.onERC721Received.selector == bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")),
    "Invalid ERC721 receiver"
);
17

gno minidex

package dex

func (dex *DEX) PlaceOrder(tokenFrom, tokenTo grc20.Token, 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)
}
18

gno pausable closures

19

gno govdao proposal

// prop1.gno

import "gno.land/r/gov/dao"
import "gno.land/r/buidl/buidl20"

func init() {
    closure := func() error {
        // this closure will preserve the execution context at the time of
        // creation, even if the proposal is executed by someone else.
        return executor()
    }
    prop := dao.ProposalRequest{
        Title:       "",
        Description: "lorem ipsum dolor sit amet",
        Executor:    closure,
    }
    dao.Propose(prop)
}

func executor() error {
  buidl20.TransferTo("g12345678", 1_000_000) // transfer 1M $BUIDL
  return nil
}
20

gno composition

21

a gno DAO interface

// gno.land/p/dao
package dao

type DAO interface {
    // Core proposal operations
    Propose(def PropDefinition) (Proposal, error)
    GetProposal(proposalID uint64) (Proposal, error)
    Execute(proposalID uint64) error

    // List operations
    ActiveProposals() PropList
    ArchivedProposals() PropList
    Len() int
}
22

a gno DAO implementation

package foodao

import "gno.land/p/dao" // interface

// FooDAO implements the dao.DAO interface.
type FooDAO struct { proposals []dao.Proposal, voters []std.Address }

func (d *FooDAO) Propose(def PropDefinition) (Proposal, error) { /* [...] */ }
func (d *FooDAO) GetProposal(proposalID uint64) (Proposal, error) { /* [...] */ }
func (d *FooDAO) Execute(proposalID uint64) error { /* [...] */ }
func (d *FooDAO) ActiveProposals() PropList { /* [...] */ }
func (d *FooDAO) ArchivedProposals() PropList { /* [...] */ }
func (d *FooDAO) Len() int { /* [...] */ }
23

a gno DAO aggregator

package aggregateddao

import "gno.land/p/dao"

type AggregatedDAO struct { dao1, dao2 dao.DAO }

func (a *AggregatedDAO) Propose(def dao.PropDefinition) (dao.Proposal, error) {
    prop1, _ := a.dao1.Propose(def)
    prop2, _ := a.dao2.Propose(def)
    // [...]
}

func (a *AggregatedDAO) Len() int { return a.dao1.Len() + a.dao2.Len() }

func (a *AggregatedDAO) ActiveProposals() dao.PropList {
    return append(a.dao1.ActiveProposals(), a.dao2.ActiveProposals()...)
}

func (a *AggregatedDAO) ArchivedProposals() dao.PropList {
    return append(a.dao1.ArchivedProposals(), a.dao2.ArchivedProposals()...)
}

// [...]
24

a gno DAO composition

// gno.land/r/foobardao
package foobardao

import (
  "gno.land/r/foodao"
  "gno.land/r/bardao"
  "gno.land/p/aggregateddao"
)

var FooBarDAO = aggregateddao.New(foodao.DAO, bardao.DAO)
25

DAO compositions ideas

26

road ahead

thank you!

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.)