/
invariants.go
168 lines (158 loc) · 5.15 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
163
164
165
166
167
168
package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmosquad-labs/squad/v3/x/lpfarm/types"
)
func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) {
ir.RegisterRoute(types.ModuleName, "reference-count", ReferenceCountInvariant(k))
ir.RegisterRoute(types.ModuleName, "current-rewards", OutstandingRewardsInvariant(k))
ir.RegisterRoute(types.ModuleName, "can-withdraw", CanWithdrawInvariant(k))
ir.RegisterRoute(types.ModuleName, "total-farming-amount", TotalFarmingAmountInvariant(k))
}
func AllInvariants(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (res string, broken bool) {
res, broken = ReferenceCountInvariant(k)(ctx)
if broken {
return
}
res, broken = OutstandingRewardsInvariant(k)(ctx)
if broken {
return
}
res, broken = CanWithdrawInvariant(k)(ctx)
if broken {
return
}
return TotalFarmingAmountInvariant(k)(ctx)
}
}
// ReferenceCountInvariant checks that the all historical rewards object has
// consistent reference counts with all farms and positions.
func ReferenceCountInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
numFarms := uint64(0)
k.IterateAllFarms(ctx, func(denom string, farm types.Farm) (stop bool) {
numFarms++
return false
})
numPositions := uint64(0)
k.IterateAllPositions(ctx, func(position types.Position) (stop bool) {
numPositions++
return false
})
expected := numFarms + numPositions
got := uint64(0)
k.IterateAllHistoricalRewards(ctx, func(denom string, period uint64, hist types.HistoricalRewards) (stop bool) {
got += uint64(hist.ReferenceCount)
return false
})
broken := got != expected
return sdk.FormatInvariant(
types.ModuleName, "reference count",
fmt.Sprintf(
"total reference count %d != expected %d(=%d farms+ %d positions)",
got, expected, numFarms, numPositions,
),
), broken
}
}
// OutstandingRewardsInvariant checks that the outstanding rewards of all
// farms are not smaller than the farm's current accrued rewards, and the reward
// pool has sufficient balances for those rewards.
func OutstandingRewardsInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
outstanding := sdk.DecCoins{}
msg := ""
cnt := 0
k.IterateAllFarms(ctx, func(denom string, farm types.Farm) (stop bool) {
_, hasNeg := farm.OutstandingRewards.SafeSub(farm.CurrentRewards)
if hasNeg {
msg += fmt.Sprintf(
"\tfarm %s has smaller outstanding rewards than current rewards: %s < %s\n",
denom, farm.OutstandingRewards, farm.CurrentRewards)
cnt++
}
outstanding = outstanding.Add(farm.CurrentRewards...)
return false
})
if cnt != 0 {
return sdk.FormatInvariant(
types.ModuleName, "outstanding rewards",
fmt.Sprintf(
"found %d farm(s) with smaller outstanding rewards than current rewards\n%s",
cnt, msg,
),
), true
}
balances := k.bankKeeper.SpendableCoins(ctx, types.RewardsPoolAddress)
_, broken := sdk.NewDecCoinsFromCoins(balances...).SafeSub(outstanding)
return sdk.FormatInvariant(
types.ModuleName, "outstanding rewards",
fmt.Sprintf(
"rewards pool balances %s is smaller than expected %s",
balances, outstanding,
),
), broken
}
}
// CanWithdrawInvariant checks that all farmers can withdraw their rewards.
func CanWithdrawInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (res string, broken bool) {
defer func() {
if r := recover(); r != nil {
broken = true
res = sdk.FormatInvariant(
types.ModuleName, "can withdraw",
"cannot withdraw due to negative outstanding rewards",
)
}
}()
ctx, _ = ctx.CacheContext()
k.IterateAllPositions(ctx, func(position types.Position) (stop bool) {
farmerAddr, _ := sdk.AccAddressFromBech32(position.Farmer)
if _, err := k.Harvest(ctx, farmerAddr, position.Denom); err != nil {
panic(err)
}
return false
})
return
}
}
// TotalFarmingAmountInvariant checks that all farm's total farming amount are
// equal to the sum of all the positions' farming amount which belong to the farm.
func TotalFarmingAmountInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
totalFarmingAmtByDenom := map[string]sdk.Int{}
farmingAmtSumByDenom := map[string]sdk.Int{}
k.IterateAllFarms(ctx, func(denom string, farm types.Farm) (stop bool) {
totalFarmingAmtByDenom[denom] = farm.TotalFarmingAmount
farmingAmtSumByDenom[denom] = sdk.ZeroInt()
return false
})
k.IterateAllPositions(ctx, func(position types.Position) (stop bool) {
farmingAmtSumByDenom[position.Denom] =
farmingAmtSumByDenom[position.Denom].Add(position.FarmingAmount)
return false
})
msg := ""
cnt := 0
for denom := range totalFarmingAmtByDenom {
if !totalFarmingAmtByDenom[denom].Equal(farmingAmtSumByDenom[denom]) {
msg += fmt.Sprintf(
"\tfarm %s total farming amount %s != sum %s\n",
denom, totalFarmingAmtByDenom[denom], farmingAmtSumByDenom[denom],
)
cnt++
}
}
broken := cnt != 0
return sdk.FormatInvariant(
types.ModuleName, "total farming amount",
fmt.Sprintf(
"found %d farm(s) with wrong total farming amount\n%s",
cnt, msg,
),
), broken
}
}