Pure package detail
daokit
gno.land/p/samcrew/daokit
Indexed deployment identity with independently loaded latest RPC source. Functions and Render are realm-only RPC capabilities.
Indexed deployment
Identity
- Package path
- gno.land/p/samcrew/daokit
- Block
- 98954
- Deployed (UTC)
- Transaction
- aMs6lHUd4/ehRovK+QdZ5C3LlBbiF1y6+LCt7oAXaJM=
Latest RPC state
Source
package daokit
// Defines executable operations that proposals can perform.
// Each action has a type and handler functions that implements the actual logic.
//
// Example: Adding a member, transferring funds, or updating configuration.
import (
"errors"
"gno.land/p/moul/md"
"gno.land/p/nt/ufmt/v0"
)
// Interface storing Action's data.
//
// SECURITY: String() is what a member reads before voting, and for an action
// built with NewAction it is the PAYLOAD's own rendering — supplied by whoever
// created the proposal. It is not trustworthy on its own.
//
// Two things keep it honest. Type() must be truthful or the wrong handler is
// looked up and its type assertion fails, so the action's KIND cannot be
// disguised; and the proposal view renders the Resource's registered
// DisplayName, Description and Condition beside it, which come from the DAO's
// own registry keyed by Type(), not from the proposal. A member therefore always
// sees what KIND of action this is from a trusted source.
//
// What String() can still misrepresent is the payload's DETAIL — and only if the
// handler lets a hostile payload through. See NewActionHandler.
type Action interface {
// Returns human-readable description of the action.
String() string
// Returns unique identifier for the action type.
Type() string
}
// Interface storing executable function that processes Action's data.
type ActionHandler interface {
// Executes the given action's logic. The rlm realm is threaded so
// handlers can perform cross-realm calls during execution.
Execute(action Action, rlm realm)
// Returns the action type this handler processes.
Type() string
}
// Generic action implementation that stores a type and a payload.
// Payload contains the data parameters that will be processed by the handler.
// Example: {kind="/p/demo/transfer.Transfer", payload={amount: 1000, recipient: "user123"}}
type genericAction struct {
kind string
payload interface{}
}
// Generic action handler that executes a function with a given payload.
// Example: A transfer handler that processes {amount, recipient} to move funds.
// {kind="transfer", executor=transferFunds}
type genericActionHandler struct {
kind string
executor func(payload interface{}, rlm realm)
}
func NewAction(kind string, payload interface{}) Action {
return &genericAction{kind: kind, payload: payload}
}
// Returns string representation of the action's payload.
func (g *genericAction) String() string {
return ufmt.Sprintf("%v", g.payload)
}
// Returns the action's type identifier.
func (g *genericAction) Type() string {
return g.kind
}
// SECURITY: the executor MUST type-assert the payload to a CONCRETE type, never
// to an interface.
//
// The concrete assertion is what makes the action's own rendering safe to show a
// voter: a hostile proposer can submit any payload, but one of the wrong type is
// rejected before the executor sees it, and one of the right type renders
// through that type's own String(). Assert to an interface instead and any
// conforming type passes — including one whose String() understates what its
// data does, so the proposal reads as something milder than it executes.
//
// Stronger still, and what the destructive built-ins do: make the payload type
// UNEXPORTED and expose only a constructor, so a proposer cannot build one at
// all. basedao's ChangeDAOImplementation and daokit's InstantExecute both do.
//
// Moving rendering onto the handler would remove this footgun rather than
// document it, since the handler is registered by the DAO and the payload is
// not — gnodaokit#13. That changes ActionHandler, so it belongs to a version
// boundary rather than a patch.
func NewActionHandler(kind string, executor func(interface{}, realm)) ActionHandler {
return &genericActionHandler{kind: kind, executor: executor}
}
// Executes the action by calling the executor function with the payload.
func (g *genericActionHandler) Execute(iaction Action, rlm realm) {
action, ok := iaction.(*genericAction)
if !ok {
panic(errors.New("invalid action type"))
}
g.executor(action.payload, rlm)
}
// Returns the action's type identifier.
func (g *genericActionHandler) Type() string {
return g.kind
}
// Creates a new empty action instance.
func (g *genericActionHandler) Instantiate() Action {
return &genericAction{
kind: g.kind,
}
}
// //////////////////////////////////////////////////////////
// ActionExecuteLambdaKind
//
// Built-in action type for executing lambda functions.
const ActionExecuteLambdaKind = "gno.land/p/samcrew/daokit.ExecuteLambda"
func NewExecuteLambdaHandler() ActionHandler {
return NewActionHandler(ActionExecuteLambdaKind, func(i interface{}, _ realm) {
cb, ok := i.(func())
if !ok {
panic(errors.New("invalid action type"))
}
cb()
})
}
func NewExecuteLambdaAction(cb func()) Action {
return NewAction(ActionExecuteLambdaKind, cb)
}
// //////////////////////////////////////////////////////////
// ActionInstantExecuteKind
//
// Built-in action type for instantly executing sub-proposals.
const ActionInstantExecuteKind = "gno.land/p/samcrew/daokit.InstantExecute"
type actionInstantExecute struct {
dao DAO // Target DAO to execute on
req ProposalRequest // Proposal to execute instantly
}
func (a *actionInstantExecute) String() string {
// XXX: find a way to be explicit about the subdao
s := ""
s += md.Paragraph(md.Blockquote(a.req.Action.Type()))
s += md.Paragraph(a.req.Action.String())
return s
}
func NewInstantExecuteHandler() ActionHandler {
return NewActionHandler(ActionInstantExecuteKind, func(i interface{}, rlm realm) {
action, ok := i.(*actionInstantExecute)
if !ok {
panic(errors.New("invalid action type"))
}
InstantExecute(action.dao, action.req, rlm)
})
}
// SAME REALM ONLY, for the reason on InstantExecute: the handler forwards the
// EXECUTING DAO's realm, so dao must be hosted by the same realm. A child DAO in
// another realm is refused — reach it through that realm's crossing function
// instead.
func NewInstantExecuteAction(dao DAO, req ProposalRequest) Action {
return NewAction(ActionInstantExecuteKind, &actionInstantExecute{dao: dao, req: req})
}
The verified vm/qfuncs operation accepts realm paths only.
Pure packages expose source files but do not have Realm Render.