hof.gno

4.89 Kb ยท 208 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	crossing()
 69
 70	if Pausable.IsPaused() {
 71		return
 72	}
 73
 74	submission := std.PreviousRealm()
 75	pkgpath := submission.PkgPath()
 76
 77	// Must be called from code
 78	if submission.IsUser() {
 79		return
 80	}
 81
 82	// Must not yet exist
 83	if exhibition.items.Has(pkgpath) {
 84		return
 85	}
 86
 87	// Title must be between 1 maxTitleLength long
 88	if title == "" || len(title) > maxTitleLength {
 89		return
 90	}
 91
 92	// Description must be between 1 maxDescriptionLength long
 93	if len(description) > maxDescriptionLength {
 94		return
 95	}
 96
 97	id := exhibition.itemCounter.Next()
 98	i := &Item{
 99		id:          id,
100		title:       title,
101		description: description,
102		pkgpath:     pkgpath,
103		blockNum:    std.ChainHeight(),
104		upvote:      &addrset.Set{},
105		downvote:    &addrset.Set{},
106	}
107
108	exhibition.items.Set(pkgpath, i)
109	exhibition.itemsSortedByCreation.Set(getCreationSortKey(i.blockNum, i.id), i)
110	exhibition.itemsSortedByUpvotes.Set(getVoteSortKey(i.upvote.Size(), i.id), i)
111	exhibition.itemsSortedByDownvotes.Set(getVoteSortKey(i.downvote.Size(), i.id), i)
112
113	std.Emit("Registration")
114}
115
116func Upvote(pkgpath string) {
117	crossing()
118
119	rawItem, ok := exhibition.items.Get(pkgpath)
120	if !ok {
121		panic(ErrNoSuchItem)
122	}
123
124	item := rawItem.(*Item)
125	caller := std.PreviousRealm().Address()
126
127	if item.upvote.Has(caller) {
128		panic(ErrDoubleUpvote)
129	}
130
131	if _, exists := exhibition.itemsSortedByUpvotes.Remove(getVoteSortKey(item.upvote.Size(), item.id)); !exists {
132		panic("error removing old upvote entry")
133	}
134
135	item.upvote.Add(caller)
136
137	exhibition.itemsSortedByUpvotes.Set(getVoteSortKey(item.upvote.Size(), item.id), item)
138}
139
140func Downvote(pkgpath string) {
141	crossing()
142
143	rawItem, ok := exhibition.items.Get(pkgpath)
144	if !ok {
145		panic(ErrNoSuchItem)
146	}
147
148	item := rawItem.(*Item)
149	caller := std.PreviousRealm().Address()
150
151	if item.downvote.Has(caller) {
152		panic(ErrDoubleDownvote)
153	}
154
155	if _, exist := exhibition.itemsSortedByDownvotes.Remove(getVoteSortKey(item.downvote.Size(), item.id)); !exist {
156		panic("error removing old downvote entry")
157
158	}
159
160	item.downvote.Add(caller)
161
162	exhibition.itemsSortedByDownvotes.Set(getVoteSortKey(item.downvote.Size(), item.id), item)
163}
164
165func Delete(pkgpath string) {
166	crossing()
167
168	if !Ownable.OwnedByPrevious() {
169		panic(ownable.ErrUnauthorized)
170	}
171
172	i, ok := exhibition.items.Get(pkgpath)
173	if !ok {
174		panic(ErrNoSuchItem)
175	}
176
177	item := i.(*Item)
178	upvoteKey := getVoteSortKey(item.upvote.Size(), item.id)
179	downvoteKey := getVoteSortKey(item.downvote.Size(), item.id)
180
181	if _, removed := exhibition.items.Remove(pkgpath); !removed {
182		panic(ErrNoSuchItem)
183	}
184
185	if _, removed := exhibition.itemsSortedByUpvotes.Remove(upvoteKey); !removed {
186		panic(ErrNoSuchItem)
187	}
188
189	if _, removed := exhibition.itemsSortedByDownvotes.Remove(downvoteKey); !removed {
190		panic(ErrNoSuchItem)
191	}
192
193	if _, removed := exhibition.itemsSortedByCreation.Remove(getCreationSortKey(item.blockNum, item.id)); !removed {
194		panic(ErrNoSuchItem)
195	}
196}
197
198func getVoteSortKey(votes int, id seqid.ID) string {
199	votesStr := strconv.Itoa(votes)
200	paddedVotes := strings.Repeat("0", 10-len(votesStr)) + votesStr
201	return paddedVotes + ":" + strconv.FormatUint(uint64(id), 10)
202}
203
204func getCreationSortKey(blockNum int64, id seqid.ID) string {
205	blockNumStr := strconv.Itoa(int(blockNum))
206	paddedBlockNum := strings.Repeat("0", 10-len(blockNumStr)) + blockNumStr
207	return paddedBlockNum + ":" + strconv.FormatUint(uint64(id), 10)
208}