Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ on:
# any change to gnovm code can make examples fail
- gnovm/**
- examples/**
- misc/deployments/**
- contribs/gnogenesis/**
workflow_dispatch:
inputs:
debug:
Expand Down Expand Up @@ -67,6 +69,28 @@ jobs:
modulepath: "examples"
go-version: "1.23.x"

#deployments:
# name: Validate deployments
# runs-on: ubuntu-latest
# timeout-minutes: 15
# steps:
# - uses: actions/checkout@v5
# - uses: actions/setup-go@v5
# with:
# go-version: "1.23.x"
# - name: Run deployment generate & test
# run: |
# for dir in misc/deployments/*/; do
# if [ -f "$dir/Makefile" ] && make -C "$dir" -n generate >/dev/null 2>&1; then
# echo "=== $dir ==="
# make -C "$dir" generate
# if make -C "$dir" -n test >/dev/null 2>&1; then
# make -C "$dir" test
# fi
# make -C "$dir" clean
# fi
# done

mod-tidy:
strategy:
fail-fast: false
Expand Down
54 changes: 24 additions & 30 deletions examples/gno.land/r/gov/dao/v3/loader/loader.gno
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
// loader.gno initialises the govDAO v3 implementation and tier structure.
//
// It intentionally does NOT add any members or set AllowedDAOs. When the
// allowedDAOs list in the DAO proxy is empty, InAllowedDAOs() returns true
// for any caller (see r/gov/dao/proxy.gno), which lets a subsequent MsgRun
// bootstrap the member set and then lock things down.
//
// Bootstrap flow (official network genesis or local dev):
//
// 1. All packages — including this loader — are deployed via MsgAddPackage.
// The loader sets up tier entries and the DAO implementation.
// 2. A MsgRun executes a setup script (e.g. govdao_prop1.gno) which:
// a. Adds a temporary deployer as T1 member (for supermajority).
// b. Creates a governance proposal to register validators, votes YES,
// and executes it.
// c. Adds the real govDAO members directly via memberstore.Get().
// d. Removes the temporary deployer.
// e. Calls dao.UpdateImpl to set AllowedDAOs, locking down access.
//
// See misc/deployments/ for concrete genesis generation examples.
package loader

import (
Expand All @@ -6,41 +26,15 @@ import (
"gno.land/r/gov/dao/v3/memberstore"
)

// this is only executed when loaded into genesis
func init() {
// Create tier entries in the members tree (required before any SetMember).
memberstore.Get().SetTier(memberstore.T1)
memberstore.Get().SetMember(memberstore.T1, address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj"), &memberstore.Member{InvitationPoints: 3}) // Jae
memberstore.Get().SetMember(memberstore.T1, address("g1manfred47kzduec920z88wfr64ylksmdcedlf5"), &memberstore.Member{InvitationPoints: 3}) // Manfred
memberstore.Get().SetMember(memberstore.T1, address("g1e6gxg5tvc55mwsn7t7dymmlasratv7mkv0rap2"), &memberstore.Member{InvitationPoints: 3}) // Milos
memberstore.Get().SetMember(memberstore.T1, address("g18amm3fc00t43dcxsys6udug0czyvqt9e7p23rd"), &memberstore.Member{InvitationPoints: 3}) // Marc
memberstore.Get().SetMember(memberstore.T1, address("g1mx4pum9976th863jgry4sdjzfwu03qan5w2v9j"), &memberstore.Member{InvitationPoints: 3}) // Ray
memberstore.Get().SetMember(memberstore.T1, address("g1m0rgan0rla00ygmdmp55f5m0unvsvknluyg2a4"), &memberstore.Member{InvitationPoints: 3}) // Morgan
memberstore.Get().SetMember(memberstore.T1, address("g1ker4vvggvsyatexxn3hkthp2hu80pkhrwmuczr"), &memberstore.Member{InvitationPoints: 3}) // Sergio
memberstore.Get().SetMember(memberstore.T1, address("g1aeddlftlfk27ret5rf750d7w5dume3kcsm8r8m"), &memberstore.Member{InvitationPoints: 3}) // Antoine
memberstore.Get().SetMember(memberstore.T1, address("g1hy6zry03hg5d8le9s2w4fxme6236hkgd928dun"), &memberstore.Member{InvitationPoints: 3}) // Jerónimo
memberstore.Get().SetMember(memberstore.T1, address("g1lckl8j2g3jyyuq6fx7pke3uz4kemht7lw4fg5l"), &memberstore.Member{InvitationPoints: 3}) // Danny
memberstore.Get().SetMember(memberstore.T1, address("g1778y2yphxs2wpuaflsy5y9qwcd4gttn4g5yjx5"), &memberstore.Member{InvitationPoints: 3}) // Michelle
memberstore.Get().SetMember(memberstore.T1, address("g125em6arxsnj49vx35f0n0z34putv5ty3376fg5"), &memberstore.Member{InvitationPoints: 3}) // Leon

memberstore.Get().SetTier(memberstore.T2)
memberstore.Get().SetMember(memberstore.T2, address("g1jazghxvvgz3egnr2fc8uf72z4g0l03596y9ls7"), &memberstore.Member{InvitationPoints: 2}) // Nemanja
memberstore.Get().SetMember(memberstore.T2, address("g1dfr24yhk5ztwtqn2a36m8f6ud8cx5hww4dkjfl"), &memberstore.Member{InvitationPoints: 2}) // Antonio
memberstore.Get().SetMember(memberstore.T2, address("g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay"), &memberstore.Member{InvitationPoints: 2}) // Dongwon
memberstore.Get().SetMember(memberstore.T2, address("g17n4y745s08awwq4e0a38lagsgtntna0749tnxe"), &memberstore.Member{InvitationPoints: 2}) // Jinwoo
memberstore.Get().SetMember(memberstore.T2, address("g1mpkp5lm8lwpm0pym4388836d009zfe4maxlqsq"), &memberstore.Member{InvitationPoints: 2}) // Alexis
memberstore.Get().SetMember(memberstore.T2, address("g197q5e9v00vuz256ly7fq7v3ekaun5cr7wmjgfh"), &memberstore.Member{InvitationPoints: 2}) // Salvo

memberstore.Get().SetTier(memberstore.T3)
memberstore.Get().SetMember(memberstore.T3, address("g1qynsu9dwj9lq0m5fkje7jh6qy3md80ztqnshhm"), &memberstore.Member{InvitationPoints: 1}) // Rémi
memberstore.Get().SetMember(memberstore.T3, address("g1mq7g0jszdmn4qdpc9tq94w0gyex37su892n80m"), &memberstore.Member{InvitationPoints: 1}) // Alan
memberstore.Get().SetMember(memberstore.T3, address("g14u5eaheavy0ux4dmpykg2gvxpvqvexm9cyg58a"), &memberstore.Member{InvitationPoints: 1}) // Norman
memberstore.Get().SetMember(memberstore.T3, address("g1sw5xklxjjuv0yvuxy5f5s3l3mnj0nqq626a9wr"), &memberstore.Member{InvitationPoints: 1}) // Albert
memberstore.Get().SetMember(memberstore.T3, address("g1ckae7tc5sez8ul3ssne75sk4muwgttp6ks2ky9"), &memberstore.Member{InvitationPoints: 1}) // ByeongJun
memberstore.Get().SetMember(memberstore.T3, address("g127l4gkhk0emwsx5tmxe96sp86c05h8vg5tufzq"), &memberstore.Member{InvitationPoints: 1}) // Maxwell
memberstore.Get().SetMember(memberstore.T3, address("g19p3yzr3cuhzqa02j0ce6kzvyjqfzwemw3vam0x"), &memberstore.Member{InvitationPoints: 1}) // Guilhem

// Set the DAO implementation. AllowedDAOs is intentionally left empty
// so that the genesis MsgRun can manipulate the memberstore directly.
dao.UpdateImpl(cross, dao.UpdateRequest{
DAO: impl.GetInstance(),
AllowedDAOs: []string{"gno.land/r/gov/dao/v3/impl"},
DAO: impl.GetInstance(),
})
}
47 changes: 13 additions & 34 deletions examples/gno.land/r/sys/names/verifier.gno
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
// Package names provides functionality for checking of package deployments
// by users registered in r/sys/users are done to proper namespaces.
// Package names enforces namespace permissions for package deployment.
// Only address-prefix (PA) namespaces are allowed.
package names

import (
"strings"

"gno.land/p/nt/ownable"

"gno.land/r/sys/users"
)
import "gno.land/p/nt/ownable/v0"

var (
Ownable = ownable.NewWithAddress("g1manfred47kzduec920z88wfr64ylksmdcedlf5") // dropped in genesis via Enable. XXX We should switch to something better once the GovDAO situation is stabilized.

Ownable = ownable.NewWithAddressByPrevious("g1edq4dugw0sgat4zxcw9xardvuydqf6cgleuc8p") // genesis deployer — dropped in genesis via Enable.
enabled = false
)

// IsAuthorizedAddressForNamespace ensures that the given address has ownership of the given name.
// A user's name found in r/sys/users is equivalent to their namespace.
// IsAuthorizedAddressForNamespace checks if the given address can deploy to the given namespace.
// Only the address's own PA namespace is permitted.
func IsAuthorizedAddressForNamespace(address_XXX address, namespace string) bool {
return verifier(enabled, address_XXX, namespace)
}
Expand All @@ -26,7 +19,7 @@ func IsAuthorizedAddressForNamespace(address_XXX address, namespace string) bool
// The namespace check is disabled initially to ease txtar and other testing contexts,
// but this function is meant to be called in the genesis of a chain.
func Enable(cur realm) {
if err := Ownable.DropOwnershipByPrevious(); err != nil {
if err := Ownable.DropOwnership(); err != nil {
panic(err)
}
enabled = true
Expand All @@ -36,32 +29,18 @@ func IsEnabled() bool {
return enabled
}

// verifier checks the store to see that the
// user has properly registered a given name/namespace.
// This function considers as valid an `address` that matches the `namespace` (PA namespaces)
func verifier(enabled bool, address_XXX address, namespace string) bool {
if !enabled {
// verifier checks namespace deployment permissions.
// An address matching the namespace is the only allowed case.
func verifier(isEnabled bool, address_XXX address, namespace string) bool {
if !isEnabled {
return true // only in pre-genesis cases
}

if strings.TrimSpace(address_XXX.String()) == "" || strings.TrimSpace(namespace) == "" {
if namespace == "" || !address_XXX.IsValid() {
return false
}

// Allow user with their own address as namespace
// This enables pseudo-anon namespaces
// ie gno.land/{p,r}/{ADDRESS}/**
if address_XXX.String() == namespace {
return true
}

// Can be a registered namespace or an alias
userData, _ := users.ResolveName(namespace)
if userData == nil || userData.IsDeleted() {
return false
}

/// XXX: add check for r/sys/teams down the line

return userData.Addr() == address_XXX
return address_XXX.String() == namespace
}
34 changes: 14 additions & 20 deletions examples/gno.land/r/sys/names/verifier_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,40 @@ package names
import (
"testing"

"gno.land/p/nt/ownable"
"gno.land/p/nt/testutils"
"gno.land/p/nt/uassert"
"gno.land/p/nt/urequire"
"gno.land/r/sys/users"
"gno.land/p/nt/ownable/v0"
"gno.land/p/nt/testutils/v0"
"gno.land/p/nt/uassert/v0"
)

var alice = testutils.TestAddress("alice")

func TestDefaultVerifier(t *testing.T) {
// Check disabled, any case is true
// Disabled: any case is true
uassert.True(t, verifier(false, alice, alice.String()))
uassert.True(t, verifier(false, "", alice.String()))
uassert.True(t, verifier(false, alice, "somerandomusername"))

// Check enabled
// username + addr mismatch
uassert.False(t, verifier(true, alice, "notregistered"))
// PA namespace check
// Enabled: PA namespace check
uassert.True(t, verifier(true, alice, alice.String()))

// Empty name/address
uassert.False(t, verifier(true, address(""), ""))

// Register proper username
testing.SetRealm(testing.NewCodeRealm("gno.land/r/gnoland/users/v1")) // authorized write
urequire.NoError(t, users.RegisterUser(cross, "alice", alice))
// Enabled: non-PA namespaces denied
uassert.False(t, verifier(true, alice, "notregistered"))
uassert.False(t, verifier(true, alice, "alice"))

// Proper namespace
uassert.True(t, verifier(true, alice, "alice"))
// Enabled: empty name/address
uassert.False(t, verifier(true, address(""), ""))
uassert.False(t, verifier(true, alice, ""))
uassert.False(t, verifier(true, address(""), "something"))
}

func TestEnable(t *testing.T) {
testing.SetRealm(testing.NewUserRealm("g1manfred47kzduec920z88wfr64ylksmdcedlf5"))
testing.SetRealm(testing.NewUserRealm("g1edq4dugw0sgat4zxcw9xardvuydqf6cgleuc8p"))

uassert.NotPanics(t, func() {
Enable(cross)
})

// Confirm enable drops ownerships
// Confirm enable drops ownership
uassert.Equal(t, Ownable.Owner().String(), "")
uassert.AbortsWithMessage(t, ownable.ErrUnauthorized.Error(), func() {
Enable(cross)
Expand Down
18 changes: 15 additions & 3 deletions gno.land/cmd/gnoland/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ type startCfg struct {
dataDir string
lazyInit bool

logLevel string
logFormat string
logLevel string
logFormat string
earlyStart bool
}

func newStartCmd(io commands.IO) *commands.Command {
Expand Down Expand Up @@ -163,6 +164,13 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) {
false,
"flag indicating if lazy init is enabled. Generates the node secrets, configuration, and genesis.json",
)

fs.BoolVar(
&c.earlyStart,
"x-early-start",
false,
"[experimental] start RPC and P2P before genesis time, deferring only consensus",
)
}

func execStart(ctx context.Context, c *startCfg, io commands.IO) error {
Expand Down Expand Up @@ -262,7 +270,11 @@ func execStart(ctx context.Context, c *startCfg, io commands.IO) error {
}

// Create a default node, with the given setup
gnoNode, err := node.DefaultNewNode(cfg, genesisPath, evsw, logger)
opts := []node.Option{}
if c.earlyStart {
opts = append(opts, node.WithEarlyStart())
}
gnoNode, err := node.DefaultNewNode(cfg, genesisPath, evsw, logger, opts...)
if err != nil {
return fmt.Errorf("unable to create the Gnoland node, %w", err)
}
Expand Down
36 changes: 9 additions & 27 deletions gno.land/pkg/integration/testdata/addpkg_namespace.txtar
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
loadpkg gno.land/r/sys/names
loadpkg gno.land/r/gnoland/users/v1

adduser admin
adduser gui

patchpkg "g1manfred47kzduec920z88wfr64ylksmdcedlf5" $admin_user_addr # use our custom admin
patchpkg "g1edq4dugw0sgat4zxcw9xardvuydqf6cgleuc8p" $admin_user_addr # use our custom admin

gnoland start

Expand Down Expand Up @@ -37,47 +36,30 @@ gnokey query vm/qeval --data "gno.land/r/sys/names.IsEnabled()"
stdout 'true bool'


# Try to add a pkg an with unregistered user
# Try to add a pkg with a user on someone else's address as namespace
# gui addpkg -> gno.land/r/<addr_test1>/one
! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$test1_user_addr/one -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test gui
stderr 'is not authorized to deploy packages to namespace'

# Try to add a pkg with an unregistered user, on their own address as namespace
# Try to add a pkg on own address as namespace (PA namespace)
# gui addpkg -> gno.land/r/<addr_gui>/one
gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$gui_user_addr/one -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test gui
stdout 'OK!'

## Test unregistered namespace
## Test non-PA namespace

# Call addpkg with admin user on gui namespace
# Call addpkg with admin user on arbitrary namespace
# admin addpkg -> gno.land/r/guiland/one
# This is expected to fail at the transaction simulation stage, which is why we set gas-wanted to 1.
! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guiland/one -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test admin
stderr 'is not authorized to deploy packages to namespace'

## Test registered namespace

# test gui register namespace
# gui call -> gnoland/users/v1.Register
gnokey maketx call -pkgpath gno.land/r/gnoland/users/v1 -func Register -send "1000000ugnot" -gas-fee 1000000ugnot -gas-wanted 13000000 -broadcast -chainid=tendermint_test -args 'guigui123' gui
stdout 'OK!'

# Test gui publishing on guigui123/one
# gui addpkg -> gno.land/r/guigui123/one
gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guigui123/one -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test gui
stdout 'OK!'

# Test admin publishing on guigui123/two
# admin addpkg -> gno.land/r/guigui123/two
! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guigui123/two -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test admin
stderr 'is not authorized to deploy packages to namespace'

! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/random!name/mypkg -gas-fee 1000000ugnot -gas-wanted 1000000 -broadcast -chainid=tendermint_test gui
# Invalid package path characters
! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/random!name/mypkg -gas-fee 1000000ugnot -gas-wanted 550000 -broadcast -chainid=tendermint_test gui
stderr 'but got "gno.land/r/random!name/mypkg"'

# Test that publishing to filetests is prohibited
! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guigui123/filetests -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test gui
stderr 'but got "gno.land/r/guigui123/filetests" ending in filetests'
! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$gui_user_addr/filetests -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test gui
stderr 'ending in filetests'

-- gnomod.toml --
module = "gno.land/r/mypkg"
Expand Down
Loading
Loading