forked from osmosis-labs/osmosis
-
Notifications
You must be signed in to change notification settings - Fork 0
/
invariants.go
153 lines (131 loc) · 5.16 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
package keeper
// DONTCOVER
import (
"fmt"
"github.com/MonOsmosis/osmosis/v3/x/gamm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)
const poolBalanceInvariantName = "pool-account-balance-equals-expected"
// RegisterInvariants registers all governance invariants
func RegisterInvariants(ir sdk.InvariantRegistry, keeper Keeper, bk types.BankKeeper) {
ir.RegisterRoute(types.ModuleName, poolBalanceInvariantName, PoolAccountInvariant(keeper, bk))
ir.RegisterRoute(types.ModuleName, "pool-total-weight", PoolTotalWeightInvariant(keeper, bk))
// ir.RegisterRoute(types.ModuleName, "pool-product-constant", PoolProductConstantInvariant(keeper))
// ir.RegisterRoute(types.ModuleName, "spot-price", SpotPriceInvariant(keeper, bk))
}
// AllInvariants runs all invariants of the gamm module
func AllInvariants(keeper Keeper, bk types.BankKeeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
msg, broke := PoolAccountInvariant(keeper, bk)(ctx)
if broke {
return msg, broke
}
msg, broke = PoolProductConstantInvariant(keeper)(ctx)
if broke {
return msg, broke
}
return PoolTotalWeightInvariant(keeper, bk)(ctx)
}
}
// PoolAccountInvariant checks that the pool account balance reflects the sum of
// pool assets
func PoolAccountInvariant(keeper Keeper, bk types.BankKeeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
pools, err := keeper.GetPools(ctx)
if err != nil {
return sdk.FormatInvariant(types.ModuleName, poolBalanceInvariantName,
fmt.Sprintf("\tgamm pool retrieval failed")), true
}
for _, pool := range pools {
assetCoins := types.PoolAssetsCoins(pool.GetAllPoolAssets())
accCoins := bk.GetAllBalances(ctx, pool.GetAddress())
if !assetCoins.IsEqual(accCoins) {
return sdk.FormatInvariant(types.ModuleName, poolBalanceInvariantName,
fmt.Sprintf("\tgamm pool id %d\n\tasset coins: %s\n\taccount coins: %s\n",
pool.GetId(), assetCoins, accCoins)), true
}
}
return sdk.FormatInvariant(types.ModuleName, poolBalanceInvariantName,
fmt.Sprintf("\tgamm all pool asset coins and account coins match\n")), false
}
}
// PoolTotalWeightInvariant checks that the pool total weight reflect the sum of
// pool weights
func PoolTotalWeightInvariant(keeper Keeper, bk types.BankKeeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
pools, err := keeper.GetPools(ctx)
if err != nil {
return sdk.FormatInvariant(types.ModuleName, "pool-total-weight",
fmt.Sprintf("\tgamm pool retrieval failed")), true
}
for _, pool := range pools {
totalWeight := sdk.ZeroInt()
for _, asset := range pool.GetAllPoolAssets() {
totalWeight = totalWeight.Add(asset.Weight)
}
if !totalWeight.Equal(pool.GetTotalWeight()) {
return sdk.FormatInvariant(types.ModuleName, "pool-total-weight",
fmt.Sprintf("\tgamm pool id %d\n\tcalculated weight sum %s\n\tpool total weight: %s\n",
pool.GetId(), totalWeight, pool.GetTotalWeight())), true
}
}
return sdk.FormatInvariant(types.ModuleName, "pool-total-weight",
fmt.Sprintf("\tgamm all pool calculated and stored total weight match\n")), false
}
}
func genericPow(base, exp sdk.Dec) sdk.Dec {
if !base.GTE(sdk.NewDec(2)) {
return pow(base, exp)
}
return powApprox(sdk.OneDec().Quo(base), exp.Neg(), powPrecision)
}
// constantChange returns the multiplicative factor difference in the pool constant, between two different pools.
// For a Balancer pool, the pool constant is prod_{t in tokens} t.bal^{t.weight}
func constantChange(p1, p2 types.PoolI) sdk.Dec {
product := sdk.OneDec()
totalWeight := p1.GetTotalWeight()
assets1 := p1.GetAllPoolAssets()
for _, asset1 := range assets1 {
asset2, err := p2.GetPoolAsset(asset1.Token.Denom)
if err != nil {
panic(err)
}
w := asset1.Weight.ToDec().Quo(totalWeight.ToDec())
ratio := asset1.Token.Amount.ToDec().Quo(asset2.Token.Amount.ToDec())
power := genericPow(ratio, w)
product = product.Mul(power)
}
return product
}
var (
errorMargin, _ = sdk.NewDecFromStr("0.0001") // 0.01%
)
// PoolProductContantInvariant chekcs that the pool constant invariant V where
// V = product([asset_balance_n^asset_weight_n]) holds.
// The invariant increases with positive swap fee, and decresed upon liquidity
// removal.
func PoolProductConstantInvariant(keeper Keeper) sdk.Invariant {
pools := make(map[uint64]types.PoolI)
return func(ctx sdk.Context) (string, bool) {
newpools, err := keeper.GetPools(ctx)
if err != nil {
return sdk.FormatInvariant(types.ModuleName, "pool-product-constant",
fmt.Sprintf("\tgamm pool retrieval failed")), true
}
for _, pool := range newpools {
oldpool, ok := pools[pool.GetId()]
if !ok {
pools[pool.GetId()] = pool
continue
}
change := constantChange(oldpool, pool)
if !(sdk.OneDec().Sub(errorMargin).LT(change) && change.LT(sdk.OneDec().Add(errorMargin))) {
return sdk.FormatInvariant(types.ModuleName, "pool-product-constant",
fmt.Sprintf("\tgamm pool id %d product constant changed\n\tdelta: %s\n", pool.GetId(), change.String())), true
}
pools[pool.GetId()] = pool
}
return sdk.FormatInvariant(types.ModuleName, "pool-product-constant",
fmt.Sprintf("\tgamm all pool product constant preserved\n")), false
}
}