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.

fishmark.gno

7.56 Kb · 303 lines
  1// Package fishmark is a second trial FISH ledger for the fishbay realm.
  2// Init mints a fixed supply and no later call can mint. Wallet transfers are not taxed.
  3// The symbol is FISH. TokenID is the chain identity, because fishcoin and the NFT
  4// collection use the same letters. SetSpender can be called only after Init, and only once.
  5// Point it at fishbay after that realm's Realm() is known. Do not point it at shoal/fish.
  6package fishmark
  7
  8import (
  9	"chain"
 10	"chain/runtime/unsafe"
 11	"strconv"
 12
 13	"gno.land/p/nt/grc20/v0"
 14	"gno.land/p/nt/ownable/v0"
 15)
 16
 17const (
 18	unit            int64 = 1_000_000
 19	supplyWhole     int64 = 100_000_000
 20	seasonWhole     int64 = 43_000_000
 21	liquidityWhole  int64 = 20_000_000
 22	ecosystemWhole  int64 = 15_000_000
 23	communityWhole  int64 = 12_000_000
 24	teamWhole       int64 = 10_000_000
 25	maxBuyTaxBps    int64 = 500
 26	maxSellTaxBps   int64 = 1000
 27	defaultBuyTax   int64 = 200
 28	defaultSellTax  int64 = 400
 29)
 30
 31var (
 32	token     *grc20.Token
 33	ledger    *grc20.PrivateLedger
 34	admin     *ownable.Ownable
 35	inited     bool
 36	sealed     bool
 37	spender    address
 38	spenderSet bool
 39	buyTaxBps  int64 = defaultBuyTax
 40	sellTaxBps int64 = defaultSellTax
 41)
 42
 43// Init mints the fixed supply once and seals the ledger. The five treasuries must be distinct.
 44func Init(cur realm, season, liquidity, ecosystem, community, team address) {
 45	if !cur.IsCurrent() {
 46		panic("fishmark: realm")
 47	}
 48	if !cur.Previous().IsUserCall() {
 49		panic("fishmark: eoa")
 50	}
 51	takeExact(0)
 52	if inited {
 53		panic("fishmark: init")
 54	}
 55	if !distinct(season, liquidity, ecosystem, community, team) {
 56		panic("fishmark: addr")
 57	}
 58	tok, led := grc20.NewToken("Fishmark", "FISH", 6, 0, cur)
 59	credit(led, season, seasonWhole*unit)
 60	credit(led, liquidity, liquidityWhole*unit)
 61	credit(led, ecosystem, ecosystemWhole*unit)
 62	credit(led, community, communityWhole*unit)
 63	credit(led, team, teamWhole*unit)
 64	if tok.TotalSupply() != supplyWhole*unit {
 65		panic("fishmark: supply")
 66	}
 67	token = tok
 68	ledger = led
 69	admin = ownable.NewWithAddress(cur.Previous().Address())
 70	inited = true
 71	sealed = true
 72	chain.Emit(
 73		"Init",
 74		"token", tok.ID(),
 75		"admin", cur.Previous().Address().String(),
 76		"supply", strconv.FormatInt(supplyWhole*unit, 10),
 77	)
 78}
 79
 80// Transfer moves FISH from the caller to to. The amount is not taxed.
 81func Transfer(cur realm, to address, amount int64) {
 82	requireUser(cur)
 83	takeExact(0)
 84	requireReady()
 85	must(ledger.CallerTeller().Transfer(0, cur, to, amount))
 86	chain.Emit("Send", "to", to.String(), "amount", strconv.FormatInt(amount, 10))
 87}
 88
 89// Approve lets spender move up to amount from the caller. This replaces the previous allowance.
 90func Approve(cur realm, spender address, amount int64) {
 91	requireUser(cur)
 92	takeExact(0)
 93	requireReady()
 94	must(ledger.CallerTeller().Approve(0, cur, spender, amount))
 95	chain.Emit("Approve", "spender", spender.String(), "amount", strconv.FormatInt(amount, 10))
 96}
 97
 98// TransferFrom moves amount from owner to to, using the caller's allowance. The amount is not taxed.
 99func TransferFrom(cur realm, owner, to address, amount int64) {
100	requireUser(cur)
101	takeExact(0)
102	requireReady()
103	must(ledger.CallerTeller().TransferFrom(0, cur, owner, to, amount))
104	chain.Emit("SendFrom", "owner", owner.String(), "to", to.String(), "amount", strconv.FormatInt(amount, 10))
105}
106
107// SetBuyTaxBps stores the buy tax for a later pool. The cap is 500 bps. This does not tax wallets.
108func SetBuyTaxBps(cur realm, next int64) {
109	caller := requireUser(cur)
110	takeExact(0)
111	requireReady()
112	if !admin.OwnedBy(caller) {
113		panic("fishmark: admin")
114	}
115	if next < 0 || next > maxBuyTaxBps {
116		panic("fishmark: tax")
117	}
118	buyTaxBps = next
119	chain.Emit("SetBuyTaxBps", "bps", strconv.FormatInt(next, 10))
120}
121
122// SetSellTaxBps stores the sell tax for a later pool. The cap is 1000 bps. This does not tax wallets.
123func SetSellTaxBps(cur realm, next int64) {
124	caller := requireUser(cur)
125	takeExact(0)
126	requireReady()
127	if !admin.OwnedBy(caller) {
128		panic("fishmark: admin")
129	}
130	if next < 0 || next > maxSellTaxBps {
131		panic("fishmark: tax")
132	}
133	sellTaxBps = next
134	chain.Emit("SetSellTaxBps", "bps", strconv.FormatInt(next, 10))
135}
136
137// TransferAdmin hands the tax hook to next. Balances stay where they are.
138func TransferAdmin(cur realm, next address) {
139	requireUser(cur)
140	takeExact(0)
141	requireReady()
142	if !next.IsValid() {
143		panic("fishmark: admin")
144	}
145	if err := admin.TransferOwnership(0, cur, next); err != nil {
146		panic("fishmark: admin")
147	}
148	chain.Emit("TransferAdmin", "next", next.String())
149}
150
151func Name() string {
152	requireReady()
153	return token.GetName()
154}
155
156func Symbol() string {
157	requireReady()
158	return token.GetSymbol()
159}
160
161func Decimals() int {
162	requireReady()
163	return token.GetDecimals()
164}
165
166func TotalSupply() int64 {
167	requireReady()
168	return token.TotalSupply()
169}
170
171func BalanceOf(who address) int64 {
172	requireReady()
173	return token.BalanceOf(who)
174}
175
176func Allowance(owner, spender address) int64 {
177	requireReady()
178	return token.Allowance(owner, spender)
179}
180
181func TokenID() string {
182	requireReady()
183	return token.ID()
184}
185
186func BuyTaxBps() int64  { return buyTaxBps }
187func SellTaxBps() int64 { return sellTaxBps }
188func Version() string   { return "fishmark-1" }
189func Sealed() bool      { return sealed }
190
191// Spender is the one realm allowed to Draw. It is empty until SetSpender.
192func Spender() address { return spender }
193
194// SetSpender records the fish realm once. A later call cannot replace it.
195func SetSpender(cur realm, who address) {
196	caller := requireUser(cur)
197	takeExact(0)
198	requireReady()
199	if !admin.OwnedBy(caller) {
200		panic("fishmark: admin")
201	}
202	if spenderSet || !who.IsValid() {
203		panic("fishmark: spender")
204	}
205	spender = who
206	spenderSet = true
207	chain.Emit("SetSpender", "who", who.String())
208}
209
210// Draw moves min(amount, balance, allowance) from owner to to.
211// Only the recorded spender may call it. The owner must have approved that spender.
212// A short balance or allowance moves nothing and does not panic. This does not mint.
213func Draw(cur realm, owner, to address, amount int64) int64 {
214	if !cur.IsCurrent() {
215		panic("fishmark: realm")
216	}
217	if !spenderSet || cur.Previous().Address() != spender {
218		panic("fishmark: spender")
219	}
220	takeExact(0)
221	requireReady()
222	if amount <= 0 || !owner.IsValid() || !to.IsValid() {
223		return 0
224	}
225	bal := token.BalanceOf(owner)
226	if amount > bal {
227		amount = bal
228	}
229	allow := token.Allowance(owner, spender)
230	if amount > allow {
231		amount = allow
232	}
233	if amount <= 0 {
234		return 0
235	}
236	must(ledger.CallerTeller().TransferFrom(0, cur, owner, to, amount))
237	chain.Emit("Draw", "owner", owner.String(), "to", to.String(), "amount", strconv.FormatInt(amount, 10))
238	return amount
239}
240
241func Admin() address {
242	if admin == nil {
243		return address("")
244	}
245	return admin.Owner()
246}
247
248func Render(path string) string {
249	if path != "" {
250		return "# Fishmark\n\nUnknown path.\n"
251	}
252	return "# Fishmark\n\nFixed supply. Wallet transfers are not taxed.\n\nThis ledger is for fishbay. TokenID is the chain identity.\n"
253}
254
255func credit(led *grc20.PrivateLedger, who address, amount int64) {
256	if err := led.Mint(who, amount); err != nil {
257		panic(err)
258	}
259}
260
261func must(err error) {
262	if err != nil {
263		panic(err)
264	}
265}
266
267func requireReady() {
268	if !inited || !sealed || token == nil || ledger == nil || admin == nil {
269		panic("fishmark: init")
270	}
271}
272
273func requireUser(cur realm) address {
274	if !cur.Previous().IsUserCall() {
275		panic("fishmark: eoa")
276	}
277	return cur.Previous().Address()
278}
279
280func takeExact(n int64) {
281	sent := unsafe.OriginSend()
282	if n == 0 {
283		if len(sent) != 0 {
284			panic("fishmark: payment")
285		}
286		return
287	}
288	panic("fishmark: payment")
289}
290
291func distinct(xs ...address) bool {
292	for i, a := range xs {
293		if !a.IsValid() {
294			return false
295		}
296		for j := 0; j < i; j++ {
297			if xs[j] == a {
298				return false
299			}
300		}
301	}
302	return true
303}