You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The most up to date spec of x/protorev can be found here.
As development of the x/protorev module is approaching a V1, Skip is ready to start breaking up the implementation into PRs that are modular and straightforward to review. The proposed outline for PRs is as follows:
This PR includes all of the proto and basic app configuration code. Much of the boilerplate code for the module is also in this PR. Although this includes all of the proto code that will be used end to end, some of it will be implemented in subsequent PRs.
High Level Overview of Changes
Init proto code for the module
Hook up x/protorev to the app
Genesis code + stateless validation
Keeper interfaces (dependencies with gamm, epoch, etc.)
This PR includes all of the store keys + setters/getters needed to build cyclic arbitrage routes as well as the epoch hook which helps construct the stores.
High Level Overview of Changes
Keys for keeper
Setters and getters for TokenPairArbRoutes
Setters and getters for Osmo Pools
Setters and getters for Atom Pools
Epoch hook runs after every week to update the highest liquidity pool pairings
Keeper Updates
State Object
Description
Key
Values
Store
TokenPairArbRoutes
TokenPairRoutes tracks cyclic arb routes that can be used to create a MultiHopSwap given two denoms
List of highest liquidity pools for every token pair with Osmo
[]byte{2} + []byte{tokenDenom}
[]byte{poolID}
KV
AtomPools
List of highest liquidity pools for every token pair with Atom
[]byte{3} + []byte{tokenDenom}
[]byte{poolID}
KV
TokenPairArbRoutes
Each hot route configured through the admin account must be stored in the keeper. Each Route will be indexed by an input and output denomination. The value will correspond to a TokenPairArbRoutes object which contains a list of lists which correspond to routes that may be taken.
OsmoPools
This tracks the highest liquidity pools for every single asset that Osmo is paired with on-chain. This is updated through the epoch hook and on genesis.
AtomPools
This tracks the highest liquidity pools for every single asset that Atom is paired with on-chain. This is updated through the epoch hook and on genesis.
Epoch Hook
The Epoch hook allows the module to update the highest liquidity pools and distribute developer profits after every week while incrementally tracking profits through the day epoch identifier.
Acceptance Criteria
This PR will also include our testing environment that is configured through keeper_test.go. This test suite initializes user accounts for trading, creates over 30 pools, sets up token pair routes, and allows us to test all of the setters and getters for stateful information.
This change added tests and can be verified as follows:
This PR implements the logic of building routes given a swap. There are two primary methods for route generation: Highest Liquidity Pools and Hot Routes.
High Level Overview of Changes
Build routes that start and end with osmo (Highest Liquidity Pools)
Build routes that start and end with atom (Highest Liquidity Pools)
Build routes that start and end with osmo or atom from the set of hot routes (Hot Routes)
Highest Liquidity Pools: Updated via the daily epoch, the module iterates through all the the pools and stores the highest liquidity pool for every asset that pairs with either osmo or atom in a pool (for example, the osmo/juno key will have a single pool id stored, that pool id having the most liquidity out of all the osmo/juno pools). This store is then used to create a route(s) at runtime after analyzing a swap. Store is updated through the epoch hook.
There are two types of trades that must be considered:
The assets in the liquidity pool are neither Atom nor Osmo
In this case, x/protorev will sandwich the pool with either Osmo or Atom on the other end. For example, say that a swap occurs on the Akash ↔ Juno pool. There are four possible routes that can be taken (in order of trades made starting on the left and ending on the right).
(Osmo → Akash), (Akash → Juno), (Juno → Osmo)
(Osmo → Juno), (Juno → Akash), (Akash → Osmo)
(Atom → Akash), (Akash → Juno), Juno → Atom)
(Atom → Juno), (Juno → Akash), (Akash → Atom)
Capturing cyclic arbitrage opportunities happen in the opposite direction of the trade. Using the example above, we can cut down the routes to only two routes if we know that the user traded Akash → Juno. The routes in that case would be
(Osmo → Juno), (Juno → Akash), (Akash → Osmo)
(Atom → Juno), (Juno → Akash), (Akash → Atom)
The assets in the liquidity pool include either Atom or Osmo or both
If the Osmo ↔ Atom pool is swapped against, then the highest liquidity pool route building method does not produce any routes. Otherwise, if only one of OSMO or ATOM was swapped against in a single pool, then x/protorev will look for the opposite asset pools from what was traded in the pool. For example, say a swap has been executed on the Osmo ↔ TokenXYZ pool, tendering OSMO and receiving TokenXYZ, the route generated would be:
In both cases, the route that is built will always sandwich the swap that was made. However, we allow for more flexibility in route generation as the highest liquidity method may not be optimal via Hot Routes.
Hot Routes: Populated through the admin account (which will be implemented in a later PR), the module’s keeper holds a KV store that associates token pairs (for example, osmo/juno) to the routes that result in a high percentage of arbitrage profit on Osmosis (as determined by external analysis, max 2 routes stored per token pair key).
The purpose of storing Hot Routes is a recognition that the Highest Liquidity Pool method may not present the best arbitrage routes. As such, hot routes can be configured through governance to store additional routes that may be more effective at capturing arbitrage opportunities. Each hot route will store a placeholder for where the current swapped pool will fit into the trade (since the key is token pairs and not pool ids for the hot routes, we can use the same set of routes for multiple pools).
Acceptance Criteria
This change added tests and can be verified as follows:
Added unit tests for atom routes
Added unit tests for osmo routes
Added unit tests for routes built with tokenPairArbRoutes
This PR implements all of the logic surrounding finding the most profitable arbitrage opportunities given a set of routes, executing the most profitable arbitrage route, and storing profits in the module account. Additionally, the module tracks various statistics about how many trades have been executed, what pools had the most amount of arbitrage opportunities and more.
High Level Overview of Changes
Calculating whether a cyclic arbitrage route is profitable
Determining the most profitable cyclic arbitrage route from a set of routes given a swap
Executing the most profitable cyclic arbitrage opportunity (if one exists)
Updating trading statistics in the module
Keeper Updates
State Object
Description
Key
Values
Store
NumberOfTrades
Tracks the number of trades protorev has executed
[]byte{4}
[]byte{numberOfTrades}
KV
ProfitsByDenom
Tracks the profits protorev has made
[]byte{5} + []byte{tokenDenom}
[]byte{sdk.Coin}
KV
TradesByRoute
Tracks the number of trades the module has executed on a given route
[]byte{6} + []byte{route}
[]byte{numberOfTrades}
KV
ProfitsByRoute
Tracks the profits the module has accumulated after trading on a given route
[]byte{7} + []byte{route}
[]byte{sdk.Coin}
KV
NumberOfTrades
This will store the total number of arbitrage trades that x/protorev has executed since genesis. When finding a pool that has a cyclic arbitrage opportunity, the trade will be executed and this incremented.
ProfitsByDenom x/protorev can execute cyclic arbitrage trades with either Osmosis or Atom. As such, this will store the profits that the module has earned from either denomination.
TradesByRoute & ProfitsByRoute
These metrics allows users and researchers to query the number of cyclic arbitrage trades that have been executed by x/protorev on an cyclic arbitrage route as well as all of the profits captured on that same route.
Trade Execution Logic
Now that we have a list of cyclic routes for each pool swapped by the user’s tx, we then determine if any of the routes are profitable. We determine this using a binary search algorithm that finds the amount of the asset to swap in that results in the most of that same asset out. We then calculate profits by taking the difference between the amount of asset out and amount of asset in. By iterating through the routes and storing the route, optimal input amount, and profit of the route with the highest profit > 0, we are left with the route and amount to execute the MultiHopSwap against.
The module mints the optimal input amount of the coin to swap in from the bankkeeper to the x/protorev module account, executes the MultiHopSwap by interacting with the x/gamm module, burns the optimal input amount of the coin minted to execute the MultiHopSwap, and sends subsequent profits to the module account.
Note: x/protorev will exclusively execute MultiHopSwaps that originate and end in either OSMO or ATOM.
Acceptance Criteria
This change added tests and can be verified as follows:
Added unit tests for setters and getters for module statistics
This PR implements the post handler, the proposal handlers that are responsible for setting the admin account, admin functionality, stateful changes depending on proposals, and developer profit sharing.
High Level Overview of Changes
Add and do stateful testing of proposals
Create admin functionality to set hot routes and the developer account
Profit splitting after every successful trade execution for the developer account
Implement the post handler
The postHandler extracts pools that were swapped in a transaction and determines if there is a cyclic arbitrage opportunity. If so, the handler will find an optimal route and execute it - rebalancing the pool and returning arbitrage profits to the module account. Additionally, the gas meter gets reset at the end of the post handler in order to refund users for computation done in the handler.
Extract all pools that were traded on in the transaction (ExtractSwappedPools) as well as the direction of the trade.
Create cyclic arbitrage routes for each of the swaps above (BuildRoutes)
For each feasible route, determine if there is a cyclic arbitrage opportunity (IterateRoutes)
Determine the optimal amount to swap in and its respective profits via binary search over range of potential input amounts (FindMaxProfitForRoute)
Compare profits of each route, keep the best route and input amount with the highest profit
If the best route and input amount has a profit > 0, execute the trade (ExecuteTrade) and rebalance the pools on-behalf of the chain through the gammkeeper (MultiHopSwapExactAmountIn)
Keep the profits in the module’s account for subsequent distribution.
ExtractSwappedPools
Checks if there were any swaps made on pools in a transaction, returning the pool ids and input/output denoms for each pool that was traded on.
BuildRoutes
BuildRoutes takes a token pair (input and output denom) and returns a list of routes for that token pair that potentially contain a cyclic arbitrage opportunity, populated via the Hot Route and Highest Liquidity Pools method as described above.
IterateRoutes
IterateRoutes iterates through a list of routes, determining the route and input amount that results in the highest cyclic arbitrage profits, and if the highest profits > 0, executes the MultiHopSwapExactAmountIn to rebalance the pools and collect the profits.
FindMaxProfitForRoute
This will take in a route and determine the optimal amount to swap in to maximize profits, given the reserves of all of the pools that are swapped against in the route.
ExecuteTrade
Execute trade takes the route and optimal input amount as params, mints the optimal amount of input coin, executes the swaps via gammKeeper’s MultiHopSwapExactAmountIn, and then burns the amount of coins originally minted, storing the profits in it’s own module account.
This will also update various trading statistics in the module’s store. It will update the total number of trades the module has executed, total profits captured, profits made on this specific pool and share of profits the developer account can withdraw.
Keeper Updates
State Object
Description
Key
Values
Store
ProtoRevEnabled
Tracks whether the protorev module is enabled
[]byte{7}
[]byte{bool}
KV
AdminAccount
Tracks the admin account for protorev
[]byte{8}
[]byte{sdk.AccAddress}
KV
DeveloperAccount
Tracks the developer account for protorev
[]byte{9}
[]byte{sdk.AccAddress}
KV
DaysSinceGenesis
Tracks the number of days since the module was initialized. Used to track profits that can be withdrawn by the developer account
[]byte{10}
[]byte{uint}
KV
DeveloperFees
Tracks the profits that the developer account can withdraw
[]byte{11} + []byte{tokenDenom}
[]byte{sdk.Coin}
KV
MaxRoutesPerTx
Tracks the maximum number of routes that can be traversed per tx
[]byte{12}
[]byte{uint64}
KV
MaxRoutesPerBlock
Tracks the maximum number of routes that can be traversed per block
[]byte{13}
[]byte{uint64}
KV
RouteCountForBlock
Tracks the number of routes that have been traversed in the current block
[]byte{14}
[]byte{uint64}
KV
LatestBlockHeight
Tracks the latest recorded block height
[]byte{15}
[]byte{uint64}
KV
RouteWeights
Tracks the weights of the different routes
[]byte{16}
[]byte{uint64}
KV
ProtoRevEnabled x/protorev can be enabled or disabled through governance. As a proposal is a stateful change, we store whether the module is currently enabled or disabled in the module.
AdminAccount
The admin account is set through governance and has permissions to set hot routes and the developer account. On genesis, the admin account is nil.
DeveloperAccount
The developer account is set through a NewMsgSetDeveloperAccount tx. This is the account that will be able to withdraw a portion of the profits from x/protorev as specified by the Osmosis ↔ Skip proposal. Only the admin account has permission to make this message.
DaysSinceGenesis x/protorev will distribute 20% of profits to skip in the year 1, 10% of profits in year 2, and 5% thereafter. To track how much profit can be distributed to the developer account at any given moment, we store the amount of days since genesis.
DeveloperFees
DeveloperFees tracks the total amount of profit that can be withdrawn by the developer account. These fees are sent to the developer account, if set, every week through the epoch hook. If unset, the funds are held in the module account.
MaxRoutesPerTx
MaxRoutesPerTx tracks the maximum number of routes that can be traversed in a given transaction. This is configurable (but bounded) by the admin account. We limit the number of routes per transaction so that all x/protorev execution is not limited to the top of the block.
MaxRoutesPerBlock
MaxRoutesPerBlock tracks the maximum number of routes that can be traversed in a given block. This is configurable (but bounded) by the admin account. We limit the number of routes per block so that the execution time of the x/protorev posthandler is reasonably bounded to ensure that block time remains as is.
RouteCountForBlock
RouteCountForBlock tracks the number of routes that have been traversed in the current block. Used to ensure that the module is not slowing down block speed.
LatestBlockHeight
LatestBlockHeight tracks the latest recorded block height. Used to track the number of routes that have been traversed in the current block.
RouteWeights
RouteWeights contains the weights of all of the different route types. Routes are broken up into different types based on the pool that is sandwiched in between the arbitrage route. This distinction is made and necessary because the execution time ranges fairly between the different route types.
Proposal Handlers
x/protorev implements two different governance proposals.
SetProtoRevAdminAccountProposal
As the landscape of pools on Osmosis evolves, an admin account will be able to add and remove routes for x/protorev to check for cyclic arbitrage opportunities. Largely, the purpose of maintaining hot routes is to reduce the amount of computation that would otherwise be required to determine optimal paths at runtime. In addition, introducing a means of altering hot can allow external researchers to conduct meaningful analysis into the markets on-chain.
SetProtoRevEnabledProposal
This proposal type allows the chain to turn the module on or off. This is meant to be used as a fail safe in the case stakers and the chain decide to turn the module off. This might be used to halt the execution of trades in the case that the x/gamm module has significant upgrades that might produce unexpected behavior from the module.
Profit Sharing
Profits accumulated by the module will be partially distributed to the developers that built the module in accordance with the governance proposal that was passed: year 1 is 20% of profits, year 2 is 10%, and subsequent years is 5%.
In order to track how much profit the developers can withdraw at any given moment, the module tracks the number of days since genesis. This gets incremented in the epoch hook after every day. When a trade gets executed by the module, the module will determine how much of the profit from the trade the developers can receive by using daysSinceGenesis in a simple calculation.
If the developer account is not set (which it is not on genesis), all funds are held in the module account. Once the admin account is set through a successful governance proposal, the developer address can be set and will start to automatically receive a share of profits every week through the epoch hook. The distribution of funds from the module account is done through SendDeveloperFeesToDeveloperAccount. Once the funds are distributed, the amount of profit developers can withdraw gets reset to 0 and profits will start to be accumulated and distributed on a week to week basis.
Acceptance Criteria
This change added tests and can be verified as follows:
Added unit tests for gas refunds
Added unit tests for mock swap tx
Added unit tests for extracting the correct pools from the tx
Added unit tests for proposals
Added unit tests for new setters and getters
Added unit tests for updating developer fees
Added unit tests for enabling or disabling the module (and affects in epoch and post handler)
This PR upgrades x/protorev to support arbitrary sets of base denominations that can be used to build cyclic arbitrage routes, introduces a new pool point system that bounds the execution time of x/protorev more accurately, and enables the execution of routes of arbitrary length.
High Level Overview of Changes
New pool point system where each pool point is essentially 1 ms of simulation & execution time
Configurable number of pool points per tx
Configurable number of pool points per block
Each pool in a route will have an associated weight which directly corresponds to the number of points it costs
Arbitrary sets of base denominations (configurable by the admin account)
Route building logic is abstracted away from osmo or atom
<10 LOC changed to support execution of routes of arbitrary length
Keeper Updates
State Object
Description
Key
Values
Store
DenomPairToPool
Tracks the pool ids of the highest liquidity pools matched with a given denom
[]byte{2}
[]byte{baseDenom} + []byte{denomToMatch}
KV
BaseDenoms
Tracks all of the base denominations that will be used to construct arbitrage routes
[]byte{3}
[]byte{[]string{}}
KV
MaxPoolPointsPerTx
Tracks the maximum number of pool points that can be consumed per tx
[]byte{12}
[]byte{uint64}
KV
MaxPoolPointsPerBlock
Tracks the maximum number of pool points that can be consumed per block
[]byte{13}
[]byte{uint64}
KV
PoolPointCountForBlock
Tracks the number of pool points that have been consumed in this block
[]byte{14}
[]byte{uint64}
KV
PoolWeights
Tracks the weights (pool points) of the different pool types
[]byte{16}
[]byte{{}types.PoolWeights}
KV
Where PoolWeights looks like the following
// PoolWeights contains the weights of all of the different pool types. This// distinction is made and necessary because the execution time ranges// significantly between the different pool types. Each weight roughly// corresponds to the amount of time (in ms) it takes to execute a swap on that// pool type.typePoolWeightsstruct {
// The weight of a stableswap poolStableWeightuint64`protobuf:"varint,1,opt,name=stable_weight,json=stableWeight,proto3" json:"stable_weight,omitempty"`// The weight of a balancer poolBalancerWeightuint64`protobuf:"varint,2,opt,name=balancer_weight,json=balancerWeight,proto3" json:"balancer_weight,omitempty"`// The weight of a concentrated poolConcentratedWeightuint64`protobuf:"varint,3,opt,name=concentrated_weight,json=concentratedWeight,proto3" json:"concentrated_weight,omitempty"`
}
DenomPairToPool
DenomPairToPool takes in a base denomination – denom that is used to build routes (ex. osmo, atom, usdc) – and a denom to match and returns the highest liquidity pool id between the pair of denominations. Each base denomination is going to have its own set of denomination it maps to. Each denomination that each base denom maps to will correspond to the highest liquidity pool for that pair. This will be updated in the same manner it was before.
BaseDenoms
BaseDenoms are the denominations that are used to build the highest liquidity routes. This will be configurable by the admin account, but will always maintain at least osmo as a base denom. These checks will be done in the last PR where the msg server is implemented. By default, osmo will be included.
MaxPoolPointsPerTx
This tracks the maximum number of pool points that can be consumed per transaction. A pool point roughly corresponds to 1 ms of simulation & execution time. i.e. this roughly tracks the maximum execution time of protorev per transaction.
MaxPoolPointsPerBlock
This tracks the maximum number of pool points that can be consumed per transaction. This will roughly track the maximum execution time of protorev per block.
PoolPointCountForBlock
This tracks the current pool point count for the current block. This cannot ever exceed MaxPoolPointsPerBlock.
PoolWeights
This tracks the pool points or weight of each pool type that can be traversed. This distinction is necessary because different pool types have different simulation and execution times.
Acceptance Criteria
This change added tests and can be verified as follows:
Added unit tests that allow us to generate routes using different base denoms
Added unit tests to ensure the pool point system is correctly tracking the cost of different routes
Added unit tests for routes of varying lengths
Added unit tests for configuring the pool point system, base denoms, and hot routes
7. Query server + Message server + CLI
Update: This PR is currently being reviewed –––> #4214
This PR implements the query server, message server, and CLI.
High Level Overview of Changes
Implement the query server
Implement the message server
Implement CLI commands
Keeper Updates
State Object
Description
Key
Values
Store
BaseDenoms
Tracks all of the base denominations that will be used to construct arbitrage routes
[]byte{3}
[]byte{[]BaseDenom{}}
KV
Where BaseDenom looks like the following
// BaseDenom is a struct that contains the base denomination and the// BaseDenom represents a single base denom that the module uses for its// arbitrage trades. It contains the denom name alongside the step size of the// binary search that is used to find the optimal swap amounttypeBaseDenomstruct {
// The denom i.e. name of the base denom (ex. uosmo)Denomstring`protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"`// The step size of the binary search that is used to find the optimal swap// amountStepSize github_com_cosmos_cosmos_sdk_types.Int`protobuf:"bytes,2,opt,name=step_size,json=stepSize,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"step_size"`
}
Queries the maximum pool points that can be consumed per block
gRPC
osmosis.v14.protorev.Query/GetProtoRevBaseDenoms
Queries the base denoms of the module
gRPC
osmosis.v14.protorev.Query/GetProtoRevEnabled
Queries whether the module is enabled or not
GET
/osmosis/v14/protorev/params
Queries the parameters of the module
GET
/osmosis/v14/protorev/number_of_trades
Queries the number of arbitrage trades the module has executed
GET
/osmosis/v14/protorev/profits_by_denom
Queries the profits of the module by denom
GET
/osmosis/v14/protorev/all_profits
Queries all of the profits from the module
GET
/osmosis/v14/protorev/statistics_by_route
Queries the number of arbitrages and profits that have happened for a given route
GET
/osmosis/v14/protorev/all_route_statistics
Queries all of pools that the module has arbitrage against and the number of trades and profits that have happened for each route
GET
/osmosis/v14/protorev/token_pair_arb_routes
Queries all of the hot routes that the module is currently arbitraging
GET
/osmosis/v14/protorev/admin_account
Queries the admin account of the module
GET
/osmosis/v14/protorev/developer_account
Queries the developer account of the module
GET
/osmosis/v14/protorev/pool_weights
Queries the pool weights of the module
GET
/osmosis/v14/protorev/max_pool_points_per_tx
Queries the maximum pool points that can be consumed per transaction
GET
/osmosis/v14/protorev/max_pool_points_per_block
Queries the maximum pool points that can be consumed per block
GET
/osmosis/v14/protorev/base_denoms
Queries the base denoms of the module
GET
/osmosis/v14/protorev/enabled
Queries whether the module is enabled or not
Transactions
Verb
Method
Description
gRPC
osmosis.v14.protorev.Msg/SetHotRoutes
Sets the hot routes that will be explored when creating cyclic arbitrage routes. Can only be called by the admin account.
gRPC
osmosis.v14.protorev.Msg/SetDeveloperAccount
Sets the account that can withdraw a portion of the profit from the protorev module. This will be Skip's address once deployed. Can only be called by the admin account.
gRPC
osmosis.v14.protorev.Msg/SetPoolWeights
Sets the pool weights of the module. Can only be called by the admin account.
gRPC
osmosis.v14.protorev.Msg/SetMaxPoolPointsPerTx
Sets the maximum pool points that can be consumed per transaction. Can only be called by the admin account.
gRPC
osmosis.v14.protorev.Msg/SetMaxPoolPointsPerBlock
Sets the maximum pool points that can be consumed per block. Can only be called by the admin account.
gRPC
osmosis.v14.protorev.Msg/SetBaseDenoms
Sets the base denoms of the module. Can only be called by the admin account.
POST
/osmosis/protorev/v1beta1/set_hot_routes
Sets the hot routes that will be explored when creating cyclic arbitrage routes. Can only be called by the admin account.
POST
/osmosis/protorev/v1beta1/set_developer_account
Sets the account that can withdraw a portion of the profit from the protorev module. This will be Skip's address once deployed
POST
/osmosis/protorev/v1beta1/set_pool_weights
Sets the pool weights of the module. Can only be called by the admin account.
Sets the maximum pool points that can be consumed per block. Can only be called by the admin account.
POST
/osmosis/protorev/v1beta1/set_base_denoms
Sets the base denoms of the module. Can only be called by the admin account.
Messages
NewMsgSetDeveloperAccount
The admin account broadcasts a NewMsgSetDeveloperAccount to set the developer account.
Messsage stateless validation fails if:
The admin is not a valid bech32 address
The signature of the user does not match the admin account’s
The developer account is not a valid bech32 address
Message stateful validation fails if:
The admin is not set in state
The admin entered in the message does not match the admin on chain
The admin’s signatures are not the same
NewMsgSetHotRoutes
The admin account broadcasts a NewMsgSetHotRoutes to set the hot routes.
Message statless validation fails if:
The admin is not a valid bech32 address
The signature of the user does not match the admin account’s
The hot routes are not valid
Message stateful validation fails if:
The admin is not set in state
The admin entered in the message does not match the admin on chain
The admin’s signatures are not the same
NewMsgSetPoolWeights
The admin account broadcasts a NewMsgSetPoolWeights to set the pool weights. The pool weights roughly correspond to the execution time of a swap on that pool type (stable, balancer, concentrated).
Message stateless validation fails if:
The admin is not a valid bech32 address
Any of the pool weights are less than 1
Message stateful validation fails if:
The admin is not set in state
The admin’s signatures are not the same
NewMsgSetMaxPoolPointsPerTx
The admin account broadcasts a NewMsgSetMaxPoolPointsPerTx to set the maximum pool points that can be consumed per transaction.
Message stateless validation fails if:
The admin is not a valid bech32 address
MaxPoolPointsPerTx is outside of the valid range
Message stateful validation fails if:
The admin is not set in state
The admin’s signatures are not the same
NewMsgSetMaxPoolPointsPerBlock
The admin account broadcasts a NewMsgSetMaxPoolPointsPerBlock to set the maximum pool points that can be consumed per block.
Message stateless validation fails if:
The admin is not a valid bech32 address
MaxPoolPointsPerBlock is outside of the valid range
Message stateful validation fails if:
The admin is not set in state
The admin’s signatures are not the same
NewMsgSetBaseDenoms
The admin account broadcasts a NewMsgSetBaseDenoms to set the base denoms of the module. The base denoms are the denominations that the module will use to construct cyclic arbitrage routes. The order of the lists
Message stateless validation fails if:
The admin is not a valid bech32 address
Osmosis is not the first base denom in the list
Acceptance Criteria
This change added tests and can be verified as follows:
This PR refactors some of the route calculation and trade execution logic to be more optimal.
High Level Overview of Changes
Routes and their pool points are only counted if a trade is guaranteed to be profitable
The step size of the binary search method is going to be related to the amount of decimal points of the underlying base denom
Check the smallest arb possible to see if any input amount would lead to a profitable arbitrage trade
Expand the range of the binary search in case there is a big arbitrage trade available
Added testing for routes of length > 3
Given a route, we now calculate if the minAmountIn is profitable as a pre-check. If minAmountIn is not profitable, then the route is not profitable and we don't run the full binary search algo. This will make Protorev take much less compute time in production compared to its previous design (which would run the full binary search even when a route has no-arb).
Given a route, we now calculate if the maxAmountIn and maxAmountIn + 1 profit is increasing as a pre-check and increase the range if so. If the profit from maxInputIn is still increasing, this means that we need to increase the range to capture the max profit.
Adds tests that verifies the limit/point system works properly, >3 pool routes work, and other denom besides osmo/atom work.
Acceptance Criteria
This change added tests and can be verified as follows:
All previous tests pass with new changes
Added a test to ensure range extension works properly
Added a test for swapExactAmountOut
Added benchmark testing
Added doomsday testing to ensure point system works properly and doesn't allow protorev to keep running
Background
The most up to date spec of
x/protorev
can be found here.As development of the
x/protorev
module is approaching a V1, Skip is ready to start breaking up the implementation into PRs that are modular and straightforward to review. The proposed outline for PRs is as follows:Suggested Approach
1. Proto-code + App Configuration + Scaffolding ✅
Update: This PR has been merged–––> #3587
This PR includes all of the proto and basic app configuration code. Much of the boilerplate code for the module is also in this PR. Although this includes all of the proto code that will be used end to end, some of it will be implemented in subsequent PRs.
High Level Overview of Changes
x/protorev
to the appAcceptance Criteria
This change added tests and can be verified as follows:
2. Basic Keeper + Epoch Hook ✅
Update: This PR has been merged –––> #3646
This PR includes all of the store keys + setters/getters needed to build cyclic arbitrage routes as well as the epoch hook which helps construct the stores.
High Level Overview of Changes
Keeper Updates
TokenPairArbRoutes
Each hot route configured through the admin account must be stored in the keeper. Each Route will be indexed by an input and output denomination. The value will correspond to a TokenPairArbRoutes object which contains a list of lists which correspond to routes that may be taken.
OsmoPools
This tracks the highest liquidity pools for every single asset that Osmo is paired with on-chain. This is updated through the epoch hook and on genesis.
AtomPools
This tracks the highest liquidity pools for every single asset that Atom is paired with on-chain. This is updated through the epoch hook and on genesis.
Epoch Hook
The Epoch hook allows the module to update the highest liquidity pools and distribute developer profits after every week while incrementally tracking profits through the day epoch identifier.
Acceptance Criteria
This PR will also include our testing environment that is configured through
keeper_test.go
. This test suite initializes user accounts for trading, creates over 30 pools, sets up token pair routes, and allows us to test all of the setters and getters for stateful information.This change added tests and can be verified as follows:
3. Route Building ✅
Update: This PR has been merged –––> #3683
This PR implements the logic of building routes given a swap. There are two primary methods for route generation: Highest Liquidity Pools and Hot Routes.
High Level Overview of Changes
Highest Liquidity Pools: Updated via the daily epoch, the module iterates through all the the pools and stores the highest liquidity pool for every asset that pairs with either osmo or atom in a pool (for example, the osmo/juno key will have a single pool id stored, that pool id having the most liquidity out of all the osmo/juno pools). This store is then used to create a route(s) at runtime after analyzing a swap. Store is updated through the
epoch
hook.There are two types of trades that must be considered:
x/protorev
will sandwich the pool with either Osmo or Atom on the other end. For example, say that a swap occurs on the Akash ↔ Juno pool. There are four possible routes that can be taken (in order of trades made starting on the left and ending on the right).x/protorev
will look for the opposite asset pools from what was traded in the pool. For example, say a swap has been executed on the Osmo ↔ TokenXYZ pool, tendering OSMO and receiving TokenXYZ, the route generated would be:In both cases, the route that is built will always sandwich the swap that was made. However, we allow for more flexibility in route generation as the highest liquidity method may not be optimal via Hot Routes.
Hot Routes: Populated through the admin account (which will be implemented in a later PR), the module’s keeper holds a KV store that associates token pairs (for example, osmo/juno) to the routes that result in a high percentage of arbitrage profit on Osmosis (as determined by external analysis, max 2 routes stored per token pair key).
Acceptance Criteria
This change added tests and can be verified as follows:
4. Trade Execution + Statistics ✅
Update: This PR has been merged –––> #3709
This PR implements all of the logic surrounding finding the most profitable arbitrage opportunities given a set of routes, executing the most profitable arbitrage route, and storing profits in the module account. Additionally, the module tracks various statistics about how many trades have been executed, what pools had the most amount of arbitrage opportunities and more.
High Level Overview of Changes
Keeper Updates
NumberOfTrades
This will store the total number of arbitrage trades that
x/protorev
has executed since genesis. When finding a pool that has a cyclic arbitrage opportunity, the trade will be executed and this incremented.ProfitsByDenom
x/protorev
can execute cyclic arbitrage trades with either Osmosis or Atom. As such, this will store the profits that the module has earned from either denomination.TradesByRoute & ProfitsByRoute
These metrics allows users and researchers to query the number of cyclic arbitrage trades that have been executed by
x/protorev
on an cyclic arbitrage route as well as all of the profits captured on that same route.Trade Execution Logic
Now that we have a list of cyclic routes for each pool swapped by the user’s tx, we then determine if any of the routes are profitable. We determine this using a binary search algorithm that finds the amount of the asset to swap in that results in the most of that same asset out. We then calculate profits by taking the difference between the amount of asset out and amount of asset in. By iterating through the routes and storing the route, optimal input amount, and profit of the route with the highest profit > 0, we are left with the route and amount to execute the MultiHopSwap against.
The module mints the optimal input amount of the coin to swap in from the
bankkeeper
to thex/protorev
module account, executes the MultiHopSwap by interacting with thex/gamm
module, burns the optimal input amount of the coin minted to execute the MultiHopSwap, and sends subsequent profits to the module account.x/protorev
will exclusively execute MultiHopSwaps that originate and end in either OSMO or ATOM.Acceptance Criteria
This change added tests and can be verified as follows:
5. Post Handler + Governance Proposals + Developer Profit Sharing ✅
Update: This PR has been merged –––> #3806
This PR implements the post handler, the proposal handlers that are responsible for setting the admin account, admin functionality, stateful changes depending on proposals, and developer profit sharing.
High Level Overview of Changes
The
postHandler
extracts pools that were swapped in a transaction and determines if there is a cyclic arbitrage opportunity. If so, the handler will find an optimal route and execute it - rebalancing the pool and returning arbitrage profits to the module account. Additionally, the gas meter gets reset at the end of the post handler in order to refund users for computation done in the handler.ExtractSwappedPools
) as well as the direction of the trade.BuildRoutes
)IterateRoutes
)FindMaxProfitForRoute
)ExecuteTrade
) and rebalance the pools on-behalf of the chain through thegammkeeper
(MultiHopSwapExactAmountIn
)ExtractSwappedPools
Checks if there were any swaps made on pools in a transaction, returning the pool ids and input/output denoms for each pool that was traded on.
BuildRoutes
BuildRoutes takes a token pair (input and output denom) and returns a list of routes for that token pair that potentially contain a cyclic arbitrage opportunity, populated via the Hot Route and Highest Liquidity Pools method as described above.
IterateRoutes
IterateRoutes iterates through a list of routes, determining the route and input amount that results in the highest cyclic arbitrage profits, and if the highest profits > 0, executes the MultiHopSwapExactAmountIn to rebalance the pools and collect the profits.
FindMaxProfitForRoute
This will take in a route and determine the optimal amount to swap in to maximize profits, given the reserves of all of the pools that are swapped against in the route.
ExecuteTrade
Execute trade takes the route and optimal input amount as params, mints the optimal amount of input coin, executes the swaps via
gammKeeper
’sMultiHopSwapExactAmountIn
, and then burns the amount of coins originally minted, storing the profits in it’s own module account.This will also update various trading statistics in the module’s store. It will update the total number of trades the module has executed, total profits captured, profits made on this specific pool and share of profits the developer account can withdraw.
Keeper Updates
ProtoRevEnabled
x/protorev
can be enabled or disabled through governance. As a proposal is a stateful change, we store whether the module is currently enabled or disabled in the module.AdminAccount
The admin account is set through governance and has permissions to set hot routes and the developer account. On genesis, the admin account is nil.
DeveloperAccount
The developer account is set through a
NewMsgSetDeveloperAccount
tx. This is the account that will be able to withdraw a portion of the profits fromx/protorev
as specified by the Osmosis ↔ Skip proposal. Only the admin account has permission to make this message.DaysSinceGenesis
x/protorev
will distribute 20% of profits to skip in the year 1, 10% of profits in year 2, and 5% thereafter. To track how much profit can be distributed to the developer account at any given moment, we store the amount of days since genesis.DeveloperFees
DeveloperFees tracks the total amount of profit that can be withdrawn by the developer account. These fees are sent to the developer account, if set, every week through the
epoch
hook. If unset, the funds are held in the module account.MaxRoutesPerTx
MaxRoutesPerTx tracks the maximum number of routes that can be traversed in a given transaction. This is configurable (but bounded) by the admin account. We limit the number of routes per transaction so that all
x/protorev
execution is not limited to the top of the block.MaxRoutesPerBlock
MaxRoutesPerBlock tracks the maximum number of routes that can be traversed in a given block. This is configurable (but bounded) by the admin account. We limit the number of routes per block so that the execution time of the
x/protorev
posthandler is reasonably bounded to ensure that block time remains as is.RouteCountForBlock
RouteCountForBlock tracks the number of routes that have been traversed in the current block. Used to ensure that the module is not slowing down block speed.
LatestBlockHeight
LatestBlockHeight tracks the latest recorded block height. Used to track the number of routes that have been traversed in the current block.
RouteWeights
RouteWeights contains the weights of all of the different route types. Routes are broken up into different types based on the pool that is sandwiched in between the arbitrage route. This distinction is made and necessary because the execution time ranges fairly between the different route types.
Proposal Handlers
x/protorev
implements two different governance proposals.SetProtoRevAdminAccountProposal
As the landscape of pools on Osmosis evolves, an admin account will be able to add and remove routes for
x/protorev
to check for cyclic arbitrage opportunities. Largely, the purpose of maintaining hot routes is to reduce the amount of computation that would otherwise be required to determine optimal paths at runtime. In addition, introducing a means of altering hot can allow external researchers to conduct meaningful analysis into the markets on-chain.SetProtoRevEnabledProposal
This proposal type allows the chain to turn the module on or off. This is meant to be used as a fail safe in the case stakers and the chain decide to turn the module off. This might be used to halt the execution of trades in the case that the
x/gamm
module has significant upgrades that might produce unexpected behavior from the module.Profit Sharing
Profits accumulated by the module will be partially distributed to the developers that built the module in accordance with the governance proposal that was passed: year 1 is 20% of profits, year 2 is 10%, and subsequent years is 5%.
In order to track how much profit the developers can withdraw at any given moment, the module tracks the number of days since genesis. This gets incremented in the epoch hook after every day. When a trade gets executed by the module, the module will determine how much of the profit from the trade the developers can receive by using
daysSinceGenesis
in a simple calculation.If the developer account is not set (which it is not on genesis), all funds are held in the module account. Once the admin account is set through a successful governance proposal, the developer address can be set and will start to automatically receive a share of profits every week through the epoch hook. The distribution of funds from the module account is done through
SendDeveloperFeesToDeveloperAccount
. Once the funds are distributed, the amount of profit developers can withdraw gets reset to 0 and profits will start to be accumulated and distributed on a week to week basis.Acceptance Criteria
This change added tests and can be verified as follows:
6. Generalizable Route Building ✅
Update: This PR has been merged –––> #4000
This PR upgrades
x/protorev
to support arbitrary sets of base denominations that can be used to build cyclic arbitrage routes, introduces a new pool point system that bounds the execution time ofx/protorev
more accurately, and enables the execution of routes of arbitrary length.High Level Overview of Changes
osmo
oratom
Keeper Updates
Where PoolWeights looks like the following
DenomPairToPool
DenomPairToPool takes in a base denomination – denom that is used to build routes (ex. osmo, atom, usdc) – and a denom to match and returns the highest liquidity pool id between the pair of denominations. Each base denomination is going to have its own set of denomination it maps to. Each denomination that each base denom maps to will correspond to the highest liquidity pool for that pair. This will be updated in the same manner it was before.
BaseDenoms
BaseDenoms are the denominations that are used to build the highest liquidity routes. This will be configurable by the admin account, but will always maintain at least
osmo
as a base denom. These checks will be done in the last PR where the msg server is implemented. By default, osmo will be included.MaxPoolPointsPerTx
This tracks the maximum number of pool points that can be consumed per transaction. A pool point roughly corresponds to 1 ms of simulation & execution time. i.e. this roughly tracks the maximum execution time of protorev per transaction.
MaxPoolPointsPerBlock
This tracks the maximum number of pool points that can be consumed per transaction. This will roughly track the maximum execution time of protorev per block.
PoolPointCountForBlock
This tracks the current pool point count for the current block. This cannot ever exceed MaxPoolPointsPerBlock.
PoolWeights
This tracks the pool points or weight of each pool type that can be traversed. This distinction is necessary because different pool types have different simulation and execution times.
Acceptance Criteria
This change added tests and can be verified as follows:
7. Query server + Message server + CLI
Update: This PR is currently being reviewed –––> #4214
This PR implements the query server, message server, and CLI.
High Level Overview of Changes
Keeper Updates
Where BaseDenom looks like the following
Queries
Transactions
Messages
NewMsgSetDeveloperAccount
The admin account broadcasts a
NewMsgSetDeveloperAccount
to set the developer account.Messsage stateless validation fails if:
Message stateful validation fails if:
NewMsgSetHotRoutes
The admin account broadcasts a
NewMsgSetHotRoutes
to set the hot routes.Message statless validation fails if:
Message stateful validation fails if:
NewMsgSetPoolWeights
The admin account broadcasts a
NewMsgSetPoolWeights
to set the pool weights. The pool weights roughly correspond to the execution time of a swap on that pool type (stable, balancer, concentrated).Message stateless validation fails if:
Message stateful validation fails if:
NewMsgSetMaxPoolPointsPerTx
The admin account broadcasts a
NewMsgSetMaxPoolPointsPerTx
to set the maximum pool points that can be consumed per transaction.Message stateless validation fails if:
Message stateful validation fails if:
NewMsgSetMaxPoolPointsPerBlock
The admin account broadcasts a
NewMsgSetMaxPoolPointsPerBlock
to set the maximum pool points that can be consumed per block.Message stateless validation fails if:
Message stateful validation fails if:
NewMsgSetBaseDenoms
The admin account broadcasts a
NewMsgSetBaseDenoms
to set the base denoms of the module. The base denoms are the denominations that the module will use to construct cyclic arbitrage routes. The order of the listsMessage stateless validation fails if:
Acceptance Criteria
This change added tests and can be verified as follows:
8. Optimizations + E2E testing ✅
Update: This PR has been merged –––> #4181
This PR refactors some of the route calculation and trade execution logic to be more optimal.
High Level Overview of Changes
Given a route, we now calculate if the minAmountIn is profitable as a pre-check. If minAmountIn is not profitable, then the route is not profitable and we don't run the full binary search algo. This will make Protorev take much less compute time in production compared to its previous design (which would run the full binary search even when a route has no-arb).
Given a route, we now calculate if the maxAmountIn and maxAmountIn + 1 profit is increasing as a pre-check and increase the range if so. If the profit from maxInputIn is still increasing, this means that we need to increase the range to capture the max profit.
Adds tests that verifies the limit/point system works properly, >3 pool routes work, and other denom besides osmo/atom work.
Acceptance Criteria
This change added tests and can be verified as follows:
The text was updated successfully, but these errors were encountered: