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}