diff --git a/run_a_bunch_of_sims.sh b/run_a_bunch_of_sims.sh new file mode 100755 index 00000000..3fe1f5d7 --- /dev/null +++ b/run_a_bunch_of_sims.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +for i in {1..10}; do + go test ./app -run TestAppStateDeterminism -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed ${i} -v -timeout 24h +done \ No newline at end of file diff --git a/x/incentive/handler_test.go b/x/incentive/handler_test.go new file mode 100644 index 00000000..70253ef3 --- /dev/null +++ b/x/incentive/handler_test.go @@ -0,0 +1,74 @@ +package incentive_test + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/incentive" + "github.com/kava-labs/kava/x/kavadist" + "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" + tmtime "github.com/tendermint/tendermint/types/time" +) + +func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } +func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } + +type HandlerTestSuite struct { + suite.Suite + + ctx sdk.Context + app app.TestApp + handler sdk.Handler + keeper incentive.Keeper + addrs []sdk.AccAddress +} + +func (suite *HandlerTestSuite) SetupTest() { + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()}) + keeper := tApp.GetIncentiveKeeper() + + // Set up genesis state and initialize + _, addrs := app.GeneratePrivKeyAddressPairs(3) + coins := []sdk.Coins{} + for j := 0; j < 3; j++ { + coins = append(coins, cs(c("bnb", 10000000000), c("ukava", 10000000000))) + } + authGS := app.NewAuthGenState(addrs, coins) + tApp.InitializeFromGenesisStates(authGS) + + suite.addrs = addrs + suite.handler = incentive.NewHandler(keeper) + suite.keeper = keeper + suite.app = tApp + suite.ctx = ctx +} + +func (suite *HandlerTestSuite) addClaim() { + supplyKeeper := suite.app.GetSupplyKeeper() + macc := supplyKeeper.GetModuleAccount(suite.ctx, kavadist.ModuleName) + err := supplyKeeper.MintCoins(suite.ctx, macc.GetName(), cs(c("ukava", 1000000))) + suite.Require().NoError(err) + cp := incentive.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), time.Hour*8766) + suite.NotPanics(func() { + suite.keeper.SetClaimPeriod(suite.ctx, cp) + }) + c1 := incentive.NewClaim(suite.addrs[0], c("ukava", 1000000), "bnb", 1) + suite.NotPanics(func() { + suite.keeper.SetClaim(suite.ctx, c1) + }) +} + +func (suite *HandlerTestSuite) TestMsgClaimReward() { + suite.addClaim() + msg := incentive.NewMsgClaimReward(suite.addrs[0], "bnb") + res, err := suite.handler(suite.ctx, msg) + suite.NoError(err) + suite.Require().NotNil(res) +} +func TestHandlerTestSuite(t *testing.T) { + suite.Run(t, new(HandlerTestSuite)) +} diff --git a/x/incentive/keeper/keeper_test.go b/x/incentive/keeper/keeper_test.go new file mode 100644 index 00000000..0d56b2d5 --- /dev/null +++ b/x/incentive/keeper/keeper_test.go @@ -0,0 +1,174 @@ +package keeper_test + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported" + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/incentive/keeper" + "github.com/kava-labs/kava/x/incentive/types" + "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" + tmtime "github.com/tendermint/tendermint/types/time" +) + +// Test suite used for all keeper tests +type KeeperTestSuite struct { + suite.Suite + + keeper keeper.Keeper + app app.TestApp + ctx sdk.Context + addrs []sdk.AccAddress +} + +// The default state used by each test +func (suite *KeeperTestSuite) SetupTest() { + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()}) + tApp.InitializeFromGenesisStates() + _, addrs := app.GeneratePrivKeyAddressPairs(1) + keeper := tApp.GetIncentiveKeeper() + suite.app = tApp + suite.ctx = ctx + suite.keeper = keeper + suite.addrs = addrs +} + +func (suite *KeeperTestSuite) getAccount(addr sdk.AccAddress) authexported.Account { + ak := suite.app.GetAccountKeeper() + return ak.GetAccount(suite.ctx, addr) +} + +func (suite *KeeperTestSuite) getModuleAccount(name string) supplyexported.ModuleAccountI { + sk := suite.app.GetSupplyKeeper() + return sk.GetModuleAccount(suite.ctx, name) +} + +func (suite *KeeperTestSuite) TestGetSetDeleteRewardPeriod() { + rp := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), time.Hour*8766) + _, found := suite.keeper.GetRewardPeriod(suite.ctx, "bnb") + suite.False(found) + suite.NotPanics(func() { + suite.keeper.SetRewardPeriod(suite.ctx, rp) + }) + testRP, found := suite.keeper.GetRewardPeriod(suite.ctx, "bnb") + suite.True(found) + suite.Equal(rp, testRP) + suite.NotPanics(func() { + suite.keeper.DeleteRewardPeriod(suite.ctx, "bnb") + }) + _, found = suite.keeper.GetRewardPeriod(suite.ctx, "bnb") + suite.False(found) +} + +func (suite *KeeperTestSuite) TestGetSetDeleteClaimPeriod() { + cp := types.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), time.Hour*8766) + _, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb") + suite.False(found) + suite.NotPanics(func() { + suite.keeper.SetClaimPeriod(suite.ctx, cp) + }) + testCP, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb") + suite.True(found) + suite.Equal(cp, testCP) + suite.NotPanics(func() { + suite.keeper.DeleteClaimPeriod(suite.ctx, 1, "bnb") + }) + _, found = suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb") + suite.False(found) +} + +func (suite *KeeperTestSuite) TestGetSetClaimPeriodID() { + suite.Panics(func() { + suite.keeper.GetNextClaimPeriodID(suite.ctx, "bnb") + }) + suite.NotPanics(func() { + suite.keeper.SetNextClaimPeriodID(suite.ctx, "bnb", 1) + }) + testID := suite.keeper.GetNextClaimPeriodID(suite.ctx, "bnb") + suite.Equal(uint64(1), testID) +} + +func (suite *KeeperTestSuite) TestGetSetDeleteClaim() { + c := types.NewClaim(suite.addrs[0], c("ukava", 1000000), "bnb", 1) + _, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1) + suite.False(found) + suite.NotPanics(func() { + suite.keeper.SetClaim(suite.ctx, c) + }) + testC, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1) + suite.True(found) + suite.Equal(c, testC) + suite.NotPanics(func() { + suite.keeper.DeleteClaim(suite.ctx, suite.addrs[0], "bnb", 1) + }) + _, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1) + suite.False(found) +} + +func (suite *KeeperTestSuite) TestIterateMethods() { + suite.addObjectsToStore() // adds 2 objects of each type to the store + + var rewardPeriods types.RewardPeriods + suite.keeper.IterateRewardPeriods(suite.ctx, func(rp types.RewardPeriod) (stop bool) { + rewardPeriods = append(rewardPeriods, rp) + return false + }) + suite.Equal(2, len(rewardPeriods)) + + var claimPeriods types.ClaimPeriods + suite.keeper.IterateClaimPeriods(suite.ctx, func(cp types.ClaimPeriod) (stop bool) { + claimPeriods = append(claimPeriods, cp) + return false + }) + suite.Equal(2, len(claimPeriods)) + + var claims types.Claims + suite.keeper.IterateClaims(suite.ctx, func(c types.Claim) (stop bool) { + claims = append(claims, c) + return false + }) + suite.Equal(2, len(claims)) + + var genIDs types.GenesisClaimPeriodIDs + suite.keeper.IterateClaimPeriodIDKeysAndValues(suite.ctx, func(denom string, id uint64) (stop bool) { + genID := types.GenesisClaimPeriodID{Denom: denom, ID: id} + genIDs = append(genIDs, genID) + return false + }) + suite.Equal(2, len(genIDs)) +} + +func (suite *KeeperTestSuite) addObjectsToStore() { + rp1 := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), time.Hour*8766) + rp2 := types.NewRewardPeriod("xrp", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), time.Hour*8766) + suite.keeper.SetRewardPeriod(suite.ctx, rp1) + suite.keeper.SetRewardPeriod(suite.ctx, rp2) + + cp1 := types.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), time.Hour*8766) + cp2 := types.NewClaimPeriod("xrp", 1, suite.ctx.BlockTime().Add(time.Hour*168), time.Hour*8766) + suite.keeper.SetClaimPeriod(suite.ctx, cp1) + suite.keeper.SetClaimPeriod(suite.ctx, cp2) + + suite.keeper.SetNextClaimPeriodID(suite.ctx, "bnb", 1) + suite.keeper.SetNextClaimPeriodID(suite.ctx, "xrp", 1) + + c1 := types.NewClaim(suite.addrs[0], c("ukava", 1000000), "bnb", 1) + c2 := types.NewClaim(suite.addrs[0], c("ukava", 1000000), "xrp", 1) + suite.keeper.SetClaim(suite.ctx, c1) + suite.keeper.SetClaim(suite.ctx, c2) + + params := types.NewParams( + true, types.Rewards{types.NewReward(true, "bnb", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24)}, + ) + suite.keeper.SetParams(suite.ctx, params) + +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} diff --git a/x/incentive/keeper/payout_test.go b/x/incentive/keeper/payout_test.go new file mode 100644 index 00000000..caefc6dd --- /dev/null +++ b/x/incentive/keeper/payout_test.go @@ -0,0 +1,357 @@ +package keeper_test + +import ( + "errors" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/cdp" + "github.com/kava-labs/kava/x/incentive/types" + "github.com/kava-labs/kava/x/kavadist" + validatorvesting "github.com/kava-labs/kava/x/validator-vesting" + abci "github.com/tendermint/tendermint/abci/types" +) + +func (suite *KeeperTestSuite) setupChain() { + // creates a new app state with 4 funded addresses and 1 module account + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: time.Unix(100, 0)}) + _, addrs := app.GeneratePrivKeyAddressPairs(4) + authGS := app.NewAuthGenState( + addrs, + []sdk.Coins{ + cs(c("ukava", 400)), + cs(c("ukava", 400)), + cs(c("ukava", 400)), + cs(c("ukava", 400)), + }) + tApp.InitializeFromGenesisStates( + authGS, + ) + supplyKeeper := tApp.GetSupplyKeeper() + macc := supplyKeeper.GetModuleAccount(ctx, kavadist.ModuleName) + err := supplyKeeper.MintCoins(ctx, macc.GetName(), cs(c("ukava", 500))) + suite.Require().NoError(err) + + // sets addrs[0] to be a periodic vesting account + ak := tApp.GetAccountKeeper() + acc := ak.GetAccount(ctx, addrs[0]) + bacc := auth.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence()) + periods := vesting.Periods{ + vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(8), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + } + bva, err2 := vesting.NewBaseVestingAccount(bacc, cs(c("ukava", 400)), ctx.BlockTime().Unix()+16) + suite.Require().NoError(err2) + pva := vesting.NewPeriodicVestingAccountRaw(bva, ctx.BlockTime().Unix(), periods) + ak.SetAccount(ctx, pva) + + // sets addrs[2] to be a validator vesting account + acc = ak.GetAccount(ctx, addrs[2]) + bacc = auth.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence()) + bva, err2 = vesting.NewBaseVestingAccount(bacc, cs(c("ukava", 400)), ctx.BlockTime().Unix()+16) + suite.Require().NoError(err2) + vva := validatorvesting.NewValidatorVestingAccountRaw(bva, ctx.BlockTime().Unix(), periods, sdk.ConsAddress{}, nil, 90) + ak.SetAccount(ctx, vva) + suite.app = tApp + suite.keeper = tApp.GetIncentiveKeeper() + suite.ctx = ctx + suite.addrs = addrs +} + +func (suite *KeeperTestSuite) setupExpiredClaims() { + // creates a new app state with 4 funded addresses + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: time.Unix(100, 0)}) + _, addrs := app.GeneratePrivKeyAddressPairs(4) + authGS := app.NewAuthGenState( + addrs, + []sdk.Coins{ + cs(c("ukava", 400)), + cs(c("ukava", 400)), + cs(c("ukava", 400)), + cs(c("ukava", 400)), + }) + tApp.InitializeFromGenesisStates( + authGS, + ) + + // creates two claim periods, one expired, and one that expires in the future + cp1 := types.NewClaimPeriod("bnb", 1, time.Unix(90, 0), time.Hour*8766) + cp2 := types.NewClaimPeriod("xrp", 1, time.Unix(110, 0), time.Hour*8766) + suite.keeper = tApp.GetIncentiveKeeper() + suite.keeper.SetClaimPeriod(ctx, cp1) + suite.keeper.SetClaimPeriod(ctx, cp2) + // creates one claim for the non-expired claim period and one claim for the expired claim period + c1 := types.NewClaim(addrs[0], c("ukava", 1000000), "bnb", 1) + c2 := types.NewClaim(addrs[0], c("ukava", 1000000), "xrp", 1) + suite.keeper.SetClaim(ctx, c1) + suite.keeper.SetClaim(ctx, c2) + suite.app = tApp + suite.ctx = ctx + suite.addrs = addrs +} + +func (suite *KeeperTestSuite) TestSendCoinsToPeriodicVestingAccount() { + suite.setupChain() + + type args struct { + coins sdk.Coins + length int64 + } + + type errArgs struct { + expectErr bool + errType error + } + + type vestingAccountTest struct { + name string + blockTime time.Time + args args + errArgs errArgs + expectedPeriods vesting.Periods + expectedOriginalVesting sdk.Coins + expectedCoins sdk.Coins + expectedStartTime int64 + expectedEndTime int64 + } + + type vestingAccountTests []vestingAccountTest + + testCases := vestingAccountTests{ + vestingAccountTest{ + name: "insert period into an existing vesting schedule", + blockTime: time.Unix(100, 0), + args: args{coins: cs(c("ukava", 100)), length: 5}, + errArgs: errArgs{expectErr: false, errType: nil}, + expectedPeriods: vesting.Periods{ + vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(6), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + }, + expectedOriginalVesting: cs(c("ukava", 500)), + expectedCoins: cs(c("ukava", 500)), + expectedStartTime: int64(100), + expectedEndTime: int64(116), + }, + vestingAccountTest{ + name: "append period to the end of an existing vesting schedule", + blockTime: time.Unix(100, 0), + args: args{coins: cs(c("ukava", 100)), length: 17}, + errArgs: errArgs{expectErr: false, errType: nil}, + expectedPeriods: vesting.Periods{ + vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(6), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))}, + }, + expectedOriginalVesting: cs(c("ukava", 600)), + expectedCoins: cs(c("ukava", 600)), + expectedStartTime: int64(100), + expectedEndTime: int64(117), + }, + vestingAccountTest{ + name: "append period to the end of a completed vesting schedule", + blockTime: time.Unix(120, 0), + args: args{coins: cs(c("ukava", 100)), length: 5}, + errArgs: errArgs{expectErr: false, errType: nil}, + expectedPeriods: vesting.Periods{ + vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(6), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(8), Amount: cs(c("ukava", 100))}, + }, + expectedOriginalVesting: cs(c("ukava", 700)), + expectedCoins: cs(c("ukava", 700)), + expectedStartTime: int64(100), + expectedEndTime: int64(125), + }, + vestingAccountTest{ + name: "prepend period to to an upcoming vesting schedule", + blockTime: time.Unix(90, 0), + args: args{coins: cs(c("ukava", 100)), length: 5}, + errArgs: errArgs{expectErr: false, errType: nil}, + expectedPeriods: vesting.Periods{ + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(6), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(6), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(8), Amount: cs(c("ukava", 100))}, + }, + expectedOriginalVesting: cs(c("ukava", 800)), + expectedCoins: cs(c("ukava", 800)), + expectedStartTime: int64(90), + expectedEndTime: int64(125), + }, + vestingAccountTest{ + name: "add period that coincides with an existing end time", + blockTime: time.Unix(90, 0), + args: args{coins: cs(c("ukava", 100)), length: 11}, + errArgs: errArgs{expectErr: false, errType: nil}, + expectedPeriods: vesting.Periods{ + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(6), Amount: cs(c("ukava", 200))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(6), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(8), Amount: cs(c("ukava", 100))}, + }, + expectedOriginalVesting: cs(c("ukava", 900)), + expectedCoins: cs(c("ukava", 900)), + expectedStartTime: int64(90), + expectedEndTime: int64(125), + }, + vestingAccountTest{ + name: "insufficient module account balance", + blockTime: time.Unix(90, 0), + args: args{coins: cs(c("ukava", 1000)), length: 11}, + errArgs: errArgs{expectErr: true, errType: types.ErrInsufficientModAccountBalance}, + expectedPeriods: vesting.Periods{}, + expectedOriginalVesting: sdk.Coins{}, + expectedCoins: sdk.Coins{}, + expectedStartTime: int64(0), + expectedEndTime: int64(0), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.ctx = suite.ctx.WithBlockTime(tc.blockTime) + err := suite.keeper.SendTimeLockedCoinsToAccount(suite.ctx, kavadist.ModuleName, suite.addrs[0], tc.args.coins, tc.args.length) + if tc.errArgs.expectErr { + suite.Require().True(errors.Is(err, tc.errArgs.errType)) + } else { + suite.Require().NoError(err) + acc := suite.getAccount(suite.addrs[0]) + vacc, ok := acc.(*vesting.PeriodicVestingAccount) + suite.True(ok) + suite.Equal(tc.expectedPeriods, vacc.VestingPeriods) + suite.Equal(tc.expectedOriginalVesting, vacc.OriginalVesting) + suite.Equal(tc.expectedCoins, vacc.Coins) + suite.Equal(tc.expectedStartTime, vacc.StartTime) + suite.Equal(tc.expectedEndTime, vacc.EndTime) + } + }) + } +} + +func (suite *KeeperTestSuite) TestSendCoinsToBaseAccount() { + suite.setupChain() + // send coins to base account + err := suite.keeper.SendTimeLockedCoinsToAccount(suite.ctx, kavadist.ModuleName, suite.addrs[1], cs(c("ukava", 100)), 5) + suite.Require().NoError(err) + acc := suite.getAccount(suite.addrs[1]) + vacc, ok := acc.(*vesting.PeriodicVestingAccount) + suite.True(ok) + expectedPeriods := vesting.Periods{ + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + } + suite.Equal(expectedPeriods, vacc.VestingPeriods) + suite.Equal(cs(c("ukava", 100)), vacc.OriginalVesting) + suite.Equal(cs(c("ukava", 500)), vacc.Coins) + suite.Equal(int64(105), vacc.EndTime) + suite.Equal(int64(100), vacc.StartTime) + +} + +func (suite *KeeperTestSuite) TestSendCoinsToInvalidAccount() { + suite.setupChain() + err := suite.keeper.SendTimeLockedCoinsToAccount(suite.ctx, kavadist.ModuleName, suite.addrs[2], cs(c("ukava", 100)), 5) + suite.Require().True(errors.Is(err, types.ErrInvalidAccountType)) + macc := suite.getModuleAccount(cdp.ModuleName) + err = suite.keeper.SendTimeLockedCoinsToAccount(suite.ctx, kavadist.ModuleName, macc.GetAddress(), cs(c("ukava", 100)), 5) + suite.Require().True(errors.Is(err, types.ErrInvalidAccountType)) +} + +func (suite *KeeperTestSuite) TestPayoutClaim() { + suite.setupChain() // adds 3 accounts - 1 periodic vesting account, 1 base account, and 1 validator vesting account + + // add 2 claims that correspond to an existing claim period and one claim that has no corresponding claim period + cp1 := types.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), time.Hour*8766) + suite.keeper.SetClaimPeriod(suite.ctx, cp1) + // valid claim for addrs[0] + c1 := types.NewClaim(suite.addrs[0], c("ukava", 100), "bnb", 1) + // invalid claim for addrs[0] + c2 := types.NewClaim(suite.addrs[0], c("ukava", 100), "xrp", 1) + // valid claim for addrs[1] + c3 := types.NewClaim(suite.addrs[1], c("ukava", 100), "bnb", 1) + suite.keeper.SetClaim(suite.ctx, c1) + suite.keeper.SetClaim(suite.ctx, c2) + suite.keeper.SetClaim(suite.ctx, c3) + + // existing claim with corresponding claim period successfully claimed by existing periodic vesting account + err := suite.keeper.PayoutClaim(suite.ctx, suite.addrs[0], "bnb", 1) + suite.Require().NoError(err) + acc := suite.getAccount(suite.addrs[0]) + // account is a periodic vesting account + vacc, ok := acc.(*vesting.PeriodicVestingAccount) + suite.True(ok) + // vesting balance is correct + suite.Equal(cs(c("ukava", 500)), vacc.OriginalVesting) + + // existing claim with corresponding claim period successfully claimed by base account + err = suite.keeper.PayoutClaim(suite.ctx, suite.addrs[1], "bnb", 1) + suite.Require().NoError(err) + acc = suite.getAccount(suite.addrs[1]) + // account has become a periodic vesting account + vacc, ok = acc.(*vesting.PeriodicVestingAccount) + suite.True(ok) + // vesting balance is correct + suite.Equal(cs(c("ukava", 100)), vacc.OriginalVesting) + + // addrs[3] has no claims + err = suite.keeper.PayoutClaim(suite.ctx, suite.addrs[3], "bnb", 1) + suite.Require().True(errors.Is(err, types.ErrClaimNotFound)) + // addrs[0] has an xrp claim, but there is not corresponding claim period + err = suite.keeper.PayoutClaim(suite.ctx, suite.addrs[0], "xrp", 1) + suite.Require().True(errors.Is(err, types.ErrClaimPeriodNotFound)) +} + +func (suite *KeeperTestSuite) TestDeleteExpiredClaimPeriods() { + suite.setupExpiredClaims() // creates new app state with one non-expired claim period (xrp) and one expired claim period (bnb) as well as a claim that corresponds to each claim period + + // both claim periods are present + _, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb") + suite.True(found) + _, found = suite.keeper.GetClaimPeriod(suite.ctx, 1, "xrp") + suite.True(found) + // both claims are present + _, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1) + suite.True(found) + _, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "xrp", 1) + suite.True(found) + + // expired claim period and associated claims should get deleted + suite.NotPanics(func() { + suite.keeper.DeleteExpiredClaimsAndClaimPeriods(suite.ctx) + }) + // expired claim period and claim are not found + _, found = suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb") + suite.False(found) + _, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1) + suite.False(found) + // non-expired claim period and claim are found + _, found = suite.keeper.GetClaimPeriod(suite.ctx, 1, "xrp") + suite.True(found) + _, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "xrp", 1) + suite.True(found) + +} diff --git a/x/incentive/keeper/querier_test.go b/x/incentive/keeper/querier_test.go new file mode 100644 index 00000000..156d0e8d --- /dev/null +++ b/x/incentive/keeper/querier_test.go @@ -0,0 +1,32 @@ +package keeper_test + +import ( + "strings" + + "github.com/kava-labs/kava/x/incentive/keeper" + "github.com/kava-labs/kava/x/incentive/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +func (suite *KeeperTestSuite) TestQuerier() { + suite.addObjectsToStore() + querier := keeper.NewQuerier(suite.keeper) + bz, err := querier(suite.ctx, []string{types.QueryGetParams}, abci.RequestQuery{}) + suite.Require().NoError(err) + suite.NotNil(bz) + + var p types.Params + suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &p)) + + claimQueryParams := types.NewQueryClaimsParams(suite.addrs[0], "bnb") + query := abci.RequestQuery{ + Path: strings.Join([]string{"custom", types.QuerierRoute, types.QueryGetClaims}, "/"), + Data: types.ModuleCdc.MustMarshalJSON(claimQueryParams), + } + bz, err = querier(suite.ctx, []string{types.QueryGetClaims}, query) + + var claims types.Claims + suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &claims)) + suite.Equal(1, len(claims)) + suite.Equal(types.Claims{types.NewClaim(suite.addrs[0], c("ukava", 1000000), "bnb", 1)}, claims) +} diff --git a/x/incentive/keeper/rewards_test.go b/x/incentive/keeper/rewards_test.go new file mode 100644 index 00000000..2bea860f --- /dev/null +++ b/x/incentive/keeper/rewards_test.go @@ -0,0 +1,258 @@ +package keeper_test + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/cdp" + "github.com/kava-labs/kava/x/incentive/types" + "github.com/kava-labs/kava/x/pricefeed" + abci "github.com/tendermint/tendermint/abci/types" + tmtime "github.com/tendermint/tendermint/types/time" +) + +func (suite *KeeperTestSuite) TestExpireRewardPeriod() { + rp := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), time.Hour*8766) + suite.keeper.SetRewardPeriod(suite.ctx, rp) + suite.keeper.SetNextClaimPeriodID(suite.ctx, "bnb", 1) + suite.NotPanics(func() { + suite.keeper.HandleRewardPeriodExpiry(suite.ctx, rp) + }) + _, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb") + suite.True(found) +} + +func (suite *KeeperTestSuite) TestAddToClaim() { + rp := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), time.Hour*8766) + suite.keeper.SetRewardPeriod(suite.ctx, rp) + suite.keeper.SetNextClaimPeriodID(suite.ctx, "bnb", 1) + suite.keeper.HandleRewardPeriodExpiry(suite.ctx, rp) + c1 := types.NewClaim(suite.addrs[0], c("ukava", 1000000), "bnb", 1) + suite.keeper.SetClaim(suite.ctx, c1) + suite.NotPanics(func() { + suite.keeper.AddToClaim(suite.ctx, suite.addrs[0], "bnb", 1, c("ukava", 1000000)) + }) + testC, _ := suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1) + suite.Equal(c("ukava", 2000000), testC.Reward) + + suite.NotPanics(func() { + suite.keeper.AddToClaim(suite.ctx, suite.addrs[0], "xpr", 1, c("ukava", 1000000)) + }) +} + +func (suite *KeeperTestSuite) TestCreateRewardPeriod() { + reward := types.NewReward(true, "bnb", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24) + suite.NotPanics(func() { + suite.keeper.CreateNewRewardPeriod(suite.ctx, reward) + }) + _, found := suite.keeper.GetRewardPeriod(suite.ctx, "bnb") + suite.True(found) +} + +func (suite *KeeperTestSuite) TestCreateAndDeleteRewardsPeriods() { + reward1 := types.NewReward(true, "bnb", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24) + reward2 := types.NewReward(false, "xrp", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24) + reward3 := types.NewReward(false, "btc", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24) + // add a reward period to the store for a non-active reward + suite.NotPanics(func() { + suite.keeper.CreateNewRewardPeriod(suite.ctx, reward3) + }) + params := types.NewParams(true, types.Rewards{reward1, reward2, reward3}) + suite.keeper.SetParams(suite.ctx, params) + + suite.NotPanics(func() { + suite.keeper.CreateAndDeleteRewardPeriods(suite.ctx) + }) + testCases := []struct { + name string + arg string + expectFound bool + }{ + { + "active reward period", + "bnb", + true, + }, + { + "attempt to add inactive reward period", + "xrp", + false, + }, + { + "remove inactive reward period", + "btc", + false, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + _, found := suite.keeper.GetRewardPeriod(suite.ctx, tc.arg) + if tc.expectFound { + suite.True(found) + } else { + suite.False(found) + } + }) + } +} + +func (suite *KeeperTestSuite) TestApplyRewardsToCdps() { + suite.setupCdpChain() // creates a test app with 3 BNB cdps and usdx incentives for bnb - each reward period is one week + + // move the context forward by 100 periods + suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 100)) + // apply rewards to BNB cdps + suite.NotPanics(func() { + suite.keeper.ApplyRewardsToCdps(suite.ctx) + }) + // each cdp should have a claim + claims := types.Claims{} + suite.keeper.IterateClaims(suite.ctx, func(c types.Claim) (stop bool) { + claims = append(claims, c) + return false + }) + suite.Equal(3, len(claims)) + // there should be no associated claim period, because the reward period has not ended yet + _, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb") + suite.False(found) + + // move ctx to the reward period expiry and check that the claim period has been created and the next claim period id has increased + suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Hour * 24 * 7)) + + suite.NotPanics(func() { + // apply rewards to cdps + suite.keeper.ApplyRewardsToCdps(suite.ctx) + // delete the old reward period amd create a new one + suite.keeper.CreateAndDeleteRewardPeriods(suite.ctx) + }) + _, found = suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb") + suite.True(found) + testID := suite.keeper.GetNextClaimPeriodID(suite.ctx, "bnb") + suite.Equal(uint64(2), testID) + + // move the context forward by 100 periods + suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 100)) + // run the begin blocker functions + suite.NotPanics(func() { + suite.keeper.DeleteExpiredClaimsAndClaimPeriods(suite.ctx) + suite.keeper.ApplyRewardsToCdps(suite.ctx) + suite.keeper.CreateAndDeleteRewardPeriods(suite.ctx) + }) + // each cdp should now have two claims + claims = types.Claims{} + suite.keeper.IterateClaims(suite.ctx, func(c types.Claim) (stop bool) { + claims = append(claims, c) + return false + }) + suite.Equal(6, len(claims)) +} + +func (suite *KeeperTestSuite) setupCdpChain() { + // creates a new test app with bnb as the only asset the pricefeed and cdp modules + // funds three addresses and creates 3 cdps, funded with 100 BNB, 1000 BNB, and 10000 BNB + // each CDP draws 10, 100, and 1000 USDX respectively + // adds usdx incentives for bnb - 1000 KAVA per week with a 1 year time lock + + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()}) + // need pricefeed and cdp gen state with one collateral + pricefeedGS := pricefeed.GenesisState{ + Params: pricefeed.Params{ + Markets: []pricefeed.Market{ + pricefeed.Market{MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + }, + }, + PostedPrices: []pricefeed.PostedPrice{ + pricefeed.PostedPrice{ + MarketID: "bnb:usd", + OracleAddress: sdk.AccAddress{}, + Price: d("12.29"), + Expiry: time.Now().Add(100000 * time.Hour), + }, + }, + } + // need incentive params for one collateral + cdpGS := cdp.GenesisState{ + Params: cdp.Params{ + GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)), + SurplusAuctionThreshold: cdp.DefaultSurplusThreshold, + DebtAuctionThreshold: cdp.DefaultDebtThreshold, + SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency, + CollateralParams: cdp.CollateralParams{ + { + Denom: "bnb", + LiquidationRatio: sdk.MustNewDecFromStr("2.0"), + DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)), + StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr + LiquidationPenalty: d("0.05"), + AuctionSize: i(10000000000), + Prefix: 0x20, + MarketID: "bnb:usd", + ConversionFactor: i(8), + }, + }, + DebtParams: cdp.DebtParams{ + { + Denom: "usdx", + ReferenceAsset: "usd", + ConversionFactor: i(6), + DebtFloor: i(10000000), + SavingsRate: d("0.95"), + }, + }, + }, + StartingCdpID: cdp.DefaultCdpStartingID, + DebtDenom: cdp.DefaultDebtDenom, + GovDenom: cdp.DefaultGovDenom, + CDPs: cdp.CDPs{}, + PreviousDistributionTime: cdp.DefaultPreviousDistributionTime, + } + incentiveGS := types.NewGenesisState( + types.NewParams( + true, types.Rewards{types.NewReward(true, "bnb", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24)}, + ), + types.DefaultPreviousBlockTime, + types.RewardPeriods{types.NewRewardPeriod("bnb", ctx.BlockTime(), ctx.BlockTime().Add(time.Hour*7*24), c("ukava", 1000), ctx.BlockTime().Add(time.Hour*7*24*2), time.Hour*365*24)}, + types.ClaimPeriods{}, + types.Claims{}, + types.GenesisClaimPeriodIDs{}) + pricefeedAppGs := app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pricefeedGS)} + cdpAppGs := app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGS)} + incentiveAppGs := app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(incentiveGS)} + _, addrs := app.GeneratePrivKeyAddressPairs(3) + authGS := app.NewAuthGenState( + addrs[0:3], + []sdk.Coins{ + cs(c("bnb", 10000000000)), + cs(c("bnb", 100000000000)), + cs(c("bnb", 1000000000000)), + }) + tApp.InitializeFromGenesisStates( + authGS, + pricefeedAppGs, + incentiveAppGs, + cdpAppGs, + ) + suite.app = tApp + suite.keeper = tApp.GetIncentiveKeeper() + suite.ctx = ctx + // create 3 cdps + cdpKeeper := tApp.GetCDPKeeper() + err := cdpKeeper.AddCdp(suite.ctx, addrs[0], cs(c("bnb", 10000000000)), cs(c("usdx", 10000000))) + suite.Require().NoError(err) + err = cdpKeeper.AddCdp(suite.ctx, addrs[1], cs(c("bnb", 100000000000)), cs(c("usdx", 100000000))) + suite.Require().NoError(err) + err = cdpKeeper.AddCdp(suite.ctx, addrs[2], cs(c("bnb", 1000000000000)), cs(c("usdx", 1000000000))) + suite.Require().NoError(err) + // total usd is 1110 + + // set the previous block time + suite.keeper.SetPreviousBlockTime(suite.ctx, suite.ctx.BlockTime()) +} + +// Avoid cluttering test cases with long function names +func i(in int64) sdk.Int { return sdk.NewInt(in) } +func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) } +func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } +func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } diff --git a/x/incentive/types/msg_test.go b/x/incentive/types/msg_test.go index 0d77c94e..dbe06fe7 100644 --- a/x/incentive/types/msg_test.go +++ b/x/incentive/types/msg_test.go @@ -47,9 +47,9 @@ func (suite *MsgTestSuite) TestMsgValidation() { msg := types.NewMsgClaimReward(t.from, t.denom) err := msg.ValidateBasic() if t.expectPass { - suite.NoError(err) + suite.Require().NoError(err) } else { - suite.Error(err) + suite.Require().Error(err) } } } diff --git a/x/incentive/types/params.go b/x/incentive/types/params.go index 555beebc..524cfb83 100644 --- a/x/incentive/types/params.go +++ b/x/incentive/types/params.go @@ -112,7 +112,7 @@ func validateRewardsParam(i interface{}) error { 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("reward timelock must be positive, is %s for %s", reward.ClaimDuration.String(), reward.Denom) + return fmt.Errorf("claim duration must be positive, is %s for %s", reward.ClaimDuration.String(), reward.Denom) } } return nil diff --git a/x/incentive/types/params_test.go b/x/incentive/types/params_test.go index 745df28d..a35a2508 100644 --- a/x/incentive/types/params_test.go +++ b/x/incentive/types/params_test.go @@ -1,6 +1,7 @@ package types_test import ( + "strings" "testing" "time" @@ -10,8 +11,14 @@ import ( ) type paramTest struct { - params types.Params + name string + params types.Params + errResult errResult +} + +type errResult struct { expectPass bool + contains string } type ParamTestSuite struct { @@ -21,129 +28,189 @@ type ParamTestSuite struct { } func (suite *ParamTestSuite) SetupTest() { - p1 := types.Params{ - Active: true, - Rewards: types.Rewards{ - types.Reward{ - Active: true, - Denom: "bnb", - AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), - Duration: time.Hour * 24 * 7, - TimeLock: time.Hour * 8766, - ClaimDuration: time.Hour * 24 * 14, - }, - }, - } - p2 := types.Params{ - Active: true, - Rewards: types.Rewards{ - types.Reward{ - Active: true, - Denom: "bnb", - AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), - Duration: time.Hour * 24 * 7, - TimeLock: time.Hour * 8766, - ClaimDuration: time.Hour * 24 * 14, - }, - types.Reward{ - Active: true, - Denom: "bnb", - AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), - Duration: time.Hour * 24 * 7, - TimeLock: time.Hour * 8766, - ClaimDuration: time.Hour * 24 * 14, - }, - }, - } - p3 := types.Params{ - Active: true, - Rewards: types.Rewards{ - types.Reward{ - Active: true, - Denom: "bnb", - AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), - Duration: time.Hour * -24 * 7, - TimeLock: time.Hour * 8766, - ClaimDuration: time.Hour * 24 * 14, - }, - }, - } - p4 := types.Params{ - Active: true, - Rewards: types.Rewards{ - types.Reward{ - Active: true, - Denom: "bnb", - AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), - Duration: time.Hour * 24 * 7, - TimeLock: time.Hour * -8766, - ClaimDuration: time.Hour * 24 * 14, - }, - }, - } - p5 := types.Params{ - Active: true, - Rewards: types.Rewards{ - types.Reward{ - Active: true, - Denom: "bnb", - AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), - Duration: time.Hour * 24 * 7, - TimeLock: time.Hour * 8766, - ClaimDuration: time.Hour * 0, - }, - }, - } - p6 := types.Params{ - Active: true, - Rewards: types.Rewards{ - types.Reward{ - Active: true, - Denom: "bnb", - AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(0)), - Duration: time.Hour * 24 * 7, - TimeLock: time.Hour * 8766, - ClaimDuration: time.Hour * 0, - }, - }, - } - suite.tests = []paramTest{ paramTest{ - params: p1, - expectPass: true, + name: "valid - active", + params: types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + }, + errResult: errResult{ + expectPass: true, + contains: "", + }, }, paramTest{ - params: p2, - expectPass: false, + name: "valid - inactive", + params: types.Params{ + Active: false, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + }, + errResult: errResult{ + expectPass: true, + contains: "", + }, }, paramTest{ - params: p3, - expectPass: false, + name: "duplicate reward", + params: types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + }, + errResult: errResult{ + expectPass: false, + contains: "cannot have duplicate reward denoms", + }, }, paramTest{ - params: p4, - expectPass: false, + name: "negative reward duration", + params: types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * -24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + }, + errResult: errResult{ + expectPass: false, + contains: "reward duration must be positive", + }, }, paramTest{ - params: p5, - expectPass: false, + name: "negative time lock", + params: types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * -8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + }, + errResult: errResult{ + expectPass: false, + contains: "reward timelock must be non-negative", + }, }, paramTest{ - params: p6, - expectPass: false, + name: "zero claim duration", + params: types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 0, + }, + }, + }, + errResult: errResult{ + expectPass: false, + contains: "claim duration must be positive", + }, + }, + paramTest{ + name: "zero reward", + params: types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(0)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + }, + errResult: errResult{ + expectPass: false, + contains: "reward amount must be positive", + }, + }, + paramTest{ + name: "empty reward denom", + params: types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(0)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + }, + errResult: errResult{ + expectPass: false, + contains: "cannot have empty reward denom", + }, }, } } func (suite *ParamTestSuite) TestParamValidation() { for _, t := range suite.tests { - err := t.params.Validate() - if t.expectPass { - suite.NoError(err) - } else { - suite.Error(err) - } + suite.Run(t.name, func() { + err := t.params.Validate() + if t.errResult.expectPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().True(strings.Contains(err.Error(), t.errResult.contains)) + } + }) } }