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 denoms for delegation rewards #176

Merged
merged 10 commits into from
May 3, 2024
2 changes: 2 additions & 0 deletions app/upgrades/v1_5/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ func CreateUpgradeHandler(mm *module.Manager, configurator module.Configurator,

// TODO: migrate gov params

// TODO: migrate delegation outstanding rewards

return mm.RunMigrations(ctx, configurator, fromVM)
}
}
Expand Down
86 changes: 72 additions & 14 deletions docs/static/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,22 @@ paths:
format: uint64
title: protocol_delegation_unbonding
protocol_rewards:
type: string
format: uint64
type: array
items:
type: object
properties:
denom:
type: string
amount:
type: string
description: >-
Coin defines a token with a denomination and an amount.


NOTE: The amount field is an Int which implements the custom
method

signatures required by gogoproto.
description: protocol_rewards ...
protocol_funding:
type: string
Expand Down Expand Up @@ -3184,10 +3198,24 @@ paths:
delegator:
type: string
description: delegator ...
current_reward:
type: string
format: uint64
description: current_reward ...
current_rewards:
type: array
items:
type: object
properties:
denom:
type: string
amount:
type: string
description: >-
Coin defines a token with a denomination and an amount.


NOTE: The amount field is an Int which implements the
custom method

signatures required by gogoproto.
description: current_rewards ...
delegation_amount:
type: string
format: uint64
Expand Down Expand Up @@ -3424,10 +3452,25 @@ paths:
delegator:
type: string
description: delegator ...
current_reward:
type: string
format: uint64
description: current_reward ...
current_rewards:
type: array
items:
type: object
properties:
denom:
type: string
amount:
type: string
description: >-
Coin defines a token with a denomination and an
amount.


NOTE: The amount field is an Int which implements the
custom method

signatures required by gogoproto.
description: current_rewards ...
delegation_amount:
type: string
format: uint64
Expand Down Expand Up @@ -3977,10 +4020,25 @@ paths:

It contains almost all needed information for a
convenient usage
current_reward:
type: string
format: uint64
description: current_reward ...
current_rewards:
type: array
items:
type: object
properties:
denom:
type: string
amount:
type: string
description: >-
Coin defines a token with a denomination and an
amount.


NOTE: The amount field is an Int which implements the
custom method

signatures required by gogoproto.
description: current_rewards ...
delegation_amount:
type: string
format: uint64
Expand Down
15 changes: 11 additions & 4 deletions proto/kyve/delegation/v1beta1/delegation.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ syntax = "proto3";

package kyve.delegation.v1beta1;

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

option go_package = "github.com/KYVENetwork/chain/x/delegation/types";
Expand Down Expand Up @@ -31,9 +33,10 @@ message DelegationEntry {
uint64 k_index = 2;

// value is the quotient of collected rewards and total stake according to F1-distribution
string value = 3 [
(gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
(gogoproto.nullable) = false
repeated cosmos.base.v1beta1.DecCoin value = 3 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins"
];
}

Expand All @@ -47,7 +50,11 @@ message DelegationData {
// F1Distribution

// current_rewards ...
uint64 current_rewards = 2;
repeated cosmos.base.v1beta1.Coin current_rewards = 2 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
// total_delegation ...
uint64 total_delegation = 3;
// latest_index_k ...
Expand Down
8 changes: 7 additions & 1 deletion proto/kyve/delegation/v1beta1/events.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ syntax = "proto3";

package kyve.delegation.v1beta1;

import "amino/amino.proto";
import "cosmos/base/v1beta1/coin.proto";
import "gogoproto/gogo.proto";
import "kyve/delegation/v1beta1/delegation.proto";
import "kyve/delegation/v1beta1/params.proto";
Expand Down Expand Up @@ -78,7 +80,11 @@ message EventWithdrawRewards {
// staker is the account address of the protocol node the users withdraws from.
string staker = 2;
// amount ...
uint64 amount = 3;
repeated cosmos.base.v1beta1.Coin amount = 3 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}

// EventSlash is an event emitted when a protocol node is slashed.
Expand Down
8 changes: 7 additions & 1 deletion proto/kyve/query/v1beta1/account.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ syntax = "proto3";

package kyve.query.v1beta1;

import "amino/amino.proto";
import "cosmos/base/query/v1beta1/pagination.proto";
import "cosmos/base/v1beta1/coin.proto";
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "kyve/query/v1beta1/query.proto";
Expand Down Expand Up @@ -55,7 +57,11 @@ message QueryAccountAssetsResponse {
// protocol_delegation_unbonding
uint64 protocol_delegation_unbonding = 5;
// protocol_rewards ...
uint64 protocol_rewards = 6;
repeated cosmos.base.v1beta1.Coin protocol_rewards = 6 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
// protocol_funding ...
uint64 protocol_funding = 7;
}
Expand Down
18 changes: 14 additions & 4 deletions proto/kyve/query/v1beta1/delegation.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ syntax = "proto3";

package kyve.query.v1beta1;

import "amino/amino.proto";
import "cosmos/base/query/v1beta1/pagination.proto";
import "cosmos/base/v1beta1/coin.proto";
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "kyve/query/v1beta1/query.proto";
Expand Down Expand Up @@ -51,8 +53,12 @@ message QueryDelegatorResponse {
message StakerDelegatorResponse {
// delegator ...
string delegator = 1;
// current_reward ...
uint64 current_reward = 2;
// current_rewards ...
repeated cosmos.base.v1beta1.Coin current_rewards = 6 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
// delegation_amount ...
uint64 delegation_amount = 3;
// staker ...
Expand Down Expand Up @@ -109,8 +115,12 @@ message QueryStakersByDelegatorResponse {
message DelegationForStakerResponse {
// staker ...
FullStaker staker = 1;
// current_reward ...
uint64 current_reward = 2;
// current_rewards ...
repeated cosmos.base.v1beta1.Coin current_rewards = 6 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
// delegation_amount ...
uint64 delegation_amount = 3;
}
28 changes: 18 additions & 10 deletions testutil/integration/checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"

"cosmossdk.io/store"
storeTypes "cosmossdk.io/store/types"

Expand Down Expand Up @@ -328,7 +330,7 @@ func (suite *KeeperTestSuite) VerifyDelegationQueries() {
Expect(resD.Delegator.Delegator).To(Equal(delegator.Delegator))
Expect(resD.Delegator.Staker).To(Equal(delegator.Staker))
Expect(resD.Delegator.DelegationAmount).To(Equal(suite.App().DelegationKeeper.GetDelegationAmountOfDelegator(suite.Ctx(), delegator.Staker, delegator.Delegator)))
Expect(resD.Delegator.CurrentReward).To(Equal(suite.App().DelegationKeeper.GetOutstandingRewards(suite.Ctx(), delegator.Staker, delegator.Delegator)))
Expect(resD.Delegator.CurrentRewards.String()).To(Equal(suite.App().DelegationKeeper.GetOutstandingRewards(suite.Ctx(), delegator.Staker, delegator.Delegator).String()))

// Query: stakers_by_delegator/{delegator}
resSbD, errSbD := suite.App().QueryKeeper.StakersByDelegator(suite.Ctx(), &querytypes.QueryStakersByDelegatorRequest{
Expand All @@ -339,7 +341,7 @@ func (suite *KeeperTestSuite) VerifyDelegationQueries() {
Expect(resSbD.Delegator).To(Equal(delegator.Delegator))
for _, sRes := range resSbD.Stakers {
Expect(sRes.DelegationAmount).To(Equal(suite.App().DelegationKeeper.GetDelegationAmountOfDelegator(suite.Ctx(), sRes.Staker.Address, delegator.Delegator)))
Expect(sRes.CurrentReward).To(Equal(suite.App().DelegationKeeper.GetOutstandingRewards(suite.Ctx(), sRes.Staker.Address, delegator.Delegator)))
Expect(sRes.CurrentRewards.String()).To(Equal(suite.App().DelegationKeeper.GetOutstandingRewards(suite.Ctx(), sRes.Staker.Address, delegator.Delegator).String()))
suite.verifyFullStaker(*sRes.Staker, sRes.Staker.Address)
}
}
Expand Down Expand Up @@ -367,28 +369,34 @@ func (suite *KeeperTestSuite) VerifyDelegationQueries() {
for _, delegator := range resDbS.Delegators {
Expect(stakersDelegators[delegator.Staker][delegator.Delegator]).ToNot(BeNil())
Expect(delegator.DelegationAmount).To(Equal(suite.App().DelegationKeeper.GetDelegationAmountOfDelegator(suite.Ctx(), delegator.Staker, delegator.Delegator)))
Expect(delegator.CurrentReward).To(Equal(suite.App().DelegationKeeper.GetOutstandingRewards(suite.Ctx(), delegator.Staker, delegator.Delegator)))
Expect(delegator.CurrentRewards.String()).To(Equal(suite.App().DelegationKeeper.GetOutstandingRewards(suite.Ctx(), delegator.Staker, delegator.Delegator).String()))
}
}
}

func (suite *KeeperTestSuite) VerifyDelegationModuleIntegrity() {
expectedBalance := uint64(0)
expectedBalance := sdk.NewCoins()

for _, delegator := range suite.App().DelegationKeeper.GetAllDelegators(suite.Ctx()) {
expectedBalance += suite.App().DelegationKeeper.GetDelegationAmountOfDelegator(suite.Ctx(), delegator.Staker, delegator.Delegator)
expectedBalance += suite.App().DelegationKeeper.GetOutstandingRewards(suite.Ctx(), delegator.Staker, delegator.Delegator)
expectedBalance = expectedBalance.Add(
sdk.NewInt64Coin(globalTypes.Denom,
int64(suite.App().DelegationKeeper.GetDelegationAmountOfDelegator(suite.Ctx(), delegator.Staker, delegator.Delegator)),
)).Add(
suite.App().DelegationKeeper.GetOutstandingRewards(suite.Ctx(), delegator.Staker, delegator.Delegator)...,
)
}

// Due to rounding errors the delegation module will get a very few nKYVE over the time.
// As long as it is guaranteed that it's always the user who gets paid out less in case of
// rounding, everything is fine.
difference := suite.GetBalanceFromModule(delegationtypes.ModuleName) - expectedBalance
difference := suite.GetCoinsFromModule(delegationtypes.ModuleName).Sub(expectedBalance...)
//nolint:all
Expect(difference >= 0).To(BeTrue())
Expect(difference.IsAnyNegative()).To(BeFalse())

// 10 should be enough for testing
Expect(difference <= 10).To(BeTrue())
// 10 should be enough for testing, these are left-over tokens due to rounding issues
for _, coin := range difference {
Expect(coin.Amount.Uint64() < 10).To(BeTrue())
}
}

func (suite *KeeperTestSuite) VerifyDelegationGenesisImportExport() {
Expand Down
14 changes: 14 additions & 0 deletions testutil/integration/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ func (suite *KeeperTestSuite) GetBalanceFromAddress(address string) uint64 {
return uint64(balance.Amount.Int64())
}

func (suite *KeeperTestSuite) GetCoinsFromAddress(address string) sdk.Coins {
accAddress, err := sdk.AccAddressFromBech32(address)
if err != nil {
return sdk.NewCoins()
}

return suite.App().BankKeeper.GetAllBalances(suite.Ctx(), accAddress)
}

func (suite *KeeperTestSuite) GetBalanceFromPool(poolId uint64) uint64 {
pool, found := suite.App().PoolKeeper.GetPool(suite.Ctx(), poolId)
if !found {
Expand All @@ -30,6 +39,11 @@ func (suite *KeeperTestSuite) GetBalanceFromModule(moduleName string) uint64 {
return suite.App().BankKeeper.GetBalance(suite.Ctx(), moduleAcc, globalTypes.Denom).Amount.Uint64()
}

func (suite *KeeperTestSuite) GetCoinsFromModule(moduleName string) sdk.Coins {
moduleAcc := suite.App().AccountKeeper.GetModuleAccount(suite.Ctx(), moduleName).GetAddress()
return suite.App().BankKeeper.GetAllBalances(suite.Ctx(), moduleAcc)
}

func (suite *KeeperTestSuite) GetNextUploader() (nextStaker string, nextValaddress string) {
bundleProposal, _ := suite.App().BundlesKeeper.GetBundleProposal(suite.Ctx(), 0)

Expand Down
25 changes: 23 additions & 2 deletions testutil/integration/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ func (suite *KeeperTestSuite) initDummyAccounts() {
}
}

func (suite *KeeperTestSuite) Mint(address string, amount uint64) error {
coins := sdk.NewCoins(sdk.NewInt64Coin(KYVE_DENOM, int64(amount)))
func (suite *KeeperTestSuite) MintDenom(address string, amount uint64, denom string) error {
coins := sdk.NewCoins(sdk.NewInt64Coin(denom, int64(amount)))
err := suite.app.BankKeeper.MintCoins(suite.ctx, mintTypes.ModuleName, coins)
if err != nil {
return err
Expand All @@ -153,6 +153,27 @@ func (suite *KeeperTestSuite) Mint(address string, amount uint64) error {
return nil
}

func (suite *KeeperTestSuite) MintDenomToModule(moduleAddress string, amount uint64, denom string) error {
coins := sdk.NewCoins(sdk.NewInt64Coin(denom, int64(amount)))
err := suite.app.BankKeeper.MintCoins(suite.ctx, mintTypes.ModuleName, coins)
if err != nil {
return err
}

suite.Commit()

err = suite.app.BankKeeper.SendCoinsFromModuleToModule(suite.ctx, mintTypes.ModuleName, moduleAddress, coins)
if err != nil {
return err
}

return nil
}

func (suite *KeeperTestSuite) Mint(address string, amount uint64) error {
return suite.MintDenom(address, amount, KYVE_DENOM)
}

type KeeperTestSuite struct {
suite.Suite

Expand Down
4 changes: 2 additions & 2 deletions x/bundles/keeper/keeper_suite_dropped_bundles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ var _ = Describe("dropped bundles", Ordered, func() {
balanceUploader := s.GetBalanceFromAddress(valaccountUploader.Staker)

Expect(balanceUploader).To(Equal(initialBalanceStaker0))
Expect(s.App().DelegationKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_0, i.STAKER_0)).To(BeZero())
Expect(s.App().DelegationKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_0, i.STAKER_0)).To(BeEmpty())

// check voter status
valaccountVoter, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_1)
Expand All @@ -195,7 +195,7 @@ var _ = Describe("dropped bundles", Ordered, func() {
Expect(balanceVoter).To(Equal(initialBalanceStaker1))

Expect(balanceVoter).To(Equal(initialBalanceStaker1))
Expect(s.App().DelegationKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_1, i.STAKER_1)).To(BeZero())
Expect(s.App().DelegationKeeper.GetOutstandingRewards(s.Ctx(), i.STAKER_1, i.STAKER_1)).To(BeEmpty())

// check pool funds
pool, _ = s.App().PoolKeeper.GetPool(s.Ctx(), 0)
Expand Down
Loading
Loading