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

fishtoken.gno

8.57 Kb · 339 lines
  1// Package fishtoken is the unreleased GnoFish FISH ledger.
  2// Init mints a fixed supply once. No later call can mint.
  3// While transfers are closed, a normal wallet cannot send FISH, so it cannot
  4// fund a pool or sell through a market. The deploy wallet opens transfers.
  5// The five treasury wallets, the deploy wallet, and a reward realm the deploy
  6// wallet has granted may still send. This realm has no pool and no trade.
  7// Public-sale FISH is minted to the deploy wallet. fishtoken-1.
  8package fishtoken
  9
 10import (
 11	"chain"
 12	"chain/runtime/unsafe"
 13	"strconv"
 14
 15	"gno.land/p/nt/grc20/v0"
 16	"gno.land/p/nt/seqid/v0"
 17)
 18
 19const (
 20	unit            int64 = 1_000_000
 21	supplyWhole     int64 = 100_000_000
 22	seasonWhole     int64 = 43_000_000
 23	liquidityWhole  int64 = 20_000_000
 24	ecosystemWhole  int64 = 15_000_000
 25	communityWhole  int64 = 12_000_000
 26	teamWhole       int64 = 5_000_000
 27	saleWhole       int64 = 5_000_000
 28)
 29
 30var (
 31	seasonAddr    = address("g1m6z3l9q577dltj73ym8fu0068peupqg468r5xe")
 32	liquidityAddr = address("g1lepzz7lehwfnwng0ddxaglz2kzmsf6pyy5f6tw")
 33	ecosystemAddr = address("g1p4cylgemajmw93e9a0nr0fmph60gee2yhkn2kz")
 34	communityAddr = address("g1dpklfykp2mrhcwccek54scv43kamtxlszya2z8")
 35	teamAddr      = address("g15ejerz2n0xn5x94zezhquvwvp98m3jgkcwdrya")
 36)
 37
 38var (
 39	token     *grc20.Token
 40	ledger    *grc20.PrivateLedger
 41	seq       seqid.ID
 42	inited    bool
 43	open      bool
 44	admin     address
 45	mover     map[address]bool
 46	payer     map[address]bool
 47)
 48
 49// Init mints the fixed supply once. The caller is the deploy wallet and receives the public-sale FISH. No coins.
 50func Init(cur realm) {
 51	caller := requireUser(cur)
 52	takeExact(0)
 53	if inited {
 54		panic("fishtoken: init")
 55	}
 56	if !distinct(caller, seasonAddr, liquidityAddr, ecosystemAddr, communityAddr, teamAddr) {
 57		panic("fishtoken: addr")
 58	}
 59	tok, led := grc20.NewToken("FISH", "FISH", 6, seq.Next(), cur)
 60	credit(led, seasonAddr, seasonWhole*unit)
 61	credit(led, liquidityAddr, liquidityWhole*unit)
 62	credit(led, ecosystemAddr, ecosystemWhole*unit)
 63	credit(led, communityAddr, communityWhole*unit)
 64	credit(led, teamAddr, teamWhole*unit)
 65	credit(led, caller, saleWhole*unit)
 66	if tok.TotalSupply() != supplyWhole*unit {
 67		panic("fishtoken: supply")
 68	}
 69	token = tok
 70	ledger = led
 71	admin = caller
 72	open = false
 73	mover = map[address]bool{
 74		caller:        true,
 75		seasonAddr:    true,
 76		liquidityAddr: true,
 77		ecosystemAddr: true,
 78		communityAddr: true,
 79		teamAddr:      true,
 80	}
 81	payer = map[address]bool{}
 82	inited = true
 83	chain.Emit("Init", "admin", caller.String(), "supply", strconv.FormatInt(supplyWhole*unit, 10))
 84}
 85
 86// SetTransfers opens or closes ordinary wallet sends. The deploy wallet only. No coins.
 87func SetTransfers(cur realm, next bool) {
 88	caller := requireUser(cur)
 89	takeExact(0)
 90	requireReady()
 91	requireAdmin(caller)
 92	open = next
 93	chain.Emit("SetTransfers", "open", strconv.FormatBool(next))
 94}
 95
 96// GrantMover lets that wallet send FISH while transfers are closed. The deploy wallet only. No coins.
 97func GrantMover(cur realm, who address) {
 98	caller := requireUser(cur)
 99	takeExact(0)
100	requireReady()
101	requireAdmin(caller)
102	if !who.IsValid() {
103		panic("fishtoken: addr")
104	}
105	mover[who] = true
106	chain.Emit("GrantMover", "who", who.String())
107}
108
109// RevokeMover removes that wallet from the closed-transfer list. The deploy wallet only. No coins.
110func RevokeMover(cur realm, who address) {
111	caller := requireUser(cur)
112	takeExact(0)
113	requireReady()
114	requireAdmin(caller)
115	if !who.IsValid() {
116		panic("fishtoken: addr")
117	}
118	delete(mover, who)
119	chain.Emit("RevokeMover", "who", who.String())
120}
121
122// GrantDistributor lets that realm pay FISH to users while transfers are closed. The deploy wallet only. No coins.
123func GrantDistributor(cur realm, who address) {
124	caller := requireUser(cur)
125	takeExact(0)
126	requireReady()
127	requireAdmin(caller)
128	if !who.IsValid() {
129		panic("fishtoken: addr")
130	}
131	payer[who] = true
132	chain.Emit("GrantDistributor", "who", who.String())
133}
134
135// RevokeDistributor removes that reward realm. The deploy wallet only. No coins.
136func RevokeDistributor(cur realm, who address) {
137	caller := requireUser(cur)
138	takeExact(0)
139	requireReady()
140	requireAdmin(caller)
141	if !who.IsValid() {
142		panic("fishtoken: addr")
143	}
144	delete(payer, who)
145	chain.Emit("RevokeDistributor", "who", who.String())
146}
147
148// TransferAdmin hands the switch to next. Balances stay where they are. No coins.
149func TransferAdmin(cur realm, next address) {
150	caller := requireUser(cur)
151	takeExact(0)
152	requireReady()
153	requireAdmin(caller)
154	if !next.IsValid() {
155		panic("fishtoken: admin")
156	}
157	admin = next
158	mover[next] = true
159	chain.Emit("TransferAdmin", "next", next.String())
160}
161
162// Transfer sends FISH from the caller to to.
163// A closed ledger rejects an ordinary wallet. Listed wallets and granted reward realms may still send.
164func Transfer(cur realm, to address, amount int64) {
165	caller := cur.Previous().Address()
166	if cur.Previous().IsUserCall() {
167		takeExact(0)
168	}
169	requireReady()
170	requireAmount(amount, to)
171	if !mayMove(cur, caller, caller) {
172		panic("fishtoken: closed")
173	}
174	must(ledger.CallerTeller().Transfer(0, cur, to, amount))
175	chain.Emit("Send", "to", to.String(), "amount", strconv.FormatInt(amount, 10))
176}
177
178// Approve sets the spender allowance. It does not move FISH and does not open trading. No coins from an EOA.
179func Approve(cur realm, spender address, amount int64) {
180	if cur.Previous().IsUserCall() {
181		takeExact(0)
182	} else if !payer[cur.Previous().Address()] {
183		panic("fishtoken: closed")
184	}
185	requireReady()
186	if amount < 0 || !spender.IsValid() {
187		panic("fishtoken: amount")
188	}
189	must(ledger.CallerTeller().Approve(0, cur, spender, amount))
190	chain.Emit("Approve", "spender", spender.String(), "amount", strconv.FormatInt(amount, 10))
191}
192
193// TransferFrom sends FISH using the caller's allowance.
194// While closed, only a granted reward realm may pull, and only from its own balance or from a listed wallet.
195func TransferFrom(cur realm, owner, to address, amount int64) {
196	caller := cur.Previous().Address()
197	if cur.Previous().IsUserCall() {
198		takeExact(0)
199	}
200	requireReady()
201	requireAmount(amount, to)
202	if !mayMove(cur, caller, owner) {
203		panic("fishtoken: closed")
204	}
205	must(ledger.CallerTeller().TransferFrom(0, cur, owner, to, amount))
206	chain.Emit("SendFrom", "owner", owner.String(), "to", to.String(), "amount", strconv.FormatInt(amount, 10))
207}
208
209func TransfersOpen() bool { return open }
210func IsMover(who address) bool {
211	return mover[who]
212}
213func IsDistributor(who address) bool { return payer[who] }
214func Version() string                { return "fishtoken-1" }
215func Admin() address                 { return admin }
216
217func Name() string {
218	requireReady()
219	return token.GetName()
220}
221
222func Symbol() string {
223	requireReady()
224	return token.GetSymbol()
225}
226
227func Decimals() int {
228	requireReady()
229	return token.GetDecimals()
230}
231
232func TotalSupply() int64 {
233	requireReady()
234	return token.TotalSupply()
235}
236
237func BalanceOf(who address) int64 {
238	requireReady()
239	return token.BalanceOf(who)
240}
241
242func Allowance(owner, spender address) int64 {
243	requireReady()
244	return token.Allowance(owner, spender)
245}
246
247func TokenID() string {
248	requireReady()
249	return token.ID()
250}
251
252func Render(path string) string {
253	if path != "" {
254		return "# FISH\n\nUnknown path.\n"
255	}
256	if !inited {
257		return "# FISH\n\nNot opened.\n"
258	}
259	state := "closed"
260	if open {
261		state = "open"
262	}
263	return "# FISH\n\nFixed supply. Transfers are " + state + ". A closed ledger does not fund a pool and does not trade. Listed wallets and granted reward realms can still send. Public-sale FISH was minted to the deploy wallet.\n"
264}
265
266func mayMove(cur realm, caller, from address) bool {
267	if open {
268		return true
269	}
270	if cur.Previous().IsUserCall() {
271		return from == caller && mover[caller]
272	}
273	if !payer[caller] {
274		return false
275	}
276	return from == caller || mover[from]
277}
278
279func requireAdmin(caller address) {
280	if caller != admin {
281		panic("fishtoken: admin")
282	}
283}
284
285func requireAmount(amount int64, to address) {
286	if amount <= 0 || !to.IsValid() {
287		panic("fishtoken: amount")
288	}
289}
290
291func credit(led *grc20.PrivateLedger, who address, amount int64) {
292	if err := led.Mint(who, amount); err != nil {
293		panic(err)
294	}
295}
296
297func must(err error) {
298	if err != nil {
299		panic(err)
300	}
301}
302
303func requireReady() {
304	if !inited || token == nil || ledger == nil {
305		panic("fishtoken: init")
306	}
307}
308
309func requireUser(cur realm) address {
310	if !cur.Previous().IsUserCall() {
311		panic("fishtoken: eoa")
312	}
313	return cur.Previous().Address()
314}
315
316func takeExact(n int64) {
317	sent := unsafe.OriginSend()
318	if n == 0 {
319		if len(sent) != 0 {
320			panic("fishtoken: payment")
321		}
322		return
323	}
324	panic("fishtoken: payment")
325}
326
327func distinct(xs ...address) bool {
328	for i, a := range xs {
329		if !a.IsValid() {
330			return false
331		}
332		for j := 0; j < i; j++ {
333			if xs[j] == a {
334				return false
335			}
336		}
337	}
338	return true
339}