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

feat: support multiple coins in bundles module #184

Merged
merged 7 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 2 additions & 0 deletions app/upgrades/v1_5/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ func CreateUpgradeHandler(mm *module.Manager, configurator module.Configurator,

// TODO: migrate delegation outstanding rewards

// TODO: migrate network fee and whitelist weights

troykessler marked this conversation as resolved.
Show resolved Hide resolved
return mm.RunMigrations(ctx, configurator, fromVM)
}
}
Expand Down
47 changes: 38 additions & 9 deletions proto/kyve/bundles/v1beta1/events.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ syntax = "proto3";

package kyve.bundles.v1beta1;

import "amino/amino.proto";
import "cosmos/base/v1beta1/coin.proto";
import "gogoproto/gogo.proto";
import "kyve/bundles/v1beta1/bundles.proto";
import "kyve/bundles/v1beta1/params.proto";
Expand Down Expand Up @@ -87,24 +89,51 @@ message EventBundleFinalized {
uint64 total = 6;
// status of the finalized bundle
BundleStatus status = 7;
// amount which funders provided to the total bundle reward (in ukyve)
uint64 funders_payout = 8;
// amount which funders provided to the total bundle reward
repeated cosmos.base.v1beta1.Coin funders_payout = 8 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
// amount which the inflation pool provided to the total reward (in ukyve)
uint64 inflation_payout = 9;
// rewards transferred to treasury (in ukyve)
uint64 reward_treasury = 10;
// rewardUploader rewards directly transferred to uploader (in ukyve)
uint64 reward_uploader = 11;
// rewardDelegation rewards distributed among all delegators (in ukyve)
uint64 reward_delegation = 12;
// rewards transferred to treasury
repeated cosmos.base.v1beta1.Coin reward_treasury = 10 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
// reward_uploader_commission are the commission rewards of the uploader.
// if the uploader has no delegations the delegation rewards are included here
repeated cosmos.base.v1beta1.Coin reward_uploader_commission = 11 [
troykessler marked this conversation as resolved.
Show resolved Hide resolved
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
// rewardDelegation rewards distributed among all delegators
repeated cosmos.base.v1beta1.Coin reward_delegation = 12 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
// rewardTotal the total bundle reward
uint64 reward_total = 13;
repeated cosmos.base.v1beta1.Coin reward_total = 13 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
// finalized_at the block height where the bundle got finalized
uint64 finalized_at = 14;
// uploader the address of the uploader of this bundle
string uploader = 15;
// next_uploader the address of the next uploader after this bundle
string next_uploader = 16;
// reward_uploader_storage_cost are the storage cost rewards for the uploader
repeated cosmos.base.v1beta1.Coin reward_uploader_storage_cost = 17 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}

// EventClaimedUploaderRole is an event emitted when an uploader claims the uploader role
Expand Down
4 changes: 2 additions & 2 deletions x/bundles/keeper/keeper_suite_valid_bundles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1076,11 +1076,11 @@ var _ = Describe("valid bundles", Ordered, func() {
balanceVoter := s.GetBalanceFromAddress(valaccountVoter.Staker)
Expect(balanceVoter).To(Equal(initialBalanceStaker1))

// calculate uploader rewards
// calculate uploader rewards
networkFee := s.App().BundlesKeeper.GetNetworkFee(s.Ctx())
whitelist := s.App().FundersKeeper.GetCoinWhitelistMap(s.Ctx())
treasuryReward := uint64(math.LegacyNewDec(int64(pool.InflationShareWeight)).Mul(networkFee).TruncateInt64())
storageReward := uint64(s.App().BundlesKeeper.GetStorageCost(s.Ctx(), storageProviderId).MulInt64(100).TruncateInt64())
storageReward := uint64(s.App().BundlesKeeper.GetStorageCost(s.Ctx(), storageProviderId).Mul(whitelist[globalTypes.Denom].CoinWeight).MulInt64(100).TruncateInt64())
troykessler marked this conversation as resolved.
Show resolved Hide resolved
totalUploaderReward := pool.InflationShareWeight - treasuryReward - storageReward

uploaderPayoutReward := uint64(math.LegacyNewDec(int64(totalUploaderReward)).Mul(uploader.Commission).TruncateInt64())
Expand Down
154 changes: 87 additions & 67 deletions x/bundles/keeper/logic_bundles.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package keeper

import (
"cosmossdk.io/errors"
"cosmossdk.io/math"
globalTypes "github.com/KYVENetwork/chain/x/global/types"
poolTypes "github.com/KYVENetwork/chain/x/pool/types"

Expand Down Expand Up @@ -232,7 +231,7 @@ func (k Keeper) handleNonVoters(ctx sdk.Context, poolId uint64) {

// calculatePayouts calculates the different payouts to treasury, uploader and delegators from the total payout
// the pool module provides for this bundle round
func (k Keeper) calculatePayouts(ctx sdk.Context, poolId uint64, totalPayout uint64) (bundleReward types.BundleReward) {
func (k Keeper) calculatePayouts(ctx sdk.Context, poolId uint64, totalPayout sdk.Coins) (bundleReward types.BundleReward) {
// This method first subtracts the network fee from it
// After that the uploader receives the storage rewards. If the total payout does not cover the
// storage rewards we pay out the remains, the commission and delegation rewards will be empty
Expand All @@ -245,37 +244,61 @@ func (k Keeper) calculatePayouts(ctx sdk.Context, poolId uint64, totalPayout uin
return
}

// Already return if there are no payouts
if totalPayout.IsZero() {
return
}

bundleReward.Total = totalPayout

// calculate share of treasury from total payout
bundleReward.Treasury = uint64(math.LegacyNewDec(int64(totalPayout)).Mul(k.GetNetworkFee(ctx)).TruncateInt64())
treasuryPayout, _ := sdk.NewDecCoinsFromCoins(totalPayout...).MulDec(k.GetNetworkFee(ctx)).TruncateDecimal()
bundleReward.Treasury = treasuryPayout

// subtract treasury payout from total payout, so we can continue splitting up the rewards from here
totalPayout = totalPayout.Sub(bundleReward.Treasury...)
if totalPayout.IsZero() {
return
}

// subtract storage cost from remaining total payout. We split the storage cost between all coins and charge
// the amount per coin. If there is not enough of that coin available we simply charge what is left, so there
// can be the case that the storageRewards are less than what we actually wanted to pay out. This is acceptable
// because this case is very rare, usually the minFundingAmount ensures that there are always enough funds left
// of each coin, and in the case there are not enough the coins are removed and therefore for the next bundle
// we split between the other remaining coins.
whitelist := k.fundersKeeper.GetCoinWhitelistMap(ctx)
wantedStorageRewards := sdk.NewCoins()
storageCostPerCoin := k.GetStorageCost(ctx, bundleProposal.StorageProviderId).MulInt64(int64(bundleProposal.DataSize)).QuoInt64(int64(totalPayout.Len()))

for _, coin := range totalPayout {
amount := storageCostPerCoin.Mul(whitelist[coin.Denom].CoinWeight)
troykessler marked this conversation as resolved.
Show resolved Hide resolved
wantedStorageRewards = wantedStorageRewards.Add(sdk.NewCoin(coin.Denom, amount.TruncateInt()))
}

// calculate wanted storage reward the uploader should receive
storageReward := uint64(k.GetStorageCost(ctx, bundleProposal.StorageProviderId).MulInt64(int64(bundleProposal.DataSize)).TruncateInt64())
bundleReward.UploaderStorageCost = totalPayout.Min(wantedStorageRewards)

// if not even the full storage reward can not be paid out we pay out the remains.
// in this case the uploader will not earn the commission rewards and delegators not
// their delegation rewards because total payout is not high enough
if totalPayout-bundleReward.Treasury < storageReward {
bundleReward.Uploader = totalPayout - bundleReward.Treasury
// the remaining total payout is split between the uploader and his delegators.
totalPayout = totalPayout.Sub(bundleReward.UploaderStorageCost...)
if totalPayout.IsZero() {
return
} else {
bundleReward.Uploader = storageReward
}

// remaining rewards to be split between uploader and its delegators
totalNodeReward := totalPayout - bundleReward.Treasury - bundleReward.Uploader
commission := k.stakerKeeper.GetCommission(ctx, bundleProposal.Uploader)
commissionRewards, _ := sdk.NewDecCoinsFromCoins(totalPayout...).MulDec(commission).TruncateDecimal()
bundleReward.UploaderCommission = commissionRewards

// payout delegators
if k.delegationKeeper.GetDelegationAmount(ctx, bundleProposal.Uploader) > 0 {
commission := k.stakerKeeper.GetCommission(ctx, bundleProposal.Uploader)
commissionRewards := uint64(math.LegacyNewDec(int64(totalNodeReward)).Mul(commission).TruncateInt64())
// the remaining total payout belongs to the delegators
totalPayout = totalPayout.Sub(bundleReward.UploaderCommission...)
if totalPayout.IsZero() {
return
}

bundleReward.Uploader += commissionRewards
bundleReward.Delegation = totalNodeReward - commissionRewards
// if the uploader has no delegators he receives the entire remaining amount
if k.delegationKeeper.GetDelegationAmount(ctx, bundleProposal.Uploader) > 0 {
bundleReward.Delegation = totalPayout
} else {
bundleReward.Uploader += totalNodeReward
bundleReward.Delegation = 0
bundleReward.UploaderCommission = bundleReward.UploaderCommission.Add(totalPayout...)
}

return
Expand Down Expand Up @@ -335,7 +358,7 @@ func (k Keeper) registerBundleProposalFromUploader(ctx sdk.Context, msg *types.M
// finalizeCurrentBundleProposal takes the data of the current evaluated proposal
// and stores it as a finalized proposal. This only happens if the network
// reached quorum on the proposal's validity.
func (k Keeper) finalizeCurrentBundleProposal(ctx sdk.Context, poolId uint64, voteDistribution types.VoteDistribution, fundersPayout uint64, inflationPayout uint64, bundleReward types.BundleReward, nextUploader string) {
func (k Keeper) finalizeCurrentBundleProposal(ctx sdk.Context, poolId uint64, voteDistribution types.VoteDistribution, fundersPayout sdk.Coins, inflationPayout uint64, bundleReward types.BundleReward, nextUploader string) {
pool, _ := k.poolKeeper.GetPool(ctx, poolId)
bundleProposal, _ := k.GetBundleProposal(ctx, poolId)

Expand Down Expand Up @@ -367,22 +390,23 @@ func (k Keeper) finalizeCurrentBundleProposal(ctx sdk.Context, poolId uint64, vo
k.SetFinalizedBundle(ctx, finalizedBundle)

_ = ctx.EventManager().EmitTypedEvent(&types.EventBundleFinalized{
PoolId: finalizedBundle.PoolId,
Id: finalizedBundle.Id,
Valid: voteDistribution.Valid,
Invalid: voteDistribution.Invalid,
Abstain: voteDistribution.Abstain,
Total: voteDistribution.Total,
Status: voteDistribution.Status,
FundersPayout: fundersPayout,
InflationPayout: inflationPayout,
RewardTreasury: bundleReward.Treasury,
RewardUploader: bundleReward.Uploader,
RewardDelegation: bundleReward.Delegation,
RewardTotal: bundleReward.Total,
FinalizedAt: uint64(ctx.BlockTime().Unix()),
Uploader: bundleProposal.Uploader,
NextUploader: nextUploader,
PoolId: finalizedBundle.PoolId,
Id: finalizedBundle.Id,
Valid: voteDistribution.Valid,
Invalid: voteDistribution.Invalid,
Abstain: voteDistribution.Abstain,
Total: voteDistribution.Total,
Status: voteDistribution.Status,
FundersPayout: fundersPayout,
InflationPayout: inflationPayout,
RewardTreasury: bundleReward.Treasury,
RewardUploaderCommission: bundleReward.UploaderCommission,
RewardUploaderStorageCost: bundleReward.UploaderStorageCost,
RewardDelegation: bundleReward.Delegation,
RewardTotal: bundleReward.Total,
FinalizedAt: uint64(ctx.BlockTime().Unix()),
Uploader: bundleProposal.Uploader,
NextUploader: nextUploader,
})

// Finalize the proposal, saving useful information.
Expand All @@ -398,19 +422,15 @@ func (k Keeper) dropCurrentBundleProposal(ctx sdk.Context, poolId uint64, voteDi
bundleProposal, _ := k.GetBundleProposal(ctx, poolId)

_ = ctx.EventManager().EmitTypedEvent(&types.EventBundleFinalized{
PoolId: pool.Id,
Id: pool.TotalBundles,
Valid: voteDistribution.Valid,
Invalid: voteDistribution.Invalid,
Abstain: voteDistribution.Abstain,
Total: voteDistribution.Total,
Status: voteDistribution.Status,
RewardTreasury: 0,
RewardUploader: 0,
RewardDelegation: 0,
RewardTotal: 0,
FinalizedAt: uint64(ctx.BlockTime().Unix()),
Uploader: bundleProposal.Uploader,
PoolId: pool.Id,
Id: pool.TotalBundles,
Valid: voteDistribution.Valid,
Invalid: voteDistribution.Invalid,
Abstain: voteDistribution.Abstain,
Total: voteDistribution.Total,
Status: voteDistribution.Status,
FinalizedAt: uint64(ctx.BlockTime().Unix()),
Uploader: bundleProposal.Uploader,
})

// drop bundle
Expand Down Expand Up @@ -533,39 +553,39 @@ func (k Keeper) tallyBundleProposal(ctx sdk.Context, bundleProposal types.Bundle
switch voteDistribution.Status {
case types.BUNDLE_STATUS_VALID:
// charge the funders of the pool
fundersPayouts, err := k.fundersKeeper.ChargeFundersOfPool(ctx, poolId, poolTypes.ModuleName)
fundersPayout, err := k.fundersKeeper.ChargeFundersOfPool(ctx, poolId, poolTypes.ModuleName)
if err != nil {
return types.TallyResult{}, err
}

// TODO: support all coins in separate PR
fundersPayout := uint64(0)
if found, payout := fundersPayouts.Find(globalTypes.Denom); found {
fundersPayout = payout.Amount.Uint64()
}

// charge the inflation pool
inflationPayout, err := k.poolKeeper.ChargeInflationPool(ctx, poolId)
if err != nil {
return types.TallyResult{}, err
}

// calculate payouts to the different stakeholders like treasury, uploader and delegators
bundleReward := k.calculatePayouts(ctx, poolId, fundersPayout+inflationPayout)
// combine funders payout with inflation payout to calculate the rewards for the different stakeholders
// like treasury, uploader and delegators
bundleReward := k.calculatePayouts(ctx, poolId, fundersPayout.Add(sdk.NewInt64Coin(globalTypes.Denom, int64(inflationPayout))))
troykessler marked this conversation as resolved.
Show resolved Hide resolved

// payout rewards to treasury
if err := util.TransferFromModuleToTreasury(k.accountKeeper, k.distrkeeper, ctx, poolTypes.ModuleName, bundleReward.Treasury); err != nil {
if err := k.distrkeeper.FundCommunityPool(ctx, bundleReward.Treasury, k.accountKeeper.GetModuleAddress(poolTypes.ModuleName)); err != nil {
return types.TallyResult{}, err
}

// payout rewards to uploader through commission rewards
if err := k.stakerKeeper.IncreaseStakerCommissionRewards(ctx, bundleProposal.Uploader, bundleReward.Uploader); err != nil {
uploaderReward := uint64(0)
if found, uploaderRewardCoin := bundleReward.UploaderCommission.Add(bundleReward.UploaderStorageCost...).Find(globalTypes.Denom); found {
uploaderReward = uploaderRewardCoin.Amount.Uint64()
}

// TODO: payout all rewards to uploader in separate PR
if err := k.stakerKeeper.IncreaseStakerCommissionRewards(ctx, bundleProposal.Uploader, uploaderReward); err != nil {
return types.TallyResult{}, err
}

// payout rewards to delegators through delegation rewards
bundleRewardCoins := sdk.NewCoins(sdk.NewInt64Coin(globalTypes.Denom, int64(bundleReward.Delegation)))
if err := k.delegationKeeper.PayoutRewards(ctx, bundleProposal.Uploader, bundleRewardCoins, poolTypes.ModuleName); err != nil {
if err := k.delegationKeeper.PayoutRewards(ctx, bundleProposal.Uploader, bundleReward.Delegation, poolTypes.ModuleName); err != nil {
return types.TallyResult{}, err
}

Expand Down Expand Up @@ -599,7 +619,7 @@ func (k Keeper) tallyBundleProposal(ctx sdk.Context, bundleProposal types.Bundle
return types.TallyResult{
Status: types.TallyResultInvalid,
VoteDistribution: voteDistribution,
FundersPayout: 0,
FundersPayout: sdk.NewCoins(),
InflationPayout: 0,
BundleReward: types.BundleReward{},
}, nil
Expand All @@ -608,7 +628,7 @@ func (k Keeper) tallyBundleProposal(ctx sdk.Context, bundleProposal types.Bundle
return types.TallyResult{
Status: types.TallyResultNoQuorum,
VoteDistribution: voteDistribution,
FundersPayout: 0,
FundersPayout: sdk.NewCoins(),
InflationPayout: 0,
BundleReward: types.BundleReward{},
}, nil
Expand Down
Loading
Loading