From de3b421e751abe2d368d8ef6a3a2a21610ea543a Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Wed, 13 Mar 2024 23:33:17 -0500 Subject: [PATCH] feat(x/incentives): min value param for epoch distribution (#7615) * min osmo value for distribution * add changelog entry * test fixes * update tests and add fixes shown from tests * spelling * change param to be Coin instead of int * lints * address tests * Update x/incentives/keeper/distribute.go Co-authored-by: Nicolas Lara * fmt --------- Co-authored-by: Matt, Park <45252226+mattverse@users.noreply.github.com> Co-authored-by: Nicolas Lara --- CHANGELOG.md | 1 + app/keepers/keepers.go | 1 + app/upgrades/v24/upgrades.go | 6 + app/upgrades/v24/upgrades_test.go | 8 + proto/osmosis/incentives/params.proto | 7 + .../msg/transmuter/transmuter_test.go | 3 + x/incentives/keeper/distribute.go | 83 +++++++++- x/incentives/keeper/distribute_test.go | 156 +++++++++++++++++- x/incentives/keeper/keeper.go | 4 +- x/incentives/keeper/keeper_test.go | 2 + x/incentives/keeper/suite_test.go | 26 ++- x/incentives/types/constants.go | 8 +- x/incentives/types/expected_keepers.go | 5 + x/incentives/types/params.go | 18 +- x/incentives/types/params.pb.go | 128 ++++++++++---- x/superfluid/keeper/keeper_test.go | 2 + 16 files changed, 399 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcf5fd4bcc9..1fb4c4570bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [#7555](https://github.com/osmosis-labs/osmosis/pull/7555) Refactor taker fees, distribute via a single module account, track once at epoch * [#7562](https://github.com/osmosis-labs/osmosis/pull/7562) Speedup Protorev estimation logic by removing unnecessary taker fee simulations. * [#7595](https://github.com/osmosis-labs/osmosis/pull/7595) Fix cosmwasm pool model code ID migration. +* [#7615](https://github.com/osmosis-labs/osmosis/pull/7615) Min value param for epoch distribution. * [#7619](https://github.com/osmosis-labs/osmosis/pull/7619) Slight speedup/gas improvement to CL GetTotalPoolLiquidity queries * [#7622](https://github.com/osmosis-labs/osmosis/pull/7622) Create/remove tick events. * [#7623](https://github.com/osmosis-labs/osmosis/pull/7623) Add query for querying all before send hooks diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index ed51b011e10..e0b2731824d 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -437,6 +437,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers( appKeepers.ConcentratedLiquidityKeeper, appKeepers.PoolManagerKeeper, appKeepers.PoolIncentivesKeeper, + appKeepers.ProtoRevKeeper, ) appKeepers.ConcentratedLiquidityKeeper.SetIncentivesKeeper(appKeepers.IncentivesKeeper) appKeepers.GAMMKeeper.SetIncentivesKeeper(appKeepers.IncentivesKeeper) diff --git a/app/upgrades/v24/upgrades.go b/app/upgrades/v24/upgrades.go index 4a0bd361219..560e30cb040 100644 --- a/app/upgrades/v24/upgrades.go +++ b/app/upgrades/v24/upgrades.go @@ -7,6 +7,8 @@ import ( "github.com/osmosis-labs/osmosis/v23/app/keepers" "github.com/osmosis-labs/osmosis/v23/app/upgrades" + + incentivestypes "github.com/osmosis-labs/osmosis/v23/x/incentives/types" ) func CreateUpgradeHandler( @@ -39,6 +41,10 @@ func CreateUpgradeHandler( // since we only need the pool indexed TWAPs. keepers.TwapKeeper.DeleteAllHistoricalTimeIndexedTWAPs(ctx) + // Set the new min value for distribution for the incentives module. + // https://www.mintscan.io/osmosis/proposals/733 + keepers.IncentivesKeeper.SetParam(ctx, incentivestypes.KeyMinValueForDistr, incentivestypes.DefaultMinValueForDistr) + return migrations, nil } } diff --git a/app/upgrades/v24/upgrades_test.go b/app/upgrades/v24/upgrades_test.go index 978f3ac97e3..eae183d4a72 100644 --- a/app/upgrades/v24/upgrades_test.go +++ b/app/upgrades/v24/upgrades_test.go @@ -16,6 +16,7 @@ import ( "github.com/osmosis-labs/osmosis/osmoutils" "github.com/osmosis-labs/osmosis/v23/app/apptesting" + incentivestypes "github.com/osmosis-labs/osmosis/v23/x/incentives/types" protorevtypes "github.com/osmosis-labs/osmosis/v23/x/protorev/types" "github.com/osmosis-labs/osmosis/v23/x/twap/types" twaptypes "github.com/osmosis-labs/osmosis/v23/x/twap/types" @@ -131,6 +132,13 @@ func (s *UpgradeTestSuite) TestUpgrade() { oldBaseDenoms, err = s.App.ProtoRevKeeper.DeprecatedGetAllBaseDenoms(s.Ctx) s.Require().NoError(err) s.Require().Empty(oldBaseDenoms) + + // INCENTIVES Tests + // + + // Check that the new min value for distribution has been set + params := s.App.IncentivesKeeper.GetParams(s.Ctx) + s.Require().Equal(incentivestypes.DefaultMinValueForDistr, params.MinValueForDistribution) } func dummyUpgrade(s *UpgradeTestSuite) { diff --git a/proto/osmosis/incentives/params.proto b/proto/osmosis/incentives/params.proto index 62d7629127f..27f9113ded0 100644 --- a/proto/osmosis/incentives/params.proto +++ b/proto/osmosis/incentives/params.proto @@ -44,4 +44,11 @@ message Params { (gogoproto.stdduration) = true, (gogoproto.moretags) = "yaml:\"internal_uptime\"" ]; + // min_value_for_distribution is the minimum amount a token must be worth + // in order to be eligible for distribution. If the token is worth + // less than this amount (or the route between the two denoms is not + // registered), it will not be distributed and is forfeited to the remaining + // distributees that are eligible. + cosmos.base.v1beta1.Coin min_value_for_distribution = 5 + [ (gogoproto.nullable) = false ]; } diff --git a/x/cosmwasmpool/cosmwasm/msg/transmuter/transmuter_test.go b/x/cosmwasmpool/cosmwasm/msg/transmuter/transmuter_test.go index d311a0335ce..f52d466ef13 100644 --- a/x/cosmwasmpool/cosmwasm/msg/transmuter/transmuter_test.go +++ b/x/cosmwasmpool/cosmwasm/msg/transmuter/transmuter_test.go @@ -50,6 +50,9 @@ func (s *TransmuterSuite) TestFunctionalTransmuter() { expectedDenomSuffix = "/transmuter/poolshare" ) + // Set base denom + s.App.IncentivesKeeper.SetParam(s.Ctx, incentivetypes.KeyMinValueForDistr, sdk.NewCoin("uosmo", osmomath.NewInt(10000))) + // Create Transmuter pool transmuter := s.PrepareCosmWasmPool() diff --git a/x/incentives/keeper/distribute.go b/x/incentives/keeper/distribute.go index 7949fcc87a2..dc40c65d99c 100644 --- a/x/incentives/keeper/distribute.go +++ b/x/incentives/keeper/distribute.go @@ -23,8 +23,15 @@ import ( var ( millisecondsInSecDec = osmomath.NewDec(1000) + zeroInt = osmomath.ZeroInt() ) +// DistributionValueCache is a cache for when we calculate the minimum value +// an underlying token must be to be distributed. +type DistributionValueCache struct { + denomToMinValueMap map[string]osmomath.Int +} + // AllocateAcrossGauges for every gauge in the input, it updates the weights according to the splitting // policy and allocates the coins to the underlying gauges per the updated weights. // Note, every group is associated with a group gauge. The distribution to regular gauges @@ -388,7 +395,7 @@ func (k Keeper) doDistributionSends(ctx sdk.Context, distrs *distributionInfo) e // the distrInfo struct. It also updates the gauge for the distribution. // locks is expected to be the correct set of lock recipients for this gauge. func (k Keeper) distributeSyntheticInternal( - ctx sdk.Context, gauge types.Gauge, locks []*lockuptypes.PeriodLock, distrInfo *distributionInfo, + ctx sdk.Context, gauge types.Gauge, locks []*lockuptypes.PeriodLock, distrInfo *distributionInfo, minDistrValueCache *DistributionValueCache, ) (sdk.Coins, error) { qualifiedLocks := k.lk.GetLocksLongerThanDurationDenom(ctx, gauge.DistributeTo.Denom, gauge.DistributeTo.Duration) @@ -425,7 +432,7 @@ func (k Keeper) distributeSyntheticInternal( sortedAndTrimmedQualifiedLocks[v.index] = &v.lock } - return k.distributeInternal(ctx, gauge, sortedAndTrimmedQualifiedLocks, distrInfo) + return k.distributeInternal(ctx, gauge, sortedAndTrimmedQualifiedLocks, distrInfo, minDistrValueCache) } // syncGroupWeights updates the individual and total weights of the group records based on the splitting policy. @@ -454,7 +461,7 @@ func (k Keeper) syncGroupWeights(ctx sdk.Context, group types.Group) error { // calculateGroupWeights calculates the updated weights of the group records based on the pool volumes. // It returns the updated group and an error if any. It does not mutate the passed in object. func (k Keeper) calculateGroupWeights(ctx sdk.Context, group types.Group) (types.Group, error) { - totalWeight := sdk.ZeroInt() + totalWeight := zeroInt // We operate on a deep copy of the given group because we expect to handle specific errors quietly // and want to avoid the scenario where the original group gauge is partially mutated in such cases. @@ -611,10 +618,14 @@ func (k Keeper) getNoLockGaugeUptime(ctx sdk.Context, gauge types.Gauge, poolId // // CONTRACT: gauge passed in as argument must be an active gauge. func (k Keeper) distributeInternal( - ctx sdk.Context, gauge types.Gauge, locks []*lockuptypes.PeriodLock, distrInfo *distributionInfo, + ctx sdk.Context, gauge types.Gauge, locks []*lockuptypes.PeriodLock, distrInfo *distributionInfo, minDistrValueCache *DistributionValueCache, ) (sdk.Coins, error) { totalDistrCoins := sdk.NewCoins() + // Retrieve the min value for distribution. + // If any distribution amount is valued less than what the param is set, it will be skipped. + minValueForDistr := k.GetParams(ctx).MinValueForDistribution + remainCoins := gauge.Coins.Sub(gauge.DistributedCoins...) // if its a perpetual gauge, we set remaining epochs to 1. @@ -722,6 +733,58 @@ func (k Keeper) distributeInternal( // which is bounded to an Int. So we can safely skip this. amtIntBi.Quo(amtIntBi, lockSumTimesRemainingEpochsBi) + + // Determine if the value to distribute is worth enough in minValueForDistr denom to be distributed. + if coin.Denom == minValueForDistr.Denom { + // If the denom is the same as the minValueForDistr param, no transformation is needed. + if amtInt.LT(minValueForDistr.Amount) { + continue + } + } else { + // If the denom is not the minValueForDistr denom, we need to transform the underlying to it. + // Check if the denom exists in the cached values + value, ok := minDistrValueCache.denomToMinValueMap[coin.Denom] + if !ok { + // Cache miss, figure out the value and add it to the cache + poolId, err := k.prk.GetPoolForDenomPairNoOrder(ctx, minValueForDistr.Denom, coin.Denom) + if err != nil { + // If the pool denom pair pool route does not exist in protorev, we add a zero value to cache to avoid + // querying the pool again. + minDistrValueCache.denomToMinValueMap[coin.Denom] = zeroInt + continue + } + swapModule, pool, err := k.pmk.GetPoolModuleAndPool(ctx, poolId) + if err != nil { + return nil, err + } + + minTokenRequiredForDistr, err := swapModule.CalcOutAmtGivenIn(ctx, pool, minValueForDistr, coin.Denom, sdk.ZeroDec()) + if err != nil { + return nil, err + } + + // Add min token required for distribution to the cache + minDistrValueCache.denomToMinValueMap[coin.Denom] = minTokenRequiredForDistr.Amount + + // Check if the value is worth enough in the token to be distributed. + if amtInt.LT(minTokenRequiredForDistr.Amount) { + // The value is not worth enough, continue + continue + } + } else { + // Cache hit, use the value + + // This route does not exist in protorev so a zero value has been added when a cache miss occurred + if value.IsZero() { + continue + } + // Check if the underlying is worth enough in the token to be distributed. + if amtInt.LT(value) { + continue + } + } + } + if amtInt.Sign() == 1 { newlyDistributedCoin := sdk.Coin{Denom: coin.Denom, Amount: amtInt} distrCoins = distrCoins.Add(newlyDistributedCoin) @@ -858,6 +921,14 @@ func (k Keeper) Distribute(ctx sdk.Context, gauges []types.Gauge) (sdk.Coins, er totalDistributedCoins := sdk.NewCoins() scratchSlice := make([]*lockuptypes.PeriodLock, 0, 50000) + // Instead of re-fetching the minimum value an underlying token must be to meet the minimum + // requirement for distribution, we cache the values here. + // While this isn't precise as it doesn't account for price impact, it is good enough for the sole + // purpose of determining if we should distribute the token or not. + minDistrValueCache := &DistributionValueCache{ + denomToMinValueMap: make(map[string]osmomath.Int), + } + for _, gauge := range gauges { var gaugeDistributedCoins sdk.Coins filteredLocks := k.getDistributeToBaseLocks(ctx, gauge, locksByDenomCache, &scratchSlice) @@ -865,14 +936,14 @@ func (k Keeper) Distribute(ctx sdk.Context, gauges []types.Gauge) (sdk.Coins, er var err error if lockuptypes.IsSyntheticDenom(gauge.DistributeTo.Denom) { ctx.Logger().Debug("distributeSyntheticInternal, gauge id %d, %d", "module", types.ModuleName, "gaugeId", gauge.Id, "height", ctx.BlockHeight()) - gaugeDistributedCoins, err = k.distributeSyntheticInternal(ctx, gauge, filteredLocks, &distrInfo) + gaugeDistributedCoins, err = k.distributeSyntheticInternal(ctx, gauge, filteredLocks, &distrInfo, minDistrValueCache) } else { // Do not distribute if LockQueryType = Group, because if we distribute here we will be double distributing. if gauge.DistributeTo.LockQueryType == lockuptypes.ByGroup { continue } - gaugeDistributedCoins, err = k.distributeInternal(ctx, gauge, filteredLocks, &distrInfo) + gaugeDistributedCoins, err = k.distributeInternal(ctx, gauge, filteredLocks, &distrInfo, minDistrValueCache) } if err != nil { return nil, err diff --git a/x/incentives/keeper/distribute_test.go b/x/incentives/keeper/distribute_test.go index 4ffcfde021b..f37df309ec2 100644 --- a/x/incentives/keeper/distribute_test.go +++ b/x/incentives/keeper/distribute_test.go @@ -1,7 +1,6 @@ package keeper_test import ( - "strings" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -87,16 +86,23 @@ type GroupCreationFields struct { // TestDistribute tests that when the distribute command is executed on a provided gauge // that the correct amount of rewards is sent to the correct lock owners. func (s *KeeperTestSuite) TestDistribute() { + nonBaseDenom := "bar" defaultGauge := perpGaugeDesc{ lockDenom: defaultLPDenom, lockDuration: defaultLockDuration, rewardAmount: sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 3000)}, } + defaultGaugeNonBaseDenom := defaultGauge + defaultGaugeNonBaseDenom.rewardAmount = sdk.Coins{sdk.NewInt64Coin(nonBaseDenom, 3000)} + doubleLengthGauge := perpGaugeDesc{ lockDenom: defaultLPDenom, lockDuration: 2 * defaultLockDuration, rewardAmount: sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 3000)}, } + doubleLengthGaugeNonBaseDenom := doubleLengthGauge + doubleLengthGaugeNonBaseDenom.rewardAmount = sdk.Coins{sdk.NewInt64Coin(nonBaseDenom, 3000)} + noRewardGauge := perpGaugeDesc{ lockDenom: defaultLPDenom, lockDuration: defaultLockDuration, @@ -104,13 +110,17 @@ func (s *KeeperTestSuite) TestDistribute() { } noRewardCoins := sdk.Coins{} oneKRewardCoins := sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 1000)} + oneKRewardCoinsNonBaseDenom := sdk.Coins{sdk.NewInt64Coin(nonBaseDenom, 1000)} twoKRewardCoins := sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 2000)} threeKRewardCoins := sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 3000)} fiveKRewardCoins := sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 5000)} + fiveKRewardCoinsNonBaseDenom := sdk.Coins{sdk.NewInt64Coin(nonBaseDenom, 5000)} + fiveKFourHundredRewardCoinsNonBaseDenom := sdk.Coins{sdk.NewInt64Coin(nonBaseDenom, 5400)} tests := []struct { name string users []userLocks gauges []perpGaugeDesc + poolCoins sdk.Coins changeRewardReceiver []changeRewardReceiver expectedRewards []sdk.Coins }{ @@ -212,9 +222,61 @@ func (s *KeeperTestSuite) TestDistribute() { }, expectedRewards: []sdk.Coins{fiveKRewardCoins, oneKRewardCoins}, }, + // Non base denom rewards test, sufficient rewards to distribute all. + // gauge 1 gives 3k coins. three locks, all eligible. + // gauge 2 gives 3k coins. one lock, to twoLockupUser. + // 1k should to oneLockupUser and 5k to twoLockupUser. + { + name: "Non base denom, sufficient rewards to distribute all users", + users: []userLocks{oneLockupUser, twoLockupUser}, + gauges: []perpGaugeDesc{defaultGaugeNonBaseDenom, doubleLengthGaugeNonBaseDenom}, + poolCoins: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(10000)), sdk.NewCoin(nonBaseDenom, sdk.NewInt(10000))), // 1 bar equals 1 defaultRewardDenom + expectedRewards: []sdk.Coins{oneKRewardCoinsNonBaseDenom, fiveKRewardCoinsNonBaseDenom}, + }, + // Non base denom rewards test, insufficient rewards to distribute for some. + // gauge 1 gives 3k coins. three locks, all eligible. + // we increased the lockup of twoLockupUser so it is eligible for distribution. + // the distribution should be 600 to user 1, and 1200 + 1200 to user 2, but only the last two 1200 get distributed due to limit of 1090. + // gauge 2 gives 3k coins. one lock, to twoLockupUser. + // the distribution should be all 3000 to twoLockupUser since it is passed the 1090 distribution threshold. + { + name: "Non base denom, insufficient rewards to distribute to user 1, sufficient rewards to distribute to user 2", + users: []userLocks{oneLockupUser, twoLockupUserDoubleAmt}, + gauges: []perpGaugeDesc{defaultGaugeNonBaseDenom, doubleLengthGaugeNonBaseDenom}, + poolCoins: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(10000)), sdk.NewCoin(nonBaseDenom, sdk.NewInt(12000))), // min amt for distribution is now equal to 1090bar + expectedRewards: []sdk.Coins{noRewardCoins, fiveKFourHundredRewardCoinsNonBaseDenom}, + }, + // Non base denom rewards test, insufficient rewards to distribute all locks + // gauge 1 gives 3k coins. three locks, all eligible. + // gauge 2 gives 3k coins. one lock, to twoLockupUser. + // All pending reward payouts (600, 1200, and 3000) are all below the min threshold of 4545, so no rewards are distributed. + { + name: "Non base denom, insufficient rewards to distribute to all users", + users: []userLocks{oneLockupUser, twoLockupUserDoubleAmt}, + gauges: []perpGaugeDesc{defaultGaugeNonBaseDenom, doubleLengthGaugeNonBaseDenom}, + poolCoins: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(10000)), sdk.NewCoin(nonBaseDenom, sdk.NewInt(50000))), // min amt for distribution is now equal to 4545bar + expectedRewards: []sdk.Coins{noRewardCoins, noRewardCoins}, + }, + // No pool exists for the first gauge, so only the second gauge should distribute rewards. + // gauge 1 gives 3k coins. three locks, all eligible, but no pool exists to determine underlying value so no rewards are distributed. + // gauge 2 gives 3k coins. one lock, to twoLockupUser. + { + name: "Non base denom and base denom, no pool to determine non base denom value, only base denom distributes", + users: []userLocks{oneLockupUser, twoLockupUserDoubleAmt}, + gauges: []perpGaugeDesc{defaultGaugeNonBaseDenom, doubleLengthGauge}, + expectedRewards: []sdk.Coins{noRewardCoins, threeKRewardCoins}, + }, } for _, tc := range tests { s.SetupTest() + + // set the base denom and min value for distribution + err := s.App.TxFeesKeeper.SetBaseDenom(s.Ctx, defaultRewardDenom) + s.Require().NoError(err) + s.App.IncentivesKeeper.SetParam(s.Ctx, types.KeyMinValueForDistr, sdk.NewCoin(defaultRewardDenom, sdk.NewInt(1000))) + baseDenom, err := s.App.TxFeesKeeper.GetBaseDenom(s.Ctx) + s.Require().NoError(err) + // setup gauges and the locks defined in the above tests, then distribute to them gauges := s.SetupGauges(tc.gauges, defaultLPDenom) addrs := s.SetupUserLocks(tc.users) @@ -224,7 +286,13 @@ func (s *KeeperTestSuite) TestDistribute() { s.SetupChangeRewardReceiver(tc.changeRewardReceiver, addrs) } - _, err := s.App.IncentivesKeeper.Distribute(s.Ctx, gauges) + // if test requires it, set up a pool with the baseDenom and the underlying reward denom + if tc.poolCoins != nil { + poolID := s.PrepareBalancerPoolWithCoins(tc.poolCoins...) + s.App.ProtoRevKeeper.SetPoolForDenomPair(s.Ctx, baseDenom, nonBaseDenom, poolID) + } + + _, err = s.App.IncentivesKeeper.Distribute(s.Ctx, gauges) s.Require().NoError(err) // check expected rewards against actual rewards received for i, addr := range addrs { @@ -663,16 +731,23 @@ func (s *KeeperTestSuite) TestDistribute_ExternalIncentives_NoLock() { // TestSyntheticDistribute tests that when the distribute command is executed on a provided gauge // the correct amount of rewards is sent to the correct synthetic lock owners. func (s *KeeperTestSuite) TestSyntheticDistribute() { + nonBaseDenom := "bar" defaultGauge := perpGaugeDesc{ lockDenom: defaultLPSyntheticDenom, lockDuration: defaultLockDuration, rewardAmount: sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 3000)}, } + defaultGaugeNonBaseDenom := defaultGauge + defaultGaugeNonBaseDenom.rewardAmount = sdk.Coins{sdk.NewInt64Coin(nonBaseDenom, 3000)} + doubleLengthGauge := perpGaugeDesc{ lockDenom: defaultLPSyntheticDenom, lockDuration: 2 * defaultLockDuration, rewardAmount: sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 3000)}, } + doubleLengthGaugeNonBaseDenom := doubleLengthGauge + doubleLengthGaugeNonBaseDenom.rewardAmount = sdk.Coins{sdk.NewInt64Coin(nonBaseDenom, 3000)} + noRewardGauge := perpGaugeDesc{ lockDenom: defaultLPSyntheticDenom, lockDuration: defaultLockDuration, @@ -680,12 +755,17 @@ func (s *KeeperTestSuite) TestSyntheticDistribute() { } noRewardCoins := sdk.Coins{} oneKRewardCoins := sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 1000)} + oneKRewardCoinsNonBaseDenom := sdk.Coins{sdk.NewInt64Coin(nonBaseDenom, 1000)} twoKRewardCoins := sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 2000)} + threeKRewardCoins := sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 3000)} fiveKRewardCoins := sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 5000)} + fiveKRewardCoinsNonBaseDenom := sdk.Coins{sdk.NewInt64Coin(nonBaseDenom, 5000)} + fiveKFourHundredRewardCoinsNonBaseDenom := sdk.Coins{sdk.NewInt64Coin(nonBaseDenom, 5400)} tests := []struct { name string users []userLocks gauges []perpGaugeDesc + poolCoins sdk.Coins expectedRewards []sdk.Coins }{ // gauge 1 gives 3k coins. three locks, all eligible. 1k coins per lock. @@ -722,24 +802,84 @@ func (s *KeeperTestSuite) TestSyntheticDistribute() { gauges: []perpGaugeDesc{noRewardGauge, defaultGauge}, expectedRewards: []sdk.Coins{oneKRewardCoins, twoKRewardCoins}, }, + // Non base denom rewards test, sufficient rewards to distribute all. + // gauge 1 gives 3k coins. three locks, all eligible. + // gauge 2 gives 3k coins. one lock, to twoLockupUser. + // 1k should to oneLockupUser and 5k to twoLockupUser. + { + name: "Non base denom, sufficient rewards to distribute all users", + users: []userLocks{oneSyntheticLockupUser, twoSyntheticLockupUser}, + gauges: []perpGaugeDesc{defaultGaugeNonBaseDenom, doubleLengthGaugeNonBaseDenom}, + poolCoins: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(10000)), sdk.NewCoin(nonBaseDenom, sdk.NewInt(10000))), // 1 bar equals 1 defaultRewardDenom + expectedRewards: []sdk.Coins{oneKRewardCoinsNonBaseDenom, fiveKRewardCoinsNonBaseDenom}, + }, + // Non base denom rewards test, insufficient rewards to distribute for some. + // gauge 1 gives 3k coins. three locks, all eligible. + // we increased the lockup of twoLockupUser so it is eligible for distribution. + // the distribution should be 600 to user 1, and 1200 + 1200 to user 2, but only the last two 1200 get distributed due to limit of 1090. + // gauge 2 gives 3k coins. one lock, to twoLockupUser. + // the distribution should be all 3000 to twoLockupUser since it is passed the 1090 distribution threshold. + { + name: "Non base denom, insufficient rewards to distribute to user 1, sufficient rewards to distribute to user 2", + users: []userLocks{oneSyntheticLockupUser, twoSyntheticLockupUserDoubleAmt}, + gauges: []perpGaugeDesc{defaultGaugeNonBaseDenom, doubleLengthGaugeNonBaseDenom}, + poolCoins: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(10000)), sdk.NewCoin(nonBaseDenom, sdk.NewInt(12000))), // min amt for distribution is now equal to 1090bar + expectedRewards: []sdk.Coins{noRewardCoins, fiveKFourHundredRewardCoinsNonBaseDenom}, + }, + // Non base denom rewards test, insufficient rewards to distribute all locks + // gauge 1 gives 3k coins. three locks, all eligible. + // gauge 2 gives 3k coins. one lock, to twoLockupUser. + // All pending reward payouts (600, 1200, and 3000) are all below the min threshold of 4545, so no rewards are distributed. + { + name: "Non base denom, insufficient rewards to distribute to all users", + users: []userLocks{oneSyntheticLockupUser, twoSyntheticLockupUserDoubleAmt}, + gauges: []perpGaugeDesc{defaultGaugeNonBaseDenom, doubleLengthGaugeNonBaseDenom}, + poolCoins: sdk.NewCoins(sdk.NewCoin(defaultRewardDenom, sdk.NewInt(10000)), sdk.NewCoin(nonBaseDenom, sdk.NewInt(50000))), // min amt for distribution is now equal to 4545bar + expectedRewards: []sdk.Coins{noRewardCoins, noRewardCoins}, + }, + // No pool exists for the first gauge, so only the second gauge should distribute rewards. + // gauge 1 gives 3k coins. three locks, all eligible, but no pool exists to determine underlying value so no rewards are distributed. + // gauge 2 gives 3k coins. one lock, to twoLockupUser. + { + name: "Non base denom and base denom, no pool to determine non base denom value, only base denom distributes", + users: []userLocks{oneSyntheticLockupUser, twoSyntheticLockupUserDoubleAmt}, + gauges: []perpGaugeDesc{defaultGaugeNonBaseDenom, doubleLengthGauge}, + expectedRewards: []sdk.Coins{noRewardCoins, threeKRewardCoins}, + }, } for _, tc := range tests { s.SetupTest() + + // set the base denom and min value for distribution + err := s.App.TxFeesKeeper.SetBaseDenom(s.Ctx, defaultRewardDenom) + s.Require().NoError(err) + s.App.IncentivesKeeper.SetParam(s.Ctx, types.KeyMinValueForDistr, sdk.NewCoin(defaultRewardDenom, sdk.NewInt(1000))) + baseDenom, err := s.App.TxFeesKeeper.GetBaseDenom(s.Ctx) + s.Require().NoError(err) + // setup gauges and the synthetic locks defined in the above tests, then distribute to them gauges := s.SetupGauges(tc.gauges, defaultLPSyntheticDenom) addrs := s.SetupUserSyntheticLocks(tc.users) - _, err := s.App.IncentivesKeeper.Distribute(s.Ctx, gauges) + + // if test requires it, set up a pool with the baseDenom and the underlying reward denom + if tc.poolCoins != nil { + poolID := s.PrepareBalancerPoolWithCoins(tc.poolCoins...) + s.App.ProtoRevKeeper.SetPoolForDenomPair(s.Ctx, baseDenom, nonBaseDenom, poolID) + } + + _, err = s.App.IncentivesKeeper.Distribute(s.Ctx, gauges) s.Require().NoError(err) // check expected rewards against actual rewards received for i, addr := range addrs { - var rewards string bal := s.App.BankKeeper.GetAllBalances(s.Ctx, addr) // extract the superbonding tokens from the rewards distribution - // TODO: figure out a less hacky way of doing this - if strings.Contains(bal.String(), "lptoken/superbonding,") { - rewards = strings.Split(bal.String(), "lptoken/superbonding,")[1] + rewards := sdk.Coins{} + for _, coin := range bal { + if coin.Denom != "lptoken/superbonding" { + rewards = append(rewards, coin) + } } - s.Require().Equal(tc.expectedRewards[i].String(), rewards, "test %v, person %d", tc.name, i) + s.Require().Equal(tc.expectedRewards[i].String(), rewards.String(), "test %v, person %d", tc.name, i) } } } diff --git a/x/incentives/keeper/keeper.go b/x/incentives/keeper/keeper.go index e96053084dd..27db39df642 100644 --- a/x/incentives/keeper/keeper.go +++ b/x/incentives/keeper/keeper.go @@ -28,10 +28,11 @@ type Keeper struct { clk types.ConcentratedLiquidityKeeper pmk types.PoolManagerKeeper pik types.PoolIncentiveKeeper + prk types.ProtorevKeeper } // NewKeeper returns a new instance of the incentive module keeper struct. -func NewKeeper(storeKey storetypes.StoreKey, paramSpace paramtypes.Subspace, ak types.AccountKeeper, bk types.BankKeeper, lk types.LockupKeeper, ek types.EpochKeeper, ck types.CommunityPoolKeeper, txfk types.TxFeesKeeper, clk types.ConcentratedLiquidityKeeper, pmk types.PoolManagerKeeper, pik types.PoolIncentiveKeeper) *Keeper { +func NewKeeper(storeKey storetypes.StoreKey, paramSpace paramtypes.Subspace, ak types.AccountKeeper, bk types.BankKeeper, lk types.LockupKeeper, ek types.EpochKeeper, ck types.CommunityPoolKeeper, txfk types.TxFeesKeeper, clk types.ConcentratedLiquidityKeeper, pmk types.PoolManagerKeeper, pik types.PoolIncentiveKeeper, prk types.ProtorevKeeper) *Keeper { if !paramSpace.HasKeyTable() { paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) } @@ -48,6 +49,7 @@ func NewKeeper(storeKey storetypes.StoreKey, paramSpace paramtypes.Subspace, ak tk: txfk, pmk: pmk, clk: clk, + prk: prk, } } diff --git a/x/incentives/keeper/keeper_test.go b/x/incentives/keeper/keeper_test.go index af623a2d2db..9d91987c85c 100644 --- a/x/incentives/keeper/keeper_test.go +++ b/x/incentives/keeper/keeper_test.go @@ -12,6 +12,7 @@ import ( cltypes "github.com/osmosis-labs/osmosis/v23/x/concentrated-liquidity/types" "github.com/osmosis-labs/osmosis/v23/x/incentives/keeper" "github.com/osmosis-labs/osmosis/v23/x/incentives/types" + incentivetypes "github.com/osmosis-labs/osmosis/v23/x/incentives/types" ) type KeeperTestSuite struct { @@ -27,6 +28,7 @@ func (s *KeeperTestSuite) SetupTest() { lockableDurations := s.App.IncentivesKeeper.GetLockableDurations(s.Ctx) lockableDurations = append(lockableDurations, 2*time.Second) s.App.IncentivesKeeper.SetLockableDurations(s.Ctx, lockableDurations) + s.App.IncentivesKeeper.SetParam(s.Ctx, incentivetypes.KeyMinValueForDistr, sdk.NewCoin("stake", osmomath.NewInt(1))) } func TestKeeperTestSuite(t *testing.T) { diff --git a/x/incentives/keeper/suite_test.go b/x/incentives/keeper/suite_test.go index 9782425eacb..216aabf2d4b 100644 --- a/x/incentives/keeper/suite_test.go +++ b/x/incentives/keeper/suite_test.go @@ -12,13 +12,15 @@ import ( ) var ( - defaultLPDenom string = "lptoken" - defaultLPSyntheticDenom string = "lptoken/superbonding" - defaultLPTokens sdk.Coins = sdk.Coins{sdk.NewInt64Coin(defaultLPDenom, 10)} - defaultLPSyntheticTokens sdk.Coins = sdk.Coins{sdk.NewInt64Coin(defaultLPSyntheticDenom, 10)} - defaultLiquidTokens sdk.Coins = sdk.Coins{sdk.NewInt64Coin("foocoin", 10)} - defaultLockDuration time.Duration = time.Second - oneLockupUser userLocks = userLocks{ + defaultLPDenom string = "lptoken" + defaultLPSyntheticDenom string = "lptoken/superbonding" + defaultLPTokens sdk.Coins = sdk.Coins{sdk.NewInt64Coin(defaultLPDenom, 10)} + defaultLPTokensDoubleAmt sdk.Coins = sdk.Coins{sdk.NewInt64Coin(defaultLPDenom, 20)} + defaultLPSyntheticTokens sdk.Coins = sdk.Coins{sdk.NewInt64Coin(defaultLPSyntheticDenom, 10)} + defaultLPSyntheticTokensDoubleAmt sdk.Coins = sdk.Coins{sdk.NewInt64Coin(defaultLPSyntheticDenom, 20)} + defaultLiquidTokens sdk.Coins = sdk.Coins{sdk.NewInt64Coin("foocoin", 10)} + defaultLockDuration time.Duration = time.Second + oneLockupUser userLocks = userLocks{ lockDurations: []time.Duration{time.Second}, lockAmounts: []sdk.Coins{defaultLPTokens}, } @@ -26,6 +28,10 @@ var ( lockDurations: []time.Duration{defaultLockDuration, 2 * defaultLockDuration}, lockAmounts: []sdk.Coins{defaultLPTokens, defaultLPTokens}, } + twoLockupUserDoubleAmt userLocks = userLocks{ + lockDurations: []time.Duration{defaultLockDuration, 2 * defaultLockDuration}, + lockAmounts: []sdk.Coins{defaultLPTokensDoubleAmt, defaultLPTokensDoubleAmt}, + } oneSyntheticLockupUser userLocks = userLocks{ lockDurations: []time.Duration{time.Second}, lockAmounts: []sdk.Coins{defaultLPSyntheticTokens}, @@ -34,6 +40,10 @@ var ( lockDurations: []time.Duration{defaultLockDuration, 2 * defaultLockDuration}, lockAmounts: []sdk.Coins{defaultLPSyntheticTokens, defaultLPSyntheticTokens}, } + twoSyntheticLockupUserDoubleAmt userLocks = userLocks{ + lockDurations: []time.Duration{defaultLockDuration, 2 * defaultLockDuration}, + lockAmounts: []sdk.Coins{defaultLPSyntheticTokensDoubleAmt, defaultLPSyntheticTokensDoubleAmt}, + } defaultRewardDenom string = "rewardDenom" otherDenom string = "someOtherDenom" ) @@ -102,7 +112,6 @@ func (s *KeeperTestSuite) SetupChangeRewardReceiver(changeRewardReceivers []chan // SetupUserSyntheticLocks takes an array of user locks and creates synthetic locks based on this array, then returns the respective account address byte array. func (s *KeeperTestSuite) SetupUserSyntheticLocks(users []userLocks) (accs []sdk.AccAddress) { accs = make([]sdk.AccAddress, len(users)) - coins := sdk.Coins{sdk.NewInt64Coin("lptoken", 10)} lockupID := uint64(1) for i, user := range users { s.Assert().Equal(len(user.lockDurations), len(user.lockAmounts)) @@ -112,6 +121,7 @@ func (s *KeeperTestSuite) SetupUserSyntheticLocks(users []userLocks) (accs []sdk } accs[i] = s.setupAddr(i, "", totalLockAmt) for j := 0; j < len(user.lockAmounts); j++ { + coins := sdk.Coins{sdk.NewInt64Coin("lptoken", user.lockAmounts[j][0].Amount.Int64())} s.LockTokens(accs[i], coins, user.lockDurations[j]) err := s.App.LockupKeeper.CreateSyntheticLockup(s.Ctx, lockupID, "lptoken/superbonding", user.lockDurations[j], false) lockupID++ diff --git a/x/incentives/types/constants.go b/x/incentives/types/constants.go index 677071008c5..7881b258b0e 100644 --- a/x/incentives/types/constants.go +++ b/x/incentives/types/constants.go @@ -1,6 +1,11 @@ package types -import time "time" +import ( + time "time" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" +) var ( BaseGasFeeForCreateGauge = 10_000 @@ -13,4 +18,5 @@ var ( // for a gauge to be perpetual. For any other number of epochs // other than zero, the gauge is non-perpetual. Zero is invalid. PerpetualNumEpochsPaidOver = uint64(0) + DefaultMinValueForDistr = sdk.NewCoin("uosmo", sdkmath.NewInt(10000)) // 0.01 OSMO ) diff --git a/x/incentives/types/expected_keepers.go b/x/incentives/types/expected_keepers.go index dcc88a52dc0..b1676b2ad81 100644 --- a/x/incentives/types/expected_keepers.go +++ b/x/incentives/types/expected_keepers.go @@ -71,4 +71,9 @@ type GAMMKeeper interface { type PoolManagerKeeper interface { GetPool(ctx sdk.Context, poolId uint64) (poolmanagertypes.PoolI, error) GetOsmoVolumeForPool(ctx sdk.Context, poolId uint64) osmomath.Int + GetPoolModuleAndPool(ctx sdk.Context, poolId uint64) (swapModule poolmanagertypes.PoolModuleI, pool poolmanagertypes.PoolI, err error) +} + +type ProtorevKeeper interface { + GetPoolForDenomPairNoOrder(ctx sdk.Context, denom1, denom2 string) (uint64, error) } diff --git a/x/incentives/types/params.go b/x/incentives/types/params.go index c1915ad5d54..5e2cee8129f 100644 --- a/x/incentives/types/params.go +++ b/x/incentives/types/params.go @@ -19,6 +19,7 @@ var ( KeyGroupCreationFee = []byte("GroupCreationFee") KeyCreatorWhitelist = []byte("CreatorWhitelist") KeyInternalUptime = []byte("InternalUptime") + KeyMinValueForDistr = []byte("MinValueForDistr") // 100 OSMO DefaultGroupCreationFee = sdk.NewCoins(sdk.NewCoin("uosmo", sdk.NewInt(100_000_000))) @@ -30,12 +31,13 @@ func ParamKeyTable() paramtypes.KeyTable { } // NewParams takes an epoch distribution identifier and group creation fee, then returns an incentives Params struct. -func NewParams(distrEpochIdentifier string, groupCreationFee sdk.Coins, internalUptime time.Duration) Params { +func NewParams(distrEpochIdentifier string, groupCreationFee sdk.Coins, internalUptime time.Duration, minValueForDistr sdk.Coin) Params { return Params{ DistrEpochIdentifier: distrEpochIdentifier, GroupCreationFee: groupCreationFee, UnrestrictedCreatorWhitelist: []string{}, InternalUptime: internalUptime, + MinValueForDistribution: minValueForDistr, } } @@ -46,6 +48,7 @@ func DefaultParams() Params { GroupCreationFee: DefaultGroupCreationFee, UnrestrictedCreatorWhitelist: []string{}, InternalUptime: DefaultConcentratedUptime, + MinValueForDistribution: DefaultMinValueForDistr, } } @@ -67,6 +70,10 @@ func (p Params) Validate() error { return err } + if err := ValidateMinValueForDistr(p.MinValueForDistribution); err != nil { + return err + } + return nil } @@ -109,6 +116,14 @@ func ValidateInternalUptime(i interface{}) error { return nil } +func ValidateMinValueForDistr(i interface{}) error { + _, ok := i.(sdk.Coin) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + return nil +} + // ParamSetPairs takes the parameter struct and associates the paramsubspace key and field of the parameters as a KVStore. func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { return paramtypes.ParamSetPairs{ @@ -116,5 +131,6 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { paramtypes.NewParamSetPair(KeyGroupCreationFee, &p.GroupCreationFee, ValidateGroupCreaionFee), paramtypes.NewParamSetPair(KeyCreatorWhitelist, &p.UnrestrictedCreatorWhitelist, osmoutils.ValidateAddressList), paramtypes.NewParamSetPair(KeyInternalUptime, &p.InternalUptime, ValidateInternalUptime), + paramtypes.NewParamSetPair(KeyMinValueForDistr, &p.MinValueForDistribution, ValidateMinValueForDistr), } } diff --git a/x/incentives/types/params.pb.go b/x/incentives/types/params.pb.go index 4fcf17e42fa..c6b163b00de 100644 --- a/x/incentives/types/params.pb.go +++ b/x/incentives/types/params.pb.go @@ -54,6 +54,12 @@ type Params struct { // the uptime of those incentives as well (i.e. distributions through volume // splitting incentives will use this uptime). InternalUptime time.Duration `protobuf:"bytes,4,opt,name=internal_uptime,json=internalUptime,proto3,stdduration" json:"internal_uptime" yaml:"internal_uptime"` + // min_value_for_distribution is the minimum amount a token must be worth + // in order to be eligible for distribution. If the token is worth + // less than this amount (or the route between the two denoms is not + // registered), it will not be distributed and is forfeited to the remaining + // distributees that are eligible. + MinValueForDistribution types.Coin `protobuf:"bytes,5,opt,name=min_value_for_distribution,json=minValueForDistribution,proto3" json:"min_value_for_distribution"` } func (m *Params) Reset() { *m = Params{} } @@ -117,6 +123,13 @@ func (m *Params) GetInternalUptime() time.Duration { return 0 } +func (m *Params) GetMinValueForDistribution() types.Coin { + if m != nil { + return m.MinValueForDistribution + } + return types.Coin{} +} + func init() { proto.RegisterType((*Params)(nil), "osmosis.incentives.Params") } @@ -124,35 +137,37 @@ func init() { func init() { proto.RegisterFile("osmosis/incentives/params.proto", fileDescriptor_1cc8b460d089f845) } var fileDescriptor_1cc8b460d089f845 = []byte{ - // 433 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x52, 0xc1, 0x8a, 0xd4, 0x40, - 0x10, 0x9d, 0x38, 0xb2, 0xb0, 0x11, 0x54, 0xc2, 0xb2, 0x8c, 0x8b, 0x76, 0xc6, 0x80, 0x30, 0x1e, - 0xb6, 0xdb, 0xdd, 0x05, 0x0f, 0x1e, 0x33, 0x2a, 0x78, 0x5b, 0x06, 0x64, 0xc1, 0x4b, 0xe8, 0x24, - 0x95, 0x4c, 0x63, 0x92, 0x0a, 0xdd, 0x9d, 0xd1, 0xf9, 0x0b, 0x8f, 0x7e, 0x83, 0x77, 0xff, 0x61, - 0x8f, 0x7b, 0xf4, 0x94, 0x95, 0x99, 0x3f, 0x98, 0x2f, 0x90, 0x74, 0x27, 0x3a, 0x88, 0x78, 0x4a, - 0xfa, 0xbd, 0x57, 0xbc, 0x7a, 0x55, 0xe5, 0xfa, 0xa8, 0x4a, 0x54, 0x42, 0x31, 0x51, 0x25, 0x50, - 0x69, 0xb1, 0x02, 0xc5, 0x6a, 0x2e, 0x79, 0xa9, 0x68, 0x2d, 0x51, 0xa3, 0xe7, 0xf5, 0x02, 0xfa, - 0x47, 0x70, 0x72, 0x94, 0x63, 0x8e, 0x86, 0x66, 0xdd, 0x9f, 0x55, 0x9e, 0x90, 0xc4, 0x48, 0x59, - 0xcc, 0x15, 0xb0, 0xd5, 0x59, 0x0c, 0x9a, 0x9f, 0xb1, 0x04, 0x45, 0x35, 0xf0, 0x39, 0x62, 0x5e, - 0x00, 0x33, 0xaf, 0xb8, 0xc9, 0x58, 0xda, 0x48, 0xae, 0x05, 0xf6, 0x7c, 0xf0, 0x7d, 0xec, 0x1e, - 0x5c, 0x1a, 0x6b, 0xef, 0xca, 0x3d, 0x4e, 0x85, 0xd2, 0x32, 0x82, 0x1a, 0x93, 0x65, 0x24, 0xd2, - 0xce, 0x39, 0x13, 0x20, 0x27, 0xce, 0xd4, 0x99, 0x1d, 0x86, 0x4f, 0x77, 0xad, 0xff, 0x64, 0xcd, - 0xcb, 0xe2, 0x55, 0xf0, 0x6f, 0x5d, 0xb0, 0x38, 0x32, 0xc4, 0x9b, 0x0e, 0x7f, 0xf7, 0x1b, 0xf6, - 0xd6, 0xae, 0x97, 0x4b, 0x6c, 0xea, 0x28, 0x91, 0x60, 0xbc, 0xa3, 0x0c, 0x60, 0x72, 0x67, 0x3a, - 0x9e, 0xdd, 0x3b, 0x7f, 0x44, 0x6d, 0x00, 0xda, 0x05, 0xa0, 0x7d, 0x00, 0x3a, 0x47, 0x51, 0x85, - 0x2f, 0xae, 0x5b, 0x7f, 0xf4, 0xed, 0xd6, 0x9f, 0xe5, 0x42, 0x2f, 0x9b, 0x98, 0x26, 0x58, 0xb2, - 0x3e, 0xad, 0xfd, 0x9c, 0xaa, 0xf4, 0x23, 0xd3, 0xeb, 0x1a, 0x94, 0x29, 0x50, 0x8b, 0x87, 0xc6, - 0x66, 0xde, 0xbb, 0xbc, 0x05, 0xf0, 0xd0, 0x25, 0x4d, 0x25, 0x41, 0x69, 0x29, 0x12, 0x0d, 0xa9, - 0xed, 0x00, 0x65, 0xf4, 0x69, 0x29, 0x34, 0x14, 0x42, 0xe9, 0xc9, 0x78, 0x3a, 0x9e, 0x1d, 0x86, - 0xcf, 0x77, 0xad, 0xff, 0xcc, 0x66, 0xfb, 0xbf, 0x3e, 0x58, 0x3c, 0xde, 0x17, 0xcc, 0x2d, 0x7f, - 0x35, 0xd0, 0x5e, 0xe6, 0x3e, 0x10, 0x95, 0x06, 0x59, 0xf1, 0x22, 0x6a, 0x6a, 0x2d, 0x4a, 0x98, - 0xdc, 0x9d, 0x3a, 0x26, 0xa8, 0xdd, 0x04, 0x1d, 0x36, 0x41, 0x5f, 0xf7, 0x9b, 0x08, 0x83, 0x2e, - 0xe8, 0xae, 0xf5, 0x8f, 0x6d, 0x03, 0x7f, 0xd5, 0x07, 0x5f, 0x6f, 0x7d, 0x67, 0x71, 0x7f, 0x40, - 0xdf, 0x1b, 0x30, 0xbc, 0xbc, 0xde, 0x10, 0xe7, 0x66, 0x43, 0x9c, 0x9f, 0x1b, 0xe2, 0x7c, 0xd9, - 0x92, 0xd1, 0xcd, 0x96, 0x8c, 0x7e, 0x6c, 0xc9, 0xe8, 0xc3, 0xcb, 0xbd, 0x71, 0xf5, 0x67, 0x74, - 0x5a, 0xf0, 0x58, 0x0d, 0x0f, 0xb6, 0x3a, 0xbf, 0x60, 0x9f, 0xf7, 0x4f, 0xcf, 0x8c, 0x30, 0x3e, - 0x30, 0x8d, 0x5d, 0xfc, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x8b, 0x38, 0x06, 0x4f, 0x9d, 0x02, 0x00, - 0x00, + // 475 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x52, 0xdd, 0x6a, 0xd4, 0x40, + 0x18, 0xdd, 0xb8, 0x6b, 0xa1, 0x11, 0x54, 0x42, 0xa9, 0x71, 0xd1, 0x64, 0x0d, 0x08, 0xeb, 0x45, + 0x33, 0xb6, 0x05, 0x2f, 0xbc, 0xcc, 0xd6, 0x82, 0x77, 0x25, 0xa0, 0x05, 0x11, 0x42, 0x7e, 0xbe, + 0x64, 0x3f, 0x4c, 0xf2, 0x85, 0x99, 0xc9, 0xea, 0xbe, 0x85, 0xe0, 0x8d, 0xcf, 0xe0, 0x93, 0xf4, + 0xb2, 0x97, 0x5e, 0x6d, 0x65, 0xf7, 0x0d, 0xf6, 0x09, 0x24, 0x93, 0x44, 0x17, 0x51, 0xaf, 0x92, + 0x39, 0xe7, 0x7c, 0x73, 0xe6, 0xcc, 0x19, 0xdd, 0x26, 0x51, 0x90, 0x40, 0xc1, 0xb0, 0x8c, 0xa1, + 0x94, 0xb8, 0x00, 0xc1, 0xaa, 0x90, 0x87, 0x85, 0x70, 0x2b, 0x4e, 0x92, 0x0c, 0xa3, 0x13, 0xb8, + 0xbf, 0x05, 0xe3, 0x83, 0x8c, 0x32, 0x52, 0x34, 0x6b, 0xfe, 0x5a, 0xe5, 0xd8, 0x8a, 0x95, 0x94, + 0x45, 0xa1, 0x00, 0xb6, 0x38, 0x8e, 0x40, 0x86, 0xc7, 0x2c, 0x26, 0x2c, 0x7b, 0x3e, 0x23, 0xca, + 0x72, 0x60, 0x6a, 0x15, 0xd5, 0x29, 0x4b, 0x6a, 0x1e, 0x4a, 0xa4, 0x8e, 0x77, 0xbe, 0x8c, 0xf4, + 0xbd, 0x0b, 0x65, 0x6d, 0x5c, 0xea, 0x87, 0x09, 0x0a, 0xc9, 0x03, 0xa8, 0x28, 0x9e, 0x07, 0x98, + 0x34, 0xce, 0x29, 0x02, 0x37, 0xb5, 0x89, 0x36, 0xdd, 0xf7, 0x9e, 0x6c, 0x57, 0xf6, 0xe3, 0x65, + 0x58, 0xe4, 0x2f, 0x9d, 0xbf, 0xeb, 0x1c, 0xff, 0x40, 0x11, 0xaf, 0x1a, 0xfc, 0xf5, 0x2f, 0xd8, + 0x58, 0xea, 0x46, 0xc6, 0xa9, 0xae, 0x82, 0x98, 0x83, 0xf2, 0x0e, 0x52, 0x00, 0xf3, 0xd6, 0x64, + 0x38, 0xbd, 0x73, 0xf2, 0xd0, 0x6d, 0x03, 0xb8, 0x4d, 0x00, 0xb7, 0x0b, 0xe0, 0xce, 0x08, 0x4b, + 0xef, 0xf9, 0xd5, 0xca, 0x1e, 0x7c, 0xbb, 0xb1, 0xa7, 0x19, 0xca, 0x79, 0x1d, 0xb9, 0x31, 0x15, + 0xac, 0x4b, 0xdb, 0x7e, 0x8e, 0x44, 0xf2, 0x81, 0xc9, 0x65, 0x05, 0x42, 0x0d, 0x08, 0xff, 0xbe, + 0xb2, 0x99, 0x75, 0x2e, 0xe7, 0x00, 0x06, 0xe9, 0x56, 0x5d, 0x72, 0x10, 0x92, 0x63, 0x2c, 0x21, + 0x69, 0x4f, 0x40, 0x3c, 0xf8, 0x38, 0x47, 0x09, 0x39, 0x0a, 0x69, 0x0e, 0x27, 0xc3, 0xe9, 0xbe, + 0xf7, 0x6c, 0xbb, 0xb2, 0x9f, 0xb6, 0xd9, 0xfe, 0xaf, 0x77, 0xfc, 0x47, 0xbb, 0x82, 0x59, 0xcb, + 0x5f, 0xf6, 0xb4, 0x91, 0xea, 0xf7, 0xb0, 0x94, 0xc0, 0xcb, 0x30, 0x0f, 0xea, 0x4a, 0x62, 0x01, + 0xe6, 0x68, 0xa2, 0xa9, 0xa0, 0x6d, 0x13, 0x6e, 0xdf, 0x84, 0x7b, 0xd6, 0x35, 0xe1, 0x39, 0x4d, + 0xd0, 0xed, 0xca, 0x3e, 0x6c, 0x0f, 0xf0, 0xc7, 0xbc, 0xf3, 0xf5, 0xc6, 0xd6, 0xfc, 0xbb, 0x3d, + 0xfa, 0x46, 0x81, 0xc6, 0x7b, 0x7d, 0x5c, 0x60, 0x19, 0x2c, 0xc2, 0xbc, 0x86, 0x20, 0x25, 0x1e, + 0xa8, 0x9b, 0xc7, 0xa8, 0x6e, 0x76, 0x34, 0x6f, 0x77, 0x96, 0xff, 0xbc, 0xdb, 0x51, 0x63, 0xe9, + 0x3f, 0x28, 0xb0, 0x7c, 0xdb, 0xec, 0x70, 0x4e, 0xfc, 0x6c, 0x67, 0xde, 0xbb, 0xb8, 0x5a, 0x5b, + 0xda, 0xf5, 0xda, 0xd2, 0x7e, 0xac, 0x2d, 0xed, 0xf3, 0xc6, 0x1a, 0x5c, 0x6f, 0xac, 0xc1, 0xf7, + 0x8d, 0x35, 0x78, 0xf7, 0x62, 0xa7, 0x8c, 0xee, 0x91, 0x1e, 0xe5, 0x61, 0x24, 0xfa, 0x05, 0x5b, + 0x9c, 0x9c, 0xb2, 0x4f, 0xbb, 0x0f, 0x5b, 0x15, 0x14, 0xed, 0xa9, 0xd8, 0xa7, 0x3f, 0x03, 0x00, + 0x00, 0xff, 0xff, 0x0a, 0x2b, 0xf1, 0x4a, 0xfb, 0x02, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -175,12 +190,22 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - n1, err1 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.InternalUptime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.InternalUptime):]) - if err1 != nil { - return 0, err1 + { + size, err := m.MinValueForDistribution.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + n2, err2 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.InternalUptime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.InternalUptime):]) + if err2 != nil { + return 0, err2 } - i -= n1 - i = encodeVarintParams(dAtA, i, uint64(n1)) + i -= n2 + i = encodeVarintParams(dAtA, i, uint64(n2)) i-- dAtA[i] = 0x22 if len(m.UnrestrictedCreatorWhitelist) > 0 { @@ -251,6 +276,8 @@ func (m *Params) Size() (n int) { } l = github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.InternalUptime) n += 1 + l + sovParams(uint64(l)) + l = m.MinValueForDistribution.Size() + n += 1 + l + sovParams(uint64(l)) return n } @@ -420,6 +447,39 @@ func (m *Params) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MinValueForDistribution", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MinValueForDistribution.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/superfluid/keeper/keeper_test.go b/x/superfluid/keeper/keeper_test.go index 6bb7ecc8f07..8749acd8dfe 100644 --- a/x/superfluid/keeper/keeper_test.go +++ b/x/superfluid/keeper/keeper_test.go @@ -14,6 +14,7 @@ import ( "github.com/osmosis-labs/osmosis/v23/app/apptesting" "github.com/osmosis-labs/osmosis/v23/x/gamm/pool-models/balancer" gammtypes "github.com/osmosis-labs/osmosis/v23/x/gamm/types" + incentivetypes "github.com/osmosis-labs/osmosis/v23/x/incentives/types" lockuptypes "github.com/osmosis-labs/osmosis/v23/x/lockup/types" minttypes "github.com/osmosis-labs/osmosis/v23/x/mint/types" "github.com/osmosis-labs/osmosis/v23/x/superfluid/keeper" @@ -78,6 +79,7 @@ func (s *KeeperTestSuite) SetupTest() { distributionParams.BonusProposerReward = osmomath.ZeroDec() distributionParams.CommunityTax = osmomath.ZeroDec() s.App.DistrKeeper.SetParams(s.Ctx, distributionParams) + s.App.IncentivesKeeper.SetParam(s.Ctx, incentivetypes.KeyMinValueForDistr, sdk.NewCoin("stake", osmomath.NewInt(1))) } func (s *KeeperTestSuite) SetupDefaultPool() {