PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/p/moul/pilot/v0

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v0
Namespace
moul / pilot
Files
3 (README)(gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/moul/pilot/v0
gno
0.9

Files (3)

  • README.mdmarkdown
  • gnomod.tomltoml
  • pilot.gnogno
pilot.gnogno
1// Package pilot is a realm-driven account: one realm holds the funds and the2// identity, a key pilots it, and its powers are separate realms installed3// afterwards without ever redeploying it. The gno answer to a Gnosis Safe4// with modules.5//6// An account realm keeps a *Pilot private and hands modules a narrow7// [Account] handle. Everything privileged stays on *Pilot, which is never8// returned, so a module can only reach the two methods it needs.9//10// Two ways to delegate, and they differ in exactly one property:11//12//   - A [Purse] is revocable. Every method on it re-enters the declaring13//     package and re-checks the live roster and budget, so Revoke and14//     SetBudget take effect immediately, even on a purse a module retained.15//   - A sub-identity token (rlm.Sub) is permanent. It lets the module act as16//     "<account>#<subpath>" toward any other realm, which a purse cannot do,17//     but the module can mint a banker from it and keep it forever. Removing18//     the module does not take that back; only emptying the sub-address does.19//     Grant one only to code you have read.20//21// Live instance: r/moul/pilot. Demo module: r/moul/x/pilotdemo.22package pilot2324import (25	"chain"26	"chain/banker"27	"errors"28	"strings"2930	"gno.land/p/nt/avl/v0"31	"gno.land/p/nt/ufmt/v0"32)3334// Module is implemented by a module realm and installed into an account.35// The account never imports it: it learns the module only as this interface.36//37// Run is threaded (the leading int keeps it out of crossing-function38// territory, which a /p/ package may not declare). rlm is whatever the39// account chose to delegate: its own sub-identity token for a trusted40// module, or a zero value when the module was installed purse-only.41type Module interface {42	Name() string43	Run(_ int, rlm realm, args string) string44}4546// Grant says what a module was given.47type Grant uint84849const (50	// GrantPurse is funds only, revocable at any time.51	GrantPurse Grant = iota52	// GrantIdentity also lends the account's sub-identity. Permanent.53	GrantIdentity54)5556func (g Grant) String() string {57	if g == GrantIdentity {58		return "identity"59	}60	return "purse"61}6263var (64	ErrNotOwner     = errors.New("pilot: not the owner")65	ErrNotApproved  = errors.New("pilot: path not approved")66	ErrNotInstalled = errors.New("pilot: module not installed")67	ErrRevoked      = errors.New("pilot: module revoked")68	ErrOverBudget   = errors.New("pilot: over budget")69	ErrStaleRealm   = errors.New("pilot: stale realm value")70	ErrBadName      = errors.New("pilot: a path and a subpath are [a-zA-Z0-9._/-] and not empty")71)7273type slot struct {74	mod     Module75	subpath string76	grant   Grant77	budget  int6478	spent   int6479	runs    int6480	live    bool81}8283// Pilot is the account. The realm that constructs it owns it and must not84// hand it out; hand out [Pilot.Handle] instead.85type Pilot struct {86	owner   address87	host    string        // the account realm's own pkgpath88	vault   banker.Banker // over host's address, minted once at New89	slots   *avl.Tree     // module pkgpath -> *slot90	handle  *Account91}9293// Account is the module-facing half: two methods, both keyed on the calling94// realm's OWN pkgpath, which a realm cannot forge for another.95type Account struct {96	p *Pilot97}9899// New builds an account owned by the caller of the crossing function that100// reaches it. Call it once, from the account realm, passing that realm's cur.101func New(_ int, rlm realm) *Pilot {102	assertCurrent(0, rlm)103	p := &Pilot{104		owner: rlm.Previous().Address(),105		host:  rlm.PkgPath(),106		vault: banker.NewBanker(banker.BankerTypeRealmSend, rlm),107		slots: avl.NewTree(),108	}109	p.handle = &Account{p: p}110	return p111}112113// assertPlain is why Render can interpolate a path without escaping it: the114// only two strings a caller ever puts in the roster are checked here, at write115// time, against the charset a package path and a subpath are built from.116func assertPlain(s string) {117	if s == "" {118		panic(ErrBadName)119	}120	for i := 0; i < len(s); i++ {121		c := s[i]122		switch {123		case c >= 'a' && c <= 'z', c >= 'A' && c <= 'Z', c >= '0' && c <= '9':124		case c == '.', c == '/', c == '-', c == '_':125		default:126			panic(ErrBadName)127		}128	}129}130131func assertCurrent(_ int, rlm realm) {132	if !rlm.IsCurrent() {133		panic(ErrStaleRealm)134	}135}136137// AssertOwner panics unless the user behind rlm owns the account. Only the138// account realm may call this: rlm must be its own live cur, so that139// rlm.Previous() is the signer and not some intermediary's caller.140func (p *Pilot) AssertOwner(_ int, rlm realm) {141	assertCurrent(0, rlm)142	if rlm.PkgPath() != p.host {143		panic(ErrNotOwner)144	}145	if rlm.Previous().Address() != p.owner {146		panic(ErrNotOwner)147	}148}149150// Handle is what an account realm exposes to modules.151func (p *Pilot) Handle() *Account { return p.handle }152153func (p *Pilot) Owner() address { return p.owner }154func (p *Pilot) Host() string   { return p.host }155156// Address is the account's main treasury.157func (p *Pilot) Address() address { return chain.PackageAddress(p.host) }158159// SubAddress is one module's own treasury, derivable off-chain by anyone.160func (p *Pilot) SubAddress(path string) address {161	s := p.mustSlot(path)162	return chain.DerivePkgSubAddr(p.host, s.subpath)163}164165func (p *Pilot) mustSlot(path string) *slot {166	s, ok := p.slots.Get(path).(*slot)167	if !ok {168		panic(ErrNotInstalled)169	}170	return s171}172173// Approve authorises a package path to install itself later. The code does174// not have to exist yet, which is the whole point: the account is deployed175// once and learns new powers afterwards.176func (p *Pilot) Approve(_ int, rlm realm, path, subpath string, grant Grant, budget int64) {177	p.AssertOwner(0, rlm)178	assertPlain(path)179	assertPlain(subpath)180	if old, ok := p.slots.Get(path).(*slot); ok {181		old.subpath = subpath182		old.grant = grant183		old.budget = budget184		old.spent = 0 // a new approval is a new allowance185		return186	}187	p.slots.Set(path, &slot{subpath: subpath, grant: grant, budget: budget})188}189190// SetBudget is the live knob. It applies to a purse a module already holds.191func (p *Pilot) SetBudget(_ int, rlm realm, path string, budget int64) {192	p.AssertOwner(0, rlm)193	p.mustSlot(path).budget = budget194}195196// Revoke stops a module. It takes back its purse immediately; it does NOT197// take back a sub-identity that was granted, nor any banker minted from one.198func (p *Pilot) Revoke(_ int, rlm realm, path string) {199	p.AssertOwner(0, rlm)200	p.mustSlot(path).live = false201}202203// Fund moves coins from the account's main treasury into a module's204// sub-treasury. For an identity grant this is the permanent blast radius.205func (p *Pilot) Fund(_ int, rlm realm, path string, amount int64) {206	p.AssertOwner(0, rlm)207	s := p.mustSlot(path)208	p.vault.SendCoins(p.Address(), chain.DerivePkgSubAddr(p.host, s.subpath),209		chain.NewCoins(chain.NewCoin("ugnot", amount)))210}211212// Exec drives an installed module. rlm must be the account realm's own live213// cur: an identity grant mints its sub-token from it, which only the account214// realm's namespace can do.215func (p *Pilot) Exec(_ int, rlm realm, path, args string) string {216	p.AssertOwner(0, rlm)217	s := p.mustSlot(path)218	if !s.live {219		panic(ErrRevoked)220	}221	s.runs++222	if s.grant == GrantIdentity {223		return s.mod.Run(0, rlm.Sub(s.subpath), args)224	}225	return s.mod.Run(0, rlm, args)226}227228// Register is called BY the module realm, with its own live cur. The account229// reads the path from the runtime and never from an argument.230func (a *Account) Register(_ int, rlm realm, m Module) {231	assertCurrent(0, rlm)232	path := rlm.PkgPath()233	s, ok := a.p.slots.Get(path).(*slot)234	if !ok {235		panic(ErrNotApproved)236	}237	s.mod = m238	s.live = true239}240241// PurseFor hands the calling module its revocable purse.242func (a *Account) PurseFor(_ int, rlm realm) *Purse {243	assertCurrent(0, rlm)244	path := rlm.PkgPath()245	if _, ok := a.p.slots.Get(path).(*slot); !ok {246		panic(ErrNotApproved)247	}248	return &Purse{p: a.p, path: path}249}250251// Purse is a capability this package declares, so every use of it runs here252// and is re-checked against live state. That is what makes it revocable,253// where a banker minted from a lent realm token is not.254type Purse struct {255	p    *Pilot256	path string257}258259// Pay spends from the account's main treasury, against the module's budget.260func (u *Purse) Pay(to address, amount int64) {261	s, ok := u.p.slots.Get(u.path).(*slot)262	if !ok {263		panic(ErrNotInstalled)264	}265	if !s.live {266		panic(ErrRevoked)267	}268	if amount <= 0 || amount > s.budget-s.spent {269		panic(ErrOverBudget)270	}271	s.spent += amount272	u.p.vault.SendCoins(u.p.Address(), to, chain.NewCoins(chain.NewCoin("ugnot", amount)))273}274275// Left is what this module may still spend.276func (u *Purse) Left() int64 {277	s, ok := u.p.slots.Get(u.path).(*slot)278	if !ok || !s.live {279		return 0280	}281	return s.budget - s.spent282}283284// Render is the account page. The account realm forwards its Render here.285func (p *Pilot) Render(path string) string {286	var sb strings.Builder287	sb.WriteString("# " + p.host + "\n\n")288	sb.WriteString("| | |\n|---|---|\n")289	sb.WriteString("| owner | " + p.owner.String() + " |\n")290	sb.WriteString("| treasury | " + p.Address().String() + " |\n")291	sb.WriteString(ufmt.Sprintf("| balance | %d ugnot |\n\n", p.balance(p.Address())))292293	if p.slots.Size() == 0 {294		sb.WriteString("No power installed yet.\n")295		return sb.String()296	}297298	sb.WriteString("## powers\n\n")299	sb.WriteString("| path | grant | identity | state | left | runs | sub-treasury |\n")300	sb.WriteString("|---|---|---|---|---|---|---|\n")301	p.slots.Iterate("", "", func(key string, v any) bool {302		s := v.(*slot)303		state := "approved"304		if s.live {305			state = "installed"306		} else if s.mod != nil {307			state = "revoked"308		}309		sub := chain.DerivePkgSubAddr(p.host, s.subpath)310		sb.WriteString(ufmt.Sprintf("| `%s` | %s | `%s#%s` | %s | %d | %d | %d ugnot |\n",311			key, s.grant.String(), p.host, s.subpath, state,312			s.budget-s.spent, s.runs, p.balance(sub)))313		return false314	})315	return sb.String()316}317318func (p *Pilot) balance(addr address) int64 {319	return banker.NewReadonlyBanker().GetCoin(addr, "ugnot")320}321

Functions

not supported for pure packages by the node (vm/qfuncs)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.