faucet.gno
3.83 Kb · 135 lines
1// Package faucet is a toy points faucet realm for the gno.land test13 testnet.
2//
3// Any caller may Claim() 100 points, but only once per cooldown window of 100
4// blocks. Cooldown is enforced deterministically via runtime.ChainHeight() — no
5// real clocks, no RNG. Per-caller balance and last-claim height live in an
6// avl.Tree so Render iterates in deterministic (address-sorted) order.
7package faucet
8
9import (
10 "chain"
11 "chain/runtime"
12 "chain/runtime/unsafe"
13 "strconv"
14 "strings"
15
16 "gno.land/p/nt/avl/v0"
17)
18
19const (
20 // ClaimAmount is granted on every successful claim.
21 ClaimAmount = 100
22 // CooldownBlocks is the minimum number of blocks between two claims by the
23 // same caller.
24 CooldownBlocks = 100
25)
26
27// claimer holds the per-address state.
28type claimer struct {
29 Balance int64 // total points accumulated
30 LastClaim int64 // chain height of the last successful claim
31 Claims int64 // number of successful claims by this address
32}
33
34var (
35 // claimers maps address string -> *claimer.
36 claimers = avl.NewTree()
37 // totalDistributed is the running sum of all points ever granted.
38 totalDistributed int64
39 // totalClaims is the total number of successful claims across all callers.
40 totalClaims int64
41)
42
43// Claim grants ClaimAmount points to the caller, subject to the per-caller
44// cooldown. It aborts (cross-realm) if the caller must still wait.
45func Claim(cur realm) {
46 caller := unsafe.PreviousRealm().Address()
47 key := caller.String()
48 height := runtime.ChainHeight()
49
50 var c *claimer
51 if v, ok := claimers.Get(key); ok {
52 c = v.(*claimer)
53 wait := c.LastClaim + CooldownBlocks - height
54 if wait > 0 {
55 panic("cooldown active: " + strconv.FormatInt(wait, 10) +
56 " block(s) remaining before you can claim again")
57 }
58 } else {
59 c = &claimer{}
60 }
61
62 c.Balance += ClaimAmount
63 c.LastClaim = height
64 c.Claims++
65 claimers.Set(key, c)
66
67 totalDistributed += ClaimAmount
68 totalClaims++
69
70 chain.Emit(
71 "Claim",
72 "caller", key,
73 "amount", strconv.FormatInt(ClaimAmount, 10),
74 "balance", strconv.FormatInt(c.Balance, 10),
75 "height", strconv.FormatInt(height, 10),
76 )
77}
78
79// BalanceOf returns the current point balance of addr (0 if never claimed).
80func BalanceOf(addr string) int64 {
81 if v, ok := claimers.Get(addr); ok {
82 return v.(*claimer).Balance
83 }
84 return 0
85}
86
87// Render produces a Markdown dashboard of faucet activity.
88func Render(path string) string {
89 var b strings.Builder
90
91 b.WriteString("# Token Faucet\n\n")
92 b.WriteString("A toy points faucet. Call `Claim` to receive **")
93 b.WriteString(strconv.FormatInt(ClaimAmount, 10))
94 b.WriteString(" points** — but only once every **")
95 b.WriteString(strconv.FormatInt(CooldownBlocks, 10))
96 b.WriteString(" blocks** per address.\n\n")
97
98 b.WriteString("## Stats\n\n")
99 b.WriteString("- Total distributed: **")
100 b.WriteString(strconv.FormatInt(totalDistributed, 10))
101 b.WriteString("** points\n")
102 b.WriteString("- Total claims: **")
103 b.WriteString(strconv.FormatInt(totalClaims, 10))
104 b.WriteString("**\n")
105 b.WriteString("- Unique claimers: **")
106 b.WriteString(strconv.Itoa(claimers.Size()))
107 b.WriteString("**\n")
108 b.WriteString("- Current block height: **")
109 b.WriteString(strconv.FormatInt(runtime.ChainHeight(), 10))
110 b.WriteString("**\n\n")
111
112 b.WriteString("## Claimers\n\n")
113 if claimers.Size() == 0 {
114 b.WriteString("_No one has claimed yet. Be the first!_\n")
115 return b.String()
116 }
117
118 b.WriteString("| Address | Balance | Claims | Last claim (height) |\n")
119 b.WriteString("| --- | ---: | ---: | ---: |\n")
120 claimers.Iterate("", "", func(key string, value interface{}) bool {
121 c := value.(*claimer)
122 b.WriteString("| `")
123 b.WriteString(key)
124 b.WriteString("` | ")
125 b.WriteString(strconv.FormatInt(c.Balance, 10))
126 b.WriteString(" | ")
127 b.WriteString(strconv.FormatInt(c.Claims, 10))
128 b.WriteString(" | ")
129 b.WriteString(strconv.FormatInt(c.LastClaim, 10))
130 b.WriteString(" |\n")
131 return false
132 })
133
134 return b.String()
135}