-
Notifications
You must be signed in to change notification settings - Fork 0
/
invariants.go
171 lines (145 loc) · 4.92 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
169
170
171
package keeper
import (
"encoding/hex"
"fmt"
"github.com/fury-labs/fury-bridge/x/bridge/types"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// RegisterInvariants registers the bridge module invariants
func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) {
ir.RegisterRoute(types.ModuleName, "bridge-pairs", BridgePairsInvariant(k))
ir.RegisterRoute(types.ModuleName, "bridge-pairs-index", BridgePairsIndexInvariant(k))
ir.RegisterRoute(types.ModuleName, "backed-coins", BackedCoinsInvariant(k))
}
// AllInvariants runs all invariants of the bridge module
func AllInvariants(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
if res, stop := BridgePairsInvariant(k)(ctx); stop {
return res, stop
}
if res, stop := BridgePairsIndexInvariant(k)(ctx); stop {
return res, stop
}
res, stop := BackedCoinsInvariant(k)(ctx)
return res, stop
}
}
// BridgePairsInvariant iterates all bridge pairs and asserts that they are valid
func BridgePairsInvariant(k Keeper) sdk.Invariant {
broken := false
message := sdk.FormatInvariant(types.ModuleName, "validate bridge pairs broken", "bridge pair invalid")
return func(ctx sdk.Context) (string, bool) {
k.IterateBridgePairs(ctx, func(pair types.ERC20BridgePair) bool {
if err := pair.Validate(); err != nil {
broken = true
return true
}
return false
})
return message, broken
}
}
// BridgePairsIndexInvariant iterates all bridge pairs and asserts the index for
// querying are all valid.
func BridgePairsIndexInvariant(k Keeper) sdk.Invariant {
broken := false
message := sdk.FormatInvariant(
types.ModuleName,
"validate bridge pairs broken",
"bridge pair index invalid",
)
return func(ctx sdk.Context) (string, bool) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.BridgedERC20PairKeyPrefix)
byExternalIterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.BridgedERC20PairByExternalKeyPrefix)
defer byExternalIterator.Close()
var byExternalIndexLength int
for ; byExternalIterator.Valid(); byExternalIterator.Next() {
byExternalIndexLength++
idBytes := byExternalIterator.Value()
pairBytes := store.Get(idBytes)
if pairBytes == nil {
invariantMessage := sdk.FormatInvariant(
types.ModuleName,
"valid index",
fmt.Sprintf(
"\tbridge pair with ID '%s' found in external index but not in store",
hex.EncodeToString(idBytes),
),
)
return invariantMessage, true
}
}
byInternalIterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.BridgedERC20PairByInternalKeyPrefix)
defer byInternalIterator.Close()
var byInternalIndexLength int
for ; byInternalIterator.Valid(); byInternalIterator.Next() {
byInternalIndexLength++
idBytes := byInternalIterator.Value()
pairBytes := store.Get(idBytes)
if pairBytes == nil {
invariantMessage := sdk.FormatInvariant(
types.ModuleName,
"valid index",
fmt.Sprintf(
"\tbridge pair with ID '%s' found in internal index but not in store",
hex.EncodeToString(idBytes),
),
)
return invariantMessage, true
}
}
// Check length of bridge pairs store matches the length of both indices
storeIterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.BridgedERC20PairKeyPrefix)
defer storeIterator.Close()
var storeLength int
for ; storeIterator.Valid(); storeIterator.Next() {
storeLength++
}
if storeLength != byExternalIndexLength || storeLength != byInternalIndexLength {
invariantMessage := sdk.FormatInvariant(
types.ModuleName,
"valid index",
fmt.Sprintf(
"\tmismatched number of items in bridge pair store (%d), internal index (%d), and external index (%d)",
storeLength, byInternalIndexLength, byExternalIndexLength,
),
)
return invariantMessage, true
}
return message, broken
}
}
// BackedCoinsInvariant iterates all conversion pairs and asserts that the
// sdk.Coin balances are less than the module ERC20 balance.
// **Note:** This compares <= and not == as anyone can send tokens to the
// ERC20 contract address and break the invariant if a strict equal check.
func BackedCoinsInvariant(k Keeper) sdk.Invariant {
broken := false
message := sdk.FormatInvariant(
types.ModuleName,
"backed coins broken",
"coin supply is greater than module account ERC20 tokens",
)
return func(ctx sdk.Context) (string, bool) {
params := k.GetParams(ctx)
for _, pair := range params.EnabledConversionPairs {
erc20Balance, err := k.QueryERC20BalanceOf(
ctx,
pair.GetAddress(),
types.NewInternalEVMAddress(types.ModuleEVMAddress),
)
// TODO: Panic or set broken here?
if err != nil {
panic(err)
}
supply := k.bankKeeper.GetSupply(ctx, pair.Denom)
// Must be true: sdk.Coin supply < ERC20 balanceOf(module account)
if supply.Amount.BigInt().Cmp(erc20Balance) > 0 {
broken = true
break
}
}
return message, broken
}
}