Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

batch.gno

4.42 Kb · 144 lines
  1package governor
  2
  3import (
  4	"strings"
  5
  6	ufmt "gno.land/p/nt/ufmt/v0"
  7)
  8
  9// batchKind runs several adopted kinds as one decision.
 10//
 11//	govern:batch   "treasury/spend 100\nfee 250"
 12//
 13// One member per line, the kind first and the rest its payload. Members must
 14// already be adopted: a batch decides several things at once and smuggles in
 15// nothing.
 16//
 17// All or nothing, because there is no halfway. A member's Do returning an error
 18// makes this panic, aborting the transaction and unwinding what earlier members
 19// did — the only atomicity gno offers.
 20//
 21// So a failed batch leaves the proposal Succeeded rather than Failed: the
 22// transaction wrote nothing, including the failure. It can be run again and
 23// will fail identically until the world changes or the grace period ends it.
 24//
 25// Single-member kinds keep the other behaviour — record the failure and finish
 26// — since there is nothing to be atomic with respect to.
 27type batchKind struct{}
 28
 29// maxBatch bounds a batch, because Describe and Check both walk it and a
 30// proposal nobody can render is a proposal nobody can vote on.
 31const maxBatch = 16
 32
 33func (k batchKind) Name() string { return reserved + "batch" }
 34
 35func (k batchKind) describe(g *Governor, payload string) string {
 36	members, err := parseBatch(payload)
 37	if err != nil {
 38		return "malformed batch: " + err.Error()
 39	}
 40	out := ufmt.Sprintf("do all %d of these, or none of them:\n", len(members))
 41	for i, m := range members {
 42		// Each member described by its own kind, from its own payload — the
 43		// same strings their Do will be handed. A batch that summarised its
 44		// members in its own words would be the description gap this design
 45		// exists to avoid, reintroduced one level up.
 46		out += ufmt.Sprintf("\n%d. **%s** — %s", i+1, m.kind, g.describeMember(m))
 47	}
 48	return out
 49}
 50
 51func (g *Governor) describeMember(m member) string {
 52	k := g.anyKind(m.kind)
 53	if k == nil {
 54		return "_no kind by that name_"
 55	}
 56	return g.describeKind(k, m.payload)
 57}
 58
 59func (k batchKind) check(g *Governor, payload string) error {
 60	members, err := parseBatch(payload)
 61	if err != nil {
 62		return err
 63	}
 64	for _, m := range members {
 65		e := g.entryOf(m.kind)
 66		if e == nil {
 67			return govErr("no kind called " + m.kind)
 68		}
 69		if !e.live {
 70			// A batch cannot reach a power the holders have not granted.
 71			return govErr(m.kind + " is not adopted")
 72		}
 73		if m.kind == (batchKind{}).Name() {
 74			// No batches of batches. Nesting makes the rendered description a
 75			// tree of unbounded depth, and the point of the description is
 76			// that somebody reads it.
 77			return govErr("a batch cannot contain a batch")
 78		}
 79		if err := g.checkKind(e.kind, m.payload); err != nil {
 80			return govErr(m.kind + ": " + err.Error())
 81		}
 82	}
 83	return nil
 84}
 85
 86func (k batchKind) run(g *Governor, dispatch Dispatch, payload string) error {
 87	members, err := parseBatch(payload)
 88	if err != nil {
 89		return err
 90	}
 91	for i, m := range members {
 92		e := g.entryOf(m.kind)
 93		if e == nil || !e.live {
 94			panic("govern: batch member " + m.kind + " is no longer adopted")
 95		}
 96		var err error
 97		if b, ok := asBuiltin(e.kind); ok {
 98			err = b.run(g, dispatch, m.payload)
 99		} else {
100			// Each member gets its OWN sub-realm, named for the power it is,
101			// rather than the batch's. A batch is a way to decide several
102			// things at once, not a way to launder one kind's authority into
103			// another's.
104			err = dispatch(e.kind, subPathOf(m.kind), m.payload)
105		}
106		if err != nil {
107			// Panic, not return. Returning would record a failure and keep
108			// whatever the earlier members already did, which is the one
109			// outcome a batch promises cannot happen.
110			panic(ufmt.Sprintf("govern: batch member %d (%s) failed: %s",
111				i+1, m.kind, err.Error()))
112		}
113	}
114	return nil
115}
116
117type member struct {
118	kind    string
119	payload string
120}
121
122func parseBatch(payload string) ([]member, error) {
123	var out []member
124	for _, line := range strings.Split(payload, "\n") {
125		line = strings.TrimSpace(line)
126		if line == "" {
127			continue
128		}
129		sp := strings.Index(line, " ")
130		if sp <= 0 {
131			// A member with no payload is almost always a typo, and a batch is
132			// the wrong place to find out.
133			return nil, govErr("expected `<kind> <payload>`, got: " + line)
134		}
135		out = append(out, member{kind: line[:sp], payload: strings.TrimSpace(line[sp+1:])})
136	}
137	if len(out) < 2 {
138		return nil, govErr("a batch needs at least two members; propose the kind directly")
139	}
140	if len(out) > maxBatch {
141		return nil, govErr("too many members in one batch")
142	}
143	return out, nil
144}