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.

hlfees.gno

9.48 Kb · 248 lines
  1// Package hlfees is the bridge's fee realm (bridge/PLAN.md §5.5): the routes pay every fee here — GNOT as coins,
  2// BUBBLE through the token realm — and only the treasurer a decision names may withdraw, to itself. The treasurer
  3// at launch is compiled in below; a decision made with the builder changes it, 7 days after it is made — the
  4// governor's own through Govern until it yields on r/hlmailbox, a GovDAO proposal's after (M1k).
  5package hlfees
  6
  7import (
  8	"chain"
  9	"chain/banker"
 10	"strconv"
 11	"strings"
 12	"time"
 13
 14	"gno.land/p/g1zdaz6sypk5tdsjc2fzsqrrn4ttn8qzmnl4waqp/decisions/v0"
 15	"gno.land/r/g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr/bubble"
 16	"gno.land/r/g1zdaz6sypk5tdsjc2fzsqrrn4ttn8qzmnl4waqp/hlmailbox"
 17	"gno.land/r/gov/dao"
 18)
 19
 20const (
 21	namespace = "gno.land/r/g1zdaz6sypk5tdsjc2fzsqrrn4ttn8qzmnl4waqp/"
 22	pkgPath   = namespace + "hlfees" // must match gnomod.toml
 23
 24	// launchTreasurer is the operator's named key (§5.5, §11.12): a PLACEHOLDER — the deployer's hotkey — until
 25	// the launch notice names it; GovDAO can name the chain's own address instead at any vote.
 26	launchTreasurer = address("g1zdaz6sypk5tdsjc2fzsqrrn4ttn8qzmnl4waqp")
 27)
 28
 29// launchGovernor is the governor (M1k): the mailbox's, compiled here too so a member reads it in this realm's
 30// served source; init refuses a deploy with another. bridge/local/e2e.sh swaps it for gnodev's test1.
 31const launchGovernor = address("g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6")
 32
 33var (
 34	treasurer = launchTreasurer
 35	ledger    decisions.Ledger
 36)
 37
 38// init refuses a deploy whose compiled governor is not the mailbox's (M1k): the bridge's realms are governed by
 39// one key until it yields, or by GovDAO from the first block, never a mix.
 40func init() {
 41	if launchGovernor != hlmailbox.Governor() {
 42		panic("hlfees: the compiled governor " + string(launchGovernor) + " is not the mailbox's " + string(hlmailbox.Governor()))
 43	}
 44}
 45
 46type (
 47	treasurerParams struct{ to address }
 48	cancelParams    struct{ id int64 }
 49)
 50
 51func now() int64 { return time.Now().Unix() }
 52
 53func itoa(n int64) string { return strconv.FormatInt(n, 10) }
 54
 55// Address is this realm's address: where the routes pay the fees.
 56func Address() address { return chain.PackageAddress(pkgPath) }
 57
 58// Treasurer is the one address that may withdraw.
 59func Treasurer() address { return treasurer }
 60
 61// Balances are the fees held: ugnot and BUBBLE base units.
 62func Balances() (ugnot, bubbles int64) {
 63	return banker.NewReadonlyBanker().GetCoin(Address(), "ugnot"), bubble.BalanceOf(Address())
 64}
 65
 66// Withdraw pays everything held to the treasurer, who alone may call it.
 67func Withdraw(cur realm) {
 68	if !cur.IsCurrent() {
 69		panic("hlfees: that realm value is not live; pass your own cur")
 70	}
 71	if cur.Previous().Address() != treasurer {
 72		panic("hlfees: only the treasurer withdraws")
 73	}
 74	ugnot, bubbles := Balances()
 75	if ugnot > 0 {
 76		banker.NewBanker(banker.BankerTypeRealmSend, cur).SendCoins(Address(), treasurer, chain.Coins{{"ugnot", ugnot}})
 77	}
 78	if bubbles > 0 {
 79		bubble.Transfer(cross(cur), treasurer, bubbles)
 80	}
 81	chain.Emit("Withdrawn", "to", string(treasurer), "ugnot", itoa(ugnot), "bubble", itoa(bubbles))
 82}
 83
 84// ------------------------------------------------------------ builders --
 85
 86// Request is what a builder returns (M1k): the proposal GovDAO's proxy executes after the yield (Proposal), and
 87// the same decision the governor runs directly before it (Govern); see hlgnot's.
 88type Request struct {
 89	proposal dao.ProposalRequest
 90	text     string
 91	decide   func(cur realm) error
 92}
 93
 94// Proposal is the request as a GovDAO proposal: `dao.MustCreateProposal(cross(cur), req.Proposal())`.
 95func (r Request) Proposal() dao.ProposalRequest { return r.proposal }
 96
 97// Description is the proposal's: the parameters as the builder wrote them.
 98func (r Request) Description() string { return r.text }
 99
100// who names a previous realm in a refusal: its pkgpath, or a user's address.
101func who(path string, a address) string {
102	if path != "" {
103		return path
104	}
105	return string(a)
106}
107
108// mustBeGovernor is a decision's identity check (M1k), read from the closure's own live cur: the governor's own
109// call until it yields on r/hlmailbox, gno.land/r/gov/dao's proxy exactly after (or from the first block, when no
110// governor was compiled); see hlgnot's.
111func mustBeGovernor(cur realm) {
112	prev := cur.Previous()
113	if decisions.DAOGoverns(launchGovernor, hlmailbox.Yielded()) {
114		if p := prev.PkgPath(); p != decisions.DAOPath {
115			panic("hlfees: only " + decisions.DAOPath + " executes a decision, not " + who(prev.PkgPath(), prev.Address()))
116		}
117		return
118	}
119	if !prev.IsUser() || prev.Address() != launchGovernor {
120		panic("hlfees: until the governor yields, " + string(launchGovernor) + " alone decides, by Govern from its own key: not " + who(prev.PkgPath(), prev.Address()) + " (a GovDAO proposal is refused until the yield)")
121	}
122}
123
124func mustBeLive() {
125	if !hlmailbox.Live() {
126		panic("hlfees: not launched: before the Launch decision only cancel is proposed")
127	}
128}
129
130func propose(cur realm, kind, text string, apply func(made, t int64)) Request {
131	if !cur.IsCurrent() {
132		panic("hlfees: that realm value is not live; pass your own cur")
133	}
134	made := now()
135	cb := func(cur realm) error {
136		mustBeGovernor(cur)
137		t := now()
138		if err := decisions.Expired(made, t); err != nil {
139			return err
140		}
141		apply(made, t)
142		return nil
143	}
144	return Request{proposal: dao.NewProposalRequest("hlfees: "+kind, text, dao.NewSimpleExecutor(0, cur, cb, text)), text: text, decide: cb}
145}
146
147// Govern is the governor's way in before the yield (M1k): the same closure GovDAO's proxy runs after it, called
148// non-crossing in this frame so it reads the governor from cur.Previous(); refused for anyone else and for
149// everyone once the governor yielded on r/hlmailbox (or when none was compiled); see hlgnot's.
150func Govern(cur realm, r Request) {
151	if !cur.IsCurrent() {
152		panic("hlfees: that realm value is not live; pass your own cur")
153	}
154	if r.decide == nil {
155		panic("hlfees: not a builder's request")
156	}
157	if yielded := hlmailbox.Yielded(); decisions.DAOGoverns(launchGovernor, yielded) {
158		if launchGovernor == "" {
159			panic("hlfees: no governor was compiled, " + decisions.DAOPath + " governs from the first block: a decision is a GovDAO proposal, never Govern")
160		}
161		panic("hlfees: the governor yielded at height " + itoa(yielded) + ", " + decisions.DAOPath + " governs since: a decision is a GovDAO proposal, never Govern")
162	}
163	mustBeGovernor(cur)
164	if err := r.decide(cur); err != nil {
165		panic("hlfees: " + err.Error())
166	}
167}
168
169// NewSetTreasurerRequest names the address that may withdraw, 7 days after the decision is made.
170func NewSetTreasurerRequest(cur realm, to address) Request {
171	mustBeLive()
172	if !to.IsValid() {
173		panic("hlfees: not an address: " + string(to))
174	}
175	if to == treasurer {
176		panic("hlfees: " + string(to) + " is the treasurer already")
177	}
178	text := "set treasurer to " + string(to) + " (now " + string(treasurer) + ")"
179	return propose(cur, "set treasurer", text, func(made, t int64) {
180		mustBeLive()
181		d := ledger.Record("set treasurer", text, treasurerParams{to}, made, t, t+decisions.Delay)
182		d.Note = "queued 7 days"
183		chain.Emit("Decision", "id", itoa(d.ID), "kind", "set treasurer", "state", "queued")
184	})
185}
186
187// NewCancelRequest cancels a pending decision of this realm, at once.
188func NewCancelRequest(cur realm, id int64) Request {
189	d := ledger.Get(id)
190	if d == nil || !d.Pending() {
191		panic("hlfees: decision " + itoa(id) + " is not pending")
192	}
193	text := "cancel decision " + itoa(id) + " (" + d.Text + ")"
194	return propose(cur, "cancel", text, func(made, t int64) {
195		if _, err := ledger.Cancel(id, t); err != nil {
196			panic("hlfees: " + err.Error())
197		}
198		c := ledger.Record("cancel", text, cancelParams{id}, made, t, t)
199		ledger.Apply(c, t, "at once: cancelled")
200	})
201}
202
203// Execute applies a queued decision whose 7 days have passed; anyone may call it.
204func Execute(cur realm, id int64) {
205	if !cur.IsCurrent() {
206		panic("hlfees: that realm value is not live; pass your own cur")
207	}
208	t := now()
209	d, err := ledger.Due(id, t)
210	if err != nil {
211		panic("hlfees: " + err.Error())
212	}
213	p, ok := d.Params.(treasurerParams)
214	if !ok {
215		panic("hlfees: decision " + itoa(id) + " has nothing to apply")
216	}
217	treasurer = p.to
218	ledger.Apply(d, t, "applied after the delay")
219	chain.Emit("TreasurerSet", "address", string(treasurer))
220}
221
222// --------------------------------------------------------------- pages --
223
224// Render: "" the page; "json" the figures; "decisions" and "decisions.json" the decisions.
225func Render(path string) string {
226	ugnot, bubbles := Balances()
227	switch path {
228	case "":
229		var b strings.Builder
230		b.WriteString("# The bridge's fees\n\n")
231		b.WriteString("Every fee the bridge takes lands here: GNOT as coins, BUBBLE as the token. Only the treasurer a decision names withdraws, to itself.\n\n")
232		b.WriteString("- held: " + itoa(ugnot) + " ugnot, " + itoa(bubbles) + " BUBBLE base units\n")
233		b.WriteString("- treasurer: " + string(treasurer) + "\n")
234		b.WriteString("- " + decisions.GovernorLine(launchGovernor, hlmailbox.Yielded()) + "\n")
235		b.WriteString("- " + decisions.Clocks() + "\n")
236		b.WriteString("\n## Decisions\n\n" + ledger.Page())
237		return b.String()
238	case "json":
239		return "{\"address\":" + strconv.Quote(string(Address())) + ",\"treasurer\":" + strconv.Quote(string(treasurer)) +
240			"," + decisions.GovernorJSON(launchGovernor, hlmailbox.Yielded()) +
241			",\"ugnot\":" + itoa(ugnot) + ",\"bubble\":" + itoa(bubbles) + ",\"clocks\":" + decisions.ClocksJSON() + ",\"decisions\":" + itoa(ledger.Last()) + "}"
242	case "decisions":
243		return "# Decisions\n\n" + ledger.Page()
244	case "decisions.json":
245		return ledger.JSON()
246	}
247	return "unknown path; see the page"
248}