store.gno
4.48 Kb · 199 lines
1package users
2
3import (
4 "chain"
5 "chain/runtime"
6 "regexp"
7
8 "gno.land/p/nt/avl/v0"
9 "gno.land/p/nt/ufmt/v0"
10)
11
12var (
13 nameStore = avl.NewTree() // name/aliases > *UserData
14 addressStore = avl.NewTree() // address > *UserData
15
16 reAddressLookalike = regexp.MustCompile(`^g1[a-z0-9]{20,38}$`)
17 reAlphanum = regexp.MustCompile(`^[a-zA-Z0-9_]{1,64}$`)
18)
19
20const (
21 RegisterUserEvent = "Registered"
22 UpdateNameEvent = "Updated"
23 DeleteUserEvent = "Deleted"
24)
25
26type UserData struct {
27 addr address
28 username string // contains the latest name of a user
29 deleted bool
30}
31
32func (u UserData) Name() string {
33 return u.username
34}
35
36func (u UserData) Addr() address {
37 return u.addr
38}
39
40func (u UserData) IsDeleted() bool {
41 return u.deleted
42}
43
44// RenderLink provides a render link to the user page on gnoweb
45// `linkText` is optional
46func (u UserData) RenderLink(linkText string) string {
47 if linkText == "" {
48 return ufmt.Sprintf("[@%s](/u/%s)", u.username, u.username)
49 }
50
51 return ufmt.Sprintf("[%s](/u/%s)", linkText, u.username)
52}
53
54// registerUser adds a new user to the system without checking controllers
55func registerUser(cur realm, name string, address_XXX address) error {
56 // Validate name
57 if err := validateName(name); err != nil {
58 return err
59 }
60
61 // Validate address
62 if !address_XXX.IsValid() {
63 return ErrInvalidAddress
64 }
65
66 // Check if name is taken
67 if nameStore.Has(name) {
68 return ErrNameTaken
69 }
70
71 raw, ok := addressStore.Get(address_XXX.String())
72 if ok {
73 // Cannot re-register after deletion
74 if raw.(*UserData).IsDeleted() {
75 return ErrDeletedUser
76 }
77
78 // For a second name, use UpdateName
79 return ErrAlreadyHasName
80 }
81
82 // Create UserData
83 data := &UserData{
84 addr: address_XXX,
85 username: name,
86 deleted: false,
87 }
88
89 // Set corresponding stores
90 nameStore.Set(name, data)
91 addressStore.Set(address_XXX.String(), data)
92
93 chain.Emit(RegisterUserEvent,
94 "name", name,
95 "address", address_XXX.String(),
96 )
97 return nil
98}
99
100// RegisterUser adds a new user to the system.
101func RegisterUser(cur realm, name string, address_XXX address) error {
102 // At genesis (height 0), allow any caller to register users.
103 // After genesis, only whitelisted controllers can register.
104 if runtime.ChainHeight() > 0 && !controllers.Has(runtime.PreviousRealm().Address()) {
105 return NewErrNotWhitelisted()
106 }
107
108 return registerUser(cur, name, address_XXX)
109}
110
111// updateName adds a name that is associated with a specific address without checking controllers
112// All previous names are preserved and resolvable.
113// The new name is the default value returned for address lookups.
114func (u *UserData) updateName(newName string) error {
115 if u == nil { // either doesn't exist or was deleted
116 return ErrUserNotExistOrDeleted
117 }
118
119 // Validate name
120 if err := validateName(newName); err != nil {
121 return err
122 }
123
124 // Check if the requested Alias is already taken
125 if nameStore.Has(newName) {
126 return ErrNameTaken
127 }
128
129 u.username = newName
130 nameStore.Set(newName, u)
131
132 chain.Emit(UpdateNameEvent,
133 "alias", newName,
134 "address", u.addr.String(),
135 )
136 return nil
137}
138
139// UpdateName adds a name that is associated with a specific address
140// All previous names are preserved and resolvable.
141// The new name is the default value returned for address lookups.
142func (u *UserData) UpdateName(newName string) error {
143 if u == nil {
144 return ErrUserNotExistOrDeleted
145 }
146
147 // Validate caller
148 if !controllers.Has(runtime.CurrentRealm().Address()) {
149 return NewErrNotWhitelisted()
150 }
151
152 return u.updateName(newName)
153}
154
155// delete marks a user and all their aliases as deleted without checking controllers.
156func (u *UserData) delete() error {
157 if u == nil {
158 return ErrUserNotExistOrDeleted
159 }
160
161 u.deleted = true
162
163 chain.Emit(DeleteUserEvent, "address", u.addr.String())
164 return nil
165}
166
167// Delete marks a user and all their aliases as deleted.
168func (u *UserData) Delete() error {
169 if u == nil {
170 return ErrUserNotExistOrDeleted
171 }
172
173 // Validate caller
174 if !controllers.Has(runtime.CurrentRealm().Address()) {
175 return NewErrNotWhitelisted()
176 }
177
178 return u.delete()
179}
180
181// Validate validates username and address passed in
182// Most of the validation is done in the controllers
183// This provides more flexibility down the line
184func validateName(username string) error {
185 if username == "" {
186 return ErrEmptyUsername
187 }
188
189 if !reAlphanum.MatchString(username) {
190 return ErrInvalidUsername
191 }
192
193 // Check if the username can be decoded or looks like a valid address
194 if address(username).IsValid() || reAddressLookalike.MatchString(username) {
195 return ErrNameLikeAddress
196 }
197
198 return nil
199}