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}