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.

campaign.gno

4.27 Kb · 166 lines
  1// Package campaign is a near-verbatim copy of the real gems6/campaign
  2// realm, redeployed here under the test address so this whole
  3// reveal-delay + gemsartfab prototype is self-contained and testable
  4// without touching the live gems6 deployment or needing the vesting
  5// wallet's own key. Logic is unchanged from the original -- see that
  6// package's own doc comments for the full design rationale (why a
  7// dedicated prunable realm instead of a Merkle root, the stacking rule
  8// in grantOne, etc.).
  9package campaign
 10
 11// Storage (2026-09-26): every grant lives in ONE map, packed as a single
 12// int64 (remainingUses*packBase + bps), plus one slice of wallets for
 13// EntryAt's stable order. The first version kept an avl.Tree of *entry
 14// pointers: every wallet became its own tree node plus entry object, about
 15// 2.9 KB each on a local chain (0.29 GNOT per wallet, ~85 GNOT for the
 16// 284-wallet campaign). A map and a slice are stored as a few objects
 17// whose size grows by roughly the key and value per wallet.
 18
 19import (
 20	unsaferealm "chain/runtime/unsafe"
 21	"strings"
 22)
 23
 24const packBase = 10001 // bps is 0..10000
 25
 26var (
 27	collectionOwner  address = "g17cjym5e9hhws46lt6329pv2gtx2ay0503hgems"
 28	approvedConsumer string
 29	grants           = map[string]int64{} // wallet -> remainingUses*packBase + bps
 30	order            []string             // wallets with a grant, for EntryAt
 31)
 32
 33func pack(bps, uses int64) int64     { return uses*packBase + bps }
 34func unpack(v int64) (bps, uses int64) { return v % packBase, v / packBase }
 35
 36func callerAddress() address {
 37	return unsaferealm.PreviousRealm().Address()
 38}
 39
 40func assertOwner() {
 41	if callerAddress() != collectionOwner {
 42		panic("unauthorized: caller is not the collection owner")
 43	}
 44}
 45
 46func SetApprovedConsumer(cur realm, pkgpath string) {
 47	assertOwner()
 48	approvedConsumer = pkgpath
 49}
 50
 51func validateBps(bps int64) {
 52	if bps < 0 || bps > 10000 {
 53		panic("bps must be between 0 and 10000")
 54	}
 55}
 56
 57func validateMaxUses(maxUses int64) {
 58	if maxUses <= 0 {
 59		panic("maxUses must be positive")
 60	}
 61}
 62
 63// grantOne adds uses to a wallet; a second grant stacks its uses and keeps
 64// the better (lower) bps.
 65func grantOne(wallet string, bps int64, maxUses int64) {
 66	if v, ok := grants[wallet]; ok {
 67		oldBps, uses := unpack(v)
 68		if oldBps < bps {
 69			bps = oldBps
 70		}
 71		grants[wallet] = pack(bps, uses+maxUses)
 72		return
 73	}
 74	grants[wallet] = pack(bps, maxUses)
 75	order = append(order, wallet)
 76}
 77
 78func removeOne(wallet string) {
 79	if _, ok := grants[wallet]; !ok {
 80		return
 81	}
 82	delete(grants, wallet)
 83	for i, w := range order {
 84		if w == wallet {
 85			order[i] = order[len(order)-1]
 86			order = order[:len(order)-1]
 87			break
 88		}
 89	}
 90}
 91
 92func AddWallet(cur realm, wallet address, bps int64, maxUses int64) {
 93	assertOwner()
 94	validateBps(bps)
 95	validateMaxUses(maxUses)
 96	grantOne(wallet.String(), bps, maxUses)
 97}
 98
 99func AddWalletsBatch(cur realm, walletsCSV string, bps int64, maxUses int64) {
100	assertOwner()
101	validateBps(bps)
102	validateMaxUses(maxUses)
103	for _, w := range strings.Split(walletsCSV, ",") {
104		w = strings.TrimSpace(w)
105		if w == "" {
106			continue
107		}
108		grantOne(w, bps, maxUses)
109	}
110}
111
112func RemoveWallet(cur realm, wallet address) {
113	assertOwner()
114	removeOne(wallet.String())
115}
116
117func RemoveWalletsBatch(cur realm, walletsCSV string) {
118	assertOwner()
119	for _, w := range strings.Split(walletsCSV, ",") {
120		w = strings.TrimSpace(w)
121		if w == "" {
122			continue
123		}
124		removeOne(w)
125	}
126}
127
128func assertCallerIsApprovedConsumer(cur realm) {
129	caller := cur.Previous().PkgPath()
130	if approvedConsumer == "" || caller != approvedConsumer {
131		panic("unauthorized: caller is not the approved consumer realm")
132	}
133}
134
135func Peek(wallet address) (bps int64, remainingUses int64, ok bool) {
136	v, has := grants[wallet.String()]
137	if !has {
138		return 0, 0, false
139	}
140	bps, remainingUses = unpack(v)
141	return bps, remainingUses, true
142}
143
144func ConsumeDiscount(cur realm, wallet address) (bps int64, ok bool) {
145	assertCallerIsApprovedConsumer(cur)
146	w := wallet.String()
147	v, has := grants[w]
148	if !has {
149		return 0, false
150	}
151	bps, uses := unpack(v)
152	if uses <= 1 {
153		removeOne(w)
154	} else {
155		grants[w] = pack(bps, uses-1)
156	}
157	return bps, true
158}
159
160func Count() int64 { return int64(len(order)) }
161
162func EntryAt(index int) (wallet string, bps int64, remainingUses int64) {
163	wallet = order[index]
164	bps, remainingUses = unpack(grants[wallet])
165	return wallet, bps, remainingUses
166}