/
invariants.go
148 lines (129 loc) · 5.24 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
package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/rotosports/fury/x/evmutil/types"
)
// RegisterInvariants registers the swap module invariants
func RegisterInvariants(ir sdk.InvariantRegistry, bankK types.BankKeeper, k Keeper) {
ir.RegisterRoute(types.ModuleName, "fully-backed", FullyBackedInvariant(bankK, k))
ir.RegisterRoute(types.ModuleName, "small-balances", SmallBalancesInvariant(bankK, k))
ir.RegisterRoute(types.ModuleName, "cosmos-coins-fully-backed", CosmosCoinsFullyBackedInvariant(bankK, k))
// Disable this invariant due to some issues with it requiring some staking params to be set in genesis.
// ir.RegisterRoute(types.ModuleName, "backed-conversion-coins", BackedCoinsInvariant(bankK, k))
}
// AllInvariants runs all invariants of the swap module
func AllInvariants(bankK types.BankKeeper, k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
if res, stop := FullyBackedInvariant(bankK, k)(ctx); stop {
return res, stop
}
if res, stop := BackedCoinsInvariant(bankK, k)(ctx); stop {
return res, stop
}
if res, stop := CosmosCoinsFullyBackedInvariant(bankK, k)(ctx); stop {
return res, stop
}
return SmallBalancesInvariant(bankK, k)(ctx)
}
}
// FullyBackedInvariant ensures all minor balances are backed by the coins in the module account.
//
// The module balance can be greater than the sum of all minor balances. This can happen in rare cases
// where the evm module burns tokens.
func FullyBackedInvariant(bankK types.BankKeeper, k Keeper) sdk.Invariant {
broken := false
message := sdk.FormatInvariant(types.ModuleName, "fully backed broken", "sum of minor balances greater than module account")
return func(ctx sdk.Context) (string, bool) {
totalMinorBalances := sdk.ZeroInt()
k.IterateAllAccounts(ctx, func(acc types.Account) bool {
totalMinorBalances = totalMinorBalances.Add(acc.Balance)
return false
})
bankAddr := authtypes.NewModuleAddress(types.ModuleName)
bankBalance := bankK.GetBalance(ctx, bankAddr, CosmosDenom).Amount.Mul(ConversionMultiplier)
broken = totalMinorBalances.GT(bankBalance)
return message, broken
}
}
// SmallBalancesInvariant ensures all minor balances are less than the overflow amount, beyond this they should be converted to the major denom.
func SmallBalancesInvariant(_ types.BankKeeper, k Keeper) sdk.Invariant {
broken := false
message := sdk.FormatInvariant(types.ModuleName, "small balances broken", "minor balances not all less than overflow")
return func(ctx sdk.Context) (string, bool) {
k.IterateAllAccounts(ctx, func(account types.Account) bool {
if account.Balance.GTE(ConversionMultiplier) {
broken = true
return true
}
return false
})
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(_ types.BankKeeper, 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),
)
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
}
}
// CosmosCoinsFullyBackedInvariant ensures the total supply of ERC20 representations of sdk.Coins
// match the balances in the module account.
//
// This invariant depends on the fact that coins can only become part of the balance through
// conversion to ERC20s.
// If in the future sdk.Coins can be sent directly to the module account,
// or the module account balance can be increased in any other way,
// this invariant should be changed from checking that the balance equals the total supply,
// to check that the balance is greater than or equal to the total supply.
func CosmosCoinsFullyBackedInvariant(bankK types.BankKeeper, k Keeper) sdk.Invariant {
broken := false
message := sdk.FormatInvariant(
types.ModuleName,
"cosmos coins fully-backed broken",
"ERC20 total supply is not equal to module account balance",
)
maccAddress := authtypes.NewModuleAddress(types.ModuleName)
return func(ctx sdk.Context) (string, bool) {
k.IterateAllDeployedCosmosCoinContracts(ctx, func(c types.DeployedCosmosCoinContract) bool {
moduleBalance := bankK.GetBalance(ctx, maccAddress, c.CosmosDenom).Amount
totalSupply, err := k.QueryERC20TotalSupply(ctx, *c.Address)
if err != nil {
panic(fmt.Sprintf("failed to query total supply for %+v", c))
}
// expect total supply to equal balance in the module
if totalSupply.Cmp(moduleBalance.BigInt()) != 0 {
broken = true
}
return broken
})
return message, broken
}
}