mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-25 15:55:18 +00:00
x/incentive: genesis validation (#519)
* x/incentive: genesis validation * validation funcs for arrays * tests * genesis tests * rewards tests * address @alexanderbez comments * fix genesis incentive simulation
This commit is contained in:
parent
b2edeb8549
commit
d130734c2e
@ -48,8 +48,8 @@ func genParams(r *rand.Rand) types.Params {
|
|||||||
|
|
||||||
// genRewards generates rewards for each specified collateral type
|
// genRewards generates rewards for each specified collateral type
|
||||||
func genRewards(r *rand.Rand) types.Rewards {
|
func genRewards(r *rand.Rand) types.Rewards {
|
||||||
var rewards types.Rewards
|
rewards := make(types.Rewards, len(CollateralDenoms))
|
||||||
for _, denom := range CollateralDenoms {
|
for i, denom := range CollateralDenoms {
|
||||||
active := true
|
active := true
|
||||||
// total reward is in range (half max total reward, max total reward)
|
// total reward is in range (half max total reward, max total reward)
|
||||||
amount := simulation.RandIntBetween(r, int(MaxTotalAssetReward.Int64()/2), int(MaxTotalAssetReward.Int64()))
|
amount := simulation.RandIntBetween(r, int(MaxTotalAssetReward.Int64()/2), int(MaxTotalAssetReward.Int64()))
|
||||||
@ -59,18 +59,17 @@ func genRewards(r *rand.Rand) types.Rewards {
|
|||||||
duration := time.Duration(time.Hour * time.Duration(numbHours))
|
duration := time.Duration(time.Hour * time.Duration(numbHours))
|
||||||
timeLock := time.Duration(time.Hour * time.Duration(numbHours/2)) // half as long as duration
|
timeLock := time.Duration(time.Hour * time.Duration(numbHours/2)) // half as long as duration
|
||||||
claimDuration := time.Hour * time.Duration(numbHours*2) // twice as long as duration
|
claimDuration := time.Hour * time.Duration(numbHours*2) // twice as long as duration
|
||||||
reward := types.NewReward(active, denom, totalRewards, duration, timeLock, claimDuration)
|
rewards[i] = types.NewReward(active, denom, totalRewards, duration, timeLock, claimDuration)
|
||||||
rewards = append(rewards, reward)
|
|
||||||
}
|
}
|
||||||
return rewards
|
return rewards
|
||||||
}
|
}
|
||||||
|
|
||||||
// genRewardPeriods generates chronological reward periods for each given reward type
|
// genRewardPeriods generates chronological reward periods for each given reward type
|
||||||
func genRewardPeriods(r *rand.Rand, timestamp time.Time, rewards types.Rewards) types.RewardPeriods {
|
func genRewardPeriods(r *rand.Rand, timestamp time.Time, rewards types.Rewards) types.RewardPeriods {
|
||||||
var rewardPeriods types.RewardPeriods
|
rewardPeriods := make(types.RewardPeriods, len(rewards))
|
||||||
for _, reward := range rewards {
|
|
||||||
rewardPeriodStart := timestamp
|
rewardPeriodStart := timestamp
|
||||||
for i := 10; i >= simulation.RandIntBetween(r, 2, 9); i-- {
|
|
||||||
|
for i, reward := range rewards {
|
||||||
// Set up reward period parameters
|
// Set up reward period parameters
|
||||||
start := rewardPeriodStart
|
start := rewardPeriodStart
|
||||||
end := start.Add(reward.Duration).UTC()
|
end := start.Add(reward.Duration).UTC()
|
||||||
@ -80,20 +79,18 @@ func genRewardPeriods(r *rand.Rand, timestamp time.Time, rewards types.Rewards)
|
|||||||
claimEnd := end.Add(reward.ClaimDuration)
|
claimEnd := end.Add(reward.ClaimDuration)
|
||||||
claimTimeLock := reward.TimeLock
|
claimTimeLock := reward.TimeLock
|
||||||
// Create reward period and append to array
|
// Create reward period and append to array
|
||||||
rewardPeriod := types.NewRewardPeriod(reward.Denom, start, end, amount, claimEnd, claimTimeLock)
|
rewardPeriods[i] = types.NewRewardPeriod(reward.Denom, start, end, amount, claimEnd, claimTimeLock)
|
||||||
rewardPeriods = append(rewardPeriods, rewardPeriod)
|
|
||||||
// Update start time of next reward period
|
// Update start time of next reward period
|
||||||
rewardPeriodStart = end
|
rewardPeriodStart = end
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return rewardPeriods
|
return rewardPeriods
|
||||||
}
|
}
|
||||||
|
|
||||||
// genClaimPeriods loads valid claim periods for an array of reward periods
|
// genClaimPeriods loads valid claim periods for an array of reward periods
|
||||||
func genClaimPeriods(rewardPeriods types.RewardPeriods) types.ClaimPeriods {
|
func genClaimPeriods(rewardPeriods types.RewardPeriods) types.ClaimPeriods {
|
||||||
denomRewardPeriodsCount := make(map[string]uint64)
|
denomRewardPeriodsCount := make(map[string]uint64)
|
||||||
var claimPeriods types.ClaimPeriods
|
claimPeriods := make(types.ClaimPeriods, len(rewardPeriods))
|
||||||
for _, rewardPeriod := range rewardPeriods {
|
for i, rewardPeriod := range rewardPeriods {
|
||||||
// Increment reward period count for this denom (this is our claim period's ID)
|
// Increment reward period count for this denom (this is our claim period's ID)
|
||||||
denom := rewardPeriod.Denom
|
denom := rewardPeriod.Denom
|
||||||
numbRewardPeriods := denomRewardPeriodsCount[denom] + 1
|
numbRewardPeriods := denomRewardPeriodsCount[denom] + 1
|
||||||
@ -102,8 +99,7 @@ func genClaimPeriods(rewardPeriods types.RewardPeriods) types.ClaimPeriods {
|
|||||||
end := rewardPeriod.ClaimEnd
|
end := rewardPeriod.ClaimEnd
|
||||||
claimTimeLock := rewardPeriod.ClaimTimeLock
|
claimTimeLock := rewardPeriod.ClaimTimeLock
|
||||||
// Create the new claim period for this reward period
|
// Create the new claim period for this reward period
|
||||||
claimPeriod := types.NewClaimPeriod(denom, numbRewardPeriods, end, claimTimeLock)
|
claimPeriods[i] = types.NewClaimPeriod(denom, numbRewardPeriods, end, claimTimeLock)
|
||||||
claimPeriods = append(claimPeriods, claimPeriod)
|
|
||||||
}
|
}
|
||||||
return claimPeriods
|
return claimPeriods
|
||||||
}
|
}
|
||||||
@ -112,16 +108,14 @@ func genClaimPeriods(rewardPeriods types.RewardPeriods) types.ClaimPeriods {
|
|||||||
func genNextClaimPeriodIds(cps types.ClaimPeriods) types.GenesisClaimPeriodIDs {
|
func genNextClaimPeriodIds(cps types.ClaimPeriods) types.GenesisClaimPeriodIDs {
|
||||||
// Build a map of the most recent claim periods by denom
|
// Build a map of the most recent claim periods by denom
|
||||||
mostRecentClaimPeriodByDenom := make(map[string]uint64)
|
mostRecentClaimPeriodByDenom := make(map[string]uint64)
|
||||||
for _, cp := range cps {
|
|
||||||
if cp.ID > mostRecentClaimPeriodByDenom[cp.Denom] {
|
|
||||||
mostRecentClaimPeriodByDenom[cp.Denom] = cp.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Write map contents to an array of GenesisClaimPeriodIDs
|
|
||||||
var claimPeriodIDs types.GenesisClaimPeriodIDs
|
var claimPeriodIDs types.GenesisClaimPeriodIDs
|
||||||
for key, value := range mostRecentClaimPeriodByDenom {
|
for _, cp := range cps {
|
||||||
claimPeriodID := types.GenesisClaimPeriodID{Denom: key, ID: value}
|
if cp.ID <= mostRecentClaimPeriodByDenom[cp.Denom] {
|
||||||
claimPeriodIDs = append(claimPeriodIDs, claimPeriodID)
|
continue
|
||||||
|
}
|
||||||
|
claimPeriodIDs = append(claimPeriodIDs, types.GenesisClaimPeriodID{Denom: cp.Denom, ID: cp.ID})
|
||||||
|
mostRecentClaimPeriodByDenom[cp.Denom] = cp.ID
|
||||||
|
|
||||||
}
|
}
|
||||||
return claimPeriodIDs
|
return claimPeriodIDs
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,11 @@ package types
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenesisClaimPeriodID stores the next claim id and its corresponding denom
|
// GenesisClaimPeriodID stores the next claim id and its corresponding denom
|
||||||
@ -12,9 +15,37 @@ type GenesisClaimPeriodID struct {
|
|||||||
ID uint64 `json:"id" yaml:"id"`
|
ID uint64 `json:"id" yaml:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate performs a basic check of a GenesisClaimPeriodID fields.
|
||||||
|
func (gcp GenesisClaimPeriodID) Validate() error {
|
||||||
|
if gcp.ID == 0 {
|
||||||
|
return errors.New("genesis claim period id cannot be 0")
|
||||||
|
}
|
||||||
|
return sdk.ValidateDenom(gcp.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
// GenesisClaimPeriodIDs array of GenesisClaimPeriodID
|
// GenesisClaimPeriodIDs array of GenesisClaimPeriodID
|
||||||
type GenesisClaimPeriodIDs []GenesisClaimPeriodID
|
type GenesisClaimPeriodIDs []GenesisClaimPeriodID
|
||||||
|
|
||||||
|
// Validate checks if all the GenesisClaimPeriodIDs are valid and there are no duplicated
|
||||||
|
// entries.
|
||||||
|
func (gcps GenesisClaimPeriodIDs) Validate() error {
|
||||||
|
seenIDS := make(map[string]bool)
|
||||||
|
var key string
|
||||||
|
for _, gcp := range gcps {
|
||||||
|
key = gcp.Denom + string(gcp.ID)
|
||||||
|
if seenIDS[key] {
|
||||||
|
return fmt.Errorf("duplicated genesis claim period with id %d and denom %s", gcp.ID, gcp.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gcp.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
seenIDS[key] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GenesisState is the state that must be provided at genesis.
|
// GenesisState is the state that must be provided at genesis.
|
||||||
type GenesisState struct {
|
type GenesisState struct {
|
||||||
Params Params `json:"params" yaml:"params"`
|
Params Params `json:"params" yaml:"params"`
|
||||||
@ -52,14 +83,22 @@ func DefaultGenesisState() GenesisState {
|
|||||||
// Validate performs basic validation of genesis data returning an
|
// Validate performs basic validation of genesis data returning an
|
||||||
// error for any failed validation criteria.
|
// error for any failed validation criteria.
|
||||||
func (gs GenesisState) Validate() error {
|
func (gs GenesisState) Validate() error {
|
||||||
|
|
||||||
if err := gs.Params.Validate(); err != nil {
|
if err := gs.Params.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if gs.PreviousBlockTime.IsZero() {
|
if gs.PreviousBlockTime.IsZero() {
|
||||||
return fmt.Errorf("previous block time not set or zero")
|
return errors.New("previous block time cannot be 0")
|
||||||
}
|
}
|
||||||
return nil
|
if err := gs.RewardPeriods.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := gs.ClaimPeriods.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := gs.Claims.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return gs.NextClaimPeriodIDs.Validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equal checks whether two gov GenesisState structs are equivalent
|
// Equal checks whether two gov GenesisState structs are equivalent
|
||||||
|
163
x/incentive/types/genesis_test.go
Normal file
163
x/incentive/types/genesis_test.go
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenesisClaimPeriodIDsValidate(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
msg string
|
||||||
|
genesisClaimPeriodIDs GenesisClaimPeriodIDs
|
||||||
|
expPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"valid",
|
||||||
|
GenesisClaimPeriodIDs{
|
||||||
|
{Denom: "bnb", ID: 1},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid denom",
|
||||||
|
GenesisClaimPeriodIDs{
|
||||||
|
{Denom: "", ID: 1},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid ID",
|
||||||
|
GenesisClaimPeriodIDs{
|
||||||
|
{Denom: "bnb", ID: 0},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"duplicate",
|
||||||
|
GenesisClaimPeriodIDs{
|
||||||
|
{Denom: "bnb", ID: 1},
|
||||||
|
{Denom: "bnb", ID: 1},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
err := tc.genesisClaimPeriodIDs.Validate()
|
||||||
|
if tc.expPass {
|
||||||
|
require.NoError(t, err, tc.msg)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err, tc.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenesisStateValidate(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
owner := sdk.AccAddress(tmtypes.NewMockPV().GetPubKey().Address())
|
||||||
|
|
||||||
|
rewards := Rewards{
|
||||||
|
NewReward(
|
||||||
|
true, "bnb", sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
||||||
|
time.Hour*24*7, time.Hour*8766, time.Hour*24*14,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
rewardPeriods := RewardPeriods{NewRewardPeriod("bnb", now, now.Add(time.Hour), sdk.NewCoin("bnb", sdk.OneInt()), now, 10)}
|
||||||
|
claimPeriods := ClaimPeriods{NewClaimPeriod("bnb", 10, now, 100)}
|
||||||
|
claims := Claims{NewClaim(owner, sdk.NewCoin("bnb", sdk.OneInt()), "bnb", 10)}
|
||||||
|
gcps := GenesisClaimPeriodIDs{{Denom: "bnb", ID: 1}}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
msg string
|
||||||
|
genesisState GenesisState
|
||||||
|
expPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
msg: "default",
|
||||||
|
genesisState: DefaultGenesisState(),
|
||||||
|
expPass: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: "valid genesis",
|
||||||
|
genesisState: NewGenesisState(
|
||||||
|
NewParams(true, rewards),
|
||||||
|
now, rewardPeriods, claimPeriods, claims, gcps,
|
||||||
|
),
|
||||||
|
expPass: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: "invalid Params",
|
||||||
|
genesisState: GenesisState{
|
||||||
|
Params: Params{
|
||||||
|
Active: true,
|
||||||
|
Rewards: Rewards{
|
||||||
|
Reward{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expPass: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: "zero PreviousBlockTime",
|
||||||
|
genesisState: GenesisState{
|
||||||
|
PreviousBlockTime: time.Time{},
|
||||||
|
},
|
||||||
|
expPass: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: "invalid RewardsPeriod",
|
||||||
|
genesisState: GenesisState{
|
||||||
|
PreviousBlockTime: now,
|
||||||
|
RewardPeriods: RewardPeriods{
|
||||||
|
{Start: time.Time{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expPass: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: "invalid ClaimPeriods",
|
||||||
|
genesisState: GenesisState{
|
||||||
|
PreviousBlockTime: now,
|
||||||
|
ClaimPeriods: ClaimPeriods{
|
||||||
|
{ID: 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expPass: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: "invalid Claims",
|
||||||
|
genesisState: GenesisState{
|
||||||
|
PreviousBlockTime: now,
|
||||||
|
Claims: Claims{
|
||||||
|
{ClaimPeriodID: 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expPass: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: "invalid NextClaimPeriodIds",
|
||||||
|
genesisState: GenesisState{
|
||||||
|
PreviousBlockTime: now,
|
||||||
|
NextClaimPeriodIDs: GenesisClaimPeriodIDs{
|
||||||
|
{ID: 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expPass: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
err := tc.genesisState.Validate()
|
||||||
|
if tc.expPass {
|
||||||
|
require.NoError(t, err, tc.msg)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err, tc.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,6 @@ package types
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
@ -71,11 +70,7 @@ func (p Params) Validate() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateRewardsParam(p.Rewards); err != nil {
|
return validateRewardsParam(p.Rewards)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateActiveParam(i interface{}) error {
|
func validateActiveParam(i interface{}) error {
|
||||||
@ -91,33 +86,8 @@ func validateRewardsParam(i interface{}) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("invalid parameter type: %T", i)
|
return fmt.Errorf("invalid parameter type: %T", i)
|
||||||
}
|
}
|
||||||
rewardDenoms := make(map[string]bool)
|
|
||||||
|
|
||||||
for _, reward := range rewards {
|
return rewards.Validate()
|
||||||
if strings.TrimSpace(reward.Denom) == "" {
|
|
||||||
return fmt.Errorf("cannot have empty reward denom: %s", reward)
|
|
||||||
}
|
|
||||||
if rewardDenoms[reward.Denom] {
|
|
||||||
return fmt.Errorf("cannot have duplicate reward denoms: %s", reward.Denom)
|
|
||||||
}
|
|
||||||
rewardDenoms[reward.Denom] = true
|
|
||||||
if !reward.AvailableRewards.IsValid() {
|
|
||||||
return fmt.Errorf("invalid reward coins %s for %s", reward.AvailableRewards, reward.Denom)
|
|
||||||
}
|
|
||||||
if !reward.AvailableRewards.IsPositive() {
|
|
||||||
return fmt.Errorf("reward amount must be positive, is %s for %s", reward.AvailableRewards, reward.Denom)
|
|
||||||
}
|
|
||||||
if int(reward.Duration.Seconds()) <= 0 {
|
|
||||||
return fmt.Errorf("reward duration must be positive, is %s for %s", reward.Duration.String(), reward.Denom)
|
|
||||||
}
|
|
||||||
if int(reward.TimeLock.Seconds()) < 0 {
|
|
||||||
return fmt.Errorf("reward timelock must be non-negative, is %s for %s", reward.TimeLock.String(), reward.Denom)
|
|
||||||
}
|
|
||||||
if int(reward.ClaimDuration.Seconds()) <= 0 {
|
|
||||||
return fmt.Errorf("claim duration must be positive, is %s for %s", reward.ClaimDuration.String(), reward.Denom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reward stores the specified state for a single reward period.
|
// Reward stores the specified state for a single reward period.
|
||||||
@ -154,9 +124,47 @@ func (r Reward) String() string {
|
|||||||
r.Active, r.Denom, r.AvailableRewards, r.Duration, r.TimeLock, r.ClaimDuration)
|
r.Active, r.Denom, r.AvailableRewards, r.Duration, r.TimeLock, r.ClaimDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate performs a basic check of a reward fields.
|
||||||
|
func (r Reward) Validate() error {
|
||||||
|
if !r.AvailableRewards.IsValid() {
|
||||||
|
return fmt.Errorf("invalid reward coins %s for %s", r.AvailableRewards, r.Denom)
|
||||||
|
}
|
||||||
|
if !r.AvailableRewards.IsPositive() {
|
||||||
|
return fmt.Errorf("reward amount must be positive, is %s for %s", r.AvailableRewards, r.Denom)
|
||||||
|
}
|
||||||
|
if r.Duration <= 0 {
|
||||||
|
return fmt.Errorf("reward duration must be positive, is %s for %s", r.Duration, r.Denom)
|
||||||
|
}
|
||||||
|
if r.TimeLock < 0 {
|
||||||
|
return fmt.Errorf("reward timelock must be non-negative, is %s for %s", r.TimeLock, r.Denom)
|
||||||
|
}
|
||||||
|
if r.ClaimDuration <= 0 {
|
||||||
|
return fmt.Errorf("claim duration must be positive, is %s for %s", r.ClaimDuration, r.Denom)
|
||||||
|
}
|
||||||
|
return sdk.ValidateDenom(r.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
// Rewards array of Reward
|
// Rewards array of Reward
|
||||||
type Rewards []Reward
|
type Rewards []Reward
|
||||||
|
|
||||||
|
// Validate checks if all the rewards are valid and there are no duplicated
|
||||||
|
// entries.
|
||||||
|
func (rs Rewards) Validate() error {
|
||||||
|
rewardDenoms := make(map[string]bool)
|
||||||
|
for _, r := range rs {
|
||||||
|
if rewardDenoms[r.Denom] {
|
||||||
|
return fmt.Errorf("cannot have duplicate reward denoms: %s", r.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rewardDenoms[r.Denom] = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// String implements fmt.Stringer
|
// String implements fmt.Stringer
|
||||||
func (rs Rewards) String() string {
|
func (rs Rewards) String() string {
|
||||||
out := "Rewards\n"
|
out := "Rewards\n"
|
||||||
|
@ -187,7 +187,7 @@ func (suite *ParamTestSuite) SetupTest() {
|
|||||||
types.Reward{
|
types.Reward{
|
||||||
Active: true,
|
Active: true,
|
||||||
Denom: "",
|
Denom: "",
|
||||||
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(0)),
|
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(1)),
|
||||||
Duration: time.Hour * 24 * 7,
|
Duration: time.Hour * 24 * 7,
|
||||||
TimeLock: time.Hour * 8766,
|
TimeLock: time.Hour * 8766,
|
||||||
ClaimDuration: time.Hour * 24 * 14,
|
ClaimDuration: time.Hour * 24 * 14,
|
||||||
@ -196,7 +196,7 @@ func (suite *ParamTestSuite) SetupTest() {
|
|||||||
},
|
},
|
||||||
errResult: errResult{
|
errResult: errResult{
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
contains: "cannot have empty reward denom",
|
contains: "invalid denom",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -41,9 +42,50 @@ func NewRewardPeriod(denom string, start time.Time, end time.Time, reward sdk.Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate performs a basic check of a RewardPeriod fields.
|
||||||
|
func (rp RewardPeriod) Validate() error {
|
||||||
|
if rp.Start.IsZero() {
|
||||||
|
return errors.New("reward period start time cannot be 0")
|
||||||
|
}
|
||||||
|
if rp.End.IsZero() {
|
||||||
|
return errors.New("reward period end time cannot be 0")
|
||||||
|
}
|
||||||
|
if rp.Start.After(rp.End) {
|
||||||
|
return fmt.Errorf("end period time %s cannot be before start time %s", rp.End, rp.Start)
|
||||||
|
}
|
||||||
|
if !rp.Reward.IsValid() {
|
||||||
|
return fmt.Errorf("invalid reward amount: %s", rp.Reward)
|
||||||
|
}
|
||||||
|
if rp.ClaimEnd.IsZero() {
|
||||||
|
return errors.New("reward period claim end time cannot be 0")
|
||||||
|
}
|
||||||
|
if rp.ClaimTimeLock == 0 {
|
||||||
|
return errors.New("reward claim time lock cannot be 0")
|
||||||
|
}
|
||||||
|
return sdk.ValidateDenom(rp.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
// RewardPeriods array of RewardPeriod
|
// RewardPeriods array of RewardPeriod
|
||||||
type RewardPeriods []RewardPeriod
|
type RewardPeriods []RewardPeriod
|
||||||
|
|
||||||
|
// Validate checks if all the RewardPeriods are valid and there are no duplicated
|
||||||
|
// entries.
|
||||||
|
func (rps RewardPeriods) Validate() error {
|
||||||
|
seenPeriods := make(map[string]bool)
|
||||||
|
for _, rp := range rps {
|
||||||
|
if seenPeriods[rp.Denom] {
|
||||||
|
return fmt.Errorf("duplicated reward period with denom %s", rp.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rp.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
seenPeriods[rp.Denom] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ClaimPeriod stores the state of an ongoing claim period
|
// ClaimPeriod stores the state of an ongoing claim period
|
||||||
type ClaimPeriod struct {
|
type ClaimPeriod struct {
|
||||||
Denom string `json:"denom" yaml:"denom"`
|
Denom string `json:"denom" yaml:"denom"`
|
||||||
@ -52,16 +94,6 @@ type ClaimPeriod struct {
|
|||||||
TimeLock time.Duration `json:"time_lock" yaml:"time_lock"`
|
TimeLock time.Duration `json:"time_lock" yaml:"time_lock"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// String implements fmt.Stringer
|
|
||||||
func (cp ClaimPeriod) String() string {
|
|
||||||
return fmt.Sprintf(`Claim Period:
|
|
||||||
Denom: %s,
|
|
||||||
ID: %d,
|
|
||||||
End: %s,
|
|
||||||
Claim Time Lock: %s
|
|
||||||
`, cp.Denom, cp.ID, cp.End, cp.TimeLock)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClaimPeriod returns a new ClaimPeriod
|
// NewClaimPeriod returns a new ClaimPeriod
|
||||||
func NewClaimPeriod(denom string, id uint64, end time.Time, timeLock time.Duration) ClaimPeriod {
|
func NewClaimPeriod(denom string, id uint64, end time.Time, timeLock time.Duration) ClaimPeriod {
|
||||||
return ClaimPeriod{
|
return ClaimPeriod{
|
||||||
@ -72,9 +104,53 @@ func NewClaimPeriod(denom string, id uint64, end time.Time, timeLock time.Durati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate performs a basic check of a ClaimPeriod fields.
|
||||||
|
func (cp ClaimPeriod) Validate() error {
|
||||||
|
if cp.ID == 0 {
|
||||||
|
return errors.New("claim period id cannot be 0")
|
||||||
|
}
|
||||||
|
if cp.End.IsZero() {
|
||||||
|
return errors.New("claim period end time cannot be 0")
|
||||||
|
}
|
||||||
|
if cp.TimeLock == 0 {
|
||||||
|
return errors.New("claim period time lock cannot be 0")
|
||||||
|
}
|
||||||
|
return sdk.ValidateDenom(cp.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements fmt.Stringer
|
||||||
|
func (cp ClaimPeriod) String() string {
|
||||||
|
return fmt.Sprintf(`Claim Period:
|
||||||
|
Denom: %s,
|
||||||
|
ID: %d,
|
||||||
|
End: %s,
|
||||||
|
Claim Time Lock: %s
|
||||||
|
`, cp.Denom, cp.ID, cp.End, cp.TimeLock)
|
||||||
|
}
|
||||||
|
|
||||||
// ClaimPeriods array of ClaimPeriod
|
// ClaimPeriods array of ClaimPeriod
|
||||||
type ClaimPeriods []ClaimPeriod
|
type ClaimPeriods []ClaimPeriod
|
||||||
|
|
||||||
|
// Validate checks if all the ClaimPeriods are valid and there are no duplicated
|
||||||
|
// entries.
|
||||||
|
func (cps ClaimPeriods) Validate() error {
|
||||||
|
seenPeriods := make(map[string]bool)
|
||||||
|
var key string
|
||||||
|
for _, cp := range cps {
|
||||||
|
key = cp.Denom + string(cp.ID)
|
||||||
|
if seenPeriods[key] {
|
||||||
|
return fmt.Errorf("duplicated claim period with id %d and denom %s", cp.ID, cp.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cp.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
seenPeriods[key] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Claim stores the rewards that can be claimed by owner
|
// Claim stores the rewards that can be claimed by owner
|
||||||
type Claim struct {
|
type Claim struct {
|
||||||
Owner sdk.AccAddress `json:"owner" yaml:"owner"`
|
Owner sdk.AccAddress `json:"owner" yaml:"owner"`
|
||||||
@ -93,6 +169,20 @@ func NewClaim(owner sdk.AccAddress, reward sdk.Coin, denom string, claimPeriodID
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate performs a basic check of a Claim fields.
|
||||||
|
func (c Claim) Validate() error {
|
||||||
|
if c.Owner.Empty() {
|
||||||
|
return errors.New("claim owner cannot be empty")
|
||||||
|
}
|
||||||
|
if !c.Reward.IsValid() {
|
||||||
|
return fmt.Errorf("invalid reward amount: %s", c.Reward)
|
||||||
|
}
|
||||||
|
if c.ClaimPeriodID == 0 {
|
||||||
|
return errors.New("claim period id cannot be 0")
|
||||||
|
}
|
||||||
|
return sdk.ValidateDenom(c.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
// String implements fmt.Stringer
|
// String implements fmt.Stringer
|
||||||
func (c Claim) String() string {
|
func (c Claim) String() string {
|
||||||
return fmt.Sprintf(`Claim:
|
return fmt.Sprintf(`Claim:
|
||||||
@ -106,6 +196,26 @@ func (c Claim) String() string {
|
|||||||
// Claims array of Claim
|
// Claims array of Claim
|
||||||
type Claims []Claim
|
type Claims []Claim
|
||||||
|
|
||||||
|
// Validate checks if all the claims are valid and there are no duplicated
|
||||||
|
// entries.
|
||||||
|
func (cs Claims) Validate() error {
|
||||||
|
seemClaims := make(map[string]bool)
|
||||||
|
var key string
|
||||||
|
for _, c := range cs {
|
||||||
|
key = c.Denom + string(c.ClaimPeriodID) + c.Owner.String()
|
||||||
|
if c.Owner != nil && seemClaims[key] {
|
||||||
|
return fmt.Errorf("duplicated claim from owner %s and denom %s", c.Owner, c.Denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
seemClaims[key] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewRewardPeriodFromReward returns a new reward period from the input reward and block time
|
// NewRewardPeriodFromReward returns a new reward period from the input reward and block time
|
||||||
func NewRewardPeriodFromReward(reward Reward, blockTime time.Time) RewardPeriod {
|
func NewRewardPeriodFromReward(reward Reward, blockTime time.Time) RewardPeriod {
|
||||||
// note: reward periods store the amount of rewards paid PER SECOND
|
// note: reward periods store the amount of rewards paid PER SECOND
|
||||||
|
272
x/incentive/types/rewards_test.go
Normal file
272
x/incentive/types/rewards_test.go
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRewardPeriodsValidate(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
msg string
|
||||||
|
rewardPeriods RewardPeriods
|
||||||
|
expPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"valid",
|
||||||
|
RewardPeriods{
|
||||||
|
NewRewardPeriod("bnb", now, now.Add(time.Hour), sdk.NewCoin("bnb", sdk.OneInt()), now, 10),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zero start time",
|
||||||
|
RewardPeriods{
|
||||||
|
{Start: time.Time{}},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zero end time",
|
||||||
|
RewardPeriods{
|
||||||
|
{Start: now, End: time.Time{}},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zero end time",
|
||||||
|
RewardPeriods{
|
||||||
|
{Start: now, End: time.Time{}},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start time > end time",
|
||||||
|
RewardPeriods{
|
||||||
|
{
|
||||||
|
Start: now.Add(time.Hour),
|
||||||
|
End: now,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid reward",
|
||||||
|
RewardPeriods{
|
||||||
|
{
|
||||||
|
Start: now,
|
||||||
|
End: now.Add(time.Hour),
|
||||||
|
Reward: sdk.Coin{Denom: "", Amount: sdk.ZeroInt()},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zero claim end time",
|
||||||
|
RewardPeriods{
|
||||||
|
{
|
||||||
|
Start: now,
|
||||||
|
End: now.Add(time.Hour),
|
||||||
|
Reward: sdk.NewCoin("bnb", sdk.OneInt()),
|
||||||
|
ClaimEnd: time.Time{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zero claim time lock",
|
||||||
|
RewardPeriods{
|
||||||
|
{
|
||||||
|
Start: now,
|
||||||
|
End: now.Add(time.Hour),
|
||||||
|
Reward: sdk.NewCoin("bnb", sdk.OneInt()),
|
||||||
|
ClaimEnd: now,
|
||||||
|
ClaimTimeLock: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid denom",
|
||||||
|
RewardPeriods{
|
||||||
|
{
|
||||||
|
Start: now,
|
||||||
|
End: now.Add(time.Hour),
|
||||||
|
Reward: sdk.NewCoin("bnb", sdk.OneInt()),
|
||||||
|
ClaimEnd: now,
|
||||||
|
ClaimTimeLock: 10,
|
||||||
|
Denom: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"duplicate reward period",
|
||||||
|
RewardPeriods{
|
||||||
|
NewRewardPeriod("bnb", now, now.Add(time.Hour), sdk.NewCoin("bnb", sdk.OneInt()), now, 10),
|
||||||
|
NewRewardPeriod("bnb", now, now.Add(time.Hour), sdk.NewCoin("bnb", sdk.OneInt()), now, 10),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
err := tc.rewardPeriods.Validate()
|
||||||
|
if tc.expPass {
|
||||||
|
require.NoError(t, err, tc.msg)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err, tc.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClaimPeriodsValidate(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
msg string
|
||||||
|
claimPeriods ClaimPeriods
|
||||||
|
expPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"valid",
|
||||||
|
ClaimPeriods{
|
||||||
|
NewClaimPeriod("bnb", 10, now, 100),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid ID",
|
||||||
|
ClaimPeriods{
|
||||||
|
{ID: 0},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zero end time",
|
||||||
|
ClaimPeriods{
|
||||||
|
{ID: 10, End: time.Time{}},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zero time lock",
|
||||||
|
ClaimPeriods{
|
||||||
|
{ID: 10, End: now, TimeLock: 0},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start time > end time",
|
||||||
|
ClaimPeriods{
|
||||||
|
{ID: 10, End: now, TimeLock: 0},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid denom",
|
||||||
|
ClaimPeriods{
|
||||||
|
{ID: 10, End: now, TimeLock: 100, Denom: ""},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"duplicate reward period",
|
||||||
|
ClaimPeriods{
|
||||||
|
NewClaimPeriod("bnb", 10, now, 100),
|
||||||
|
NewClaimPeriod("bnb", 10, now, 100),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
err := tc.claimPeriods.Validate()
|
||||||
|
if tc.expPass {
|
||||||
|
require.NoError(t, err, tc.msg)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err, tc.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClaimsValidate(t *testing.T) {
|
||||||
|
owner := sdk.AccAddress(tmtypes.NewMockPV().GetPubKey().Address())
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
msg string
|
||||||
|
claims Claims
|
||||||
|
expPass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"valid",
|
||||||
|
Claims{
|
||||||
|
NewClaim(owner, sdk.NewCoin("bnb", sdk.OneInt()), "bnb", 10),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid owner",
|
||||||
|
Claims{
|
||||||
|
{Owner: nil},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid reward",
|
||||||
|
Claims{
|
||||||
|
{
|
||||||
|
Owner: owner,
|
||||||
|
Reward: sdk.Coin{Denom: "", Amount: sdk.ZeroInt()},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zero claim period id",
|
||||||
|
Claims{
|
||||||
|
{
|
||||||
|
Owner: owner,
|
||||||
|
Reward: sdk.NewCoin("bnb", sdk.OneInt()),
|
||||||
|
ClaimPeriodID: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid denom",
|
||||||
|
Claims{
|
||||||
|
{
|
||||||
|
Owner: owner,
|
||||||
|
Reward: sdk.NewCoin("bnb", sdk.OneInt()),
|
||||||
|
ClaimPeriodID: 10,
|
||||||
|
Denom: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"duplicate reward period",
|
||||||
|
Claims{
|
||||||
|
NewClaim(owner, sdk.NewCoin("bnb", sdk.OneInt()), "bnb", 10),
|
||||||
|
NewClaim(owner, sdk.NewCoin("bnb", sdk.OneInt()), "bnb", 10),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
err := tc.claims.Validate()
|
||||||
|
if tc.expPass {
|
||||||
|
require.NoError(t, err, tc.msg)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err, tc.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user