Skip to content

Commit

Permalink
point system for route building
Browse files Browse the repository at this point in the history
  • Loading branch information
davidterpay committed Jan 4, 2023
1 parent dab13ed commit 47c767b
Show file tree
Hide file tree
Showing 6 changed files with 325 additions and 133 deletions.
7 changes: 7 additions & 0 deletions x/protorev/keeper/epoch_hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

epochstypes "github.com/osmosis-labs/osmosis/v13/x/epochs/types"
"github.com/osmosis-labs/osmosis/v13/x/protorev/types"
swaproutertypes "github.com/osmosis-labs/osmosis/v13/x/swaprouter/types"
)

type EpochHooks struct {
Expand Down Expand Up @@ -93,6 +94,12 @@ func (k Keeper) GetHighestLiquidityPools(ctx sdk.Context) (map[string]LiquidityP
for _, pool := range pools {
coins := pool.GetTotalPoolLiquidity(ctx)

// Pool must be a non-stableswap pool
pooltype, err := k.gammKeeper.GetPoolType(ctx, pool.GetId())
if err != nil || pooltype == swaproutertypes.Stableswap {
continue
}

// Pool must be active and the number of coins must be 2
if pool.IsActive(ctx) && len(coins) == 2 {
tokenA := coins[0]
Expand Down
59 changes: 9 additions & 50 deletions x/protorev/keeper/posthandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,71 +96,30 @@ func (k Keeper) AnteHandleCheck(ctx sdk.Context) error {
// if they exist. It returns an error if there was an issue executing any single trade.
func (k Keeper) ProtoRevTrade(ctx sdk.Context, swappedPools []SwapToBackrun) error {
// Get the total number of routes that can be iterated
numIterableRoutes, err := k.CalcNumberOfIterableRoutes(ctx)
maxIterableRoutes, err := k.CalcNumberOfIterableRoutes(ctx)
if err != nil {
return err
}

// Iterate and build arbitrage routes for each pool that was swapped on
for index := 0; index < len(swappedPools) && numIterableRoutes > 0; index++ {
// Build the routes for the pool that was swapped on and the number of routes that will be explored
routes := k.BuildRoutes(ctx, swappedPools[index].TokenInDenom, swappedPools[index].TokenOutDenom, swappedPools[index].PoolId)
numExploredRoutes := uint64(len(routes))

if numExploredRoutes != 0 {
// filter out routes that are not iterable
if numIterableRoutes < numExploredRoutes {
routes = routes[:numIterableRoutes]
numExploredRoutes = numIterableRoutes
}
for index := 0; index < len(swappedPools) && *maxIterableRoutes > 0; index++ {
// Build the routes for the pool that was swapped on
routes := k.BuildRoutes(ctx, swappedPools[index].TokenInDenom, swappedPools[index].TokenOutDenom, swappedPools[index].PoolId, maxIterableRoutes)

// Find optimal input amounts for routes
maxProfitInputCoin, maxProfitAmount, optimalRoute := k.IterateRoutes(ctx, routes)
// Find optimal input amounts for routes
maxProfitInputCoin, maxProfitAmount, optimalRoute := k.IterateRoutes(ctx, routes)

// Update route counts
if err := k.IncrementRouteCountForBlock(ctx, numExploredRoutes); err != nil {
// The error that returns here is particularly focused on the minting/burning of coins, and the execution of the MultiHopSwapExactAmountIn.
if maxProfitAmount.GT(sdk.ZeroInt()) {
if err := k.ExecuteTrade(ctx, optimalRoute, maxProfitInputCoin); err != nil {
return err
}
numIterableRoutes -= numExploredRoutes

// The error that returns here is particularly focused on the minting/burning of coins, and the execution of the MultiHopSwapExactAmountIn.
if maxProfitAmount.GT(sdk.ZeroInt()) {
if err := k.ExecuteTrade(ctx, optimalRoute, maxProfitInputCoin); err != nil {
return err
}
}
}
}

return nil
}

// CalcNumberOfIterableRoutes calculates the number of routes that can be iterated over in the current transaction
func (k Keeper) CalcNumberOfIterableRoutes(ctx sdk.Context) (uint64, error) {
maxRoutesPerTx, err := k.GetMaxRoutesPerTx(ctx)
if err != nil {
return 0, err
}

maxRoutesPerBlock, err := k.GetMaxRoutesPerBlock(ctx)
if err != nil {
return 0, err
}

currentRouteCount, err := k.GetRouteCountForBlock(ctx)
if err != nil {
return 0, err
}

// Calculate the number of routes that can be iterated over
numberOfIterableRoutes := maxRoutesPerBlock - currentRouteCount
if numberOfIterableRoutes > maxRoutesPerTx {
numberOfIterableRoutes = maxRoutesPerTx
}

return numberOfIterableRoutes, nil
}

// 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 {
Expand Down
2 changes: 1 addition & 1 deletion x/protorev/keeper/posthandler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func (suite *KeeperTestSuite) TestAnteHandle() {
Amount: sdk.NewInt(56609900),
},
},
expectedRouteCount: 5,
expectedRouteCount: 6,
},
expectPass: true,
},
Expand Down
116 changes: 100 additions & 16 deletions x/protorev/keeper/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,33 @@ import (
)

// BuildRoutes builds all of the possible arbitrage routes given the tokenIn, tokenOut and poolId that were used in the swap
func (k Keeper) BuildRoutes(ctx sdk.Context, tokenIn, tokenOut string, poolId uint64) []swaproutertypes.SwapAmountInRoutes {
func (k Keeper) BuildRoutes(ctx sdk.Context, tokenIn, tokenOut string, poolId uint64, maxIterableRoutes *uint64) []swaproutertypes.SwapAmountInRoutes {
routes := make([]swaproutertypes.SwapAmountInRoutes, 0)

// Append hot routes if they exist
if tokenPairRoutes, err := k.BuildTokenPairRoutes(ctx, tokenIn, tokenOut, poolId); err == nil {
if tokenPairRoutes, err := k.BuildTokenPairRoutes(ctx, tokenIn, tokenOut, poolId, maxIterableRoutes); err == nil {
routes = append(routes, tokenPairRoutes...)
}

// Append an osmo route if one exists
if osmoRoute, err := k.BuildOsmoRoute(ctx, tokenIn, tokenOut, poolId); err == nil {
if osmoRoute, err := k.BuildOsmoRoute(ctx, tokenIn, tokenOut, poolId, maxIterableRoutes); err == nil {
routes = append(routes, osmoRoute)
}

// Append an atom route if one exists
if atomRoute, err := k.BuildAtomRoute(ctx, tokenIn, tokenOut, poolId); err == nil {
if atomRoute, err := k.BuildAtomRoute(ctx, tokenIn, tokenOut, poolId, maxIterableRoutes); err == nil {
routes = append(routes, atomRoute)
}

return routes
}

// BuildTokenPairRoutes builds all of the possible arbitrage routes from the hot routes given the tokenIn, tokenOut and poolId that were used in the swap
func (k Keeper) BuildTokenPairRoutes(ctx sdk.Context, tokenIn, tokenOut string, poolId uint64) ([]swaproutertypes.SwapAmountInRoutes, error) {
func (k Keeper) BuildTokenPairRoutes(ctx sdk.Context, tokenIn, tokenOut string, poolId uint64, maxIterableRoutes *uint64) ([]swaproutertypes.SwapAmountInRoutes, error) {
if *maxIterableRoutes <= 0 {
return []swaproutertypes.SwapAmountInRoutes{}, fmt.Errorf("the number of routes that can be iterated through has been exceeded")
}

// Get all of the routes from the store that match the given tokenIn and tokenOut
tokenPairArbRoutes, err := k.GetTokenPairArbRoutes(ctx, tokenIn, tokenOut)
if err != nil {
Expand All @@ -42,9 +46,8 @@ func (k Keeper) BuildTokenPairRoutes(ctx sdk.Context, tokenIn, tokenOut string,

// Iterate through all of the routes and build hot routes
routes := make([]swaproutertypes.SwapAmountInRoutes, 0)
for _, route := range tokenPairArbRoutes.ArbRoutes {
newRoute, err := k.BuildHotRoute(ctx, route, tokenIn, tokenOut, poolId)
if err == nil {
for index := 0; index < len(tokenPairArbRoutes.ArbRoutes) && *maxIterableRoutes > 0; index++ {
if newRoute, err := k.BuildHotRoute(ctx, tokenPairArbRoutes.ArbRoutes[index], tokenIn, tokenOut, poolId, maxIterableRoutes); err == nil {
routes = append(routes, newRoute)
}
}
Expand All @@ -54,7 +57,7 @@ func (k Keeper) BuildTokenPairRoutes(ctx sdk.Context, tokenIn, tokenOut string,

// BuildHotRoute constructs a cyclic arbitrage route given a hot route from the store and information about the swap that should be placed
// in the hot route.
func (k Keeper) BuildHotRoute(ctx sdk.Context, route *types.Route, tokenIn, tokenOut string, poolId uint64) (swaproutertypes.SwapAmountInRoutes, error) {
func (k Keeper) BuildHotRoute(ctx sdk.Context, route *types.Route, tokenIn, tokenOut string, poolId uint64, maxIterableRoutes *uint64) (swaproutertypes.SwapAmountInRoutes, error) {
newRoute := make(swaproutertypes.SwapAmountInRoutes, 0)

for _, trade := range route.Trades {
Expand All @@ -76,7 +79,19 @@ func (k Keeper) BuildHotRoute(ctx sdk.Context, route *types.Route, tokenIn, toke
if err := k.CheckValidHotRoute(ctx, newRoute); err != nil {
return swaproutertypes.SwapAmountInRoutes{}, err
}
return newRoute, nil

// Check that the route can be iterated
if weight, err := k.GetRouteWeight(ctx, newRoute); err == nil && *maxIterableRoutes >= weight {
err := k.IncrementRouteCountForBlock(ctx, weight)
if err != nil {
return swaproutertypes.SwapAmountInRoutes{}, err
}

*maxIterableRoutes -= weight
return newRoute, nil
}

return swaproutertypes.SwapAmountInRoutes{}, fmt.Errorf("the number of routes that can be iterated through has been exceeded")
}

// CheckValidHotRoute checks if the cyclic arbitrage route that was built using the hot routes method is correct. Much of the stateless
Expand All @@ -99,18 +114,22 @@ func (k Keeper) CheckValidHotRoute(ctx sdk.Context, route swaproutertypes.SwapAm
}

// BuildOsmoRoute builds a cyclic arbitrage route that starts and ends with osmo given the tokenIn, tokenOut and poolId that were used in the swap
func (k Keeper) BuildOsmoRoute(ctx sdk.Context, tokenIn, tokenOut string, poolId uint64) (swaproutertypes.SwapAmountInRoutes, error) {
return k.BuildRoute(ctx, types.OsmosisDenomination, tokenIn, tokenOut, poolId, k.GetOsmoPool)
func (k Keeper) BuildOsmoRoute(ctx sdk.Context, tokenIn, tokenOut string, poolId uint64, maxIterableRoutes *uint64) (swaproutertypes.SwapAmountInRoutes, error) {
return k.BuildRoute(ctx, types.OsmosisDenomination, tokenIn, tokenOut, poolId, maxIterableRoutes, k.GetOsmoPool)
}

// BuildAtomRoute builds a cyclic arbitrage route that starts and ends with atom given the tokenIn, tokenOut and poolId that were used in the swap
func (k Keeper) BuildAtomRoute(ctx sdk.Context, tokenIn, tokenOut string, poolId uint64) (swaproutertypes.SwapAmountInRoutes, error) {
return k.BuildRoute(ctx, types.AtomDenomination, tokenIn, tokenOut, poolId, k.GetAtomPool)
func (k Keeper) BuildAtomRoute(ctx sdk.Context, tokenIn, tokenOut string, poolId uint64, maxIterableRoutes *uint64) (swaproutertypes.SwapAmountInRoutes, error) {
return k.BuildRoute(ctx, types.AtomDenomination, tokenIn, tokenOut, poolId, maxIterableRoutes, k.GetAtomPool)
}

// BuildRoute constructs a cyclic arbitrage route that is starts/ends with swapDenom (atom or osmo) given the swap (tokenIn, tokenOut, poolId), and
// a function that can get the poolId from the store given a (token, swapDenom) pair.
func (k Keeper) BuildRoute(ctx sdk.Context, swapDenom, tokenIn, tokenOut string, poolId uint64, getPoolIDFromStore func(sdk.Context, string) (uint64, error)) (swaproutertypes.SwapAmountInRoutes, error) {
func (k Keeper) BuildRoute(ctx sdk.Context, swapDenom, tokenIn, tokenOut string, poolId uint64, maxIterableRoutes *uint64, getPoolIDFromStore func(sdk.Context, string) (uint64, error)) (swaproutertypes.SwapAmountInRoutes, error) {
if *maxIterableRoutes <= 0 {
return swaproutertypes.SwapAmountInRoutes{}, fmt.Errorf("the number of routes that can be iterated through has been exceeded")
}

// Creating the first trade in the arb
entryPoolId, err := getPoolIDFromStore(ctx, tokenOut)
if err != nil {
Expand Down Expand Up @@ -152,7 +171,20 @@ func (k Keeper) BuildRoute(ctx sdk.Context, swapDenom, tokenIn, tokenOut string,
TokenOutDenom: swapDenom,
}

return swaproutertypes.SwapAmountInRoutes{entryRoute, middleRoute, exitRoute}, nil
newRoute := swaproutertypes.SwapAmountInRoutes{entryRoute, middleRoute, exitRoute}

// Check that the route can be iterated
if weight, err := k.GetRouteWeight(ctx, newRoute); err == nil && *maxIterableRoutes >= weight {
err := k.IncrementRouteCountForBlock(ctx, weight)
if err != nil {
return swaproutertypes.SwapAmountInRoutes{}, err
}

*maxIterableRoutes -= weight
return newRoute, nil
}

return swaproutertypes.SwapAmountInRoutes{}, fmt.Errorf("the number of routes that can be iterated through has been exceeded")
}

// GetAndCheckPool retrieves the pool from the x/gamm module given a poolId and ensures that the pool can be traded on
Expand All @@ -166,3 +198,55 @@ func (k Keeper) GetAndCheckPool(ctx sdk.Context, poolId uint64) (gammtypes.CFMMP
}
return pool, nil
}

// GetRouteWeight retrieves the weight of a route. The weight of a route is determined by the pools that are used in the route.
// If the route includes a stable pool, the weight of the route is 2. Otherwise, the weight of the route is 1.
func (k Keeper) GetRouteWeight(ctx sdk.Context, route swaproutertypes.SwapAmountInRoutes) (uint64, error) {
// Routes must always be of length 3
if route.Length() != 3 {
return 0, fmt.Errorf("invalid route length")
}

// The middle pool is the pool that may be a stable pool (outside pools will always be balancer pools)
middlePool := route.PoolIds()[1]
poolType, err := k.gammKeeper.GetPoolType(ctx, middlePool)
if err != nil {
return 0, err
}

switch poolType {
case swaproutertypes.Balancer:
return 1, nil
case swaproutertypes.Stableswap:
return 2, nil
default:
return 0, fmt.Errorf("invalid pool type")
}
}

// CalcNumberOfIterableRoutes calculates the number of routes that can be iterated over in the current transaction.
// Returns a pointer that will be used throughout the lifetime of a transaction.
func (k Keeper) CalcNumberOfIterableRoutes(ctx sdk.Context) (*uint64, error) {
maxRoutesPerTx, err := k.GetMaxRoutesPerTx(ctx)
if err != nil {
return nil, err
}

maxRoutesPerBlock, err := k.GetMaxRoutesPerBlock(ctx)
if err != nil {
return nil, err
}

currentRouteCount, err := k.GetRouteCountForBlock(ctx)
if err != nil {
return nil, err
}

// Calculate the number of routes that can be iterated over
numberOfIterableRoutes := maxRoutesPerBlock - currentRouteCount
if numberOfIterableRoutes > maxRoutesPerTx {
numberOfIterableRoutes = maxRoutesPerTx
}

return &numberOfIterableRoutes, nil
}
Loading

0 comments on commit 47c767b

Please sign in to comment.