/
hooks.go
288 lines (234 loc) · 10.3 KB
/
hooks.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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
package keeper
import (
"errors"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/osmosis-labs/osmosis/osmomath"
gammtypes "github.com/osmosis-labs/osmosis/v24/x/gamm/types"
"github.com/osmosis-labs/osmosis/v24/x/protorev/types"
epochstypes "github.com/osmosis-labs/osmosis/x/epochs/types"
)
type Hooks struct {
k Keeper
}
var (
_ gammtypes.GammHooks = Hooks{}
_ epochstypes.EpochHooks = Hooks{}
)
// Create new ProtoRev hooks.
func (k Keeper) Hooks() Hooks { return Hooks{k} }
// ----------------------------------------------------------------------------
// GAMM HOOKS
// ----------------------------------------------------------------------------
// AfterCFMMPoolCreated hook checks and potentially stores the pool via the highest liquidity method.
func (h Hooks) AfterCFMMPoolCreated(ctx sdk.Context, sender sdk.AccAddress, poolId uint64) {
h.k.AfterPoolCreatedWithCoins(ctx, poolId)
}
// AfterJoinPool stores swaps to be checked by protorev given the coins entered into the pool.
func (h Hooks) AfterJoinPool(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, enterCoins sdk.Coins, shareOutAmount osmomath.Int) {
// Checked to avoid future unintended behavior based on how the hook is called
if len(enterCoins) != 1 {
return
}
h.k.StoreJoinExitPoolSwaps(ctx, sender, poolId, enterCoins[0].Denom, true)
}
// AfterExitPool stores swaps to be checked by protorev given the coins exited from the pool.
func (h Hooks) AfterExitPool(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, shareInAmount osmomath.Int, exitCoins sdk.Coins) {
// Added due to ExitSwapShareAmountIn both calling
// ExitPoolHook with all denoms of the pool and then also
// Swapping which triggers the after swap hook.
// So this filters out the exit pool hook call with all denoms
if len(exitCoins) != 1 {
return
}
h.k.StoreJoinExitPoolSwaps(ctx, sender, poolId, exitCoins[0].Denom, false)
}
// AfterCFMMSwap stores swaps to be checked by protorev given the coins swapped in the pool.
func (h Hooks) AfterCFMMSwap(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, input sdk.Coins, output sdk.Coins) {
// Checked to avoid future unintended behavior based on how the hook is called
if len(input) != 1 || len(output) != 1 {
return
}
h.k.StoreSwap(ctx, poolId, input[0].Denom, output[0].Denom)
}
// ----------------------------------------------------------------------------
// CONCENTRATED LIQUIDITY HOOKS
// ----------------------------------------------------------------------------
// AfterConcentratedPoolCreated is a noop.
func (h Hooks) AfterConcentratedPoolCreated(ctx sdk.Context, sender sdk.AccAddress, poolId uint64) {
}
// AfterInitialPoolPositionCreated checks and potentially stores the pool via the highest liquidity method.
func (h Hooks) AfterInitialPoolPositionCreated(ctx sdk.Context, sender sdk.AccAddress, poolId uint64) {
h.k.AfterPoolCreatedWithCoins(ctx, poolId)
}
// AfterLastPoolPositionRemoved is a noop.
func (h Hooks) AfterLastPoolPositionRemoved(ctx sdk.Context, sender sdk.AccAddress, poolId uint64) {
}
// AfterConcentratedPoolSwap stores swaps to be checked by protorev given the coins swapped in the pool.
func (h Hooks) AfterConcentratedPoolSwap(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, input sdk.Coins, output sdk.Coins) {
// Checked to avoid future unintended behavior based on how the hook is called
if len(input) != 1 || len(output) != 1 {
return
}
h.k.StoreSwap(ctx, poolId, input[0].Denom, output[0].Denom)
}
// ----------------------------------------------------------------------------
// EPOCH HOOKS
// ----------------------------------------------------------------------------
// Hooks wrapper struct for incentives keeper
func (h Hooks) BeforeEpochStart(ctx sdk.Context, epochIdentifier string, epochNumber int64) error {
return h.k.BeforeEpochStart(ctx, epochIdentifier, epochNumber)
}
func (h Hooks) AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumber int64) error {
return h.k.AfterEpochEnd(ctx, epochIdentifier, epochNumber)
}
func (h Hooks) GetModuleName() string {
return epochstypes.ModuleName
}
// ----------------------------------------------------------------------------
// HELPER METHODS
// ----------------------------------------------------------------------------
// StoreSwap stores a swap to be checked by protorev when attempting backruns.
func (k Keeper) StoreSwap(ctx sdk.Context, poolId uint64, tokenIn, tokenOut string) {
swapToBackrun := types.Trade{
Pool: poolId,
TokenIn: tokenIn,
TokenOut: tokenOut,
}
if err := k.AddSwapsToSwapsToBackrun(ctx, []types.Trade{swapToBackrun}); err != nil {
ctx.Logger().Error("Protorev error adding swap to backrun from storeSwap: " + err.Error()) // Does not return since logging is last thing in the function
}
}
// GetComparablePoolLiquidity gets the comparable liquidity of a pool by multiplying the amounts of the pool coins.
func (k Keeper) GetComparablePoolLiquidity(ctx sdk.Context, poolId uint64) (comparableLiquidity osmomath.Int, err error) {
coins, err := k.poolmanagerKeeper.GetTotalPoolLiquidity(ctx, poolId)
if err != nil {
return osmomath.Int{}, err
}
// Recover from overflow panic
defer func() {
if r := recover(); r != nil {
comparableLiquidity = osmomath.Int{}
err = errors.New("Int overflow in GetComparablePoolLiquidity")
}
}()
comparableLiquidity = coins[0].Amount.Mul(coins[1].Amount)
return comparableLiquidity, nil
}
// StoreJoinExitPoolSwaps stores the swaps associated with GAMM join/exit pool messages in the store, depending on if it is a join or exit.
func (k Keeper) StoreJoinExitPoolSwaps(ctx sdk.Context, sender sdk.AccAddress, poolId uint64, denom string, isJoin bool) {
pool, err := k.gammKeeper.GetPoolAndPoke(ctx, poolId)
if err != nil {
return
}
// Get all the pool coins and iterate to get the denoms that make up the swap
coins := pool.GetTotalPoolLiquidity(ctx)
// Create swaps to backrun being the join coin swapped against all other pool coins
for _, coin := range coins {
if coin.Denom == denom {
continue
}
// Join messages swap in the denom, exit messages swap out the denom
if isJoin {
k.StoreSwap(ctx, poolId, denom, coin.Denom)
} else {
k.StoreSwap(ctx, poolId, coin.Denom, denom)
}
}
}
// AfterPoolCreatedWithCoins checks if the new pool should be stored as the highest liquidity pool
// for any of the base denoms, and stores it if so.
func (k Keeper) AfterPoolCreatedWithCoins(ctx sdk.Context, poolId uint64) {
baseDenoms, err := k.GetAllBaseDenoms(ctx)
if err != nil {
ctx.Logger().Error("Protorev error getting base denoms in AfterCFMMPoolCreated hook: " + err.Error())
return
}
baseDenomMap := make(map[string]bool)
for _, baseDenom := range baseDenoms {
baseDenomMap[baseDenom.Denom] = true
}
pool, err := k.poolmanagerKeeper.GetPool(ctx, poolId)
if err != nil {
ctx.Logger().Error("Protorev error getting pool in AfterCFMMPoolCreated hook: " + err.Error())
return
}
denoms, err := k.poolmanagerKeeper.RouteGetPoolDenoms(ctx, poolId)
if err != nil {
ctx.Logger().Error("Protorev error getting pool liquidity in afterPoolCreated: " + err.Error())
return
}
// Pool must be active and the number of denoms must be 2
if pool.IsActive(ctx) && len(denoms) == 2 {
// Check if either of the denoms are base denoms (denoms in which we store highest liquidity
// pools for to create backrun routes). If so, we call CompareAndStorePool which will check
// if a pool already exists for the base denom pair, and if not, stores the new pool.
// If a pool does already exist for the base denom pair, it will compare the liquidity
// of the new pool with the stored pool, and store the new pool if it has more liquidity.
if _, ok := baseDenomMap[denoms[0]]; ok {
k.CompareAndStorePool(ctx, poolId, denoms[0], denoms[1])
}
if _, ok := baseDenomMap[denoms[1]]; ok {
k.CompareAndStorePool(ctx, poolId, denoms[1], denoms[0])
}
}
}
// CompareAndStorePool compares the liquidity of the new pool with the liquidity of the stored pool, and stores the new pool if it has more liquidity.
func (k Keeper) CompareAndStorePool(ctx sdk.Context, poolId uint64, baseDenom, otherDenom string) {
storedPoolId, err := k.GetPoolForDenomPair(ctx, baseDenom, otherDenom)
if err != nil {
// Error means no pool exists for this pair, so we set it
k.SetPoolForDenomPair(ctx, baseDenom, otherDenom, poolId)
return
}
// Get comparable liquidity for the new pool
newPoolLiquidity, err := k.GetComparablePoolLiquidity(ctx, poolId)
if err != nil {
ctx.Logger().Error("Protorev error getting newPoolLiquidity in compareAndStorePool: " + err.Error())
return
}
// Get comparable liquidity for the stored pool
storedPoolLiquidity, err := k.GetComparablePoolLiquidity(ctx, storedPoolId)
if err != nil {
ctx.Logger().Error("Protorev error getting storedPoolLiquidity in compareAndStorePool: " + err.Error())
return
}
// If the new pool has more liquidity, we set it
if newPoolLiquidity.GT(storedPoolLiquidity) {
k.SetPoolForDenomPair(ctx, baseDenom, otherDenom, poolId)
}
}
func (k Keeper) BeforeEpochStart(ctx sdk.Context, epochIdentifier string, epochNumber int64) error {
return nil
}
func (k Keeper) AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumber int64) error {
// Get the current arb profits (only in base denoms to prevent spam vector)
profit, err := k.CurrentBaseDenomProfits(ctx)
if err != nil {
return err
}
// Distribute profits to developer account, community pool, and burn osmo
err = k.DistributeProfit(ctx, profit)
if err != nil {
return err
}
return nil
}
// CalculateCurrentProfit retrieves the current balance of the protorev module account and filters for base denoms.
func (k Keeper) CurrentBaseDenomProfits(ctx sdk.Context) (sdk.Coins, error) {
moduleAcc := k.accountKeeper.GetModuleAddress(types.ModuleName)
baseDenoms, err := k.GetAllBaseDenoms(ctx)
if err != nil {
return nil, err
}
// Get the current protorev balance of all denoms
protorevBalanceAllDenoms := k.bankKeeper.GetAllBalances(ctx, moduleAcc)
// Filter for base denoms
var protorevBalanceBaseDenoms sdk.Coins
for _, baseDenom := range baseDenoms {
amountOfBaseDenom := protorevBalanceAllDenoms.AmountOf(baseDenom.Denom)
if !amountOfBaseDenom.IsZero() {
protorevBalanceBaseDenoms = append(protorevBalanceBaseDenoms, sdk.NewCoin(baseDenom.Denom, amountOfBaseDenom))
}
}
return protorevBalanceBaseDenoms.Sort(), nil
}