Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

v0 source pure

Package pilot is a realm-driven account: one realm holds the funds and the identity, a key pilots it, and its powers ...

Readme View source

gno.land/p/moul/pilot/v0

A realm-driven account: one realm holds the funds and the identity, a key pilots it, and its powers arrive afterwards as separate realms it never imports. The gno answer to a Gnosis Safe with modules.

Live instance: r/moul/pilot. A power: r/moul/x/pilotdemo.

Why it is not just a multisig

gno has no dynamic call: a realm cannot invoke an arbitrary package path with runtime-built arguments. So an account can never execute arbitrary calldata the way a Safe does. Every outbound action has to be Go code in some realm.

The inversion that makes it work: the account is deployed once and stores Module values handed to it by realms that did not exist at the time. The power is the calldata, published as readable source, and installing one costs no redeploy of the account.

The two grants, and the only difference that matters

GrantPurse GrantIdentity
spends the main treasury, metered its own sub-treasury account#subpath
can act as the account toward other realms no yes
Revoke takes it back yes, immediately no, never

A Purse is a type this package declares, so every call on one re-enters this code and re-reads the live roster and budget. A module that stashed a purse and calls it a year later still goes through the check.

A sub-identity token is the opposite. The token itself cannot be persisted, but banker.NewBanker authorizes at construction and re-checks nothing ever again, so a module can mint one from the lent token and keep it. Revoke shuts the account's door and does not reach that banker. The blast radius is exactly what the sub-address was funded with, and it is permanent. Grant an identity only to code you have read, and note that you can read it: module source is on chain before you approve it.

Shape

 1// once, from the account realm
 2acct := pilot.New(0, cur)
 3
 4// the owner, through the account realm's crossing functions
 5acct.Approve(0, cur, "gno.land/r/you/somepower/v0", "power", pilot.GrantPurse, 1_000)
 6acct.Fund(0, cur, path, 500)
 7acct.SetBudget(0, cur, path, 2_000)
 8acct.Revoke(0, cur, path)
 9acct.Exec(0, cur, path, args)
10
11// the module realm, with its own cur: the account reads the path from the
12// runtime and never from an argument
13h := account.Handle()
14h.Register(0, cur, self)
15purse := h.PurseFor(0, cur)

Both realms in the pair must be public

A module's object is persisted in the account's roster, and a value of a type defined in a private realm cannot be persisted by anyone else. A private account realm cannot be imported at all, and a redeploy would wipe the owner, the roster and every budget while leaving the coins at the address. private = true is wrong for both halves, and each gnomod.toml says so.


Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.

Dependency graph:

gno.land/p/moul/pilot/v0 dependency graph

⚠️ Disclaimer: provided as-is, without warranty; not security-audited. Full disclaimer: DISCLAIMER.

Overview

Package pilot is a realm-driven account: one realm holds the funds and the identity, a key pilots it, and its powers are separate realms installed afterwards without ever redeploying it. The gno answer to a Gnosis Safe with modules.

An account realm keeps a *Pilot private and hands modules a narrow Account handle. Everything privileged stays on *Pilot, which is never returned, so a module can only reach the two methods it needs.

Two ways to delegate, and they differ in exactly one property:

  • A Purse is revocable. Every method on it re-enters the declaring package and re-checks the live roster and budget, so Revoke and SetBudget take effect immediately, even on a purse a module retained.
  • A sub-identity token (rlm.Sub) is permanent. It lets the module act as "<account>#<subpath>" toward any other realm, which a purse cannot do, but the module can mint a banker from it and keep it forever. Removing the module does not take that back; only emptying the sub-address does. Grant one only to code you have read.

Live instance: r/moul/pilot. Demo module: r/moul/x/pilotdemo.

Constants 1

const GrantPurse, GrantIdentity

1const (
2	// GrantPurse is funds only, revocable at any time.
3	GrantPurse Grant = iota
4	// GrantIdentity also lends the account's sub-identity. Permanent.
5	GrantIdentity
6)
source

Variables 1

var ErrNotOwner, ErrNotApproved, ErrNotInstalled, ErrRevoked, ErrOverBudget, ErrStaleRealm, ErrBadName

1var (
2	ErrNotOwner     = errors.New("pilot: not the owner")
3	ErrNotApproved  = errors.New("pilot: path not approved")
4	ErrNotInstalled = errors.New("pilot: module not installed")
5	ErrRevoked      = errors.New("pilot: module revoked")
6	ErrOverBudget   = errors.New("pilot: over budget")
7	ErrStaleRealm   = errors.New("pilot: stale realm value")
8	ErrBadName      = errors.New("pilot: a path and a subpath are [a-zA-Z0-9._/-] and not empty")
9)
source

Functions 1

func New

1func New(_ int, rlm realm) *Pilot
source

New builds an account owned by the caller of the crossing function that reaches it. Call it once, from the account realm, passing that realm's cur.

Types 5

type Account

struct
1type Account struct {
2	p *Pilot
3}
source

Account is the module-facing half: two methods, both keyed on the calling realm's OWN pkgpath, which a realm cannot forge for another.

Methods on Account

func PurseFor

method on Account
1func (a *Account) PurseFor(_ int, rlm realm) *Purse
source

PurseFor hands the calling module its revocable purse.

func Register

method on Account
1func (a *Account) Register(_ int, rlm realm, m Module)
source

Register is called BY the module realm, with its own live cur. The account reads the path from the runtime and never from an argument.

type Grant

ident
1type Grant uint8
source

Grant says what a module was given.

Methods on Grant

func String

method on Grant
1func (g Grant) String() string
source

type Module

interface
1type Module interface {
2	Name() string
3	Run(_ int, rlm realm, args string) string
4}
source

Module is implemented by a module realm and installed into an account. The account never imports it: it learns the module only as this interface.

Run is threaded (the leading int keeps it out of crossing-function territory, which a /p/ package may not declare). rlm is whatever the account chose to delegate: its own sub-identity token for a trusted module, or a zero value when the module was installed purse-only.

type Pilot

struct
1type Pilot struct {
2	owner  address
3	host   string        // the account realm's own pkgpath
4	vault  banker.Banker // over host's address, minted once at New
5	slots  *avl.Tree     // module pkgpath -> *slot
6	handle *Account
7}
source

Pilot is the account. The realm that constructs it owns it and must not hand it out; hand out Pilot.Handle instead.

Methods on Pilot

func Address

method on Pilot
1func (p *Pilot) Address() address
source

Address is the account's main treasury.

func Approve

method on Pilot
1func (p *Pilot) Approve(_ int, rlm realm, path, subpath string, grant Grant, budget int64)
source

Approve authorises a package path to install itself later. The code does not have to exist yet, which is the whole point: the account is deployed once and learns new powers afterwards.

func AssertOwner

method on Pilot
1func (p *Pilot) AssertOwner(_ int, rlm realm)
source

AssertOwner panics unless the user behind rlm owns the account. Only the account realm may call this: rlm must be its own live cur, so that rlm.Previous() is the signer and not some intermediary's caller.

func Exec

method on Pilot
1func (p *Pilot) Exec(_ int, rlm realm, path, args string) string
source

Exec drives an installed module. rlm must be the account realm's own live cur: an identity grant mints its sub-token from it, which only the account realm's namespace can do.

func Fund

method on Pilot
1func (p *Pilot) Fund(_ int, rlm realm, path string, amount int64)
source

Fund moves coins from the account's main treasury into a module's sub-treasury. For an identity grant this is the permanent blast radius.

func Handle

method on Pilot
1func (p *Pilot) Handle() *Account
source

Handle is what an account realm exposes to modules.

func Host

method on Pilot
1func (p *Pilot) Host() string
source

func Owner

method on Pilot
1func (p *Pilot) Owner() address
source

func Render

method on Pilot
1func (p *Pilot) Render(path string) string
source

Render is the account page. The account realm forwards its Render here.

func Revoke

method on Pilot
1func (p *Pilot) Revoke(_ int, rlm realm, path string)
source

Revoke stops a module. It takes back its purse immediately; it does NOT take back a sub-identity that was granted, nor any banker minted from one.

func SetBudget

method on Pilot
1func (p *Pilot) SetBudget(_ int, rlm realm, path string, budget int64)
source

SetBudget is the live knob. It applies to a purse a module already holds.

func SubAddress

method on Pilot
1func (p *Pilot) SubAddress(path string) address
source

SubAddress is one module's own treasury, derivable off-chain by anyone.

type Purse

struct
1type Purse struct {
2	p    *Pilot
3	path string
4}
source

Purse is a capability this package declares, so every use of it runs here and is re-checked against live state. That is what makes it revocable, where a banker minted from a lent realm token is not.

Methods on Purse

func Left

method on Purse
1func (u *Purse) Left() int64
source

Left is what this module may still spend.

func Pay

method on Purse
1func (u *Purse) Pay(to address, amount int64)
source

Pay spends from the account's main treasury, against the module's budget.

Imports 6

Source Files 3