Skip to content

Commit

Permalink
[VS Incentives]: Implement trackVolume helper (osmosis-labs#6108)
Browse files Browse the repository at this point in the history
* implement and thoroughly test trackVolume helper

* remove error returns and fail quietly

* clean up comments

* add further clarifying comments on panic

* further comment cleanups
  • Loading branch information
AlpinYukseloglu authored Aug 23, 2023
1 parent 12ad732 commit 62f13bc
Show file tree
Hide file tree
Showing 10 changed files with 738 additions and 2 deletions.
4 changes: 4 additions & 0 deletions app/keepers/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,10 @@ func (appKeepers *AppKeepers) InitNormalKeepers(
appKeepers.BankKeeper,
appKeepers.AccountKeeper,
appKeepers.DistrKeeper,
appKeepers.StakingKeeper,
appKeepers.ProtoRevKeeper,
)
appKeepers.PoolManagerKeeper.SetStakingKeeper(appKeepers.StakingKeeper)
appKeepers.GAMMKeeper.SetPoolManager(appKeepers.PoolManagerKeeper)
appKeepers.ConcentratedLiquidityKeeper.SetPoolManagerKeeper(appKeepers.PoolManagerKeeper)
appKeepers.CosmwasmPoolKeeper.SetPoolManagerKeeper(appKeepers.PoolManagerKeeper)
Expand All @@ -368,6 +371,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers(
appKeepers.ConcentratedLiquidityKeeper,
)
appKeepers.ProtoRevKeeper = &protorevKeeper
appKeepers.PoolManagerKeeper.SetProtorevKeeper(appKeepers.ProtoRevKeeper)

txFeesKeeper := txfeeskeeper.NewKeeper(
appKeepers.AccountKeeper,
Expand Down
14 changes: 14 additions & 0 deletions proto/osmosis/poolmanager/v1beta1/tracked_volume.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
syntax = "proto3";
package osmosis.poolmanager.v1beta1;

import "gogoproto/gogo.proto";
import "cosmos/base/v1beta1/coin.proto";

option go_package = "github.com/osmosis-labs/osmosis/v17/x/poolmanager/types";

message TrackedVolume {
repeated cosmos.base.v1beta1.Coin amount = 1 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}
4 changes: 4 additions & 0 deletions x/poolmanager/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,7 @@ func (k Keeper) CreateOsmoMultihopExpectedSwapOuts(
) ([]sdk.Int, error) {
return k.createOsmoMultihopExpectedSwapOuts(ctx, route, tokenOut, cumulativeRouteSwapFee, sumOfSwapFees)
}

func (k Keeper) TrackVolume(ctx sdk.Context, poolId uint64, volumeGenerated sdk.Coin) {
k.trackVolume(ctx, poolId, volumeGenerated)
}
16 changes: 15 additions & 1 deletion x/poolmanager/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ type Keeper struct {
bankKeeper types.BankI
accountKeeper types.AccountI
communityPoolKeeper types.CommunityPoolI
stakingKeeper types.StakingKeeper
protorevKeeper types.ProtorevKeeper

// routes is a map to get the pool module by id.
routes map[types.PoolType]types.PoolModuleI
Expand All @@ -33,7 +35,7 @@ type Keeper struct {
paramSpace paramtypes.Subspace
}

func NewKeeper(storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, gammKeeper types.PoolModuleI, concentratedKeeper types.PoolModuleI, cosmwasmpoolKeeper types.PoolModuleI, bankKeeper types.BankI, accountKeeper types.AccountI, communityPoolKeeper types.CommunityPoolI) *Keeper {
func NewKeeper(storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, gammKeeper types.PoolModuleI, concentratedKeeper types.PoolModuleI, cosmwasmpoolKeeper types.PoolModuleI, bankKeeper types.BankI, accountKeeper types.AccountI, communityPoolKeeper types.CommunityPoolI, stakingKeeper types.StakingKeeper, protorevKeeper types.ProtorevKeeper) *Keeper {
// set KeyTable if it has not already been set
if !paramSpace.HasKeyTable() {
paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable())
Expand Down Expand Up @@ -61,6 +63,8 @@ func NewKeeper(storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, gammKeeper
communityPoolKeeper: communityPoolKeeper,
routes: routesMap,
poolModules: routesList,
stakingKeeper: stakingKeeper,
protorevKeeper: protorevKeeper,
}
}

Expand Down Expand Up @@ -117,3 +121,13 @@ func (k Keeper) SetNextPoolId(ctx sdk.Context, poolId uint64) {
func (k *Keeper) SetPoolIncentivesKeeper(poolIncentivesKeeper types.PoolIncentivesKeeperI) {
k.poolIncentivesKeeper = poolIncentivesKeeper
}

// SetStakingKeeper sets staking keeper
func (k *Keeper) SetStakingKeeper(stakingKeeper types.StakingKeeper) {
k.stakingKeeper = stakingKeeper
}

// SetProtorevKeeper sets protorev keeper
func (k *Keeper) SetProtorevKeeper(protorevKeeper types.ProtorevKeeper) {
k.protorevKeeper = protorevKeeper
}
5 changes: 5 additions & 0 deletions x/poolmanager/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ func TestKeeperTestSuite(t *testing.T) {

func (s *KeeperTestSuite) SetupTest() {
s.Setup()

// Set the bond denom to be uosmo to make volume tracking tests more readable.
skParams := s.App.StakingKeeper.GetParams(s.Ctx)
skParams.BondDenom = "uosmo"
s.App.StakingKeeper.SetParams(s.Ctx, skParams)
}

// createBalancerPoolsFromCoinsWithSpreadFactor creates balancer pools from given sets of coins and respective spread factors.
Expand Down
90 changes: 90 additions & 0 deletions x/poolmanager/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -741,3 +741,93 @@ func (k Keeper) TotalLiquidity(ctx sdk.Context) (sdk.Coins, error) {
totalLiquidity := totalGammLiquidity.Add(totalConcentratedLiquidity...).Add(totalCosmwasmLiquidity...)
return totalLiquidity, nil
}

// nolint: unused
// trackVolume converts the input token into OSMO units and adds it to the global tracked volume for the given pool ID.
// Fails quietly if an OSMO paired pool cannot be found, although this should only happen in rare scenarios where OSMO is
// removed as a base denom from the protorev module (which this function relies on).
//
// CONTRACT: `volumeGenerated` corresponds to one of the denoms in the pool
// CONTRACT: pool with `poolId` exists
func (k Keeper) trackVolume(ctx sdk.Context, poolId uint64, volumeGenerated sdk.Coin) {
// If the denom is already denominated in uosmo, we can just use it directly
OSMO := k.stakingKeeper.BondDenom(ctx)
if volumeGenerated.Denom == OSMO {
k.addVolume(ctx, poolId, volumeGenerated)
return
}

// Get the most liquid OSMO-paired pool with `volumeGenerated`'s denom using `GetPoolForDenomPair`
osmoPairedPoolId, err := k.protorevKeeper.GetPoolForDenomPair(ctx, OSMO, volumeGenerated.Denom)

// If no pool is found, fail quietly.
//
// This is a rare scenario that should only happen if OSMO-paired pools are all removed from the protorev module.
// Since this removal scenario is all-or-nothing, this is functionally equiavalent to freezing the tracked volume amounts
// where they were prior to the disabling, which seems an appropriate response.
//
// This branch would also get triggered in the case where there is a token that has no OSMO-paired pool on the entire chain.
// We simply do not track volume in these cases. Importantly, volume splitting gauge logic should prevent a gauge from being
// created for such a pool that includes such a token, although it is okay to no-op in these cases regardless.
if err != nil {
return
}

// Since we want to ultimately multiply the volume by this spot price, we want to quote OSMO in terms of the input token.
// This is so that once we multiply the volume by the spot price, we get the volume in units of OSMO.
osmoPerInputToken, err := k.RouteCalculateSpotPrice(ctx, osmoPairedPoolId, OSMO, volumeGenerated.Denom)

// We expect that if a pool is found, there should always be an available spot price as well.
// That being said, if there is an error finding the spot price, we fail quietly and leave tracked volume unchanged.
// This is because we do not want to escalate an issue with finding spot price to locking all swaps involving the given asset.
if err != nil {
return
}

// Multiply `volumeGenerated.Amount.ToDec()` by this spot price.
// While rounding does not particularly matter here, we round down to ensure that we do not overcount volume.
volumeInOsmo := volumeGenerated.Amount.ToDec().Mul(osmoPerInputToken).TruncateInt()

// Add this new volume to the global tracked volume for the pool ID
k.addVolume(ctx, poolId, sdk.NewCoin(OSMO, volumeInOsmo))
}

// nolint: unused
// addVolume adds the given volume to the global tracked volume for the given pool ID.
func (k Keeper) addVolume(ctx sdk.Context, poolId uint64, volumeGenerated sdk.Coin) {
// Get the current volume for the pool ID
currentTotalVolume := k.GetTotalVolumeForPool(ctx, poolId)

// Add newly generated volume to existing volume and set updated volume in state
newTotalVolume := currentTotalVolume.Add(volumeGenerated)
k.setVolume(ctx, poolId, newTotalVolume)
}

// nolint: unused
// setVolume sets the given volume to the global tracked volume for the given pool ID.
func (k Keeper) setVolume(ctx sdk.Context, poolId uint64, totalVolume sdk.Coins) {
storedVolume := types.TrackedVolume{Amount: totalVolume}
osmoutils.MustSet(ctx.KVStore(k.storeKey), types.KeyPoolVolume(poolId), &storedVolume)
}

// GetTotalVolumeForPool gets the total OSMO-denominated historical volume for a given pool ID.
func (k Keeper) GetTotalVolumeForPool(ctx sdk.Context, poolId uint64) sdk.Coins {
var currentTrackedVolume types.TrackedVolume
volumeFound, err := osmoutils.Get(ctx.KVStore(k.storeKey), types.KeyPoolVolume(poolId), &currentTrackedVolume)
if err != nil {
// We can only encounter an error if a database or serialization errors occurs, so we panic here.
// Normally this would be handled by `osmoutils.MustGet`, but since we want to specifically use `osmoutils.Get`,
// we also have to manually panic here.
panic(err)
}

// If no volume was found, we treat the existing volume as 0.
// While we can technically require volume to exist, we would need to store empty coins in state for each pool (past and present),
// which is a high storage cost to pay for a weak guardrail.
currentTotalVolume := sdk.NewCoins()
if volumeFound {
currentTotalVolume = currentTrackedVolume.Amount
}

return currentTotalVolume
}
Loading

0 comments on commit 62f13bc

Please sign in to comment.