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}
799Raw Package Data
Raw JSON data