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}