forked from cosmos/cosmos-sdk
/
invariants.go
127 lines (103 loc) · 3.52 KB
/
invariants.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
package simulation
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
)
// AllInvariants runs all invariants of the distribution module
func AllInvariants(d distr.Keeper, stk types.StakingKeeper) sdk.Invariant {
return func(ctx sdk.Context) error {
err := CanWithdrawInvariant(d, stk)(ctx)
if err != nil {
return err
}
err = NonNegativeOutstandingInvariant(d)(ctx)
if err != nil {
return err
}
err = ReferenceCountInvariant(d, stk)(ctx)
if err != nil {
return err
}
return nil
}
}
// NonNegativeOutstandingInvariant checks that outstanding unwithdrawn fees are never negative
func NonNegativeOutstandingInvariant(k distr.Keeper) sdk.Invariant {
return func(ctx sdk.Context) error {
var outstanding sdk.DecCoins
k.IterateValidatorOutstandingRewards(ctx, func(_ sdk.ValAddress, rewards types.ValidatorOutstandingRewards) (stop bool) {
outstanding = rewards
if outstanding.IsAnyNegative() {
return true
}
return false
})
if outstanding.IsAnyNegative() {
return fmt.Errorf("negative outstanding coins: %v", outstanding)
}
return nil
}
}
// CanWithdrawInvariant checks that current rewards can be completely withdrawn
func CanWithdrawInvariant(k distr.Keeper, sk types.StakingKeeper) sdk.Invariant {
return func(ctx sdk.Context) error {
// cache, we don't want to write changes
ctx, _ = ctx.CacheContext()
var remaining sdk.DecCoins
valDelegationAddrs := make(map[string][]sdk.AccAddress)
for _, del := range sk.GetAllSDKDelegations(ctx) {
valAddr := del.GetValidatorAddr().String()
valDelegationAddrs[valAddr] = append(valDelegationAddrs[valAddr], del.GetDelegatorAddr())
}
// iterate over all validators
sk.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) {
_ = k.WithdrawValidatorCommission(ctx, val.GetOperator())
delegationAddrs, ok := valDelegationAddrs[val.GetOperator().String()]
if ok {
for _, delAddr := range delegationAddrs {
if err := k.WithdrawDelegationRewards(ctx, delAddr, val.GetOperator()); err != nil {
panic(err)
}
}
}
remaining = k.GetValidatorOutstandingRewards(ctx, val.GetOperator())
if len(remaining) > 0 && remaining[0].Amount.LT(sdk.ZeroDec()) {
return true
}
return false
})
if len(remaining) > 0 && remaining[0].Amount.LT(sdk.ZeroDec()) {
return fmt.Errorf("negative remaining coins: %v", remaining)
}
return nil
}
}
// ReferenceCountInvariant checks that the number of historical rewards records is correct
func ReferenceCountInvariant(k distr.Keeper, sk types.StakingKeeper) sdk.Invariant {
return func(ctx sdk.Context) error {
valCount := uint64(0)
sk.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) {
valCount++
return false
})
dels := sk.GetAllSDKDelegations(ctx)
slashCount := uint64(0)
k.IterateValidatorSlashEvents(ctx,
func(_ sdk.ValAddress, _ uint64, _ types.ValidatorSlashEvent) (stop bool) {
slashCount++
return false
})
// one record per validator (last tracked period), one record per
// delegation (previous period), one record per slash (previous period)
expected := valCount + uint64(len(dels)) + slashCount
count := k.GetValidatorHistoricalReferenceCount(ctx)
if count != expected {
return fmt.Errorf("unexpected number of historical rewards records: "+
"expected %v (%v vals + %v dels + %v slashes), got %v",
expected, valCount, len(dels), slashCount, count)
}
return nil
}
}