Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(v18: feat) Volume-Split, setup gauges to split evenly #6085

Merged
merged 7 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#5948](https://github.com/osmosis-labs/osmosis/pull/5948) Parameterizing Pool Type Information in Protorev
* [#6001](https://github.com/osmosis-labs/osmosis/pull/6001) feat: improve set-env CLI cmd
* [#6012](https://github.com/osmosis-labs/osmosis/pull/6012) chore: add autocomplete to makefile
* [#6085](https://github.com/osmosis-labs/osmosis/pull/6085) (v18: feat) Volume-Split, setup gauges to split evenly

### Minor improvements & Bug Fixes

Expand Down
17 changes: 17 additions & 0 deletions proto/osmosis/incentives/gauge.proto
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,23 @@ message Gauge {
];
}

// SplittingPolicy determines the way we want to split incentives in groupGauges
enum SplittingPolicy {
option (gogoproto.goproto_enum_prefix) = false;

Volume = 0;
Evenly = 1;
}

// Gauge is an object that stores GroupGaugeId as well as internalGaugeIds. We
// linked these two together so that we can distribute tokens from groupGauge to
// internalGauges.
message GroupGauge {
uint64 group_gauge_id = 1;
repeated uint64 internal_ids = 2;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should include a splitting policy field here as an enum, even if it currently just evenly splits

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SplittingPolicy splitting_policy = 3;
}

message LockableDurationsInfo {
// List of incentivised durations that gauges will pay out to
repeated google.protobuf.Duration lockable_durations = 1 [
Expand Down
1 change: 1 addition & 0 deletions proto/osmosis/lockup/lock.proto
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ enum LockQueryType {
ByDuration = 0;
ByTime = 1;
NoLock = 2;
ByGroup = 3;
}

// QueryCondition is a struct used for querying locks upon different conditions.
Expand Down
72 changes: 70 additions & 2 deletions x/incentives/keeper/distribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,70 @@ func (k Keeper) distributeSyntheticInternal(
return k.distributeInternal(ctx, gauge, sortedAndTrimmedQualifiedLocks, distrInfo)
}

// AllocateAcrossGauges gets all the active groupGauges and distributes tokens evenly based on the internalGauges set for that
// groupGauge. After each iteration we update the groupGauge by modifying filledEpoch and distributed coins.
func (k Keeper) AllocateAcrossGauges(ctx sdk.Context) error {
currTime := ctx.BlockTime()

groupGauges, err := k.GetAllGroupGauges(ctx)
if err != nil {
return err
}

for _, groupGauge := range groupGauges {
gauge, err := k.GetGaugeByID(ctx, groupGauge.GroupGaugeId)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err what are we actually getting here? Don't we already have the group gauge?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetAllGroupGauges returns the ids for ex: {GroupGaugeId, InternalIds and Splitting policy}, we still have to fetch the actual gauge to get its fields

if err != nil {
return err
}

// only allow distribution if the GroupGauge is Active
if gauge.IsActiveGauge(currTime) {
coinsToDistributePerInternalGauge, coinsToDistributeThisEpoch, err := k.calcSplitPolicyCoins(ctx, groupGauge.SplittingPolicy, gauge, groupGauge)
if err != nil {
return err
}

for _, internalGaugeId := range groupGauge.InternalIds {
err = k.AddToGaugeRewardsFromGauge(ctx, groupGauge.GroupGaugeId, coinsToDistributePerInternalGauge, internalGaugeId)
if err != nil {
return err
}
}

// we distribute tokens from groupGauge to internal gauge therefore update groupGauge fields
// updates filledEpoch and distributedCoins
k.updateGaugePostDistribute(ctx, *gauge, coinsToDistributeThisEpoch)
}
}

return nil
}

// calcSplitPolicyCoins calculates tokens to split given a policy and groupGauge.
// TODO: add volume split policy
func (k Keeper) calcSplitPolicyCoins(ctx sdk.Context, policy types.SplittingPolicy, groupGauge *types.Gauge, groupGaugeObj types.GroupGauge) (sdk.Coins, sdk.Coins, error) {
if policy == types.Evenly {
remainCoins := groupGauge.Coins.Sub(groupGauge.DistributedCoins)

var coinsDistPerInternalGauge, coinsDistThisEpoch sdk.Coins
for _, coin := range remainCoins {
epochDiff := groupGauge.NumEpochsPaidOver - groupGauge.FilledEpochs
internalGaugeLen := len(groupGaugeObj.InternalIds)

distPerEpoch := coin.Amount.Quo(sdk.NewIntFromUint64(epochDiff))
distPerGauge := distPerEpoch.Quo(sdk.NewInt(int64(internalGaugeLen)))

coinsDistThisEpoch = coinsDistThisEpoch.Add(sdk.NewCoin(coin.Denom, distPerEpoch))
coinsDistPerInternalGauge = coinsDistPerInternalGauge.Add(sdk.NewCoin(coin.Denom, distPerGauge))
}

return coinsDistPerInternalGauge, coinsDistThisEpoch, nil
} else {
return nil, nil, fmt.Errorf("GroupGauge id %d doesnot have enought coins to distribute.", &groupGauge.Id)
}

}

// distributeInternal runs the distribution logic for a gauge, and adds the sends to
// the distrInfo struct. It also updates the gauge for the distribution.
// It handles any kind of gauges:
Expand All @@ -285,6 +349,7 @@ func (k Keeper) distributeInternal(
totalDistrCoins := sdk.NewCoins()

remainCoins := gauge.Coins.Sub(gauge.DistributedCoins)

// if its a perpetual gauge, we set remaining epochs to 1.
// otherwise is is a non perpetual gauge and we determine how many epoch payouts are left
remainEpochs := uint64(1)
Expand Down Expand Up @@ -329,7 +394,6 @@ func (k Keeper) distributeInternal(
// for ex: 10000uosmo to be distributed over 1day epoch will be 1000 tokens ÷ 86,400 seconds ≈ 0.01157 tokens per second (truncated)
// Note: reason why we do millisecond conversion is because floats are non-deterministic.
emissionRate := sdk.NewDecFromInt(remainAmountPerEpoch).QuoTruncate(sdk.NewDec(currentEpoch.Duration.Milliseconds()).QuoInt(sdk.NewInt(1000)))

ctx.Logger().Debug("distributeInternal, CreateIncentiveRecord NoLock gauge", "module", types.ModuleName, "gaugeId", gauge.Id, "poolId", pool.GetId(), "remainCoinPerEpoch", remainCoinPerEpoch, "height", ctx.BlockHeight())
_, err := k.clk.CreateIncentive(ctx,
pool.GetId(),
Expand All @@ -346,7 +410,6 @@ func (k Keeper) distributeInternal(
if err != nil {
return nil, err
}

totalDistrCoins = totalDistrCoins.Add(remainCoinPerEpoch)
}
} else {
Expand Down Expand Up @@ -441,6 +504,11 @@ func (k Keeper) Distribute(ctx sdk.Context, gauges []types.Gauge) (sdk.Coins, er
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)
} else {
// Do not distribue 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)
}
if err != nil {
Expand Down
Loading