← Back to Realms

Realm

gno.land/r/nym-wizards001/drop

Overview

Realm Path
gno.land/r/nym-wizards001/drop
Exported Functions
53
State Entries
45
Source Files
2
Total Package Entries
116

Exported Functions

53 exported functions

State

45 state entries

Source Code

FILES
drop.gno
go
1// Package drop is the wizards token, symbol WIZ, version 4: the meme token and its genesis claim.
2//
3// Supply is 10_000_000_000 WIZ. Decimals are 6, so one WIZ is
4// tokenUnit base units and the mint is 10_000_000_000_000_000.
5//   - 75% airdrop, pro-rata to each wallet's genesis GNOT, paid from here
6//   - 15% liquidity, minted once to the pinned wallet g17xukdzx4w0q4tmendmsjy2luxk7lvfzv39nfkl
7//   - 7% liquidity incentives, minted once to the bound GovDAO realm
8//   - 3% community development, plus the pro-rata remainder, minted once
9//     to that same GovDAO realm
10//
11// A leaf is "g1...:<amount>". Claim pays that full amount once. The
12// deployer sets the claim start. The window is 90 days. Unclaimed airdrop
13// is burned. Claim attaches no GNOT. Any attached coins revert.
14// This realm has no withdraw. It cannot move the liquidity sleeve or the
15// GovDAO balance after Seal. GovDAO spends the incentive sleeve and the
16// community sleeve separately.
17// GovDAO lives in gno.land/r/nym-wizards001/gov. Holders vote there.
18// The realm does not recompute GNOT weights.
19// SetTradeOpen stores the instant other wallets may transfer. Until that
20// instant, only the liquidity wallet, the airdrop escrow, and GovDAO may
21// move WIZ through Transfer or TransferFrom. Claim and Seal move the
22// ledger directly and stay available. After SetPool, tokens that leave
23// that pool share one buy cap for a short window. The liquidity wallet
24// is never capped, so it can add liquidity during the window.
25package drop
26
27import (
28	"chain"
29	"chain/runtime/unsafe"
30	"crypto/merkle"
31	"encoding/hex"
32	"math"
33	"strconv"
34	"time"
35
36	"gno.land/p/nt/grc20/v0"
37	"gno.land/p/nt/avl/v0"
38	"gno.land/p/nt/ownable/v0"
39	"gno.land/p/nt/ufmt/v0"
40)
41
42const (
43	// tokenDecimals is the GRC20 display scale. One WIZ is tokenUnit base units.
44	tokenDecimals = 6
45	// tokenUnit is 10^tokenDecimals.
46	tokenUnit int64 = 1_000_000
47	// wholeSupply is the mint in whole WIZ. mintSupply is the same mint in base units.
48	// TotalSupply() is the circulating supply and shrinks when leftover airdrop is burned.
49	wholeSupply int64 = 10_000_000_000
50	mintSupply  int64 = wholeSupply * tokenUnit
51	// CommunityBps is 3% of supply. The pro-rata remainder is added on top.
52	CommunityBps int64 = 300
53	// IncentiveBps is the liquidity-incentive sleeve, 7% of supply.
54	IncentiveBps int64 = 700
55	// LiquidityBps is the liquidity sleeve, 15% of supply.
56	LiquidityBps int64 = 1_500
57	// BpsBasis is 100%.
58	BpsBasis int64 = 10_000
59
60	// ClaimWindow is the claim period after the deployer-set start: 90 days.
61	ClaimWindow int64 = 90 * 24 * 60 * 60
62	// VoteWindow is the longest a GovDAO ballot may lock a balance.
63	// gno.land/r/nym-wizards001/gov uses the same duration.
64	VoteWindow int64 = 7 * 24 * 60 * 60
65	// quorumBps is the yes-vote floor, measured against the sealed airdrop budget.
66	quorumBps int64 = 1_000
67
68	// BuyCapWindow is how long buys that leave the pool stay capped.
69	BuyCapWindow int64 = 10 * 60
70	// BuyCapBucket is one slice of that window. The cap resets each slice.
71	BuyCapBucket int64 = 60
72	// BuyCapPerBucket is 1% of the liquidity sleeve. Divide before multiply so the product fits in int64.
73	BuyCapPerBucket int64 = mintSupply / BpsBasis * LiquidityBps / 100
74
75	tokenVersion = "4"
76
77	maxProofAunts = 100
78	hashLen       = 32
79)
80
81// hold is the largest ballot still locking an address.
82type hold struct {
83	amount int64
84	until  int64
85}
86
87var (
88	// publisher is the addpkg signer. Captured once, when this package is initialized.
89	publisher = unsafe.OriginCaller()
90
91	token     *grc20.Token
92	ledger    *grc20.PrivateLedger
93	admin     *ownable.Ownable
94	adminAddr address
95
96	escrow address
97	gov    address
98
99	sealed         bool
100	root           []byte
101	eligible       int
102	allocated      int64
103	communitySent  int64
104	liquidityTo    address
105	liquiditySent  int64
106	incentiveSent  int64
107	airdropLeft    int64
108	claimStart     int64
109	tradeOpen      int64
110	burned         bool
111	claimed        avl.Tree
112	claimedSupply  int64
113	locks          avl.Tree
114
115	// pair is the pool. Buys are transfers that leave it.
116	pair      address
117	pairSetAt int64
118	buyBucket int64
119	buySpent  int64
120)
121
122// liquidityRecipient is the only wallet Seal may pay the 15% sleeve.
123var liquidityRecipient = address("g17xukdzx4w0q4tmendmsjy2luxk7lvfzv39nfkl")
124
125// Version is the token book. Version 4 sends liquidity to the named address
126// and sends incentive plus community to the bound GovDAO.
127func Version() string { return tokenVersion }
128
129// Init binds the addpkg signer as admin. Any other caller panics.
130func Init(cur realm) {
131	mustUser(cur)
132	mustNoPayment()
133	if token != nil {
134		panic("already initialized")
135	}
136	caller := cur.Previous().Address()
137	// A deployed package records the addpkg signer in publisher.
138	// The test VM leaves that address empty; the first Init caller is recorded.
139	if !publisher.IsValid() {
140		publisher = caller
141	}
142	if caller != publisher {
143		panic("deployer")
144	}
145	admin = ownable.NewWithAddress(caller)
146	adminAddr = caller
147	escrow = cur.Address()
148	token, ledger = grc20.NewToken("wizards", "WIZ", tokenDecimals, 0, cur)
149	chain.Emit("Init", "admin", caller.String(), "version", tokenVersion)
150}
151
152// TransferAdmin moves the admin seat. Admin cannot move funds.
153func TransferAdmin(cur realm, next address) {
154	mustAdmin(cur)
155	mustNoPayment()
156	if err := admin.TransferOwnership(0, cur, next); err != nil {
157		panic(err.Error())
158	}
159	adminAddr = next
160}
161
162// SetGov binds the GovDAO. The caller must be that realm, and the
163// transaction signer must be this token's admin. One call, before Seal.
164// Seal sends both the incentive sleeve and the community sleeve there.
165func SetGov(cur realm) {
166	mustCurrent(cur)
167	mustNoPayment()
168	if token == nil {
169		panic("not initialized")
170	}
171	if sealed {
172		panic("sealed")
173	}
174	if gov.IsValid() {
175		panic("gov set")
176	}
177	prev := cur.Previous()
178	if prev.IsUserCall() || prev.IsUserRun() {
179		panic("gov")
180	}
181	if unsafe.OriginCaller() != adminAddr {
182		panic("admin")
183	}
184	caller := prev.Address()
185	if !caller.IsValid() || caller == escrow {
186		panic("gov")
187	}
188	gov = caller
189	chain.Emit("SetGov", "gov", caller.String())
190}
191
192// Seal mints the whole supply, commits the eligible-set Merkle root, and
193// sends the sleeves out. airdropTotal is the sum of the leaf amounts,
194// at most the 75% pool. Integer dust is added to the community mint.
195// liquidityTo must be liquidityRecipient. Any other address panics.
196// Incentive and community both go to the realm bound with SetGov.
197func Seal(cur realm, rootHex string, eligibleCount int, airdropTotal int64, liquidityToArg address) {
198	mustAdmin(cur)
199	mustNoPayment()
200	if sealed {
201		panic("already sealed")
202	}
203	if eligibleCount <= 0 {
204		panic("eligible count")
205	}
206	decoded, err := hex.DecodeString(rootHex)
207	if err != nil || len(decoded) != hashLen {
208		panic("root")
209	}
210	communityAmt, incentiveAmt, liqAmt, pool := sleeves()
211	if airdropTotal <= 0 || airdropTotal > pool {
212		panic("allocated")
213	}
214	if !gov.IsValid() {
215		panic("gov")
216	}
217	if liquidityToArg != liquidityRecipient || liquidityToArg == escrow || liquidityToArg == gov {
218		panic("recipient")
219	}
220	communityAmt += pool - airdropTotal
221
222	mustMint(escrow, communityAmt+incentiveAmt+liqAmt+airdropTotal)
223	mustTransfer(escrow, liquidityToArg, liqAmt)
224	mustTransfer(escrow, gov, incentiveAmt)
225	mustTransfer(escrow, gov, communityAmt)
226
227	root = append([]byte(nil), decoded...)
228	eligible = eligibleCount
229	allocated = airdropTotal
230	communitySent = communityAmt
231	liquidityTo = liquidityToArg
232	liquiditySent = liqAmt
233	incentiveSent = incentiveAmt
234	airdropLeft = airdropTotal
235	sealed = true
236
237	chain.Emit(
238		"Seal",
239		"root", hex.EncodeToString(root),
240		"eligible", strconv.Itoa(eligibleCount),
241		"allocated", strconv.FormatInt(airdropTotal, 10),
242		"community", strconv.FormatInt(communityAmt, 10),
243		"liquidity", strconv.FormatInt(liqAmt, 10),
244		"incentive", strconv.FormatInt(incentiveAmt, 10),
245		"liquidityTo", liquidityToArg.String(),
246		"gov", gov.String(),
247	)
248}
249
250// SetClaimStart opens claims at start (unix seconds). The deployer may
251// call it once. start must be in the future. Claims run for ClaimWindow.
252func SetClaimStart(cur realm, start int64) {
253	mustAdmin(cur)
254	mustNoPayment()
255	if !sealed {
256		panic("not sealed")
257	}
258	if claimStart != 0 {
259		panic("claim start set")
260	}
261	if start <= time.Now().Unix() || start > math.MaxInt64-ClaimWindow {
262		panic("claim start")
263	}
264	claimStart = start
265	chain.Emit(
266		"SetClaimStart",
267		"start", strconv.FormatInt(start, 10),
268		"end", strconv.FormatInt(start+ClaimWindow, 10),
269	)
270}
271
272// SetTradeOpen stores the instant public transfers open. One call.
273// The instant has to be in the future. Until it arrives, Transfer and
274// TransferFrom move tokens only for the liquidity wallet, the airdrop
275// escrow, and the bound GovDAO.
276func SetTradeOpen(cur realm, start int64) {
277	mustAdmin(cur)
278	mustNoPayment()
279	if tradeOpen != 0 {
280		panic("trade open set")
281	}
282	if start <= time.Now().Unix() {
283		panic("trade open")
284	}
285	tradeOpen = start
286	chain.Emit("SetTradeOpen", "start", strconv.FormatInt(start, 10))
287}
288
289// SetPool registers the pool once. The buy cap starts at this call.
290// The liquidity wallet cannot be the pool. That wallet sends tokens
291// into the pool, and those sends are not buys.
292func SetPool(cur realm, market address) {
293	mustAdmin(cur)
294	mustNoPayment()
295	if pair.IsValid() {
296		panic("pool")
297	}
298	if !market.IsValid() || market == escrow || market == gov || market == liquidityRecipient {
299		panic("pool")
300	}
301	pair = market
302	pairSetAt = time.Now().Unix()
303	buyBucket = 0
304	buySpent = 0
305	chain.Emit(
306		"SetPool",
307		"pool", market.String(),
308		"start", strconv.FormatInt(pairSetAt, 10),
309	)
310}
311
312// Claim pays the caller's full sealed amount. Attaching coins reverts.
313// There is no withdraw.
314func Claim(cur realm, index int, amount int64, auntsHex string) {
315	var to address
316	claim(cur, index, amount, auntsHex, to)
317}
318
319// ClaimTo pays the caller's full sealed amount to another address.
320// The caller must still be the leaf. An invalid to pays the caller.
321// to cannot be this realm. Attaching coins reverts. There is no withdraw.
322func ClaimTo(cur realm, index int, amount int64, auntsHex string, to address) {
323	claim(cur, index, amount, auntsHex, to)
324}
325
326func claim(cur realm, index int, amount int64, auntsHex string, to address) {
327	mustUser(cur)
328	mustNoPayment()
329	if !sealed {
330		panic("not sealed")
331	}
332	if !claimOpen() {
333		panic("claim closed")
334	}
335	if amount <= 0 || amount > allocated {
336		panic("amount")
337	}
338	caller := cur.Previous().Address()
339	key := caller.String()
340	dest := to
341	if !dest.IsValid() {
342		dest = caller
343	}
344	if dest == escrow {
345		panic("recipient")
346	}
347	if index < 0 || index >= eligible {
348		panic("index")
349	}
350	aunts, err := hex.DecodeString(auntsHex)
351	if err != nil || len(aunts)%hashLen != 0 || len(aunts)/hashLen > maxProofAunts {
352		panic("bad proof")
353	}
354	if !merkle.VerifySimpleProof(root, leafData(key, amount), index, eligible, aunts) {
355		panic("bad proof")
356	}
357	if claimed.Has(key) {
358		panic("already claimed")
359	}
360	if airdropLeft < amount {
361		panic("airdrop empty")
362	}
363	airdropLeft -= amount
364	claimedSupply += amount
365	claimed.Set(key, amount)
366	mustTransfer(escrow, dest, amount)
367	chain.Emit(
368		"Claim",
369		"leaf", key,
370		"to", dest.String(),
371		"index", strconv.Itoa(index),
372		"amount", strconv.FormatInt(amount, 10),
373	)
374}
375
376// BurnUnclaimed destroys the airdrop budget still here after the window.
377// Anyone may call it. A second call reverts. GovDAO cannot recover it.
378// The sleeves already sent are not burned.
379func BurnUnclaimed(cur realm) {
380	mustCurrent(cur)
381	if cur.Address() != escrow {
382		panic("escrow")
383	}
384	mustNoPayment()
385	if !sealed {
386		panic("not sealed")
387	}
388	if burned {
389		panic("already burned")
390	}
391	if claimStart == 0 || time.Now().Unix() < claimStart+ClaimWindow {
392		panic("claim open")
393	}
394	left := airdropLeft
395	airdropLeft = 0
396	burned = true
397	if left > 0 {
398		mustBurn(escrow, left)
399	}
400	chain.Emit("BurnUnclaimed", "amount", strconv.FormatInt(left, 10))
401}
402
403// NoteVote locks the signing caller's current balance until until.
404// Only the bound GovDAO may call it. until cannot pass VoteWindow.
405func NoteVote(cur realm, until int64) {
406	mustCurrent(cur)
407	mustNoPayment()
408	if !gov.IsValid() || cur.Previous().Address() != gov {
409		panic("gov")
410	}
411	now := time.Now().Unix()
412	if until <= now || until > now+VoteWindow {
413		panic("lock")
414	}
415	voter := unsafe.OriginCaller()
416	if !voter.IsValid() || voter == gov || voter == escrow {
417		panic("voter")
418	}
419	weight := BalanceOf(voter)
420	if weight <= 0 {
421		panic("vote weight")
422	}
423	key := voter.String()
424	amt := weight
425	end := until
426	if v := locks.Get(key); v != nil {
427		prev, ok := v.(*hold)
428		if !ok || prev == nil {
429			panic("lock")
430		}
431		if now < prev.until {
432			if prev.amount > amt {
433				amt = prev.amount
434			}
435			if prev.until > end {
436				end = prev.until
437			}
438		}
439	}
440	locks.Set(key, &hold{amount: amt, until: end})
441	chain.Emit(
442		"NoteVote",
443		"voter", voter.String(),
444		"weight", strconv.FormatInt(amt, 10),
445		"until", strconv.FormatInt(end, 10),
446	)
447}
448
449func Transfer(cur realm, to address, amount int64) {
450	mustCurrent(cur)
451	mustToken()
452	mustNoPayment()
453	caller := cur.Previous().Address()
454	mustTradable(caller)
455	mustUnlocked(caller, amount)
456	mustBuyCap(caller, amount)
457	teller := ledger.ImpersonateTeller(caller)
458	if err := teller.Transfer(0, cur, to, amount); err != nil {
459		panic(err.Error())
460	}
461}
462
463func Approve(cur realm, spender address, amount int64) {
464	mustCurrent(cur)
465	mustToken()
466	mustNoPayment()
467	caller := cur.Previous().Address()
468	teller := ledger.ImpersonateTeller(caller)
469	if err := teller.Approve(0, cur, spender, amount); err != nil {
470		panic(err.Error())
471	}
472}
473
474func TransferFrom(cur realm, from, to address, amount int64) {
475	mustCurrent(cur)
476	mustToken()
477	mustNoPayment()
478	mustTradable(from)
479	mustUnlocked(from, amount)
480	mustBuyCap(from, amount)
481	caller := cur.Previous().Address()
482	teller := ledger.ImpersonateTeller(caller)
483	if err := teller.TransferFrom(0, cur, from, to, amount); err != nil {
484		panic(err.Error())
485	}
486}
487
488func Name() string {
489	mustToken()
490	return token.GetName()
491}
492
493func Symbol() string {
494	mustToken()
495	return token.GetSymbol()
496}
497
498func Decimals() int {
499	mustToken()
500	return token.GetDecimals()
501}
502
503func Supply() int64 { return TotalSupply() }
504
505func TotalSupply() int64 {
506	mustToken()
507	return token.TotalSupply()
508}
509
510func GetName() string   { return Name() }
511func GetSymbol() string { return Symbol() }
512func GetDecimals() int  { return Decimals() }
513
514func BalanceOf(owner address) int64 {
515	mustToken()
516	return token.BalanceOf(owner)
517}
518
519func Allowance(owner, spender address) int64 {
520	mustToken()
521	return token.Allowance(owner, spender)
522}
523
524func Admin() address { return adminAddr }
525
526func Escrow() address       { return escrow }
527func Gov() address          { return gov }
528func Sealed() bool          { return sealed }
529func Eligible() int         { return eligible }
530func Allocated() int64      { return allocated }
531func CommunitySent() int64  { return communitySent }
532// LiquidityRecipient is the only address Seal accepts for the 15% sleeve.
533func LiquidityRecipient() address { return liquidityRecipient }
534
535func Pool() address       { return pair }
536func PoolSetAt() int64    { return pairSetAt }
537func BuySpent() int64     { return buySpent }
538func LiquidityTo() address { return liquidityTo }
539func LiquiditySent() int64 { return liquiditySent }
540func IncentiveSent() int64 { return incentiveSent }
541func AirdropLeft() int64    { return airdropLeft }
542func ClaimStart() int64     { return claimStart }
543func TradeOpen() int64      { return tradeOpen }
544
545func ClaimEnd() int64 {
546	if claimStart == 0 {
547		return 0
548	}
549	return claimStart + ClaimWindow
550}
551
552func Burned() bool { return burned }
553func Claimed() int { return claimed.Size() }
554
555// ClaimsStarted reports whether the claim window has opened.
556func ClaimsStarted() bool { return claimsStarted() }
557
558func Quorum() int64 {
559	if !sealed {
560		return 0
561	}
562	// Floor on whole WIZ, then scale. allocated * quorumBps can overflow,
563	// and dividing the base amount first would keep a fraction the 0-decimal book dropped.
564	whole := allocated / tokenUnit
565	return whole * quorumBps / BpsBasis * tokenUnit
566}
567
568// ClaimedSupply is the sum of leaves already paid. Burns and transfers do not change it.
569func ClaimedSupply() int64 { return claimedSupply }
570
571// Publisher is the only address allowed to call Init.
572func Publisher() address { return publisher }
573
574func RootHex() string {
575	if len(root) == 0 {
576		return ""
577	}
578	return hex.EncodeToString(root)
579}
580
581func HasClaimed(owner address) bool {
582	if !owner.IsValid() {
583		return false
584	}
585	return claimed.Has(owner.String())
586}
587
588func ClaimedOf(owner address) int64 {
589	if !owner.IsValid() {
590		return 0
591	}
592	return claimedOf(owner.String())
593}
594
595// Render shows the version-4 split and the claim window.
596func Render(path string) string {
597	if token == nil {
598		return "# WIZ\n\nNot initialized.\n"
599	}
600	out := "# WIZ\n\n"
601	out += ufmt.Sprintf("* **Version**: %s\n", tokenVersion)
602	out += ufmt.Sprintf("* **Symbol**: %s\n", token.GetSymbol())
603	out += ufmt.Sprintf("* **Decimals**: %d\n", token.GetDecimals())
604	out += ufmt.Sprintf("* **One WIZ**: %d base units\n", tokenUnit)
605	out += ufmt.Sprintf("* **Total supply**: %d WIZ\n", token.TotalSupply()/tokenUnit)
606	out += ufmt.Sprintf("* **Total supply base**: %d\n", token.TotalSupply())
607	out += "* **Airdrop**: 75%, pro-rata to genesis GNOT, 100% unlocked at claim\n"
608	out += "* **Claim fee**: none\n"
609	out += "* **Claim recipient**: the caller, or another address in the same call\n"
610	out += "* **Claim window**: 90 days from the deployer start\n"
611	out += "* **Unclaimed**: burned after the window\n"
612	out += "* **Liquidity**: 15%, sent once at seal to the named address\n"
613	out += "* **Incentives**: 7%, sent once at seal to GovDAO\n"
614	out += "* **Community development**: 3% plus the pro-rata remainder, sent once at seal to GovDAO\n"
615	out += "* **GovDAO**: a separate realm. Holders vote there on incentive and community, separately\n"
616	out += "* **No withdraw**\n"
617	out += ufmt.Sprintf("* **Trade open**: %d\n", tradeOpen)
618	out += "* **Before trade open**: only the liquidity wallet, the airdrop escrow, and GovDAO can transfer\n"
619	out += ufmt.Sprintf("* **Buy cap**: %d WIZ may leave the pool every %d seconds, for %d seconds after the pool is registered\n", BuyCapPerBucket/tokenUnit, BuyCapBucket, BuyCapWindow)
620	out += "* **Liquidity add**: the liquidity wallet is not capped\n"
621	if pair.IsValid() {
622		out += ufmt.Sprintf("* **Pool**: %s\n", pair.String())
623		out += ufmt.Sprintf("* **Buy cap ends**: %d\n", pairSetAt+BuyCapWindow)
624	}
625	if !sealed {
626		out += "\nRoot is not sealed.\n"
627		return out
628	}
629	out += ufmt.Sprintf("\n* **Eligible**: %d\n", eligible)
630	out += ufmt.Sprintf("* **Airdrop budget left**: %d base units\n", airdropLeft)
631	out += ufmt.Sprintf("* **Community sent**: %d base units\n", communitySent)
632	out += ufmt.Sprintf("* **Liquidity sent**: %d base units\n", liquiditySent)
633	out += ufmt.Sprintf("* **Incentive sent**: %d base units\n", incentiveSent)
634	out += ufmt.Sprintf("* **Claim start**: %d\n", claimStart)
635	out += ufmt.Sprintf("* **Claim end**: %d\n", ClaimEnd())
636	out += ufmt.Sprintf("* **Burned**: %t\n", burned)
637	out += ufmt.Sprintf("* **Root**: %s\n", hex.EncodeToString(root))
638	out += ufmt.Sprintf("* **Claimed supply**: %d base units\n", claimedSupply)
639	return out
640}
641
642func leafData(bech32 string, amount int64) []byte {
643	return []byte(bech32 + ":" + strconv.FormatInt(amount, 10))
644}
645
646func sleeves() (community, incentive, liquidity, pool int64) {
647	// Divide first. mintSupply * LiquidityBps does not fit in int64.
648	community = mintSupply / BpsBasis * CommunityBps
649	incentive = mintSupply / BpsBasis * IncentiveBps
650	liquidity = mintSupply / BpsBasis * LiquidityBps
651	pool = mintSupply - community - incentive - liquidity
652	return
653}
654
655func claimOpen() bool {
656	if claimStart == 0 {
657		return false
658	}
659	now := time.Now().Unix()
660	return now >= claimStart && now < claimStart+ClaimWindow
661}
662
663func claimsStarted() bool {
664	return claimStart != 0 && time.Now().Unix() >= claimStart
665}
666
667func mustCurrent(cur realm) {
668	if !cur.IsCurrent() {
669		panic("realm")
670	}
671}
672
673func mustUser(cur realm) {
674	mustCurrent(cur)
675	if !cur.Previous().IsUserCall() {
676		panic("user call only")
677	}
678}
679
680// mustTradable panics while public transfers are closed.
681// The liquidity wallet, the airdrop escrow, and GovDAO stay able to move tokens.
682// Claim and Seal do not call this.
683func mustTradable(from address) {
684	if from == liquidityRecipient || (escrow.IsValid() && from == escrow) || (gov.IsValid() && from == gov) {
685		return
686	}
687	if tradeOpen == 0 || time.Now().Unix() < tradeOpen {
688		panic("trade closed")
689	}
690}
691
692// mustUnlocked panics when amount would push owner's balance under the
693// largest weight they already cast on a ballot that is still open.
694// An amount above the balance is left to the token transfer.
695// mustBuyCap limits tokens leaving the registered pool during the short window.
696// The liquidity wallet is exempt. A transfer that does not leave the pool is not a buy.
697func mustBuyCap(from address, amount int64) {
698	if amount <= 0 || from == liquidityRecipient || !pair.IsValid() || from != pair {
699		return
700	}
701	now := time.Now().Unix()
702	if pairSetAt <= 0 || now < pairSetAt {
703		panic("buy cap")
704	}
705	if now >= pairSetAt+BuyCapWindow {
706		return
707	}
708	bucket := (now - pairSetAt) / BuyCapBucket
709	if bucket != buyBucket {
710		buyBucket = bucket
711		buySpent = 0
712	}
713	if amount > BuyCapPerBucket || buySpent > BuyCapPerBucket-amount {
714		panic("buy cap")
715	}
716	buySpent += amount
717}
718
719func mustUnlocked(owner address, amount int64) {
720	if amount <= 0 {
721		return
722	}
723	bal := BalanceOf(owner)
724	if amount > bal {
725		return
726	}
727	if bal-amount < voteLock(owner) {
728		panic("vote lock")
729	}
730}
731
732func voteLock(owner address) int64 {
733	if !owner.IsValid() {
734		return 0
735	}
736	v := locks.Get(owner.String())
737	if v == nil {
738		return 0
739	}
740	h, ok := v.(*hold)
741	if !ok || h == nil {
742		panic("lock")
743	}
744	if time.Now().Unix() >= h.until {
745		return 0
746	}
747	return h.amount
748}
749
750func mustNoPayment() {
751	if len(unsafe.OriginSend()) != 0 {
752		panic("unexpected payment")
753	}
754}
755
756func mustAdmin(cur realm) {
757	if admin == nil || token == nil {
758		panic("not initialized")
759	}
760	mustUser(cur)
761	admin.AssertOwnedBy(cur.Previous().Address())
762}
763
764func mustToken() {
765	if token == nil {
766		panic("not initialized")
767	}
768}
769
770func claimedOf(key string) int64 {
771	v := claimed.Get(key)
772	if v == nil {
773		return 0
774	}
775	n, ok := v.(int64)
776	if !ok {
777		panic("claimed amount")
778	}
779	return n
780}
781
782func mustMint(to address, amount int64) {
783	if err := ledger.Mint(to, amount); err != nil {
784		panic(err.Error())
785	}
786}
787
788func mustTransfer(from, to address, amount int64) {
789	if err := ledger.Transfer(from, to, amount); err != nil {
790		panic(err.Error())
791	}
792}
793
794func mustBurn(from address, amount int64) {
795	if err := ledger.Burn(from, amount); err != nil {
796		panic(err.Error())
797	}
798}
799

Raw Package Data

Raw JSON data