mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-24 15:25: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
|
||||
func genRewards(r *rand.Rand) types.Rewards {
|
||||
var rewards types.Rewards
|
||||
for _, denom := range CollateralDenoms {
|
||||
rewards := make(types.Rewards, len(CollateralDenoms))
|
||||
for i, denom := range CollateralDenoms {
|
||||
active := true
|
||||
// total reward is in range (half max total reward, max total reward)
|
||||
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))
|
||||
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
|
||||
reward := types.NewReward(active, denom, totalRewards, duration, timeLock, claimDuration)
|
||||
rewards = append(rewards, reward)
|
||||
rewards[i] = types.NewReward(active, denom, totalRewards, duration, timeLock, claimDuration)
|
||||
}
|
||||
return rewards
|
||||
}
|
||||
|
||||
// genRewardPeriods generates chronological reward periods for each given reward type
|
||||
func genRewardPeriods(r *rand.Rand, timestamp time.Time, rewards types.Rewards) types.RewardPeriods {
|
||||
var rewardPeriods types.RewardPeriods
|
||||
for _, reward := range rewards {
|
||||
rewardPeriodStart := timestamp
|
||||
for i := 10; i >= simulation.RandIntBetween(r, 2, 9); i-- {
|
||||
// Set up reward period parameters
|
||||
start := rewardPeriodStart
|
||||
end := start.Add(reward.Duration).UTC()
|
||||
baseRewardAmount := reward.AvailableRewards.Amount.Quo(sdk.NewInt(100)) // base period reward is 1/100 total reward
|
||||
// Earlier periods have larger rewards
|
||||
amount := sdk.NewCoin(reward.Denom, baseRewardAmount.Mul(sdk.NewInt(int64(i))))
|
||||
claimEnd := end.Add(reward.ClaimDuration)
|
||||
claimTimeLock := reward.TimeLock
|
||||
// Create reward period and append to array
|
||||
rewardPeriod := types.NewRewardPeriod(reward.Denom, start, end, amount, claimEnd, claimTimeLock)
|
||||
rewardPeriods = append(rewardPeriods, rewardPeriod)
|
||||
// Update start time of next reward period
|
||||
rewardPeriodStart = end
|
||||
}
|
||||
rewardPeriods := make(types.RewardPeriods, len(rewards))
|
||||
rewardPeriodStart := timestamp
|
||||
|
||||
for i, reward := range rewards {
|
||||
// Set up reward period parameters
|
||||
start := rewardPeriodStart
|
||||
end := start.Add(reward.Duration).UTC()
|
||||
baseRewardAmount := reward.AvailableRewards.Amount.Quo(sdk.NewInt(100)) // base period reward is 1/100 total reward
|
||||
// Earlier periods have larger rewards
|
||||
amount := sdk.NewCoin(reward.Denom, baseRewardAmount.Mul(sdk.NewInt(int64(i))))
|
||||
claimEnd := end.Add(reward.ClaimDuration)
|
||||
claimTimeLock := reward.TimeLock
|
||||
// Create reward period and append to array
|
||||
rewardPeriods[i] = types.NewRewardPeriod(reward.Denom, start, end, amount, claimEnd, claimTimeLock)
|
||||
// Update start time of next reward period
|
||||
rewardPeriodStart = end
|
||||
}
|
||||
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
|
||||
func genClaimPeriods(rewardPeriods types.RewardPeriods) types.ClaimPeriods {
|
||||
denomRewardPeriodsCount := make(map[string]uint64)
|
||||
var claimPeriods types.ClaimPeriods
|
||||
for _, rewardPeriod := range rewardPeriods {
|
||||
claimPeriods := make(types.ClaimPeriods, len(rewardPeriods))
|
||||
for i, rewardPeriod := range rewardPeriods {
|
||||
// Increment reward period count for this denom (this is our claim period's ID)
|
||||
denom := rewardPeriod.Denom
|
||||
numbRewardPeriods := denomRewardPeriodsCount[denom] + 1
|
||||
@ -102,8 +99,7 @@ func genClaimPeriods(rewardPeriods types.RewardPeriods) types.ClaimPeriods {
|
||||
end := rewardPeriod.ClaimEnd
|
||||
claimTimeLock := rewardPeriod.ClaimTimeLock
|
||||
// Create the new claim period for this reward period
|
||||
claimPeriod := types.NewClaimPeriod(denom, numbRewardPeriods, end, claimTimeLock)
|
||||
claimPeriods = append(claimPeriods, claimPeriod)
|
||||
claimPeriods[i] = types.NewClaimPeriod(denom, numbRewardPeriods, end, claimTimeLock)
|
||||
}
|
||||
return claimPeriods
|
||||
}
|
||||
@ -112,16 +108,14 @@ func genClaimPeriods(rewardPeriods types.RewardPeriods) types.ClaimPeriods {
|
||||
func genNextClaimPeriodIds(cps types.ClaimPeriods) types.GenesisClaimPeriodIDs {
|
||||
// Build a map of the most recent claim periods by denom
|
||||
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
|
||||
for key, value := range mostRecentClaimPeriodByDenom {
|
||||
claimPeriodID := types.GenesisClaimPeriodID{Denom: key, ID: value}
|
||||
claimPeriodIDs = append(claimPeriodIDs, claimPeriodID)
|
||||
for _, cp := range cps {
|
||||
if cp.ID <= mostRecentClaimPeriodByDenom[cp.Denom] {
|
||||
continue
|
||||
}
|
||||
claimPeriodIDs = append(claimPeriodIDs, types.GenesisClaimPeriodID{Denom: cp.Denom, ID: cp.ID})
|
||||
mostRecentClaimPeriodByDenom[cp.Denom] = cp.ID
|
||||
|
||||
}
|
||||
return claimPeriodIDs
|
||||
}
|
||||
|
@ -2,8 +2,11 @@ package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// GenesisClaimPeriodID stores the next claim id and its corresponding denom
|
||||
@ -12,9 +15,37 @@ type GenesisClaimPeriodID struct {
|
||||
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
|
||||
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.
|
||||
type GenesisState struct {
|
||||
Params Params `json:"params" yaml:"params"`
|
||||
@ -52,14 +83,22 @@ func DefaultGenesisState() GenesisState {
|
||||
// Validate performs basic validation of genesis data returning an
|
||||
// error for any failed validation criteria.
|
||||
func (gs GenesisState) Validate() error {
|
||||
|
||||
if err := gs.Params.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
|
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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
@ -71,11 +70,7 @@ func (p Params) Validate() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateRewardsParam(p.Rewards); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return validateRewardsParam(p.Rewards)
|
||||
}
|
||||
|
||||
func validateActiveParam(i interface{}) error {
|
||||
@ -91,33 +86,8 @@ func validateRewardsParam(i interface{}) error {
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid parameter type: %T", i)
|
||||
}
|
||||
rewardDenoms := make(map[string]bool)
|
||||
|
||||
for _, reward := range rewards {
|
||||
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
|
||||
return rewards.Validate()
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
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
|
||||
func (rs Rewards) String() string {
|
||||
out := "Rewards\n"
|
||||
|
@ -187,7 +187,7 @@ func (suite *ParamTestSuite) SetupTest() {
|
||||
types.Reward{
|
||||
Active: true,
|
||||
Denom: "",
|
||||
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(0)),
|
||||
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(1)),
|
||||
Duration: time.Hour * 24 * 7,
|
||||
TimeLock: time.Hour * 8766,
|
||||
ClaimDuration: time.Hour * 24 * 14,
|
||||
@ -196,7 +196,7 @@ func (suite *ParamTestSuite) SetupTest() {
|
||||
},
|
||||
errResult: errResult{
|
||||
expectPass: false,
|
||||
contains: "cannot have empty reward denom",
|
||||
contains: "invalid denom",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"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
|
||||
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
|
||||
type ClaimPeriod struct {
|
||||
Denom string `json:"denom" yaml:"denom"`
|
||||
@ -52,16 +94,6 @@ type ClaimPeriod struct {
|
||||
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
|
||||
func NewClaimPeriod(denom string, id uint64, end time.Time, timeLock time.Duration) 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
|
||||
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
|
||||
type Claim struct {
|
||||
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
|
||||
func (c Claim) String() string {
|
||||
return fmt.Sprintf(`Claim:
|
||||
@ -106,6 +196,26 @@ func (c Claim) String() string {
|
||||
// Claims array of 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
|
||||
func NewRewardPeriodFromReward(reward Reward, blockTime time.Time) RewardPeriod {
|
||||
// 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