mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-11-10 10:05:18 +00:00
Community Pool Staking Rewards Implementation & Improvements (#1742)
* add new field upgrade_time_set_staking_rewards_per_second with intention of integrating into the disable inflation logic to set an initial staking reward time * when the disable inflation upgrade time occurs, set the staking rewards per second to the value specified by the new upgrade_time_set_staking_rewards_per_second. This will allow a decoupled implementation between the ugprade switching logic, and the core functionality of paying staking rewards from the pool * add staking rewards state to community keeper and community module genesis that is required to calculate and track staking reward payouts accross blocks * add implementation of staking reward payouts * remove unused error * touch up tests and add a test case that fully tests behavior when pool is drained * add function comments * refactor and pull out main calculation to private pure function with no dependence on keeper * zero out default parameters -- these are too chain specific to have useful defaults * small touch ups on comments, test cases * use correct Int from sdkmath, not old sdk types; update protonet genesis for new parmater * fix copy pasta comment * use bond denom from staking keeper instead of referncing ukava directly * add staking reward state for valid genesis * update kvtool genesis for new params and rewards state
This commit is contained in:
parent
43be3815cc
commit
102cc0fff3
@ -666,6 +666,7 @@ func NewApp(
|
||||
&hardKeeper,
|
||||
&app.mintKeeper,
|
||||
&app.kavadistKeeper,
|
||||
app.stakingKeeper,
|
||||
)
|
||||
|
||||
app.incentiveKeeper = incentivekeeper.NewKeeper(
|
||||
|
9
ci/env/kava-protonet/genesis.json
vendored
9
ci/env/kava-protonet/genesis.json
vendored
@ -1317,7 +1317,12 @@
|
||||
"community": {
|
||||
"params": {
|
||||
"upgrade_time_disable_inflation": "2023-11-01T00:00:00Z",
|
||||
"staking_rewards_per_second": "744191"
|
||||
"upgrade_time_set_staking_rewards_per_second": "744191",
|
||||
"staking_rewards_per_second": "0"
|
||||
},
|
||||
"staking_rewards_state": {
|
||||
"last_accumulation_time": "0001-01-01T00:00:00Z",
|
||||
"last_truncation_error": "0"
|
||||
}
|
||||
},
|
||||
"crisis": {
|
||||
@ -3627,4 +3632,4 @@
|
||||
"validatorvesting": null,
|
||||
"vesting": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13072,6 +13072,13 @@ paths:
|
||||
title: >-
|
||||
staking_rewards_per_second is the amount paid out to
|
||||
delegators each block from the community account
|
||||
upgrade_time_set_staking_rewards_per_second:
|
||||
type: string
|
||||
title: >-
|
||||
upgrade_time_set_staking_rewards_per_second is the initial
|
||||
staking_rewards_per_second to set
|
||||
|
||||
and use when the disable inflation time is reached
|
||||
description: Params defines the parameters of the community module.
|
||||
description: >-
|
||||
QueryParamsResponse defines the response type for querying
|
||||
@ -57028,6 +57035,13 @@ definitions:
|
||||
title: >-
|
||||
staking_rewards_per_second is the amount paid out to delegators each
|
||||
block from the community account
|
||||
upgrade_time_set_staking_rewards_per_second:
|
||||
type: string
|
||||
title: >-
|
||||
upgrade_time_set_staking_rewards_per_second is the initial
|
||||
staking_rewards_per_second to set
|
||||
|
||||
and use when the disable inflation time is reached
|
||||
description: Params defines the parameters of the community module.
|
||||
kava.community.v1beta1.QueryBalanceResponse:
|
||||
type: object
|
||||
@ -57069,6 +57083,13 @@ definitions:
|
||||
title: >-
|
||||
staking_rewards_per_second is the amount paid out to delegators
|
||||
each block from the community account
|
||||
upgrade_time_set_staking_rewards_per_second:
|
||||
type: string
|
||||
title: >-
|
||||
upgrade_time_set_staking_rewards_per_second is the initial
|
||||
staking_rewards_per_second to set
|
||||
|
||||
and use when the disable inflation time is reached
|
||||
description: Params defines the parameters of the community module.
|
||||
description: >-
|
||||
QueryParamsResponse defines the response type for querying x/community
|
||||
|
@ -184,6 +184,9 @@
|
||||
- [kava/community/v1beta1/params.proto](#kava/community/v1beta1/params.proto)
|
||||
- [Params](#kava.community.v1beta1.Params)
|
||||
|
||||
- [kava/community/v1beta1/staking.proto](#kava/community/v1beta1/staking.proto)
|
||||
- [StakingRewardsState](#kava.community.v1beta1.StakingRewardsState)
|
||||
|
||||
- [kava/community/v1beta1/genesis.proto](#kava/community/v1beta1/genesis.proto)
|
||||
- [GenesisState](#kava.community.v1beta1.GenesisState)
|
||||
|
||||
@ -2907,6 +2910,39 @@ Params defines the parameters of the community module.
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| `upgrade_time_disable_inflation` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | upgrade_time_disable_inflation is the time at which to disable mint and kavadist module inflation. If set to 0, inflation will be disabled from block 1. |
|
||||
| `staking_rewards_per_second` | [string](#string) | | staking_rewards_per_second is the amount paid out to delegators each block from the community account |
|
||||
| `upgrade_time_set_staking_rewards_per_second` | [string](#string) | | upgrade_time_set_staking_rewards_per_second is the initial staking_rewards_per_second to set and use when the disable inflation time is reached |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- end messages -->
|
||||
|
||||
<!-- end enums -->
|
||||
|
||||
<!-- end HasExtensions -->
|
||||
|
||||
<!-- end services -->
|
||||
|
||||
|
||||
|
||||
<a name="kava/community/v1beta1/staking.proto"></a>
|
||||
<p align="right"><a href="#top">Top</a></p>
|
||||
|
||||
## kava/community/v1beta1/staking.proto
|
||||
|
||||
|
||||
|
||||
<a name="kava.community.v1beta1.StakingRewardsState"></a>
|
||||
|
||||
### StakingRewardsState
|
||||
StakingRewardsState represents the state of staking reward accumulation between blocks.
|
||||
|
||||
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| `last_accumulation_time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | last_accumulation_time represents the last block time which rewards where calculated and distributed. This may be zero to signal accumulation should start on the next interval. |
|
||||
| `last_truncation_error` | [string](#string) | | accumulated_truncation_error represents the sum of previous errors due to truncation on payout This value will always be on the interval [0, 1). |
|
||||
|
||||
|
||||
|
||||
@ -2938,6 +2974,7 @@ GenesisState defines the community module's genesis state.
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| `params` | [Params](#kava.community.v1beta1.Params) | | params defines all the paramaters related to commmunity |
|
||||
| `staking_rewards_state` | [StakingRewardsState](#kava.community.v1beta1.StakingRewardsState) | | StakingRewardsState stores the internal staking reward data required to track staking rewards across blocks |
|
||||
|
||||
|
||||
|
||||
|
@ -3,6 +3,7 @@ package kava.community.v1beta1;
|
||||
|
||||
import "gogoproto/gogo.proto";
|
||||
import "kava/community/v1beta1/params.proto";
|
||||
import "kava/community/v1beta1/staking.proto";
|
||||
|
||||
option go_package = "github.com/kava-labs/kava/x/community/types";
|
||||
|
||||
@ -10,4 +11,8 @@ option go_package = "github.com/kava-labs/kava/x/community/types";
|
||||
message GenesisState {
|
||||
// params defines all the paramaters related to commmunity
|
||||
Params params = 1 [(gogoproto.nullable) = false];
|
||||
|
||||
// StakingRewardsState stores the internal staking reward data required to
|
||||
// track staking rewards across blocks
|
||||
StakingRewardsState staking_rewards_state = 2 [(gogoproto.nullable) = false];
|
||||
}
|
||||
|
@ -22,4 +22,12 @@ message Params {
|
||||
(gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
|
||||
(gogoproto.nullable) = false
|
||||
];
|
||||
|
||||
// upgrade_time_set_staking_rewards_per_second is the initial staking_rewards_per_second to set
|
||||
// and use when the disable inflation time is reached
|
||||
string upgrade_time_set_staking_rewards_per_second = 3 [
|
||||
(cosmos_proto.scalar) = "cosmos.Dec",
|
||||
(gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
|
||||
(gogoproto.nullable) = false
|
||||
];
|
||||
}
|
||||
|
26
proto/kava/community/v1beta1/staking.proto
Normal file
26
proto/kava/community/v1beta1/staking.proto
Normal file
@ -0,0 +1,26 @@
|
||||
syntax = "proto3";
|
||||
package kava.community.v1beta1;
|
||||
|
||||
import "cosmos_proto/cosmos.proto";
|
||||
import "gogoproto/gogo.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
option go_package = "github.com/kava-labs/kava/x/community/types";
|
||||
|
||||
// StakingRewardsState represents the state of staking reward accumulation between blocks.
|
||||
message StakingRewardsState {
|
||||
// last_accumulation_time represents the last block time which rewards where calculated and distributed.
|
||||
// This may be zero to signal accumulation should start on the next interval.
|
||||
google.protobuf.Timestamp last_accumulation_time = 1 [
|
||||
(gogoproto.stdtime) = true,
|
||||
(gogoproto.nullable) = false
|
||||
];
|
||||
|
||||
// accumulated_truncation_error represents the sum of previous errors due to truncation on payout
|
||||
// This value will always be on the interval [0, 1).
|
||||
string last_truncation_error = 2 [
|
||||
(cosmos_proto.scalar) = "cosmos.Dec",
|
||||
(gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
|
||||
(gogoproto.nullable) = false
|
||||
];
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit 434f7d12322e4713429fe2cc73027938e11eb5a0
|
||||
Subproject commit 6184dc56e9b8ee94a2fe9a1e0c165d6c1129fa42
|
@ -10,8 +10,11 @@ import (
|
||||
"github.com/kava-labs/kava/x/community/types"
|
||||
)
|
||||
|
||||
// BeginBlocker runs the community module begin blocker logic.
|
||||
func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
|
||||
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)
|
||||
|
||||
// This exact call order is required to allow payout on the upgrade block
|
||||
k.CheckAndDisableMintAndKavaDistInflation(ctx)
|
||||
k.PayoutAccumulatedStakingRewards(ctx)
|
||||
}
|
||||
|
74
x/community/abci_test.go
Normal file
74
x/community/abci_test.go
Normal file
@ -0,0 +1,74 @@
|
||||
package community_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sdkmath "cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/community"
|
||||
"github.com/kava-labs/kava/x/community/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
)
|
||||
|
||||
func TestABCIStakingRewardsArePaidOutOnDisableInflationBlock(t *testing.T) {
|
||||
app.SetSDKConfig()
|
||||
tApp := app.NewTestApp()
|
||||
tApp.InitializeFromGenesisStates()
|
||||
keeper := tApp.GetCommunityKeeper()
|
||||
accountKeeper := tApp.GetAccountKeeper()
|
||||
bankKeeper := tApp.GetBankKeeper()
|
||||
|
||||
// a block that runs after addition of the disable inflation code on chain
|
||||
// but before the disable inflation time
|
||||
initialBlockTime := time.Now()
|
||||
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: initialBlockTime})
|
||||
|
||||
poolAcc := accountKeeper.GetModuleAccount(ctx, types.ModuleName)
|
||||
feeCollectorAcc := accountKeeper.GetModuleAccount(ctx, authtypes.FeeCollectorName)
|
||||
|
||||
disableTime := initialBlockTime.Add(9 * time.Second)
|
||||
|
||||
// set state
|
||||
params, _ := keeper.GetParams(ctx)
|
||||
params.UpgradeTimeDisableInflation = disableTime
|
||||
params.UpgradeTimeSetStakingRewardsPerSecond = sdkmath.LegacyNewDec(1000000) // 1 KAVA
|
||||
params.StakingRewardsPerSecond = sdkmath.LegacyZeroDec()
|
||||
keeper.SetParams(ctx, params)
|
||||
|
||||
// fund community pool account
|
||||
tApp.FundAccount(ctx, poolAcc.GetAddress(), sdk.NewCoins(sdk.NewCoin("ukava", sdkmath.NewInt(10000000)))) // 10 KAVA
|
||||
initialFeeCollectorBalance := bankKeeper.GetBalance(ctx, feeCollectorAcc.GetAddress(), "ukava").Amount
|
||||
|
||||
// run one block
|
||||
community.BeginBlocker(ctx, keeper)
|
||||
|
||||
// assert that staking rewards in parameters are still set to zero
|
||||
params, found := keeper.GetParams(ctx)
|
||||
require.True(t, found)
|
||||
require.Equal(t, sdkmath.LegacyZeroDec(), params.StakingRewardsPerSecond)
|
||||
|
||||
// assert no rewards are given yet
|
||||
rewards := bankKeeper.GetBalance(ctx, feeCollectorAcc.GetAddress(), "ukava").Amount.Sub(initialFeeCollectorBalance)
|
||||
require.Equal(t, sdkmath.ZeroInt(), rewards)
|
||||
|
||||
// new block when disable inflation runs, 10 seconds from initial block for easy math
|
||||
blockTime := disableTime.Add(1 * time.Second)
|
||||
ctx = tApp.NewContext(true, tmproto.Header{Height: ctx.BlockHeight() + 1, Time: blockTime})
|
||||
|
||||
// run the next block
|
||||
community.BeginBlocker(ctx, keeper)
|
||||
|
||||
// assert that staking rewards have been set and disable inflation time is zero
|
||||
params, found = keeper.GetParams(ctx)
|
||||
require.True(t, found)
|
||||
require.True(t, params.UpgradeTimeDisableInflation.IsZero())
|
||||
require.Equal(t, sdkmath.LegacyNewDec(1000000), params.StakingRewardsPerSecond)
|
||||
|
||||
// assert that 10 KAVA has been distributed in rewards
|
||||
rewards = bankKeeper.GetBalance(ctx, feeCollectorAcc.GetAddress(), "ukava").Amount.Sub(initialFeeCollectorBalance)
|
||||
require.Equal(t, sdkmath.NewInt(10000000).String(), rewards.String())
|
||||
}
|
@ -18,6 +18,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, ak types.AccountKeeper, gs ty
|
||||
}
|
||||
|
||||
k.SetParams(ctx, gs.Params)
|
||||
k.SetStakingRewardsState(ctx, gs.StakingRewardsState)
|
||||
}
|
||||
|
||||
// ExportGenesis exports the store to a genesis state
|
||||
@ -27,5 +28,7 @@ func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState {
|
||||
params = types.Params{}
|
||||
}
|
||||
|
||||
return types.NewGenesisState(params)
|
||||
stakingRewardsState := k.GetStakingRewardsState(ctx)
|
||||
|
||||
return types.NewGenesisState(params, stakingRewardsState)
|
||||
}
|
||||
|
@ -34,6 +34,11 @@ func (suite *genesisTestSuite) TestInitGenesis() {
|
||||
types.NewParams(
|
||||
time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
sdkmath.LegacyNewDec(1000),
|
||||
sdkmath.LegacyNewDec(1000),
|
||||
),
|
||||
types.NewStakingRewardsState(
|
||||
time.Date(1997, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
sdkmath.LegacyMustNewDecFromStr("0.100000000000000000"),
|
||||
),
|
||||
)
|
||||
|
||||
@ -47,21 +52,33 @@ func (suite *genesisTestSuite) TestInitGenesis() {
|
||||
_, ok := acc.(authtypes.ModuleAccountI)
|
||||
suite.True(ok)
|
||||
|
||||
storedParams, found := suite.App.GetCommunityKeeper().GetParams(suite.Ctx)
|
||||
keeper := suite.App.GetCommunityKeeper()
|
||||
storedParams, found := keeper.GetParams(suite.Ctx)
|
||||
suite.True(found)
|
||||
suite.Equal(genesisState.Params, storedParams)
|
||||
|
||||
stakingRewardsState := keeper.GetStakingRewardsState(suite.Ctx)
|
||||
suite.Equal(genesisState.StakingRewardsState, stakingRewardsState)
|
||||
}
|
||||
|
||||
func (suite *genesisTestSuite) TestExportGenesis() {
|
||||
params := types.NewParams(
|
||||
time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
sdkmath.LegacyNewDec(1000),
|
||||
sdkmath.LegacyNewDec(1000),
|
||||
)
|
||||
suite.Keeper.SetParams(suite.Ctx, params)
|
||||
|
||||
stakingRewardsState := types.NewStakingRewardsState(
|
||||
time.Date(1997, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
sdkmath.LegacyMustNewDecFromStr("0.100000000000000000"),
|
||||
)
|
||||
suite.Keeper.SetStakingRewardsState(suite.Ctx, stakingRewardsState)
|
||||
|
||||
genesisState := community.ExportGenesis(suite.Ctx, suite.Keeper)
|
||||
|
||||
suite.Equal(params, genesisState.Params)
|
||||
suite.Equal(stakingRewardsState, genesisState.StakingRewardsState)
|
||||
}
|
||||
|
||||
func (suite *genesisTestSuite) TestInitExportIsLossless() {
|
||||
@ -69,6 +86,11 @@ func (suite *genesisTestSuite) TestInitExportIsLossless() {
|
||||
types.NewParams(
|
||||
time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
sdkmath.LegacyNewDec(1000),
|
||||
sdkmath.LegacyNewDec(1000),
|
||||
),
|
||||
types.NewStakingRewardsState(
|
||||
time.Date(1997, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
sdkmath.LegacyMustNewDecFromStr("0.100000000000000000"),
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -10,13 +10,10 @@ import (
|
||||
// and disables inflation if time is set and before block time. Inflation time is reset,
|
||||
// so this method is safe to call more than once.
|
||||
func (k Keeper) CheckAndDisableMintAndKavaDistInflation(ctx sdk.Context) {
|
||||
params, found := k.GetParams(ctx)
|
||||
if !found {
|
||||
// panic since this can only be reached if chain state is corrupted or method is ran at an invalid height
|
||||
panic("invalid state: module parameters not found")
|
||||
}
|
||||
// panic if params are not found since this can only be reached if chain state is corrupted or method is ran at an invalid height
|
||||
params := k.mustGetParams(ctx)
|
||||
|
||||
// if upgrade time is in the future or zero there is nothing to do, so return
|
||||
// if disable inflation time is in the future or zero there is nothing to do, so return
|
||||
if params.UpgradeTimeDisableInflation.IsZero() || params.UpgradeTimeDisableInflation.After(ctx.BlockTime()) {
|
||||
return
|
||||
}
|
||||
@ -26,6 +23,8 @@ func (k Keeper) CheckAndDisableMintAndKavaDistInflation(ctx sdk.Context) {
|
||||
|
||||
// reset disable inflation time to ensure next call is a no-op
|
||||
params.UpgradeTimeDisableInflation = time.Time{}
|
||||
// set staking rewards to provided intial value
|
||||
params.StakingRewardsPerSecond = params.UpgradeTimeSetStakingRewardsPerSecond
|
||||
k.SetParams(ctx, params)
|
||||
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ func (suite *grpcQueryTestSuite) TestGrpcQueryParams() {
|
||||
p := types.NewParams(
|
||||
time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
sdkmath.LegacyNewDec(1000),
|
||||
sdkmath.LegacyNewDec(1000),
|
||||
)
|
||||
suite.Keeper.SetParams(suite.Ctx, p)
|
||||
|
||||
|
@ -23,6 +23,7 @@ type Keeper struct {
|
||||
moduleAddress sdk.AccAddress
|
||||
mintKeeper types.MintKeeper
|
||||
kavadistKeeper types.KavadistKeeper
|
||||
stakingKeeper types.StakingKeeper
|
||||
|
||||
legacyCommunityPoolAddress sdk.AccAddress
|
||||
}
|
||||
@ -38,6 +39,7 @@ func NewKeeper(
|
||||
hk types.HardKeeper,
|
||||
mk types.MintKeeper,
|
||||
kk types.KavadistKeeper,
|
||||
sk types.StakingKeeper,
|
||||
) Keeper {
|
||||
// ensure community module account is set
|
||||
addr := ak.GetModuleAddress(types.ModuleAccountName)
|
||||
@ -59,6 +61,7 @@ func NewKeeper(
|
||||
hardKeeper: hk,
|
||||
mintKeeper: mk,
|
||||
kavadistKeeper: kk,
|
||||
stakingKeeper: sk,
|
||||
moduleAddress: addr,
|
||||
|
||||
legacyCommunityPoolAddress: legacyAddr,
|
||||
@ -84,3 +87,30 @@ func (k Keeper) FundCommunityPool(ctx sdk.Context, sender sdk.AccAddress, amount
|
||||
func (k Keeper) DistributeFromCommunityPool(ctx sdk.Context, recipient sdk.AccAddress, amount sdk.Coins) error {
|
||||
return k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, recipient, amount)
|
||||
}
|
||||
|
||||
// GetStakingRewardsState returns the staking reward state or the default state if not set
|
||||
func (k Keeper) GetStakingRewardsState(ctx sdk.Context) types.StakingRewardsState {
|
||||
store := ctx.KVStore(k.key)
|
||||
|
||||
b := store.Get(types.StakingRewardsStateKey)
|
||||
if b == nil {
|
||||
return types.DefaultStakingRewardsState()
|
||||
}
|
||||
|
||||
state := types.StakingRewardsState{}
|
||||
k.cdc.MustUnmarshal(b, &state)
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
// SetStakingRewardsState validates and sets the staking rewards state in the store
|
||||
func (k Keeper) SetStakingRewardsState(ctx sdk.Context, state types.StakingRewardsState) {
|
||||
if err := state.Validate(); err != nil {
|
||||
panic(fmt.Sprintf("invalid state: %s", err))
|
||||
}
|
||||
|
||||
store := ctx.KVStore(k.key)
|
||||
b := k.cdc.MustMarshal(&state)
|
||||
|
||||
store.Set(types.StakingRewardsStateKey, b)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sdkmath "cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
@ -65,3 +66,26 @@ func (suite *KeeperTestSuite) TestCommunityPool() {
|
||||
suite.Require().ErrorContains(err, "insufficient funds")
|
||||
})
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestGetAndSetStakingRewardsState() {
|
||||
keeper := suite.Keeper
|
||||
|
||||
defaultParams := keeper.GetStakingRewardsState(suite.Ctx)
|
||||
suite.Equal(time.Time{}, defaultParams.LastAccumulationTime, "expected default returned accumulation time to be zero")
|
||||
suite.Equal(sdkmath.LegacyZeroDec(), defaultParams.LastTruncationError, "expected default truncation error to be zero")
|
||||
|
||||
suite.NotPanics(func() { keeper.SetStakingRewardsState(suite.Ctx, defaultParams) }, "expected setting default state to not panic")
|
||||
|
||||
invalidParams := defaultParams
|
||||
invalidParams.LastTruncationError = sdkmath.LegacyDec{}
|
||||
|
||||
suite.Panics(func() { keeper.SetStakingRewardsState(suite.Ctx, invalidParams) }, "expected setting invalid state to panic")
|
||||
|
||||
validParams := defaultParams
|
||||
validParams.LastAccumulationTime = time.Date(2023, 9, 29, 11, 42, 53, 123456789, time.UTC)
|
||||
validParams.LastTruncationError = sdkmath.LegacyMustNewDecFromStr("0.50000000000000000")
|
||||
|
||||
suite.NotPanics(func() { keeper.SetStakingRewardsState(suite.Ctx, validParams) }, "expected setting valid state to not panic")
|
||||
|
||||
suite.Equal(validParams, keeper.GetStakingRewardsState(suite.Ctx), "expected fetched state to equal set state")
|
||||
}
|
||||
|
@ -34,3 +34,12 @@ func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
|
||||
|
||||
store.Set(types.ParamsKey, bz)
|
||||
}
|
||||
|
||||
func (k Keeper) mustGetParams(ctx sdk.Context) types.Params {
|
||||
params, found := k.GetParams(ctx)
|
||||
if !found {
|
||||
panic("invalid state: module parameters not found")
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ func (suite *StoreTestSuite) TestGetSetParams() {
|
||||
params := types.NewParams(
|
||||
time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
sdkmath.LegacyNewDec(1000),
|
||||
sdkmath.LegacyNewDec(1000),
|
||||
)
|
||||
suite.Keeper.SetParams(suite.Ctx, params)
|
||||
|
||||
|
91
x/community/keeper/staking.go
Normal file
91
x/community/keeper/staking.go
Normal file
@ -0,0 +1,91 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
sdkmath "cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
"github.com/kava-labs/kava/x/community/types"
|
||||
)
|
||||
|
||||
const nanosecondsInOneSecond = int64(1000000000)
|
||||
|
||||
// PayoutAccumulatedStakingRewards calculates and transfers taking rewards to the fee collector address
|
||||
func (k Keeper) PayoutAccumulatedStakingRewards(ctx sdk.Context) {
|
||||
// get module parameters which define the amount of rewards to payout per second
|
||||
params := k.mustGetParams(ctx)
|
||||
currentBlockTime := ctx.BlockTime()
|
||||
state := k.GetStakingRewardsState(ctx)
|
||||
|
||||
// we have un-initialized state -- set accumulation time and exit since there is nothing to do
|
||||
if state.LastAccumulationTime.IsZero() {
|
||||
state.LastAccumulationTime = currentBlockTime
|
||||
|
||||
k.SetStakingRewardsState(ctx, state)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// get the denom for staking
|
||||
stakingRewardDenom := k.stakingKeeper.BondDenom(ctx)
|
||||
|
||||
// we fetch the community pool balance to ensure only accumulate rewards up to the current balance
|
||||
communityPoolBalance := sdkmath.LegacyNewDecFromInt(k.bankKeeper.GetBalance(ctx, k.moduleAddress, stakingRewardDenom).Amount)
|
||||
|
||||
// calculate staking reward payout capped to community pool balance
|
||||
truncatedRewards, truncationError := calculateStakingRewards(
|
||||
currentBlockTime,
|
||||
state.LastAccumulationTime,
|
||||
state.LastTruncationError,
|
||||
params.StakingRewardsPerSecond,
|
||||
communityPoolBalance,
|
||||
)
|
||||
|
||||
// only payout if the truncated rewards are non-zero
|
||||
if !truncatedRewards.IsZero() {
|
||||
transferAmount := sdk.NewCoins(sdk.NewCoin(stakingRewardDenom, truncatedRewards))
|
||||
|
||||
if err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleAccountName, authtypes.FeeCollectorName, transferAmount); err != nil {
|
||||
// we check for a valid balance and rewards can never be negative so panic since this will only
|
||||
// occur in cases where the chain is running in an invalid state
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// update accumulation state
|
||||
state.LastAccumulationTime = currentBlockTime
|
||||
// if the community pool balance is zero, this also resets the truncation error
|
||||
state.LastTruncationError = truncationError
|
||||
|
||||
// save state
|
||||
k.SetStakingRewardsState(ctx, state)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// calculateStakingRewards takees the currentBlockTime, state of last accumulation, rewards per second, and the community pool balance
|
||||
// in order to calculate the total payout since the last accumulation time. It returns the truncated payout amount and the truncation error.
|
||||
func calculateStakingRewards(currentBlockTime, lastAccumulationTime time.Time, lastTruncationError, stakingRewardsPerSecond, communityPoolBalance sdkmath.LegacyDec) (sdkmath.Int, sdkmath.LegacyDec) {
|
||||
// we get the duration since we last accumulated, then use nanoseconds for full precision available
|
||||
durationSinceLastPayout := currentBlockTime.Sub(lastAccumulationTime)
|
||||
nanosecondsSinceLastPayout := sdkmath.LegacyNewDec(durationSinceLastPayout.Nanoseconds())
|
||||
|
||||
// We multiply by nanoseconds first, then divide by conversion to avoid loss of precision.
|
||||
// This multiplicaiton is also tested against very large values so we are safe from overflow
|
||||
// in normal operations.
|
||||
accumulatedRewards := nanosecondsSinceLastPayout.Mul(stakingRewardsPerSecond).QuoInt64(nanosecondsInOneSecond)
|
||||
// Ensure we add any error from previous truncations
|
||||
accumulatedRewards = accumulatedRewards.Add(lastTruncationError)
|
||||
|
||||
if communityPoolBalance.LT(accumulatedRewards) {
|
||||
accumulatedRewards = communityPoolBalance
|
||||
}
|
||||
|
||||
// we truncate since we can only transfer whole units
|
||||
truncatedRewards := accumulatedRewards.TruncateDec()
|
||||
// the truncation error to carry over to the next accumulation
|
||||
truncationError := accumulatedRewards.Sub(truncatedRewards)
|
||||
|
||||
return truncatedRewards.TruncateInt(), truncationError
|
||||
}
|
18
x/community/keeper/staking_test.go
Normal file
18
x/community/keeper/staking_test.go
Normal file
@ -0,0 +1,18 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/kava-labs/kava/x/community/keeper"
|
||||
"github.com/kava-labs/kava/x/community/testutil"
|
||||
)
|
||||
|
||||
func TestKeeperPayoutAccumulatedStakingRewards(t *testing.T) {
|
||||
testFunc := func(ctx sdk.Context, k keeper.Keeper) {
|
||||
k.PayoutAccumulatedStakingRewards(ctx)
|
||||
}
|
||||
suite.Run(t, testutil.NewStakingRewardsTestSuite(testFunc))
|
||||
}
|
@ -25,6 +25,7 @@ func Migrate(
|
||||
params := types.NewParams(
|
||||
time.Time{},
|
||||
sdkmath.LegacyNewDec(0),
|
||||
sdkmath.LegacyNewDec(0),
|
||||
)
|
||||
|
||||
if err := params.Validate(); err != nil {
|
||||
|
@ -42,6 +42,7 @@ func TestMigrateStore(t *testing.T) {
|
||||
types.NewParams(
|
||||
time.Time{},
|
||||
sdkmath.LegacyNewDec(0),
|
||||
sdkmath.LegacyNewDec(0),
|
||||
),
|
||||
params,
|
||||
"params should be correct after migration",
|
||||
|
18
x/community/staking_rewards_abci_test.go
Normal file
18
x/community/staking_rewards_abci_test.go
Normal file
@ -0,0 +1,18 @@
|
||||
package community_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/kava-labs/kava/x/community"
|
||||
"github.com/kava-labs/kava/x/community/keeper"
|
||||
"github.com/kava-labs/kava/x/community/testutil"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
func TestABCIPayoutAccumulatedStakingRewards(t *testing.T) {
|
||||
testFunc := func(ctx sdk.Context, k keeper.Keeper) {
|
||||
community.BeginBlocker(ctx, k)
|
||||
}
|
||||
suite.Run(t, testutil.NewStakingRewardsTestSuite(testFunc))
|
||||
}
|
@ -9,6 +9,7 @@ import (
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
|
||||
sdkmath "cosmossdk.io/math"
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/community"
|
||||
"github.com/kava-labs/kava/x/community/keeper"
|
||||
@ -64,7 +65,7 @@ func (suite *disableInflationTestSuite) SetupTest() {
|
||||
}
|
||||
|
||||
func (suite *disableInflationTestSuite) TestDisableInflation() {
|
||||
validateState := func(upgraded bool, expectedDisableTime time.Time, msg string) {
|
||||
validateState := func(upgraded bool, expectedDisableTime time.Time, originalStakingRewards sdkmath.LegacyDec, setStakingRewards sdkmath.LegacyDec, msg string) {
|
||||
params, found := suite.Keeper.GetParams(suite.Ctx)
|
||||
suite.Require().True(found)
|
||||
mintParams := suite.App.GetMintKeeper().GetParams(suite.Ctx)
|
||||
@ -73,6 +74,7 @@ func (suite *disableInflationTestSuite) TestDisableInflation() {
|
||||
disableTimeMsg := "expected inflation disable time to match"
|
||||
expectedMintState := suite.genesisMintState
|
||||
expectedKavadistState := suite.genesisKavadistState
|
||||
expectedStakingRewards := originalStakingRewards
|
||||
msgSuffix := "before upgrade"
|
||||
|
||||
// The state expected after upgrade time is reached
|
||||
@ -84,6 +86,7 @@ func (suite *disableInflationTestSuite) TestDisableInflation() {
|
||||
// without extra logic or state.
|
||||
expectedDisableTime = time.Time{}
|
||||
disableTimeMsg = "expected inflation disable time to be reset"
|
||||
expectedStakingRewards = setStakingRewards
|
||||
|
||||
expectedMintState.Params.InflationMin = sdk.ZeroDec()
|
||||
expectedMintState.Params.InflationMax = sdk.ZeroDec()
|
||||
@ -96,37 +99,49 @@ func (suite *disableInflationTestSuite) TestDisableInflation() {
|
||||
suite.Require().Equal(expectedMintState.Params.InflationMax, mintParams.InflationMax, msg+": expected mint inflation max to match state "+msgSuffix)
|
||||
suite.Require().Equal(expectedKavadistState.Params.Active, kavadistParams.Active, msg+":expected kavadist active flag match state "+msgSuffix)
|
||||
suite.Require().Equal(expectedDisableTime, params.UpgradeTimeDisableInflation, msg+": "+disableTimeMsg)
|
||||
|
||||
// we always check staking rewards per second matches the passed in expectation
|
||||
suite.Require().Equal(expectedStakingRewards, params.StakingRewardsPerSecond, msg+": "+"staking rewards per second to match "+msgSuffix)
|
||||
// we don't modify or zero out the initial rewards per second for upgrade time
|
||||
suite.Require().Equal(setStakingRewards, params.UpgradeTimeSetStakingRewardsPerSecond, msg+": "+"set staking rewards per second to match "+msgSuffix)
|
||||
}
|
||||
|
||||
blockTime := suite.Ctx.BlockTime()
|
||||
testCases := []struct {
|
||||
name string
|
||||
upgradeTime time.Time
|
||||
shouldUpgrade bool
|
||||
name string
|
||||
upgradeTime time.Time
|
||||
setStakingRewards sdkmath.LegacyDec
|
||||
shouldUpgrade bool
|
||||
}{
|
||||
{"zero upgrade time -- should not upgrade", time.Time{}, false},
|
||||
{"upgrade time in future -- should not upgrade", blockTime.Add(1 * time.Second), false},
|
||||
{"upgrade time in past -- should upgrade", blockTime.Add(-1 * time.Second), true},
|
||||
{"upgrade time equal to block time -- should upgrade", blockTime, true},
|
||||
{"zero upgrade time -- should not upgrade", time.Time{}, sdkmath.LegacyNewDec(1001), false},
|
||||
{"upgrade time in future -- should not upgrade", blockTime.Add(1 * time.Second), sdkmath.LegacyNewDec(1002), false},
|
||||
{"upgrade time in past -- should upgrade", blockTime.Add(-1 * time.Second), sdkmath.LegacyNewDec(1003), true},
|
||||
{"upgrade time equal to block time -- should upgrade", blockTime, sdkmath.LegacyNewDec(1004), true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
suite.SetupTest()
|
||||
// ensure state is as we expect before running upgrade or updating time
|
||||
validateState(false, time.Time{}, "initial state")
|
||||
|
||||
// set inflation disable time
|
||||
params, found := suite.Keeper.GetParams(suite.Ctx)
|
||||
suite.Require().True(found)
|
||||
|
||||
// these should not match in order to assure assertions test correct behavior
|
||||
suite.Require().NotEqual(params.StakingRewardsPerSecond, tc.setStakingRewards, "set staking rewards can not match initial staking rewards")
|
||||
|
||||
// ensure state is as we expect before running upgrade or updating time
|
||||
validateState(false, time.Time{}, params.StakingRewardsPerSecond, params.UpgradeTimeSetStakingRewardsPerSecond, "initial state")
|
||||
|
||||
// set inflation disable time
|
||||
params.UpgradeTimeDisableInflation = tc.upgradeTime
|
||||
// set upgrade time set staking rewards per second
|
||||
params.UpgradeTimeSetStakingRewardsPerSecond = tc.setStakingRewards
|
||||
suite.Keeper.SetParams(suite.Ctx, params)
|
||||
|
||||
// run test function
|
||||
suite.testFunc(suite.Ctx, suite.Keeper)
|
||||
|
||||
// run assertions to ensure upgrade did or did not run
|
||||
validateState(tc.shouldUpgrade, tc.upgradeTime, "first begin blocker run")
|
||||
validateState(tc.shouldUpgrade, tc.upgradeTime, params.StakingRewardsPerSecond, tc.setStakingRewards, "first begin blocker run")
|
||||
|
||||
// test idempotence only if upgrade should have been ran
|
||||
if tc.shouldUpgrade {
|
||||
@ -134,11 +149,17 @@ func (suite *disableInflationTestSuite) TestDisableInflation() {
|
||||
suite.App.GetMintKeeper().SetParams(suite.Ctx, suite.genesisMintState.Params)
|
||||
suite.App.GetKavadistKeeper().SetParams(suite.Ctx, suite.genesisKavadistState.Params)
|
||||
|
||||
// modify staking rewards per second to ensure they are not overridden again
|
||||
params, found := suite.Keeper.GetParams(suite.Ctx)
|
||||
suite.Require().True(found)
|
||||
params.StakingRewardsPerSecond = params.StakingRewardsPerSecond.Add(sdkmath.LegacyOneDec())
|
||||
suite.Keeper.SetParams(suite.Ctx, params)
|
||||
|
||||
// run begin blocker again
|
||||
community.BeginBlocker(suite.Ctx, suite.Keeper)
|
||||
|
||||
// ensure begin blocker is impodent and never runs twice
|
||||
validateState(false, time.Time{}, "second begin blocker run")
|
||||
// ensure begin blocker is idempotent and never runs twice
|
||||
validateState(false, time.Time{}, params.StakingRewardsPerSecond, tc.setStakingRewards, "second begin blocker run")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
375
x/community/testutil/staking_rewards.go
Normal file
375
x/community/testutil/staking_rewards.go
Normal file
@ -0,0 +1,375 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
|
||||
sdkmath "cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/community"
|
||||
"github.com/kava-labs/kava/x/community/keeper"
|
||||
"github.com/kava-labs/kava/x/community/types"
|
||||
)
|
||||
|
||||
// StakingRewardsTestSuite tests staking rewards per second logic
|
||||
type stakingRewardsTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
App app.TestApp
|
||||
Keeper keeper.Keeper
|
||||
|
||||
testFunc testFunc
|
||||
}
|
||||
|
||||
func NewStakingRewardsTestSuite(tf testFunc) *stakingRewardsTestSuite {
|
||||
suite := &stakingRewardsTestSuite{}
|
||||
suite.testFunc = tf
|
||||
return suite
|
||||
}
|
||||
|
||||
// The default state used by each test
|
||||
func (suite *stakingRewardsTestSuite) SetupTest() {
|
||||
app.SetSDKConfig()
|
||||
|
||||
tApp := app.NewTestApp()
|
||||
tApp.InitializeFromGenesisStates()
|
||||
|
||||
suite.App = tApp
|
||||
suite.Keeper = suite.App.GetCommunityKeeper()
|
||||
}
|
||||
|
||||
func (suite *stakingRewardsTestSuite) TestStakingRewards() {
|
||||
testCases := []struct {
|
||||
// name of subtest
|
||||
name string
|
||||
|
||||
// block time of first block
|
||||
periodStart time.Time
|
||||
// block time of last block
|
||||
periodEnd time.Time
|
||||
|
||||
// block time n will be periodStart + rand(range_min...range_max)*(n-1) up to periodEnd
|
||||
blockTimeRangeMin float64
|
||||
blockTimeRangeMax float64
|
||||
|
||||
// rewards per second to set in state
|
||||
rewardsPerSecond sdkmath.LegacyDec
|
||||
|
||||
// the amount of ukava to mint and transfer to the community pool
|
||||
// to use to pay for rewards
|
||||
communityPoolFunds sdkmath.Int
|
||||
|
||||
// how many total rewards are expected to be accumulated in ukava
|
||||
expectedRewardsTotal sdkmath.Int
|
||||
}{
|
||||
// ** These take a long time to run **
|
||||
//{
|
||||
// name: "one year with 0.5 to 1 second block times",
|
||||
// periodStart: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
// periodEnd: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
// blockTimeRangeMin: 0.5,
|
||||
// blockTimeRangeMax: 1,
|
||||
// rewardsPerSecond: sdkmath.LegacyMustNewDecFromStr("1585489.599188229325215626"),
|
||||
// expectedRewardsTotal: sdkmath.NewInt(49999999999999), // 50 million KAVA per year
|
||||
//},
|
||||
//{
|
||||
// name: "one year with 5.5 to 6.5 second blocktimes",
|
||||
// periodStart: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
// periodEnd: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
// blockTimeRangeMin: 5.5,
|
||||
// blockTimeRangeMax: 6.5,
|
||||
// rewardsPerSecond: sdkmath.LegacyMustNewDecFromStr("1585489.599188229325215626"), // 50 million kava per year
|
||||
// communityPoolFunds: sdkmath.NewInt(50000000000000),
|
||||
// expectedRewardsTotal: sdkmath.NewInt(49999999999999), // truncation results in 1 ukava error
|
||||
//},
|
||||
//
|
||||
//
|
||||
// One Day of blocks with different block time variations
|
||||
//
|
||||
//
|
||||
{
|
||||
name: "one day with sub-second block times and 50 million KAVA per year",
|
||||
periodStart: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
periodEnd: time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||
blockTimeRangeMin: 0.1,
|
||||
blockTimeRangeMax: 1,
|
||||
rewardsPerSecond: sdkmath.LegacyMustNewDecFromStr("1585489.599188229325215626"), // 50 million kava per year
|
||||
communityPoolFunds: sdkmath.NewInt(200000000000),
|
||||
expectedRewardsTotal: sdkmath.NewInt(136986301369), // 50 million / 365 days - 1 ukava
|
||||
|
||||
},
|
||||
{
|
||||
name: "one day with 5.5 to 6.5 second block times and 50 million KAVA per year",
|
||||
periodStart: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
periodEnd: time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||
blockTimeRangeMin: 5.5,
|
||||
blockTimeRangeMax: 6.5,
|
||||
rewardsPerSecond: sdkmath.LegacyMustNewDecFromStr("1585489.599188229325215626"), // 50 million kava per year
|
||||
communityPoolFunds: sdkmath.NewInt(200000000000),
|
||||
expectedRewardsTotal: sdkmath.NewInt(136986301369), // 50 million / 365 days - 1 ukava
|
||||
},
|
||||
//
|
||||
//
|
||||
// Total time span under 1 second
|
||||
//
|
||||
//
|
||||
{
|
||||
name: "single 6.9 second time span and 25 million KAVA per year",
|
||||
periodStart: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
periodEnd: time.Date(2023, 1, 1, 0, 0, 6, 900000000, time.UTC),
|
||||
blockTimeRangeMin: 10, // forces only two blocks -- one time span
|
||||
blockTimeRangeMax: 10,
|
||||
rewardsPerSecond: sdkmath.LegacyMustNewDecFromStr("792744.799594114662607813"), // 25 million kava per year
|
||||
communityPoolFunds: sdkmath.NewInt(10000000),
|
||||
expectedRewardsTotal: sdkmath.NewInt(5469939), // per second rate * 6.9
|
||||
},
|
||||
{
|
||||
name: "multiple blocks across sub-second time span nd 10 million KAVA per year",
|
||||
periodStart: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
periodEnd: time.Date(2023, 1, 1, 0, 0, 0, 800000000, time.UTC),
|
||||
blockTimeRangeMin: 0.1, // multiple blocks in a sub-second time span
|
||||
blockTimeRangeMax: 0.2,
|
||||
rewardsPerSecond: sdkmath.LegacyMustNewDecFromStr("317097.919837645865043125"), // 10 million kava per year
|
||||
communityPoolFunds: sdkmath.NewInt(300000),
|
||||
expectedRewardsTotal: sdkmath.NewInt(253678), // per second rate * 0.8
|
||||
},
|
||||
//
|
||||
//
|
||||
// Variations of community pool balance
|
||||
//
|
||||
//
|
||||
{
|
||||
name: "community pool exact funds -- should spend community to zero and not panic",
|
||||
periodStart: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
periodEnd: time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||
blockTimeRangeMin: 5.5,
|
||||
blockTimeRangeMax: 6.2,
|
||||
rewardsPerSecond: sdkmath.LegacyMustNewDecFromStr("317097.919837645865043125"), // 10 million kava per year
|
||||
communityPoolFunds: sdkmath.NewInt(27397260273),
|
||||
expectedRewardsTotal: sdkmath.NewInt(27397260273),
|
||||
},
|
||||
{
|
||||
name: "community pool under funded -- should spend community pool to down to zero and not panic",
|
||||
periodStart: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
periodEnd: time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||
blockTimeRangeMin: 5.5,
|
||||
blockTimeRangeMax: 6.5,
|
||||
rewardsPerSecond: sdkmath.LegacyMustNewDecFromStr("1585489.599188229325215626"), // 25 million kava per year
|
||||
communityPoolFunds: sdkmath.NewInt(100000000000), // under funded
|
||||
expectedRewardsTotal: sdkmath.NewInt(100000000000), // rewards max is the community pool balance
|
||||
},
|
||||
{
|
||||
name: "community pool no funds -- should pay zero rewards and not panic",
|
||||
periodStart: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
periodEnd: time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||
blockTimeRangeMin: 5.5,
|
||||
blockTimeRangeMax: 6.5,
|
||||
rewardsPerSecond: sdkmath.LegacyMustNewDecFromStr("792744.799594114662607813"), // 25 million kava per year
|
||||
communityPoolFunds: sdkmath.NewInt(0),
|
||||
expectedRewardsTotal: sdkmath.NewInt(0),
|
||||
},
|
||||
//
|
||||
//
|
||||
// Disabled
|
||||
//
|
||||
//
|
||||
{
|
||||
name: "zero rewards per second results in zero rewards paid",
|
||||
periodStart: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
periodEnd: time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||
blockTimeRangeMin: 5.5,
|
||||
blockTimeRangeMax: 6.5,
|
||||
rewardsPerSecond: sdkmath.LegacyMustNewDecFromStr("0.000000000000000000"), // 25 million kava per year
|
||||
communityPoolFunds: sdkmath.NewInt(100000000000000),
|
||||
expectedRewardsTotal: sdkmath.NewInt(0),
|
||||
},
|
||||
//
|
||||
//
|
||||
// Test underlying calculations are safe and overflow/underflow bounds are reasonable
|
||||
//
|
||||
//
|
||||
{
|
||||
name: "does not overflow with extremely large per second value and extremely large single block durations",
|
||||
periodStart: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
periodEnd: time.Date(2033, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
blockTimeRangeMin: 315619200, // a single 10 year long block in seconds (w/ 3 leap years)
|
||||
blockTimeRangeMax: 315619200, // a single 10 year long block in seconds (w/ 3 leap years)
|
||||
rewardsPerSecond: sdkmath.LegacyMustNewDecFromStr("100000000000000000000000000.000000000000000000"), // 100 million kava per second in 18 decimal form
|
||||
communityPoolFunds: newIntFromString("40000000000000000000000000000000000"),
|
||||
expectedRewardsTotal: newIntFromString("31561920000000000000000000000000000"), // 10 years worth of rewards (with three leap years)
|
||||
},
|
||||
{
|
||||
name: "able to accumulate decimal ukava units across blocks",
|
||||
periodStart: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
periodEnd: time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||
blockTimeRangeMin: 5.5,
|
||||
blockTimeRangeMax: 6.5,
|
||||
rewardsPerSecond: sdkmath.LegacyMustNewDecFromStr("0.100000000000000000"), // blocks are not long enough to accumulate a single ukava with this rate
|
||||
communityPoolFunds: sdkmath.NewInt(10000),
|
||||
expectedRewardsTotal: sdkmath.NewInt(8640),
|
||||
},
|
||||
{
|
||||
name: "down to 1 ukava per year can be accumulated -- we are safe from underflow at reasonably small values",
|
||||
periodStart: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
periodEnd: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
blockTimeRangeMin: 60, // large block times speed up this test case
|
||||
blockTimeRangeMax: 120,
|
||||
rewardsPerSecond: sdkmath.LegacyMustNewDecFromStr("0.000000031709791984"),
|
||||
communityPoolFunds: sdkmath.NewInt(1),
|
||||
expectedRewardsTotal: sdkmath.NewInt(1),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
suite.SetupTest()
|
||||
|
||||
// keepers
|
||||
keeper := suite.Keeper
|
||||
accountKeeper := suite.App.GetAccountKeeper()
|
||||
bankKeeper := suite.App.GetBankKeeper()
|
||||
|
||||
// initial context at height 1
|
||||
height := int64(1)
|
||||
blockTime := tc.periodStart
|
||||
ctx := suite.App.NewContext(true, tmproto.Header{Height: height, Time: blockTime})
|
||||
|
||||
// ensure community pool balance matches the test expectations
|
||||
poolAcc := accountKeeper.GetModuleAccount(ctx, types.ModuleName)
|
||||
// community pool balance should start at zero
|
||||
suite.Require().True(bankKeeper.GetBalance(ctx, poolAcc.GetAddress(), "ukava").Amount.IsZero(), "expected community pool to start with zero coins in test genesis")
|
||||
// fund withexact amount from test case
|
||||
suite.App.FundAccount(ctx, poolAcc.GetAddress(), sdk.NewCoins(sdk.NewCoin("ukava", tc.communityPoolFunds)))
|
||||
|
||||
// get starting balance of fee collector to substract later in case this is non-zero in genesis
|
||||
feeCollectorAcc := accountKeeper.GetModuleAccount(ctx, authtypes.FeeCollectorName)
|
||||
initialFeeCollectorBalance := bankKeeper.GetBalance(ctx, feeCollectorAcc.GetAddress(), "ukava").Amount
|
||||
|
||||
// set rewards per second in state
|
||||
params, found := keeper.GetParams(ctx)
|
||||
suite.Require().True(found)
|
||||
params.StakingRewardsPerSecond = tc.rewardsPerSecond
|
||||
keeper.SetParams(ctx, params)
|
||||
|
||||
for {
|
||||
// run community begin blocker logic
|
||||
suite.testFunc(ctx, keeper)
|
||||
|
||||
// exit loop if we are at last block
|
||||
if blockTime.Equal(tc.periodEnd) {
|
||||
break
|
||||
}
|
||||
|
||||
// create random block duration in nanoseconds
|
||||
randomBlockDurationInSeconds := tc.blockTimeRangeMin + rand.Float64()*(tc.blockTimeRangeMax-tc.blockTimeRangeMin)
|
||||
nextBlockDuration := time.Duration(randomBlockDurationInSeconds * math.Pow10(9))
|
||||
|
||||
// move to next block by incrementing height, adding random duration, and settings new context
|
||||
height++
|
||||
blockTime = blockTime.Add(nextBlockDuration)
|
||||
// set last block to exact end of period if we go past
|
||||
if blockTime.After(tc.periodEnd) {
|
||||
blockTime = tc.periodEnd
|
||||
}
|
||||
ctx = suite.App.NewContext(true, tmproto.Header{Height: height, Time: blockTime})
|
||||
}
|
||||
|
||||
endingFeeCollectorBalance := bankKeeper.GetBalance(ctx, feeCollectorAcc.GetAddress(), "ukava").Amount
|
||||
feeCollectorBalanceAdded := endingFeeCollectorBalance.Sub(initialFeeCollectorBalance)
|
||||
|
||||
// assert fee pool was payed the correct rewards
|
||||
suite.Equal(tc.expectedRewardsTotal.String(), feeCollectorBalanceAdded.String(), "expected fee collector balance to match")
|
||||
|
||||
// assert the community pool deducted the same amount
|
||||
expectedCommunityPoolBalance := tc.communityPoolFunds.Sub(tc.expectedRewardsTotal)
|
||||
actualCommunityPoolBalance := bankKeeper.GetBalance(ctx, poolAcc.GetAddress(), "ukava").Amount
|
||||
suite.Equal(expectedCommunityPoolBalance.String(), actualCommunityPoolBalance.String(), "expected community pool balance to match")
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (suite *stakingRewardsTestSuite) TestStakingRewardsDoNotAccumulateWhenPoolIsDrained() {
|
||||
app := suite.App
|
||||
keeper := suite.Keeper
|
||||
accountKeeper := suite.App.GetAccountKeeper()
|
||||
bankKeeper := suite.App.GetBankKeeper()
|
||||
|
||||
// first block
|
||||
blockTime := time.Now()
|
||||
ctx := app.NewContext(true, tmproto.Header{Height: 1, Time: blockTime})
|
||||
|
||||
poolAcc := accountKeeper.GetModuleAccount(ctx, types.ModuleName)
|
||||
feeCollectorAcc := accountKeeper.GetModuleAccount(ctx, authtypes.FeeCollectorName)
|
||||
|
||||
// set state to pay staking rewards
|
||||
params, _ := keeper.GetParams(ctx)
|
||||
// we set a decimal amount that ensures after 10 seconds we overspend the community pool
|
||||
// with enough truncation error that we would have an ending balance of 20.000001 if it was
|
||||
// carried over after the pool run out of funds
|
||||
params.StakingRewardsPerSecond = sdkmath.LegacyMustNewDecFromStr("1000000.099999999999999999") // > 1 KAVA per second
|
||||
keeper.SetParams(ctx, params)
|
||||
|
||||
// fund community pool account
|
||||
app.FundAccount(ctx, poolAcc.GetAddress(), sdk.NewCoins(sdk.NewCoin("ukava", sdkmath.NewInt(10000000)))) // 10 KAVA
|
||||
initialFeeCollectorBalance := bankKeeper.GetBalance(ctx, feeCollectorAcc.GetAddress(), "ukava").Amount
|
||||
|
||||
// run first block (no rewards hapeen on first block)
|
||||
community.BeginBlocker(ctx, keeper)
|
||||
|
||||
// run second block 10 seconds in future and spend all community pool rewards
|
||||
blockTime = blockTime.Add(10 * time.Second)
|
||||
ctx = app.NewContext(true, tmproto.Header{Height: 2, Time: blockTime})
|
||||
community.BeginBlocker(ctx, keeper)
|
||||
|
||||
// run third block 10 seconds in future which no rewards will be paid
|
||||
blockTime = blockTime.Add(10 * time.Second)
|
||||
ctx = app.NewContext(true, tmproto.Header{Height: 3, Time: blockTime})
|
||||
community.BeginBlocker(ctx, keeper)
|
||||
|
||||
// run fourth block 10 seconds in future which no rewards will be paid
|
||||
blockTime = blockTime.Add(10 * time.Second)
|
||||
ctx = app.NewContext(true, tmproto.Header{Height: 4, Time: blockTime})
|
||||
community.BeginBlocker(ctx, keeper)
|
||||
|
||||
// refund the community pool with 100 KAVA -- plenty of funds
|
||||
app.FundAccount(ctx, poolAcc.GetAddress(), sdk.NewCoins(sdk.NewCoin("ukava", sdkmath.NewInt(100000000)))) // 100 KAVA
|
||||
|
||||
// run fifth block 10 seconds in future which no rewards will be paid
|
||||
blockTime = blockTime.Add(10 * time.Second)
|
||||
ctx = app.NewContext(true, tmproto.Header{Height: 5, Time: blockTime})
|
||||
community.BeginBlocker(ctx, keeper)
|
||||
|
||||
// assert that only 20 total KAVA has been distributed in rewards
|
||||
// and blocks where community pool had d
|
||||
rewards := bankKeeper.GetBalance(ctx, feeCollectorAcc.GetAddress(), "ukava").Amount.Sub(initialFeeCollectorBalance)
|
||||
suite.Require().Equal(sdkmath.NewInt(20000000).String(), rewards.String())
|
||||
}
|
||||
|
||||
func (suite *stakingRewardsTestSuite) TestPanicsOnMissingParameters() {
|
||||
suite.SetupTest()
|
||||
|
||||
ctx := suite.App.NewContext(true, tmproto.Header{Height: 1, Time: time.Now()})
|
||||
store := ctx.KVStore(suite.App.GetKVStoreKey(types.StoreKey))
|
||||
store.Delete(types.ParamsKey)
|
||||
|
||||
suite.PanicsWithValue("invalid state: module parameters not found", func() {
|
||||
suite.testFunc(ctx, suite.Keeper)
|
||||
})
|
||||
}
|
||||
|
||||
// newIntFromString returns a new sdkmath.Int from a string
|
||||
func newIntFromString(str string) sdkmath.Int {
|
||||
num, ok := sdkmath.NewIntFromString(str)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("overflow creating Int from %s", str))
|
||||
}
|
||||
return num
|
||||
}
|
@ -15,10 +15,12 @@ type AccountKeeper interface {
|
||||
|
||||
// BankKeeper defines the contract needed to be fulfilled for banking dependencies.
|
||||
type BankKeeper interface {
|
||||
GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin
|
||||
GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins
|
||||
|
||||
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
|
||||
SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
|
||||
SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error
|
||||
}
|
||||
|
||||
// CdpKeeper defines the contract needed to be fulfilled for cdp dependencies.
|
||||
@ -49,3 +51,8 @@ type KavadistKeeper interface {
|
||||
GetParams(ctx sdk.Context) (params kavadisttypes.Params)
|
||||
SetParams(ctx sdk.Context, params kavadisttypes.Params)
|
||||
}
|
||||
|
||||
// StakingKeeper expected interface for the staking keeper
|
||||
type StakingKeeper interface {
|
||||
BondDenom(ctx sdk.Context) string
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
package types
|
||||
|
||||
// NewGenesisState returns a new genesis state object
|
||||
func NewGenesisState(params Params) GenesisState {
|
||||
func NewGenesisState(params Params, stakingRewardsState StakingRewardsState) GenesisState {
|
||||
return GenesisState{
|
||||
Params: params,
|
||||
Params: params,
|
||||
StakingRewardsState: stakingRewardsState,
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,10 +12,15 @@ func NewGenesisState(params Params) GenesisState {
|
||||
func DefaultGenesisState() GenesisState {
|
||||
return NewGenesisState(
|
||||
DefaultParams(),
|
||||
DefaultStakingRewardsState(),
|
||||
)
|
||||
}
|
||||
|
||||
// Validate checks the params are valid
|
||||
func (gs GenesisState) Validate() error {
|
||||
return gs.Params.Validate()
|
||||
if err := gs.Params.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gs.StakingRewardsState.Validate()
|
||||
}
|
||||
|
@ -27,6 +27,9 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
type GenesisState struct {
|
||||
// params defines all the paramaters related to commmunity
|
||||
Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"`
|
||||
// StakingRewardsState stores the internal staking reward data required to
|
||||
// track staking rewards across blocks
|
||||
StakingRewardsState StakingRewardsState `protobuf:"bytes,2,opt,name=staking_rewards_state,json=stakingRewardsState,proto3" json:"staking_rewards_state"`
|
||||
}
|
||||
|
||||
func (m *GenesisState) Reset() { *m = GenesisState{} }
|
||||
@ -69,6 +72,13 @@ func (m *GenesisState) GetParams() Params {
|
||||
return Params{}
|
||||
}
|
||||
|
||||
func (m *GenesisState) GetStakingRewardsState() StakingRewardsState {
|
||||
if m != nil {
|
||||
return m.StakingRewardsState
|
||||
}
|
||||
return StakingRewardsState{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*GenesisState)(nil), "kava.community.v1beta1.GenesisState")
|
||||
}
|
||||
@ -78,20 +88,23 @@ func init() {
|
||||
}
|
||||
|
||||
var fileDescriptor_ccf84d82ea3861e0 = []byte{
|
||||
// 201 bytes of a gzipped FileDescriptorProto
|
||||
// 255 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xc9, 0x4e, 0x2c, 0x4b,
|
||||
0xd4, 0x4f, 0xce, 0xcf, 0xcd, 0x2d, 0xcd, 0xcb, 0x2c, 0xa9, 0xd4, 0x2f, 0x33, 0x4c, 0x4a, 0x2d,
|
||||
0x49, 0x34, 0xd4, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9,
|
||||
0x17, 0x12, 0x03, 0xa9, 0xd2, 0x83, 0xab, 0xd2, 0x83, 0xaa, 0x92, 0x12, 0x49, 0xcf, 0x4f, 0xcf,
|
||||
0x07, 0x2b, 0xd1, 0x07, 0xb1, 0x20, 0xaa, 0xa5, 0x94, 0x71, 0x98, 0x59, 0x90, 0x58, 0x94, 0x98,
|
||||
0x0b, 0x35, 0x52, 0xc9, 0x87, 0x8b, 0xc7, 0x1d, 0x62, 0x47, 0x70, 0x49, 0x62, 0x49, 0xaa, 0x90,
|
||||
0x0d, 0x17, 0x1b, 0x44, 0x5e, 0x82, 0x51, 0x81, 0x51, 0x83, 0xdb, 0x48, 0x4e, 0x0f, 0xbb, 0x9d,
|
||||
0x7a, 0x01, 0x60, 0x55, 0x4e, 0x2c, 0x27, 0xee, 0xc9, 0x33, 0x04, 0x41, 0xf5, 0x38, 0xb9, 0x9e,
|
||||
0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31,
|
||||
0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x76, 0x7a, 0x66, 0x49, 0x46, 0x69,
|
||||
0x12, 0xc8, 0x20, 0x7d, 0x90, 0x89, 0xba, 0x39, 0x89, 0x49, 0xc5, 0x60, 0x96, 0x7e, 0x05, 0x92,
|
||||
0x1b, 0x4b, 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, 0xc0, 0x6e, 0x33, 0x06, 0x04, 0x00, 0x00, 0xff,
|
||||
0xff, 0xb3, 0x0f, 0x84, 0xbd, 0x16, 0x01, 0x00, 0x00,
|
||||
0x0b, 0x35, 0x52, 0x0a, 0x97, 0xc5, 0xc5, 0x25, 0x89, 0xd9, 0x99, 0x79, 0xe9, 0x10, 0x55, 0x4a,
|
||||
0x9b, 0x19, 0xb9, 0x78, 0xdc, 0x21, 0x4e, 0x09, 0x2e, 0x49, 0x2c, 0x49, 0x15, 0xb2, 0xe1, 0x62,
|
||||
0x83, 0x18, 0x23, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x6d, 0x24, 0xa7, 0x87, 0xdd, 0x69, 0x7a, 0x01,
|
||||
0x60, 0x55, 0x4e, 0x2c, 0x27, 0xee, 0xc9, 0x33, 0x04, 0x41, 0xf5, 0x08, 0xa5, 0x72, 0x89, 0x42,
|
||||
0xcd, 0x8f, 0x2f, 0x4a, 0x2d, 0x4f, 0x2c, 0x4a, 0x29, 0x8e, 0x2f, 0x06, 0x19, 0x2b, 0xc1, 0x04,
|
||||
0x36, 0x4c, 0x1b, 0x97, 0x61, 0xc1, 0x10, 0x4d, 0x41, 0x10, 0x3d, 0x60, 0x97, 0x40, 0x4d, 0x16,
|
||||
0x2e, 0xc6, 0x22, 0xe5, 0x7a, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e, 0xc9,
|
||||
0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, 0x72, 0x0c, 0x51, 0xda,
|
||||
0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x20, 0x2b, 0xf4, 0x41, 0x76, 0xe9, 0xe6, 0x24, 0x26, 0x15,
|
||||
0x83, 0x59, 0xfa, 0x15, 0x48, 0x81, 0x51, 0x52, 0x59, 0x90, 0x5a, 0x9c, 0xc4, 0x06, 0x0e, 0x03,
|
||||
0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc5, 0x84, 0x31, 0x9f, 0xa4, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *GenesisState) Marshal() (dAtA []byte, err error) {
|
||||
@ -114,6 +127,16 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
{
|
||||
size, err := m.StakingRewardsState.MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintGenesis(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
{
|
||||
size, err := m.Params.MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
@ -146,6 +169,8 @@ func (m *GenesisState) Size() (n int) {
|
||||
_ = l
|
||||
l = m.Params.Size()
|
||||
n += 1 + l + sovGenesis(uint64(l))
|
||||
l = m.StakingRewardsState.Size()
|
||||
n += 1 + l + sovGenesis(uint64(l))
|
||||
return n
|
||||
}
|
||||
|
||||
@ -217,6 +242,39 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field StakingRewardsState", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenesis
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthGenesis
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthGenesis
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if err := m.StakingRewardsState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipGenesis(dAtA[iNdEx:])
|
||||
|
@ -13,12 +13,32 @@ func TestDefaultGenesisState(t *testing.T) {
|
||||
|
||||
require.NoError(t, defaultGen.Validate())
|
||||
require.Equal(t, types.DefaultParams(), defaultGen.Params)
|
||||
require.Equal(t, types.DefaultStakingRewardsState(), defaultGen.StakingRewardsState)
|
||||
}
|
||||
|
||||
func TestGenesisState_ValidateParams(t *testing.T) {
|
||||
for _, tc := range paramTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
genState := types.NewGenesisState(tc.params)
|
||||
genState := types.DefaultGenesisState()
|
||||
genState.Params = tc.params
|
||||
|
||||
err := genState.Validate()
|
||||
|
||||
if tc.expectedErr == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tc.expectedErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenesisState_ValidateStakingRewardsState(t *testing.T) {
|
||||
for _, tc := range stakingRewardsStateTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
genState := types.DefaultGenesisState()
|
||||
genState.StakingRewardsState = tc.stakingRewardsState
|
||||
|
||||
err := genState.Validate()
|
||||
|
||||
|
@ -23,5 +23,6 @@ const (
|
||||
|
||||
// key prefixes for store
|
||||
var (
|
||||
ParamsKey = []byte{0x01}
|
||||
ParamsKey = []byte{0x01}
|
||||
StakingRewardsStateKey = []byte{0x02}
|
||||
)
|
||||
|
@ -1,7 +1,6 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
fmt "fmt"
|
||||
"time"
|
||||
|
||||
@ -10,18 +9,22 @@ import (
|
||||
|
||||
var (
|
||||
DefaultUpgradeTimeDisableInflation = time.Time{}
|
||||
// DefaultStakingRewardsPerSecond is ~4.6 KAVA per block, 6.3s block time
|
||||
DefaultStakingRewardsPerSecond = sdkmath.LegacyNewDec(744191)
|
||||
// DefaultStakingRewardsPerSecond is zero and should be set by genesis or upgrade
|
||||
DefaultStakingRewardsPerSecond = sdkmath.LegacyNewDec(0)
|
||||
// DefaultStakingRewardsPerSecond is zero and should be set by genesis or upgrade
|
||||
DefaultUpgradeTimeSetStakingRewardsPerSecond = sdkmath.LegacyNewDec(0)
|
||||
)
|
||||
|
||||
// NewParams returns a new params object
|
||||
func NewParams(
|
||||
upgradeTime time.Time,
|
||||
stakingRewardsPerSecond sdkmath.LegacyDec,
|
||||
upgradeTimeSetstakingRewardsPerSecond sdkmath.LegacyDec,
|
||||
) Params {
|
||||
return Params{
|
||||
UpgradeTimeDisableInflation: upgradeTime,
|
||||
StakingRewardsPerSecond: stakingRewardsPerSecond,
|
||||
UpgradeTimeDisableInflation: upgradeTime,
|
||||
StakingRewardsPerSecond: stakingRewardsPerSecond,
|
||||
UpgradeTimeSetStakingRewardsPerSecond: upgradeTimeSetstakingRewardsPerSecond,
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +33,7 @@ func DefaultParams() Params {
|
||||
return NewParams(
|
||||
DefaultUpgradeTimeDisableInflation,
|
||||
DefaultStakingRewardsPerSecond,
|
||||
DefaultUpgradeTimeSetStakingRewardsPerSecond,
|
||||
)
|
||||
}
|
||||
|
||||
@ -37,12 +41,24 @@ func DefaultParams() Params {
|
||||
func (p Params) Validate() error {
|
||||
// p.UpgradeTimeDisableInflation.IsZero() is a valid state. It's taken to mean inflation will be disabled on the block 1.
|
||||
|
||||
if p.StakingRewardsPerSecond.IsNil() {
|
||||
return errors.New("StakingRewardsPerSecond should not be nil")
|
||||
if err := validateDecNotNilNonNegative(p.StakingRewardsPerSecond, "StakingRewardsPerSecond"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.StakingRewardsPerSecond.IsNegative() {
|
||||
return fmt.Errorf("StakingRewardsPerSecond should not be negative: %s", p.StakingRewardsPerSecond)
|
||||
if err := validateDecNotNilNonNegative(p.UpgradeTimeSetStakingRewardsPerSecond, "UpgradeTimeSetStakingRewardsPerSecond"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDecNotNilNonNegative(value sdkmath.LegacyDec, name string) error {
|
||||
if value.IsNil() {
|
||||
return fmt.Errorf("%s should not be nil", name)
|
||||
}
|
||||
|
||||
if value.IsNegative() {
|
||||
return fmt.Errorf("%s should not be negative: %s", name, value)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -36,6 +36,9 @@ type Params struct {
|
||||
UpgradeTimeDisableInflation time.Time `protobuf:"bytes,1,opt,name=upgrade_time_disable_inflation,json=upgradeTimeDisableInflation,proto3,stdtime" json:"upgrade_time_disable_inflation"`
|
||||
// staking_rewards_per_second is the amount paid out to delegators each block from the community account
|
||||
StakingRewardsPerSecond cosmossdk_io_math.LegacyDec `protobuf:"bytes,2,opt,name=staking_rewards_per_second,json=stakingRewardsPerSecond,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"staking_rewards_per_second"`
|
||||
// upgrade_time_set_staking_rewards_per_second is the initial staking_rewards_per_second to set
|
||||
// and use when the disable inflation time is reached
|
||||
UpgradeTimeSetStakingRewardsPerSecond cosmossdk_io_math.LegacyDec `protobuf:"bytes,3,opt,name=upgrade_time_set_staking_rewards_per_second,json=upgradeTimeSetStakingRewardsPerSecond,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"upgrade_time_set_staking_rewards_per_second"`
|
||||
}
|
||||
|
||||
func (m *Params) Reset() { *m = Params{} }
|
||||
@ -87,29 +90,31 @@ func init() {
|
||||
}
|
||||
|
||||
var fileDescriptor_0a48475520900507 = []byte{
|
||||
// 339 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x91, 0x31, 0x4e, 0xc3, 0x30,
|
||||
0x14, 0x86, 0x63, 0x86, 0x0a, 0xc2, 0x56, 0x21, 0x28, 0xa9, 0x94, 0x54, 0xb0, 0x54, 0x42, 0xb5,
|
||||
0x55, 0xb8, 0x41, 0x55, 0x06, 0x24, 0x86, 0xaa, 0x30, 0xb1, 0x44, 0x2f, 0x89, 0xeb, 0x5a, 0x4d,
|
||||
0xe2, 0xc8, 0x76, 0x0a, 0xbd, 0x45, 0x0f, 0xc3, 0x21, 0x3a, 0x56, 0x4c, 0x88, 0xa1, 0xa0, 0xe6,
|
||||
0x22, 0xc8, 0x71, 0x8a, 0xd8, 0xde, 0x7b, 0xfe, 0xfc, 0x59, 0xef, 0xb7, 0x7b, 0xbd, 0x80, 0x25,
|
||||
0x90, 0x58, 0x64, 0x59, 0x99, 0x73, 0xbd, 0x22, 0xcb, 0x61, 0x44, 0x35, 0x0c, 0x49, 0x01, 0x12,
|
||||
0x32, 0x85, 0x0b, 0x29, 0xb4, 0x68, 0x9f, 0x1b, 0x08, 0xff, 0x41, 0xb8, 0x81, 0xbc, 0xcb, 0x58,
|
||||
0xa8, 0x4c, 0xa8, 0xb0, 0xa6, 0x88, 0x6d, 0xec, 0x15, 0xef, 0x8c, 0x09, 0x26, 0xec, 0xdc, 0x54,
|
||||
0xcd, 0x34, 0x60, 0x42, 0xb0, 0x94, 0x92, 0xba, 0x8b, 0xca, 0x19, 0xd1, 0x3c, 0xa3, 0x4a, 0x43,
|
||||
0x56, 0x58, 0xe0, 0xaa, 0x42, 0x6e, 0x6b, 0x52, 0x3f, 0xdd, 0xe6, 0xae, 0x5f, 0x16, 0x4c, 0x42,
|
||||
0x42, 0x43, 0x43, 0x85, 0x09, 0x57, 0x10, 0xa5, 0x34, 0xe4, 0xf9, 0x2c, 0x05, 0xcd, 0x45, 0xde,
|
||||
0x41, 0x3d, 0xd4, 0x3f, 0xbd, 0xf5, 0xb0, 0x95, 0xe2, 0x83, 0x14, 0x3f, 0x1f, 0xa4, 0xa3, 0xe3,
|
||||
0xcd, 0x2e, 0x70, 0xd6, 0xdf, 0x01, 0x9a, 0x76, 0x1b, 0x97, 0x39, 0x1b, 0x5b, 0xd3, 0xc3, 0x41,
|
||||
0xd4, 0xce, 0x5d, 0x4f, 0x69, 0x58, 0xf0, 0x9c, 0x85, 0x92, 0xbe, 0x82, 0x4c, 0x54, 0x58, 0x50,
|
||||
0x19, 0x2a, 0x1a, 0x8b, 0x3c, 0xe9, 0x1c, 0xf5, 0x50, 0xff, 0x64, 0x34, 0x34, 0xaa, 0xaf, 0x5d,
|
||||
0xd0, 0xb5, 0x6b, 0xaa, 0x64, 0x81, 0xb9, 0x20, 0x19, 0xe8, 0x39, 0x7e, 0xa4, 0x0c, 0xe2, 0xd5,
|
||||
0x98, 0xc6, 0x1f, 0xef, 0x03, 0xb7, 0x49, 0x61, 0x4c, 0xe3, 0xe9, 0x45, 0x23, 0x9d, 0x5a, 0xe7,
|
||||
0x84, 0xca, 0xa7, 0xda, 0x38, 0xba, 0xdf, 0xec, 0x7d, 0xb4, 0xdd, 0xfb, 0xe8, 0x67, 0xef, 0xa3,
|
||||
0x75, 0xe5, 0x3b, 0xdb, 0xca, 0x77, 0x3e, 0x2b, 0xdf, 0x79, 0xb9, 0x61, 0x5c, 0xcf, 0xcb, 0xc8,
|
||||
0x64, 0x4d, 0x4c, 0xe8, 0x83, 0x14, 0x22, 0x55, 0x57, 0xe4, 0xed, 0xdf, 0x2f, 0xe9, 0x55, 0x41,
|
||||
0x55, 0xd4, 0xaa, 0x37, 0xbe, 0xfb, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xa1, 0x87, 0x19, 0x27, 0xc4,
|
||||
0x01, 0x00, 0x00,
|
||||
// 373 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x92, 0xc1, 0x4a, 0xc3, 0x30,
|
||||
0x18, 0xc7, 0x9b, 0x09, 0x43, 0xeb, 0xad, 0x88, 0xce, 0x0e, 0xda, 0xa1, 0x08, 0x83, 0xb1, 0x84,
|
||||
0xe9, 0x1b, 0x8c, 0x79, 0x10, 0x3c, 0x8c, 0xcd, 0x93, 0x97, 0x90, 0xb6, 0x59, 0x16, 0xd6, 0x36,
|
||||
0xa5, 0x49, 0xa7, 0x7b, 0x08, 0x61, 0x0f, 0xe3, 0x43, 0xec, 0x38, 0x3c, 0x89, 0x87, 0x29, 0xdb,
|
||||
0x23, 0xf8, 0x02, 0xd2, 0xa6, 0x93, 0x09, 0xe2, 0xc1, 0xdb, 0xf7, 0x7d, 0xf9, 0xe7, 0x97, 0xef,
|
||||
0x9f, 0xef, 0x33, 0xcf, 0x27, 0x64, 0x4a, 0x90, 0x2f, 0xa2, 0x28, 0x8b, 0xb9, 0x9a, 0xa1, 0x69,
|
||||
0xc7, 0xa3, 0x8a, 0x74, 0x50, 0x42, 0x52, 0x12, 0x49, 0x98, 0xa4, 0x42, 0x09, 0xeb, 0x38, 0x17,
|
||||
0xc1, 0x6f, 0x11, 0x2c, 0x45, 0xf6, 0xa9, 0x2f, 0x64, 0x24, 0x24, 0x2e, 0x54, 0x48, 0x27, 0xfa,
|
||||
0x8a, 0x7d, 0xc4, 0x04, 0x13, 0xba, 0x9e, 0x47, 0x65, 0xd5, 0x65, 0x42, 0xb0, 0x90, 0xa2, 0x22,
|
||||
0xf3, 0xb2, 0x11, 0x52, 0x3c, 0xa2, 0x52, 0x91, 0x28, 0xd1, 0x82, 0xb3, 0xcf, 0x8a, 0x59, 0xed,
|
||||
0x17, 0x4f, 0x5b, 0xdc, 0x74, 0xb2, 0x84, 0xa5, 0x24, 0xa0, 0x38, 0x57, 0xe1, 0x80, 0x4b, 0xe2,
|
||||
0x85, 0x14, 0xf3, 0x78, 0x14, 0x12, 0xc5, 0x45, 0x5c, 0x03, 0x0d, 0xd0, 0x3c, 0xbc, 0xb4, 0xa1,
|
||||
0x86, 0xc2, 0x2d, 0x14, 0xde, 0x6d, 0xa1, 0xdd, 0xfd, 0xc5, 0xca, 0x35, 0xe6, 0xef, 0x2e, 0x18,
|
||||
0xd4, 0x4b, 0x56, 0x7e, 0xd6, 0xd3, 0xa4, 0x9b, 0x2d, 0xc8, 0x8a, 0x4d, 0x5b, 0x2a, 0x32, 0xe1,
|
||||
0x31, 0xc3, 0x29, 0x7d, 0x20, 0x69, 0x20, 0x71, 0x42, 0x53, 0x2c, 0xa9, 0x2f, 0xe2, 0xa0, 0x56,
|
||||
0x69, 0x80, 0xe6, 0x41, 0xb7, 0x93, 0xa3, 0xde, 0x56, 0x6e, 0x5d, 0xdb, 0x94, 0xc1, 0x04, 0x72,
|
||||
0x81, 0x22, 0xa2, 0xc6, 0xf0, 0x96, 0x32, 0xe2, 0xcf, 0x7a, 0xd4, 0x7f, 0x79, 0x6e, 0x9b, 0xe5,
|
||||
0x2f, 0xf4, 0xa8, 0x3f, 0x38, 0x29, 0xa1, 0x03, 0xcd, 0xec, 0xd3, 0x74, 0x58, 0x10, 0xad, 0x27,
|
||||
0x60, 0xb6, 0x7e, 0x78, 0x93, 0x54, 0xe1, 0x3f, 0x3a, 0xd8, 0xfb, 0x6f, 0x07, 0x17, 0x3b, 0xae,
|
||||
0x87, 0x54, 0x0d, 0x7f, 0xef, 0xa7, 0x7b, 0xbd, 0x58, 0x3b, 0x60, 0xb9, 0x76, 0xc0, 0xc7, 0xda,
|
||||
0x01, 0xf3, 0x8d, 0x63, 0x2c, 0x37, 0x8e, 0xf1, 0xba, 0x71, 0x8c, 0xfb, 0x16, 0xe3, 0x6a, 0x9c,
|
||||
0x79, 0xf9, 0xec, 0x51, 0xbe, 0x04, 0xed, 0x90, 0x78, 0xb2, 0x88, 0xd0, 0xe3, 0xce, 0xd6, 0xa8,
|
||||
0x59, 0x42, 0xa5, 0x57, 0x2d, 0x26, 0x70, 0xf5, 0x15, 0x00, 0x00, 0xff, 0xff, 0x8a, 0x51, 0x6e,
|
||||
0xa4, 0x54, 0x02, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *Params) Marshal() (dAtA []byte, err error) {
|
||||
@ -132,6 +137,16 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
{
|
||||
size := m.UpgradeTimeSetStakingRewardsPerSecond.Size()
|
||||
i -= size
|
||||
if _, err := m.UpgradeTimeSetStakingRewardsPerSecond.MarshalTo(dAtA[i:]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i = encodeVarintParams(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x1a
|
||||
{
|
||||
size := m.StakingRewardsPerSecond.Size()
|
||||
i -= size
|
||||
@ -174,6 +189,8 @@ func (m *Params) Size() (n int) {
|
||||
n += 1 + l + sovParams(uint64(l))
|
||||
l = m.StakingRewardsPerSecond.Size()
|
||||
n += 1 + l + sovParams(uint64(l))
|
||||
l = m.UpgradeTimeSetStakingRewardsPerSecond.Size()
|
||||
n += 1 + l + sovParams(uint64(l))
|
||||
return n
|
||||
}
|
||||
|
||||
@ -279,6 +296,40 @@ func (m *Params) Unmarshal(dAtA []byte) error {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 3:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field UpgradeTimeSetStakingRewardsPerSecond", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowParams
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthParams
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthParams
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if err := m.UpgradeTimeSetStakingRewardsPerSecond.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipParams(dAtA[iNdEx:])
|
||||
|
@ -25,35 +25,66 @@ var paramTestCases = []paramTestCase{
|
||||
{
|
||||
name: "valid params",
|
||||
params: types.Params{
|
||||
UpgradeTimeDisableInflation: time.Time{},
|
||||
StakingRewardsPerSecond: sdkmath.LegacyNewDec(1000),
|
||||
UpgradeTimeDisableInflation: time.Time{},
|
||||
StakingRewardsPerSecond: sdkmath.LegacyNewDec(1000),
|
||||
UpgradeTimeSetStakingRewardsPerSecond: sdkmath.LegacyNewDec(1000),
|
||||
},
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "rewards per second are allowed to be zero",
|
||||
params: types.Params{
|
||||
UpgradeTimeDisableInflation: time.Time{},
|
||||
StakingRewardsPerSecond: sdkmath.LegacyNewDec(0),
|
||||
UpgradeTimeDisableInflation: time.Time{},
|
||||
StakingRewardsPerSecond: sdkmath.LegacyNewDec(0),
|
||||
UpgradeTimeSetStakingRewardsPerSecond: sdkmath.LegacyNewDec(1000),
|
||||
},
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "nil rewards per second",
|
||||
params: types.Params{
|
||||
UpgradeTimeDisableInflation: time.Time{},
|
||||
StakingRewardsPerSecond: sdkmath.LegacyDec{},
|
||||
UpgradeTimeDisableInflation: time.Time{},
|
||||
StakingRewardsPerSecond: sdkmath.LegacyDec{},
|
||||
UpgradeTimeSetStakingRewardsPerSecond: sdkmath.LegacyNewDec(1000),
|
||||
},
|
||||
expectedErr: "StakingRewardsPerSecond should not be nil",
|
||||
},
|
||||
{
|
||||
name: "negative rewards per second",
|
||||
params: types.Params{
|
||||
UpgradeTimeDisableInflation: time.Time{},
|
||||
StakingRewardsPerSecond: sdkmath.LegacyNewDec(-5),
|
||||
UpgradeTimeDisableInflation: time.Time{},
|
||||
StakingRewardsPerSecond: sdkmath.LegacyNewDec(-5),
|
||||
UpgradeTimeSetStakingRewardsPerSecond: sdkmath.LegacyNewDec(1000),
|
||||
},
|
||||
expectedErr: "StakingRewardsPerSecond should not be negative",
|
||||
},
|
||||
{
|
||||
name: "upgrade time set rewards per second are allowed to be zero",
|
||||
params: types.Params{
|
||||
UpgradeTimeDisableInflation: time.Time{},
|
||||
StakingRewardsPerSecond: sdkmath.LegacyNewDec(1000),
|
||||
UpgradeTimeSetStakingRewardsPerSecond: sdkmath.LegacyNewDec(0),
|
||||
},
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "nil upgrade time set rewards per second",
|
||||
params: types.Params{
|
||||
UpgradeTimeDisableInflation: time.Time{},
|
||||
StakingRewardsPerSecond: sdkmath.LegacyNewDec(1000),
|
||||
UpgradeTimeSetStakingRewardsPerSecond: sdkmath.LegacyDec{},
|
||||
},
|
||||
expectedErr: "UpgradeTimeSetStakingRewardsPerSecond should not be nil",
|
||||
},
|
||||
{
|
||||
name: "upgrade time set negative rewards per second",
|
||||
params: types.Params{
|
||||
UpgradeTimeDisableInflation: time.Time{},
|
||||
StakingRewardsPerSecond: sdkmath.LegacyNewDec(1000),
|
||||
UpgradeTimeSetStakingRewardsPerSecond: sdkmath.LegacyNewDec(-5),
|
||||
},
|
||||
expectedErr: "UpgradeTimeSetStakingRewardsPerSecond should not be negative",
|
||||
},
|
||||
}
|
||||
|
||||
func TestParamsValidate(t *testing.T) {
|
||||
|
51
x/community/types/staking.go
Normal file
51
x/community/types/staking.go
Normal file
@ -0,0 +1,51 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
sdkmath "cosmossdk.io/math"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultLastAccumulationTime is zero
|
||||
DefaultLastAccumulationTime = time.Time{}
|
||||
// DefaultLastTruncationError is zero
|
||||
DefaultLastTruncationError = sdkmath.LegacyZeroDec()
|
||||
)
|
||||
|
||||
// NewStakingRewardsState returns a new staking rewards state object
|
||||
func NewStakingRewardsState(
|
||||
lastAccumulationTime time.Time,
|
||||
lastTruncationError sdkmath.LegacyDec,
|
||||
) StakingRewardsState {
|
||||
return StakingRewardsState{
|
||||
LastAccumulationTime: lastAccumulationTime,
|
||||
LastTruncationError: lastTruncationError,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultStakingRewardsState returns default params
|
||||
func DefaultStakingRewardsState() StakingRewardsState {
|
||||
return NewStakingRewardsState(
|
||||
DefaultLastAccumulationTime,
|
||||
DefaultLastTruncationError,
|
||||
)
|
||||
}
|
||||
|
||||
// Validate checks the params are valid
|
||||
func (p StakingRewardsState) Validate() error {
|
||||
if err := validateDecNotNilNonNegative(p.LastTruncationError, "LastTruncationError"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.LastTruncationError.GTE(sdkmath.LegacyOneDec()) {
|
||||
return errors.New("LastTruncationError should not be greater or equal to 1")
|
||||
}
|
||||
|
||||
if p.LastAccumulationTime.IsZero() && !p.LastTruncationError.IsZero() {
|
||||
return errors.New("LastTruncationError should be zero if last accumulation time is zero")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
386
x/community/types/staking.pb.go
Normal file
386
x/community/types/staking.pb.go
Normal file
@ -0,0 +1,386 @@
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: kava/community/v1beta1/staking.proto
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
cosmossdk_io_math "cosmossdk.io/math"
|
||||
fmt "fmt"
|
||||
_ "github.com/cosmos/cosmos-proto"
|
||||
_ "github.com/gogo/protobuf/gogoproto"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
github_com_gogo_protobuf_types "github.com/gogo/protobuf/types"
|
||||
_ "google.golang.org/protobuf/types/known/timestamppb"
|
||||
io "io"
|
||||
math "math"
|
||||
math_bits "math/bits"
|
||||
time "time"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
var _ = time.Kitchen
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// StakingRewardsState represents the state of staking reward accumulation between blocks.
|
||||
type StakingRewardsState struct {
|
||||
// last_accumulation_time represents the last block time which rewards where calculated and distributed.
|
||||
// This may be zero to signal accumulation should start on the next interval.
|
||||
LastAccumulationTime time.Time `protobuf:"bytes,1,opt,name=last_accumulation_time,json=lastAccumulationTime,proto3,stdtime" json:"last_accumulation_time"`
|
||||
// accumulated_truncation_error represents the sum of previous errors due to truncation on payout
|
||||
// This value will always be on the interval [0, 1).
|
||||
LastTruncationError cosmossdk_io_math.LegacyDec `protobuf:"bytes,2,opt,name=last_truncation_error,json=lastTruncationError,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"last_truncation_error"`
|
||||
}
|
||||
|
||||
func (m *StakingRewardsState) Reset() { *m = StakingRewardsState{} }
|
||||
func (m *StakingRewardsState) String() string { return proto.CompactTextString(m) }
|
||||
func (*StakingRewardsState) ProtoMessage() {}
|
||||
func (*StakingRewardsState) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_fce59dad9b680fa3, []int{0}
|
||||
}
|
||||
func (m *StakingRewardsState) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *StakingRewardsState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_StakingRewardsState.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *StakingRewardsState) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_StakingRewardsState.Merge(m, src)
|
||||
}
|
||||
func (m *StakingRewardsState) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *StakingRewardsState) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_StakingRewardsState.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_StakingRewardsState proto.InternalMessageInfo
|
||||
|
||||
func (m *StakingRewardsState) GetLastAccumulationTime() time.Time {
|
||||
if m != nil {
|
||||
return m.LastAccumulationTime
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*StakingRewardsState)(nil), "kava.community.v1beta1.StakingRewardsState")
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterFile("kava/community/v1beta1/staking.proto", fileDescriptor_fce59dad9b680fa3)
|
||||
}
|
||||
|
||||
var fileDescriptor_fce59dad9b680fa3 = []byte{
|
||||
// 331 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x91, 0x31, 0x6e, 0x2a, 0x31,
|
||||
0x10, 0x86, 0xd7, 0xaf, 0x78, 0x7a, 0x6f, 0xd3, 0x01, 0x41, 0x84, 0x48, 0xbb, 0x28, 0x4a, 0x81,
|
||||
0x14, 0x61, 0x8b, 0xe4, 0x04, 0x41, 0xd0, 0xa5, 0x02, 0x2a, 0x1a, 0x34, 0x6b, 0x1c, 0xb3, 0x62,
|
||||
0xbd, 0x46, 0xeb, 0x59, 0x12, 0x6e, 0xc1, 0x61, 0x72, 0x08, 0x4a, 0x94, 0x0a, 0xa5, 0x20, 0x11,
|
||||
0x5c, 0x24, 0xf2, 0x1a, 0x10, 0xdd, 0xcc, 0xe8, 0x9b, 0xcf, 0xfa, 0xc7, 0xfe, 0xfd, 0x0c, 0x16,
|
||||
0xc0, 0xb8, 0x56, 0x2a, 0x4f, 0x63, 0x5c, 0xb2, 0x45, 0x3b, 0x12, 0x08, 0x6d, 0x66, 0x10, 0x66,
|
||||
0x71, 0x2a, 0xe9, 0x3c, 0xd3, 0xa8, 0x4b, 0x55, 0x4b, 0xd1, 0x33, 0x45, 0x8f, 0x54, 0xfd, 0x86,
|
||||
0x6b, 0xa3, 0xb4, 0x19, 0x17, 0x14, 0x73, 0x8d, 0x5b, 0xa9, 0x57, 0xa4, 0x96, 0xda, 0xcd, 0x6d,
|
||||
0x75, 0x9c, 0x86, 0x52, 0x6b, 0x99, 0x08, 0x56, 0x74, 0x51, 0xfe, 0xca, 0x30, 0x56, 0xc2, 0x20,
|
||||
0xa8, 0xb9, 0x03, 0xee, 0xb6, 0xc4, 0x2f, 0x0f, 0xdc, 0xdb, 0x7d, 0xf1, 0x06, 0xd9, 0xc4, 0x0c,
|
||||
0x10, 0x50, 0x94, 0x46, 0x7e, 0x35, 0x01, 0x83, 0x63, 0xe0, 0x3c, 0x57, 0x79, 0x02, 0x18, 0xeb,
|
||||
0x74, 0x6c, 0x97, 0x6b, 0xa4, 0x41, 0x9a, 0x57, 0x8f, 0x75, 0xea, 0xcc, 0xf4, 0x64, 0xa6, 0xc3,
|
||||
0x93, 0xb9, 0xf3, 0x6f, 0xbd, 0x0b, 0xbd, 0xd5, 0x77, 0x48, 0xfa, 0x15, 0xeb, 0x78, 0xbe, 0x50,
|
||||
0x58, 0xa8, 0x24, 0xfc, 0xeb, 0xc2, 0x8d, 0x59, 0x9e, 0x72, 0x67, 0x16, 0x59, 0xa6, 0xb3, 0xda,
|
||||
0x9f, 0x06, 0x69, 0xfe, 0xef, 0xb4, 0xed, 0xfa, 0xd7, 0x2e, 0xbc, 0x75, 0xf9, 0xcc, 0x64, 0x46,
|
||||
0x63, 0xcd, 0x14, 0xe0, 0x94, 0xbe, 0x08, 0x09, 0x7c, 0xd9, 0x15, 0xfc, 0xf3, 0xa3, 0xe5, 0x1f,
|
||||
0xe3, 0x77, 0x05, 0xef, 0x97, 0xad, 0x6f, 0x78, 0xd6, 0xf5, 0xac, 0xad, 0xd3, 0x5b, 0xef, 0x03,
|
||||
0xb2, 0xd9, 0x07, 0xe4, 0x67, 0x1f, 0x90, 0xd5, 0x21, 0xf0, 0x36, 0x87, 0xc0, 0xdb, 0x1e, 0x02,
|
||||
0x6f, 0xf4, 0x20, 0x63, 0x9c, 0xe6, 0x91, 0x3d, 0x30, 0xb3, 0x97, 0x6e, 0x25, 0x10, 0x99, 0xa2,
|
||||
0x62, 0xef, 0x17, 0x7f, 0x83, 0xcb, 0xb9, 0x30, 0xd1, 0xdf, 0x22, 0xe1, 0xd3, 0x6f, 0x00, 0x00,
|
||||
0x00, 0xff, 0xff, 0x98, 0xd8, 0x77, 0x55, 0xba, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *StakingRewardsState) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *StakingRewardsState) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *StakingRewardsState) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
{
|
||||
size := m.LastTruncationError.Size()
|
||||
i -= size
|
||||
if _, err := m.LastTruncationError.MarshalTo(dAtA[i:]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i = encodeVarintStaking(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
n1, err1 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastAccumulationTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastAccumulationTime):])
|
||||
if err1 != nil {
|
||||
return 0, err1
|
||||
}
|
||||
i -= n1
|
||||
i = encodeVarintStaking(dAtA, i, uint64(n1))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func encodeVarintStaking(dAtA []byte, offset int, v uint64) int {
|
||||
offset -= sovStaking(v)
|
||||
base := offset
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
dAtA[offset] = uint8(v)
|
||||
return base
|
||||
}
|
||||
func (m *StakingRewardsState) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
l = github_com_gogo_protobuf_types.SizeOfStdTime(m.LastAccumulationTime)
|
||||
n += 1 + l + sovStaking(uint64(l))
|
||||
l = m.LastTruncationError.Size()
|
||||
n += 1 + l + sovStaking(uint64(l))
|
||||
return n
|
||||
}
|
||||
|
||||
func sovStaking(x uint64) (n int) {
|
||||
return (math_bits.Len64(x|1) + 6) / 7
|
||||
}
|
||||
func sozStaking(x uint64) (n int) {
|
||||
return sovStaking(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (m *StakingRewardsState) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowStaking
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: StakingRewardsState: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: StakingRewardsState: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field LastAccumulationTime", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowStaking
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthStaking
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthStaking
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.LastAccumulationTime, dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field LastTruncationError", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowStaking
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthStaking
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthStaking
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if err := m.LastTruncationError.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipStaking(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthStaking
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipStaking(dAtA []byte) (n int, err error) {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
depth := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowStaking
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
wireType := int(wire & 0x7)
|
||||
switch wireType {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowStaking
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx++
|
||||
if dAtA[iNdEx-1] < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
iNdEx += 8
|
||||
case 2:
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowStaking
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
length |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthStaking
|
||||
}
|
||||
iNdEx += length
|
||||
case 3:
|
||||
depth++
|
||||
case 4:
|
||||
if depth == 0 {
|
||||
return 0, ErrUnexpectedEndOfGroupStaking
|
||||
}
|
||||
depth--
|
||||
case 5:
|
||||
iNdEx += 4
|
||||
default:
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthStaking
|
||||
}
|
||||
if depth == 0 {
|
||||
return iNdEx, nil
|
||||
}
|
||||
}
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthStaking = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowStaking = fmt.Errorf("proto: integer overflow")
|
||||
ErrUnexpectedEndOfGroupStaking = fmt.Errorf("proto: unexpected end of group")
|
||||
)
|
105
x/community/types/staking_test.go
Normal file
105
x/community/types/staking_test.go
Normal file
@ -0,0 +1,105 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
sdkmath "cosmossdk.io/math"
|
||||
"github.com/kava-labs/kava/x/community/types"
|
||||
)
|
||||
|
||||
type stakingRewardsStateTestCase struct {
|
||||
name string
|
||||
stakingRewardsState types.StakingRewardsState
|
||||
expectedErr string
|
||||
}
|
||||
|
||||
var stakingRewardsStateTestCases = []stakingRewardsStateTestCase{
|
||||
{
|
||||
name: "default stakingRewardsState are valid",
|
||||
stakingRewardsState: types.DefaultStakingRewardsState(),
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "valid example state",
|
||||
stakingRewardsState: types.StakingRewardsState{
|
||||
LastAccumulationTime: time.Now(),
|
||||
LastTruncationError: newDecFromString("0.10000000000000000"),
|
||||
},
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "last accumulation time can be zero",
|
||||
stakingRewardsState: types.StakingRewardsState{
|
||||
LastAccumulationTime: time.Time{},
|
||||
LastTruncationError: sdkmath.LegacyZeroDec(),
|
||||
},
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "nil last truncation error is invalid",
|
||||
stakingRewardsState: types.StakingRewardsState{
|
||||
LastAccumulationTime: time.Now(),
|
||||
LastTruncationError: sdkmath.LegacyDec{},
|
||||
},
|
||||
expectedErr: "LastTruncationError should not be nil",
|
||||
},
|
||||
{
|
||||
name: "negative last truncation error is invalid",
|
||||
stakingRewardsState: types.StakingRewardsState{
|
||||
LastAccumulationTime: time.Now(),
|
||||
LastTruncationError: newDecFromString("-0.10000000000000000"),
|
||||
},
|
||||
expectedErr: "LastTruncationError should not be negative",
|
||||
},
|
||||
{
|
||||
name: "last truncation error equal to 1 is invalid",
|
||||
stakingRewardsState: types.StakingRewardsState{
|
||||
LastAccumulationTime: time.Now(),
|
||||
LastTruncationError: newDecFromString("1.00000000000000000"),
|
||||
},
|
||||
expectedErr: "LastTruncationError should not be greater or equal to 1",
|
||||
},
|
||||
{
|
||||
name: "last truncation error greater than 1 is invalid",
|
||||
stakingRewardsState: types.StakingRewardsState{
|
||||
LastAccumulationTime: time.Now(),
|
||||
LastTruncationError: newDecFromString("1.00000000000000000"),
|
||||
},
|
||||
expectedErr: "LastTruncationError should not be greater or equal to 1",
|
||||
},
|
||||
{
|
||||
name: "last truncation error can not be set if last accumulation time is zero",
|
||||
stakingRewardsState: types.StakingRewardsState{
|
||||
LastAccumulationTime: time.Time{},
|
||||
LastTruncationError: newDecFromString("0.10000000000000000"),
|
||||
},
|
||||
expectedErr: "LastTruncationError should be zero if last accumulation time is zero",
|
||||
},
|
||||
}
|
||||
|
||||
func TestStakingRewardsStateValidate(t *testing.T) {
|
||||
for _, tc := range stakingRewardsStateTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.stakingRewardsState.Validate()
|
||||
|
||||
if tc.expectedErr == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tc.expectedErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// newDecFromString returns a new sdkmath.Int from a string
|
||||
func newDecFromString(str string) sdkmath.LegacyDec {
|
||||
num, err := sdkmath.LegacyNewDecFromStr(str)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return num
|
||||
}
|
Loading…
Reference in New Issue
Block a user