diff --git a/CHANGELOG.md b/CHANGELOG.md index 396ec9378b9..bcd9433f09d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [#4830](https://github.com/osmosis-labs/osmosis/pull/4830) Add gas cost when we AddToGaugeRewards, linearly increase with coins to add * [#4886](https://github.com/osmosis-labs/osmosis/pull/4886) Implement MsgSplitRouteSwapExactAmountIn and MsgSplitRouteSwapExactAmountOut that supports route splitting. * [#5000](https://github.com/osmosis-labs/osmosis/pull/5000) osmomath.Power panics for base < 1 to temporarily restrict broken logic for such base. + * [#5045] (https://github.com/osmosis-labs/osmosis/pull/5045) Implement hook-based backrunning logic for ProtoRev * [#5281](https://github.com/osmosis-labs/osmosis/pull/5281) Add option to designate Reward Recipient to Lock and Incentives. * [#4827] (https://github.com/osmosis-labs/osmosis/pull/4827) Protorev: Change highest liquidity pool updating from weekly to daily and change dev fee payout from weekly to after every trade. diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index a747eab5c19..3cc7c08d0db 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -690,6 +690,7 @@ func (appKeepers *AppKeepers) SetupHooks() { // insert gamm hooks receivers here appKeepers.PoolIncentivesKeeper.Hooks(), appKeepers.TwapKeeper.GammHooks(), + appKeepers.ProtoRevKeeper.Hooks(), ), ) @@ -697,6 +698,7 @@ func (appKeepers *AppKeepers) SetupHooks() { concentratedliquiditytypes.NewConcentratedLiquidityListeners( appKeepers.TwapKeeper.ConcentratedLiquidityListener(), appKeepers.PoolIncentivesKeeper.Hooks(), + appKeepers.ProtoRevKeeper.Hooks(), ), ) diff --git a/x/protorev/keeper/epoch_hook_test.go b/x/protorev/keeper/epoch_hook_test.go index 012cafe0f7b..ee3d85691de 100644 --- a/x/protorev/keeper/epoch_hook_test.go +++ b/x/protorev/keeper/epoch_hook_test.go @@ -162,7 +162,7 @@ func (s *KeeperTestSuite) TestUpdateHighestLiquidityPools() { }, expectedBaseDenomPools: map[string]map[string]keeper.LiquidityPoolStruct{ "epochTwo": { - "uosmo": {Liquidity: sdk.NewInt(2000000), PoolId: 49}, + "uosmo": {Liquidity: sdk.Int(sdk.NewUintFromString("999999999000000001000000000000000000")), PoolId: 49}, }, }, }, diff --git a/x/protorev/keeper/hooks.go b/x/protorev/keeper/hooks.go new file mode 100644 index 00000000000..2904b5c3165 --- /dev/null +++ b/x/protorev/keeper/hooks.go @@ -0,0 +1,222 @@ +package keeper + +import ( + "errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + gammtypes "github.com/osmosis-labs/osmosis/v16/x/gamm/types" + "github.com/osmosis-labs/osmosis/v16/x/protorev/types" +) + +type Hooks struct { + k Keeper +} + +var ( + _ gammtypes.GammHooks = 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 sdk.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 sdk.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) +} + +// ---------------------------------------------------------------------------- +// 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) // 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 sdk.Int, err error) { + coins, err := k.poolmanagerKeeper.GetTotalPoolLiquidity(ctx, poolId) + if err != nil { + return sdk.Int{}, err + } + + // Recover from overflow panic + defer func() { + if r := recover(); r != nil { + comparableLiquidity = sdk.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) + 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) + return + } + + denoms, err := k.poolmanagerKeeper.RouteGetPoolDenoms(ctx, poolId) + if err != nil { + ctx.Logger().Error("Protorev error getting pool liquidity in afterPoolCreated", err) + 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) + 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) + return + } + + // If the new pool has more liquidity, we set it + if newPoolLiquidity.GT(storedPoolLiquidity) { + k.SetPoolForDenomPair(ctx, baseDenom, otherDenom, poolId) + } +} diff --git a/x/protorev/keeper/hooks_test.go b/x/protorev/keeper/hooks_test.go new file mode 100644 index 00000000000..6166fa22529 --- /dev/null +++ b/x/protorev/keeper/hooks_test.go @@ -0,0 +1,849 @@ +package keeper_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/osmosis-labs/osmosis/v16/x/gamm/pool-models/balancer" + "github.com/osmosis-labs/osmosis/v16/x/gamm/pool-models/stableswap" + poolmanagertypes "github.com/osmosis-labs/osmosis/v16/x/poolmanager/types" + "github.com/osmosis-labs/osmosis/v16/x/protorev/types" +) + +// Tests the hook implementation that is called after swapping +func (s *KeeperTestSuite) TestSwapping() { + type param struct { + expectedTrades []types.Trade + executeSwap func() + } + + tests := []struct { + name string + param param + expectPass bool + }{ + { + name: "swap exact amount in", + param: param{ + expectedTrades: []types.Trade{ + { + Pool: 1, + TokenIn: "akash", + TokenOut: "Atom", + }, + }, + executeSwap: func() { + _, err := s.App.PoolManagerKeeper.SwapExactAmountIn(s.Ctx, s.TestAccs[0], 1, sdk.NewCoin("akash", sdk.NewInt(100)), "Atom", sdk.NewInt(1)) + s.Require().NoError(err) + }, + }, + expectPass: true, + }, + { + name: "swap route exact amount in", + param: param{ + expectedTrades: []types.Trade{ + { + Pool: 1, + TokenIn: "akash", + TokenOut: "Atom", + }, + }, + executeSwap: func() { + + route := []poolmanagertypes.SwapAmountInRoute{{PoolId: 1, TokenOutDenom: "Atom"}} + + _, err := s.App.PoolManagerKeeper.RouteExactAmountIn(s.Ctx, s.TestAccs[0], route, sdk.NewCoin("akash", sdk.NewInt(100)), sdk.NewInt(1)) + s.Require().NoError(err) + }, + }, + expectPass: true, + }, + { + name: "swap route exact amount out", + param: param{ + expectedTrades: []types.Trade{ + { + Pool: 1, + TokenIn: "akash", + TokenOut: "Atom", + }, + }, + executeSwap: func() { + + route := []poolmanagertypes.SwapAmountOutRoute{{PoolId: 1, TokenInDenom: "akash"}} + + _, err := s.App.PoolManagerKeeper.RouteExactAmountOut(s.Ctx, s.TestAccs[0], route, sdk.NewInt(10000), sdk.NewCoin("Atom", sdk.NewInt(100))) + s.Require().NoError(err) + }, + }, + expectPass: true, + }, + { + name: "swap route exact amount in - 2 routes", + param: param{ + expectedTrades: []types.Trade{ + { + Pool: 1, + TokenIn: "akash", + TokenOut: "Atom", + }, + { + Pool: 1, + TokenIn: "Atom", + TokenOut: "akash", + }, + }, + executeSwap: func() { + + route := []poolmanagertypes.SwapAmountInRoute{{PoolId: 1, TokenOutDenom: "Atom"}, {PoolId: 1, TokenOutDenom: "akash"}} + + _, err := s.App.PoolManagerKeeper.RouteExactAmountIn(s.Ctx, s.TestAccs[0], route, sdk.NewCoin("akash", sdk.NewInt(100)), sdk.NewInt(1)) + s.Require().NoError(err) + }, + }, + expectPass: true, + }, + { + name: "swap route exact amount in - Concentrated Liquidity", + param: param{ + expectedTrades: []types.Trade{ + { + Pool: 49, + TokenIn: "uosmo", + TokenOut: "epochTwo", + }, + }, + executeSwap: func() { + + route := []poolmanagertypes.SwapAmountInRoute{{PoolId: 49, TokenOutDenom: "epochTwo"}} + + _, err := s.App.PoolManagerKeeper.RouteExactAmountIn(s.Ctx, s.TestAccs[0], route, sdk.NewCoin("uosmo", sdk.NewInt(10)), sdk.NewInt(1)) + s.Require().NoError(err) + }, + }, + expectPass: true, + }, + } + + for _, tc := range tests { + s.Run(tc.name, func() { + s.SetupTest() + tc.param.executeSwap() + + routes, err := s.App.ProtoRevKeeper.GetSwapsToBackrun(s.Ctx) + s.Require().NoError(err) + s.Require().Equal(tc.param.expectedTrades, routes.Trades) + }) + } +} + +// Tests the hook implementation that is called after liquidity providing +func (s *KeeperTestSuite) TestLiquidityChanging() { + type param struct { + expectedTrades []types.Trade + executeLiquidityProviding func() + } + + tests := []struct { + name string + param param + expectPass bool + }{ + { + name: "GAMM - Join Swap Exact Amount In", + param: param{ + expectedTrades: []types.Trade{ + { + Pool: 1, + TokenIn: "akash", + TokenOut: "Atom", + }, + }, + executeLiquidityProviding: func() { + _, err := s.App.GAMMKeeper.JoinSwapExactAmountIn(s.Ctx, s.TestAccs[0], 1, sdk.NewCoins(sdk.NewCoin("akash", sdk.NewInt(100))), sdk.NewInt(1)) + s.Require().NoError(err) + }, + }, + expectPass: true, + }, + { + name: "GAMM - Join Swap Share Amount Out", + param: param{ + expectedTrades: []types.Trade{ + { + Pool: 1, + TokenIn: "akash", + TokenOut: "Atom", + }, + }, + executeLiquidityProviding: func() { + _, err := s.App.GAMMKeeper.JoinSwapShareAmountOut(s.Ctx, s.TestAccs[0], 1, "akash", sdk.NewInt(1000), sdk.NewInt(10000)) + s.Require().NoError(err) + }, + }, + expectPass: true, + }, + { + name: "GAMM - Exit Swap Exact Amount Out", + param: param{ + expectedTrades: []types.Trade{ + { + Pool: 1, + TokenIn: "akash", + TokenOut: "Atom", + }, + }, + executeLiquidityProviding: func() { + _, err := s.App.GAMMKeeper.ExitSwapExactAmountOut(s.Ctx, s.TestAccs[0], 1, sdk.NewCoin("Atom", sdk.NewInt(1)), sdk.NewInt(1002141106353159235)) + s.Require().NoError(err) + }, + }, + expectPass: true, + }, + { + name: "GAMM - Exit Swap Share Amount In", + param: param{ + expectedTrades: []types.Trade{ + { + Pool: 1, + TokenIn: "akash", + TokenOut: "Atom", + }, + }, + executeLiquidityProviding: func() { + _, err := s.App.GAMMKeeper.ExitSwapShareAmountIn(s.Ctx, s.TestAccs[0], 1, "Atom", sdk.NewInt(1000000000000000000), sdk.NewInt(1)) + s.Require().NoError(err) + }, + }, + expectPass: true, + }, + { + name: "GAMM - Exit Swap Share Amount In - Low Shares", + param: param{ + expectedTrades: []types.Trade(nil), + executeLiquidityProviding: func() { + _, err := s.App.GAMMKeeper.ExitSwapShareAmountIn(s.Ctx, s.TestAccs[0], 1, "Atom", sdk.NewInt(1000), sdk.NewInt(0)) + s.Require().NoError(err) + }, + }, + expectPass: true, + }, + } + + for _, tc := range tests { + s.Run(tc.name, func() { + s.SetupTest() + tc.param.executeLiquidityProviding() + + routes, err := s.App.ProtoRevKeeper.GetSwapsToBackrun(s.Ctx) + if tc.expectPass { + s.Require().NoError(err) + s.Require().Equal(tc.param.expectedTrades, routes.Trades) + } + }) + } +} + +// Tests the hook implementation that is called after pool creation with coins +func (s *KeeperTestSuite) TestPoolCreation() { + type param struct { + matchDenom string + executePoolCreation func() uint64 + } + + tests := []struct { + name string + param param + expectPass bool + }{ + { + name: "GAMM - Create Pool", + param: param{ + matchDenom: "hookGamm", + executePoolCreation: func() uint64 { + poolId := s.createGAMMPool([]balancer.PoolAsset{ + { + Token: sdk.NewCoin("hookGamm", sdk.NewInt(1000000000)), + Weight: sdk.NewInt(1), + }, + { + Token: sdk.NewCoin(types.OsmosisDenomination, sdk.NewInt(1000000000)), + Weight: sdk.NewInt(1), + }, + }, + sdk.NewDecWithPrec(2, 3), + sdk.NewDecWithPrec(0, 2)) + + return poolId + }, + }, + expectPass: true, + }, + { + name: "Concentrated Liquidity - Create Pool w/ No Liqudity", + param: param{ + matchDenom: "hookCL", + executePoolCreation: func() uint64 { + clPool := s.PrepareConcentratedPool() + return clPool.GetId() + }, + }, + expectPass: false, + }, + { + name: "Concentrated Liquidity - Create Pool w/ Liqudity", + param: param{ + matchDenom: "hookCL", + executePoolCreation: func() uint64 { + clPool := s.PrepareConcentratedPoolWithCoinsAndFullRangePosition("hookCL", "uosmo") + return clPool.GetId() + }, + }, + expectPass: true, + }, + { + name: "Create Balancer Pool First, Then Concentrated Liquidity w/ Liquidity - CL with more liquidity so should be stored", + param: param{ + matchDenom: "hook", + executePoolCreation: func() uint64 { + // Create balancer pool first with a new denom pair + balancerPoolId := s.createGAMMPool([]balancer.PoolAsset{ + { + Token: sdk.NewCoin("hook", sdk.NewInt(1)), + Weight: sdk.NewInt(1), + }, + { + Token: sdk.NewCoin("uosmo", sdk.NewInt(1)), + Weight: sdk.NewInt(1), + }, + }, + sdk.NewDecWithPrec(1, 1), + sdk.NewDecWithPrec(0, 2), + ) + + // Ensure that the balancer pool is stored since no other pool exists for the denom pair + setPoolId, err := s.App.ProtoRevKeeper.GetPoolForDenomPair(s.Ctx, "uosmo", "hook") + s.Require().NoError(err) + s.Require().Equal(balancerPoolId, setPoolId) + + // Create Concentrated Liquidity pool with the same denom pair and more liquidity + // The returned pool id should be what is finally stored in the protorev keeper + clPool := s.PrepareConcentratedPoolWithCoinsAndFullRangePosition("hook", "uosmo") + return clPool.GetId() + }, + }, + expectPass: true, + }, + { + name: "Create Concentrated Liquidity Pool w/ Liquidity First, Then Balancer Pool - Balancer with more liquidity so should be stored", + param: param{ + matchDenom: "hook", + executePoolCreation: func() uint64 { + // Create Concentrated Liquidity pool with a denom pair not already stored + clPool := s.PrepareConcentratedPoolWithCoinsAndFullRangePosition("hook", "uosmo") + + // Ensure that the concentrated pool is stored since no other pool exists for the denom pair + setPoolId, err := s.App.ProtoRevKeeper.GetPoolForDenomPair(s.Ctx, "uosmo", "hook") + s.Require().NoError(err) + s.Require().Equal(clPool.GetId(), setPoolId) + + // Get clPool liquidity + clPoolLiquidity, err := s.App.PoolManagerKeeper.GetTotalPoolLiquidity(s.Ctx, clPool.GetId()) + s.Require().NoError(err) + + // Create balancer pool with the same denom pair and more liquidity + balancerPoolId := s.createGAMMPool([]balancer.PoolAsset{ + { + Token: sdk.NewCoin(clPoolLiquidity[0].Denom, clPoolLiquidity[0].Amount.Add(sdk.NewInt(1))), + Weight: sdk.NewInt(1), + }, + { + Token: sdk.NewCoin(clPoolLiquidity[1].Denom, clPoolLiquidity[1].Amount.Add(sdk.NewInt(1))), + Weight: sdk.NewInt(1), + }, + }, + sdk.NewDecWithPrec(1, 1), + sdk.NewDecWithPrec(0, 2), + ) + + // The returned pool id should be what is finally stored in the protorev keeper since it has more liquidity + return balancerPoolId + }, + }, + expectPass: true, + }, + } + + for _, tc := range tests { + s.Run(tc.name, func() { + s.SetupTest() + poolId := tc.param.executePoolCreation() + setPoolId, err := s.App.ProtoRevKeeper.GetPoolForDenomPair(s.Ctx, types.OsmosisDenomination, tc.param.matchDenom) + + if tc.expectPass { + s.Require().NoError(err) + s.Require().Equal(poolId, setPoolId) + } else { + s.Require().Error(err) + s.Require().NotEqual(poolId, setPoolId) + } + }) + } +} + +// Helper function tests + +// Tests StoreSwap stores a swap properly +func (s *KeeperTestSuite) TestStoreSwap() { + type param struct { + expectedSwap types.Trade + prepareState func() + expectedSwapsStoredLen int + } + + tests := []struct { + name string + param param + expectPass bool + }{ + { + name: "Store Swap Without An Existing Swap Stored", + param: param{ + expectedSwap: types.Trade{ + Pool: 1, + TokenIn: "akash", + TokenOut: "Atom", + }, + prepareState: func() { + s.App.ProtoRevKeeper.DeleteSwapsToBackrun(s.Ctx) + }, + expectedSwapsStoredLen: 1, + }, + expectPass: true, + }, + { + name: "Store Swap With With An Existing Swap Stored", + param: param{ + expectedSwap: types.Trade{ + Pool: 2, + TokenIn: "uosmo", + TokenOut: "test", + }, + prepareState: func() { + s.App.ProtoRevKeeper.SetSwapsToBackrun(s.Ctx, types.Route{ + Trades: []types.Trade{ + { + Pool: 1, + TokenIn: "Atom", + TokenOut: "akash", + }, + }, + StepSize: sdk.NewInt(1), + }) + }, + expectedSwapsStoredLen: 2, + }, + expectPass: true, + }, + } + + for _, tc := range tests { + s.Run(tc.name, func() { + s.SetupTest() + + // Run any state preparation + tc.param.prepareState() + + // Store the swap + s.App.ProtoRevKeeper.StoreSwap(s.Ctx, tc.param.expectedSwap.Pool, tc.param.expectedSwap.TokenIn, tc.param.expectedSwap.TokenOut) + + routes, err := s.App.ProtoRevKeeper.GetSwapsToBackrun(s.Ctx) + if tc.expectPass { + s.Require().NoError(err) + s.Require().Equal(tc.param.expectedSwapsStoredLen, len(routes.Trades)) + s.Require().Equal(tc.param.expectedSwap, routes.Trades[len(routes.Trades)-1]) + } + }) + } +} + +// Tests GetComparablePoolLiquidity gets the comparable liquidity of a pool by multiplying the amounts of the pool coins. +func (s *KeeperTestSuite) TestGetComparablePoolLiquidity() { + type param struct { + executePoolCreation func() uint64 + expectedComparableLiquidity sdk.Int + } + + tests := []struct { + name string + param param + expectPass bool + }{ + { + name: "Get Balancer Pool Comparable Liquidity", + param: param{ + executePoolCreation: func() uint64 { + return s.PrepareBalancerPoolWithCoins(sdk.NewCoin("uosmo", sdk.NewInt(10)), sdk.NewCoin("juno", sdk.NewInt(10))) + }, + expectedComparableLiquidity: sdk.NewInt(100), + }, + expectPass: true, + }, + { + name: "Get Stable Swap Pool Comparable Liquidity", + param: param{ + executePoolCreation: func() uint64 { + return s.createStableswapPool( + sdk.NewCoins( + sdk.NewCoin("uosmo", sdk.NewInt(10)), + sdk.NewCoin("juno", sdk.NewInt(10)), + ), + stableswap.PoolParams{ + SwapFee: sdk.NewDecWithPrec(1, 4), + ExitFee: sdk.NewDecWithPrec(0, 2), + }, + []uint64{1, 1}, + ) + }, + expectedComparableLiquidity: sdk.NewInt(100), + }, + expectPass: true, + }, + { + name: "Get Concentrated Liquidity Pool Comparable Liquidity", + param: param{ + executePoolCreation: func() uint64 { + pool := s.PrepareConcentratedPool() + err := s.App.BankKeeper.SendCoins( + s.Ctx, + s.TestAccs[0], + pool.GetAddress(), + sdk.NewCoins(sdk.NewCoin("eth", sdk.NewInt(10)), sdk.NewCoin("usdc", sdk.NewInt(10)))) + s.Require().NoError(err) + return pool.GetId() + }, + expectedComparableLiquidity: sdk.NewInt(100), + }, + expectPass: true, + }, + { + name: "Catch overflow error on liquidity multiplication", + param: param{ + executePoolCreation: func() uint64 { + return s.PrepareBalancerPoolWithCoins( + sdk.NewCoin("uosmo", sdk.Int(sdk.NewUintFromString("999999999999999999999999999999999999999"))), + sdk.NewCoin("juno", sdk.Int(sdk.NewUintFromString("999999999999999999999999999999999999999")))) + }, + expectedComparableLiquidity: sdk.Int{}, + }, + expectPass: false, + }, + } + + for _, tc := range tests { + s.Run(tc.name, func() { + s.SetupTest() + + // Create the pool + poolId := tc.param.executePoolCreation() + + // Get the comparable liquidity + comparableLiquidity, err := s.App.ProtoRevKeeper.GetComparablePoolLiquidity(s.Ctx, poolId) + + if tc.expectPass { + s.Require().NoError(err) + s.Require().Equal(tc.param.expectedComparableLiquidity, comparableLiquidity) + } else { + s.Require().Error(err) + s.Require().Equal(tc.param.expectedComparableLiquidity, comparableLiquidity) + } + }) + } +} + +// Tests StoreJoinExitPoolSwaps stores the swaps associated with GAMM join/exit pool messages in the store, depending on if it is a join or exit. +func (s *KeeperTestSuite) TestStoreJoinExitPoolSwaps() { + type param struct { + poolId uint64 + denom string + isJoin bool + expectedSwap types.Trade + } + + tests := []struct { + name string + param param + expectPass bool + }{ + { + name: "Store Join Pool Swap", + param: param{ + poolId: 1, + denom: "akash", + isJoin: true, + expectedSwap: types.Trade{ + Pool: 1, + TokenIn: "akash", + TokenOut: "Atom", + }, + }, + expectPass: true, + }, + { + name: "Store Exit Pool Swap", + param: param{ + poolId: 1, + denom: "akash", + isJoin: false, + expectedSwap: types.Trade{ + Pool: 1, + TokenIn: "Atom", + TokenOut: "akash", + }, + }, + expectPass: true, + }, + { + name: "Non-Gamm Pool, Return Early Do Not Store Any Swaps", + param: param{ + poolId: 49, + denom: "uosmo", + isJoin: true, + expectedSwap: types.Trade{}, + }, + expectPass: false, + }, + } + + for _, tc := range tests { + s.Run(tc.name, func() { + s.SetupTest() + + // All pools are already created in the setup + s.App.ProtoRevKeeper.StoreJoinExitPoolSwaps(s.Ctx, s.TestAccs[0], tc.param.poolId, tc.param.denom, tc.param.isJoin) + + // Get the swaps to backrun after storing the swap via StoreJoinExitPoolSwaps + swapsToBackrun, err := s.App.ProtoRevKeeper.GetSwapsToBackrun(s.Ctx) + + if tc.expectPass { + s.Require().NoError(err) + s.Require().Equal(tc.param.expectedSwap, swapsToBackrun.Trades[len(swapsToBackrun.Trades)-1]) + } else { + s.Require().Equal(swapsToBackrun, types.Route{}) + } + }) + } +} + +// Tests CompareAndStorePool compares the comparable liquidity of a pool with the stored pool, storing the new pool if it has higher comparable liquidity. +// Note: This test calls DeleteAllPoolsForBaseDenom in the prepareStateAndGetPoolIdToCompare function because the +// hooks are triggered by default and we want to test the CompareAndStorePool on the state before the hooks are triggered. +func (s *KeeperTestSuite) TestCompareAndStorePool() { + type param struct { + baseDenom string + matchDenom string + prepareStateAndGetPoolIdToCompare func() (uint64, uint64) + } + + tests := []struct { + name string + param param + expectPass bool + }{ + { + name: "Nothing Stored, Store Balancer", + param: param{ + baseDenom: "uosmo", + matchDenom: "juno", + prepareStateAndGetPoolIdToCompare: func() (uint64, uint64) { + // Prepare a balancer pool with coins + poolId := s.PrepareBalancerPoolWithCoins(sdk.NewCoin("uosmo", sdk.NewInt(10)), sdk.NewCoin("juno", sdk.NewInt(10))) + + // Delete all pools for the base denom uosmo so that all tests start with a clean slate + s.App.ProtoRevKeeper.DeleteAllPoolsForBaseDenom(s.Ctx, "uosmo") + + return poolId, poolId + }, + }, + expectPass: true, + }, + { + name: "Nothing Stored, Store Concentrated Liquidity Pool w/ Coins", + param: param{ + baseDenom: "uosmo", + matchDenom: "stake", + prepareStateAndGetPoolIdToCompare: func() (uint64, uint64) { + // Prepare a concentrated liquidity pool with coins + poolId := s.PrepareConcentratedPoolWithCoinsAndFullRangePosition("uosmo", "stake").GetId() + + // Delete all pools for the base denom uosmo so that all tests start with a clean slate + s.App.ProtoRevKeeper.DeleteAllPoolsForBaseDenom(s.Ctx, "uosmo") + + return poolId, poolId + }, + }, + expectPass: true, + }, + { + name: "Balancer Previously Stored w/ Less liquidity, Compare Concentrated Liquidity Pool w/ More liqudidity, Ensure CL Gets Stored", + param: param{ + baseDenom: "uosmo", + matchDenom: "stake", + prepareStateAndGetPoolIdToCompare: func() (uint64, uint64) { + // Create a concentrated liquidity pool with more liquidity + clPoolId := s.PrepareConcentratedPoolWithCoinsAndFullRangePosition("uosmo", "stake").GetId() + + // Delete all pools for the base denom uosmo so that all tests start with a clean slate + s.App.ProtoRevKeeper.DeleteAllPoolsForBaseDenom(s.Ctx, "uosmo") + + preparedPoolId := s.PrepareBalancerPoolWithCoins(sdk.NewCoin("uosmo", sdk.NewInt(10)), sdk.NewCoin("stake", sdk.NewInt(10))) + storedPoolId, err := s.App.ProtoRevKeeper.GetPoolForDenomPair(s.Ctx, "uosmo", "stake") + s.Require().NoError(err) + s.Require().Equal(preparedPoolId, storedPoolId) + + // Return the cl pool id as expected since it has higher liquidity, compare the cl pool id + return clPoolId, clPoolId + }, + }, + expectPass: true, + }, + { + name: "Balancer Previously Stored w/ More liquidity, Compare Concentrated Liquidity Pool w/ Less liqudidity, Ensure Balancer Stays Stored", + param: param{ + baseDenom: "uosmo", + matchDenom: "stake", + prepareStateAndGetPoolIdToCompare: func() (uint64, uint64) { + // Create a concentrated liquidity pool with more liquidity + clPoolId := s.PrepareConcentratedPoolWithCoinsAndFullRangePosition("uosmo", "stake").GetId() + + // Delete all pools for the base denom uosmo so that all tests start with a clean slate + s.App.ProtoRevKeeper.DeleteAllPoolsForBaseDenom(s.Ctx, "uosmo") + + // Prepare a balancer pool with more liquidity + balancerPoolId := s.PrepareBalancerPoolWithCoins(sdk.NewCoin("uosmo", sdk.NewInt(2000000000000000000)), sdk.NewCoin("stake", sdk.NewInt(1000000000000000000))) + storedPoolId, err := s.App.ProtoRevKeeper.GetPoolForDenomPair(s.Ctx, "uosmo", "stake") + s.Require().NoError(err) + s.Require().Equal(balancerPoolId, storedPoolId) + + // Return the balancer pool id as expected since it has higher liquidity, compare the cl pool id + return balancerPoolId, clPoolId + }, + }, + expectPass: true, + }, + { + name: "Concentrated Liquidity Previously Stored w/ Less liquidity, Compare Balancer Pool w/ More liqudidity, Ensure Balancer Gets Stored", + param: param{ + baseDenom: "uosmo", + matchDenom: "stake", + prepareStateAndGetPoolIdToCompare: func() (uint64, uint64) { + // Prepare a balancer pool with more liquidity + balancerPoolId := s.PrepareBalancerPoolWithCoins(sdk.NewCoin("uosmo", sdk.NewInt(2000000000000000000)), sdk.NewCoin("stake", sdk.NewInt(1000000000000000000))) + + // Delete all pools for the base denom uosmo so that all tests start with a clean slate + s.App.ProtoRevKeeper.DeleteAllPoolsForBaseDenom(s.Ctx, "uosmo") + + // Prepare a concentrated liquidity pool with less liquidity, should be stored since nothing is stored + clPoolId := s.PrepareConcentratedPoolWithCoinsAndFullRangePosition("uosmo", "stake").GetId() + storedPoolId, err := s.App.ProtoRevKeeper.GetPoolForDenomPair(s.Ctx, "uosmo", "stake") + s.Require().NoError(err) + s.Require().Equal(clPoolId, storedPoolId) + + // Return the balancer pool id as expected since it has higher liquidity, compare the balancer pool id + return balancerPoolId, balancerPoolId + }, + }, + expectPass: true, + }, + { + name: "Concentrated Liquidity Previously Stored w/ More liquidity, Compare Balancer Pool w/ Less liqudidity, Ensure CL Stays Stored", + param: param{ + baseDenom: "uosmo", + matchDenom: "stake", + prepareStateAndGetPoolIdToCompare: func() (uint64, uint64) { + // Prepare a balancer pool with less liquidity + balancerPoolId := s.PrepareBalancerPoolWithCoins(sdk.NewCoin("uosmo", sdk.NewInt(500000000000000000)), sdk.NewCoin("stake", sdk.NewInt(1000000000000000000))) + + // Delete all pools for the base denom uosmo so that all tests start with a clean slate + s.App.ProtoRevKeeper.DeleteAllPoolsForBaseDenom(s.Ctx, "uosmo") + + // Prepare a concentrated liquidity pool with less liquidity, should be stored since nothing is stored + clPoolId := s.PrepareConcentratedPoolWithCoinsAndFullRangePosition("uosmo", "stake").GetId() + storedPoolId, err := s.App.ProtoRevKeeper.GetPoolForDenomPair(s.Ctx, "uosmo", "stake") + s.Require().NoError(err) + s.Require().Equal(clPoolId, storedPoolId) + + // Return the cl pool id as expected since it has higher liquidity, compare the balancer pool id + return clPoolId, balancerPoolId + }, + }, + expectPass: true, + }, + { + name: "Catch overflow error when getting newPoolLiquidity - Ensure test doesn't panic", + param: param{ + baseDenom: "uosmo", + matchDenom: "stake", + prepareStateAndGetPoolIdToCompare: func() (uint64, uint64) { + // Prepare a balancer pool with liquidity levels that will overflow when multiplied + overflowPoolId := s.PrepareBalancerPoolWithCoins( + sdk.NewCoin("uosmo", sdk.Int(sdk.NewUintFromString("999999999999999999999999999999999999999"))), + sdk.NewCoin("stake", sdk.Int(sdk.NewUintFromString("999999999999999999999999999999999999999")))) + + // Delete all pools for the base denom uosmo so that all tests start with a clean slate + s.App.ProtoRevKeeper.DeleteAllPoolsForBaseDenom(s.Ctx, "uosmo") + + // Prepare a balancer pool with normal liquidity + poolId := s.PrepareBalancerPoolWithCoins(sdk.NewCoin("uosmo", sdk.NewInt(10)), sdk.NewCoin("stake", sdk.NewInt(10))) + + // The normal liquidity pool should be stored since the function will return early when catching the overflow error + return poolId, overflowPoolId + }, + }, + expectPass: true, + }, + { + name: "Catch overflow error when getting storedPoolLiquidity - Ensure test doesn't panic", + param: param{ + baseDenom: "uosmo", + matchDenom: "stake", + prepareStateAndGetPoolIdToCompare: func() (uint64, uint64) { + // Prepare a balancer pool with normal liquidity + poolId := s.PrepareBalancerPoolWithCoins(sdk.NewCoin("uosmo", sdk.NewInt(10)), sdk.NewCoin("stake", sdk.NewInt(10))) + + // Delete all pools for the base denom uosmo so that all tests start with a clean slate + s.App.ProtoRevKeeper.DeleteAllPoolsForBaseDenom(s.Ctx, "uosmo") + + // Prepare a balancer pool with liquidity levels that will overflow when multiplied + overflowPoolId := s.PrepareBalancerPoolWithCoins( + sdk.NewCoin("uosmo", sdk.Int(sdk.NewUintFromString("999999999999999999999999999999999999999"))), + sdk.NewCoin("stake", sdk.Int(sdk.NewUintFromString("999999999999999999999999999999999999999")))) + + // The overflow pool should be stored since the function will return early when catching the overflow error + return overflowPoolId, poolId + }, + }, + expectPass: true, + }, + } + + for _, tc := range tests { + s.Run(tc.name, func() { + s.SetupTest() + + // Run any state preparation and get the pool id to compare to the stored pool + expectedStoredPoolId, comparePoolId := tc.param.prepareStateAndGetPoolIdToCompare() + + // Compare and store the pool + s.App.ProtoRevKeeper.CompareAndStorePool(s.Ctx, comparePoolId, tc.param.baseDenom, tc.param.matchDenom) + + // Get the stored pool id for the highest liquidity pool in protorev + storedPoolId, err := s.App.ProtoRevKeeper.GetPoolForDenomPair(s.Ctx, tc.param.baseDenom, tc.param.matchDenom) + + if tc.expectPass { + s.Require().NoError(err) + s.Require().Equal(expectedStoredPoolId, storedPoolId) + } + }) + } +} diff --git a/x/protorev/keeper/keeper_test.go b/x/protorev/keeper/keeper_test.go index de4b7961ac7..e568bc87ae1 100644 --- a/x/protorev/keeper/keeper_test.go +++ b/x/protorev/keeper/keeper_test.go @@ -135,6 +135,11 @@ func (s *KeeperTestSuite) SetupTest() { sdk.NewCoin("usdy", sdk.NewInt(9000000000000000000)), sdk.NewCoin("epochOne", sdk.NewInt(9000000000000000000)), sdk.NewCoin("epochTwo", sdk.NewInt(9000000000000000000)), + sdk.NewCoin("hookGamm", sdk.NewInt(9000000000000000000)), + sdk.NewCoin("hookCL", sdk.NewInt(9000000000000000000)), + sdk.NewCoin("hook", sdk.NewInt(9000000000000000000)), + sdk.NewCoin("eth", sdk.NewInt(9000000000000000000)), + sdk.NewCoin("gamm/pool/1", sdk.NewInt(9000000000000000000)), ) s.fundAllAccountsWith() s.Commit() @@ -888,24 +893,21 @@ func (s *KeeperTestSuite) setUpPools() { } // Create a concentrated liquidity pool for epoch_hook testing - clPoolOne := s.PrepareConcentratedPoolWithCoins("epochTwo", "uosmo") - - // Provide liquidity to the concentrated liquidity pool - clPoolOneLiquidity := sdk.NewCoins(sdk.NewCoin("epochTwo", sdk.NewInt(1000)), sdk.NewCoin("uosmo", sdk.NewInt(2000))) - err := s.App.BankKeeper.SendCoins(s.Ctx, s.TestAccs[0], clPoolOne.GetAddress(), clPoolOneLiquidity) - s.Require().NoError(err) + // Pool 49 + s.PrepareConcentratedPoolWithCoinsAndFullRangePosition("epochTwo", "uosmo") // Set all of the pool info into the stores - err = s.App.ProtoRevKeeper.UpdatePools(s.Ctx) + err := s.App.ProtoRevKeeper.UpdatePools(s.Ctx) s.Require().NoError(err) } // createStableswapPool creates a stableswap pool with the given pool assets and params -func (s *KeeperTestSuite) createStableswapPool(initialLiquidity sdk.Coins, poolParams stableswap.PoolParams, scalingFactors []uint64) { - _, err := s.App.PoolManagerKeeper.CreatePool( +func (s *KeeperTestSuite) createStableswapPool(initialLiquidity sdk.Coins, poolParams stableswap.PoolParams, scalingFactors []uint64) uint64 { + poolId, err := s.App.PoolManagerKeeper.CreatePool( s.Ctx, stableswap.NewMsgCreateStableswapPool(s.TestAccs[1], poolParams, initialLiquidity, scalingFactors, "")) s.Require().NoError(err) + return poolId } // createGAMMPool creates a balancer pool with the given pool assets and params diff --git a/x/protorev/keeper/posthandler.go b/x/protorev/keeper/posthandler.go index d64d542d236..ec60ff4621f 100644 --- a/x/protorev/keeper/posthandler.go +++ b/x/protorev/keeper/posthandler.go @@ -4,9 +4,6 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - - gammtypes "github.com/osmosis-labs/osmosis/v16/x/gamm/types" - poolmanagertypes "github.com/osmosis-labs/osmosis/v16/x/poolmanager/types" ) type SwapToBackrun struct { @@ -42,7 +39,8 @@ func (protoRevDec ProtoRevDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu // // 50M is chosen as a large enough number to ensure that the posthandler will not run out of gas, // but will eventually terminate in event of an accidental infinite loop with some gas usage. - cacheCtx = cacheCtx.WithGasMeter(sdk.NewGasMeter(sdk.Gas(50_000_000))) + upperGasLimitMeter := sdk.NewGasMeter(sdk.Gas(50_000_000)) + cacheCtx = cacheCtx.WithGasMeter(upperGasLimitMeter) // Check if the protorev posthandler can be executed if err := protoRevDec.ProtoRevKeeper.AnteHandleCheck(cacheCtx); err != nil { @@ -50,7 +48,7 @@ func (protoRevDec ProtoRevDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu } // Extract all of the pools that were swapped in the tx - swappedPools := ExtractSwappedPools(tx) + swappedPools := protoRevDec.ProtoRevKeeper.ExtractSwappedPools(cacheCtx) if len(swappedPools) == 0 { return next(ctx, tx, simulate) } @@ -63,6 +61,10 @@ func (protoRevDec ProtoRevDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu ctx.Logger().Error("ProtoRevTrade failed with error", err) } + // Delete swaps to backrun for next transaction without consuming gas + // from the current transaction's gas meter, but instead from a new gas meter + protoRevDec.ProtoRevKeeper.DeleteSwapsToBackrun(ctx.WithGasMeter(upperGasLimitMeter)) + return next(ctx, tx, simulate) } @@ -140,58 +142,20 @@ func (k Keeper) ProtoRevTrade(ctx sdk.Context, swappedPools []SwapToBackrun) (er // ExtractSwappedPools checks if there were any swaps made on pools and if so returns a list of all the pools that were // swapped on and metadata about the swap -func ExtractSwappedPools(tx sdk.Tx) []SwapToBackrun { - swappedPools := make([]SwapToBackrun, 0) - - // Extract only swaps types and the swapped pools from the tx - for _, msg := range tx.GetMsgs() { - switch msg := msg.(type) { - case *poolmanagertypes.MsgSwapExactAmountIn: - swappedPools = append(swappedPools, extractSwapInPools(msg.Routes, msg.TokenIn.Denom)...) - case *poolmanagertypes.MsgSwapExactAmountOut: - swappedPools = append(swappedPools, extractSwapOutPools(msg.Routes, msg.TokenOut.Denom)...) - case *gammtypes.MsgSwapExactAmountIn: - swappedPools = append(swappedPools, extractSwapInPools(msg.Routes, msg.TokenIn.Denom)...) - case *gammtypes.MsgSwapExactAmountOut: - swappedPools = append(swappedPools, extractSwapOutPools(msg.Routes, msg.TokenOut.Denom)...) - } - } - - return swappedPools -} - -// extractSwapInPools extracts the pools that were swapped on for a MsgSwapExactAmountIn -func extractSwapInPools(routes []poolmanagertypes.SwapAmountInRoute, tokenInDenom string) []SwapToBackrun { +func (k Keeper) ExtractSwappedPools(ctx sdk.Context) []SwapToBackrun { swappedPools := make([]SwapToBackrun, 0) - prevTokenIn := tokenInDenom - for _, route := range routes { - swappedPools = append(swappedPools, SwapToBackrun{ - PoolId: route.PoolId, - TokenOutDenom: route.TokenOutDenom, - TokenInDenom: prevTokenIn, - }) - - prevTokenIn = route.TokenOutDenom + swapsToBackrun, err := k.GetSwapsToBackrun(ctx) + if err != nil { + return swappedPools } - return swappedPools -} - -// extractSwapOutPools extracts the pools that were swapped on for a MsgSwapExactAmountOut -func extractSwapOutPools(routes []poolmanagertypes.SwapAmountOutRoute, tokenOutDenom string) []SwapToBackrun { - swappedPools := make([]SwapToBackrun, 0) - - prevTokenOut := tokenOutDenom - for i := len(routes) - 1; i >= 0; i-- { - route := routes[i] + for _, swap := range swapsToBackrun.Trades { swappedPools = append(swappedPools, SwapToBackrun{ - PoolId: route.PoolId, - TokenOutDenom: prevTokenOut, - TokenInDenom: route.TokenInDenom, + PoolId: swap.Pool, + TokenInDenom: swap.TokenIn, + TokenOutDenom: swap.TokenOut, }) - - prevTokenOut = route.TokenInDenom } return swappedPools diff --git a/x/protorev/keeper/posthandler_test.go b/x/protorev/keeper/posthandler_test.go index a790659bd3e..83b217fdc2c 100644 --- a/x/protorev/keeper/posthandler_test.go +++ b/x/protorev/keeper/posthandler_test.go @@ -13,7 +13,6 @@ import ( authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" "github.com/osmosis-labs/osmosis/v16/app/apptesting" - gammtypes "github.com/osmosis-labs/osmosis/v16/x/gamm/types" poolmanagertypes "github.com/osmosis-labs/osmosis/v16/x/poolmanager/types" "github.com/osmosis-labs/osmosis/v16/x/protorev/keeper" "github.com/osmosis-labs/osmosis/v16/x/protorev/types" @@ -75,12 +74,7 @@ func BenchmarkFourHopHotRouteArb(b *testing.B) { func (s *KeeperTestSuite) TestAnteHandle() { type param struct { - msgs []sdk.Msg - txFee sdk.Coins - minGasPrices sdk.DecCoins - gasLimit uint64 - isCheckTx bool - baseDenomGas bool + trades []types.Trade expectedNumOfTrades sdk.Int expectedProfits []sdk.Coin expectedPoolPoints uint64 @@ -106,12 +100,7 @@ func (s *KeeperTestSuite) TestAnteHandle() { { name: "Random Msg - Expect Nothing to Happen", params: param{ - msgs: []sdk.Msg{testdata.NewTestMsg(addr0)}, - txFee: sdk.NewCoins(sdk.NewCoin("uosmo", sdk.NewInt(10000))), - minGasPrices: sdk.NewDecCoins(), - gasLimit: 500000, - isCheckTx: false, - baseDenomGas: true, + trades: []types.Trade{}, expectedNumOfTrades: sdk.ZeroInt(), expectedProfits: []sdk.Coin{}, expectedPoolPoints: 0, @@ -121,24 +110,13 @@ func (s *KeeperTestSuite) TestAnteHandle() { { name: "No Arb", params: param{ - msgs: []sdk.Msg{ - &poolmanagertypes.MsgSwapExactAmountIn{ - Sender: addr0.String(), - Routes: []poolmanagertypes.SwapAmountInRoute{ - { - PoolId: 12, - TokenOutDenom: "akash", - }, - }, - TokenIn: sdk.NewCoin("juno", sdk.NewInt(10)), - TokenOutMinAmount: sdk.NewInt(1), + trades: []types.Trade{ + { + Pool: 12, + TokenOut: "akash", + TokenIn: "juno", }, }, - txFee: sdk.NewCoins(sdk.NewCoin(types.OsmosisDenomination, sdk.NewInt(10000))), - minGasPrices: sdk.NewDecCoins(), - gasLimit: 500000, - isCheckTx: false, - baseDenomGas: true, expectedNumOfTrades: sdk.ZeroInt(), expectedProfits: []sdk.Coin{}, expectedPoolPoints: 0, @@ -148,24 +126,13 @@ func (s *KeeperTestSuite) TestAnteHandle() { { name: "Mainnet Arb (Block: 5905150) - Highest Liquidity Pool Build", params: param{ - msgs: []sdk.Msg{ - &poolmanagertypes.MsgSwapExactAmountIn{ - Sender: addr0.String(), - Routes: []poolmanagertypes.SwapAmountInRoute{ - { - PoolId: 23, - TokenOutDenom: "ibc/BE1BB42D4BE3C30D50B68D7C41DB4DFCE9678E8EF8C539F6E6A9345048894FCC", - }, - }, - TokenIn: sdk.NewCoin("ibc/0EF15DF2F02480ADE0BB6E85D9EBB5DAEA2836D3860E9F97F9AADE4F57A31AA0", sdk.NewInt(10000)), - TokenOutMinAmount: sdk.NewInt(10000), + trades: []types.Trade{ + { + Pool: 23, + TokenOut: "ibc/BE1BB42D4BE3C30D50B68D7C41DB4DFCE9678E8EF8C539F6E6A9345048894FCC", + TokenIn: "ibc/0EF15DF2F02480ADE0BB6E85D9EBB5DAEA2836D3860E9F97F9AADE4F57A31AA0", }, }, - txFee: sdk.NewCoins(sdk.NewCoin(types.OsmosisDenomination, sdk.NewInt(10000))), - minGasPrices: sdk.NewDecCoins(), - gasLimit: 500000, - isCheckTx: false, - baseDenomGas: true, expectedNumOfTrades: sdk.OneInt(), expectedProfits: []sdk.Coin{ { @@ -180,24 +147,13 @@ func (s *KeeperTestSuite) TestAnteHandle() { { name: "Mainnet Arb Route - Multi Asset, Same Weights (Block: 6906570) - Hot Route Build - Atom Arb", params: param{ - msgs: []sdk.Msg{ - &poolmanagertypes.MsgSwapExactAmountIn{ - Sender: addr0.String(), - Routes: []poolmanagertypes.SwapAmountInRoute{ - { - PoolId: 33, - TokenOutDenom: "ibc/A0CC0CF735BFB30E730C70019D4218A1244FF383503FF7579C9201AB93CA9293", - }, - }, - TokenIn: sdk.NewCoin("Atom", sdk.NewInt(10000)), - TokenOutMinAmount: sdk.NewInt(10000), + trades: []types.Trade{ + { + Pool: 33, + TokenOut: "ibc/A0CC0CF735BFB30E730C70019D4218A1244FF383503FF7579C9201AB93CA9293", + TokenIn: "Atom", }, }, - txFee: sdk.NewCoins(sdk.NewCoin(types.OsmosisDenomination, sdk.NewInt(10000))), - minGasPrices: sdk.NewDecCoins(), - gasLimit: 500000, - isCheckTx: false, - baseDenomGas: true, expectedNumOfTrades: sdk.NewInt(2), expectedProfits: []sdk.Coin{ { @@ -216,24 +172,13 @@ func (s *KeeperTestSuite) TestAnteHandle() { { name: "Stableswap Test Arb Route - Hot Route Build", params: param{ - msgs: []sdk.Msg{ - &poolmanagertypes.MsgSwapExactAmountIn{ - Sender: addr0.String(), - Routes: []poolmanagertypes.SwapAmountInRoute{ - { - PoolId: 29, - TokenOutDenom: types.OsmosisDenomination, - }, - }, - TokenIn: sdk.NewCoin("usdc", sdk.NewInt(10000)), - TokenOutMinAmount: sdk.NewInt(100), + trades: []types.Trade{ + { + Pool: 29, + TokenOut: types.OsmosisDenomination, + TokenIn: "usdc", }, }, - txFee: sdk.NewCoins(sdk.NewCoin(types.OsmosisDenomination, sdk.NewInt(10000))), - minGasPrices: sdk.NewDecCoins(), - gasLimit: 500000, - isCheckTx: false, - baseDenomGas: true, expectedNumOfTrades: sdk.NewInt(3), expectedProfits: []sdk.Coin{ { @@ -252,24 +197,13 @@ func (s *KeeperTestSuite) TestAnteHandle() { { name: "Four Pool Arb Route - Hot Route Build", params: param{ - msgs: []sdk.Msg{ - &poolmanagertypes.MsgSwapExactAmountIn{ - Sender: addr0.String(), - Routes: []poolmanagertypes.SwapAmountInRoute{ - { - PoolId: 37, - TokenOutDenom: "test/2", - }, - }, - TokenIn: sdk.NewCoin("Atom", sdk.NewInt(10000)), - TokenOutMinAmount: sdk.NewInt(100), + trades: []types.Trade{ + { + Pool: 37, + TokenOut: "test/2", + TokenIn: "Atom", }, }, - txFee: sdk.NewCoins(sdk.NewCoin(types.OsmosisDenomination, sdk.NewInt(10000))), - minGasPrices: sdk.NewDecCoins(), - gasLimit: 500000, - isCheckTx: false, - baseDenomGas: true, expectedNumOfTrades: sdk.NewInt(4), expectedProfits: []sdk.Coin{ { @@ -288,24 +222,13 @@ func (s *KeeperTestSuite) TestAnteHandle() { { name: "Two Pool Arb Route - Hot Route Build", params: param{ - msgs: []sdk.Msg{ - &poolmanagertypes.MsgSwapExactAmountIn{ - Sender: addr0.String(), - Routes: []poolmanagertypes.SwapAmountInRoute{ - { - PoolId: 38, - TokenOutDenom: "test/3", - }, - }, - TokenIn: sdk.NewCoin(types.OsmosisDenomination, sdk.NewInt(10000)), - TokenOutMinAmount: sdk.NewInt(100), + trades: []types.Trade{ + { + Pool: 38, + TokenOut: "test/3", + TokenIn: types.OsmosisDenomination, }, }, - txFee: sdk.NewCoins(sdk.NewCoin(types.OsmosisDenomination, sdk.NewInt(10000))), - minGasPrices: sdk.NewDecCoins(), - gasLimit: 500000, - isCheckTx: false, - baseDenomGas: true, expectedNumOfTrades: sdk.NewInt(5), expectedProfits: []sdk.Coin{ { @@ -328,24 +251,13 @@ func (s *KeeperTestSuite) TestAnteHandle() { { // This test the tx pool points limit caps the number of iterations name: "Doomsday Test - Stableswap - Tx Pool Points Limit", params: param{ - msgs: []sdk.Msg{ - &poolmanagertypes.MsgSwapExactAmountIn{ - Sender: addr0.String(), - Routes: []poolmanagertypes.SwapAmountInRoute{ - { - PoolId: 41, - TokenOutDenom: "usdc", - }, - }, - TokenIn: sdk.NewCoin("busd", sdk.NewInt(10000)), - TokenOutMinAmount: sdk.NewInt(100), + trades: []types.Trade{ + { + Pool: 41, + TokenOut: "usdc", + TokenIn: "busd", }, }, - txFee: sdk.NewCoins(sdk.NewCoin(types.OsmosisDenomination, sdk.NewInt(10000))), - minGasPrices: sdk.NewDecCoins(), - gasLimit: 500000, - isCheckTx: false, - baseDenomGas: true, expectedNumOfTrades: sdk.NewInt(5), expectedProfits: []sdk.Coin{ { @@ -368,24 +280,13 @@ func (s *KeeperTestSuite) TestAnteHandle() { { // This test the block pool points limit caps the number of iterations within a tx name: "Doomsday Test - Stableswap - Block Pool Points Limit - Within a tx", params: param{ - msgs: []sdk.Msg{ - &poolmanagertypes.MsgSwapExactAmountIn{ - Sender: addr0.String(), - Routes: []poolmanagertypes.SwapAmountInRoute{ - { - PoolId: 41, - TokenOutDenom: "usdc", - }, - }, - TokenIn: sdk.NewCoin("busd", sdk.NewInt(10000)), - TokenOutMinAmount: sdk.NewInt(100), + trades: []types.Trade{ + { + Pool: 41, + TokenOut: "usdc", + TokenIn: "busd", }, }, - txFee: sdk.NewCoins(sdk.NewCoin(types.OsmosisDenomination, sdk.NewInt(10000))), - minGasPrices: sdk.NewDecCoins(), - gasLimit: 500000, - isCheckTx: false, - baseDenomGas: true, expectedNumOfTrades: sdk.NewInt(5), expectedProfits: []sdk.Coin{ { @@ -408,24 +309,13 @@ func (s *KeeperTestSuite) TestAnteHandle() { { // This test the block pool points limit caps the number of txs processed if already reached the limit name: "Doomsday Test - Stableswap - Block Pool Points Limit Already Reached - New tx", params: param{ - msgs: []sdk.Msg{ - &poolmanagertypes.MsgSwapExactAmountIn{ - Sender: addr0.String(), - Routes: []poolmanagertypes.SwapAmountInRoute{ - { - PoolId: 41, - TokenOutDenom: "usdc", - }, - }, - TokenIn: sdk.NewCoin("busd", sdk.NewInt(10000)), - TokenOutMinAmount: sdk.NewInt(100), + trades: []types.Trade{ + { + Pool: 41, + TokenOut: "usdc", + TokenIn: "busd", }, }, - txFee: sdk.NewCoins(sdk.NewCoin(types.OsmosisDenomination, sdk.NewInt(10000))), - minGasPrices: sdk.NewDecCoins(), - gasLimit: 500000, - isCheckTx: false, - baseDenomGas: true, expectedNumOfTrades: sdk.NewInt(5), expectedProfits: []sdk.Coin{ { @@ -456,9 +346,12 @@ func (s *KeeperTestSuite) TestAnteHandle() { for _, tc := range tests { s.Run(tc.name, func() { - s.Ctx = s.Ctx.WithIsCheckTx(tc.params.isCheckTx) + s.Ctx = s.Ctx.WithIsCheckTx(false) s.Ctx = s.Ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - s.Ctx = s.Ctx.WithMinGasPrices(tc.params.minGasPrices) + s.Ctx = s.Ctx.WithMinGasPrices(sdk.NewDecCoins()) + + gasLimit := uint64(500000) + txFee := sdk.NewCoins(sdk.NewCoin("uosmo", sdk.NewInt(10000))) privs, accNums, accSeqs := []cryptotypes.PrivKey{priv0}, []uint64{0}, []uint64{0} signerData := authsigning.SignerData{ @@ -466,7 +359,7 @@ func (s *KeeperTestSuite) TestAnteHandle() { AccountNumber: accNums[0], Sequence: accSeqs[0], } - gasLimit := tc.params.gasLimit + sigV2, _ := clienttx.SignWithPrivKey( 1, signerData, @@ -475,11 +368,12 @@ func (s *KeeperTestSuite) TestAnteHandle() { s.clientCtx.TxConfig, accSeqs[0], ) - err := simapp.FundAccount(s.App.BankKeeper, s.Ctx, addr0, tc.params.txFee) + + err := simapp.FundAccount(s.App.BankKeeper, s.Ctx, addr0, txFee) s.Require().NoError(err) var tx authsigning.Tx - var msgs []sdk.Msg + msgs := []sdk.Msg{testdata.NewTestMsg(addr0)} // Lower the max points per tx and block if the test cases are doomsday testing if strings.Contains(tc.name, "Tx Pool Points Limit") { @@ -494,8 +388,9 @@ func (s *KeeperTestSuite) TestAnteHandle() { } if strings.Contains(tc.name, "Doomsday") { + singleTrade := tc.params.trades[0] for i := 0; i < 100; i++ { - msgs = append(msgs, tc.params.msgs...) + tc.params.trades = append(tc.params.trades, singleTrade) } err := txBuilder.SetMsgs(msgs...) @@ -503,21 +398,24 @@ func (s *KeeperTestSuite) TestAnteHandle() { err = txBuilder.SetSignatures(sigV2) s.Require().NoError(err) txBuilder.SetMemo("") - txBuilder.SetFeeAmount(tc.params.txFee) + txBuilder.SetFeeAmount(txFee) txBuilder.SetGasLimit(gasLimit) tx = txBuilder.GetTx() } else { - msgs = tc.params.msgs - tx = s.BuildTx(txBuilder, msgs, sigV2, "", tc.params.txFee, gasLimit) + tx = s.BuildTx(txBuilder, msgs, sigV2, "", txFee, gasLimit) } protoRevDecorator := keeper.NewProtoRevDecorator(*s.App.ProtoRevKeeper) posthandlerProtoRev := sdk.ChainAnteDecorators(protoRevDecorator) // Added so we can check the gas consumed during the posthandler - s.Ctx = s.Ctx.WithGasMeter(sdk.NewGasMeter(tc.params.gasLimit)) - halfGas := tc.params.gasLimit / 2 + s.Ctx = s.Ctx.WithGasMeter(sdk.NewGasMeter(gasLimit)) + halfGas := gasLimit / 2 s.Ctx.GasMeter().ConsumeGas(halfGas, "consume half gas") + + // Set pools to backrun + s.App.AppKeepers.ProtoRevKeeper.AddSwapsToSwapsToBackrun(s.Ctx, tc.params.trades) + gasBefore := s.Ctx.GasMeter().GasConsumed() gasLimitBefore := s.Ctx.GasMeter().Limit() @@ -561,6 +459,8 @@ func (s *KeeperTestSuite) TestAnteHandle() { s.Require().Error(err) } + s.App.AppKeepers.ProtoRevKeeper.DeleteSwapsToBackrun(s.Ctx) + // Reset the max points per tx and block if strings.Contains(tc.name, "Tx Pool Points Limit") { err = s.App.ProtoRevKeeper.SetMaxPointsPerTx(s.Ctx, 18) @@ -575,21 +475,10 @@ func (s *KeeperTestSuite) TestAnteHandle() { func (s *KeeperTestSuite) TestExtractSwappedPools() { type param struct { - msgs []sdk.Msg - txFee sdk.Coins - minGasPrices sdk.DecCoins - gasLimit uint64 - isCheckTx bool - baseDenomGas bool - expectedNumOfPools int expectedSwappedPools []keeper.SwapToBackrun + expectedNumOfPools int } - txBuilder := s.clientCtx.TxConfig.NewTxBuilder() - priv0, _, addr0 := testdata.KeyTestPubAddr() - acc1 := s.App.AccountKeeper.NewAccountWithAddress(s.Ctx, addr0) - s.App.AccountKeeper.SetAccount(s.Ctx, acc1) - tests := []struct { name string params param @@ -598,24 +487,6 @@ func (s *KeeperTestSuite) TestExtractSwappedPools() { { name: "Single Swap", params: param{ - msgs: []sdk.Msg{ - &poolmanagertypes.MsgSwapExactAmountIn{ - Sender: addr0.String(), - Routes: []poolmanagertypes.SwapAmountInRoute{ - { - PoolId: 28, - TokenOutDenom: "ibc/BE1BB42D4BE3C30D50B68D7C41DB4DFCE9678E8EF8C539F6E6A9345048894FCC", - }, - }, - TokenIn: sdk.NewCoin("ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858", sdk.NewInt(10000)), - TokenOutMinAmount: sdk.NewInt(10000), - }, - }, - txFee: sdk.NewCoins(sdk.NewCoin("uosmo", sdk.NewInt(10000))), - minGasPrices: sdk.NewDecCoins(), - gasLimit: 500000, - isCheckTx: false, - baseDenomGas: true, expectedNumOfPools: 1, expectedSwappedPools: []keeper.SwapToBackrun{ { @@ -630,35 +501,6 @@ func (s *KeeperTestSuite) TestExtractSwappedPools() { { name: "Two Swaps", params: param{ - msgs: []sdk.Msg{ - &poolmanagertypes.MsgSwapExactAmountIn{ - Sender: addr0.String(), - Routes: []poolmanagertypes.SwapAmountInRoute{ - { - PoolId: 28, - TokenOutDenom: "ibc/BE1BB42D4BE3C30D50B68D7C41DB4DFCE9678E8EF8C539F6E6A9345048894FCC", - }, - }, - TokenIn: sdk.NewCoin("ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858", sdk.NewInt(10000)), - TokenOutMinAmount: sdk.NewInt(10000), - }, - &poolmanagertypes.MsgSwapExactAmountIn{ - Sender: addr0.String(), - Routes: []poolmanagertypes.SwapAmountInRoute{ - { - PoolId: 22, - TokenOutDenom: "ibc/BE1BB42D4BE3C30D50B68D7C41DB4DFCE9678E8EF8C539F6E6A9345048894FCC", - }, - }, - TokenIn: sdk.NewCoin("uosmo", sdk.NewInt(10000)), - TokenOutMinAmount: sdk.NewInt(10000), - }, - }, - txFee: sdk.NewCoins(sdk.NewCoin("uosmo", sdk.NewInt(10000))), - minGasPrices: sdk.NewDecCoins(), - gasLimit: 500000, - isCheckTx: false, - baseDenomGas: true, expectedNumOfPools: 2, expectedSwappedPools: []keeper.SwapToBackrun{ { @@ -678,24 +520,6 @@ func (s *KeeperTestSuite) TestExtractSwappedPools() { { name: "Single Swap Amount Out Test", params: param{ - msgs: []sdk.Msg{ - &poolmanagertypes.MsgSwapExactAmountOut{ - Sender: addr0.String(), - Routes: []poolmanagertypes.SwapAmountOutRoute{ - { - PoolId: 28, - TokenInDenom: "ibc/BE1BB42D4BE3C30D50B68D7C41DB4DFCE9678E8EF8C539F6E6A9345048894FCC", - }, - }, - TokenOut: sdk.NewCoin("ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858", sdk.NewInt(10000)), - TokenInMaxAmount: sdk.NewInt(10000), - }, - }, - txFee: sdk.NewCoins(sdk.NewCoin("uosmo", sdk.NewInt(10000))), - minGasPrices: sdk.NewDecCoins(), - gasLimit: 500000, - isCheckTx: false, - baseDenomGas: true, expectedNumOfPools: 1, expectedSwappedPools: []keeper.SwapToBackrun{ { @@ -710,32 +534,6 @@ func (s *KeeperTestSuite) TestExtractSwappedPools() { { name: "Single Swap with multiple hops (swapOut)", params: param{ - msgs: []sdk.Msg{ - &poolmanagertypes.MsgSwapExactAmountOut{ - Sender: addr0.String(), - Routes: []poolmanagertypes.SwapAmountOutRoute{ - { - PoolId: 28, - TokenInDenom: "atom", - }, - { - PoolId: 30, - TokenInDenom: "weth", - }, - { - PoolId: 35, - TokenInDenom: "bitcoin", - }, - }, - TokenOut: sdk.NewCoin("akash", sdk.NewInt(10000)), - TokenInMaxAmount: sdk.NewInt(10000), - }, - }, - txFee: sdk.NewCoins(sdk.NewCoin("uosmo", sdk.NewInt(10000))), - minGasPrices: sdk.NewDecCoins(), - gasLimit: 500000, - isCheckTx: false, - baseDenomGas: true, expectedNumOfPools: 3, expectedSwappedPools: []keeper.SwapToBackrun{ { @@ -760,36 +558,6 @@ func (s *KeeperTestSuite) TestExtractSwappedPools() { { name: "Single Swap with multiple hops (swapIn)", params: param{ - msgs: []sdk.Msg{ - &poolmanagertypes.MsgSwapExactAmountIn{ - Sender: addr0.String(), - Routes: []poolmanagertypes.SwapAmountInRoute{ - { - PoolId: 28, - TokenOutDenom: "atom", - }, - { - PoolId: 30, - TokenOutDenom: "weth", - }, - { - PoolId: 35, - TokenOutDenom: "bitcoin", - }, - { - PoolId: 36, - TokenOutDenom: "juno", - }, - }, - TokenIn: sdk.NewCoin("akash", sdk.NewInt(10000)), - TokenOutMinAmount: sdk.NewInt(1), - }, - }, - txFee: sdk.NewCoins(sdk.NewCoin("uosmo", sdk.NewInt(10000))), - minGasPrices: sdk.NewDecCoins(), - gasLimit: 500000, - isCheckTx: false, - baseDenomGas: true, expectedNumOfPools: 4, expectedSwappedPools: []keeper.SwapToBackrun{ { @@ -819,32 +587,6 @@ func (s *KeeperTestSuite) TestExtractSwappedPools() { { name: "Single Swap with multiple hops (gamm msg swapOut)", params: param{ - msgs: []sdk.Msg{ - &gammtypes.MsgSwapExactAmountOut{ - Sender: addr0.String(), - Routes: []poolmanagertypes.SwapAmountOutRoute{ - { - PoolId: 28, - TokenInDenom: "atom", - }, - { - PoolId: 30, - TokenInDenom: "weth", - }, - { - PoolId: 35, - TokenInDenom: "bitcoin", - }, - }, - TokenOut: sdk.NewCoin("akash", sdk.NewInt(10000)), - TokenInMaxAmount: sdk.NewInt(10000), - }, - }, - txFee: sdk.NewCoins(sdk.NewCoin("uosmo", sdk.NewInt(10000))), - minGasPrices: sdk.NewDecCoins(), - gasLimit: 500000, - isCheckTx: false, - baseDenomGas: true, expectedNumOfPools: 3, expectedSwappedPools: []keeper.SwapToBackrun{ { @@ -869,36 +611,6 @@ func (s *KeeperTestSuite) TestExtractSwappedPools() { { name: "Single Swap with multiple hops (gamm swapIn)", params: param{ - msgs: []sdk.Msg{ - &gammtypes.MsgSwapExactAmountIn{ - Sender: addr0.String(), - Routes: []poolmanagertypes.SwapAmountInRoute{ - { - PoolId: 28, - TokenOutDenom: "atom", - }, - { - PoolId: 30, - TokenOutDenom: "weth", - }, - { - PoolId: 35, - TokenOutDenom: "bitcoin", - }, - { - PoolId: 36, - TokenOutDenom: "juno", - }, - }, - TokenIn: sdk.NewCoin("akash", sdk.NewInt(10000)), - TokenOutMinAmount: sdk.NewInt(1), - }, - }, - txFee: sdk.NewCoins(sdk.NewCoin("uosmo", sdk.NewInt(10000))), - minGasPrices: sdk.NewDecCoins(), - gasLimit: 500000, - isCheckTx: false, - baseDenomGas: true, expectedNumOfPools: 4, expectedSwappedPools: []keeper.SwapToBackrun{ { @@ -929,45 +641,18 @@ func (s *KeeperTestSuite) TestExtractSwappedPools() { for _, tc := range tests { s.Run(tc.name, func() { - s.Ctx = s.Ctx.WithIsCheckTx(tc.params.isCheckTx) - s.Ctx = s.Ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - s.Ctx = s.Ctx.WithMinGasPrices(tc.params.minGasPrices) - msgs := tc.params.msgs - privs, accNums, accSeqs := []cryptotypes.PrivKey{priv0}, []uint64{0}, []uint64{0} - signerData := authsigning.SignerData{ - ChainID: s.Ctx.ChainID(), - AccountNumber: accNums[0], - Sequence: accSeqs[0], + for _, swap := range tc.params.expectedSwappedPools { + s.App.ProtoRevKeeper.AddSwapsToSwapsToBackrun(s.Ctx, []types.Trade{{Pool: swap.PoolId, TokenIn: swap.TokenInDenom, TokenOut: swap.TokenOutDenom}}) } - gasLimit := tc.params.gasLimit - sigV2, _ := clienttx.SignWithPrivKey( - 1, - signerData, - txBuilder, - privs[0], - s.clientCtx.TxConfig, - accSeqs[0], - ) - err := simapp.FundAccount(s.App.BankKeeper, s.Ctx, addr0, tc.params.txFee) - s.Require().NoError(err) - // Can't use test suite BuildTx because it doesn't allow for multiple msgs - err = txBuilder.SetMsgs(msgs...) - s.Require().NoError(err) - err = txBuilder.SetSignatures(sigV2) - s.Require().NoError(err) - txBuilder.SetMemo("") - txBuilder.SetFeeAmount(tc.params.txFee) - txBuilder.SetGasLimit(gasLimit) - - tx := txBuilder.GetTx() - - swappedPools := keeper.ExtractSwappedPools(tx) + swappedPools := s.App.ProtoRevKeeper.ExtractSwappedPools(s.Ctx) if tc.expectPass { s.Require().Equal(tc.params.expectedNumOfPools, len(swappedPools)) s.Require().Equal(tc.params.expectedSwappedPools, swappedPools) } + + s.App.ProtoRevKeeper.DeleteSwapsToBackrun(s.Ctx) }) } } diff --git a/x/protorev/keeper/protorev.go b/x/protorev/keeper/protorev.go index 3e0bbfe06e5..c2a18718432 100644 --- a/x/protorev/keeper/protorev.go +++ b/x/protorev/keeper/protorev.go @@ -146,6 +146,57 @@ func (k Keeper) DeleteAllPoolsForBaseDenom(ctx sdk.Context, baseDenom string) { k.DeleteAllEntriesForKeyPrefix(ctx, key) } +// SetSwapsToBackrun sets the swaps to backrun, updated via hooks +func (k Keeper) SetSwapsToBackrun(ctx sdk.Context, swapsToBackrun types.Route) error { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixSwapsToBackrun) + + bz, err := swapsToBackrun.Marshal() + if err != nil { + return err + } + + store.Set(types.KeyPrefixSwapsToBackrun, bz) + + return nil +} + +// GetSwapsToBackrun returns the swaps to backrun, updated via hooks +func (k Keeper) GetSwapsToBackrun(ctx sdk.Context) (types.Route, error) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixSwapsToBackrun) + bz := store.Get(types.KeyPrefixSwapsToBackrun) + + swapsToBackrun := types.Route{} + err := swapsToBackrun.Unmarshal(bz) + if err != nil { + return types.Route{}, err + } + + return swapsToBackrun, nil +} + +// DeleteSwapsToBackrun deletes the swaps to backrun +func (k Keeper) DeleteSwapsToBackrun(ctx sdk.Context) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixSwapsToBackrun) + store.Delete(types.KeyPrefixSwapsToBackrun) +} + +// AddSwapToSwapsToBackrun appends a swap to the swaps to backrun +func (k Keeper) AddSwapsToSwapsToBackrun(ctx sdk.Context, swaps []types.Trade) error { + swapsToBackrun, err := k.GetSwapsToBackrun(ctx) + if err != nil { + return err + } + + swapsToBackrun.Trades = append(swapsToBackrun.Trades, swaps...) + + err = k.SetSwapsToBackrun(ctx, swapsToBackrun) + if err != nil { + return err + } + + return nil +} + // DeleteAllEntriesForKeyPrefix deletes all the entries from the store for the given key prefix func (k Keeper) DeleteAllEntriesForKeyPrefix(ctx sdk.Context, keyPrefix []byte) { store := ctx.KVStore(k.storeKey) diff --git a/x/protorev/types/expected_keepers.go b/x/protorev/types/expected_keepers.go index d8e6b0cca96..838b8c1c5b7 100644 --- a/x/protorev/types/expected_keepers.go +++ b/x/protorev/types/expected_keepers.go @@ -53,6 +53,7 @@ type PoolManagerKeeper interface { ) (poolmanagertypes.PoolI, error) GetPoolModule(ctx sdk.Context, poolId uint64) (poolmanagertypes.PoolModuleI, error) GetTotalPoolLiquidity(ctx sdk.Context, poolId uint64) (sdk.Coins, error) + RouteGetPoolDenoms(ctx sdk.Context, poolId uint64) ([]string, error) } // EpochKeeper defines the Epoch contract that must be fulfilled when diff --git a/x/protorev/types/keys.go b/x/protorev/types/keys.go index cb4566ad141..86aeb24733a 100644 --- a/x/protorev/types/keys.go +++ b/x/protorev/types/keys.go @@ -35,6 +35,7 @@ const ( prefixPoolPointCountForBlock prefixLatestBlockHeight prefixPoolWeights + prefixSwapsToBackrun ) var ( @@ -85,6 +86,9 @@ var ( // KeyPrefixPoolWeights is the prefix for store that keeps track of the weights for different pool types KeyPrefixPoolWeights = []byte{prefixPoolWeights} + + // KeyPrefixSwapsToBackrun is the prefix for store that keeps track of the swaps that need to be backrun for a given tx + KeyPrefixSwapsToBackrun = []byte{prefixSwapsToBackrun} ) // Returns the key needed to fetch the pool id for a given denom