-
Notifications
You must be signed in to change notification settings - Fork 193
/
host_zone.go
361 lines (310 loc) · 13 KB
/
host_zone.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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
package keeper
import (
"errors"
"fmt"
"math"
sdkmath "cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
errorsmod "cosmossdk.io/errors"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/Stride-Labs/stride/v16/utils"
"github.com/Stride-Labs/stride/v16/x/stakeibc/types"
)
// SetHostZone set a specific hostZone in the store
func (k Keeper) SetHostZone(ctx sdk.Context, hostZone types.HostZone) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.HostZoneKey))
b := k.cdc.MustMarshal(&hostZone)
store.Set([]byte(hostZone.ChainId), b)
}
// GetHostZone returns a hostZone from its id
func (k Keeper) GetHostZone(ctx sdk.Context, chainId string) (val types.HostZone, found bool) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.HostZoneKey))
b := store.Get([]byte(chainId))
if b == nil {
return val, false
}
k.cdc.MustUnmarshal(b, &val)
return val, true
}
// GetHostZoneFromHostDenom returns a HostZone from a HostDenom
func (k Keeper) GetHostZoneFromHostDenom(ctx sdk.Context, denom string) (*types.HostZone, error) {
var matchZone types.HostZone
k.IterateHostZones(ctx, func(ctx sdk.Context, index int64, zoneInfo types.HostZone) error {
if zoneInfo.HostDenom == denom {
matchZone = zoneInfo
return nil
}
return nil
})
if matchZone.ChainId != "" {
return &matchZone, nil
}
return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "No HostZone for %s found", denom)
}
// GetHostZoneFromTransferChannelID returns a HostZone from a transfer channel ID
func (k Keeper) GetHostZoneFromTransferChannelID(ctx sdk.Context, channelID string) (hostZone types.HostZone, found bool) {
for _, hostZone := range k.GetAllActiveHostZone(ctx) {
if hostZone.TransferChannelId == channelID {
return hostZone, true
}
}
return types.HostZone{}, false
}
// RemoveHostZone removes a hostZone from the store
func (k Keeper) RemoveHostZone(ctx sdk.Context, chain_id string) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.HostZoneKey))
store.Delete([]byte(chain_id))
}
// GetAllHostZone returns all hostZone
func (k Keeper) GetAllHostZone(ctx sdk.Context) (list []types.HostZone) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.HostZoneKey))
iterator := sdk.KVStorePrefixIterator(store, []byte{})
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var val types.HostZone
k.cdc.MustUnmarshal(iterator.Value(), &val)
list = append(list, val)
}
return
}
// GetAllActiveHostZone returns all hostZones that are active (halted = false)
func (k Keeper) GetAllActiveHostZone(ctx sdk.Context) (list []types.HostZone) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.HostZoneKey))
iterator := sdk.KVStorePrefixIterator(store, []byte{})
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var val types.HostZone
k.cdc.MustUnmarshal(iterator.Value(), &val)
if !val.Halted {
list = append(list, val)
}
}
return
}
// Updates a validator's individual delegation, and the corresponding total delegation on the host zone
// Note: This modifies the original host zone struct. The calling function must Set this host zone
// for changes to persist
func (k Keeper) AddDelegationToValidator(
ctx sdk.Context,
hostZone *types.HostZone,
validatorAddress string,
amount sdkmath.Int,
callbackId string,
) error {
for _, validator := range hostZone.Validators {
if validator.Address == validatorAddress {
k.Logger(ctx).Info(utils.LogICACallbackWithHostZone(hostZone.ChainId, callbackId,
" Validator %s, Current Delegation: %v, Delegation Change: %v", validator.Address, validator.Delegation, amount))
// If the delegation change is negative, make sure it wont cause the delegation to fall below zero
if amount.IsNegative() {
if amount.Abs().GT(validator.Delegation) {
return errorsmod.Wrapf(types.ErrValidatorDelegationChg,
"Delegation change (%v) is greater than validator (%s) delegation %v",
amount.Abs(), validatorAddress, validator.Delegation)
}
if amount.Abs().GT(hostZone.TotalDelegations) {
return errorsmod.Wrapf(types.ErrValidatorDelegationChg,
"Delegation change (%v) is greater than total delegation amount on host %s (%v)",
amount.Abs(), hostZone.ChainId, hostZone.TotalDelegations)
}
}
validator.Delegation = validator.Delegation.Add(amount)
hostZone.TotalDelegations = hostZone.TotalDelegations.Add(amount)
return nil
}
}
return errorsmod.Wrapf(types.ErrValidatorNotFound,
"Could not find validator %s on host zone %s", validatorAddress, hostZone.ChainId)
}
// Increments the validators slash query progress tracker
func (k Keeper) IncrementValidatorSlashQueryProgress(
ctx sdk.Context,
chainId string,
validatorAddress string,
amount sdkmath.Int,
) error {
hostZone, found := k.GetHostZone(ctx, chainId)
if !found {
return types.ErrHostZoneNotFound
}
validator, valIndex, found := GetValidatorFromAddress(hostZone.Validators, validatorAddress)
if !found {
return types.ErrValidatorNotFound
}
// Increment the progress tracker
oldProgress := validator.SlashQueryProgressTracker
newProgress := validator.SlashQueryProgressTracker.Add(amount)
validator.SlashQueryProgressTracker = newProgress
// If the checkpoint is zero, it implies the TVL was 0 last time it was set, and we should
// update it here
// If the checkpoint is non-zero, only update it if it was just breached
shouldUpdateCheckpoint := true
if !validator.SlashQueryCheckpoint.IsZero() {
oldInterval := oldProgress.Quo(validator.SlashQueryCheckpoint)
newInterval := newProgress.Quo(validator.SlashQueryCheckpoint)
shouldUpdateCheckpoint = oldInterval.LT(newInterval)
}
// Optionally re-calculate the checkpoint
// Threshold of 1% means once 1% of TVL has been breached, the query is issued
if shouldUpdateCheckpoint {
validator.SlashQueryCheckpoint = k.GetUpdatedSlashQueryCheckpoint(ctx, hostZone.TotalDelegations)
}
hostZone.Validators[valIndex] = &validator
k.SetHostZone(ctx, hostZone)
return nil
}
// Increments the number of validator delegation changes in progress by 1
// Note: This modifies the original host zone struct. The calling function must Set this host zone
// for changes to persist
func (k Keeper) IncrementValidatorDelegationChangesInProgress(hostZone *types.HostZone, validatorAddress string) error {
validator, valIndex, found := GetValidatorFromAddress(hostZone.Validators, validatorAddress)
if !found {
return errorsmod.Wrapf(types.ErrValidatorNotFound, "validator %s not found", validatorAddress)
}
validator.DelegationChangesInProgress += 1
hostZone.Validators[valIndex] = &validator
return nil
}
// Decrements the number of validator delegation changes in progress by 1
// Note: This modifies the original host zone struct. The calling function must Set this host zone
// for changes to persist
func (k Keeper) DecrementValidatorDelegationChangesInProgress(hostZone *types.HostZone, validatorAddress string) error {
validator, valIndex, found := GetValidatorFromAddress(hostZone.Validators, validatorAddress)
if !found {
return errorsmod.Wrapf(types.ErrValidatorNotFound, "validator %s not found", validatorAddress)
}
if validator.DelegationChangesInProgress == 0 {
return errorsmod.Wrapf(types.ErrInvalidValidatorDelegationUpdates,
"cannot decrement the number of delegation updates if the validator has 0 updates in progress")
}
validator.DelegationChangesInProgress -= 1
hostZone.Validators[valIndex] = &validator
return nil
}
// Appends a validator to host zone (if the host zone is not already at capacity)
// If the validator is added through governance, the weight is equal to the minimum weight across the set
// If the validator is added through an admin transactions, the weight is specified in the message
func (k Keeper) AddValidatorToHostZone(ctx sdk.Context, chainId string, validator types.Validator, fromGovernance bool) error {
// Get the corresponding host zone
hostZone, found := k.GetHostZone(ctx, chainId)
if !found {
return errorsmod.Wrapf(types.ErrHostZoneNotFound, "Host Zone (%s) not found", chainId)
}
// Check that we don't already have this validator
// Grab the minimum weight in the process (to assign to validator's added through governance)
var minWeight uint64 = math.MaxUint64
for _, existingValidator := range hostZone.Validators {
if existingValidator.Address == validator.Address {
return errorsmod.Wrapf(types.ErrValidatorAlreadyExists, "Validator address (%s) already exists on Host Zone (%s)", validator.Address, chainId)
}
if existingValidator.Name == validator.Name {
return errorsmod.Wrapf(types.ErrValidatorAlreadyExists, "Validator name (%s) already exists on Host Zone (%s)", validator.Name, chainId)
}
// Store the min weight to assign to new validator added through governance (ignore zero-weight validators)
if existingValidator.Weight < minWeight && existingValidator.Weight > 0 {
minWeight = existingValidator.Weight
}
}
// If the validator was added via governance, set the weight to the min validator weight of the host zone
valWeight := validator.Weight
if fromGovernance {
valWeight = minWeight
}
// Determine the slash query checkpoint for LSM liquid stakes
checkpoint := k.GetUpdatedSlashQueryCheckpoint(ctx, hostZone.TotalDelegations)
// Finally, add the validator to the host
hostZone.Validators = append(hostZone.Validators, &types.Validator{
Name: validator.Name,
Address: validator.Address,
Weight: valWeight,
Delegation: sdkmath.ZeroInt(),
SlashQueryProgressTracker: sdkmath.ZeroInt(),
SlashQueryCheckpoint: checkpoint,
})
k.SetHostZone(ctx, hostZone)
return nil
}
// Removes a validator from a host zone
// The validator must be zero-weight and have no delegations in order to be removed
// There must also be no LSMTokenDeposits in progress since this would update the delegation on completion
func (k Keeper) RemoveValidatorFromHostZone(ctx sdk.Context, chainId string, validatorAddress string) error {
hostZone, found := k.GetHostZone(ctx, chainId)
if !found {
errMsg := fmt.Sprintf("HostZone (%s) not found", chainId)
k.Logger(ctx).Error(errMsg)
return errorsmod.Wrapf(types.ErrHostZoneNotFound, errMsg)
}
// Check for LSMTokenDeposit records with this specific validator address
lsmTokenDeposits := k.RecordsKeeper.GetAllLSMTokenDeposit(ctx)
for _, lsmTokenDeposit := range lsmTokenDeposits {
if lsmTokenDeposit.ValidatorAddress == validatorAddress {
return errorsmod.Wrapf(types.ErrUnableToRemoveValidator, "Validator (%s) still has at least one LSMTokenDeposit (%+v)", validatorAddress, lsmTokenDeposit)
}
}
for i, val := range hostZone.Validators {
if val.GetAddress() == validatorAddress {
if val.Delegation.IsZero() && val.Weight == 0 {
hostZone.Validators = append(hostZone.Validators[:i], hostZone.Validators[i+1:]...)
k.SetHostZone(ctx, hostZone)
return nil
}
errMsg := fmt.Sprintf("Validator (%s) has non-zero delegation (%v) or weight (%d)", validatorAddress, val.Delegation, val.Weight)
k.Logger(ctx).Error(errMsg)
return errors.New(errMsg)
}
}
errMsg := fmt.Sprintf("Validator address (%s) not found on host zone (%s)", validatorAddress, chainId)
k.Logger(ctx).Error(errMsg)
return errorsmod.Wrapf(types.ErrValidatorNotFound, errMsg)
}
// Get a validator and its index from a list of validators, by address
func GetValidatorFromAddress(validators []*types.Validator, address string) (val types.Validator, index int64, found bool) {
for i, v := range validators {
if v.Address == address {
return *v, int64(i), true
}
}
return types.Validator{}, 0, false
}
// GetHostZoneFromIBCDenom returns a HostZone from a IBCDenom
func (k Keeper) GetHostZoneFromIBCDenom(ctx sdk.Context, denom string) (*types.HostZone, error) {
var matchZone types.HostZone
k.IterateHostZones(ctx, func(ctx sdk.Context, index int64, zoneInfo types.HostZone) error {
if zoneInfo.IbcDenom == denom {
matchZone = zoneInfo
return nil
}
return nil
})
if matchZone.ChainId != "" {
return &matchZone, nil
}
return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "No HostZone for %s found", denom)
}
// Validate whether a denom is a supported liquid staking token
func (k Keeper) CheckIsStToken(ctx sdk.Context, denom string) bool {
for _, hostZone := range k.GetAllHostZone(ctx) {
if types.StAssetDenomFromHostZoneDenom(hostZone.HostDenom) == denom {
return true
}
}
return false
}
// IterateHostZones iterates zones
func (k Keeper) IterateHostZones(ctx sdk.Context, fn func(ctx sdk.Context, index int64, zoneInfo types.HostZone) error) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.HostZoneKey))
iterator := sdk.KVStorePrefixIterator(store, nil)
defer iterator.Close()
i := int64(0)
for ; iterator.Valid(); iterator.Next() {
k.Logger(ctx).Debug(fmt.Sprintf("Iterating HostZone %d", i))
zone := types.HostZone{}
k.cdc.MustUnmarshal(iterator.Value(), &zone)
error := fn(ctx, i, zone)
if error != nil {
break
}
i++
}
}