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:
Federico Kunze 2020-05-28 10:26:08 -04:00 committed by GitHub
parent b2edeb8549
commit d130734c2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 668 additions and 82 deletions

View File

@ -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,32 +59,29 @@ 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()
baseRewardAmount := reward.AvailableRewards.Amount.Quo(sdk.NewInt(100)) // base period reward is 1/100 total reward baseRewardAmount := reward.AvailableRewards.Amount.Quo(sdk.NewInt(100)) // base period reward is 1/100 total reward
// Earlier periods have larger rewards // Earlier periods have larger rewards
amount := sdk.NewCoin(reward.Denom, baseRewardAmount.Mul(sdk.NewInt(int64(i)))) amount := sdk.NewCoin(reward.Denom, baseRewardAmount.Mul(sdk.NewInt(int64(i))))
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
} }
@ -92,8 +89,8 @@ func genRewardPeriods(r *rand.Rand, timestamp time.Time, rewards types.Rewards)
// 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
} }

View File

@ -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

View 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)
}
}
}

View File

@ -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"

View File

@ -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",
}, },
}, },
} }

View File

@ -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

View 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)
}
}
}