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

actions.gno

6.07 Kb · 174 lines
  1package daokit
  2
  3// Defines executable operations that proposals can perform.
  4// Each action has a type and handler functions that implements the actual logic.
  5//
  6// Example: Adding a member, transferring funds, or updating configuration.
  7
  8import (
  9	"errors"
 10
 11	"gno.land/p/moul/md"
 12	"gno.land/p/nt/ufmt/v0"
 13)
 14
 15// Interface storing Action's data.
 16//
 17// SECURITY: String() is what a member reads before voting, and for an action
 18// built with NewAction it is the PAYLOAD's own rendering — supplied by whoever
 19// created the proposal. It is not trustworthy on its own.
 20//
 21// Two things keep it honest. Type() must be truthful or the wrong handler is
 22// looked up and its type assertion fails, so the action's KIND cannot be
 23// disguised; and the proposal view renders the Resource's registered
 24// DisplayName, Description and Condition beside it, which come from the DAO's
 25// own registry keyed by Type(), not from the proposal. A member therefore always
 26// sees what KIND of action this is from a trusted source.
 27//
 28// What String() can still misrepresent is the payload's DETAIL — and only if the
 29// handler lets a hostile payload through. See NewActionHandler.
 30type Action interface {
 31	// Returns human-readable description of the action.
 32	String() string
 33	// Returns unique identifier for the action type.
 34	Type() string
 35}
 36
 37// Interface storing executable function that processes Action's data.
 38type ActionHandler interface {
 39	// Executes the given action's logic. The rlm realm is threaded so
 40	// handlers can perform cross-realm calls during execution.
 41	Execute(action Action, rlm realm)
 42	// Returns the action type this handler processes.
 43	Type() string
 44}
 45
 46// Generic action implementation that stores a type and a payload.
 47// Payload contains the data parameters that will be processed by the handler.
 48// Example: {kind="/p/demo/transfer.Transfer", payload={amount: 1000, recipient: "user123"}}
 49type genericAction struct {
 50	kind    string
 51	payload interface{}
 52}
 53
 54// Generic action handler that executes a function with a given payload.
 55// Example: A transfer handler that processes {amount, recipient} to move funds.
 56// {kind="transfer", executor=transferFunds}
 57type genericActionHandler struct {
 58	kind     string
 59	executor func(payload interface{}, rlm realm)
 60}
 61
 62func NewAction(kind string, payload interface{}) Action {
 63	return &genericAction{kind: kind, payload: payload}
 64}
 65
 66// Returns string representation of the action's payload.
 67func (g *genericAction) String() string {
 68	return ufmt.Sprintf("%v", g.payload)
 69}
 70
 71// Returns the action's type identifier.
 72func (g *genericAction) Type() string {
 73	return g.kind
 74}
 75
 76// SECURITY: the executor MUST type-assert the payload to a CONCRETE type, never
 77// to an interface.
 78//
 79// The concrete assertion is what makes the action's own rendering safe to show a
 80// voter: a hostile proposer can submit any payload, but one of the wrong type is
 81// rejected before the executor sees it, and one of the right type renders
 82// through that type's own String(). Assert to an interface instead and any
 83// conforming type passes — including one whose String() understates what its
 84// data does, so the proposal reads as something milder than it executes.
 85//
 86// Stronger still, and what the destructive built-ins do: make the payload type
 87// UNEXPORTED and expose only a constructor, so a proposer cannot build one at
 88// all. basedao's ChangeDAOImplementation and daokit's InstantExecute both do.
 89//
 90// Moving rendering onto the handler would remove this footgun rather than
 91// document it, since the handler is registered by the DAO and the payload is
 92// not — gnodaokit#13. That changes ActionHandler, so it belongs to a version
 93// boundary rather than a patch.
 94func NewActionHandler(kind string, executor func(interface{}, realm)) ActionHandler {
 95	return &genericActionHandler{kind: kind, executor: executor}
 96}
 97
 98// Executes the action by calling the executor function with the payload.
 99func (g *genericActionHandler) Execute(iaction Action, rlm realm) {
100	action, ok := iaction.(*genericAction)
101	if !ok {
102		panic(errors.New("invalid action type"))
103	}
104	g.executor(action.payload, rlm)
105}
106
107// Returns the action's type identifier.
108func (g *genericActionHandler) Type() string {
109	return g.kind
110}
111
112// Creates a new empty action instance.
113func (g *genericActionHandler) Instantiate() Action {
114	return &genericAction{
115		kind: g.kind,
116	}
117}
118
119// //////////////////////////////////////////////////////////
120// ActionExecuteLambdaKind
121//
122// Built-in action type for executing lambda functions.
123const ActionExecuteLambdaKind = "gno.land/p/samcrew/daokit.ExecuteLambda"
124
125func NewExecuteLambdaHandler() ActionHandler {
126	return NewActionHandler(ActionExecuteLambdaKind, func(i interface{}, _ realm) {
127		cb, ok := i.(func())
128		if !ok {
129			panic(errors.New("invalid action type"))
130		}
131		cb()
132	})
133}
134
135func NewExecuteLambdaAction(cb func()) Action {
136	return NewAction(ActionExecuteLambdaKind, cb)
137}
138
139// //////////////////////////////////////////////////////////
140// ActionInstantExecuteKind
141//
142// Built-in action type for instantly executing sub-proposals.
143const ActionInstantExecuteKind = "gno.land/p/samcrew/daokit.InstantExecute"
144
145type actionInstantExecute struct {
146	dao DAO             // Target DAO to execute on
147	req ProposalRequest // Proposal to execute instantly
148}
149
150func (a *actionInstantExecute) String() string {
151	// XXX: find a way to be explicit about the subdao
152	s := ""
153	s += md.Paragraph(md.Blockquote(a.req.Action.Type()))
154	s += md.Paragraph(a.req.Action.String())
155	return s
156}
157
158func NewInstantExecuteHandler() ActionHandler {
159	return NewActionHandler(ActionInstantExecuteKind, func(i interface{}, rlm realm) {
160		action, ok := i.(*actionInstantExecute)
161		if !ok {
162			panic(errors.New("invalid action type"))
163		}
164		InstantExecute(action.dao, action.req, rlm)
165	})
166}
167
168// SAME REALM ONLY, for the reason on InstantExecute: the handler forwards the
169// EXECUTING DAO's realm, so dao must be hosted by the same realm. A child DAO in
170// another realm is refused — reach it through that realm's crossing function
171// instead.
172func NewInstantExecuteAction(dao DAO, req ProposalRequest) Action {
173	return NewAction(ActionInstantExecuteKind, &actionInstantExecute{dao: dao, req: req})
174}