mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-11-10 10:05:18 +00:00
Add incentive migrations for earn rewards (#1406)
* Add initial earn claim migrations * Use existing types for migrations, add accural time migrations * Add MigrateRewardIndexes * Delete old state after migration * Update store test with multiple entries * Move key methods to keys.go * Update incentive consensus version to 3 * Call MigrateRewardIndexes in main migration, remove debugging statements * Fix migration version to v3 * Update module versions * Update outdated v1 comment
This commit is contained in:
parent
35041fd909
commit
937e5f339f
@ -56,8 +56,11 @@ var (
|
|||||||
// TestApp is a simple wrapper around an App. It exposes internal keepers for use in integration tests.
|
// TestApp is a simple wrapper around an App. It exposes internal keepers for use in integration tests.
|
||||||
// This file also contains test helpers. Ideally they would be in separate package.
|
// This file also contains test helpers. Ideally they would be in separate package.
|
||||||
// Basic Usage:
|
// Basic Usage:
|
||||||
|
//
|
||||||
// Create a test app with NewTestApp, then all keepers and their methods can be accessed for test setup and execution.
|
// Create a test app with NewTestApp, then all keepers and their methods can be accessed for test setup and execution.
|
||||||
|
//
|
||||||
// Advanced Usage:
|
// Advanced Usage:
|
||||||
|
//
|
||||||
// Some tests call for an app to be initialized with some state. This can be achieved through keeper method calls (ie keeper.SetParams(...)).
|
// Some tests call for an app to be initialized with some state. This can be achieved through keeper method calls (ie keeper.SetParams(...)).
|
||||||
// However this leads to a lot of duplicated logic similar to InitGenesis methods.
|
// However this leads to a lot of duplicated logic similar to InitGenesis methods.
|
||||||
// So TestApp.InitializeFromGenesisStates() will call InitGenesis with the default genesis state.
|
// So TestApp.InitializeFromGenesisStates() will call InitGenesis with the default genesis state.
|
||||||
@ -115,6 +118,10 @@ func (tApp TestApp) GetLiquidKeeper() liquidkeeper.Keeper { return tApp.li
|
|||||||
func (tApp TestApp) GetEarnKeeper() earnkeeper.Keeper { return tApp.earnKeeper }
|
func (tApp TestApp) GetEarnKeeper() earnkeeper.Keeper { return tApp.earnKeeper }
|
||||||
func (tApp TestApp) GetRouterKeeper() routerkeeper.Keeper { return tApp.routerKeeper }
|
func (tApp TestApp) GetRouterKeeper() routerkeeper.Keeper { return tApp.routerKeeper }
|
||||||
|
|
||||||
|
func (tApp TestApp) GetKeys() map[string]*sdk.KVStoreKey {
|
||||||
|
return tApp.keys
|
||||||
|
}
|
||||||
|
|
||||||
// LegacyAmino returns the app's amino codec.
|
// LegacyAmino returns the app's amino codec.
|
||||||
func (app *App) LegacyAmino() *codec.LegacyAmino {
|
func (app *App) LegacyAmino() *codec.LegacyAmino {
|
||||||
return app.legacyAmino
|
return app.legacyAmino
|
||||||
|
52
x/incentive/migrations/v3/keys.go
Normal file
52
x/incentive/migrations/v3/keys.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package v3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Legacy store key prefixes
|
||||||
|
var (
|
||||||
|
EarnClaimKeyPrefix = []byte{0x18} // prefix for keys that store earn claims
|
||||||
|
EarnRewardIndexesKeyPrefix = []byte{0x19} // prefix for key that stores earn reward indexes
|
||||||
|
PreviousEarnRewardAccrualTimeKeyPrefix = []byte{0x20} // prefix for key that stores the previous time earn rewards accrued
|
||||||
|
)
|
||||||
|
|
||||||
|
func LegacyAccrualTimeKeyFromClaimType(claimType types.ClaimType) []byte {
|
||||||
|
switch claimType {
|
||||||
|
case types.CLAIM_TYPE_HARD_BORROW:
|
||||||
|
panic("todo")
|
||||||
|
case types.CLAIM_TYPE_HARD_SUPPLY:
|
||||||
|
panic("todo")
|
||||||
|
case types.CLAIM_TYPE_EARN:
|
||||||
|
return PreviousEarnRewardAccrualTimeKeyPrefix
|
||||||
|
case types.CLAIM_TYPE_SAVINGS:
|
||||||
|
panic("todo")
|
||||||
|
case types.CLAIM_TYPE_SWAP:
|
||||||
|
panic("todo")
|
||||||
|
case types.CLAIM_TYPE_USDX_MINTING:
|
||||||
|
panic("todo")
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unrecognized claim type: %s", claimType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LegacyRewardIndexesKeyFromClaimType(claimType types.ClaimType) []byte {
|
||||||
|
switch claimType {
|
||||||
|
case types.CLAIM_TYPE_HARD_BORROW:
|
||||||
|
panic("todo")
|
||||||
|
case types.CLAIM_TYPE_HARD_SUPPLY:
|
||||||
|
panic("todo")
|
||||||
|
case types.CLAIM_TYPE_EARN:
|
||||||
|
return EarnRewardIndexesKeyPrefix
|
||||||
|
case types.CLAIM_TYPE_SAVINGS:
|
||||||
|
panic("todo")
|
||||||
|
case types.CLAIM_TYPE_SWAP:
|
||||||
|
panic("todo")
|
||||||
|
case types.CLAIM_TYPE_USDX_MINTING:
|
||||||
|
panic("todo")
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unrecognized claim type: %s", claimType))
|
||||||
|
}
|
||||||
|
}
|
142
x/incentive/migrations/v3/store.go
Normal file
142
x/incentive/migrations/v3/store.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package v3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||||
|
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MigrateStore performs in-place migrations from incentive ConsensusVersion 2 to 3.
|
||||||
|
func MigrateStore(ctx sdk.Context, storeKey storetypes.StoreKey, cdc codec.BinaryCodec) error {
|
||||||
|
store := ctx.KVStore(storeKey)
|
||||||
|
|
||||||
|
if err := MigrateEarnClaims(store, cdc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := MigrateAccrualTimes(store, cdc, types.CLAIM_TYPE_EARN); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := MigrateRewardIndexes(store, cdc, types.CLAIM_TYPE_EARN); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MigrateEarnClaims migrates earn claims from v2 to v3
|
||||||
|
func MigrateEarnClaims(store sdk.KVStore, cdc codec.BinaryCodec) error {
|
||||||
|
newStore := prefix.NewStore(store, types.GetClaimKeyPrefix(types.CLAIM_TYPE_EARN))
|
||||||
|
|
||||||
|
oldStore := prefix.NewStore(store, EarnClaimKeyPrefix)
|
||||||
|
iterator := sdk.KVStorePrefixIterator(oldStore, []byte{})
|
||||||
|
defer iterator.Close()
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
var c types.EarnClaim
|
||||||
|
cdc.MustUnmarshal(iterator.Value(), &c)
|
||||||
|
|
||||||
|
if err := c.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("invalid v2 EarnClaim: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to the new Claim type
|
||||||
|
newClaim := types.NewClaim(
|
||||||
|
types.CLAIM_TYPE_EARN,
|
||||||
|
c.Owner,
|
||||||
|
c.Reward,
|
||||||
|
c.RewardIndexes,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := newClaim.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("invalid v3 EarnClaim: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set in the **newStore** for the new store prefix
|
||||||
|
newStore.Set(c.Owner, cdc.MustMarshal(&newClaim))
|
||||||
|
|
||||||
|
// Remove the old claim in the old store
|
||||||
|
oldStore.Delete(iterator.Key())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MigrateAccrualTimes migrates accrual times from v2 to v3
|
||||||
|
func MigrateAccrualTimes(
|
||||||
|
store sdk.KVStore,
|
||||||
|
cdc codec.BinaryCodec,
|
||||||
|
claimType types.ClaimType,
|
||||||
|
) error {
|
||||||
|
newStore := prefix.NewStore(store, types.GetPreviousRewardAccrualTimeKeyPrefix(claimType))
|
||||||
|
|
||||||
|
// Need prefix.NewStore instead of using it directly in the iterator, as
|
||||||
|
// there would be an extra space in the key
|
||||||
|
legacyPrefix := LegacyAccrualTimeKeyFromClaimType(claimType)
|
||||||
|
oldStore := prefix.NewStore(store, legacyPrefix)
|
||||||
|
iterator := sdk.KVStorePrefixIterator(oldStore, []byte{})
|
||||||
|
|
||||||
|
defer iterator.Close()
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
var blockTime time.Time
|
||||||
|
if err := blockTime.UnmarshalBinary(iterator.Value()); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceID := string(iterator.Key())
|
||||||
|
|
||||||
|
at := types.NewAccrualTime(claimType, sourceID, blockTime)
|
||||||
|
if err := at.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("invalid v3 accrual time for claim type %s: %w", claimType, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set in the **newStore** for the new store prefix
|
||||||
|
bz := cdc.MustMarshal(&at)
|
||||||
|
newStore.Set(types.GetKeyFromSourceID(sourceID), bz)
|
||||||
|
|
||||||
|
// Remove the old accrual time in the old store
|
||||||
|
oldStore.Delete(iterator.Key())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MigrateRewardIndexes migrates reward indexes from v2 to v3
|
||||||
|
func MigrateRewardIndexes(
|
||||||
|
store sdk.KVStore,
|
||||||
|
cdc codec.BinaryCodec,
|
||||||
|
claimType types.ClaimType,
|
||||||
|
) error {
|
||||||
|
newStore := prefix.NewStore(store, types.GetRewardIndexesKeyPrefix(claimType))
|
||||||
|
|
||||||
|
legacyPrefix := LegacyRewardIndexesKeyFromClaimType(claimType)
|
||||||
|
oldStore := prefix.NewStore(store, legacyPrefix)
|
||||||
|
iterator := sdk.KVStorePrefixIterator(oldStore, []byte{})
|
||||||
|
|
||||||
|
defer iterator.Close()
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
var proto types.RewardIndexesProto
|
||||||
|
cdc.MustUnmarshal(iterator.Value(), &proto)
|
||||||
|
|
||||||
|
sourceID := string(iterator.Key())
|
||||||
|
|
||||||
|
rewardIndex := types.NewTypedRewardIndexes(
|
||||||
|
claimType,
|
||||||
|
sourceID,
|
||||||
|
proto.RewardIndexes,
|
||||||
|
)
|
||||||
|
|
||||||
|
bz := cdc.MustMarshal(&rewardIndex)
|
||||||
|
newStore.Set(types.GetKeyFromSourceID(sourceID), bz)
|
||||||
|
|
||||||
|
// Remove the old reward indexes in the old store
|
||||||
|
oldStore.Delete(iterator.Key())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
159
x/incentive/migrations/v3/store_test.go
Normal file
159
x/incentive/migrations/v3/store_test.go
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
package v3_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/app"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/testutil"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
v3 "github.com/kava-labs/kava/x/incentive/migrations/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StoreMigrateTestSuite struct {
|
||||||
|
testutil.IntegrationTester
|
||||||
|
|
||||||
|
Addrs []sdk.AccAddress
|
||||||
|
|
||||||
|
keeper testutil.TestKeeper
|
||||||
|
storeKey sdk.StoreKey
|
||||||
|
cdc codec.Codec
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStoreMigrateTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(StoreMigrateTestSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *StoreMigrateTestSuite) SetupTest() {
|
||||||
|
suite.IntegrationTester.SetupTest()
|
||||||
|
|
||||||
|
suite.keeper = testutil.TestKeeper{
|
||||||
|
Keeper: suite.App.GetIncentiveKeeper(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, suite.Addrs = app.GeneratePrivKeyAddressPairs(5)
|
||||||
|
suite.cdc = suite.App.AppCodec()
|
||||||
|
suite.storeKey = suite.App.GetKeys()[types.StoreKey]
|
||||||
|
|
||||||
|
suite.StartChain()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *StoreMigrateTestSuite) TestMigrateEarnClaims() {
|
||||||
|
store := suite.Ctx.KVStore(suite.storeKey)
|
||||||
|
|
||||||
|
// Create v2 earn claims
|
||||||
|
claim1 := types.NewEarnClaim(
|
||||||
|
suite.Addrs[0],
|
||||||
|
sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(100))),
|
||||||
|
types.MultiRewardIndexes{
|
||||||
|
types.NewMultiRewardIndex("bnb-a", types.RewardIndexes{
|
||||||
|
types.NewRewardIndex("bnb", sdk.NewDec(1)),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
claim2 := types.NewEarnClaim(
|
||||||
|
suite.Addrs[1],
|
||||||
|
sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(100))),
|
||||||
|
types.MultiRewardIndexes{
|
||||||
|
types.NewMultiRewardIndex("ukava", types.RewardIndexes{
|
||||||
|
types.NewRewardIndex("ukava", sdk.NewDec(1)),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.keeper.SetEarnClaim(suite.Ctx, claim1)
|
||||||
|
suite.keeper.SetEarnClaim(suite.Ctx, claim2)
|
||||||
|
|
||||||
|
// Run earn claim migrations
|
||||||
|
err := v3.MigrateEarnClaims(store, suite.cdc)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// Check that the claim was migrated correctly
|
||||||
|
newClaim1, found := suite.keeper.Store.GetClaim(suite.Ctx, types.CLAIM_TYPE_EARN, claim1.Owner)
|
||||||
|
suite.Require().True(found)
|
||||||
|
suite.Require().Equal(claim1.Owner, newClaim1.Owner)
|
||||||
|
|
||||||
|
newClaim2, found := suite.keeper.Store.GetClaim(suite.Ctx, types.CLAIM_TYPE_EARN, claim2.Owner)
|
||||||
|
suite.Require().True(found)
|
||||||
|
suite.Require().Equal(claim2.Owner, newClaim2.Owner)
|
||||||
|
|
||||||
|
// Ensure removed from old store
|
||||||
|
_, found = suite.keeper.GetEarnClaim(suite.Ctx, claim1.Owner)
|
||||||
|
suite.Require().False(found)
|
||||||
|
|
||||||
|
_, found = suite.keeper.GetEarnClaim(suite.Ctx, claim2.Owner)
|
||||||
|
suite.Require().False(found)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *StoreMigrateTestSuite) TestMigrateAccrualTimes() {
|
||||||
|
store := suite.Ctx.KVStore(suite.storeKey)
|
||||||
|
vaultDenom1 := "ukava"
|
||||||
|
vaultDenom2 := "usdc"
|
||||||
|
|
||||||
|
// Create v2 accrual times
|
||||||
|
accrualTime1 := time.Now()
|
||||||
|
accrualTime2 := time.Now().Add(time.Hour * 24)
|
||||||
|
suite.keeper.SetEarnRewardAccrualTime(suite.Ctx, vaultDenom1, accrualTime1)
|
||||||
|
suite.keeper.SetEarnRewardAccrualTime(suite.Ctx, vaultDenom2, accrualTime2)
|
||||||
|
|
||||||
|
// Run accrual time migrations
|
||||||
|
err := v3.MigrateAccrualTimes(store, suite.cdc, types.CLAIM_TYPE_EARN)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// Check that the accrual time was migrated correctly
|
||||||
|
newAccrualTime1, found := suite.keeper.Store.GetRewardAccrualTime(suite.Ctx, types.CLAIM_TYPE_EARN, vaultDenom1)
|
||||||
|
suite.Require().True(found)
|
||||||
|
suite.Require().Equal(accrualTime1.Unix(), newAccrualTime1.Unix())
|
||||||
|
|
||||||
|
newAccrualTime2, found := suite.keeper.Store.GetRewardAccrualTime(suite.Ctx, types.CLAIM_TYPE_EARN, vaultDenom2)
|
||||||
|
suite.Require().True(found)
|
||||||
|
suite.Require().Equal(accrualTime2.Unix(), newAccrualTime2.Unix())
|
||||||
|
|
||||||
|
// Ensure removed from old store
|
||||||
|
_, found = suite.keeper.GetEarnRewardAccrualTime(suite.Ctx, vaultDenom1)
|
||||||
|
suite.Require().False(found)
|
||||||
|
_, found = suite.keeper.GetEarnRewardAccrualTime(suite.Ctx, vaultDenom2)
|
||||||
|
suite.Require().False(found)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *StoreMigrateTestSuite) TestMigrateRewardIndexes() {
|
||||||
|
store := suite.Ctx.KVStore(suite.storeKey)
|
||||||
|
vaultDenom1 := "ukava"
|
||||||
|
vaultDenom2 := "usdc"
|
||||||
|
|
||||||
|
rewardIndexes1 := types.RewardIndexes{
|
||||||
|
types.NewRewardIndex("ukava", sdk.NewDec(1)),
|
||||||
|
types.NewRewardIndex("hard", sdk.NewDec(2)),
|
||||||
|
}
|
||||||
|
rewardIndexes2 := types.RewardIndexes{
|
||||||
|
types.NewRewardIndex("ukava", sdk.NewDec(4)),
|
||||||
|
types.NewRewardIndex("swp", sdk.NewDec(10)),
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.keeper.SetEarnRewardIndexes(suite.Ctx, vaultDenom1, rewardIndexes1)
|
||||||
|
suite.keeper.SetEarnRewardIndexes(suite.Ctx, vaultDenom2, rewardIndexes2)
|
||||||
|
|
||||||
|
err := v3.MigrateRewardIndexes(store, suite.cdc, types.CLAIM_TYPE_EARN)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
newRewardIndexes1, found := suite.keeper.Store.GetRewardIndexesOfClaimType(suite.Ctx, types.CLAIM_TYPE_EARN, vaultDenom1)
|
||||||
|
suite.Require().True(found)
|
||||||
|
suite.Require().Equal(rewardIndexes1, newRewardIndexes1)
|
||||||
|
|
||||||
|
newRewardIndexes2, found := suite.keeper.Store.GetRewardIndexesOfClaimType(suite.Ctx, types.CLAIM_TYPE_EARN, vaultDenom2)
|
||||||
|
suite.Require().True(found)
|
||||||
|
suite.Require().Equal(rewardIndexes2, newRewardIndexes2)
|
||||||
|
|
||||||
|
// Ensure removed from old store
|
||||||
|
_, found = suite.keeper.GetEarnRewardIndexes(suite.Ctx, vaultDenom1)
|
||||||
|
suite.Require().False(found)
|
||||||
|
|
||||||
|
_, found = suite.keeper.GetEarnRewardIndexes(suite.Ctx, vaultDenom2)
|
||||||
|
suite.Require().False(found)
|
||||||
|
}
|
@ -21,6 +21,9 @@ import (
|
|||||||
"github.com/kava-labs/kava/x/incentive/types"
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ConsensusVersion defines the current module consensus version.
|
||||||
|
const ConsensusVersion = 3
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ module.AppModule = AppModule{}
|
_ module.AppModule = AppModule{}
|
||||||
_ module.AppModuleBasic = AppModuleBasic{}
|
_ module.AppModuleBasic = AppModuleBasic{}
|
||||||
@ -81,7 +84,7 @@ func (am AppModule) LegacyQuerierHandler(legacyQuerierCdc *codec.LegacyAmino) sd
|
|||||||
|
|
||||||
// ConsensusVersion implements AppModule/ConsensusVersion.
|
// ConsensusVersion implements AppModule/ConsensusVersion.
|
||||||
func (AppModule) ConsensusVersion() uint64 {
|
func (AppModule) ConsensusVersion() uint64 {
|
||||||
return 1
|
return ConsensusVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTxCmd returns the root tx command for the incentive module.
|
// GetTxCmd returns the root tx command for the incentive module.
|
||||||
|
@ -200,6 +200,10 @@ func NewAccrualTime(claimType ClaimType, collateralType string, prevTime time.Ti
|
|||||||
|
|
||||||
// Validate performs validation of AccrualTime
|
// Validate performs validation of AccrualTime
|
||||||
func (at AccrualTime) Validate() error {
|
func (at AccrualTime) Validate() error {
|
||||||
|
if at.PreviousAccumulationTime.IsZero() {
|
||||||
|
return fmt.Errorf("previous accumulation time cannot be zero")
|
||||||
|
}
|
||||||
|
|
||||||
if err := at.ClaimType.Validate(); err != nil {
|
if err := at.ClaimType.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user