forked from cosmos/cosmos-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
invariants.go
162 lines (137 loc) · 5.33 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
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
package keeper
import (
"fmt"
sdk "github.com/JaTochNietDan/cosmos-sdk/types"
"github.com/JaTochNietDan/cosmos-sdk/x/distribution/types"
stakingtypes "github.com/JaTochNietDan/cosmos-sdk/x/staking/types"
)
// register all distribution invariants
func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) {
ir.RegisterRoute(types.ModuleName, "nonnegative-outstanding",
NonNegativeOutstandingInvariant(k))
ir.RegisterRoute(types.ModuleName, "can-withdraw",
CanWithdrawInvariant(k))
ir.RegisterRoute(types.ModuleName, "reference-count",
ReferenceCountInvariant(k))
ir.RegisterRoute(types.ModuleName, "module-account",
ModuleAccountInvariant(k))
}
// AllInvariants runs all invariants of the distribution module
func AllInvariants(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
res, stop := CanWithdrawInvariant(k)(ctx)
if stop {
return res, stop
}
res, stop = NonNegativeOutstandingInvariant(k)(ctx)
if stop {
return res, stop
}
res, stop = ReferenceCountInvariant(k)(ctx)
if stop {
return res, stop
}
return ModuleAccountInvariant(k)(ctx)
}
}
// NonNegativeOutstandingInvariant checks that outstanding unwithdrawn fees are never negative
func NonNegativeOutstandingInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
var msg string
var count int
var outstanding sdk.DecCoins
k.IterateValidatorOutstandingRewards(ctx, func(addr sdk.ValAddress, rewards types.ValidatorOutstandingRewards) (stop bool) {
outstanding = rewards.GetRewards()
if outstanding.IsAnyNegative() {
count++
msg += fmt.Sprintf("\t%v has negative outstanding coins: %v\n", addr, outstanding)
}
return false
})
broken := count != 0
return sdk.FormatInvariant(types.ModuleName, "nonnegative outstanding",
fmt.Sprintf("found %d validators with negative outstanding rewards\n%s", count, msg)), broken
}
}
// CanWithdrawInvariant checks that current rewards can be completely withdrawn
func CanWithdrawInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
// cache, we don't want to write changes
ctx, _ = ctx.CacheContext()
var remaining sdk.DecCoins
valDelegationAddrs := make(map[string][]sdk.AccAddress)
for _, del := range k.stakingKeeper.GetAllSDKDelegations(ctx) {
valAddr := del.GetValidatorAddr().String()
valDelegationAddrs[valAddr] = append(valDelegationAddrs[valAddr], del.GetDelegatorAddr())
}
// iterate over all validators
k.stakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (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.GetValidatorOutstandingRewardsCoins(ctx, val.GetOperator())
if len(remaining) > 0 && remaining[0].Amount.IsNegative() {
return true
}
return false
})
broken := len(remaining) > 0 && remaining[0].Amount.IsNegative()
return sdk.FormatInvariant(types.ModuleName, "can withdraw",
fmt.Sprintf("remaining coins: %v\n", remaining)), broken
}
}
// ReferenceCountInvariant checks that the number of historical rewards records is correct
func ReferenceCountInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
valCount := uint64(0)
k.stakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) {
valCount++
return false
})
dels := k.stakingKeeper.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)
broken := count != expected
return sdk.FormatInvariant(types.ModuleName, "reference count",
fmt.Sprintf("expected historical reference count: %d = %v validators + %v delegations + %v slashes\n"+
"total validator historical reference count: %d\n",
expected, valCount, len(dels), slashCount, count)), broken
}
}
// ModuleAccountInvariant checks that the coins held by the distr ModuleAccount
// is consistent with the sum of validator outstanding rewards
func ModuleAccountInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
var expectedCoins sdk.DecCoins
k.IterateValidatorOutstandingRewards(ctx, func(_ sdk.ValAddress, rewards types.ValidatorOutstandingRewards) (stop bool) {
expectedCoins = expectedCoins.Add(rewards.Rewards...)
return false
})
communityPool := k.GetFeePoolCommunityCoins(ctx)
expectedInt, _ := expectedCoins.Add(communityPool...).TruncateDecimal()
macc := k.GetDistributionAccount(ctx)
balances := k.bankKeeper.GetAllBalances(ctx, macc.GetAddress())
broken := !balances.IsEqual(expectedInt)
return sdk.FormatInvariant(
types.ModuleName, "ModuleAccount coins",
fmt.Sprintf("\texpected ModuleAccount coins: %s\n"+
"\tdistribution ModuleAccount coins: %s\n",
expectedInt, balances,
),
), broken
}
}