hof.gno

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