PathrockNetwork Gno Explorer
HomeBlocksTransactionsRealmsPackagesValidatorsAnalytics

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/authz/v0

Package
Open in gnoweb ↗

Overview

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

Files (5)

  • gnomod.tomltoml
  • authz.gnogno
  • authz_test.gnogno
  • example_test.gnogno
  • z_contract_authority_shape_filetest.gnogno
authz.gnogno
1// Package authz provides flexible authorization control for privileged actions.2//3// # Authorization Strategies4//5// The package supports multiple authorization strategies:6//   - Member-based: Single user or team of users7//   - Contract-based: The contract at a given path is itself the authority8//   - Auto-accept: Allow all actions9//   - Drop: Deny all actions10//11// Core Components12//13//   - Authority interface: Base interface implemented by all authorities14//   - Authorizer: Main wrapper object for authority management15//   - MemberAuthority: Manages authorized addresses16//   - ContractAuthority: Makes the contract at a path its own authority17//   - AutoAcceptAuthority: Accepts all actions18//   - DroppedAuthority: Denies all actions19//20// Quick Start21//22//	// Initialize with contract deployer as authority23//	var member address(...)24//	var auth = authz.NewWithMembers(member)25//26//	// Create functions that require authorization27//	func UpdateConfig(cur realm, newValue string) error {28//		return auth.DoByPrevious(0, cur, "update_config", func() error {29//			config = newValue30//			return nil31//		})32//	}33//34// See example_test.gno for more usage examples.35package authz3637import (38	"chain"39	"errors"40	"strings"4142	"gno.land/p/moul/addrset/v0"43	"gno.land/p/moul/once/v0"44	"gno.land/p/nt/avl/rotree/v0"45	"gno.land/p/nt/avl/v0"46	"gno.land/p/nt/ufmt/v0"47)4849// Authorizer is the main wrapper object that handles authority management.50// It is configured with a replaceable Authority implementation.51type Authorizer struct {52	auth Authority53}5455// Authority represents an entity that can authorize privileged actions.56// It is implemented by MemberAuthority, ContractAuthority, AutoAcceptAuthority,57// and DroppedAuthority.58//59// Authority is the canonical safe shape for cross-package authority60// interfaces: methods are address-typed (no realm/cur crosses the interface61// boundary), and consumers correctly derive `caller` from62// `cur.Previous().Address()` under `rlm.IsCurrent()` before invoking63// Authorize. No cur-leak (class 1) is possible through this interface.64//65// However, two RESIDUAL RISKS apply:66//67//   - Class-3 impl-substitution: NewWithAuthority and Authorizer.Transfer68//     accept any Authority impl. A malicious Authority can always-approve69//     (silent privilege escalation) or always-deny (denial-of-service).70//     Consumers should pass canonical impls from this package71//     (MemberAuthority, ContractAuthority, AutoAcceptAuthority,72//     DroppedAuthority) unless they have explicit reason to register a73//     foreign impl. We do not expose an IsCanonicalAuthority allowlist74//     because the package is intentionally extensible — third-party impls75//     are the design intent.76//77//   - Class-4 closed-over-authority: NewContractAuthority and78//     NewRestrictedContractAuthority capture a caller-supplied79//     PrivilegedActionHandler closure. The handler runs synchronously80//     inside Authorize with the consumer's authority. A hostile handler81//     can swallow actions, log the caller, or execute arbitrary code82//     under the consumer's frame. Register only trusted handler functions.83//     See r/gnops/valopers/init.gno for the realistic registration shape.84//85//   - Caller- and title-forgery on the RAW interface: Authorize takes86//     `caller` and `title` as ARGUMENTS, not from the frame. A consumer87//     that exposes a bare Authority (rather than the *Authorizer that88//     wraps it) therefore lets any holder present any caller and any89//     title. Caller-forgery is contained — the only authority-mutating90//     closure lives inside Authorizer.Transfer, so a forged caller on a91//     raw Authorize runs the caller's own inert closure and cannot92//     transfer; TestForgedCallerCannotTransfer pins that boundary.93//     Title-forgery is NOT contained: the title is passed straight to the94//     contractHandler, so a handler that branches on it (routing, quotas,95//     audit trails) must not treat it as trusted. Keep the Authority96//     unexported and hand out only what callers need; see97//     r/gnops/valopers/admin.gno, which exports a description string.98//99// We do NOT seal Authority via an unexported marker method — that pattern100// is bypassable via embedding in Gno; see101// p/test/seal/filetests/z_seal_*_filetest.gno for the four bypass tests.102type Authority interface {103	// Authorize executes a privileged action if the caller is authorized104	// Additional args can be provided for context (e.g., for proposal creation)105	Authorize(caller address, title string, action PrivilegedAction, args ...any) error106107	// String returns a human-readable description of the authority108	String() string109}110111// PrivilegedAction defines a function that performs a privileged action.112type PrivilegedAction func() error113114// PrivilegedActionHandler is called by contract-based authorities to handle115// privileged actions.116type PrivilegedActionHandler func(title string, action PrivilegedAction) error117118// NewWithMembers creates a new Authorizer whose authority is a119// MemberAuthority containing the given addresses. Callers express120// authority intent at the call site:121//122//	// "auth realm is the authority"123//	a := authz.NewWithMembers(cur.Address())124//125//	// "previous realm is the authority" (from a crossing function)126//	a := authz.NewWithMembers(cur.Previous().Address())127//128//	// "EOA caller is the authority" (from init(cur realm))129//	if !cur.Previous().IsUserCall() {130//	    panic("realm must be initialized by EOA")131//	}132//	a := authz.NewWithMembers(cur.Previous().Address())133//134// This replaces the previous NewWithCurrent / NewWithPrevious /135// NewWithOrigin sugar — those baked runtime.{Current,Previous,Origin}136// reads into the constructor, which (a) prevented use from package-137// level var initializers, (b) made the EOA-origin check inside138// NewWithOrigin an indirect address comparison rather than the139// straightforward IsUserCall predicate, and (c) coupled the140// constructor to the runtime walks the rest of the migration is141// moving away from.142func NewWithMembers(addrs ...address) *Authorizer {143	return &Authorizer{144		auth: NewMemberAuthority(addrs...),145	}146}147148// NewWithAuthority creates a new Authorizer with a specific authority.149//150// SECURITY: `authority` is an open-interface input — any value satisfying151// Authority is accepted. A malicious impl can always-approve (privilege152// escalation) or always-deny (DoS). Prefer canonical impls from this153// package (NewMemberAuthority, NewContractAuthority, NewAutoAcceptAuthority,154// NewDroppedAuthority) unless you specifically need a foreign impl.155func NewWithAuthority(authority Authority) *Authorizer {156	return &Authorizer{157		auth: authority,158	}159}160161// Authority returns the auth authority implementation162func (a *Authorizer) Authority() Authority {163	return a.auth164}165166// Transfer changes the auth authority after validation. rlm must be the167// caller's own captured cur (asserted via rlm.IsCurrent()); the168// principal is rlm.Previous().Address(). Closes the address-parameter169// forgery: an external realm cannot supply Owner() as `caller` to170// bypass the underlying Authority's check.171//172// SECURITY (runtime substitution): once the current authority approves a173// Transfer, the new authority is installed and effective on the next call.174// If an attacker ever becomes the authority — even briefly — they can175// install a permanent DroppedAuthority (DoS) or an AutoAcceptAuthority176// (privilege escalation). Consumers concerned about this should wrap177// Transfer with a one-shot guard or a quorum/cooldown check.178//179// `newAuthority` is also an open-interface input — see NewWithAuthority's180// Class-3 caveat. Pass canonical impls.181func (a *Authorizer) Transfer(_ int, rlm realm, newAuthority Authority) error {182	if !rlm.IsCurrent() {183		return errors.New("unauthorized")184	}185	caller := rlm.Previous().Address()186	return a.auth.Authorize(caller, "transfer_authority", func() error {187		a.auth = newAuthority188		return nil189	})190}191192// DoByCurrent executes a privileged action authorized as `rlm`. `rlm`193// must be the caller's own live cur (asserted via rlm.IsCurrent());194// the authorized principal is `rlm.Address()`. To authorize as the195// realm that called your function, use `DoByPrevious`.196//197//	auth.DoByCurrent(0, cur, "update_config", func() error { ... })    // current realm authorizes198//	auth.DoByPrevious(0, cur, "update_config", func() error { ... })   // calling realm authorizes199//200// The `_ int` first parameter is a deliberate sentinel that pushes201// `rlm realm` past the first-arg position so DoByCurrent stays a202// non-crossing method — otherwise it would be a crossing method and203// rlm.Previous() inside would resolve one realm deeper than the caller204// intended.205//206// SECURITY: the IsCurrent guard closes Class-2 designation forgery (see207// docs/resources/gno-security.md). A realm value's .Address() is set208// when the value is minted at a crossing frame; the value can in209// principle be stored and replayed. Without IsCurrent, a hostile realm210// could capture a high-privilege realm's cur.Previous() (e.g., when211// that realm called into it) and later pass the stored value here to212// authorize actions as that realm. IsCurrent rejects stale captures by213// requiring the value to match the topmost live crossing frame's cur.214func (a *Authorizer) DoByCurrent(_ int, rlm realm, title string, action PrivilegedAction, args ...any) error {215	if !rlm.IsCurrent() {216		return errors.New("unauthorized")217	}218	return a.auth.Authorize(rlm.Address(), title, action, args...)219}220221// DoByPrevious executes a privileged action authorized as the realm222// that called the function invoking DoByPrevious. `rlm` must be the223// caller's own live cur; the principal is derived as224// `rlm.Previous().Address()`. Mirrors the Transfer/AddMember pattern:225// always take live cur, derive the caller-of-caller internally rather226// than accepting a stored/forwarded realm value.227func (a *Authorizer) DoByPrevious(_ int, rlm realm, title string, action PrivilegedAction, args ...any) error {228	if !rlm.IsCurrent() {229		return errors.New("unauthorized")230	}231	return a.auth.Authorize(rlm.Previous().Address(), title, action, args...)232}233234// String returns a string representation of the auth authority.235//236// A non-canonical impl is wrapped as custom_authority[...] so that the237// official "dropped" is distinguishable from a "*custom*: dropped"238// (autoclaimed) one. Shared with ContractAuthority.String, which applies239// the same rule to its nested proposer.240func (a *Authorizer) String() string {241	return canonicalAuthorityString(a.auth)242}243244// MemberAuthority is the default implementation using addrset for member245// management.246type MemberAuthority struct {247	members addrset.Set248}249250func NewMemberAuthority(members ...address) *MemberAuthority {251	auth := &MemberAuthority{}252	for _, addr := range members {253		auth.members.Add(addr)254	}255	return auth256}257258func (a *MemberAuthority) Authorize(caller address, title string, action PrivilegedAction, args ...any) error {259	if !a.members.Has(caller) {260		return errors.New("unauthorized")261	}262263	if err := action(); err != nil {264		return err265	}266	return nil267}268269func (a *MemberAuthority) String() string {270	addrs := []string{}271	a.members.Tree().Iterate("", "", func(key string, _ any) bool {272		addrs = append(addrs, key)273		return false274	})275	addrsStr := strings.Join(addrs, ",")276	return ufmt.Sprintf("member_authority[%s]", addrsStr)277}278279// AddMember adds a new member to the authority. rlm must be the caller's280// own captured cur; the principal is rlm.Previous().Address() and must281// already be a member. The IsCurrent guard closes the forgery where an282// external realm passes Owner() as caller to bypass members.Has(caller).283func (a *MemberAuthority) AddMember(_ int, rlm realm, addr address) error {284	if !rlm.IsCurrent() {285		return errors.New("unauthorized")286	}287	caller := rlm.Previous().Address()288	return a.Authorize(caller, "add_member", func() error {289		a.members.Add(addr)290		return nil291	})292}293294// AddMembers adds a list of members to the authority. Same rlm contract295// as AddMember.296func (a *MemberAuthority) AddMembers(_ int, rlm realm, addrs ...address) error {297	if !rlm.IsCurrent() {298		return errors.New("unauthorized")299	}300	caller := rlm.Previous().Address()301	return a.Authorize(caller, "add_members", func() error {302		for _, addr := range addrs {303			a.members.Add(addr)304		}305		return nil306	})307}308309// RemoveMember removes a member from the authority. Same rlm contract310// as AddMember.311func (a *MemberAuthority) RemoveMember(_ int, rlm realm, addr address) error {312	if !rlm.IsCurrent() {313		return errors.New("unauthorized")314	}315	caller := rlm.Previous().Address()316	return a.Authorize(caller, "remove_member", func() error {317		a.members.Remove(addr)318		return nil319	})320}321322// Tree returns a read-only view of the members tree323func (a *MemberAuthority) Tree() *rotree.ReadOnlyTree {324	tree := a.members.Tree().(*avl.Tree)325	return rotree.Wrap(tree, nil)326}327328// Has checks if the given address is a member of the authority329func (a *MemberAuthority) Has(addr address) bool {330	return a.members.Has(addr)331}332333// ContractAuthority implements async contract-based authority334type ContractAuthority struct {335	contractPath    string336	contractAddr    address337	contractHandler PrivilegedActionHandler338	proposer        Authority // controls who can create proposals339}340341// NewContractAuthority makes the contract at `path` its OWN authority:342// the default proposer accepts only chain.PackageAddress(path), so a343// privileged action proceeds only when driven from that contract's own344// frame.345//346// NewRestrictedContractAuthority does NOT widen this — it REPLACES it.347// See its godoc; with an explicit proposer, `path` is a label and348// contractAddr is never consulted.349//350// The `caller` compared against that address is established upstream by351// Authorizer.DoByCurrent / DoByPrevious / Transfer under rlm.IsCurrent(),352// so an external realm cannot present the contract address.353//354// BREAKING: the default proposer used to be355// NewAutoAcceptAuthority ("anyone can propose"). Consumers that relied on356// arbitrary callers driving a plain ContractAuthority will start getting357// "unauthorized"; that is the fix, not a regression. Pass an explicit358// proposer to NewRestrictedContractAuthority to opt back in.359//360// SECURITY — the gate binds the contract's IDENTITY, not its intent.361// Inside any crossing frame of the contract, rlm.Address() is362// unconditionally PackageAddress(path), so `DoByCurrent` from within the363// contract is a tautology; only DoByPrevious and Transfer gain a real364// check. Consequently ANY exported function of the contract that returns365// a crossing closure (or anything else carrying its frame) hands this366// authority to its caller. Keep privileged closures unexported.367//368// So DO NOT write NewContractAuthority(ownPath) + DoByCurrent. It reads369// like "only I may do this" and compiles to "anyone may do this". Point370// `path` at the principal you actually want to assert — usually the371// governance realm that will drive the action — and use DoByPrevious, so372// the comparison is against whoever crossed in:373//374//	// in r/gnops/valopers/init.gno375//	auth = authz.NewWithAuthority(376//	    authz.NewContractAuthority("gno.land/r/gov/dao", handler),377//	)378//	// in the privileged write379//	auth.DoByPrevious(0, rlm, "update-instructions", action)380//381// Own-path is right only when something OTHER than the contract's own382// frame supplies the caller — i.e. you drive it via DoByPrevious from a383// realm you have deliberately let call in, or via Transfer.384//385// SECURITY — a gate is only as good as the principal it names. Pointing386// `path` at a realm whose frame ANY caller can mint buys nothing. Check,387// for whatever principal you choose, that nothing exported by that realm388// hands out its frame identity:389//390//   - `gno.land/r/gov/dao` is safe to assert ONLY because391//     SimpleExecutor.Execute rejects invocation from outside the proxy392//     (r/gov/dao/types.gno). Before that gate existed, dao's exported393//     NewSimpleExecutor + Execute let any realm mint a frame whose394//     Previous() was r/gov/dao, so this whole pairing authenticated395//     nothing. Do not assume a governance path is unforgeable; verify it.396//   - Whatever the principal, an exported function of YOUR realm whose397//     signature is assignable to a callback type the principal invokes398//     (for r/gov/dao: `func(realm) error`) is itself the leak — the399//     principal will happily call it for an attacker. Keep privileged400//     entrypoints out of that shape, or take extra parameters.401//402// Neither shape defends against re-exporting the privileged closure: its403// holder picks the Previous() they present by wrapping it in an executor404// of their own. Unexported closures remain the load-bearing rule.405//406// A syntactically valid but WRONG `path` is a permanent brick, not an407// error: the authority accepts nobody, and Transfer routes through the408// same gate, so it cannot be rotated out either. Construction rejects409// malformed paths, but it cannot tell a wrong path from a right one —410// e.g. "gno.land/r/gov/dao/impl/v0" (the path r/gov/dao's own411// allowedDAOs list holds) is well-formed and permanently dead, because412// an executor's Previous() is the PROXY path, never the impl. Give the413// consumer a governance-gated rotation entrypoint before deploying; see414// r/gnops/valopers/admin.gno's NewAuthorityRotationProposalRequest.415//416// SECURITY (Class-4 captured callback): `handler` is a caller-supplied417// closure that runs SYNCHRONOUSLY inside Authorize, with the consumer's418// authority. A hostile handler can swallow actions, log the caller, or419// execute arbitrary code under the consumer's frame. The package-internal420// wrappedAction enforces at-most-once invocation and NOTHING ELSE — in421// particular it does not check the caller, and the handler may call it422// however it likes (multiple times, never, out of order). Register only423// trusted handler functions; treat handler registration as the trust424// boundary.425//426// Panics if `path` is empty or malformed, or if `handler` is nil.427func NewContractAuthority(path string, handler PrivilegedActionHandler) *ContractAuthority {428	addr := chain.PackageAddress(assertValidContractPath(path))429	// The default proposer is a real Authority, not an absent one. Encoding430	// the strictest policy as a nil field would mean the secure default is431	// what you get by leaving something OUT: a struct literal that skips432	// this constructor, a persisted value from an older build, or re-adding433	// `if proposer == nil { proposer = NewAutoAcceptAuthority() }` would all434	// silently be wide open again. A one-field struct costs the same as the435	// nil it replaces and cannot be reached by omission.436	return newContractAuthority(path, addr, handler, &contractIdentityAuthority{addr: addr})437}438439// NewRestrictedContractAuthority creates a contract authority whose440// proposer REPLACES the contract-identity gate.441//442// This is the escape hatch, not a second lock. `proposer` becomes the ONLY443// authorization decision: contractAddr is not consulted on this path, so444// `path` degrades to a label that appears in String() and asserts nothing.445// NewRestrictedContractAuthority(p, h, NewAutoAcceptAuthority()) is exactly446// the old default behaviour ("anyone can propose"), stated447// explicitly rather than reached by default.448//449// In particular NewRestrictedContractAuthority("gno.land/r/gov/dao", h,450// NewMemberAuthority(alice)) does NOT mean "GovDAO and alice". It means451// "alice, and not GovDAO".452//453// SECURITY:454//   - `handler` is the same Class-4 captured-callback risk as455//     NewContractAuthority — runs synchronously inside Authorize with the456//     consumer's authority. Register only trusted handler functions.457//   - `proposer` is an open-interface input (Class-3 impl-substitution).458//     A hostile proposer Authority can always-approve creation of any459//     proposal, defeating the restriction. Pass canonical impls only.460//     String() renders a non-canonical proposer wrapped as461//     custom_authority[...] so such a substitution is at least visible.462func NewRestrictedContractAuthority(path string, handler PrivilegedActionHandler, proposer Authority) Authority {463	if proposer == nil {464		panic("proposer cannot be nil")465	}466	addr := chain.PackageAddress(assertValidContractPath(path))467	return newContractAuthority(path, addr, handler, proposer)468}469470// newContractAuthority is the single construction path: both exported471// constructors validate, then land here, so a guard added once applies to472// both. `handler` is rejected here rather than surfaced at Authorize time473// because Authorize checks contractHandler == nil BEFORE consulting the474// proposer, and Transfer routes through Authorize — so a nil-handler475// authority could never be rotated out. It is a permanent brick with no476// on-chain recovery for anyone: the same bricked-governance failure477// mode, reached by accident.478func newContractAuthority(path string, addr address, handler PrivilegedActionHandler, proposer Authority) *ContractAuthority {479	if handler == nil {480		panic("contract handler cannot be nil")481	}482	return &ContractAuthority{483		contractPath:    path,484		contractAddr:    addr,485		contractHandler: handler,486		proposer:        proposer,487	}488}489490// assertValidContractPath rejects paths that chain.PackageAddress would491// hash happily but that no realm could ever present, which would brick the492// authority permanently (nobody authorizes, and Transfer routes through the493// same gate so nobody can rotate out). Returns `path` so it composes.494//495// The authoritative rule is gnolang.ReGnoUserPkgPath ("all paths must be496// lowercase ascii alphanumeric characters", gnovm/pkg/gnolang/mempackage.go),497// but no validator is exported to Gno code — chain's isValidSubpath /498// assertValidSubpath are unexported, and there is no chain.IsValidPkgPath.499// So this is a deliberately permissive superset: segment ("/" segment)*,500// segment = [a-z0-9] ([a-z0-9_.-]* [a-z0-9])?. It accepts every real501// package path and catches what actually gets typed wrong — trailing or502// embedded whitespace, empty segments, uppercase, stray punctuation. It503// CANNOT catch a well-formed path that is simply the wrong principal. See504// NewContractAuthority's godoc on rotation.505func assertValidContractPath(path string) string {506	if path == "" {507		panic("contract path cannot be empty")508	}509	if !strings.Contains(path, "/") {510		panic("contract path must be a package path, e.g. gno.land/r/gov/dao")511	}512	start := 0513	for i := 0; i <= len(path); i++ {514		if i == len(path) || path[i] == '/' {515			if !isValidContractPathSegment(path[start:i]) {516				panic("contract path must be '/'-separated segments of [a-z0-9] with '_.-' allowed inside a segment, e.g. gno.land/r/gov/dao")517			}518			start = i + 1519		}520	}521	return path522}523524func isValidContractPathSegment(seg string) bool {525	if seg == "" {526		return false527	}528	for i := 0; i < len(seg); i++ {529		switch c := seg[i]; {530		case c >= 'a' && c <= 'z', c >= '0' && c <= '9':531		case c == '_' || c == '.' || c == '-':532			// Allowed only strictly inside a segment.533			if i == 0 || i == len(seg)-1 {534				return false535			}536		default:537			return false538		}539	}540	return true541}542543// contractIdentityAuthority is the default proposer installed by544// NewContractAuthority: the contract at the bound path, and nobody else.545// Unexported and unmutable by design — AddMember/RemoveMember are546// meaningless for a fixed contract identity, which is why this is not a547// MemberAuthority holding one address.548type contractIdentityAuthority struct {549	addr address550}551552func (a *contractIdentityAuthority) Authorize(caller address, _ string, action PrivilegedAction, _ ...any) error {553	if caller != a.addr {554		return errors.New("unauthorized")555	}556	return action()557}558559func (a *contractIdentityAuthority) String() string { return "contract-identity" }560561func (a *ContractAuthority) Authorize(caller address, title string, action PrivilegedAction, args ...any) error {562	if a.contractHandler == nil {563		return errors.New("contract handler is not set")564	}565566	// setup a once instance to ensure the action is executed only once567	executionOnce := once.Once{}568569	// wrappedAction enforces at-most-once invocation. The previous570	// gate `unsafe.CurrentRealm() == contractAddr` is removed: it571	// was .Title()-bypassable (runtime.CurrentRealm walks past572	// non-crossing frames to the most-recent crossing ancestor) and573	// the trust boundary is now upstream — Authorizer.DoByCurrent /574	// DoByPrevious require rlm.IsCurrent() and pass a non-forgeable575	// principal to Authorize, while the consumer realm's handler576	// closure is the Class-4 trust root by lexical capture at577	// registration time.578	wrappedAction := func() error {579		return executionOnce.DoErr(func() error {580			return action()581		})582	}583584	handle := func() error {585		if err := a.contractHandler(title, wrappedAction); err != nil {586			return err587		}588		return nil589	}590591	// The proposer IS the authorization decision. For an authority built by592	// NewContractAuthority it is a contractIdentityAuthority bound to593	// contractAddr — the contract itself and nobody else; for one built by594	// NewRestrictedContractAuthority it is whatever the consumer installed,595	// and contractAddr is deliberately not consulted (see that constructor's596	// godoc). `caller` is established upstream by Authorizer.DoByCurrent /597	// DoByPrevious / Transfer under rlm.IsCurrent(), so an external realm598	// cannot present an arbitrary principal here.599	//600	// A nil proposer is not a policy, it is a malformed value: both601	// constructors always install one, so nil means the struct was built by602	// a literal that bypassed them. Fail closed rather than dereference.603	if a.proposer == nil {604		return errors.New("proposer is not set")605	}606	return a.proposer.Authorize(caller, title+"_proposal", handle, args...)607}608609// String renders the contract path AND the proposer.610//611// The proposer half is security-relevant, not cosmetic: it is the only612// thing distinguishing a gated authority from a wide-open one. Rendering613// the path alone made614//615//	NewContractAuthority(path, handler)                              // gated616//	NewRestrictedContractAuthority(path, handler, AutoAccept{})      // open to all617//618// byte-identical, so any consumer test asserting on this string — and619// any on-chain reader inspecting it — was blind to the difference. A620// consumer realm could have its authority swapped for a fully permissive621// one and its assertions would stay green.622//623// "contract-identity" names the default installed by NewContractAuthority:624// the contract at contractPath, and nobody else, may drive this authority.625//626// The proposer is rendered through canonicalAuthorityString, NOT by calling627// a.proposer.String() directly. Authority is an open interface, so a foreign628// impl can return any text it likes — including "contract-identity". That629// made a fully permissive authority byte-identical to the gated default630// again, defeating the very assertions this rendering exists to support.631// Non-canonical impls are wrapped as custom_authority[...], mirroring what632// Authorizer.String has always done at the outer level.633//634// Read the `contract=` half with care: it is load-bearing only for the635// contract-identity default. With any other proposer it is a label —636// see NewRestrictedContractAuthority.637func (a *ContractAuthority) String() string {638	return ufmt.Sprintf(639		"contract_authority[contract=%s,proposer=%s]",640		a.contractPath,641		canonicalAuthorityString(a.proposer),642	)643}644645// canonicalAuthorityString renders an Authority, wrapping any646// implementation that is not one of this package's own as647// custom_authority[...] so a foreign impl cannot impersonate a canonical648// one by choosing its String() text. Shared by ContractAuthority.String649// (for the nested proposer) and Authorizer.String (for the installed650// authority).651func canonicalAuthorityString(auth Authority) string {652	if auth == nil {653		// Only reachable via a struct literal that bypassed the654		// constructors; Authorize fails closed on the same condition.655		return "<unset>"656	}657	switch auth.(type) {658	case *MemberAuthority, *ContractAuthority, *AutoAcceptAuthority,659		*droppedAuthority, *contractIdentityAuthority:660		return auth.String()661	default:662		return ufmt.Sprintf("custom_authority[%s]", auth.String())663	}664}665666// AutoAcceptAuthority implements an authority that accepts all actions667// AutoAcceptAuthority is a simple authority that automatically accepts all668// actions.669// It can be used as a proposer authority to allow anyone to create proposals.670type AutoAcceptAuthority struct{}671672func NewAutoAcceptAuthority() *AutoAcceptAuthority {673	return &AutoAcceptAuthority{}674}675676func (a *AutoAcceptAuthority) Authorize(caller address, title string, action PrivilegedAction, args ...any) error {677	return action()678}679680func (a *AutoAcceptAuthority) String() string {681	return "auto_accept_authority"682}683684// droppedAuthority implements an authority that denies all actions685type droppedAuthority struct{}686687func NewDroppedAuthority() Authority {688	return &droppedAuthority{}689}690691func (a *droppedAuthority) Authorize(caller address, title string, action PrivilegedAction, args ...any) error {692	return errors.New("dropped authority: all actions are denied")693}694695func (a *droppedAuthority) String() string {696	return "dropped_authority"697}698

Functions

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

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