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.

program.gno

5.19 Kb · 156 lines
  1package grants
  2
  3import (
  4	"strconv"
  5	"strings"
  6)
  7
  8// Program is a Board plus the money that moves around it: what came in, what
  9// went out, and the denomination it is all counted in.
 10//
 11// It still holds no coins and calls no banker. A realm hands it the current
 12// treasury balance whenever a decision depends on one, and executes the
 13// Payment it hands back. That split is the whole point: everything a grant
 14// program decides is testable without a node, and the realm on top is thin
 15// enough to read in one screen.
 16type Program struct {
 17	Board     *Board
 18	Denom     string
 19	Donations []Donation
 20	Payments  []Payment
 21}
 22
 23// Donation is one top-up of the treasury, credited to a name.
 24type Donation struct {
 25	Height int64
 26	From   address
 27	Amount int64
 28}
 29
 30// Payment is one released tranche. The ledger of these is a program's answer
 31// to "where did the money go".
 32type Payment struct {
 33	Height    int64
 34	Request   int
 35	Milestone int
 36	To        address
 37	Amount    int64
 38}
 39
 40// NewProgram returns a program whose board is seated with founders. denom is
 41// the coin everything is counted in ("ugnot" on gno.land); it is carried so a
 42// renderer can label amounts without the realm telling it twice.
 43func NewProgram(denom string, founders ...address) *Program {
 44	return &Program{Board: New(founders...), Denom: denom}
 45}
 46
 47// Fund records a top-up. The coins have already moved by the time this is
 48// called: a realm calls it to put a name next to a transfer that would
 49// otherwise be anonymous.
 50func (p *Program) Fund(from address, amount, height int64) error {
 51	if amount <= 0 {
 52		return ErrNothingSent
 53	}
 54	if !from.IsValid() {
 55		return ErrBadAddress
 56	}
 57	p.Donations = append(p.Donations, Donation{Height: height, From: from, Amount: amount})
 58	return nil
 59}
 60
 61// Apply files a grant request from a milestone spec, "design:100,ship:400".
 62// See ParseMilestones for the grammar.
 63func (p *Program) Apply(applicant address, title, body, spec string, height int64) (*Request, error) {
 64	return p.ApplyFor(applicant, applicant, "", title, body, spec, height)
 65}
 66
 67// ApplyFor is Apply for a request whose money goes to someone other than the
 68// address filing it. See Board.SubmitFor for why the reason is required.
 69func (p *Program) ApplyFor(applicant, beneficiary address, reason, title, body, spec string, height int64) (*Request, error) {
 70	ms, err := ParseMilestones(spec)
 71	if err != nil {
 72		return nil, err
 73	}
 74	return p.Board.SubmitFor(applicant, beneficiary, reason, title, body, ms, height)
 75}
 76
 77// Review casts a member's verdict on the proof under review and, when that
 78// verdict releases the tranche, records the payment and hands it back for the
 79// caller to execute.
 80//
 81// balance is what the treasury holds right now. A verdict that would release
 82// more than that is refused with ErrUnderfunded and changes NOTHING: no
 83// ballot, no release, no payment. That ordering is the reason Board.Review is
 84// not called directly by a realm. A refusal is always free to record, since it
 85// costs the treasury nothing.
 86func (p *Program) Review(voter address, id, idx int, accept bool, reason string, height, balance int64) (Outcome, *Payment, error) {
 87	if releases, out := p.Board.ReviewDecides(id, idx, voter, accept); releases && out == Accepted {
 88		if amount := p.Board.Get(id).Milestones[idx].Amount; balance < amount {
 89			return UnderReview, nil, ErrUnderfunded
 90		}
 91	}
 92
 93	out, err := p.Board.Review(voter, id, idx, accept, reason, height)
 94	if err != nil || out != Accepted {
 95		return out, nil, err
 96	}
 97
 98	r := p.Board.Get(id)
 99	pay := Payment{
100		Height:    height,
101		Request:   id,
102		Milestone: idx,
103		To:        r.Payee(),
104		Amount:    r.Milestones[idx].Amount,
105	}
106	p.Payments = append(p.Payments, pay)
107	return out, &pay, nil
108}
109
110// Raised is everything ever donated through Fund.
111func (p *Program) Raised() int64 {
112	var n int64
113	for _, d := range p.Donations {
114		n += d.Amount
115	}
116	return n
117}
118
119// Disbursed is everything ever released.
120func (p *Program) Disbursed() int64 { return p.Board.Disbursed() }
121
122// Committed is what approved grants can still claim.
123func (p *Program) Committed() int64 { return p.Board.Committed() }
124
125// Available is balance minus what is already promised. It goes NEGATIVE when
126// the board has approved more than the treasury holds, which is allowed on
127// purpose: a board that can only approve what it already has cannot approve
128// anything before a donor shows up. Renderers say so out loud.
129func (p *Program) Available(balance int64) int64 { return balance - p.Committed() }
130
131// ParseMilestones reads "design:100,ship:400" into milestones, amounts in the
132// program's denomination, paid in the order written. A title may not contain a
133// comma, and the amount is whatever follows the LAST colon, so
134// "port gno:land tooling:250" parses the way it reads.
135func ParseMilestones(spec string) ([]*Milestone, error) {
136	out := []*Milestone{}
137	for _, part := range strings.Split(spec, ",") {
138		part = strings.TrimSpace(part)
139		if part == "" {
140			continue
141		}
142		i := strings.LastIndex(part, ":")
143		if i < 0 {
144			return nil, ErrBadSpec
145		}
146		amount, err := strconv.ParseInt(strings.TrimSpace(part[i+1:]), 10, 64)
147		if err != nil {
148			return nil, ErrBadSpec
149		}
150		out = append(out, NewMilestone(strings.TrimSpace(part[:i]), amount))
151	}
152	if len(out) == 0 {
153		return nil, ErrBadSpec
154	}
155	return out, nil
156}