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

/p/nt/commondao/v0

Directory · 10 Files
README.md Open

v0 - Unaudited This is an initial version of this package that has not yet been formally audited. A fully audited version will be published as a subsequent release. Use in production at your own risk.

commondao

Governance primitives following the Common DAO Spec (docs/CONSTITUTION.md, Appendix): a CommonDAO is a Council (a set of addresses with equal voting power), a proposal lifecycle, and an optional sub-DAO tree.

CommonDAO
├── council:            *addrset.Set — who may vote
├── kinds:              registered ProposalKind factories — what may be
│                       proposed (name → New(readonly dao, args))
├── active proposals:   active + early passed, each with an electorate
│                       snapshot and voting record
├── finished proposals: dismissed / executed / failed / withdrawn
├── treasury:           a derived address + frozen flag (funds moved by
│                       the hosting realm, never by this package)
└── children:           sub-DAOs (each a CommonDAO with a parent pointer)

Quick start

 1import "gno.land/p/nt/commondao/v0"
 2
 3// A proposal kind names one proposal type and builds its definitions.
 4type textKind struct{}
 5
 6func (textKind) Name() string { return "text" }
 7func (textKind) New(dao commondao.ReadonlyCommonDAO, args any) (commondao.ProposalDefinition, error) {
 8    text, ok := args.(string) // validate args, build the definition
 9    if !ok || text == "" {
10        return nil, errors.New("a proposal text is required")
11    }
12    return textDefinition{text}, nil
13}
14
15var dao = commondao.New(
16    commondao.WithName("My DAO"),
17    commondao.WithCouncilMember(founder),
18    commondao.WithProposalKind(textKind{}), // a type is proposable iff registered
19)
20
21// Propose looks the kind up in the DAO's registry and calls its New
22// factory with a readonly view of the host DAO and args to build the
23// frozen definition. The council snapshot taken at Propose is the
24// proposal's electorate. (The package does not gate who proposes —
25// hosting realms do.)
26p, _ := dao.Propose(founder, "text", "hello world")
27
28// Electorate members vote; default rule proposals can be decided the
29// moment the outcome is settled.
30dao.Vote(founder, p.ID(), commondao.ChoiceYes, "")
31
32// Execute runs passed proposals (early passed ones immediately, active
33// ones once their voting deadline passes). The host mints a DAO-scoped
34// sub-identity and passes it as the executor's value-movement authority.
35dao.Execute(p.ID(), sub)

Voting rules (the constitutional defaults)

Every proposal definition returns a Threshold(); proposals are decided by TallyDefault with integer math over the proposal's electorate snapshot E (the council at Propose time):

D = |E| - abstains                 // the tally denominator
supermajority:   pass ⇔ D > 0 && 3*yes >= 2*D   ("two thirds or more")
simple majority: pass ⇔ D > 0 && 2*yes > D       ("more than half")
dismiss (both):       ⇔ 2*no > D
undecided at deadline ⇒ dismissed

Abstaining shrinks the denominator (deference); not voting counts against passage (silence is opposition). Votes are re-evaluated after every ballot — including changed votes — so a proposal passes or is dismissed the moment the outcome is mathematically settled and an early-passed proposal may be executed before its deadline.

Vote choices are fixed at YES/NO/ABSTAIN.

Council changes

UpdateCouncil(add, remove) applies idempotent set operations: the final set is (council ∪ add) \ remove, duplicate adds and absent removes are no-ops (so concurrently passed updates merge in execution order), full replacement in one call is legal, and an update that would empty a non-empty council returns ErrEmptyCouncil — executors propagate the error to fail the proposal cleanly.

Proposal kinds

Proposal types are registered on the DAO, not passed per proposal: a ProposalKind couples a registry name with a New(dao ReadonlyCommonDAO, args) factory, and Propose(creator, kind, args) accepts exactly the kinds registered (WithProposalKind at construction; RegisterKind/DeregisterKind afterwards, typically from a governance proposal executor). The registry is read only at Propose: deregistering a kind blocks new proposals but never touches in-flight ones, whose definitions were frozen at creation. HasKind/KindNames expose the registry, also on the readonly view.

New receives only a ReadonlyCommonDAO, so a kind — including an externally-authored or user-registered one — cannot mutate the host DAO (or its tree) at Propose time, before the vote. A kind that must mutate state on execution takes the target *CommonDAO through args, which only a trusted caller can populate (an external proposer cannot obtain a *CommonDAO), captures it in the definition, and mutates in its Executor — which runs only after the vote passes.

The ExecutionKind concrete kind

The package ships exactly one concrete kind, /p/-typed so any realm can seed it with WithProposalKind(ExecutionKind{}) or register it later with RegisterKind:

  • ExecutionKind ("execution") runs an arbitrary ExecFunc supplied by the proposer (ExecutionArgs{Title, Body, Fn}) on approval, under a default policy (7-day voting period, supermajority threshold) and no check on the closure beyond a non-nil Fn. The Fn closure is frozen at Propose (vote-integrity), so it must be authored in a persistent realm — a closure created by a maketx run script does not persist to Execute and cannot run.

    Because it applies no policy to the closure, a realm with treasury constraints (e.g. a freeze flag) should not catalog ExecutionKind directly: it should author its own execution kind whose definition wraps the closure with a Validable check enforcing those constraints, so arbitrary execution cannot bypass them. The reference realm does this to keep a frozen DAO from draining its own treasury via an execution proposal.

A registered foreign-realm kind runs under its defining realm's authority — registering one is a governance trust grant, not a sandbox.

The package ships no governance meta-kinds. RegisterKind / DeregisterKind are plain registry primitives with no reserved names: any registered kind can be removed. Managing a DAO's kind set through governance — and keeping a managing kind un-removable so a DAO can always recover — is the consuming realm's policy, built on these primitives (see the reference realm's manage-kinds kind).

Extending commondao in your own realm

The package is mostly mechanism: it ships the ExecutionKind concrete kind (with a default voting policy) and the registry primitives, and leaves the rest of governance policy — which kinds a DAO accepts, how it manages them, and any per-kind constraints such as a treasury freeze — to the consuming realm. To add your own proposal type:

  1. Author a ProposalKindName() plus New(dao ReadonlyCommonDAO, args any) (ProposalDefinition, error). Make the definition Executable if it mutates on approval. If its executor moves funds from a DAO other than the host, have the host realm consume a Funded-style contract (FundingDAOID() uint64): minting a DAO sub needs the host's cur, so it is host-consumed, not package-dispatched — define it in your realm.
  2. Seed it — the owning realm holds the handle, so no proposal is needed: commondao.New(WithProposalKind(YourKind{}), …) at construction, or dao.RegisterKind(YourKind{}) directly.
  3. Author a typed, CLI-friendly wrapper CreateYourProposal(cur realm, daoID uint64, …params…) that council-gates the caller, builds the args, and calls Propose.
  4. Optionally add a governance toggle — a manage-kinds-style kind whose executor calls RegisterKind/DeregisterKind, kept itself un-deregisterable, if the council should manage kinds at runtime.

Trust boundary: New gets only a ReadonlyCommonDAO; the mutable *CommonDAO reaches a definition only via args your trusted wrapper populates; the executor gets the DAO's terminal, RealmSend-only sub. See the reference realm for a worked example.

Proposal lifecycle

Propose (kind-gated as above; capped via SetMaxActiveProposals; CapExempt definitions such as council updates bypass the cap, bounded to one active proposal per creator) → Vote (electorate-gated, deadline-gated, rejects non-active proposals) → Execute (early-passed: immediately, still validating; active: after the deadline, dismissing undecided proposals) or Withdraw (active, zero votes). Dissolve dismisses every in-flight proposal and soft deletes the DAO; deleted DAOs reject proposals, votes, and executions.

Treasury

The package stores a treasury address (WithAddress, Address()) and a frozen flag (SetTreasuryFrozen, IsTreasuryFrozen) but never moves funds — hosting realms derive the address (typically a realm sub-identity via chain.DerivePkgSubAddr) and enforce the frozen flag. Execute runs the executor with the DAO-scoped sub-identity the host passes as its value-movement authority: a fund-moving definition builds its banker from that sub, so value moves are structurally bounded to that one DAO address. Which DAO's sub the host mints is the host's decision — minting a sub needs the host realm's cur, so the package cannot make it — typically the proposal's own DAO, but a fund-moving definition may direct the host to a different DAO (e.g. clawback sweeps the target, not the host). See the reference realm's treasury proposals and its host-side Funded contract for the constitutional pattern.

Realm boundaries

A *CommonDAO is a mutable handle for the realm that owns it:

  1. Do not ACCEPT a *CommonDAO from an untrusted caller.
  2. Do not RETURN a *CommonDAO — return dao.Readonly(), a ReadonlyCommonDAO view whose whole reachable graph is read-only (ReadonlyProposal flattens Title()/Body() and never exposes the ProposalDefinition, whose executor would otherwise be callable under your realm's authority).
  3. Do not TRUST a readonly view received from an untrusted caller — it is a live handle over the sender's data.

See gno.land/r/nt/commondao/v0 for the reference realm hosting many DAOs with invitations, council governance, treasuries, and rendering.