/
keeper.go
250 lines (199 loc) · 7.62 KB
/
keeper.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
package keeper
import (
"fmt"
"regexp"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
"cosmossdk.io/errors"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
paramstypes "github.com/cosmos/cosmos-sdk/x/params/types"
"github.com/cometbft/cometbft/libs/log"
"github.com/cosmos/cosmos-sdk/codec"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/desmos-labs/desmos/v7/x/profiles/types"
)
// Keeper maintains the link to data storage and exposes getter/setter methods for the various parts of the state machine
type Keeper struct {
storeKey storetypes.StoreKey
cdc codec.Codec
legacyAmino *codec.LegacyAmino
paramSubspace paramstypes.Subspace
hooks types.ProfilesHooks
ak authkeeper.AccountKeeper
rk types.RelationshipsKeeper
ChannelKeeper types.ChannelKeeper
PortKeeper types.PortKeeper
ScopedKeeper types.ScopedKeeper
// the address capable of executing a MsgUpdateParams message. Typically, this
// should be the x/gov module account.
authority string
}
// NewKeeper creates new instances of the Profiles Keeper.
// This k stores the profile data using two different associations:
// 1. Address -> Profile
// This is used to easily retrieve the profile of a user based on an address
// 2. DTag -> Address
// This is used to get the address of a user based on a DTag
func NewKeeper(
cdc codec.Codec,
legacyAmino *codec.LegacyAmino,
storeKey storetypes.StoreKey,
ak authkeeper.AccountKeeper,
rk types.RelationshipsKeeper,
channelKeeper types.ChannelKeeper,
portKeeper types.PortKeeper,
scopedKeeper types.ScopedKeeper,
authority string,
) *Keeper {
return &Keeper{
storeKey: storeKey,
cdc: cdc,
legacyAmino: legacyAmino,
ak: ak,
rk: rk,
ChannelKeeper: channelKeeper,
PortKeeper: portKeeper,
ScopedKeeper: scopedKeeper,
authority: authority,
}
}
// SetIBCKeepers set IBCKeepers for Keeper
func (k *Keeper) SetIBCKeepers(
channelKeeper types.ChannelKeeper,
portKeeper types.PortKeeper,
scopedKeeper types.ScopedKeeper,
) {
k.ChannelKeeper = channelKeeper
k.PortKeeper = portKeeper
k.ScopedKeeper = scopedKeeper
}
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", "x/"+types.ModuleName)
}
// SetHooks allows to set the profiles hooks
func (k *Keeper) SetHooks(ph types.ProfilesHooks) *Keeper {
if k.hooks != nil {
panic("cannot set profiles hooks twice")
}
k.hooks = ph
return k
}
// IsUserBlocked returns true if the provided blocker has blocked the given user for the given subspace.
// If the provided subspace is empty, all subspaces will be checked
func (k Keeper) IsUserBlocked(ctx sdk.Context, user, blocker string) bool {
return k.rk.HasUserBlocked(ctx, user, blocker, 0)
}
// storeProfileWithoutDTagCheck stores the given profile inside the current context
// without checking if another profile with the same DTag already exists.
// It assumes that the given profile has already been validated.
func (k Keeper) storeProfileWithoutDTagCheck(ctx sdk.Context, profile *types.Profile) error {
store := ctx.KVStore(k.storeKey)
oldProfile, found, err := k.GetProfile(ctx, profile.GetAddress().String())
if err != nil {
return err
}
if found && oldProfile.DTag != profile.DTag {
// Remove the previous DTag association (if the DTag has changed)
store.Delete(types.DTagStoreKey(oldProfile.DTag))
// Remove all incoming DTag transfer requests if the DTag has changed since these will be invalid now
k.DeleteAllUserIncomingDTagTransferRequests(ctx, profile.GetAddress().String())
}
// Store the DTag -> Address association
store.Set(types.DTagStoreKey(profile.DTag), profile.GetAddress())
// Store the account inside the auth keeper
k.ak.SetAccount(ctx, profile)
k.Logger(ctx).Info("saved profile", "DTag", profile.DTag, "from", profile.GetAddress())
k.AfterProfileSaved(ctx, profile)
return nil
}
// SaveProfile stores the given profile inside the current context.
// It assumes that the given profile has already been validated.
// It returns an error if a profile with the same DTag from a different creator already exists
func (k Keeper) SaveProfile(ctx sdk.Context, profile *types.Profile) error {
addr := k.GetAddressFromDTag(ctx, profile.DTag)
if addr != "" && addr != profile.GetAddress().String() {
return errors.Wrapf(sdkerrors.ErrInvalidRequest,
"a profile with DTag %s has already been created", profile.DTag)
}
return k.storeProfileWithoutDTagCheck(ctx, profile)
}
// GetProfile returns the profile corresponding to the given address inside the current context.
func (k Keeper) GetProfile(ctx sdk.Context, address string) (profile *types.Profile, found bool, err error) {
sdkAcc, err := sdk.AccAddressFromBech32(address)
if err != nil {
return nil, false, err
}
stored, ok := k.ak.GetAccount(ctx, sdkAcc).(*types.Profile)
if !ok {
return nil, false, nil
}
return stored, true, nil
}
// GetAddressFromDTag returns the address associated to the given DTag or an empty string if it does not exists
func (k Keeper) GetAddressFromDTag(ctx sdk.Context, dTag string) (addr string) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.DTagStoreKey(dTag))
if bz == nil {
return ""
}
return sdk.AccAddress(bz).String()
}
// RemoveProfile allows to delete a profile associated with the given address inside the current context.
// It assumes that the address-related profile exists.
func (k Keeper) RemoveProfile(ctx sdk.Context, address string) error {
profile, found, err := k.GetProfile(ctx, address)
if err != nil {
return err
}
if !found {
return errors.Wrapf(sdkerrors.ErrInvalidRequest,
"no profile associated with the following address found: %s", address)
}
// Delete the DTag -> Address association
store := ctx.KVStore(k.storeKey)
store.Delete(types.DTagStoreKey(profile.DTag))
// Delete all DTag transfer requests made towards this account
k.DeleteAllUserIncomingDTagTransferRequests(ctx, address)
// Delete all chains links
k.DeleteAllUserChainLinks(ctx, address)
// Delete all the application links
k.DeleteAllUserApplicationLinks(ctx, address)
// Delete the profile data by replacing the stored account
k.ak.SetAccount(ctx, profile.GetAccount())
k.AfterProfileDeleted(ctx, profile)
return nil
}
// ValidateProfile checks if the given profile is valid according to the current profile's module params
func (k Keeper) ValidateProfile(ctx sdk.Context, profile *types.Profile) error {
params := k.GetParams(ctx)
minNicknameLen := params.Nickname.MinLength.Int64()
maxNicknameLen := params.Nickname.MaxLength.Int64()
if profile.Nickname != "" {
nameLen := int64(len(profile.Nickname))
if nameLen < minNicknameLen {
return fmt.Errorf("profile nickname cannot be less than %d characters", minNicknameLen)
}
if nameLen > maxNicknameLen {
return fmt.Errorf("profile nickname cannot exceed %d characters", maxNicknameLen)
}
}
dTagRegEx := regexp.MustCompile(params.DTag.RegEx)
minDTagLen := params.DTag.MinLength.Int64()
maxDTagLen := params.DTag.MaxLength.Int64()
dTagLen := int64(len(profile.DTag))
if !dTagRegEx.MatchString(profile.DTag) {
return fmt.Errorf("invalid profile dtag, it should match the following regEx %s", dTagRegEx)
}
if dTagLen < minDTagLen {
return fmt.Errorf("profile dtag cannot be less than %d characters", minDTagLen)
}
if dTagLen > maxDTagLen {
return fmt.Errorf("profile dtag cannot exceed %d characters", maxDTagLen)
}
maxBioLen := params.Bio.MaxLength.Int64()
if int64(len(profile.Bio)) > maxBioLen {
return fmt.Errorf("profile biography cannot exceed %d characters", maxBioLen)
}
return profile.Validate()
}