Search Apps Documentation Source Content File Folder Download Copy Actions Download

hor.gno

4.91 Kb ยท 201 lines
  1// Package hor is the hall of realms.
  2// The Hall of Realms is an exhibition that holds items. Users can add their realms to the Hall of Realms by
  3// importing the Hall of Realms package and calling hor.Register() from their init function.
  4package hor
  5
  6import (
  7	"chain"
  8	"chain/runtime"
  9	"strconv"
 10	"strings"
 11
 12	"gno.land/p/moul/addrset"
 13	"gno.land/p/nt/avl"
 14	"gno.land/p/nt/ownable"
 15	"gno.land/p/nt/pausable"
 16	"gno.land/p/nt/seqid"
 17	"gno.land/r/leon/config"
 18)
 19
 20const (
 21	maxTitleLength       = 30
 22	maxDescriptionLength = 50
 23)
 24
 25var (
 26	exhibition *Exhibition
 27
 28	// Safe objects
 29	Ownable  *ownable.Ownable
 30	Pausable *pausable.Pausable
 31)
 32
 33type (
 34	Exhibition struct {
 35		itemCounter            seqid.ID
 36		description            string
 37		items                  *avl.Tree // pkgPath > *Item
 38		itemsSortedByCreation  *avl.Tree // same data but sorted by creation time
 39		itemsSortedByUpvotes   *avl.Tree // same data but sorted by upvotes
 40		itemsSortedByDownvotes *avl.Tree // same data but sorted by downvotes
 41	}
 42
 43	Item struct {
 44		id          seqid.ID
 45		title       string
 46		description string
 47		pkgpath     string
 48		blockNum    int64
 49		upvote      *addrset.Set
 50		downvote    *addrset.Set
 51	}
 52)
 53
 54func init() {
 55	exhibition = &Exhibition{
 56		items:                  avl.NewTree(),
 57		itemsSortedByCreation:  avl.NewTree(),
 58		itemsSortedByUpvotes:   avl.NewTree(),
 59		itemsSortedByDownvotes: avl.NewTree(),
 60	}
 61
 62	Ownable = ownable.NewWithAddress(config.OwnableMain.Owner()) // OrigSendOwnable?
 63	Pausable = pausable.NewFromOwnable(Ownable)
 64}
 65
 66// Register registers your realm to the Hall of Fame
 67// Should be called from within code
 68func Register(cur realm, title, description string) {
 69	if Pausable.IsPaused() {
 70		return
 71	}
 72
 73	submission := runtime.PreviousRealm()
 74	pkgpath := submission.PkgPath()
 75
 76	// Must be called from code
 77	if submission.IsUser() {
 78		return
 79	}
 80
 81	// Must not yet exist
 82	if exhibition.items.Has(pkgpath) {
 83		return
 84	}
 85
 86	// Title must be between 1 maxTitleLength long
 87	if title == "" || len(title) > maxTitleLength {
 88		return
 89	}
 90
 91	// Description must be between 1 maxDescriptionLength long
 92	if len(description) > maxDescriptionLength {
 93		return
 94	}
 95
 96	id := exhibition.itemCounter.Next()
 97	i := &Item{
 98		id:          id,
 99		title:       title,
100		description: description,
101		pkgpath:     pkgpath,
102		blockNum:    runtime.ChainHeight(),
103		upvote:      &addrset.Set{},
104		downvote:    &addrset.Set{},
105	}
106
107	exhibition.items.Set(pkgpath, i)
108	exhibition.itemsSortedByCreation.Set(getCreationSortKey(i.blockNum, i.id), i)
109	exhibition.itemsSortedByUpvotes.Set(getVoteSortKey(i.upvote.Size(), i.id), i)
110	exhibition.itemsSortedByDownvotes.Set(getVoteSortKey(i.downvote.Size(), i.id), i)
111
112	chain.Emit("Registration")
113}
114
115func Upvote(cur realm, pkgpath string) {
116	rawItem, ok := exhibition.items.Get(pkgpath)
117	if !ok {
118		panic(ErrNoSuchItem)
119	}
120
121	item := rawItem.(*Item)
122	caller := runtime.PreviousRealm().Address()
123
124	if item.upvote.Has(caller) {
125		panic(ErrDoubleUpvote)
126	}
127
128	if _, exists := exhibition.itemsSortedByUpvotes.Remove(getVoteSortKey(item.upvote.Size(), item.id)); !exists {
129		panic("error removing old upvote entry")
130	}
131
132	item.upvote.Add(caller)
133
134	exhibition.itemsSortedByUpvotes.Set(getVoteSortKey(item.upvote.Size(), item.id), item)
135}
136
137func Downvote(cur realm, pkgpath string) {
138	rawItem, ok := exhibition.items.Get(pkgpath)
139	if !ok {
140		panic(ErrNoSuchItem)
141	}
142
143	item := rawItem.(*Item)
144	caller := runtime.PreviousRealm().Address()
145
146	if item.downvote.Has(caller) {
147		panic(ErrDoubleDownvote)
148	}
149
150	if _, exist := exhibition.itemsSortedByDownvotes.Remove(getVoteSortKey(item.downvote.Size(), item.id)); !exist {
151		panic("error removing old downvote entry")
152
153	}
154
155	item.downvote.Add(caller)
156
157	exhibition.itemsSortedByDownvotes.Set(getVoteSortKey(item.downvote.Size(), item.id), item)
158}
159
160func Delete(cur realm, pkgpath string) {
161	if !Ownable.OwnedByPrevious() {
162		panic(ownable.ErrUnauthorized)
163	}
164
165	i, ok := exhibition.items.Get(pkgpath)
166	if !ok {
167		panic(ErrNoSuchItem)
168	}
169
170	item := i.(*Item)
171	upvoteKey := getVoteSortKey(item.upvote.Size(), item.id)
172	downvoteKey := getVoteSortKey(item.downvote.Size(), item.id)
173
174	if _, removed := exhibition.items.Remove(pkgpath); !removed {
175		panic(ErrNoSuchItem)
176	}
177
178	if _, removed := exhibition.itemsSortedByUpvotes.Remove(upvoteKey); !removed {
179		panic(ErrNoSuchItem)
180	}
181
182	if _, removed := exhibition.itemsSortedByDownvotes.Remove(downvoteKey); !removed {
183		panic(ErrNoSuchItem)
184	}
185
186	if _, removed := exhibition.itemsSortedByCreation.Remove(getCreationSortKey(item.blockNum, item.id)); !removed {
187		panic(ErrNoSuchItem)
188	}
189}
190
191func getVoteSortKey(votes int, id seqid.ID) string {
192	votesStr := strconv.Itoa(votes)
193	paddedVotes := strings.Repeat("0", 10-len(votesStr)) + votesStr
194	return paddedVotes + ":" + strconv.FormatUint(uint64(id), 10)
195}
196
197func getCreationSortKey(blockNum int64, id seqid.ID) string {
198	blockNumStr := strconv.Itoa(int(blockNum))
199	paddedBlockNum := strings.Repeat("0", 10-len(blockNumStr)) + blockNumStr
200	return paddedBlockNum + ":" + strconv.FormatUint(uint64(id), 10)
201}