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 5 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
18 changes: 18 additions & 0 deletions proto/osmosis/incentives/gauge.proto
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ message Gauge {
];
}

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

Volume = 0;
Liquidity = 1;
Evenly = 2;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Liquidity = 1;
Evenly = 2;
Evenly = 1;

Copy link
Contributor

Choose a reason for hiding this comment

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

We're likely to simply remove liquidity based splitting for the reasons described in #6093

}

// 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
73 changes: 71 additions & 2 deletions x/incentives/keeper/distribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,71 @@ 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.
// TODO: Replace even split by volume split once its implemented.
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 !currTime.Before(gauge.StartTime) && (gauge.IsPerpetual || gauge.FilledEpochs < gauge.NumEpochsPaidOver) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: for general readability, I think doing something like "if currTime.After(startTime) && (!gaugeIsActive bool), then continue" would be cleaner instead of having half the function in an if branch.

Ofc feel free to ignore this if you think the current approach is more readable (hence the nit) – my thinking is just that you need to keep track of fewer things mentally when you're not in a branch

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yup i think we can just replace this with
gauge.IsActiveGauge(currTime) as well. Looks cleaner

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.
func (k Keeper) CalcSplitPolicyCoins(ctx sdk.Context, policy types.SplittingPolicy, groupGauge *types.Gauge, groupGaugeObj types.GroupGauge) (sdk.Coins, sdk.Coins, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this should probably be a private function

// TODO: add volume split policy
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 +350,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 +395,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 +411,6 @@ func (k Keeper) distributeInternal(
if err != nil {
return nil, err
}

totalDistrCoins = totalDistrCoins.Add(remainCoinPerEpoch)
}
} else {
Expand Down Expand Up @@ -441,6 +505,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 {
// Donot distribue if LockQueryType = Group, because if we distribute here we will be double distributing.
stackman27 marked this conversation as resolved.
Show resolved Hide resolved
if gauge.DistributeTo.LockQueryType == lockuptypes.ByGroup {
continue
}

gaugeDistributedCoins, err = k.distributeInternal(ctx, gauge, filteredLocks, &distrInfo)
}
if err != nil {
Expand Down
Loading