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

faucet.gno

5.53 Kb · 175 lines
  1// Package grc20faucet issues two free GRC20 tokens, RED and BLUE, and gives
  2// them away on request.
  3//
  4// It exists so that anything needing a token to experiment on has one that is
  5// worthless, unlimited and shared. Both tokens are registered in
  6// gno.land/r/nt/grc20reg/v0, which is what lets another realm find and move
  7// them by key without importing this package: that indirection is the whole
  8// point, and it is what gno.land/r/moul/x/grc20wrapdemo/v0 wraps.
  9//
 10// Two tokens rather than one, because the interesting patterns start at two: a
 11// meta-token over a pair needs a pair.
 12package grc20faucet
 13
 14import (
 15	"chain/runtime"
 16
 17	"gno.land/p/nt/avl/v0"
 18	"gno.land/p/nt/grc20/v0"
 19	"gno.land/p/nt/ufmt/v0"
 20	"gno.land/r/nt/grc20reg/v0"
 21)
 22
 23// Tokens are exported so an importing realm can read them directly; the
 24// ledgers stay private, since they carry unrestricted mint and burn.
 25var (
 26	Red  *grc20.Token
 27	Blue *grc20.Token
 28
 29	redLedger  *grc20.PrivateLedger
 30	blueLedger *grc20.PrivateLedger
 31
 32	// RedKey and BlueKey are the grc20reg lookup keys, the way a realm that
 33	// does NOT import this package refers to these tokens.
 34	RedKey  string
 35	BlueKey string
 36
 37	lastClaim = avl.NewTree() // address -> block height of its last claim
 38)
 39
 40const (
 41	decimals = 4
 42
 43	// ClaimAmount is minted of EACH token per claim: 100.0000 units.
 44	ClaimAmount = int64(1_000_000)
 45
 46	// ClaimEvery is the cooldown, in blocks, between two claims by the same
 47	// account. Free money with no cooldown is a spam vector, not a faucet.
 48	ClaimEvery = int64(100)
 49)
 50
 51func init(cur realm) {
 52	Red, redLedger = grc20.NewToken("Red Token", "RED", decimals, 0, cur)
 53	Blue, blueLedger = grc20.NewToken("Blue Token", "BLUE", decimals, 1, cur)
 54	RedKey = grc20reg.Register(cross(cur), Red, "red")
 55	BlueKey = grc20reg.Register(cross(cur), Blue, "blue")
 56}
 57
 58// Claim mints ClaimAmount of both RED and BLUE to the caller.
 59func Claim(cur realm) {
 60	who := caller(cur)
 61	now := runtime.ChainHeight()
 62	if next, ok := NextClaim(who); ok && now < next {
 63		panic(ufmt.Sprintf("too soon: next claim at block %d, current %d", next, now))
 64	}
 65	lastClaim.Set(who.String(), now)
 66	checkErr(redLedger.Mint(who, ClaimAmount))
 67	checkErr(blueLedger.Mint(who, ClaimAmount))
 68}
 69
 70// NextClaim returns the block height at which `who` may claim again, and
 71// whether they have ever claimed at all.
 72func NextClaim(who address) (int64, bool) {
 73	v := lastClaim.Get(who.String())
 74	if v == nil {
 75		return 0, false
 76	}
 77	return v.(int64) + ClaimEvery, true
 78}
 79
 80// Transfer moves the caller's own units of `symbol`.
 81func Transfer(cur realm, symbol string, to address, amount int64) {
 82	checkErr(ledgerOf(symbol).CallerTeller().Transfer(0, cur, to, amount))
 83}
 84
 85// Approve lets `spender` draw `amount` of `symbol` from the caller's balance.
 86//
 87// This is the call that makes every wrapper in grc20wrapdemo work: the wrapper
 88// realm has no authority over anybody's tokens until its address is named here.
 89func Approve(cur realm, symbol string, spender address, amount int64) {
 90	checkErr(ledgerOf(symbol).CallerTeller().Approve(0, cur, spender, amount))
 91}
 92
 93// TransferFrom spends an allowance the caller was granted.
 94func TransferFrom(cur realm, symbol string, from, to address, amount int64) {
 95	checkErr(ledgerOf(symbol).CallerTeller().TransferFrom(0, cur, from, to, amount))
 96}
 97
 98// BalanceOf returns `owner`'s balance of `symbol`.
 99func BalanceOf(symbol string, owner address) int64 {
100	return tokenOf(symbol).BalanceOf(owner)
101}
102
103// Allowance returns what `owner` let `spender` draw of `symbol`.
104func Allowance(symbol string, owner, spender address) int64 {
105	return tokenOf(symbol).Allowance(owner, spender)
106}
107
108// TotalSupply returns how much of `symbol` has been claimed so far.
109func TotalSupply(symbol string) int64 {
110	return tokenOf(symbol).TotalSupply()
111}
112
113func tokenOf(symbol string) *grc20.Token {
114	switch symbol {
115	case "RED":
116		return Red
117	case "BLUE":
118		return Blue
119	}
120	panic("unknown symbol " + symbol + " (RED or BLUE)")
121}
122
123func ledgerOf(symbol string) *grc20.PrivateLedger {
124	switch symbol {
125	case "RED":
126		return redLedger
127	case "BLUE":
128		return blueLedger
129	}
130	panic("unknown symbol " + symbol + " (RED or BLUE)")
131}
132
133// caller is the account or realm that crossed into this one.
134func caller(cur realm) address {
135	if !cur.IsCurrent() {
136		panic("grc20faucet: stale realm token")
137	}
138	return cur.Previous().Address()
139}
140
141func checkErr(err error) {
142	if err != nil {
143		panic(err)
144	}
145}
146
147func Render(path string) string {
148	s := "# GRC20 faucet: RED and BLUE\n\n"
149	s += "Two worthless tokens to experiment on. `Claim` gives you "
150	s += ufmt.Sprintf("%d of each, once every %d blocks.\n\n", ClaimAmount, ClaimEvery)
151
152	s += "| token | symbol | decimals | supply | registry key |\n"
153	s += "|---|---|---|---|---|\n"
154	s += ufmt.Sprintf("| %s | RED | %d | %d | `%s` |\n",
155		Red.GetName(), Red.GetDecimals(), Red.TotalSupply(), RedKey)
156	s += ufmt.Sprintf("| %s | BLUE | %d | %d | `%s` |\n",
157		Blue.GetName(), Blue.GetDecimals(), Blue.TotalSupply(), BlueKey)
158
159	s += "\n## Use it\n\n"
160	s += "```\n"
161	s += "gnokey maketx call -pkgpath gno.land/r/moul/x/grc20faucet/v0 -func Claim ...\n"
162	s += "gnokey maketx call -pkgpath gno.land/r/moul/x/grc20faucet/v0 -func Approve \\\n"
163	s += "  -args RED -args <spender> -args 1000000 ...\n"
164	s += "```\n\n"
165	s += "Both tokens are registered in [grc20reg](/r/nt/grc20reg/v0), so a realm can\n"
166	s += "move them by key without importing this one. That is what\n"
167	s += "[grc20wrapdemo](/r/moul/x/grc20wrapdemo/v0) does with them.\n"
168
169	if path != "" {
170		s += "\n---\n\n"
171		s += ufmt.Sprintf("Balance of `%s`: RED %d, BLUE %d\n",
172			path, Red.BalanceOf(address(path)), Blue.BalanceOf(address(path)))
173	}
174	return s
175}