PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/upkeep

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
upkeep
Namespace
g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39
Files
3 (gnomod.toml)
Exported functions
20
Module
gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/upkeep
gno
0.9

Files (3)

  • gnomod.tomltoml
  • upkeep.gnogno
  • upkeep_test.gnogno
upkeep.gnogno
1// Package upkeep pays people to run the ecosystem's permissionless2// valves. The portfolio's realms deliberately expose maintenance3// entrypoints that anyone may call — subscriptions.Expire frees a lapsed4// subscription slot, timelock_guardian.Execute fires a matured timelocked5// action — because no slot's liveness may depend on an interested party6// showing up. This realm adds the missing economics: funders finance a7// reward pot, and whoever triggers a valve THROUGH this realm is credited8// a bounded reward, claimable by pull.9//10// THE REALM-TO-REALM BOUNDARY, PRECISELY:11//12//	which calls which     : upkeep -> subscriptions.Expire(cross, id)13//	                        upkeep -> timelock_guardian.Execute(cross, id)14//	why it is necessary   : the reward must be conditioned on the valve15//	                        actually firing. Only making the call from16//	                        inside this realm ties "the valve fired" and17//	                        "the reward is credited" into one atomic18//	                        transaction; observing from outside cannot.19//	caller identity       : downstream sees cur.Previous() = THIS realm,20//	                        not the poking EOA. Both valves are21//	                        permissionless BY DESIGN and use the caller22//	                        identity for nothing, so the intermediary23//	                        changes no authorization outcome. This realm24//	                        is not a deputy for any downstream authority25//	                        — it holds none to confuse.26//	authorization boundary: upstream, anyone may poke (the reward is the27//	                        only thing at stake and the pot is the only28//	                        source). Downstream, each valve enforces its29//	                        own STATE conditions (grace elapsed, delay30//	                        matured) exactly as it would for a direct31//	                        caller.32//	ordering              : downstream call FIRST, reward accounting33//	                        AFTER. A downstream abort therefore reverts34//	                        the whole transaction before any pot or35//	                        ledger mutation exists.36//	failure behavior      : any downstream panic (not expirable, too37//	                        early, already executed, unknown id...)38//	                        aborts this transaction. No partial state,39//	                        no reward, by VM atomicity — not by cleanup40//	                        code.41//	atomicity assumptions : a Gno transaction is all-or-nothing across42//	                        realm boundaries; there is no catch/recover43//	                        anywhere on this path (and none may be added44//	                        — recovery would break exactly this45//	                        guarantee).46//	value movement        : none crosses the boundary. Both valves move47//	                        no coins; the poke transaction must carry no48//	                        coins (subscriptions' assertNoSend reads the49//	                        ORIGIN envelope unconditionally — measured,50//	                        not assumed). Rewards move only inside this51//	                        realm's ledger, funded by explicit Fund52//	                        transactions.53//	downstream rejection  : reward denied automatically — the abort is54//	                        the denial.55//	downstream trust      : neither valve trusts nor validates the56//	                        caller; both validate state. This realm57//	                        symmetrically does not trust the downstream58//	                        REPLY beyond "it did not abort".59//	replay                : enforced downstream. A second Expire on the60//	                        same subscription aborts ("subscription is61//	                        expired"); a second Execute aborts ("action62//	                        already executed"). A poker cannot be paid63//	                        twice for one valve event.64//	adversarial callers   : an EOA or realm poking with bogus ids,65//	                        premature targets, or replays hits a66//	                        downstream abort and pays its own gas. The67//	                        remaining economic edge — manufacturing68//	                        expirable state to farm rewards — differs69//	                        per task. sub_expire: every farmed70//	                        subscription permanently locks a sub-record71//	                        storage deposit of roughly 30,000ugnot at72//	                        the observed 100ugnot/byte rate (Expire73//	                        flips status; it does not free the record),74//	                        which exceeds MaxReward before price and75//	                        gas — loss-making at any legal setting.76//	                        timelock_execute: a farmed Action record is77//	                        small and its deposit can sit below the78//	                        cap, so farming resistance there rests on79//	                        the admin keeping the setting below the80//	                        measured cycle cost. Funders trust the cap81//	                        AND the admin's reward policy, not the cap82//	                        alone. The pot is a donation either way —83//	                        farming can drain it, never third-party84//	                        balances.85//86// Rewards default to 0 per task; the admin sets them within the87// compile-time MaxReward. The pot only moves down via successful pokes88// and only up via Fund. Conservation: Held == pot + UsersTotal89// (+ out-of-band surplus, recoverable above that reserve by SweepDenom).90// The ledger's fee cap is 0 — no fee exists anywhere in this realm.91package upkeep9293import (94	"chain"95	"chain/runtime/unsafe"96	"strconv"9798	"gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coinio"99	"gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledger"100101	subs "gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/subscriptions"102	guardian "gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/timelock_guardian"103)104105// Denom is the only asset this realm accepts.106const Denom = "ugnot"107108// Task identifiers — the two supported valves.109const (110	TaskSubExpire       = "sub_expire"111	TaskTimelockExecute = "timelock_execute"112)113114// MaxReward is the compile-time ceiling on the per-poke reward:115// 20,000ugnot (0.02 GNOT). It bounds what any single poke can extract116// from the pot. For sub_expire it also defeats farming outright: one117// manufactured expirable subscription permanently locks a sub-record118// deposit of ~30,000ugnot at the observed 100ugnot/byte rate — already119// above the cap before price and gas. For timelock_execute the farmed120// Action record is smaller and its deposit can sit below the cap, so121// there the admin's setting, kept below the measured farm-cycle cost,122// is what deters farming. Funders trust the cap and the admin's123// policy together; the pot they fund is an explicit donation either124// way.125const MaxReward = int64(20000)126127var (128	admin        address // may set rewards, stage a successor129	pendingAdmin address // staged by TransferAdmin, completes via AcceptAdmin130	sweeper      address // may sweep out-of-band surplus131132	self address // this realm's address, captured at deploy133	pot  int64   // funded, not-yet-awarded ugnot134135	rewards = map[string]int64{136		TaskSubExpire:       0,137		TaskTimelockExecute: 0,138	}139	ledger = feeledger.MustNew(0)140141	// lifetime counters, rendered for operators142	pokes  int64143	funded int64144)145146func init() {147	admin = unsafe.OriginCaller()148	sweeper = admin149	self = unsafe.CurrentRealm().Address()150}151152// --- funding ---153154// Fund adds the attached coins to the reward pot. Anyone may fund;155// funding is a donation to ecosystem maintenance and is not refundable.156func Fund(cur realm) {157	funder, amount := coinio.Receive(0, cur, Denom)158	newPot, ok := checkedAdd(pot, amount)159	if !ok {160		panic("pot would overflow")161	}162	pot = newPot163	funded += amount164	chain.Emit("Funded", "from", funder.String(), "amount", itoa(amount),165		"pot", itoa(pot))166}167168// --- the pokes: the realm-to-realm calls ---169170// PokeExpire triggers subscriptions.Expire(subID) through this realm and171// credits the caller the sub_expire reward. The downstream realm decides172// whether the subscription is expirable; its abort is the authorization.173// The transaction must attach no coins (the downstream realm checks the174// origin envelope).175func PokeExpire(cur realm, subID int64) {176	assertNoSend()177	caller := cur.Previous().Address()178	reward := requireReward(TaskSubExpire)179180	// Downstream first: an abort here reverts everything below.181	subs.Expire(cross(cur), subID)182183	award(caller, reward, TaskSubExpire, itoa(subID))184}185186// PokeExecute triggers timelock_guardian.Execute(actionID) through this187// realm and credits the caller the timelock_execute reward. The guardian188// decides whether the action is executable; its abort is the189// authorization.190func PokeExecute(cur realm, actionID string) {191	assertNoSend()192	caller := cur.Previous().Address()193	reward := requireReward(TaskTimelockExecute)194195	guardian.Execute(cross(cur), actionID)196197	award(caller, reward, TaskTimelockExecute, actionID)198}199200// award moves reward from the pot to the caller's claimable balance.201// Callers have already established that the pot covers it.202func award(caller address, reward int64, task, target string) {203	pot -= reward204	ledger.MustDeposit(caller.String(), reward, 0)205	pokes++206	chain.Emit("Poked",207		"task", task,208		"target", target,209		"caller", caller.String(),210		"reward", itoa(reward),211		"pot", itoa(pot),212	)213}214215// requireReward returns the configured reward for a task, refusing when216// the task is unknown, unrewarded, or the pot cannot cover it. Refusal217// on an empty pot is deliberate: both valves remain directly callable on218// their own realms, so an unrewarded detour through this one is only a219// gas trap for the caller.220func requireReward(task string) int64 {221	r, ok := rewards[task]222	if !ok {223		panic("unknown task: " + task)224	}225	if r <= 0 {226		panic("no reward configured for " + task)227	}228	if pot < r {229		panic("reward pot cannot cover " + task + ": pot " + itoa(pot) +230			", reward " + itoa(r))231	}232	return r233}234235// --- claims ---236237// Claim sends amount ugnot of the caller's earned balance back to the238// caller.239func Claim(cur realm, amount int64) {240	assertNoSend()241	caller := cur.Previous().Address()242	if err := ledger.Withdraw(caller.String(), amount); err != nil {243		panic(err)244	}245	coinio.Payout(0, cur, caller, Denom, amount)246	chain.Emit("Claim", "to", caller.String(), "amount", itoa(amount))247}248249// ClaimAll sends the caller's entire earned balance back to the caller.250func ClaimAll(cur realm) {251	assertNoSend()252	caller := cur.Previous().Address()253	amount, err := ledger.WithdrawAll(caller.String())254	if err != nil {255		panic(err)256	}257	if amount == 0 {258		panic("nothing to claim")259	}260	coinio.Payout(0, cur, caller, Denom, amount)261	chain.Emit("Claim", "to", caller.String(), "amount", itoa(amount))262}263264// --- administration ---265266// SetReward configures the per-poke reward for a task, bounded by267// MaxReward. Admin only. Zero disables the task.268func SetReward(cur realm, task string, amount int64) {269	assertNoSend()270	assertAdmin(cur.Previous().Address())271	if _, ok := rewards[task]; !ok {272		panic("unknown task: " + task)273	}274	if amount < 0 || amount > MaxReward {275		panic("reward must be in [0, " + itoa(MaxReward) + "]")276	}277	old := rewards[task]278	rewards[task] = amount279	chain.Emit("RewardChanged", "task", task, "old", itoa(old), "new", itoa(amount))280}281282// SweepDenom recovers out-of-band coins to the sweeper. For the pot283// denom the reserve is pot + earned balances — both structurally284// unreachable. Sweeper only.285func SweepDenom(cur realm, denom string) {286	assertNoSend()287	if cur.Previous().Address() != sweeper {288		panic("sweeper only")289	}290	reserve := int64(0)291	if denom == Denom {292		reserve = pot + ledger.Liabilities()293	}294	swept := coinio.Sweep(0, cur, sweeper, denom, reserve)295	chain.Emit("SurplusSwept", "to", sweeper.String(), "coins", itoa(swept)+denom)296}297298// TransferAdmin stages a two-step admin handover.299func TransferAdmin(cur realm, successor address) {300	assertNoSend()301	assertAdmin(cur.Previous().Address())302	var zero address303	if successor == zero {304		panic("successor must not be the zero address")305	}306	pendingAdmin = successor307	chain.Emit("AdminTransferStaged", "from", admin.String(), "to", successor.String())308}309310// AcceptAdmin completes the handover; only the staged successor may.311// The sweeper role moves with the admin.312func AcceptAdmin(cur realm) {313	assertNoSend()314	caller := cur.Previous().Address()315	if caller != pendingAdmin {316		panic("only the staged successor may accept")317	}318	old := admin319	admin = caller320	sweeper = caller321	var zero address322	pendingAdmin = zero323	chain.Emit("AdminTransferred", "from", old.String(), "to", admin.String())324}325326// --- views ---327328func Admin() address        { return admin }329func PendingAdmin() address { return pendingAdmin }330func Pot() int64            { return pot }331func RewardFor(task string) int64 {332	r, ok := rewards[task]333	if !ok {334		panic("unknown task: " + task)335	}336	return r337}338func BalanceOf(a address) int64 { return ledger.BalanceOf(a.String()) }339func UsersTotal() int64         { return ledger.UsersTotal() }340func Pokes() int64              { return pokes }341func Funded() int64             { return funded }342func Address() address          { return self }343func Held() int64               { return coinio.HeldAt(self, Denom) }344345// --- render ---346347func Render(path string) string {348	if path != "" {349		return "unknown page; try the realm root"350	}351	out := "# upkeep\n\n"352	out += "Rewards for running the ecosystem's permissionless valves. " +353		"Fund the pot; poke a valve through this realm; claim what you " +354		"earn. A poke succeeds only when the downstream realm accepts " +355		"the valve call in the same transaction.\n\n"356	out += "- pot: " + itoa(pot) + Denom + "\n"357	out += "- earned, unclaimed: " + itoa(ledger.UsersTotal()) + Denom + "\n"358	out += "- lifetime pokes: " + itoa(pokes) + "\n"359	out += "- lifetime funding: " + itoa(funded) + Denom + "\n\n"360	out += "## rewards per poke\n\n"361	out += "- `" + TaskSubExpire + "` (subscriptions.Expire): " +362		itoa(rewards[TaskSubExpire]) + Denom + "\n"363	out += "- `" + TaskTimelockExecute + "` (timelock_guardian.Execute): " +364		itoa(rewards[TaskTimelockExecute]) + Denom + "\n"365	out += "\n(cap per poke: " + itoa(MaxReward) + Denom + ", compile-time)\n"366	return out367}368369// --- internals ---370371func assertNoSend() {372	if len(unsafe.OriginSend()) != 0 {373		panic("this function does not accept coins")374	}375}376377func assertAdmin(caller address) {378	if caller != admin {379		panic("admin only")380	}381}382383func checkedAdd(a, b int64) (int64, bool) {384	if b > 0 && a > int64(^uint64(0)>>1)-b {385		return 0, false386	}387	return a + b, true388}389390func itoa(n int64) string { return strconv.FormatInt(n, 10) }391

Functions

  • AcceptAdmin(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})

  • Address() string

  • Admin() string

  • BalanceOf(a string) int64

  • Claim(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, amount int64)

  • ClaimAll(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})

  • Fund(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})

  • Funded() int64

  • Held() int64

  • PendingAdmin() string

  • PokeExecute(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, actionID string)

  • PokeExpire(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, subID int64)

  • Pokes() int64

  • Pot() int64

  • Render(path string) string

  • RewardFor(task string) int64

  • SetReward(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, task string, amount int64)

  • SweepDenom(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, denom string)

  • TransferAdmin(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, successor string)

  • UsersTotal() int64

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

Rendered

RenderedRawgnoweb ↗

vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.