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
// Handles DAO governance through proposal voting and execution.
// They contain actions that run when voting requirements are satisfied.
//
// Common use cases: member management, treasury operations, rule updates.
import (
"chain/runtime"
"errors"
"time"
"gno.land/p/nt/seqid/v0"
"gno.land/p/onbloc/json"
"gno.land/p/samcrew/avl"
"gno.land/p/samcrew/daocond"
)
type ProposalStatus int
const (
ProposalStatusOpen ProposalStatus = iota // Currently accepting votes
ProposalStatusPassed // Met conditions, ready for execution
ProposalStatusExecuted // Successfully executed
)
func (s ProposalStatus) String() string {
switch s {
case ProposalStatusOpen:
return "Open"
case ProposalStatusPassed:
return "Passed"
case ProposalStatusExecuted:
return "Executed"
default:
return "Unknown"
}
}
type Proposal struct {
ID seqid.ID
Title string
Description string
CreatedAt time.Time
CreatedHeight int64
ProposerID string
Action Action // What to execute if passed
Condition daocond.Condition // Voting conditions for this proposal
Status ProposalStatus
ExecutedAt time.Time
Ballot daocond.Ballot // All votes cast on this proposal
}
type ProposalsStore struct {
Tree *avl.Tree // int -> Proposal
genID seqid.ID
}
// Data needed to create a new proposal.
type ProposalRequest struct {
Title string
Description string
Action Action
}
func NewProposalsStore() *ProposalsStore {
return &ProposalsStore{
Tree: avl.NewTree(),
}
}
func (p *ProposalsStore) GetProposal(id uint64) *Proposal {
value, ok := p.Tree.Get(seqid.ID(id).String())
if !ok {
return nil
}
proposal := value.(*Proposal)
return proposal
}
// Returns all proposals that match the given filter function.
func (p *ProposalsStore) GetProposals(filter func(Proposal) bool) *ProposalsStore {
proposals := NewProposalsStore()
p.Tree.Iterate("", "", func(key string, value interface{}) bool {
prop, ok := value.(*Proposal)
if !ok {
panic(errors.New("unexpected invalid proposal type"))
}
if filter(*prop) {
proposals.Tree.Set(key, prop)
}
return false
})
return proposals
}
// Reports the status a proposal should be displayed as, without writing it.
//
// Reading must never move a proposal. Render is on the cross-realm DAO
// interface, so anything it touches is reachable by any realm holding the
// handle: every read path used to call UpdateStatus on each proposal it walked,
// and a single Render flipped every condition-met proposal from Open to Passed —
// which, back when Execute demanded Open, bricked it permanently.
func (p *Proposal) DisplayStatus() ProposalStatus {
if p.Status == ProposalStatusOpen && p.Condition.Eval(p.Ballot) {
return ProposalStatusPassed
}
return p.Status
}
// Persists the status implied by the current votes: Open becomes Passed once the
// condition is met.
//
// Nothing in this package calls it. Execute evaluates the condition itself and
// the read paths use DisplayStatus, so a proposal that is never passed through
// here stays Open until it is Executed. It exists for a realm that wants the
// passed state on-chain rather than computed.
//
// SAFE ONLY BECAUSE Vote and Execute both key on Executed alone. When they
// demanded Open, calling this was destructive twice over: it froze the ballot,
// so support could not be withdrawn, and it made the proposal permanently
// unexecutable. Do not narrow either of those checks without removing this.
//
// It is also a READ path hazard — do not call it while rendering. And once
// persisted the status no longer tracks the votes: withdraw support afterwards
// and this still reads Passed, though Execute re-evaluates the condition and
// will refuse.
func (p *Proposal) UpdateStatus() {
p.Status = p.DisplayStatus()
}
// Returns all proposals as a JSON string.
func (p *ProposalsStore) GetProposalsJSON() string {
props := make([]*json.Node, 0, p.Tree.Size())
// XXX: pagination
p.Tree.Iterate("", "", func(key string, value interface{}) bool {
prop, ok := value.(*Proposal)
if !ok {
panic(errors.New("unexpected invalid proposal type"))
}
props = append(props, json.ObjectNode("", map[string]*json.Node{
"id": json.NumberNode("", float64(prop.ID)),
"title": json.StringNode("", prop.Title),
"description": json.StringNode("", prop.Description),
"proposer": json.StringNode("", prop.ProposerID),
"status": json.StringNode("", prop.DisplayStatus().String()),
"startHeight": json.NumberNode("", float64(prop.CreatedHeight)),
"signal": json.NumberNode("", prop.Condition.Signal(prop.Ballot)),
}))
return false
})
bz, err := json.Marshal(json.ArrayNode("", props))
if err != nil {
panic(err)
}
return string(bz)
}
func (p *ProposalsStore) newProposal(proposer string, req ProposalRequest, condition daocond.Condition) *Proposal {
id := p.genID.Next()
proposal := &Proposal{
ID: id,
Title: req.Title,
Description: req.Description,
ProposerID: proposer,
Status: ProposalStatusOpen,
Action: req.Action,
Condition: condition,
Ballot: daocond.NewBallot(),
CreatedAt: time.Now(),
CreatedHeight: runtime.ChainHeight(),
}
p.Tree.Set(id.String(), proposal)
return proposal
}
The verified vm/qfuncs operation accepts realm paths only.
Pure packages expose source files but do not have Realm Render.