mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-24 15:25:18 +00:00
Refactor incentive accumulators to be the same (#970)
* add test for validate multi reward periods * tidy up: combine files * don't accumulate global indexes containing zeros Previously if the time since last block was 0, indexes were added containing 0s. Now leave them out. Missing is assumed to be 0. * move state independent test to types folder * clarify reward source concept to "source shares" - rename variables and update doc comments - extract method from swap accumulation * tidy up and expand swap accumulation unit tests * rename swap test file to match others * update swap pool id format in tests * refactor borrow accumulation, use new accumulator * refactor supply accumulation, use new accumulator * refactor delegator accumulation, use accumulator * refactor usdx accumulation, use new accumulator * fix types const * remove unsed methods * more usdx minting param validation. Protect against the rewards per second denom changing. It should always be "ukava". * add safety check in InitGenesis It prevents huge accumulations on the first block by limiting all previous accumulation times to be within one year of genesis * add todo for adding swp token distirbution info
This commit is contained in:
parent
cbb8b04292
commit
6f193c7f2a
@ -8,30 +8,20 @@ import (
|
|||||||
|
|
||||||
// BeginBlocker runs at the start of every block
|
// BeginBlocker runs at the start of every block
|
||||||
func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
|
func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
|
||||||
|
|
||||||
params := k.GetParams(ctx)
|
params := k.GetParams(ctx)
|
||||||
|
|
||||||
for _, rp := range params.USDXMintingRewardPeriods {
|
for _, rp := range params.USDXMintingRewardPeriods {
|
||||||
err := k.AccumulateUSDXMintingRewards(ctx, rp)
|
k.AccumulateUSDXMintingRewards(ctx, rp)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for _, rp := range params.HardSupplyRewardPeriods {
|
for _, rp := range params.HardSupplyRewardPeriods {
|
||||||
err := k.AccumulateHardSupplyRewards(ctx, rp)
|
k.AccumulateHardSupplyRewards(ctx, rp)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for _, rp := range params.HardBorrowRewardPeriods {
|
for _, rp := range params.HardBorrowRewardPeriods {
|
||||||
err := k.AccumulateHardBorrowRewards(ctx, rp)
|
k.AccumulateHardBorrowRewards(ctx, rp)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for _, rp := range params.DelegatorRewardPeriods {
|
for _, rp := range params.DelegatorRewardPeriods {
|
||||||
err := k.AccumulateDelegatorRewards(ctx, rp)
|
k.AccumulateDelegatorRewards(ctx, rp)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for _, rp := range params.SwapRewardPeriods {
|
for _, rp := range params.SwapRewardPeriods {
|
||||||
k.AccumulateSwapRewards(ctx, rp)
|
k.AccumulateSwapRewards(ctx, rp)
|
||||||
|
@ -17,7 +17,6 @@ const (
|
|||||||
AttributeKeyClaimedBy = types.AttributeKeyClaimedBy
|
AttributeKeyClaimedBy = types.AttributeKeyClaimedBy
|
||||||
AttributeKeyRewardPeriod = types.AttributeKeyRewardPeriod
|
AttributeKeyRewardPeriod = types.AttributeKeyRewardPeriod
|
||||||
AttributeValueCategory = types.AttributeValueCategory
|
AttributeValueCategory = types.AttributeValueCategory
|
||||||
BondDenom = types.BondDenom
|
|
||||||
DefaultParamspace = types.DefaultParamspace
|
DefaultParamspace = types.DefaultParamspace
|
||||||
DelegatorClaimType = types.DelegatorClaimType
|
DelegatorClaimType = types.DelegatorClaimType
|
||||||
EventTypeClaim = types.EventTypeClaim
|
EventTypeClaim = types.EventTypeClaim
|
||||||
@ -49,7 +48,6 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// function aliases
|
// function aliases
|
||||||
CalculateTimeElapsed = keeper.CalculateTimeElapsed
|
|
||||||
NewKeeper = keeper.NewKeeper
|
NewKeeper = keeper.NewKeeper
|
||||||
NewQuerier = keeper.NewQuerier
|
NewQuerier = keeper.NewQuerier
|
||||||
DefaultGenesisState = types.DefaultGenesisState
|
DefaultGenesisState = types.DefaultGenesisState
|
||||||
@ -73,6 +71,7 @@ var (
|
|||||||
NewMsgClaimUSDXMintingRewardVVesting = types.NewMsgClaimUSDXMintingRewardVVesting
|
NewMsgClaimUSDXMintingRewardVVesting = types.NewMsgClaimUSDXMintingRewardVVesting
|
||||||
NewMultiRewardIndex = types.NewMultiRewardIndex
|
NewMultiRewardIndex = types.NewMultiRewardIndex
|
||||||
NewMultiRewardPeriod = types.NewMultiRewardPeriod
|
NewMultiRewardPeriod = types.NewMultiRewardPeriod
|
||||||
|
NewMultiRewardPeriodFromRewardPeriod = types.NewMultiRewardPeriodFromRewardPeriod
|
||||||
NewMultiplier = types.NewMultiplier
|
NewMultiplier = types.NewMultiplier
|
||||||
NewParams = types.NewParams
|
NewParams = types.NewParams
|
||||||
NewPeriod = types.NewPeriod
|
NewPeriod = types.NewPeriod
|
||||||
@ -86,6 +85,7 @@ var (
|
|||||||
RegisterCodec = types.RegisterCodec
|
RegisterCodec = types.RegisterCodec
|
||||||
|
|
||||||
// variable aliases
|
// variable aliases
|
||||||
|
BondDenom = types.BondDenom
|
||||||
DefaultActive = types.DefaultActive
|
DefaultActive = types.DefaultActive
|
||||||
DefaultClaimEnd = types.DefaultClaimEnd
|
DefaultClaimEnd = types.DefaultClaimEnd
|
||||||
DefaultDelegatorClaims = types.DefaultDelegatorClaims
|
DefaultDelegatorClaims = types.DefaultDelegatorClaims
|
||||||
@ -111,7 +111,6 @@ var (
|
|||||||
ErrNoClaimsFound = types.ErrNoClaimsFound
|
ErrNoClaimsFound = types.ErrNoClaimsFound
|
||||||
ErrRewardPeriodNotFound = types.ErrRewardPeriodNotFound
|
ErrRewardPeriodNotFound = types.ErrRewardPeriodNotFound
|
||||||
ErrZeroClaim = types.ErrZeroClaim
|
ErrZeroClaim = types.ErrZeroClaim
|
||||||
GovDenom = types.GovDenom
|
|
||||||
HardBorrowRewardIndexesKeyPrefix = types.HardBorrowRewardIndexesKeyPrefix
|
HardBorrowRewardIndexesKeyPrefix = types.HardBorrowRewardIndexesKeyPrefix
|
||||||
HardLiquidityClaimKeyPrefix = types.HardLiquidityClaimKeyPrefix
|
HardLiquidityClaimKeyPrefix = types.HardLiquidityClaimKeyPrefix
|
||||||
HardSupplyRewardIndexesKeyPrefix = types.HardSupplyRewardIndexesKeyPrefix
|
HardSupplyRewardIndexesKeyPrefix = types.HardSupplyRewardIndexesKeyPrefix
|
||||||
@ -129,7 +128,6 @@ var (
|
|||||||
PreviousHardSupplyRewardAccrualTimeKeyPrefix = types.PreviousHardSupplyRewardAccrualTimeKeyPrefix
|
PreviousHardSupplyRewardAccrualTimeKeyPrefix = types.PreviousHardSupplyRewardAccrualTimeKeyPrefix
|
||||||
PreviousSwapRewardAccrualTimeKeyPrefix = types.PreviousSwapRewardAccrualTimeKeyPrefix
|
PreviousSwapRewardAccrualTimeKeyPrefix = types.PreviousSwapRewardAccrualTimeKeyPrefix
|
||||||
PreviousUSDXMintingRewardAccrualTimeKeyPrefix = types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix
|
PreviousUSDXMintingRewardAccrualTimeKeyPrefix = types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix
|
||||||
PrincipalDenom = types.PrincipalDenom
|
|
||||||
SwapClaimKeyPrefix = types.SwapClaimKeyPrefix
|
SwapClaimKeyPrefix = types.SwapClaimKeyPrefix
|
||||||
SwapRewardIndexesKeyPrefix = types.SwapRewardIndexesKeyPrefix
|
SwapRewardIndexesKeyPrefix = types.SwapRewardIndexesKeyPrefix
|
||||||
USDXMintingClaimKeyPrefix = types.USDXMintingClaimKeyPrefix
|
USDXMintingClaimKeyPrefix = types.USDXMintingClaimKeyPrefix
|
||||||
|
@ -10,6 +10,12 @@ import (
|
|||||||
"github.com/kava-labs/kava/x/incentive/types"
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const year = 365 * 24 * time.Hour
|
||||||
|
|
||||||
|
// EarliestValidAccumulationTime is how far behind the genesis time an accumulation time can be for it to be valid.
|
||||||
|
// It's a safety check to ensure rewards aren't accidentally accumulated for many years on the first block (eg since Jan 1970).
|
||||||
|
var EarliestValidAccumulationTime time.Duration = year
|
||||||
|
|
||||||
// InitGenesis initializes the store state from a genesis state.
|
// InitGenesis initializes the store state from a genesis state.
|
||||||
func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeeper, cdpKeeper types.CdpKeeper, gs types.GenesisState) {
|
func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeeper, cdpKeeper types.CdpKeeper, gs types.GenesisState) {
|
||||||
|
|
||||||
@ -37,6 +43,9 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeep
|
|||||||
k.SetUSDXMintingClaim(ctx, claim)
|
k.SetUSDXMintingClaim(ctx, claim)
|
||||||
}
|
}
|
||||||
for _, gat := range gs.USDXRewardState.AccumulationTimes {
|
for _, gat := range gs.USDXRewardState.AccumulationTimes {
|
||||||
|
if err := ValidateAccumulationTime(gat.PreviousAccumulationTime, ctx.BlockTime()); err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
k.SetPreviousUSDXMintingAccrualTime(ctx, gat.CollateralType, gat.PreviousAccumulationTime)
|
k.SetPreviousUSDXMintingAccrualTime(ctx, gat.CollateralType, gat.PreviousAccumulationTime)
|
||||||
}
|
}
|
||||||
for _, mri := range gs.USDXRewardState.MultiRewardIndexes {
|
for _, mri := range gs.USDXRewardState.MultiRewardIndexes {
|
||||||
@ -52,12 +61,18 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeep
|
|||||||
k.SetHardLiquidityProviderClaim(ctx, claim)
|
k.SetHardLiquidityProviderClaim(ctx, claim)
|
||||||
}
|
}
|
||||||
for _, gat := range gs.HardSupplyRewardState.AccumulationTimes {
|
for _, gat := range gs.HardSupplyRewardState.AccumulationTimes {
|
||||||
|
if err := ValidateAccumulationTime(gat.PreviousAccumulationTime, ctx.BlockTime()); err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
k.SetPreviousHardSupplyRewardAccrualTime(ctx, gat.CollateralType, gat.PreviousAccumulationTime)
|
k.SetPreviousHardSupplyRewardAccrualTime(ctx, gat.CollateralType, gat.PreviousAccumulationTime)
|
||||||
}
|
}
|
||||||
for _, mri := range gs.HardSupplyRewardState.MultiRewardIndexes {
|
for _, mri := range gs.HardSupplyRewardState.MultiRewardIndexes {
|
||||||
k.SetHardSupplyRewardIndexes(ctx, mri.CollateralType, mri.RewardIndexes)
|
k.SetHardSupplyRewardIndexes(ctx, mri.CollateralType, mri.RewardIndexes)
|
||||||
}
|
}
|
||||||
for _, gat := range gs.HardBorrowRewardState.AccumulationTimes {
|
for _, gat := range gs.HardBorrowRewardState.AccumulationTimes {
|
||||||
|
if err := ValidateAccumulationTime(gat.PreviousAccumulationTime, ctx.BlockTime()); err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
k.SetPreviousHardBorrowRewardAccrualTime(ctx, gat.CollateralType, gat.PreviousAccumulationTime)
|
k.SetPreviousHardBorrowRewardAccrualTime(ctx, gat.CollateralType, gat.PreviousAccumulationTime)
|
||||||
}
|
}
|
||||||
for _, mri := range gs.HardBorrowRewardState.MultiRewardIndexes {
|
for _, mri := range gs.HardBorrowRewardState.MultiRewardIndexes {
|
||||||
@ -69,6 +84,9 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeep
|
|||||||
k.SetDelegatorClaim(ctx, claim)
|
k.SetDelegatorClaim(ctx, claim)
|
||||||
}
|
}
|
||||||
for _, gat := range gs.DelegatorRewardState.AccumulationTimes {
|
for _, gat := range gs.DelegatorRewardState.AccumulationTimes {
|
||||||
|
if err := ValidateAccumulationTime(gat.PreviousAccumulationTime, ctx.BlockTime()); err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
k.SetPreviousDelegatorRewardAccrualTime(ctx, gat.CollateralType, gat.PreviousAccumulationTime)
|
k.SetPreviousDelegatorRewardAccrualTime(ctx, gat.CollateralType, gat.PreviousAccumulationTime)
|
||||||
}
|
}
|
||||||
for _, mri := range gs.DelegatorRewardState.MultiRewardIndexes {
|
for _, mri := range gs.DelegatorRewardState.MultiRewardIndexes {
|
||||||
@ -80,6 +98,9 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeep
|
|||||||
k.SetSwapClaim(ctx, claim)
|
k.SetSwapClaim(ctx, claim)
|
||||||
}
|
}
|
||||||
for _, gat := range gs.SwapRewardState.AccumulationTimes {
|
for _, gat := range gs.SwapRewardState.AccumulationTimes {
|
||||||
|
if err := ValidateAccumulationTime(gat.PreviousAccumulationTime, ctx.BlockTime()); err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
k.SetSwapRewardAccrualTime(ctx, gat.CollateralType, gat.PreviousAccumulationTime)
|
k.SetSwapRewardAccrualTime(ctx, gat.CollateralType, gat.PreviousAccumulationTime)
|
||||||
}
|
}
|
||||||
for _, mri := range gs.SwapRewardState.MultiRewardIndexes {
|
for _, mri := range gs.SwapRewardState.MultiRewardIndexes {
|
||||||
@ -201,3 +222,15 @@ func getSwapGenesisRewardState(ctx sdk.Context, keeper keeper.Keeper) types.Gene
|
|||||||
|
|
||||||
return types.NewGenesisRewardState(ats, mris)
|
return types.NewGenesisRewardState(ats, mris)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ValidateAccumulationTime(previousAccumulationTime, genesisTime time.Time) error {
|
||||||
|
if previousAccumulationTime.Before(genesisTime.Add(-1 * EarliestValidAccumulationTime)) {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"found accumulation time '%s' more than '%s' behind genesis time '%s'",
|
||||||
|
previousAccumulationTime,
|
||||||
|
EarliestValidAccumulationTime,
|
||||||
|
genesisTime,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -96,34 +96,6 @@ func (suite *GenesisTestSuite) SetupTest() {
|
|||||||
suite.ctx = ctx
|
suite.ctx = ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test to cover an bug where paid out claims would zero out rewards incorrectly, creating an invalid coins object.
|
|
||||||
// The invalid reward coins would fail the genesis state validation
|
|
||||||
func (suite *GenesisTestSuite) TestPaidOutClaimsPassValidateGenesis() {
|
|
||||||
hardHandler := hard.NewHandler(suite.app.GetHardKeeper())
|
|
||||||
_, err := hardHandler(suite.ctx, hard.NewMsgDeposit(suite.addrs[0], cs(c("bnb", 100_000_000))))
|
|
||||||
suite.Require().NoError(err)
|
|
||||||
|
|
||||||
suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{})
|
|
||||||
suite.ctx = suite.ctx.WithBlockTime(suite.genesisTime.Add(1 * 10 * time.Second))
|
|
||||||
suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{})
|
|
||||||
|
|
||||||
suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{})
|
|
||||||
suite.ctx = suite.ctx.WithBlockTime(suite.genesisTime.Add(2 * 10 * time.Second))
|
|
||||||
suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{})
|
|
||||||
|
|
||||||
_, err = hardHandler(suite.ctx, hard.NewMsgWithdraw(suite.addrs[0], cs(c("bnb", 100_000_000))))
|
|
||||||
suite.Require().NoError(err)
|
|
||||||
|
|
||||||
incentiveHandler := incentive.NewHandler(suite.keeper)
|
|
||||||
_, err = incentiveHandler(suite.ctx, incentive.NewMsgClaimHardReward(suite.addrs[0], string(incentive.Large), nil))
|
|
||||||
suite.Require().NoError(err)
|
|
||||||
|
|
||||||
genState := incentive.ExportGenesis(suite.ctx, suite.keeper)
|
|
||||||
suite.Require().NoError(
|
|
||||||
genState.Validate(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *GenesisTestSuite) TestExportedGenesisMatchesImported() {
|
func (suite *GenesisTestSuite) TestExportedGenesisMatchesImported() {
|
||||||
genesisTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
genesisTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
genesisState := incentive.NewGenesisState(
|
genesisState := incentive.NewGenesisState(
|
||||||
@ -219,7 +191,7 @@ func (suite *GenesisTestSuite) TestExportedGenesisMatchesImported() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
tApp := app.NewTestApp()
|
tApp := app.NewTestApp()
|
||||||
ctx := tApp.NewContext(true, abci.Header{Height: 1})
|
ctx := tApp.NewContext(true, abci.Header{Height: 0, Time: genesisTime})
|
||||||
|
|
||||||
// Incentive init genesis reads from the cdp keeper to check params are ok. So it needs to be initialized first.
|
// Incentive init genesis reads from the cdp keeper to check params are ok. So it needs to be initialized first.
|
||||||
// Then the cdp keeper reads from pricefeed keeper to check its params are ok. So it also need initialization.
|
// Then the cdp keeper reads from pricefeed keeper to check its params are ok. So it also need initialization.
|
||||||
@ -235,6 +207,84 @@ func (suite *GenesisTestSuite) TestExportedGenesisMatchesImported() {
|
|||||||
suite.Equal(genesisState, exportedGenesisState)
|
suite.Equal(genesisState, exportedGenesisState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *GenesisTestSuite) TestInitGenesisPanicsWhenAccumulationTimesToLongAgo() {
|
||||||
|
genesisTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
invalidRewardState := incentive.NewGenesisRewardState(
|
||||||
|
incentive.AccumulationTimes{
|
||||||
|
incentive.NewAccumulationTime("bnb", genesisTime.Add(-23*incentive.EarliestValidAccumulationTime).Add(-time.Nanosecond)),
|
||||||
|
},
|
||||||
|
incentive.MultiRewardIndexes{},
|
||||||
|
)
|
||||||
|
minimalParams := incentive.Params{
|
||||||
|
ClaimEnd: genesisTime.Add(5 * oneYear),
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
genesisState incentive.GenesisState
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
incentive.GenesisState{
|
||||||
|
Params: minimalParams,
|
||||||
|
USDXRewardState: invalidRewardState,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
incentive.GenesisState{
|
||||||
|
Params: minimalParams,
|
||||||
|
HardSupplyRewardState: invalidRewardState,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
incentive.GenesisState{
|
||||||
|
Params: minimalParams,
|
||||||
|
HardBorrowRewardState: invalidRewardState,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
incentive.GenesisState{
|
||||||
|
Params: minimalParams,
|
||||||
|
DelegatorRewardState: invalidRewardState,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
incentive.GenesisState{
|
||||||
|
Params: minimalParams,
|
||||||
|
SwapRewardState: invalidRewardState,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
|
||||||
|
tApp := app.NewTestApp()
|
||||||
|
ctx := tApp.NewContext(true, abci.Header{Height: 0, Time: genesisTime})
|
||||||
|
|
||||||
|
// Incentive init genesis reads from the cdp keeper to check params are ok. So it needs to be initialized first.
|
||||||
|
// Then the cdp keeper reads from pricefeed keeper to check its params are ok. So it also need initialization.
|
||||||
|
tApp.InitializeFromGenesisStates(
|
||||||
|
NewCDPGenStateMulti(),
|
||||||
|
NewPricefeedGenStateMultiFromTime(genesisTime),
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.PanicsWithValue(
|
||||||
|
"found accumulation time '1975-01-06 23:59:59.999999999 +0000 UTC' more than '8760h0m0s' behind genesis time '1998-01-01 00:00:00 +0000 UTC'",
|
||||||
|
func() {
|
||||||
|
incentive.InitGenesis(ctx, tApp.GetIncentiveKeeper(), tApp.GetSupplyKeeper(), tApp.GetCDPKeeper(), tc.genesisState)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *GenesisTestSuite) TestValidateAccumulationTime() {
|
||||||
|
genTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
err := incentive.ValidateAccumulationTime(
|
||||||
|
genTime.Add(-incentive.EarliestValidAccumulationTime).Add(-time.Nanosecond),
|
||||||
|
genTime,
|
||||||
|
)
|
||||||
|
suite.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGenesisTestSuite(t *testing.T) {
|
func TestGenesisTestSuite(t *testing.T) {
|
||||||
suite.Run(t, new(GenesisTestSuite))
|
suite.Run(t, new(GenesisTestSuite))
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,8 @@ func TestRiskyCDPsAccumulateRewards(t *testing.T) {
|
|||||||
WithSimpleUSDXRewardPeriod(collateralType, rewardsPerSecond)
|
WithSimpleUSDXRewardPeriod(collateralType, rewardsPerSecond)
|
||||||
|
|
||||||
tApp := app.NewTestApp()
|
tApp := app.NewTestApp()
|
||||||
tApp.InitializeFromGenesisStates(
|
tApp.InitializeFromGenesisStatesWithTime(
|
||||||
|
genesisTime,
|
||||||
authBuilder.BuildMarshalled(),
|
authBuilder.BuildMarshalled(),
|
||||||
NewPricefeedGenStateMultiFromTime(genesisTime),
|
NewPricefeedGenStateMultiFromTime(genesisTime),
|
||||||
NewCDPGenStateMulti(),
|
NewCDPGenStateMulti(),
|
||||||
|
@ -319,7 +319,7 @@ func (k Keeper) GetHardSupplyRewardIndexes(ctx sdk.Context, denom string) (types
|
|||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.HardSupplyRewardIndexesKeyPrefix)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.HardSupplyRewardIndexesKeyPrefix)
|
||||||
bz := store.Get([]byte(denom))
|
bz := store.Get([]byte(denom))
|
||||||
if bz == nil {
|
if bz == nil {
|
||||||
return types.RewardIndexes{}, false
|
return nil, false
|
||||||
}
|
}
|
||||||
var rewardIndexes types.RewardIndexes
|
var rewardIndexes types.RewardIndexes
|
||||||
k.cdc.MustUnmarshalBinaryBare(bz, &rewardIndexes)
|
k.cdc.MustUnmarshalBinaryBare(bz, &rewardIndexes)
|
||||||
@ -366,7 +366,7 @@ func (k Keeper) GetHardBorrowRewardIndexes(ctx sdk.Context, denom string) (types
|
|||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.HardBorrowRewardIndexesKeyPrefix)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.HardBorrowRewardIndexesKeyPrefix)
|
||||||
bz := store.Get([]byte(denom))
|
bz := store.Get([]byte(denom))
|
||||||
if bz == nil {
|
if bz == nil {
|
||||||
return types.RewardIndexes{}, false
|
return nil, false
|
||||||
}
|
}
|
||||||
var rewardIndexes types.RewardIndexes
|
var rewardIndexes types.RewardIndexes
|
||||||
k.cdc.MustUnmarshalBinaryBare(bz, &rewardIndexes)
|
k.cdc.MustUnmarshalBinaryBare(bz, &rewardIndexes)
|
||||||
@ -406,7 +406,7 @@ func (k Keeper) GetDelegatorRewardIndexes(ctx sdk.Context, denom string) (types.
|
|||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.DelegatorRewardIndexesKeyPrefix)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.DelegatorRewardIndexesKeyPrefix)
|
||||||
bz := store.Get([]byte(denom))
|
bz := store.Get([]byte(denom))
|
||||||
if bz == nil {
|
if bz == nil {
|
||||||
return types.RewardIndexes{}, false
|
return nil, false
|
||||||
}
|
}
|
||||||
var rewardIndexes types.RewardIndexes
|
var rewardIndexes types.RewardIndexes
|
||||||
k.cdc.MustUnmarshalBinaryBare(bz, &rewardIndexes)
|
k.cdc.MustUnmarshalBinaryBare(bz, &rewardIndexes)
|
||||||
@ -511,7 +511,7 @@ func (k Keeper) GetSwapRewardIndexes(ctx sdk.Context, poolID string) (types.Rewa
|
|||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.SwapRewardIndexesKeyPrefix)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.SwapRewardIndexesKeyPrefix)
|
||||||
bz := store.Get([]byte(poolID))
|
bz := store.Get([]byte(poolID))
|
||||||
if bz == nil {
|
if bz == nil {
|
||||||
return types.RewardIndexes{}, false
|
return nil, false
|
||||||
}
|
}
|
||||||
var rewardIndexes types.RewardIndexes
|
var rewardIndexes types.RewardIndexes
|
||||||
k.cdc.MustUnmarshalBinaryBare(bz, &rewardIndexes)
|
k.cdc.MustUnmarshalBinaryBare(bz, &rewardIndexes)
|
||||||
|
@ -10,70 +10,52 @@ import (
|
|||||||
"github.com/kava-labs/kava/x/incentive/types"
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AccumulateHardBorrowRewards updates the rewards accumulated for the input reward period
|
// AccumulateHardBorrowRewards calculates new rewards to distribute this block and updates the global indexes to reflect this.
|
||||||
func (k Keeper) AccumulateHardBorrowRewards(ctx sdk.Context, rewardPeriod types.MultiRewardPeriod) error {
|
// The provided rewardPeriod must be valid to avoid panics in calculating time durations.
|
||||||
|
func (k Keeper) AccumulateHardBorrowRewards(ctx sdk.Context, rewardPeriod types.MultiRewardPeriod) {
|
||||||
|
|
||||||
previousAccrualTime, found := k.GetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType)
|
previousAccrualTime, found := k.GetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType)
|
||||||
if !found {
|
if !found {
|
||||||
k.SetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
previousAccrualTime = ctx.BlockTime()
|
||||||
return nil
|
|
||||||
}
|
|
||||||
timeElapsed := CalculateTimeElapsed(rewardPeriod.Start, rewardPeriod.End, ctx.BlockTime(), previousAccrualTime)
|
|
||||||
if timeElapsed.IsZero() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if rewardPeriod.RewardsPerSecond.IsZero() {
|
|
||||||
k.SetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
totalBorrowedCoins, foundTotalBorrowedCoins := k.hardKeeper.GetBorrowedCoins(ctx)
|
indexes, found := k.GetHardBorrowRewardIndexes(ctx, rewardPeriod.CollateralType)
|
||||||
if !foundTotalBorrowedCoins {
|
|
||||||
k.SetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
totalBorrowed := totalBorrowedCoins.AmountOf(rewardPeriod.CollateralType).ToDec()
|
|
||||||
if totalBorrowed.IsZero() {
|
|
||||||
k.SetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
previousRewardIndexes, found := k.GetHardBorrowRewardIndexes(ctx, rewardPeriod.CollateralType)
|
|
||||||
if !found {
|
if !found {
|
||||||
for _, rewardCoin := range rewardPeriod.RewardsPerSecond {
|
indexes = types.RewardIndexes{}
|
||||||
rewardIndex := types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec())
|
|
||||||
previousRewardIndexes = append(previousRewardIndexes, rewardIndex)
|
|
||||||
}
|
|
||||||
k.SetHardBorrowRewardIndexes(ctx, rewardPeriod.CollateralType, previousRewardIndexes)
|
|
||||||
}
|
}
|
||||||
hardFactor, found := k.hardKeeper.GetBorrowInterestFactor(ctx, rewardPeriod.CollateralType)
|
|
||||||
|
acc := types.NewAccumulator(previousAccrualTime, indexes)
|
||||||
|
|
||||||
|
totalSource := k.getHardBorrowTotalSourceShares(ctx, rewardPeriod.CollateralType)
|
||||||
|
|
||||||
|
acc.Accumulate(rewardPeriod, totalSource, ctx.BlockTime())
|
||||||
|
|
||||||
|
k.SetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType, acc.PreviousAccumulationTime)
|
||||||
|
if len(acc.Indexes) > 0 {
|
||||||
|
// the store panics when setting empty or nil indexes
|
||||||
|
k.SetHardBorrowRewardIndexes(ctx, rewardPeriod.CollateralType, acc.Indexes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getHardBorrowTotalSourceShares fetches the sum of all source shares for a borrow reward.
|
||||||
|
// In the case of hard borrow, this is the total borrowed divided by the borrow interest factor.
|
||||||
|
// This give the "pre interest" value of the total borrowed.
|
||||||
|
func (k Keeper) getHardBorrowTotalSourceShares(ctx sdk.Context, denom string) sdk.Dec {
|
||||||
|
totalBorrowedCoins, found := k.hardKeeper.GetBorrowedCoins(ctx)
|
||||||
if !found {
|
if !found {
|
||||||
k.SetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
// assume no coins have been borrowed
|
||||||
return nil
|
totalBorrowedCoins = sdk.NewCoins()
|
||||||
|
}
|
||||||
|
totalBorrowed := totalBorrowedCoins.AmountOf(denom)
|
||||||
|
|
||||||
|
interestFactor, found := k.hardKeeper.GetBorrowInterestFactor(ctx, denom)
|
||||||
|
if !found {
|
||||||
|
// assume nothing has been borrowed so the factor starts at it's default value
|
||||||
|
interestFactor = sdk.OneDec()
|
||||||
}
|
}
|
||||||
|
|
||||||
newRewardIndexes := previousRewardIndexes
|
// return borrowed/factor to get the "pre interest" value of the current total borrowed
|
||||||
for _, rewardCoin := range rewardPeriod.RewardsPerSecond {
|
return totalBorrowed.ToDec().Quo(interestFactor)
|
||||||
newRewards := rewardCoin.Amount.ToDec().Mul(timeElapsed.ToDec())
|
|
||||||
previousRewardIndex, found := previousRewardIndexes.GetRewardIndex(rewardCoin.Denom)
|
|
||||||
if !found {
|
|
||||||
previousRewardIndex = types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate new reward factor and update reward index
|
|
||||||
rewardFactor := newRewards.Mul(hardFactor).Quo(totalBorrowed)
|
|
||||||
newRewardFactorValue := previousRewardIndex.RewardFactor.Add(rewardFactor)
|
|
||||||
newRewardIndex := types.NewRewardIndex(rewardCoin.Denom, newRewardFactorValue)
|
|
||||||
i, found := newRewardIndexes.GetFactorIndex(rewardCoin.Denom)
|
|
||||||
if found {
|
|
||||||
newRewardIndexes[i] = newRewardIndex
|
|
||||||
} else {
|
|
||||||
newRewardIndexes = append(newRewardIndexes, newRewardIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
k.SetHardBorrowRewardIndexes(ctx, rewardPeriod.CollateralType, newRewardIndexes)
|
|
||||||
k.SetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitializeHardBorrowReward initializes the borrow-side of a hard liquidity provider claim
|
// InitializeHardBorrowReward initializes the borrow-side of a hard liquidity provider claim
|
||||||
@ -172,14 +154,14 @@ func (k Keeper) UpdateHardBorrowIndexDenoms(ctx sdk.Context, borrow hardtypes.Bo
|
|||||||
k.SetHardLiquidityProviderClaim(ctx, claim)
|
k.SetHardLiquidityProviderClaim(ctx, claim)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalculateRewards computes how much rewards should have accrued to a source (eg a user's hard borrowed btc amount)
|
// CalculateRewards computes how much rewards should have accrued to a reward source (eg a user's hard borrowed btc amount)
|
||||||
// between two index values.
|
// between two index values.
|
||||||
//
|
//
|
||||||
// oldIndex is normally the index stored on a claim, newIndex the current global value, and rewardSource a hard borrowed/supplied amount.
|
// oldIndex is normally the index stored on a claim, newIndex the current global value, and sourceShares a hard borrowed/supplied amount.
|
||||||
//
|
//
|
||||||
// Returns an error if newIndexes does not contain all CollateralTypes from oldIndexes, or if any value of oldIndex.RewardFactor > newIndex.RewardFactor.
|
// It returns an error if newIndexes does not contain all CollateralTypes from oldIndexes, or if any value of oldIndex.RewardFactor > newIndex.RewardFactor.
|
||||||
// This should never happen, as it would mean that a global reward index has decreased in value, or that a global reward index has been deleted from state.
|
// This should never happen, as it would mean that a global reward index has decreased in value, or that a global reward index has been deleted from state.
|
||||||
func (k Keeper) CalculateRewards(oldIndexes, newIndexes types.RewardIndexes, rewardSource sdk.Dec) (sdk.Coins, error) {
|
func (k Keeper) CalculateRewards(oldIndexes, newIndexes types.RewardIndexes, sourceShares sdk.Dec) (sdk.Coins, error) {
|
||||||
// check for missing CollateralType's
|
// check for missing CollateralType's
|
||||||
for _, oldIndex := range oldIndexes {
|
for _, oldIndex := range oldIndexes {
|
||||||
if newIndex, found := newIndexes.Get(oldIndex.CollateralType); !found {
|
if newIndex, found := newIndexes.Get(oldIndex.CollateralType); !found {
|
||||||
@ -193,7 +175,7 @@ func (k Keeper) CalculateRewards(oldIndexes, newIndexes types.RewardIndexes, rew
|
|||||||
oldFactor = sdk.ZeroDec()
|
oldFactor = sdk.ZeroDec()
|
||||||
}
|
}
|
||||||
|
|
||||||
rewardAmount, err := k.CalculateSingleReward(oldFactor, newIndex.RewardFactor, rewardSource)
|
rewardAmount, err := k.CalculateSingleReward(oldFactor, newIndex.RewardFactor, sourceShares)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -205,18 +187,18 @@ func (k Keeper) CalculateRewards(oldIndexes, newIndexes types.RewardIndexes, rew
|
|||||||
return reward, nil
|
return reward, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalculateSingleReward computes how much rewards should have accrued to a source (eg a user's btcb-a cdp principal)
|
// CalculateSingleReward computes how much rewards should have accrued to a reward source (eg a user's btcb-a cdp principal)
|
||||||
// between two index values.
|
// between two index values.
|
||||||
//
|
//
|
||||||
// oldIndex is normally the index stored on a claim, newIndex the current global value, and rewardSource a cdp principal amount.
|
// oldIndex is normally the index stored on a claim, newIndex the current global value, and sourceShares a cdp principal amount.
|
||||||
//
|
//
|
||||||
// Returns an error if oldIndex > newIndex. This should never happen, as it would mean that a global reward index has decreased in value,
|
// Returns an error if oldIndex > newIndex. This should never happen, as it would mean that a global reward index has decreased in value,
|
||||||
// or that a global reward index has been deleted from state.
|
// or that a global reward index has been deleted from state.
|
||||||
func (k Keeper) CalculateSingleReward(oldIndex, newIndex, rewardSource sdk.Dec) (sdk.Int, error) {
|
func (k Keeper) CalculateSingleReward(oldIndex, newIndex, sourceShares sdk.Dec) (sdk.Int, error) {
|
||||||
increase := newIndex.Sub(oldIndex)
|
increase := newIndex.Sub(oldIndex)
|
||||||
if increase.IsNegative() {
|
if increase.IsNegative() {
|
||||||
return sdk.Int{}, sdkerrors.Wrapf(types.ErrDecreasingRewardFactor, "old: %v, new: %v", oldIndex, newIndex)
|
return sdk.Int{}, sdkerrors.Wrapf(types.ErrDecreasingRewardFactor, "old: %v, new: %v", oldIndex, newIndex)
|
||||||
}
|
}
|
||||||
reward := increase.Mul(rewardSource).RoundInt()
|
reward := increase.Mul(sourceShares).RoundInt()
|
||||||
return reward, nil
|
return reward, nil
|
||||||
}
|
}
|
||||||
|
316
x/incentive/keeper/rewards_borrow_accum_test.go
Normal file
316
x/incentive/keeper/rewards_borrow_accum_test.go
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccumulateBorrowRewardsTests struct {
|
||||||
|
unitTester
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateBorrowRewardsTests) storedTimeEquals(denom string, expected time.Time) {
|
||||||
|
storedTime, found := suite.keeper.GetPreviousHardBorrowRewardAccrualTime(suite.ctx, denom)
|
||||||
|
suite.True(found)
|
||||||
|
suite.Equal(expected, storedTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateBorrowRewardsTests) storedIndexesEqual(denom string, expected types.RewardIndexes) {
|
||||||
|
storedIndexes, found := suite.keeper.GetHardBorrowRewardIndexes(suite.ctx, denom)
|
||||||
|
suite.Equal(found, expected != nil)
|
||||||
|
suite.Equal(expected, storedIndexes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccumulateBorrowRewards(t *testing.T) {
|
||||||
|
suite.Run(t, new(AccumulateBorrowRewardsTests))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateBorrowRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreased() {
|
||||||
|
denom := "bnb"
|
||||||
|
|
||||||
|
hardKeeper := newFakeHardKeeper().addTotalBorrow(c(denom, 1e6), d("1"))
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil)
|
||||||
|
|
||||||
|
suite.storeGlobalBorrowIndexes(types.MultiRewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: denom,
|
||||||
|
RewardIndexes: types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("0.02"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, denom, previousAccrualTime)
|
||||||
|
|
||||||
|
newAccrualTime := previousAccrualTime.Add(1 * time.Hour)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(newAccrualTime)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
denom,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)), // same denoms as in global indexes
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateHardBorrowRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// check time and factors
|
||||||
|
|
||||||
|
suite.storedTimeEquals(denom, newAccrualTime)
|
||||||
|
suite.storedIndexesEqual(denom, types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("7.22"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("3.64"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateBorrowRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIncreased() {
|
||||||
|
denom := "bnb"
|
||||||
|
|
||||||
|
hardKeeper := newFakeHardKeeper().addTotalBorrow(c(denom, 1e6), d("1"))
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil)
|
||||||
|
|
||||||
|
previousIndexes := types.MultiRewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: denom,
|
||||||
|
RewardIndexes: types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("0.02"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
suite.storeGlobalBorrowIndexes(previousIndexes)
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, denom, previousAccrualTime)
|
||||||
|
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(previousAccrualTime)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
denom,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)), // same denoms as in global indexes
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateHardBorrowRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// check time and factors
|
||||||
|
|
||||||
|
suite.storedTimeEquals(denom, previousAccrualTime)
|
||||||
|
expected, f := previousIndexes.Get(denom)
|
||||||
|
suite.True(f)
|
||||||
|
suite.storedIndexesEqual(denom, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateBorrowRewardsTests) TestNoAccumulationWhenSourceSharesAreZero() {
|
||||||
|
denom := "bnb"
|
||||||
|
|
||||||
|
hardKeeper := newFakeHardKeeper() // zero total borrows
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil)
|
||||||
|
|
||||||
|
previousIndexes := types.MultiRewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: denom,
|
||||||
|
RewardIndexes: types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("0.02"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
suite.storeGlobalBorrowIndexes(previousIndexes)
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, denom, previousAccrualTime)
|
||||||
|
|
||||||
|
firstAccrualTime := previousAccrualTime.Add(7 * time.Second)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
denom,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)), // same denoms as in global indexes
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateHardBorrowRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// check time and factors
|
||||||
|
|
||||||
|
suite.storedTimeEquals(denom, firstAccrualTime)
|
||||||
|
expected, f := previousIndexes.Get(denom)
|
||||||
|
suite.True(f)
|
||||||
|
suite.storedIndexesEqual(denom, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateBorrowRewardsTests) TestStateAddedWhenStateDoesNotExist() {
|
||||||
|
denom := "bnb"
|
||||||
|
|
||||||
|
hardKeeper := newFakeHardKeeper().addTotalBorrow(c(denom, 1e6), d("1"))
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
denom,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)),
|
||||||
|
)
|
||||||
|
|
||||||
|
firstAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateHardBorrowRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// After the first accumulation only the current block time should be stored.
|
||||||
|
// The indexes will be empty as no time has passed since the previous block because it didn't exist.
|
||||||
|
suite.storedTimeEquals(denom, firstAccrualTime)
|
||||||
|
suite.storedIndexesEqual(denom, nil)
|
||||||
|
|
||||||
|
secondAccrualTime := firstAccrualTime.Add(10 * time.Second)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(secondAccrualTime)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateHardBorrowRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// After the second accumulation both current block time and indexes should be stored.
|
||||||
|
suite.storedTimeEquals(denom, secondAccrualTime)
|
||||||
|
suite.storedIndexesEqual(denom, types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("0.02"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("0.01"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateBorrowRewardsTests) TestNoPanicWhenStateDoesNotExist() {
|
||||||
|
denom := "bnb"
|
||||||
|
|
||||||
|
hardKeeper := newFakeHardKeeper()
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
denom,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
cs(),
|
||||||
|
)
|
||||||
|
|
||||||
|
accrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(accrualTime)
|
||||||
|
|
||||||
|
// Accumulate with no source shares and no rewards per second will result in no increment to the indexes.
|
||||||
|
// No increment and no previous indexes stored, results in an updated of nil. Setting this in the state panics.
|
||||||
|
// Check there is no panic.
|
||||||
|
suite.NotPanics(func() {
|
||||||
|
suite.keeper.AccumulateHardBorrowRewards(suite.ctx, period)
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.storedTimeEquals(denom, accrualTime)
|
||||||
|
suite.storedIndexesEqual(denom, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateBorrowRewardsTests) TestNoAccumulationWhenBeforeStartTime() {
|
||||||
|
denom := "bnb"
|
||||||
|
|
||||||
|
hardKeeper := newFakeHardKeeper().addTotalBorrow(c(denom, 1e6), d("1"))
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil)
|
||||||
|
|
||||||
|
previousIndexes := types.MultiRewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: denom,
|
||||||
|
RewardIndexes: types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("0.02"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
suite.storeGlobalBorrowIndexes(previousIndexes)
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, denom, previousAccrualTime)
|
||||||
|
|
||||||
|
firstAccrualTime := previousAccrualTime.Add(10 * time.Second)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
denom,
|
||||||
|
firstAccrualTime.Add(time.Nanosecond), // start time after accrual time
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)),
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateHardBorrowRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// The accrual time should be updated, but the indexes unchanged
|
||||||
|
suite.storedTimeEquals(denom, firstAccrualTime)
|
||||||
|
expectedIndexes, f := previousIndexes.Get(denom)
|
||||||
|
suite.True(f)
|
||||||
|
suite.storedIndexesEqual(denom, expectedIndexes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateBorrowRewardsTests) TestPanicWhenCurrentTimeLessThanPrevious() {
|
||||||
|
denom := "bnb"
|
||||||
|
|
||||||
|
hardKeeper := newFakeHardKeeper().addTotalBorrow(c(denom, 1e6), d("1"))
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil)
|
||||||
|
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, denom, previousAccrualTime)
|
||||||
|
|
||||||
|
firstAccrualTime := time.Time{}
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
denom,
|
||||||
|
time.Time{}, // start time after accrual time
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)),
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
suite.Panics(func() {
|
||||||
|
suite.keeper.AccumulateHardBorrowRewards(suite.ctx, period)
|
||||||
|
})
|
||||||
|
}
|
@ -185,8 +185,7 @@ func (suite *BorrowRewardsTestSuite) TestAccumulateHardBorrowRewards() {
|
|||||||
// Accumulate hard borrow rewards for the deposit denom
|
// Accumulate hard borrow rewards for the deposit denom
|
||||||
multiRewardPeriod, found := suite.keeper.GetHardBorrowRewardPeriods(runCtx, tc.args.borrow.Denom)
|
multiRewardPeriod, found := suite.keeper.GetHardBorrowRewardPeriods(runCtx, tc.args.borrow.Denom)
|
||||||
suite.Require().True(found)
|
suite.Require().True(found)
|
||||||
err = suite.keeper.AccumulateHardBorrowRewards(runCtx, multiRewardPeriod)
|
suite.keeper.AccumulateHardBorrowRewards(runCtx, multiRewardPeriod)
|
||||||
suite.Require().NoError(err)
|
|
||||||
|
|
||||||
// Check that each expected reward index matches the current stored reward index for the denom
|
// Check that each expected reward index matches the current stored reward index for the denom
|
||||||
globalRewardIndexes, found := suite.keeper.GetHardBorrowRewardIndexes(runCtx, tc.args.borrow.Denom)
|
globalRewardIndexes, found := suite.keeper.GetHardBorrowRewardIndexes(runCtx, tc.args.borrow.Denom)
|
||||||
@ -555,8 +554,7 @@ func (suite *BorrowRewardsTestSuite) TestSynchronizeHardBorrowReward() {
|
|||||||
// Accumulate hard borrow-side rewards
|
// Accumulate hard borrow-side rewards
|
||||||
multiRewardPeriod, found := suite.keeper.GetHardBorrowRewardPeriods(blockCtx, tc.args.borrow.Denom)
|
multiRewardPeriod, found := suite.keeper.GetHardBorrowRewardPeriods(blockCtx, tc.args.borrow.Denom)
|
||||||
if found {
|
if found {
|
||||||
err := suite.keeper.AccumulateHardBorrowRewards(blockCtx, multiRewardPeriod)
|
suite.keeper.AccumulateHardBorrowRewards(blockCtx, multiRewardPeriod)
|
||||||
suite.Require().NoError(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
||||||
@ -666,15 +664,13 @@ func (suite *BorrowRewardsTestSuite) TestSynchronizeHardBorrowReward() {
|
|||||||
// But new borrow denoms don't have their PreviousHardBorrowRewardAccrualTime set yet,
|
// But new borrow denoms don't have their PreviousHardBorrowRewardAccrualTime set yet,
|
||||||
// so we need to call the accumulation method once to set the initial reward accrual time
|
// so we need to call the accumulation method once to set the initial reward accrual time
|
||||||
if tc.args.borrow.Denom != tc.args.incentiveBorrowRewardDenom {
|
if tc.args.borrow.Denom != tc.args.incentiveBorrowRewardDenom {
|
||||||
err = suite.keeper.AccumulateHardBorrowRewards(suite.ctx, multiRewardPeriod)
|
suite.keeper.AccumulateHardBorrowRewards(suite.ctx, multiRewardPeriod)
|
||||||
suite.Require().NoError(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we can jump forward in time and accumulate rewards
|
// Now we can jump forward in time and accumulate rewards
|
||||||
updatedBlockTime = previousBlockTime.Add(time.Duration(int(time.Second) * tc.args.updatedTimeDuration))
|
updatedBlockTime = previousBlockTime.Add(time.Duration(int(time.Second) * tc.args.updatedTimeDuration))
|
||||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||||
err = suite.keeper.AccumulateHardBorrowRewards(suite.ctx, multiRewardPeriod)
|
suite.keeper.AccumulateHardBorrowRewards(suite.ctx, multiRewardPeriod)
|
||||||
suite.Require().NoError(err)
|
|
||||||
|
|
||||||
// After we've accumulated, run synchronize
|
// After we've accumulated, run synchronize
|
||||||
borrow, found = suite.hardKeeper.GetBorrow(suite.ctx, userAddr)
|
borrow, found = suite.hardKeeper.GetBorrow(suite.ctx, userAddr)
|
||||||
@ -963,8 +959,7 @@ func (suite *BorrowRewardsTestSuite) TestSimulateHardBorrowRewardSynchronization
|
|||||||
// Accumulate hard borrow-side rewards
|
// Accumulate hard borrow-side rewards
|
||||||
multiRewardPeriod, found := suite.keeper.GetHardBorrowRewardPeriods(blockCtx, tc.args.borrow.Denom)
|
multiRewardPeriod, found := suite.keeper.GetHardBorrowRewardPeriods(blockCtx, tc.args.borrow.Denom)
|
||||||
suite.Require().True(found)
|
suite.Require().True(found)
|
||||||
err := suite.keeper.AccumulateHardBorrowRewards(blockCtx, multiRewardPeriod)
|
suite.keeper.AccumulateHardBorrowRewards(blockCtx, multiRewardPeriod)
|
||||||
suite.Require().NoError(err)
|
|
||||||
}
|
}
|
||||||
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
||||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||||
|
@ -8,59 +8,39 @@ import (
|
|||||||
"github.com/kava-labs/kava/x/incentive/types"
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AccumulateDelegatorRewards updates the rewards accumulated for the input reward period
|
// AccumulateDelegatorRewards calculates new rewards to distribute this block and updates the global indexes to reflect this.
|
||||||
func (k Keeper) AccumulateDelegatorRewards(ctx sdk.Context, rewardPeriods types.MultiRewardPeriod) error {
|
// The provided rewardPeriod must be valid to avoid panics in calculating time durations.
|
||||||
previousAccrualTime, found := k.GetPreviousDelegatorRewardAccrualTime(ctx, rewardPeriods.CollateralType)
|
func (k Keeper) AccumulateDelegatorRewards(ctx sdk.Context, rewardPeriod types.MultiRewardPeriod) {
|
||||||
|
|
||||||
|
previousAccrualTime, found := k.GetPreviousDelegatorRewardAccrualTime(ctx, rewardPeriod.CollateralType)
|
||||||
if !found {
|
if !found {
|
||||||
k.SetPreviousDelegatorRewardAccrualTime(ctx, rewardPeriods.CollateralType, ctx.BlockTime())
|
previousAccrualTime = ctx.BlockTime()
|
||||||
return nil
|
|
||||||
}
|
|
||||||
timeElapsed := CalculateTimeElapsed(rewardPeriods.Start, rewardPeriods.End, ctx.BlockTime(), previousAccrualTime)
|
|
||||||
if timeElapsed.IsZero() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if rewardPeriods.RewardsPerSecond.IsZero() {
|
|
||||||
k.SetPreviousDelegatorRewardAccrualTime(ctx, rewardPeriods.CollateralType, ctx.BlockTime())
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
totalBonded := k.stakingKeeper.TotalBondedTokens(ctx).ToDec()
|
indexes, found := k.GetDelegatorRewardIndexes(ctx, rewardPeriod.CollateralType)
|
||||||
if totalBonded.IsZero() {
|
|
||||||
k.SetPreviousDelegatorRewardAccrualTime(ctx, rewardPeriods.CollateralType, ctx.BlockTime())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
previousRewardIndexes, found := k.GetDelegatorRewardIndexes(ctx, rewardPeriods.CollateralType)
|
|
||||||
if !found {
|
if !found {
|
||||||
for _, rewardCoin := range rewardPeriods.RewardsPerSecond {
|
indexes = types.RewardIndexes{}
|
||||||
rewardIndex := types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec())
|
|
||||||
previousRewardIndexes = append(previousRewardIndexes, rewardIndex)
|
|
||||||
}
|
|
||||||
k.SetDelegatorRewardIndexes(ctx, rewardPeriods.CollateralType, previousRewardIndexes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newRewardIndexes := previousRewardIndexes
|
acc := types.NewAccumulator(previousAccrualTime, indexes)
|
||||||
for _, rewardCoin := range rewardPeriods.RewardsPerSecond {
|
|
||||||
newRewards := rewardCoin.Amount.ToDec().Mul(timeElapsed.ToDec())
|
|
||||||
previousRewardIndex, found := previousRewardIndexes.GetRewardIndex(rewardCoin.Denom)
|
|
||||||
if !found {
|
|
||||||
previousRewardIndex = types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate new reward factor and update reward index
|
totalSource := k.getDelegatorTotalSourceShares(ctx, rewardPeriod.CollateralType)
|
||||||
rewardFactor := newRewards.Quo(totalBonded)
|
|
||||||
newRewardFactorValue := previousRewardIndex.RewardFactor.Add(rewardFactor)
|
acc.Accumulate(rewardPeriod, totalSource, ctx.BlockTime())
|
||||||
newRewardIndex := types.NewRewardIndex(rewardCoin.Denom, newRewardFactorValue)
|
|
||||||
i, found := newRewardIndexes.GetFactorIndex(rewardCoin.Denom)
|
k.SetPreviousDelegatorRewardAccrualTime(ctx, rewardPeriod.CollateralType, acc.PreviousAccumulationTime)
|
||||||
if found {
|
if len(acc.Indexes) > 0 {
|
||||||
newRewardIndexes[i] = newRewardIndex
|
// the store panics when setting empty or nil indexes
|
||||||
} else {
|
k.SetDelegatorRewardIndexes(ctx, rewardPeriod.CollateralType, acc.Indexes)
|
||||||
newRewardIndexes = append(newRewardIndexes, newRewardIndex)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
k.SetDelegatorRewardIndexes(ctx, rewardPeriods.CollateralType, newRewardIndexes)
|
}
|
||||||
k.SetPreviousDelegatorRewardAccrualTime(ctx, rewardPeriods.CollateralType, ctx.BlockTime())
|
|
||||||
return nil
|
// getDelegatorTotalSourceShares fetches the sum of all source shares for a delegator reward.
|
||||||
|
// In the case of delegation, this is the total tokens staked to bonded validators.
|
||||||
|
func (k Keeper) getDelegatorTotalSourceShares(ctx sdk.Context, denom string) sdk.Dec {
|
||||||
|
totalBonded := k.stakingKeeper.TotalBondedTokens(ctx)
|
||||||
|
|
||||||
|
return totalBonded.ToDec()
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitializeDelegatorReward initializes the reward index of a delegator claim
|
// InitializeDelegatorReward initializes the reward index of a delegator claim
|
||||||
|
309
x/incentive/keeper/rewards_delegator_accum_test.go
Normal file
309
x/incentive/keeper/rewards_delegator_accum_test.go
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccumulateDelegatorRewardsTests struct {
|
||||||
|
unitTester
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateDelegatorRewardsTests) storedTimeEquals(denom string, expected time.Time) {
|
||||||
|
storedTime, found := suite.keeper.GetPreviousDelegatorRewardAccrualTime(suite.ctx, denom)
|
||||||
|
suite.True(found)
|
||||||
|
suite.Equal(expected, storedTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateDelegatorRewardsTests) storedIndexesEqual(denom string, expected types.RewardIndexes) {
|
||||||
|
storedIndexes, found := suite.keeper.GetDelegatorRewardIndexes(suite.ctx, denom)
|
||||||
|
suite.Equal(found, expected != nil)
|
||||||
|
suite.Equal(expected, storedIndexes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccumulateDelegatorRewards(t *testing.T) {
|
||||||
|
suite.Run(t, new(AccumulateDelegatorRewardsTests))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateDelegatorRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreased() {
|
||||||
|
|
||||||
|
stakingKeeper := newFakeStakingKeeper().addBondedTokens(1e6)
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil)
|
||||||
|
|
||||||
|
suite.storeGlobalDelegatorIndexes(types.MultiRewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: types.BondDenom,
|
||||||
|
RewardIndexes: types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("0.02"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousDelegatorRewardAccrualTime(suite.ctx, types.BondDenom, previousAccrualTime)
|
||||||
|
|
||||||
|
newAccrualTime := previousAccrualTime.Add(1 * time.Hour)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(newAccrualTime)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
types.BondDenom,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)), // same denoms as in global indexes
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateDelegatorRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// check time and factors
|
||||||
|
|
||||||
|
suite.storedTimeEquals(types.BondDenom, newAccrualTime)
|
||||||
|
suite.storedIndexesEqual(types.BondDenom, types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("7.22"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("3.64"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateDelegatorRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIncreased() {
|
||||||
|
|
||||||
|
stakingKeeper := newFakeStakingKeeper().addBondedTokens(1e6)
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil)
|
||||||
|
|
||||||
|
previousIndexes := types.MultiRewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: types.BondDenom,
|
||||||
|
RewardIndexes: types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("0.02"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
suite.storeGlobalDelegatorIndexes(previousIndexes)
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousDelegatorRewardAccrualTime(suite.ctx, types.BondDenom, previousAccrualTime)
|
||||||
|
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(previousAccrualTime)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
types.BondDenom,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)), // same denoms as in global indexes
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateDelegatorRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// check time and factors
|
||||||
|
|
||||||
|
suite.storedTimeEquals(types.BondDenom, previousAccrualTime)
|
||||||
|
expected, f := previousIndexes.Get(types.BondDenom)
|
||||||
|
suite.True(f)
|
||||||
|
suite.storedIndexesEqual(types.BondDenom, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateDelegatorRewardsTests) TestNoAccumulationWhenSourceSharesAreZero() {
|
||||||
|
|
||||||
|
stakingKeeper := newFakeStakingKeeper() // zero total bonded
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil)
|
||||||
|
|
||||||
|
previousIndexes := types.MultiRewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: types.BondDenom,
|
||||||
|
RewardIndexes: types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("0.02"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
suite.storeGlobalDelegatorIndexes(previousIndexes)
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousDelegatorRewardAccrualTime(suite.ctx, types.BondDenom, previousAccrualTime)
|
||||||
|
|
||||||
|
firstAccrualTime := previousAccrualTime.Add(7 * time.Second)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
types.BondDenom,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)), // same denoms as in global indexes
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateDelegatorRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// check time and factors
|
||||||
|
|
||||||
|
suite.storedTimeEquals(types.BondDenom, firstAccrualTime)
|
||||||
|
expected, f := previousIndexes.Get(types.BondDenom)
|
||||||
|
suite.True(f)
|
||||||
|
suite.storedIndexesEqual(types.BondDenom, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateDelegatorRewardsTests) TestStateAddedWhenStateDoesNotExist() {
|
||||||
|
|
||||||
|
stakingKeeper := newFakeStakingKeeper().addBondedTokens(1e6)
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
types.BondDenom,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)),
|
||||||
|
)
|
||||||
|
|
||||||
|
firstAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateDelegatorRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// After the first accumulation only the current block time should be stored.
|
||||||
|
// The indexes will be empty as no time has passed since the previous block because it didn't exist.
|
||||||
|
suite.storedTimeEquals(types.BondDenom, firstAccrualTime)
|
||||||
|
suite.storedIndexesEqual(types.BondDenom, nil)
|
||||||
|
|
||||||
|
secondAccrualTime := firstAccrualTime.Add(10 * time.Second)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(secondAccrualTime)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateDelegatorRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// After the second accumulation both current block time and indexes should be stored.
|
||||||
|
suite.storedTimeEquals(types.BondDenom, secondAccrualTime)
|
||||||
|
suite.storedIndexesEqual(types.BondDenom, types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("0.02"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("0.01"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateDelegatorRewardsTests) TestNoPanicWhenStateDoesNotExist() {
|
||||||
|
|
||||||
|
stakingKeeper := newFakeStakingKeeper()
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
types.BondDenom,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
cs(),
|
||||||
|
)
|
||||||
|
|
||||||
|
accrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(accrualTime)
|
||||||
|
|
||||||
|
// Accumulate with no source shares and no rewards per second will result in no increment to the indexes.
|
||||||
|
// No increment and no previous indexes stored, results in an updated of nil. Setting this in the state panics.
|
||||||
|
// Check there is no panic.
|
||||||
|
suite.NotPanics(func() {
|
||||||
|
suite.keeper.AccumulateDelegatorRewards(suite.ctx, period)
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.storedTimeEquals(types.BondDenom, accrualTime)
|
||||||
|
suite.storedIndexesEqual(types.BondDenom, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateDelegatorRewardsTests) TestNoAccumulationWhenBeforeStartTime() {
|
||||||
|
|
||||||
|
stakingKeeper := newFakeStakingKeeper().addBondedTokens(1e6)
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil)
|
||||||
|
|
||||||
|
previousIndexes := types.MultiRewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: types.BondDenom,
|
||||||
|
RewardIndexes: types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("0.02"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
suite.storeGlobalDelegatorIndexes(previousIndexes)
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousDelegatorRewardAccrualTime(suite.ctx, types.BondDenom, previousAccrualTime)
|
||||||
|
|
||||||
|
firstAccrualTime := previousAccrualTime.Add(10 * time.Second)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
types.BondDenom,
|
||||||
|
firstAccrualTime.Add(time.Nanosecond), // start time after accrual time
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)),
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateDelegatorRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// The accrual time should be updated, but the indexes unchanged
|
||||||
|
suite.storedTimeEquals(types.BondDenom, firstAccrualTime)
|
||||||
|
expectedIndexes, f := previousIndexes.Get(types.BondDenom)
|
||||||
|
suite.True(f)
|
||||||
|
suite.storedIndexesEqual(types.BondDenom, expectedIndexes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateDelegatorRewardsTests) TestPanicWhenCurrentTimeLessThanPrevious() {
|
||||||
|
|
||||||
|
stakingKeeper := newFakeStakingKeeper().addBondedTokens(1e6)
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil)
|
||||||
|
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousDelegatorRewardAccrualTime(suite.ctx, types.BondDenom, previousAccrualTime)
|
||||||
|
|
||||||
|
firstAccrualTime := time.Time{}
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
types.BondDenom,
|
||||||
|
time.Time{}, // start time after accrual time
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)),
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
suite.Panics(func() {
|
||||||
|
suite.keeper.AccumulateDelegatorRewards(suite.ctx, period)
|
||||||
|
})
|
||||||
|
}
|
@ -47,7 +47,7 @@ func (suite *InitializeDelegatorRewardTests) TestClaimIndexesAreSetWhenClaimDoes
|
|||||||
|
|
||||||
func (suite *InitializeDelegatorRewardTests) TestClaimIsSyncedAndIndexesAreSetWhenClaimDoesExist() {
|
func (suite *InitializeDelegatorRewardTests) TestClaimIsSyncedAndIndexesAreSetWhenClaimDoesExist() {
|
||||||
validatorAddress := arbitraryValidatorAddress()
|
validatorAddress := arbitraryValidatorAddress()
|
||||||
sk := fakeStakingKeeper{
|
sk := &fakeStakingKeeper{
|
||||||
delegations: stakingtypes.Delegations{{
|
delegations: stakingtypes.Delegations{{
|
||||||
ValidatorAddress: validatorAddress,
|
ValidatorAddress: validatorAddress,
|
||||||
Shares: d("1000"),
|
Shares: d("1000"),
|
||||||
@ -96,32 +96,3 @@ var arbitraryDelegatorRewardIndexes = types.MultiRewardIndexes{
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
type fakeStakingKeeper struct {
|
|
||||||
delegations stakingtypes.Delegations
|
|
||||||
validators stakingtypes.Validators
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k fakeStakingKeeper) TotalBondedTokens(ctx sdk.Context) sdk.Int {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
func (k fakeStakingKeeper) GetDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAddress, maxRetrieve uint16) []stakingtypes.Delegation {
|
|
||||||
return k.delegations
|
|
||||||
}
|
|
||||||
func (k fakeStakingKeeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (stakingtypes.Validator, bool) {
|
|
||||||
for _, val := range k.validators {
|
|
||||||
if val.GetOperator().Equals(addr) {
|
|
||||||
return val, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stakingtypes.Validator{}, false
|
|
||||||
}
|
|
||||||
func (k fakeStakingKeeper) GetValidatorDelegations(ctx sdk.Context, valAddr sdk.ValAddress) []stakingtypes.Delegation {
|
|
||||||
var delegations stakingtypes.Delegations
|
|
||||||
for _, d := range k.delegations {
|
|
||||||
if d.ValidatorAddress.Equals(valAddr) {
|
|
||||||
delegations = append(delegations, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return delegations
|
|
||||||
}
|
|
||||||
|
@ -36,7 +36,7 @@ func (suite *SynchronizeDelegatorRewardTests) storeGlobalDelegatorFactor(multiRe
|
|||||||
func (suite *SynchronizeDelegatorRewardTests) TestClaimIndexesAreUnchangedWhenGlobalFactorUnchanged() {
|
func (suite *SynchronizeDelegatorRewardTests) TestClaimIndexesAreUnchangedWhenGlobalFactorUnchanged() {
|
||||||
delegator := arbitraryAddress()
|
delegator := arbitraryAddress()
|
||||||
|
|
||||||
stakingKeeper := fakeStakingKeeper{} // use an empty staking keeper that returns no delegations
|
stakingKeeper := &fakeStakingKeeper{} // use an empty staking keeper that returns no delegations
|
||||||
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil)
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, stakingKeeper, nil)
|
||||||
|
|
||||||
claim := types.DelegatorClaim{
|
claim := types.DelegatorClaim{
|
||||||
@ -58,7 +58,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestClaimIndexesAreUnchangedWhenGl
|
|||||||
func (suite *SynchronizeDelegatorRewardTests) TestClaimIndexesAreUpdatedWhenGlobalFactorIncreased() {
|
func (suite *SynchronizeDelegatorRewardTests) TestClaimIndexesAreUpdatedWhenGlobalFactorIncreased() {
|
||||||
delegator := arbitraryAddress()
|
delegator := arbitraryAddress()
|
||||||
|
|
||||||
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, fakeStakingKeeper{}, nil)
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, &fakeStakingKeeper{}, nil)
|
||||||
|
|
||||||
claim := types.DelegatorClaim{
|
claim := types.DelegatorClaim{
|
||||||
BaseMultiClaim: types.BaseMultiClaim{
|
BaseMultiClaim: types.BaseMultiClaim{
|
||||||
@ -85,7 +85,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestClaimIndexesAreUpdatedWhenGlob
|
|||||||
func (suite *SynchronizeDelegatorRewardTests) TestRewardIsUnchangedWhenGlobalFactorUnchanged() {
|
func (suite *SynchronizeDelegatorRewardTests) TestRewardIsUnchangedWhenGlobalFactorUnchanged() {
|
||||||
delegator := arbitraryAddress()
|
delegator := arbitraryAddress()
|
||||||
validatorAddress := arbitraryValidatorAddress()
|
validatorAddress := arbitraryValidatorAddress()
|
||||||
stakingKeeper := fakeStakingKeeper{
|
stakingKeeper := &fakeStakingKeeper{
|
||||||
delegations: stakingtypes.Delegations{
|
delegations: stakingtypes.Delegations{
|
||||||
{
|
{
|
||||||
DelegatorAddress: delegator,
|
DelegatorAddress: delegator,
|
||||||
@ -130,7 +130,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestRewardIsUnchangedWhenGlobalFac
|
|||||||
func (suite *SynchronizeDelegatorRewardTests) TestRewardIsIncreasedWhenNewRewardAdded() {
|
func (suite *SynchronizeDelegatorRewardTests) TestRewardIsIncreasedWhenNewRewardAdded() {
|
||||||
delegator := arbitraryAddress()
|
delegator := arbitraryAddress()
|
||||||
validatorAddress := arbitraryValidatorAddress()
|
validatorAddress := arbitraryValidatorAddress()
|
||||||
stakingKeeper := fakeStakingKeeper{
|
stakingKeeper := &fakeStakingKeeper{
|
||||||
delegations: stakingtypes.Delegations{
|
delegations: stakingtypes.Delegations{
|
||||||
{
|
{
|
||||||
DelegatorAddress: delegator,
|
DelegatorAddress: delegator,
|
||||||
@ -180,7 +180,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestRewardIsIncreasedWhenNewReward
|
|||||||
func (suite *SynchronizeDelegatorRewardTests) TestRewardIsIncreasedWhenGlobalFactorIncreased() {
|
func (suite *SynchronizeDelegatorRewardTests) TestRewardIsIncreasedWhenGlobalFactorIncreased() {
|
||||||
delegator := arbitraryAddress()
|
delegator := arbitraryAddress()
|
||||||
validatorAddress := arbitraryValidatorAddress()
|
validatorAddress := arbitraryValidatorAddress()
|
||||||
stakingKeeper := fakeStakingKeeper{
|
stakingKeeper := &fakeStakingKeeper{
|
||||||
delegations: stakingtypes.Delegations{
|
delegations: stakingtypes.Delegations{
|
||||||
{
|
{
|
||||||
DelegatorAddress: delegator,
|
DelegatorAddress: delegator,
|
||||||
@ -266,7 +266,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestGetDelegatedWhenValAddrIsNil()
|
|||||||
// when valAddr is nil, get total delegated to bonded validators
|
// when valAddr is nil, get total delegated to bonded validators
|
||||||
delegator := arbitraryAddress()
|
delegator := arbitraryAddress()
|
||||||
validatorAddresses := generateValidatorAddresses(4)
|
validatorAddresses := generateValidatorAddresses(4)
|
||||||
stakingKeeper := fakeStakingKeeper{
|
stakingKeeper := &fakeStakingKeeper{
|
||||||
delegations: stakingtypes.Delegations{
|
delegations: stakingtypes.Delegations{
|
||||||
//bonded
|
//bonded
|
||||||
{
|
{
|
||||||
@ -309,7 +309,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestGetDelegatedWhenExcludingAVali
|
|||||||
// when valAddr is x, get total delegated to bonded validators excluding those to x
|
// when valAddr is x, get total delegated to bonded validators excluding those to x
|
||||||
delegator := arbitraryAddress()
|
delegator := arbitraryAddress()
|
||||||
validatorAddresses := generateValidatorAddresses(4)
|
validatorAddresses := generateValidatorAddresses(4)
|
||||||
stakingKeeper := fakeStakingKeeper{
|
stakingKeeper := &fakeStakingKeeper{
|
||||||
delegations: stakingtypes.Delegations{
|
delegations: stakingtypes.Delegations{
|
||||||
//bonded
|
//bonded
|
||||||
{
|
{
|
||||||
@ -352,7 +352,7 @@ func (suite *SynchronizeDelegatorRewardTests) TestGetDelegatedWhenIncludingAVali
|
|||||||
// when valAddr is x, get total delegated to bonded validators including those to x
|
// when valAddr is x, get total delegated to bonded validators including those to x
|
||||||
delegator := arbitraryAddress()
|
delegator := arbitraryAddress()
|
||||||
validatorAddresses := generateValidatorAddresses(4)
|
validatorAddresses := generateValidatorAddresses(4)
|
||||||
stakingKeeper := fakeStakingKeeper{
|
stakingKeeper := &fakeStakingKeeper{
|
||||||
delegations: stakingtypes.Delegations{
|
delegations: stakingtypes.Delegations{
|
||||||
//bonded
|
//bonded
|
||||||
{
|
{
|
||||||
|
@ -148,8 +148,7 @@ func (suite *DelegatorRewardsTestSuite) TestAccumulateDelegatorRewards() {
|
|||||||
|
|
||||||
rewardPeriods, found := suite.keeper.GetDelegatorRewardPeriods(runCtx, tc.args.delegation.Denom)
|
rewardPeriods, found := suite.keeper.GetDelegatorRewardPeriods(runCtx, tc.args.delegation.Denom)
|
||||||
suite.Require().True(found)
|
suite.Require().True(found)
|
||||||
err = suite.keeper.AccumulateDelegatorRewards(runCtx, rewardPeriods)
|
suite.keeper.AccumulateDelegatorRewards(runCtx, rewardPeriods)
|
||||||
suite.Require().NoError(err)
|
|
||||||
|
|
||||||
rewardIndexes, _ := suite.keeper.GetDelegatorRewardIndexes(runCtx, tc.args.delegation.Denom)
|
rewardIndexes, _ := suite.keeper.GetDelegatorRewardIndexes(runCtx, tc.args.delegation.Denom)
|
||||||
suite.Require().Equal(tc.args.expectedRewardIndexes, rewardIndexes)
|
suite.Require().Equal(tc.args.expectedRewardIndexes, rewardIndexes)
|
||||||
@ -269,8 +268,7 @@ func (suite *DelegatorRewardsTestSuite) TestSynchronizeDelegatorReward() {
|
|||||||
rewardPeriods, found := suite.keeper.GetDelegatorRewardPeriods(blockCtx, tc.args.delegation.Denom)
|
rewardPeriods, found := suite.keeper.GetDelegatorRewardPeriods(blockCtx, tc.args.delegation.Denom)
|
||||||
suite.Require().True(found)
|
suite.Require().True(found)
|
||||||
|
|
||||||
err := suite.keeper.AccumulateDelegatorRewards(blockCtx, rewardPeriods)
|
suite.keeper.AccumulateDelegatorRewards(blockCtx, rewardPeriods)
|
||||||
suite.Require().NoError(err)
|
|
||||||
}
|
}
|
||||||
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
||||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||||
@ -385,8 +383,7 @@ func (suite *DelegatorRewardsTestSuite) TestSimulateDelegatorRewardSynchronizati
|
|||||||
// Accumulate delegator rewards
|
// Accumulate delegator rewards
|
||||||
rewardPeriods, found := suite.keeper.GetDelegatorRewardPeriods(blockCtx, tc.args.delegation.Denom)
|
rewardPeriods, found := suite.keeper.GetDelegatorRewardPeriods(blockCtx, tc.args.delegation.Denom)
|
||||||
suite.Require().True(found)
|
suite.Require().True(found)
|
||||||
err := suite.keeper.AccumulateDelegatorRewards(blockCtx, rewardPeriods)
|
suite.keeper.AccumulateDelegatorRewards(blockCtx, rewardPeriods)
|
||||||
suite.Require().NoError(err)
|
|
||||||
}
|
}
|
||||||
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
||||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||||
|
@ -2,8 +2,6 @@ package keeper
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
@ -11,70 +9,52 @@ import (
|
|||||||
"github.com/kava-labs/kava/x/incentive/types"
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AccumulateHardSupplyRewards updates the rewards accumulated for the input reward period
|
// AccumulateHardSupplyRewards calculates new rewards to distribute this block and updates the global indexes to reflect this.
|
||||||
func (k Keeper) AccumulateHardSupplyRewards(ctx sdk.Context, rewardPeriod types.MultiRewardPeriod) error {
|
// The provided rewardPeriod must be valid to avoid panics in calculating time durations.
|
||||||
|
func (k Keeper) AccumulateHardSupplyRewards(ctx sdk.Context, rewardPeriod types.MultiRewardPeriod) {
|
||||||
|
|
||||||
previousAccrualTime, found := k.GetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType)
|
previousAccrualTime, found := k.GetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType)
|
||||||
if !found {
|
if !found {
|
||||||
k.SetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
previousAccrualTime = ctx.BlockTime()
|
||||||
return nil
|
|
||||||
}
|
|
||||||
timeElapsed := CalculateTimeElapsed(rewardPeriod.Start, rewardPeriod.End, ctx.BlockTime(), previousAccrualTime)
|
|
||||||
if timeElapsed.IsZero() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if rewardPeriod.RewardsPerSecond.IsZero() {
|
|
||||||
k.SetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
totalSuppliedCoins, foundTotalSuppliedCoins := k.hardKeeper.GetSuppliedCoins(ctx)
|
indexes, found := k.GetHardSupplyRewardIndexes(ctx, rewardPeriod.CollateralType)
|
||||||
if !foundTotalSuppliedCoins {
|
|
||||||
k.SetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
totalSupplied := totalSuppliedCoins.AmountOf(rewardPeriod.CollateralType).ToDec()
|
|
||||||
if totalSupplied.IsZero() {
|
|
||||||
k.SetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
previousRewardIndexes, found := k.GetHardSupplyRewardIndexes(ctx, rewardPeriod.CollateralType)
|
|
||||||
if !found {
|
if !found {
|
||||||
for _, rewardCoin := range rewardPeriod.RewardsPerSecond {
|
indexes = types.RewardIndexes{}
|
||||||
rewardIndex := types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec())
|
|
||||||
previousRewardIndexes = append(previousRewardIndexes, rewardIndex)
|
|
||||||
}
|
|
||||||
k.SetHardSupplyRewardIndexes(ctx, rewardPeriod.CollateralType, previousRewardIndexes)
|
|
||||||
}
|
}
|
||||||
hardFactor, found := k.hardKeeper.GetSupplyInterestFactor(ctx, rewardPeriod.CollateralType)
|
|
||||||
|
acc := types.NewAccumulator(previousAccrualTime, indexes)
|
||||||
|
|
||||||
|
totalSource := k.getHardSupplyTotalSourceShares(ctx, rewardPeriod.CollateralType)
|
||||||
|
|
||||||
|
acc.Accumulate(rewardPeriod, totalSource, ctx.BlockTime())
|
||||||
|
|
||||||
|
k.SetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType, acc.PreviousAccumulationTime)
|
||||||
|
if len(acc.Indexes) > 0 {
|
||||||
|
// the store panics when setting empty or nil indexes
|
||||||
|
k.SetHardSupplyRewardIndexes(ctx, rewardPeriod.CollateralType, acc.Indexes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getHardSupplyTotalSourceShares fetches the sum of all source shares for a supply reward.
|
||||||
|
// In the case of hard supply, this is the total supplied divided by the supply interest factor.
|
||||||
|
// This give the "pre interest" value of the total supplied.
|
||||||
|
func (k Keeper) getHardSupplyTotalSourceShares(ctx sdk.Context, denom string) sdk.Dec {
|
||||||
|
totalSuppliedCoins, found := k.hardKeeper.GetSuppliedCoins(ctx)
|
||||||
if !found {
|
if !found {
|
||||||
k.SetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
// assume no coins have been supplied
|
||||||
return nil
|
totalSuppliedCoins = sdk.NewCoins()
|
||||||
|
}
|
||||||
|
totalSupplied := totalSuppliedCoins.AmountOf(denom)
|
||||||
|
|
||||||
|
interestFactor, found := k.hardKeeper.GetSupplyInterestFactor(ctx, denom)
|
||||||
|
if !found {
|
||||||
|
// assume nothing has been borrowed so the factor starts at it's default value
|
||||||
|
interestFactor = sdk.OneDec()
|
||||||
}
|
}
|
||||||
|
|
||||||
newRewardIndexes := previousRewardIndexes
|
// return supplied/factor to get the "pre interest" value of the current total supplied
|
||||||
for _, rewardCoin := range rewardPeriod.RewardsPerSecond {
|
return totalSupplied.ToDec().Quo(interestFactor)
|
||||||
newRewards := rewardCoin.Amount.ToDec().Mul(timeElapsed.ToDec())
|
|
||||||
previousRewardIndex, found := previousRewardIndexes.GetRewardIndex(rewardCoin.Denom)
|
|
||||||
if !found {
|
|
||||||
previousRewardIndex = types.NewRewardIndex(rewardCoin.Denom, sdk.ZeroDec())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate new reward factor and update reward index
|
|
||||||
rewardFactor := newRewards.Mul(hardFactor).Quo(totalSupplied)
|
|
||||||
newRewardFactorValue := previousRewardIndex.RewardFactor.Add(rewardFactor)
|
|
||||||
newRewardIndex := types.NewRewardIndex(rewardCoin.Denom, newRewardFactorValue)
|
|
||||||
i, found := newRewardIndexes.GetFactorIndex(rewardCoin.Denom)
|
|
||||||
if found {
|
|
||||||
newRewardIndexes[i] = newRewardIndex
|
|
||||||
} else {
|
|
||||||
newRewardIndexes = append(newRewardIndexes, newRewardIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
k.SetHardSupplyRewardIndexes(ctx, rewardPeriod.CollateralType, newRewardIndexes)
|
|
||||||
k.SetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitializeHardSupplyReward initializes the supply-side of a hard liquidity provider claim
|
// InitializeHardSupplyReward initializes the supply-side of a hard liquidity provider claim
|
||||||
@ -188,13 +168,6 @@ func (k Keeper) SynchronizeHardLiquidityProviderClaim(ctx sdk.Context, owner sdk
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ZeroHardLiquidityProviderClaim zeroes out the claim object's rewards and returns the updated claim object
|
|
||||||
func (k Keeper) ZeroHardLiquidityProviderClaim(ctx sdk.Context, claim types.HardLiquidityProviderClaim) types.HardLiquidityProviderClaim {
|
|
||||||
claim.Reward = sdk.NewCoins()
|
|
||||||
k.SetHardLiquidityProviderClaim(ctx, claim)
|
|
||||||
return claim
|
|
||||||
}
|
|
||||||
|
|
||||||
// SimulateHardSynchronization calculates a user's outstanding hard rewards by simulating reward synchronization
|
// SimulateHardSynchronization calculates a user's outstanding hard rewards by simulating reward synchronization
|
||||||
func (k Keeper) SimulateHardSynchronization(ctx sdk.Context, claim types.HardLiquidityProviderClaim) types.HardLiquidityProviderClaim {
|
func (k Keeper) SimulateHardSynchronization(ctx sdk.Context, claim types.HardLiquidityProviderClaim) types.HardLiquidityProviderClaim {
|
||||||
// 1. Simulate Hard supply-side rewards
|
// 1. Simulate Hard supply-side rewards
|
||||||
@ -300,29 +273,6 @@ func (k Keeper) SimulateHardSynchronization(ctx sdk.Context, claim types.HardLiq
|
|||||||
return claim
|
return claim
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalculateTimeElapsed calculates the number of reward-eligible seconds that have passed since the previous
|
|
||||||
// time rewards were accrued, taking into account the end time of the reward period
|
|
||||||
func CalculateTimeElapsed(start, end, blockTime time.Time, previousAccrualTime time.Time) sdk.Int {
|
|
||||||
if (end.Before(blockTime) &&
|
|
||||||
(end.Before(previousAccrualTime) || end.Equal(previousAccrualTime))) ||
|
|
||||||
(start.After(blockTime)) ||
|
|
||||||
(start.Equal(blockTime)) {
|
|
||||||
return sdk.ZeroInt()
|
|
||||||
}
|
|
||||||
if start.After(previousAccrualTime) && start.Before(blockTime) {
|
|
||||||
previousAccrualTime = start
|
|
||||||
}
|
|
||||||
|
|
||||||
if end.Before(blockTime) {
|
|
||||||
return sdk.MaxInt(sdk.ZeroInt(), sdk.NewInt(int64(math.RoundToEven(
|
|
||||||
end.Sub(previousAccrualTime).Seconds(),
|
|
||||||
))))
|
|
||||||
}
|
|
||||||
return sdk.MaxInt(sdk.ZeroInt(), sdk.NewInt(int64(math.RoundToEven(
|
|
||||||
blockTime.Sub(previousAccrualTime).Seconds(),
|
|
||||||
))))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set setDifference: A - B
|
// Set setDifference: A - B
|
||||||
func setDifference(a, b []string) (diff []string) {
|
func setDifference(a, b []string) (diff []string) {
|
||||||
m := make(map[string]bool)
|
m := make(map[string]bool)
|
||||||
|
316
x/incentive/keeper/rewards_supply_accum_test.go
Normal file
316
x/incentive/keeper/rewards_supply_accum_test.go
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccumulateSupplyRewardsTests struct {
|
||||||
|
unitTester
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateSupplyRewardsTests) storedTimeEquals(denom string, expected time.Time) {
|
||||||
|
storedTime, found := suite.keeper.GetPreviousHardSupplyRewardAccrualTime(suite.ctx, denom)
|
||||||
|
suite.True(found)
|
||||||
|
suite.Equal(expected, storedTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateSupplyRewardsTests) storedIndexesEqual(denom string, expected types.RewardIndexes) {
|
||||||
|
storedIndexes, found := suite.keeper.GetHardSupplyRewardIndexes(suite.ctx, denom)
|
||||||
|
suite.Equal(found, expected != nil)
|
||||||
|
suite.Equal(expected, storedIndexes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccumulateSupplyRewards(t *testing.T) {
|
||||||
|
suite.Run(t, new(AccumulateSupplyRewardsTests))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateSupplyRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreased() {
|
||||||
|
denom := "bnb"
|
||||||
|
|
||||||
|
hardKeeper := newFakeHardKeeper().addTotalSupply(c(denom, 1e6), d("1"))
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil)
|
||||||
|
|
||||||
|
suite.storeGlobalSupplyIndexes(types.MultiRewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: denom,
|
||||||
|
RewardIndexes: types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("0.02"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, denom, previousAccrualTime)
|
||||||
|
|
||||||
|
newAccrualTime := previousAccrualTime.Add(1 * time.Hour)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(newAccrualTime)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
denom,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)), // same denoms as in global indexes
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateHardSupplyRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// check time and factors
|
||||||
|
|
||||||
|
suite.storedTimeEquals(denom, newAccrualTime)
|
||||||
|
suite.storedIndexesEqual(denom, types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("7.22"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("3.64"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateSupplyRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIncreased() {
|
||||||
|
denom := "bnb"
|
||||||
|
|
||||||
|
hardKeeper := newFakeHardKeeper().addTotalSupply(c(denom, 1e6), d("1"))
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil)
|
||||||
|
|
||||||
|
previousIndexes := types.MultiRewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: denom,
|
||||||
|
RewardIndexes: types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("0.02"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
suite.storeGlobalSupplyIndexes(previousIndexes)
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, denom, previousAccrualTime)
|
||||||
|
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(previousAccrualTime)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
denom,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)), // same denoms as in global indexes
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateHardSupplyRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// check time and factors
|
||||||
|
|
||||||
|
suite.storedTimeEquals(denom, previousAccrualTime)
|
||||||
|
expected, f := previousIndexes.Get(denom)
|
||||||
|
suite.True(f)
|
||||||
|
suite.storedIndexesEqual(denom, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateSupplyRewardsTests) TestNoAccumulationWhenSourceSharesAreZero() {
|
||||||
|
denom := "bnb"
|
||||||
|
|
||||||
|
hardKeeper := newFakeHardKeeper() // zero total supplys
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil)
|
||||||
|
|
||||||
|
previousIndexes := types.MultiRewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: denom,
|
||||||
|
RewardIndexes: types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("0.02"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
suite.storeGlobalSupplyIndexes(previousIndexes)
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, denom, previousAccrualTime)
|
||||||
|
|
||||||
|
firstAccrualTime := previousAccrualTime.Add(7 * time.Second)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
denom,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)), // same denoms as in global indexes
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateHardSupplyRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// check time and factors
|
||||||
|
|
||||||
|
suite.storedTimeEquals(denom, firstAccrualTime)
|
||||||
|
expected, f := previousIndexes.Get(denom)
|
||||||
|
suite.True(f)
|
||||||
|
suite.storedIndexesEqual(denom, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateSupplyRewardsTests) TestStateAddedWhenStateDoesNotExist() {
|
||||||
|
denom := "bnb"
|
||||||
|
|
||||||
|
hardKeeper := newFakeHardKeeper().addTotalSupply(c(denom, 1e6), d("1"))
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
denom,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)),
|
||||||
|
)
|
||||||
|
|
||||||
|
firstAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateHardSupplyRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// After the first accumulation only the current block time should be stored.
|
||||||
|
// The indexes will be empty as no time has passed since the previous block because it didn't exist.
|
||||||
|
suite.storedTimeEquals(denom, firstAccrualTime)
|
||||||
|
suite.storedIndexesEqual(denom, nil)
|
||||||
|
|
||||||
|
secondAccrualTime := firstAccrualTime.Add(10 * time.Second)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(secondAccrualTime)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateHardSupplyRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// After the second accumulation both current block time and indexes should be stored.
|
||||||
|
suite.storedTimeEquals(denom, secondAccrualTime)
|
||||||
|
suite.storedIndexesEqual(denom, types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("0.02"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("0.01"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateSupplyRewardsTests) TestNoPanicWhenStateDoesNotExist() {
|
||||||
|
denom := "bnb"
|
||||||
|
|
||||||
|
hardKeeper := newFakeHardKeeper()
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
denom,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
cs(),
|
||||||
|
)
|
||||||
|
|
||||||
|
accrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(accrualTime)
|
||||||
|
|
||||||
|
// Accumulate with no source shares and no rewards per second will result in no increment to the indexes.
|
||||||
|
// No increment and no previous indexes stored, results in an updated of nil. Setting this in the state panics.
|
||||||
|
// Check there is no panic.
|
||||||
|
suite.NotPanics(func() {
|
||||||
|
suite.keeper.AccumulateHardSupplyRewards(suite.ctx, period)
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.storedTimeEquals(denom, accrualTime)
|
||||||
|
suite.storedIndexesEqual(denom, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateSupplyRewardsTests) TestNoAccumulationWhenBeforeStartTime() {
|
||||||
|
denom := "bnb"
|
||||||
|
|
||||||
|
hardKeeper := newFakeHardKeeper().addTotalSupply(c(denom, 1e6), d("1"))
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil)
|
||||||
|
|
||||||
|
previousIndexes := types.MultiRewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: denom,
|
||||||
|
RewardIndexes: types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "hard",
|
||||||
|
RewardFactor: d("0.02"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
suite.storeGlobalSupplyIndexes(previousIndexes)
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, denom, previousAccrualTime)
|
||||||
|
|
||||||
|
firstAccrualTime := previousAccrualTime.Add(10 * time.Second)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
denom,
|
||||||
|
firstAccrualTime.Add(time.Nanosecond), // start time after accrual time
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)),
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateHardSupplyRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// The accrual time should be updated, but the indexes unchanged
|
||||||
|
suite.storedTimeEquals(denom, firstAccrualTime)
|
||||||
|
expectedIndexes, f := previousIndexes.Get(denom)
|
||||||
|
suite.True(f)
|
||||||
|
suite.storedIndexesEqual(denom, expectedIndexes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateSupplyRewardsTests) TestPanicWhenCurrentTimeLessThanPrevious() {
|
||||||
|
denom := "bnb"
|
||||||
|
|
||||||
|
hardKeeper := newFakeHardKeeper().addTotalSupply(c(denom, 1e6), d("1"))
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, hardKeeper, nil, nil, nil)
|
||||||
|
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, denom, previousAccrualTime)
|
||||||
|
|
||||||
|
firstAccrualTime := time.Time{}
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
denom,
|
||||||
|
time.Time{}, // start time after accrual time
|
||||||
|
distantFuture,
|
||||||
|
cs(c("hard", 2000), c("ukava", 1000)),
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
suite.Panics(func() {
|
||||||
|
suite.keeper.AccumulateHardSupplyRewards(suite.ctx, period)
|
||||||
|
})
|
||||||
|
}
|
@ -186,8 +186,7 @@ func (suite *SupplyRewardsTestSuite) TestAccumulateHardSupplyRewards() {
|
|||||||
// Accumulate hard supply rewards for the deposit denom
|
// Accumulate hard supply rewards for the deposit denom
|
||||||
multiRewardPeriod, found := suite.keeper.GetHardSupplyRewardPeriods(runCtx, tc.args.deposit.Denom)
|
multiRewardPeriod, found := suite.keeper.GetHardSupplyRewardPeriods(runCtx, tc.args.deposit.Denom)
|
||||||
suite.Require().True(found)
|
suite.Require().True(found)
|
||||||
err = suite.keeper.AccumulateHardSupplyRewards(runCtx, multiRewardPeriod)
|
suite.keeper.AccumulateHardSupplyRewards(runCtx, multiRewardPeriod)
|
||||||
suite.Require().NoError(err)
|
|
||||||
|
|
||||||
// Check that each expected reward index matches the current stored reward index for the denom
|
// Check that each expected reward index matches the current stored reward index for the denom
|
||||||
globalRewardIndexes, found := suite.keeper.GetHardSupplyRewardIndexes(runCtx, tc.args.deposit.Denom)
|
globalRewardIndexes, found := suite.keeper.GetHardSupplyRewardIndexes(runCtx, tc.args.deposit.Denom)
|
||||||
@ -548,8 +547,8 @@ func (suite *SupplyRewardsTestSuite) TestSynchronizeHardSupplyReward() {
|
|||||||
// Accumulate hard supply-side rewards
|
// Accumulate hard supply-side rewards
|
||||||
multiRewardPeriod, found := suite.keeper.GetHardSupplyRewardPeriods(blockCtx, tc.args.deposit.Denom)
|
multiRewardPeriod, found := suite.keeper.GetHardSupplyRewardPeriods(blockCtx, tc.args.deposit.Denom)
|
||||||
if found {
|
if found {
|
||||||
err := suite.keeper.AccumulateHardSupplyRewards(blockCtx, multiRewardPeriod)
|
suite.keeper.AccumulateHardSupplyRewards(blockCtx, multiRewardPeriod)
|
||||||
suite.Require().NoError(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
||||||
@ -658,15 +657,13 @@ func (suite *SupplyRewardsTestSuite) TestSynchronizeHardSupplyReward() {
|
|||||||
// But new deposit denoms don't have their PreviousHardSupplyRewardAccrualTime set yet,
|
// But new deposit denoms don't have their PreviousHardSupplyRewardAccrualTime set yet,
|
||||||
// so we need to call the accumulation method once to set the initial reward accrual time
|
// so we need to call the accumulation method once to set the initial reward accrual time
|
||||||
if tc.args.deposit.Denom != tc.args.incentiveSupplyRewardDenom {
|
if tc.args.deposit.Denom != tc.args.incentiveSupplyRewardDenom {
|
||||||
err = suite.keeper.AccumulateHardSupplyRewards(suite.ctx, multiRewardPeriod)
|
suite.keeper.AccumulateHardSupplyRewards(suite.ctx, multiRewardPeriod)
|
||||||
suite.Require().NoError(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we can jump forward in time and accumulate rewards
|
// Now we can jump forward in time and accumulate rewards
|
||||||
updatedBlockTime = previousBlockTime.Add(time.Duration(int(time.Second) * tc.args.updatedTimeDuration))
|
updatedBlockTime = previousBlockTime.Add(time.Duration(int(time.Second) * tc.args.updatedTimeDuration))
|
||||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||||
err = suite.keeper.AccumulateHardSupplyRewards(suite.ctx, multiRewardPeriod)
|
suite.keeper.AccumulateHardSupplyRewards(suite.ctx, multiRewardPeriod)
|
||||||
suite.Require().NoError(err)
|
|
||||||
|
|
||||||
// After we've accumulated, run synchronize
|
// After we've accumulated, run synchronize
|
||||||
deposit, found = suite.hardKeeper.GetDeposit(suite.ctx, userAddr)
|
deposit, found = suite.hardKeeper.GetDeposit(suite.ctx, userAddr)
|
||||||
@ -919,8 +916,7 @@ func (suite *SupplyRewardsTestSuite) TestSimulateHardSupplyRewardSynchronization
|
|||||||
// Accumulate hard supply-side rewards
|
// Accumulate hard supply-side rewards
|
||||||
multiRewardPeriod, found := suite.keeper.GetHardSupplyRewardPeriods(blockCtx, tc.args.deposit.Denom)
|
multiRewardPeriod, found := suite.keeper.GetHardSupplyRewardPeriods(blockCtx, tc.args.deposit.Denom)
|
||||||
suite.Require().True(found)
|
suite.Require().True(found)
|
||||||
err := suite.keeper.AccumulateHardSupplyRewards(blockCtx, multiRewardPeriod)
|
suite.keeper.AccumulateHardSupplyRewards(blockCtx, multiRewardPeriod)
|
||||||
suite.Require().NoError(err)
|
|
||||||
}
|
}
|
||||||
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
||||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||||
|
@ -24,12 +24,9 @@ func (k Keeper) AccumulateSwapRewards(ctx sdk.Context, rewardPeriod types.MultiR
|
|||||||
|
|
||||||
acc := types.NewAccumulator(previousAccrualTime, indexes)
|
acc := types.NewAccumulator(previousAccrualTime, indexes)
|
||||||
|
|
||||||
totalShares, found := k.swapKeeper.GetPoolShares(ctx, rewardPeriod.CollateralType)
|
totalSource := k.getSwapTotalSourceShares(ctx, rewardPeriod.CollateralType)
|
||||||
if !found {
|
|
||||||
totalShares = sdk.ZeroInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
acc.Accumulate(rewardPeriod, totalShares.ToDec(), ctx.BlockTime())
|
acc.Accumulate(rewardPeriod, totalSource, ctx.BlockTime())
|
||||||
|
|
||||||
k.SetSwapRewardAccrualTime(ctx, rewardPeriod.CollateralType, acc.PreviousAccumulationTime)
|
k.SetSwapRewardAccrualTime(ctx, rewardPeriod.CollateralType, acc.PreviousAccumulationTime)
|
||||||
if len(acc.Indexes) > 0 {
|
if len(acc.Indexes) > 0 {
|
||||||
@ -38,6 +35,16 @@ func (k Keeper) AccumulateSwapRewards(ctx sdk.Context, rewardPeriod types.MultiR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getSwapTotalSourceShares fetches the sum of all source shares for a swap reward.
|
||||||
|
// In the case of swap, these are the total (swap module) shares in a particular pool.
|
||||||
|
func (k Keeper) getSwapTotalSourceShares(ctx sdk.Context, poolID string) sdk.Dec {
|
||||||
|
totalShares, found := k.swapKeeper.GetPoolShares(ctx, poolID)
|
||||||
|
if !found {
|
||||||
|
totalShares = sdk.ZeroInt()
|
||||||
|
}
|
||||||
|
return totalShares.ToDec()
|
||||||
|
}
|
||||||
|
|
||||||
// InitializeSwapReward creates a new claim with zero rewards and indexes matching the global indexes.
|
// InitializeSwapReward creates a new claim with zero rewards and indexes matching the global indexes.
|
||||||
// If the claim already exists it just updates the indexes.
|
// If the claim already exists it just updates the indexes.
|
||||||
func (k Keeper) InitializeSwapReward(ctx sdk.Context, poolID string, owner sdk.AccAddress) {
|
func (k Keeper) InitializeSwapReward(ctx sdk.Context, poolID string, owner sdk.AccAddress) {
|
||||||
@ -107,7 +114,7 @@ func (k Keeper) GetSynchronizedSwapClaim(ctx sdk.Context, owner sdk.AccAddress)
|
|||||||
if !found {
|
if !found {
|
||||||
return types.SwapClaim{}, false
|
return types.SwapClaim{}, false
|
||||||
}
|
}
|
||||||
for _, indexes := range claim.RewardIndexes {
|
for _, indexes := range claim.RewardIndexes { // TODO shouldn't this loop through global indexes, in case some have been recently added?
|
||||||
poolID := indexes.CollateralType
|
poolID := indexes.CollateralType
|
||||||
|
|
||||||
shares, found := k.swapKeeper.GetDepositorSharesAmount(ctx, owner, poolID)
|
shares, found := k.swapKeeper.GetDepositorSharesAmount(ctx, owner, poolID)
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/x/incentive/types"
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
@ -14,15 +13,15 @@ type AccumulateSwapRewardsTests struct {
|
|||||||
unitTester
|
unitTester
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AccumulateSwapRewardsTests) checkStoredTimeEquals(poolID string, expected time.Time) {
|
func (suite *AccumulateSwapRewardsTests) storedTimeEquals(poolID string, expected time.Time) {
|
||||||
storedTime, found := suite.keeper.GetSwapRewardAccrualTime(suite.ctx, poolID)
|
storedTime, found := suite.keeper.GetSwapRewardAccrualTime(suite.ctx, poolID)
|
||||||
suite.True(found)
|
suite.True(found)
|
||||||
suite.Equal(expected, storedTime)
|
suite.Equal(expected, storedTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AccumulateSwapRewardsTests) checkStoredIndexesEqual(poolID string, expected types.RewardIndexes) {
|
func (suite *AccumulateSwapRewardsTests) storedIndexesEqual(poolID string, expected types.RewardIndexes) {
|
||||||
storedIndexes, found := suite.keeper.GetSwapRewardIndexes(suite.ctx, poolID)
|
storedIndexes, found := suite.keeper.GetSwapRewardIndexes(suite.ctx, poolID)
|
||||||
suite.True(found)
|
suite.Equal(found, expected != nil)
|
||||||
suite.Equal(expected, storedIndexes)
|
suite.Equal(expected, storedIndexes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,10 +30,11 @@ func TestAccumulateSwapRewards(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AccumulateSwapRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreased() {
|
func (suite *AccumulateSwapRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreased() {
|
||||||
swapKeeper := &fakeSwapKeeper{i(1e6)}
|
pool := "btc:usdx"
|
||||||
|
|
||||||
|
swapKeeper := newFakeSwapKeeper().addPool(pool, i(1e6))
|
||||||
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper)
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper)
|
||||||
|
|
||||||
pool := "btc/usdx"
|
|
||||||
suite.storeGlobalSwapIndexes(types.MultiRewardIndexes{
|
suite.storeGlobalSwapIndexes(types.MultiRewardIndexes{
|
||||||
{
|
{
|
||||||
CollateralType: pool,
|
CollateralType: pool,
|
||||||
@ -68,9 +68,8 @@ func (suite *AccumulateSwapRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas
|
|||||||
|
|
||||||
// check time and factors
|
// check time and factors
|
||||||
|
|
||||||
suite.checkStoredTimeEquals(pool, newAccrualTime)
|
suite.storedTimeEquals(pool, newAccrualTime)
|
||||||
|
suite.storedIndexesEqual(pool, types.RewardIndexes{
|
||||||
expectedIndexes := types.RewardIndexes{
|
|
||||||
{
|
{
|
||||||
CollateralType: "swap",
|
CollateralType: "swap",
|
||||||
RewardFactor: d("7.22"),
|
RewardFactor: d("7.22"),
|
||||||
@ -79,62 +78,16 @@ func (suite *AccumulateSwapRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas
|
|||||||
CollateralType: "ukava",
|
CollateralType: "ukava",
|
||||||
RewardFactor: d("3.64"),
|
RewardFactor: d("3.64"),
|
||||||
},
|
},
|
||||||
}
|
|
||||||
suite.checkStoredIndexesEqual(pool, expectedIndexes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *AccumulateSwapRewardsTests) TestLimitsOfAccumulationPrecision() {
|
|
||||||
swapKeeper := &fakeSwapKeeper{i(1e17)} // approximate shares in a $1B pool of 10^8 precision ~$1 asset
|
|
||||||
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper)
|
|
||||||
|
|
||||||
pool := "btc/usdx"
|
|
||||||
suite.storeGlobalSwapIndexes(types.MultiRewardIndexes{
|
|
||||||
{
|
|
||||||
CollateralType: pool,
|
|
||||||
RewardIndexes: types.RewardIndexes{
|
|
||||||
{
|
|
||||||
CollateralType: "swap",
|
|
||||||
RewardFactor: d("0.0"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
||||||
suite.keeper.SetSwapRewardAccrualTime(suite.ctx, pool, previousAccrualTime)
|
|
||||||
|
|
||||||
newAccrualTime := previousAccrualTime.Add(1 * time.Second) // 1 second is the smallest increment accrual happens over
|
|
||||||
suite.ctx = suite.ctx.WithBlockTime(newAccrualTime)
|
|
||||||
|
|
||||||
period := types.NewMultiRewardPeriod(
|
|
||||||
true,
|
|
||||||
pool,
|
|
||||||
time.Unix(0, 0),
|
|
||||||
distantFuture,
|
|
||||||
cs(c("swap", 1)), // single unit of any denom is the smallest reward amount
|
|
||||||
)
|
|
||||||
|
|
||||||
suite.keeper.AccumulateSwapRewards(suite.ctx, period)
|
|
||||||
|
|
||||||
// check time and factors
|
|
||||||
|
|
||||||
suite.checkStoredTimeEquals(pool, newAccrualTime)
|
|
||||||
|
|
||||||
expectedIndexes := types.RewardIndexes{
|
|
||||||
{
|
|
||||||
CollateralType: "swap",
|
|
||||||
// smallest reward amount over smallest accumulation duration does not go past 10^-18 decimal precision
|
|
||||||
RewardFactor: d("0.000000000000000010"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
suite.checkStoredIndexesEqual(pool, expectedIndexes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AccumulateSwapRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIncreased() {
|
func (suite *AccumulateSwapRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIncreased() {
|
||||||
swapKeeper := &fakeSwapKeeper{i(1e6)}
|
pool := "btc:usdx"
|
||||||
|
|
||||||
|
swapKeeper := newFakeSwapKeeper().addPool(pool, i(1e6))
|
||||||
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper)
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper)
|
||||||
|
|
||||||
pool := "btc/usdx"
|
previousIndexes := types.MultiRewardIndexes{
|
||||||
suite.storeGlobalSwapIndexes(types.MultiRewardIndexes{
|
|
||||||
{
|
{
|
||||||
CollateralType: pool,
|
CollateralType: pool,
|
||||||
RewardIndexes: types.RewardIndexes{
|
RewardIndexes: types.RewardIndexes{
|
||||||
@ -148,7 +101,8 @@ func (suite *AccumulateSwapRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIn
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
suite.storeGlobalSwapIndexes(previousIndexes)
|
||||||
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
suite.keeper.SetSwapRewardAccrualTime(suite.ctx, pool, previousAccrualTime)
|
suite.keeper.SetSwapRewardAccrualTime(suite.ctx, pool, previousAccrualTime)
|
||||||
|
|
||||||
@ -166,26 +120,63 @@ func (suite *AccumulateSwapRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIn
|
|||||||
|
|
||||||
// check time and factors
|
// check time and factors
|
||||||
|
|
||||||
suite.checkStoredTimeEquals(pool, previousAccrualTime)
|
suite.storedTimeEquals(pool, previousAccrualTime)
|
||||||
|
expected, f := previousIndexes.Get(pool)
|
||||||
|
suite.True(f)
|
||||||
|
suite.storedIndexesEqual(pool, expected)
|
||||||
|
}
|
||||||
|
|
||||||
expectedIndexes := types.RewardIndexes{
|
func (suite *AccumulateSwapRewardsTests) TestNoAccumulationWhenSourceSharesAreZero() {
|
||||||
|
pool := "btc:usdx"
|
||||||
|
|
||||||
|
swapKeeper := newFakeSwapKeeper() // no pools, so no source shares
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper)
|
||||||
|
|
||||||
|
previousIndexes := types.MultiRewardIndexes{
|
||||||
{
|
{
|
||||||
CollateralType: "swap",
|
CollateralType: pool,
|
||||||
RewardFactor: d("0.02"),
|
RewardIndexes: types.RewardIndexes{
|
||||||
},
|
{
|
||||||
{
|
CollateralType: "swap",
|
||||||
CollateralType: "ukava",
|
RewardFactor: d("0.02"),
|
||||||
RewardFactor: d("0.04"),
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
suite.checkStoredIndexesEqual(pool, expectedIndexes)
|
suite.storeGlobalSwapIndexes(previousIndexes)
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetSwapRewardAccrualTime(suite.ctx, pool, previousAccrualTime)
|
||||||
|
|
||||||
|
firstAccrualTime := previousAccrualTime.Add(7 * time.Second)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
pool,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
cs(c("swap", 2000), c("ukava", 1000)), // same denoms as in global indexes
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateSwapRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// check time and factors
|
||||||
|
|
||||||
|
suite.storedTimeEquals(pool, firstAccrualTime)
|
||||||
|
expected, f := previousIndexes.Get(pool)
|
||||||
|
suite.True(f)
|
||||||
|
suite.storedIndexesEqual(pool, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AccumulateSwapRewardsTests) TestStateAddedWhenStateDoesNotExist() {
|
func (suite *AccumulateSwapRewardsTests) TestStateAddedWhenStateDoesNotExist() {
|
||||||
swapKeeper := &fakeSwapKeeper{i(1e6)}
|
pool := "btc:usdx"
|
||||||
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper)
|
|
||||||
|
|
||||||
pool := "btc/usdx"
|
swapKeeper := newFakeSwapKeeper().addPool(pool, i(1e6))
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper)
|
||||||
|
|
||||||
period := types.NewMultiRewardPeriod(
|
period := types.NewMultiRewardPeriod(
|
||||||
true,
|
true,
|
||||||
@ -201,8 +192,9 @@ func (suite *AccumulateSwapRewardsTests) TestStateAddedWhenStateDoesNotExist() {
|
|||||||
suite.keeper.AccumulateSwapRewards(suite.ctx, period)
|
suite.keeper.AccumulateSwapRewards(suite.ctx, period)
|
||||||
|
|
||||||
// After the first accumulation only the current block time should be stored.
|
// After the first accumulation only the current block time should be stored.
|
||||||
// This indexes will be zero as no time has passed since the previous block because it didn't exist.
|
// The indexes will be empty as no time has passed since the previous block because it didn't exist.
|
||||||
suite.checkStoredTimeEquals(pool, firstAccrualTime)
|
suite.storedTimeEquals(pool, firstAccrualTime)
|
||||||
|
suite.storedIndexesEqual(pool, nil)
|
||||||
|
|
||||||
secondAccrualTime := firstAccrualTime.Add(10 * time.Second)
|
secondAccrualTime := firstAccrualTime.Add(10 * time.Second)
|
||||||
suite.ctx = suite.ctx.WithBlockTime(secondAccrualTime)
|
suite.ctx = suite.ctx.WithBlockTime(secondAccrualTime)
|
||||||
@ -210,9 +202,8 @@ func (suite *AccumulateSwapRewardsTests) TestStateAddedWhenStateDoesNotExist() {
|
|||||||
suite.keeper.AccumulateSwapRewards(suite.ctx, period)
|
suite.keeper.AccumulateSwapRewards(suite.ctx, period)
|
||||||
|
|
||||||
// After the second accumulation both current block time and indexes should be stored.
|
// After the second accumulation both current block time and indexes should be stored.
|
||||||
suite.checkStoredTimeEquals(pool, secondAccrualTime)
|
suite.storedTimeEquals(pool, secondAccrualTime)
|
||||||
|
suite.storedIndexesEqual(pool, types.RewardIndexes{
|
||||||
expectedIndexes := types.RewardIndexes{
|
|
||||||
{
|
{
|
||||||
CollateralType: "swap",
|
CollateralType: "swap",
|
||||||
RewardFactor: d("0.02"),
|
RewardFactor: d("0.02"),
|
||||||
@ -221,14 +212,14 @@ func (suite *AccumulateSwapRewardsTests) TestStateAddedWhenStateDoesNotExist() {
|
|||||||
CollateralType: "ukava",
|
CollateralType: "ukava",
|
||||||
RewardFactor: d("0.01"),
|
RewardFactor: d("0.01"),
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
suite.checkStoredIndexesEqual(pool, expectedIndexes)
|
|
||||||
}
|
}
|
||||||
func (suite *AccumulateSwapRewardsTests) TestNoPanicWhenStateDoesNotExist() {
|
|
||||||
swapKeeper := &fakeSwapKeeper{i(0)}
|
|
||||||
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper)
|
|
||||||
|
|
||||||
pool := "btc/usdx"
|
func (suite *AccumulateSwapRewardsTests) TestNoPanicWhenStateDoesNotExist() {
|
||||||
|
pool := "btc:usdx"
|
||||||
|
|
||||||
|
swapKeeper := newFakeSwapKeeper()
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper)
|
||||||
|
|
||||||
period := types.NewMultiRewardPeriod(
|
period := types.NewMultiRewardPeriod(
|
||||||
true,
|
true,
|
||||||
@ -248,20 +239,78 @@ func (suite *AccumulateSwapRewardsTests) TestNoPanicWhenStateDoesNotExist() {
|
|||||||
suite.keeper.AccumulateSwapRewards(suite.ctx, period)
|
suite.keeper.AccumulateSwapRewards(suite.ctx, period)
|
||||||
})
|
})
|
||||||
|
|
||||||
suite.checkStoredTimeEquals(pool, accrualTime)
|
suite.storedTimeEquals(pool, accrualTime)
|
||||||
|
suite.storedIndexesEqual(pool, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
type fakeSwapKeeper struct {
|
func (suite *AccumulateSwapRewardsTests) TestNoAccumulationWhenBeforeStartTime() {
|
||||||
poolShares sdk.Int
|
pool := "btc:usdx"
|
||||||
|
|
||||||
|
swapKeeper := newFakeSwapKeeper().addPool(pool, i(1e6))
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper)
|
||||||
|
|
||||||
|
previousIndexes := types.MultiRewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: pool,
|
||||||
|
RewardIndexes: types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: "swap",
|
||||||
|
RewardFactor: d("0.02"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CollateralType: "ukava",
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
suite.storeGlobalSwapIndexes(previousIndexes)
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetSwapRewardAccrualTime(suite.ctx, pool, previousAccrualTime)
|
||||||
|
|
||||||
|
firstAccrualTime := previousAccrualTime.Add(10 * time.Second)
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
pool,
|
||||||
|
firstAccrualTime.Add(time.Nanosecond), // start time after accrual time
|
||||||
|
distantFuture,
|
||||||
|
cs(c("swap", 2000), c("ukava", 1000)),
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateSwapRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// The accrual time should be updated, but the indexes unchanged
|
||||||
|
suite.storedTimeEquals(pool, firstAccrualTime)
|
||||||
|
expectedIndexes, f := previousIndexes.Get(pool)
|
||||||
|
suite.True(f)
|
||||||
|
suite.storedIndexesEqual(pool, expectedIndexes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k fakeSwapKeeper) GetPoolShares(ctx sdk.Context, poolID string) (sdk.Int, bool) {
|
func (suite *AccumulateSwapRewardsTests) TestPanicWhenCurrentTimeLessThanPrevious() {
|
||||||
return k.poolShares, true
|
pool := "btc:usdx"
|
||||||
}
|
|
||||||
func (k fakeSwapKeeper) GetDepositorSharesAmount(ctx sdk.Context, depositor sdk.AccAddress, poolID string) (sdk.Int, bool) {
|
|
||||||
// This is just to implement the swap keeper interface.
|
|
||||||
return sdk.Int{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// note: amino panics when encoding times ≥ the start of year 10000.
|
swapKeeper := newFakeSwapKeeper().addPool(pool, i(1e6))
|
||||||
var distantFuture = time.Date(9000, 1, 1, 0, 0, 0, 0, time.UTC)
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper)
|
||||||
|
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetSwapRewardAccrualTime(suite.ctx, pool, previousAccrualTime)
|
||||||
|
|
||||||
|
firstAccrualTime := time.Time{}
|
||||||
|
|
||||||
|
period := types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
pool,
|
||||||
|
time.Time{}, // start time after accrual time
|
||||||
|
distantFuture,
|
||||||
|
cs(c("swap", 2000), c("ukava", 1000)),
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
suite.Panics(func() {
|
||||||
|
suite.keeper.AccumulateSwapRewards(suite.ctx, period)
|
||||||
|
})
|
||||||
|
}
|
@ -29,7 +29,7 @@ func (suite *InitializeSwapRewardTests) TestClaimAddedWhenClaimDoesNotExistAndNo
|
|||||||
// When a claim doesn't exist, and a user deposits to a non-rewarded pool;
|
// When a claim doesn't exist, and a user deposits to a non-rewarded pool;
|
||||||
// then a claim is added with no rewards and no indexes
|
// then a claim is added with no rewards and no indexes
|
||||||
|
|
||||||
poolID := "base/quote"
|
poolID := "base:quote"
|
||||||
|
|
||||||
// no global indexes stored as this pool is not rewarded
|
// no global indexes stored as this pool is not rewarded
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ func (suite *InitializeSwapRewardTests) TestClaimAddedWhenClaimDoesNotExistAndRe
|
|||||||
// When a claim doesn't exist, and a user deposits to a rewarded pool;
|
// When a claim doesn't exist, and a user deposits to a rewarded pool;
|
||||||
// then a claim is added with no rewards and indexes matching the global indexes
|
// then a claim is added with no rewards and indexes matching the global indexes
|
||||||
|
|
||||||
poolID := "base/quote"
|
poolID := "base:quote"
|
||||||
|
|
||||||
globalIndexes := types.MultiRewardIndexes{
|
globalIndexes := types.MultiRewardIndexes{
|
||||||
{
|
{
|
||||||
@ -92,7 +92,7 @@ func (suite *InitializeSwapRewardTests) TestClaimUpdatedWhenClaimExistsAndNoRewa
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
newPoolID := "btcb/usdx"
|
newPoolID := "btcb:usdx"
|
||||||
|
|
||||||
claim := types.SwapClaim{
|
claim := types.SwapClaim{
|
||||||
BaseMultiClaim: types.BaseMultiClaim{
|
BaseMultiClaim: types.BaseMultiClaim{
|
||||||
@ -141,7 +141,7 @@ func (suite *InitializeSwapRewardTests) TestClaimUpdatedWhenClaimExistsAndReward
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
newPoolID := "btcb/usdx"
|
newPoolID := "btcb:usdx"
|
||||||
newIndexes := types.RewardIndexes{
|
newIndexes := types.RewardIndexes{
|
||||||
{
|
{
|
||||||
CollateralType: "otherrewarddenom",
|
CollateralType: "otherrewarddenom",
|
||||||
|
@ -32,7 +32,7 @@ func (suite *SynchronizeSwapRewardTests) TestClaimUpdatedWhenGlobalIndexesHaveIn
|
|||||||
// The user earns rewards for the time passed, and the claim indexes are updated
|
// The user earns rewards for the time passed, and the claim indexes are updated
|
||||||
|
|
||||||
originalReward := arbitraryCoins()
|
originalReward := arbitraryCoins()
|
||||||
poolID := "base/quote"
|
poolID := "base:quote"
|
||||||
|
|
||||||
claim := types.SwapClaim{
|
claim := types.SwapClaim{
|
||||||
BaseMultiClaim: types.BaseMultiClaim{
|
BaseMultiClaim: types.BaseMultiClaim{
|
||||||
@ -83,7 +83,7 @@ func (suite *SynchronizeSwapRewardTests) TestClaimUpdatedWhenGlobalIndexesHaveIn
|
|||||||
func (suite *SynchronizeSwapRewardTests) TestClaimUnchangedWhenGlobalIndexesUnchanged() {
|
func (suite *SynchronizeSwapRewardTests) TestClaimUnchangedWhenGlobalIndexesUnchanged() {
|
||||||
// It should be safe to call SynchronizeSwapReward multiple times
|
// It should be safe to call SynchronizeSwapReward multiple times
|
||||||
|
|
||||||
poolID := "base/quote"
|
poolID := "base:quote"
|
||||||
unchangingIndexes := types.MultiRewardIndexes{
|
unchangingIndexes := types.MultiRewardIndexes{
|
||||||
{
|
{
|
||||||
CollateralType: poolID,
|
CollateralType: poolID,
|
||||||
@ -211,7 +211,7 @@ func (suite *SynchronizeSwapRewardTests) TestClaimUpdatedWhenNewRewardDenomAdded
|
|||||||
// Then the user earns rewards for the time since the reward was added, and the new indexes are added.
|
// Then the user earns rewards for the time since the reward was added, and the new indexes are added.
|
||||||
|
|
||||||
originalReward := arbitraryCoins()
|
originalReward := arbitraryCoins()
|
||||||
poolID := "base/quote"
|
poolID := "base:quote"
|
||||||
|
|
||||||
claim := types.SwapClaim{
|
claim := types.SwapClaim{
|
||||||
BaseMultiClaim: types.BaseMultiClaim{
|
BaseMultiClaim: types.BaseMultiClaim{
|
||||||
@ -271,7 +271,7 @@ func (suite *SynchronizeSwapRewardTests) TestClaimUpdatedWhenGlobalIndexesIncrea
|
|||||||
// When the claim is synced, but the user has no shares
|
// When the claim is synced, but the user has no shares
|
||||||
// The user earns no rewards for the time passed, but the claim indexes are updated
|
// The user earns no rewards for the time passed, but the claim indexes are updated
|
||||||
|
|
||||||
poolID := "base/quote"
|
poolID := "base:quote"
|
||||||
|
|
||||||
claim := types.SwapClaim{
|
claim := types.SwapClaim{
|
||||||
BaseMultiClaim: types.BaseMultiClaim{
|
BaseMultiClaim: types.BaseMultiClaim{
|
||||||
|
@ -9,42 +9,49 @@ import (
|
|||||||
"github.com/kava-labs/kava/x/incentive/types"
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AccumulateUSDXMintingRewards updates the rewards accumulated for the input reward period
|
// AccumulateUSDXMintingRewards calculates new rewards to distribute this block and updates the global indexes to reflect this.
|
||||||
func (k Keeper) AccumulateUSDXMintingRewards(ctx sdk.Context, rewardPeriod types.RewardPeriod) error {
|
// The provided rewardPeriod must be valid to avoid panics in calculating time durations.
|
||||||
|
func (k Keeper) AccumulateUSDXMintingRewards(ctx sdk.Context, rewardPeriod types.RewardPeriod) {
|
||||||
previousAccrualTime, found := k.GetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType)
|
previousAccrualTime, found := k.GetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType)
|
||||||
if !found {
|
if !found {
|
||||||
k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
previousAccrualTime = ctx.BlockTime()
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
timeElapsed := CalculateTimeElapsed(rewardPeriod.Start, rewardPeriod.End, ctx.BlockTime(), previousAccrualTime)
|
|
||||||
if timeElapsed.IsZero() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if rewardPeriod.RewardsPerSecond.Amount.IsZero() {
|
|
||||||
k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
totalPrincipal := k.cdpKeeper.GetTotalPrincipal(ctx, rewardPeriod.CollateralType, types.PrincipalDenom).ToDec()
|
|
||||||
if totalPrincipal.IsZero() {
|
|
||||||
k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount)
|
|
||||||
cdpFactor, found := k.cdpKeeper.GetInterestFactor(ctx, rewardPeriod.CollateralType)
|
|
||||||
if !found {
|
|
||||||
k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
rewardFactor := newRewards.ToDec().Mul(cdpFactor).Quo(totalPrincipal)
|
|
||||||
|
|
||||||
previousRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, rewardPeriod.CollateralType)
|
factor, found := k.GetUSDXMintingRewardFactor(ctx, rewardPeriod.CollateralType)
|
||||||
if !found {
|
if !found {
|
||||||
previousRewardFactor = sdk.ZeroDec()
|
factor = sdk.ZeroDec()
|
||||||
}
|
}
|
||||||
newRewardFactor := previousRewardFactor.Add(rewardFactor)
|
// wrap in RewardIndexes for compatibility with Accumulator
|
||||||
k.SetUSDXMintingRewardFactor(ctx, rewardPeriod.CollateralType, newRewardFactor)
|
indexes := types.RewardIndexes{}.With(types.USDXMintingRewardDenom, factor)
|
||||||
k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
|
||||||
return nil
|
acc := types.NewAccumulator(previousAccrualTime, indexes)
|
||||||
|
|
||||||
|
totalSource := k.getUSDXTotalSourceShares(ctx, rewardPeriod.CollateralType)
|
||||||
|
|
||||||
|
acc.Accumulate(types.NewMultiRewardPeriodFromRewardPeriod(rewardPeriod), totalSource, ctx.BlockTime())
|
||||||
|
|
||||||
|
k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, acc.PreviousAccumulationTime)
|
||||||
|
|
||||||
|
factor, found = acc.Indexes.Get(types.USDXMintingRewardDenom)
|
||||||
|
if !found {
|
||||||
|
panic("could not find factor that should never be missing when accumulating usdx rewards")
|
||||||
|
}
|
||||||
|
k.SetUSDXMintingRewardFactor(ctx, rewardPeriod.CollateralType, factor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUSDXTotalSourceShares fetches the sum of all source shares for a usdx minting reward.
|
||||||
|
// In the case of usdx minting, this is the total debt from all cdps of a particular type, divided by the cdp interest factor.
|
||||||
|
// This give the "pre interest" value of the total debt.
|
||||||
|
func (k Keeper) getUSDXTotalSourceShares(ctx sdk.Context, collateralType string) sdk.Dec {
|
||||||
|
totalPrincipal := k.cdpKeeper.GetTotalPrincipal(ctx, collateralType, cdptypes.DefaultStableDenom)
|
||||||
|
|
||||||
|
cdpFactor, found := k.cdpKeeper.GetInterestFactor(ctx, collateralType)
|
||||||
|
if !found {
|
||||||
|
// assume nothing has been borrowed so the factor starts at it's default value
|
||||||
|
cdpFactor = sdk.OneDec()
|
||||||
|
}
|
||||||
|
// return debt/factor to get the "pre interest" value of the current total debt
|
||||||
|
return totalPrincipal.ToDec().Quo(cdpFactor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitializeUSDXMintingClaim creates or updates a claim such that no new rewards are accrued, but any existing rewards are not lost.
|
// InitializeUSDXMintingClaim creates or updates a claim such that no new rewards are accrued, but any existing rewards are not lost.
|
||||||
|
234
x/incentive/keeper/rewards_usdx_accum_test.go
Normal file
234
x/incentive/keeper/rewards_usdx_accum_test.go
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccumulateUSDXRewardsTests struct {
|
||||||
|
usdxRewardsUnitTester
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateUSDXRewardsTests) storedTimeEquals(cType string, expected time.Time) {
|
||||||
|
storedTime, found := suite.keeper.GetPreviousUSDXMintingAccrualTime(suite.ctx, cType)
|
||||||
|
suite.True(found)
|
||||||
|
suite.Equal(expected, storedTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateUSDXRewardsTests) storedIndexesEqual(cType string, expected sdk.Dec) {
|
||||||
|
storedIndexes, found := suite.keeper.GetUSDXMintingRewardFactor(suite.ctx, cType)
|
||||||
|
suite.True(found)
|
||||||
|
suite.Equal(expected, storedIndexes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccumulateUSDXRewards(t *testing.T) {
|
||||||
|
suite.Run(t, new(AccumulateUSDXRewardsTests))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateUSDXRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreased() {
|
||||||
|
cType := "bnb-a"
|
||||||
|
|
||||||
|
cdpKeeper := newFakeCDPKeeper().addTotalPrincipal(i(1e6)).addInterestFactor(d("1"))
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil)
|
||||||
|
|
||||||
|
suite.storeGlobalUSDXIndexes(types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: cType,
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, cType, previousAccrualTime)
|
||||||
|
|
||||||
|
newAccrualTime := previousAccrualTime.Add(1 * time.Hour)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(newAccrualTime)
|
||||||
|
|
||||||
|
period := types.NewRewardPeriod(
|
||||||
|
true,
|
||||||
|
cType,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
c("ukava", 1000),
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateUSDXMintingRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// check time and factors
|
||||||
|
|
||||||
|
suite.storedTimeEquals(cType, newAccrualTime)
|
||||||
|
suite.storedIndexesEqual(cType, d("3.64"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateUSDXRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIncreased() {
|
||||||
|
cType := "bnb-a"
|
||||||
|
|
||||||
|
cdpKeeper := newFakeCDPKeeper().addTotalPrincipal(i(1e6)).addInterestFactor(d("1"))
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil)
|
||||||
|
|
||||||
|
previousIndexes := types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: cType,
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
suite.storeGlobalUSDXIndexes(previousIndexes)
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, cType, previousAccrualTime)
|
||||||
|
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(previousAccrualTime)
|
||||||
|
|
||||||
|
period := types.NewRewardPeriod(
|
||||||
|
true,
|
||||||
|
cType,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
c("ukava", 2000),
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateUSDXMintingRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// check time and factors
|
||||||
|
|
||||||
|
suite.storedTimeEquals(cType, previousAccrualTime)
|
||||||
|
expected, f := previousIndexes.Get(cType)
|
||||||
|
suite.True(f)
|
||||||
|
suite.storedIndexesEqual(cType, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateUSDXRewardsTests) TestNoAccumulationWhenSourceSharesAreZero() {
|
||||||
|
cType := "bnb-a"
|
||||||
|
|
||||||
|
cdpKeeper := newFakeCDPKeeper() // zero total borrows
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil)
|
||||||
|
|
||||||
|
previousIndexes := types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: cType,
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
suite.storeGlobalUSDXIndexes(previousIndexes)
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, cType, previousAccrualTime)
|
||||||
|
|
||||||
|
firstAccrualTime := previousAccrualTime.Add(7 * time.Second)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
period := types.NewRewardPeriod(
|
||||||
|
true,
|
||||||
|
cType,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
c("ukava", 1000),
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateUSDXMintingRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// check time and factors
|
||||||
|
|
||||||
|
suite.storedTimeEquals(cType, firstAccrualTime)
|
||||||
|
expected, f := previousIndexes.Get(cType)
|
||||||
|
suite.True(f)
|
||||||
|
suite.storedIndexesEqual(cType, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateUSDXRewardsTests) TestStateAddedWhenStateDoesNotExist() {
|
||||||
|
cType := "bnb-a"
|
||||||
|
|
||||||
|
cdpKeeper := newFakeCDPKeeper().addTotalPrincipal(i(1e6)).addInterestFactor(d("1"))
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil)
|
||||||
|
|
||||||
|
period := types.NewRewardPeriod(
|
||||||
|
true,
|
||||||
|
cType,
|
||||||
|
time.Unix(0, 0), // ensure the test is within start and end times
|
||||||
|
distantFuture,
|
||||||
|
c("ukava", 1000),
|
||||||
|
)
|
||||||
|
|
||||||
|
firstAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateUSDXMintingRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// After the first accumulation the current block time should be stored and the factor will be zero.
|
||||||
|
suite.storedTimeEquals(cType, firstAccrualTime)
|
||||||
|
suite.storedIndexesEqual(cType, sdk.ZeroDec())
|
||||||
|
|
||||||
|
secondAccrualTime := firstAccrualTime.Add(10 * time.Second)
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(secondAccrualTime)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateUSDXMintingRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// After the second accumulation both current block time and indexes should be stored.
|
||||||
|
suite.storedTimeEquals(cType, secondAccrualTime)
|
||||||
|
suite.storedIndexesEqual(cType, d("0.01"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateUSDXRewardsTests) TestNoAccumulationWhenBeforeStartTime() {
|
||||||
|
cType := "bnb-a"
|
||||||
|
|
||||||
|
cdpKeeper := newFakeCDPKeeper().addTotalPrincipal(i(1e6)).addInterestFactor(d("1"))
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil)
|
||||||
|
|
||||||
|
previousIndexes := types.RewardIndexes{
|
||||||
|
{
|
||||||
|
CollateralType: cType,
|
||||||
|
RewardFactor: d("0.04"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
suite.storeGlobalUSDXIndexes(previousIndexes)
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, cType, previousAccrualTime)
|
||||||
|
|
||||||
|
firstAccrualTime := previousAccrualTime.Add(10 * time.Second)
|
||||||
|
|
||||||
|
period := types.NewRewardPeriod(
|
||||||
|
true,
|
||||||
|
cType,
|
||||||
|
firstAccrualTime.Add(time.Nanosecond), // start time after accrual time
|
||||||
|
distantFuture,
|
||||||
|
c("ukava", 1000),
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
suite.keeper.AccumulateUSDXMintingRewards(suite.ctx, period)
|
||||||
|
|
||||||
|
// The accrual time should be updated, but the indexes unchanged
|
||||||
|
suite.storedTimeEquals(cType, firstAccrualTime)
|
||||||
|
expected, f := previousIndexes.Get(cType)
|
||||||
|
suite.True(f)
|
||||||
|
suite.storedIndexesEqual(cType, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccumulateUSDXRewardsTests) TestPanicWhenCurrentTimeLessThanPrevious() {
|
||||||
|
cType := "bnb-a"
|
||||||
|
|
||||||
|
cdpKeeper := newFakeCDPKeeper().addTotalPrincipal(i(1e6)).addInterestFactor(d("1"))
|
||||||
|
suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, cdpKeeper, nil, nil, nil, nil)
|
||||||
|
|
||||||
|
previousAccrualTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, cType, previousAccrualTime)
|
||||||
|
|
||||||
|
firstAccrualTime := time.Time{}
|
||||||
|
|
||||||
|
period := types.NewRewardPeriod(
|
||||||
|
true,
|
||||||
|
cType,
|
||||||
|
time.Time{}, // start time after accrual time
|
||||||
|
distantFuture,
|
||||||
|
c("ukava", 1000),
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.ctx = suite.ctx.WithBlockTime(firstAccrualTime)
|
||||||
|
|
||||||
|
suite.Panics(func() {
|
||||||
|
suite.keeper.AccumulateUSDXMintingRewards(suite.ctx, period)
|
||||||
|
})
|
||||||
|
}
|
@ -117,8 +117,7 @@ func (suite *USDXRewardsTestSuite) TestAccumulateUSDXMintingRewards() {
|
|||||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||||
rewardPeriod, found := suite.keeper.GetUSDXMintingRewardPeriod(suite.ctx, tc.args.ctype)
|
rewardPeriod, found := suite.keeper.GetUSDXMintingRewardPeriod(suite.ctx, tc.args.ctype)
|
||||||
suite.Require().True(found)
|
suite.Require().True(found)
|
||||||
err := suite.keeper.AccumulateUSDXMintingRewards(suite.ctx, rewardPeriod)
|
suite.keeper.AccumulateUSDXMintingRewards(suite.ctx, rewardPeriod)
|
||||||
suite.Require().NoError(err)
|
|
||||||
|
|
||||||
rewardFactor, _ := suite.keeper.GetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype)
|
rewardFactor, _ := suite.keeper.GetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype)
|
||||||
suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor)
|
suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor)
|
||||||
@ -191,8 +190,7 @@ func (suite *USDXRewardsTestSuite) TestSynchronizeUSDXMintingReward() {
|
|||||||
blockCtx := suite.ctx.WithBlockTime(updatedBlockTime)
|
blockCtx := suite.ctx.WithBlockTime(updatedBlockTime)
|
||||||
rewardPeriod, found := suite.keeper.GetUSDXMintingRewardPeriod(blockCtx, tc.args.ctype)
|
rewardPeriod, found := suite.keeper.GetUSDXMintingRewardPeriod(blockCtx, tc.args.ctype)
|
||||||
suite.Require().True(found)
|
suite.Require().True(found)
|
||||||
err := suite.keeper.AccumulateUSDXMintingRewards(blockCtx, rewardPeriod)
|
suite.keeper.AccumulateUSDXMintingRewards(blockCtx, rewardPeriod)
|
||||||
suite.Require().NoError(err)
|
|
||||||
}
|
}
|
||||||
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
||||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||||
@ -278,8 +276,7 @@ func (suite *USDXRewardsTestSuite) TestSimulateUSDXMintingRewardSynchronization(
|
|||||||
blockCtx := suite.ctx.WithBlockTime(updatedBlockTime)
|
blockCtx := suite.ctx.WithBlockTime(updatedBlockTime)
|
||||||
rewardPeriod, found := suite.keeper.GetUSDXMintingRewardPeriod(blockCtx, tc.args.ctype)
|
rewardPeriod, found := suite.keeper.GetUSDXMintingRewardPeriod(blockCtx, tc.args.ctype)
|
||||||
suite.Require().True(found)
|
suite.Require().True(found)
|
||||||
err := suite.keeper.AccumulateUSDXMintingRewards(blockCtx, rewardPeriod)
|
suite.keeper.AccumulateUSDXMintingRewards(blockCtx, rewardPeriod)
|
||||||
suite.Require().NoError(err)
|
|
||||||
}
|
}
|
||||||
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
||||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||||
|
@ -2,17 +2,21 @@ package keeper_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
"github.com/cosmos/cosmos-sdk/store"
|
"github.com/cosmos/cosmos-sdk/store"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/params"
|
"github.com/cosmos/cosmos-sdk/x/params"
|
||||||
|
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
db "github.com/tendermint/tm-db"
|
db "github.com/tendermint/tm-db"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/app"
|
"github.com/kava-labs/kava/app"
|
||||||
|
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||||
|
hardtypes "github.com/kava-labs/kava/x/hard/types"
|
||||||
"github.com/kava-labs/kava/x/incentive/keeper"
|
"github.com/kava-labs/kava/x/incentive/keeper"
|
||||||
"github.com/kava-labs/kava/x/incentive/types"
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
)
|
)
|
||||||
@ -94,6 +98,7 @@ func (suite *unitTester) storeSwapClaim(claim types.SwapClaim) {
|
|||||||
suite.keeper.SetSwapClaim(suite.ctx, claim)
|
suite.keeper.SetSwapClaim(suite.ctx, claim)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fakeParamSubspace is a stub paramSpace to simplify keeper unit test setup.
|
||||||
type fakeParamSubspace struct {
|
type fakeParamSubspace struct {
|
||||||
params types.Params
|
params types.Params
|
||||||
}
|
}
|
||||||
@ -113,6 +118,197 @@ func (subspace *fakeParamSubspace) WithKeyTable(params.KeyTable) params.Subspace
|
|||||||
return params.Subspace{}
|
return params.Subspace{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fakeSwapKeeper is a stub swap keeper.
|
||||||
|
// It can be used to return values to the incentive keeper without having to initialize a full swap keeper.
|
||||||
|
type fakeSwapKeeper struct {
|
||||||
|
poolShares map[string]sdk.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.SwapKeeper = newFakeSwapKeeper()
|
||||||
|
|
||||||
|
func newFakeSwapKeeper() *fakeSwapKeeper {
|
||||||
|
return &fakeSwapKeeper{
|
||||||
|
poolShares: map[string]sdk.Int{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (k *fakeSwapKeeper) addPool(id string, shares sdk.Int) *fakeSwapKeeper {
|
||||||
|
k.poolShares[id] = shares
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
func (k *fakeSwapKeeper) GetPoolShares(_ sdk.Context, poolID string) (sdk.Int, bool) {
|
||||||
|
shares, ok := k.poolShares[poolID]
|
||||||
|
return shares, ok
|
||||||
|
}
|
||||||
|
func (k *fakeSwapKeeper) GetDepositorSharesAmount(_ sdk.Context, depositor sdk.AccAddress, poolID string) (sdk.Int, bool) {
|
||||||
|
// This is just to implement the swap keeper interface.
|
||||||
|
return sdk.Int{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// fakeHardKeeper is a stub hard keeper.
|
||||||
|
// It can be used to return values to the incentive keeper without having to initialize a full hard keeper.
|
||||||
|
type fakeHardKeeper struct {
|
||||||
|
borrows fakeHardState
|
||||||
|
deposits fakeHardState
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeHardState struct {
|
||||||
|
total sdk.Coins
|
||||||
|
interestFactors map[string]sdk.Dec
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFakeHardState() fakeHardState {
|
||||||
|
return fakeHardState{
|
||||||
|
total: nil,
|
||||||
|
interestFactors: map[string]sdk.Dec{}, // initialize map to avoid panics on read
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.HardKeeper = newFakeHardKeeper()
|
||||||
|
|
||||||
|
func newFakeHardKeeper() *fakeHardKeeper {
|
||||||
|
return &fakeHardKeeper{
|
||||||
|
borrows: newFakeHardState(),
|
||||||
|
deposits: newFakeHardState(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *fakeHardKeeper) addTotalBorrow(coin sdk.Coin, factor sdk.Dec) *fakeHardKeeper {
|
||||||
|
k.borrows.total = k.borrows.total.Add(coin)
|
||||||
|
k.borrows.interestFactors[coin.Denom] = factor
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
func (k *fakeHardKeeper) addTotalSupply(coin sdk.Coin, factor sdk.Dec) *fakeHardKeeper {
|
||||||
|
k.deposits.total = k.deposits.total.Add(coin)
|
||||||
|
k.deposits.interestFactors[coin.Denom] = factor
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *fakeHardKeeper) GetBorrowedCoins(_ sdk.Context) (sdk.Coins, bool) {
|
||||||
|
if k.borrows.total == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return k.borrows.total, true
|
||||||
|
}
|
||||||
|
func (k *fakeHardKeeper) GetSuppliedCoins(_ sdk.Context) (sdk.Coins, bool) {
|
||||||
|
if k.deposits.total == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return k.deposits.total, true
|
||||||
|
}
|
||||||
|
func (k *fakeHardKeeper) GetBorrowInterestFactor(_ sdk.Context, denom string) (sdk.Dec, bool) {
|
||||||
|
f, ok := k.borrows.interestFactors[denom]
|
||||||
|
return f, ok
|
||||||
|
}
|
||||||
|
func (k *fakeHardKeeper) GetSupplyInterestFactor(_ sdk.Context, denom string) (sdk.Dec, bool) {
|
||||||
|
f, ok := k.deposits.interestFactors[denom]
|
||||||
|
return f, ok
|
||||||
|
}
|
||||||
|
func (k *fakeHardKeeper) GetBorrow(_ sdk.Context, _ sdk.AccAddress) (hardtypes.Borrow, bool) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
func (k *fakeHardKeeper) GetDeposit(_ sdk.Context, _ sdk.AccAddress) (hardtypes.Deposit, bool) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// fakeStakingKeeper is a stub staking keeper.
|
||||||
|
// It can be used to return values to the incentive keeper without having to initialize a full staking keeper.
|
||||||
|
type fakeStakingKeeper struct {
|
||||||
|
delegations stakingtypes.Delegations
|
||||||
|
validators stakingtypes.Validators
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.StakingKeeper = newFakeStakingKeeper()
|
||||||
|
|
||||||
|
func newFakeStakingKeeper() *fakeStakingKeeper { return &fakeStakingKeeper{} }
|
||||||
|
|
||||||
|
func (k *fakeStakingKeeper) addBondedTokens(amount int64) *fakeStakingKeeper {
|
||||||
|
if len(k.validators) != 0 {
|
||||||
|
panic("cannot set total bonded if keeper already has validators set")
|
||||||
|
}
|
||||||
|
// add a validator with all the tokens
|
||||||
|
k.validators = append(k.validators, stakingtypes.Validator{
|
||||||
|
Status: sdk.Bonded,
|
||||||
|
Tokens: sdk.NewInt(amount),
|
||||||
|
})
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *fakeStakingKeeper) TotalBondedTokens(_ sdk.Context) sdk.Int {
|
||||||
|
total := sdk.ZeroInt()
|
||||||
|
for _, val := range k.validators {
|
||||||
|
if val.GetStatus() == sdk.Bonded {
|
||||||
|
total = total.Add(val.GetBondedTokens())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
func (k *fakeStakingKeeper) GetDelegatorDelegations(_ sdk.Context, delegator sdk.AccAddress, maxRetrieve uint16) []stakingtypes.Delegation {
|
||||||
|
return k.delegations
|
||||||
|
}
|
||||||
|
func (k *fakeStakingKeeper) GetValidator(_ sdk.Context, addr sdk.ValAddress) (stakingtypes.Validator, bool) {
|
||||||
|
for _, val := range k.validators {
|
||||||
|
if val.GetOperator().Equals(addr) {
|
||||||
|
return val, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stakingtypes.Validator{}, false
|
||||||
|
}
|
||||||
|
func (k *fakeStakingKeeper) GetValidatorDelegations(_ sdk.Context, valAddr sdk.ValAddress) []stakingtypes.Delegation {
|
||||||
|
var delegations stakingtypes.Delegations
|
||||||
|
for _, d := range k.delegations {
|
||||||
|
if d.ValidatorAddress.Equals(valAddr) {
|
||||||
|
delegations = append(delegations, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return delegations
|
||||||
|
}
|
||||||
|
|
||||||
|
// fakeCDPKeeper is a stub cdp keeper.
|
||||||
|
// It can be used to return values to the incentive keeper without having to initialize a full cdp keeper.
|
||||||
|
type fakeCDPKeeper struct {
|
||||||
|
interestFactor *sdk.Dec
|
||||||
|
totalPrincipal sdk.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.CdpKeeper = newFakeCDPKeeper()
|
||||||
|
|
||||||
|
func newFakeCDPKeeper() *fakeCDPKeeper {
|
||||||
|
return &fakeCDPKeeper{
|
||||||
|
interestFactor: nil,
|
||||||
|
totalPrincipal: sdk.ZeroInt(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *fakeCDPKeeper) addInterestFactor(f sdk.Dec) *fakeCDPKeeper {
|
||||||
|
k.interestFactor = &f
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
func (k *fakeCDPKeeper) addTotalPrincipal(p sdk.Int) *fakeCDPKeeper {
|
||||||
|
k.totalPrincipal = p
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *fakeCDPKeeper) GetInterestFactor(_ sdk.Context, collateralType string) (sdk.Dec, bool) {
|
||||||
|
if k.interestFactor != nil {
|
||||||
|
return *k.interestFactor, true
|
||||||
|
}
|
||||||
|
return sdk.Dec{}, false
|
||||||
|
}
|
||||||
|
func (k *fakeCDPKeeper) GetTotalPrincipal(_ sdk.Context, collateralType string, principalDenom string) sdk.Int {
|
||||||
|
return k.totalPrincipal
|
||||||
|
}
|
||||||
|
func (k *fakeCDPKeeper) GetCdpByOwnerAndCollateralType(_ sdk.Context, owner sdk.AccAddress, collateralType string) (cdptypes.CDP, bool) {
|
||||||
|
return cdptypes.CDP{}, false
|
||||||
|
}
|
||||||
|
func (k *fakeCDPKeeper) GetCollateral(_ sdk.Context, collateralType string) (cdptypes.CollateralParam, bool) {
|
||||||
|
return cdptypes.CollateralParam{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assorted Testing Data
|
||||||
|
|
||||||
|
// note: amino panics when encoding times ≥ the start of year 10000.
|
||||||
|
var distantFuture = time.Date(9000, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
func arbitraryCoin() sdk.Coin {
|
func arbitraryCoin() sdk.Coin {
|
||||||
return c("hard", 1e9)
|
return c("hard", 1e9)
|
||||||
}
|
}
|
||||||
|
@ -12,15 +12,15 @@ Rewards target various user activity. For example, usdx borrowed from bnb CDPs,
|
|||||||
|
|
||||||
Each second, the rewards accumulate at a rate set in the params, eg 100 ukava per second. These are then distributed to all users ratably based on their percentage involvement in the rewarded activity. For example if a user holds 1% of all funds deposited to the kava/usdx swap pool. They will receive 1% of the total rewards each second.
|
Each second, the rewards accumulate at a rate set in the params, eg 100 ukava per second. These are then distributed to all users ratably based on their percentage involvement in the rewarded activity. For example if a user holds 1% of all funds deposited to the kava/usdx swap pool. They will receive 1% of the total rewards each second.
|
||||||
|
|
||||||
The number tracking a user's involvement is referred to as "reward source" in the code. And the total across all users the "reward source total".
|
The quantity tracking a user's involvement is referred to as "source shares" in the code. And the total across all users the "total source shares". The quotient then gives their percentage involvement, eg if a user borrowed 10,000 usdx, and there is 100,000 usdx borrowed by all users, then they will get 10% of rewards.
|
||||||
|
|
||||||
## Efficiency
|
## Efficiency
|
||||||
|
|
||||||
Paying out rewards to every user every block would be slow and lead to long block times. Instead rewards are calculated much less frequently.
|
Paying out rewards to every user every block would be slow and lead to long block times. Instead rewards are calculated much less frequently.
|
||||||
|
|
||||||
Every block a global tracker adds up total rewards paid out per unit of user involvement. For example, per unit of xrpb supplied to hard, or per share in a kava/usdx swap pool. A user's specific reward can then be calculated as needed based on their current deposit/shares/borrow.
|
Every block a global tracker adds up total rewards paid out per unit of user involvement. For example, per unit of xrpb supplied to hard, or per share in a kava/usdx swap pool. A user's specific reward can then be calculated as needed based on their current source shares.
|
||||||
|
|
||||||
User's rewards must be updated whenever their reward source changes. This happens through hooks into other modules that run before deposits/borrows/supplies etc.
|
Users' rewards must be updated whenever their source shares change. This happens through hooks into other modules that run before deposits/borrows/supplies etc.
|
||||||
|
|
||||||
## HARD Token distribution
|
## HARD Token distribution
|
||||||
|
|
||||||
@ -39,3 +39,7 @@ The exact multipliers will be voted by governance and can be changed via a gover
|
|||||||
## USDX Minting Rewards
|
## USDX Minting Rewards
|
||||||
|
|
||||||
The incentive module is responsible for distribution of KAVA tokens to users who mint USDX. When governance adds a collateral type to be eligible for rewards, they set the rate (coins/second) at which rewards are given to users, the length of each reward period, the length of each claim period, and the amount of time reward coins must vest before users who claim them can transfer them. For the duration of a reward period, any user that has minted USDX using an eligible collateral type will ratably accumulate rewards in a `USDXMintingClaim` object. For example, if a user has minted 10% of all USDX for the duration of the reward period, they will earn 10% of all rewards for that period. When the reward period ends, the claim period begins immediately, at which point users can submit a message to claim their rewards. Rewards are time-locked, meaning that when a user claims rewards they will receive them as a vesting balance on their account. Vesting balances can be used to stake coins, but cannot be transferred until the vesting period ends. In addition to vesting, rewards can have multipliers that vary the number of tokens received. For example, a reward with a vesting period of 1 month may have a multiplier of 0.25, meaning that the user will receive 25% of the reward balance if they choose that vesting schedule.
|
The incentive module is responsible for distribution of KAVA tokens to users who mint USDX. When governance adds a collateral type to be eligible for rewards, they set the rate (coins/second) at which rewards are given to users, the length of each reward period, the length of each claim period, and the amount of time reward coins must vest before users who claim them can transfer them. For the duration of a reward period, any user that has minted USDX using an eligible collateral type will ratably accumulate rewards in a `USDXMintingClaim` object. For example, if a user has minted 10% of all USDX for the duration of the reward period, they will earn 10% of all rewards for that period. When the reward period ends, the claim period begins immediately, at which point users can submit a message to claim their rewards. Rewards are time-locked, meaning that when a user claims rewards they will receive them as a vesting balance on their account. Vesting balances can be used to stake coins, but cannot be transferred until the vesting period ends. In addition to vesting, rewards can have multipliers that vary the number of tokens received. For example, a reward with a vesting period of 1 month may have a multiplier of 0.25, meaning that the user will receive 25% of the reward balance if they choose that vesting schedule.
|
||||||
|
|
||||||
|
## SWP Token Distribution
|
||||||
|
|
||||||
|
TODO
|
@ -22,4 +22,4 @@ parent:
|
|||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
This module uses hooks to update user rewards. Currently, `incentive` implements hooks from the `cdp`, `hard`, and `staking` (comsos-sdk) modules. All rewards are paid out from the `kavadist` module account.
|
This module uses hooks to update user rewards. Currently, `incentive` implements hooks from the `cdp`, `hard`, `swap`, and `staking` (comsos-sdk) modules. All rewards are paid out from the `kavadist` module account.
|
||||||
|
@ -46,6 +46,8 @@ func (builder IncentiveGenesisBuilder) WithGenesisTime(time time.Time) Incentive
|
|||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithInitializedBorrowRewardPeriod sets the genesis time as the previous accumulation time for the specified period.
|
||||||
|
// This can be helpful in tests. With no prev time set, the first block accrues no rewards as it just sets the prev time to the current.
|
||||||
func (builder IncentiveGenesisBuilder) WithInitializedBorrowRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder {
|
func (builder IncentiveGenesisBuilder) WithInitializedBorrowRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder {
|
||||||
builder.Params.HardBorrowRewardPeriods = append(builder.Params.HardBorrowRewardPeriods, period)
|
builder.Params.HardBorrowRewardPeriods = append(builder.Params.HardBorrowRewardPeriods, period)
|
||||||
|
|
||||||
@ -55,6 +57,7 @@ func (builder IncentiveGenesisBuilder) WithInitializedBorrowRewardPeriod(period
|
|||||||
accumulationTimeForPeriod,
|
accumulationTimeForPeriod,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO remove to better reflect real states
|
||||||
builder.HardBorrowRewardState.MultiRewardIndexes = builder.HardBorrowRewardState.MultiRewardIndexes.With(
|
builder.HardBorrowRewardState.MultiRewardIndexes = builder.HardBorrowRewardState.MultiRewardIndexes.With(
|
||||||
period.CollateralType,
|
period.CollateralType,
|
||||||
newZeroRewardIndexesFromCoins(period.RewardsPerSecond...),
|
newZeroRewardIndexesFromCoins(period.RewardsPerSecond...),
|
||||||
@ -67,6 +70,8 @@ func (builder IncentiveGenesisBuilder) WithSimpleBorrowRewardPeriod(ctype string
|
|||||||
return builder.WithInitializedBorrowRewardPeriod(builder.simpleRewardPeriod(ctype, rewardsPerSecond))
|
return builder.WithInitializedBorrowRewardPeriod(builder.simpleRewardPeriod(ctype, rewardsPerSecond))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithInitializedSupplyRewardPeriod sets the genesis time as the previous accumulation time for the specified period.
|
||||||
|
// This can be helpful in tests. With no prev time set, the first block accrues no rewards as it just sets the prev time to the current.
|
||||||
func (builder IncentiveGenesisBuilder) WithInitializedSupplyRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder {
|
func (builder IncentiveGenesisBuilder) WithInitializedSupplyRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder {
|
||||||
builder.Params.HardSupplyRewardPeriods = append(builder.Params.HardSupplyRewardPeriods, period)
|
builder.Params.HardSupplyRewardPeriods = append(builder.Params.HardSupplyRewardPeriods, period)
|
||||||
|
|
||||||
@ -76,6 +81,7 @@ func (builder IncentiveGenesisBuilder) WithInitializedSupplyRewardPeriod(period
|
|||||||
accumulationTimeForPeriod,
|
accumulationTimeForPeriod,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO remove to better reflect real states
|
||||||
builder.HardSupplyRewardState.MultiRewardIndexes = builder.HardSupplyRewardState.MultiRewardIndexes.With(
|
builder.HardSupplyRewardState.MultiRewardIndexes = builder.HardSupplyRewardState.MultiRewardIndexes.With(
|
||||||
period.CollateralType,
|
period.CollateralType,
|
||||||
newZeroRewardIndexesFromCoins(period.RewardsPerSecond...),
|
newZeroRewardIndexesFromCoins(period.RewardsPerSecond...),
|
||||||
@ -88,6 +94,8 @@ func (builder IncentiveGenesisBuilder) WithSimpleSupplyRewardPeriod(ctype string
|
|||||||
return builder.WithInitializedSupplyRewardPeriod(builder.simpleRewardPeriod(ctype, rewardsPerSecond))
|
return builder.WithInitializedSupplyRewardPeriod(builder.simpleRewardPeriod(ctype, rewardsPerSecond))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithInitializedDelegatorRewardPeriod sets the genesis time as the previous accumulation time for the specified period.
|
||||||
|
// This can be helpful in tests. With no prev time set, the first block accrues no rewards as it just sets the prev time to the current.
|
||||||
func (builder IncentiveGenesisBuilder) WithInitializedDelegatorRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder {
|
func (builder IncentiveGenesisBuilder) WithInitializedDelegatorRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder {
|
||||||
builder.Params.DelegatorRewardPeriods = append(builder.Params.DelegatorRewardPeriods, period)
|
builder.Params.DelegatorRewardPeriods = append(builder.Params.DelegatorRewardPeriods, period)
|
||||||
|
|
||||||
@ -97,6 +105,7 @@ func (builder IncentiveGenesisBuilder) WithInitializedDelegatorRewardPeriod(peri
|
|||||||
accumulationTimeForPeriod,
|
accumulationTimeForPeriod,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO remove to better reflect real states
|
||||||
builder.DelegatorRewardState.MultiRewardIndexes = builder.DelegatorRewardState.MultiRewardIndexes.With(
|
builder.DelegatorRewardState.MultiRewardIndexes = builder.DelegatorRewardState.MultiRewardIndexes.With(
|
||||||
period.CollateralType,
|
period.CollateralType,
|
||||||
newZeroRewardIndexesFromCoins(period.RewardsPerSecond...),
|
newZeroRewardIndexesFromCoins(period.RewardsPerSecond...),
|
||||||
@ -109,6 +118,8 @@ func (builder IncentiveGenesisBuilder) WithSimpleDelegatorRewardPeriod(ctype str
|
|||||||
return builder.WithInitializedDelegatorRewardPeriod(builder.simpleRewardPeriod(ctype, rewardsPerSecond))
|
return builder.WithInitializedDelegatorRewardPeriod(builder.simpleRewardPeriod(ctype, rewardsPerSecond))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithInitializedSwapRewardPeriod sets the genesis time as the previous accumulation time for the specified period.
|
||||||
|
// This can be helpful in tests. With no prev time set, the first block accrues no rewards as it just sets the prev time to the current.
|
||||||
func (builder IncentiveGenesisBuilder) WithInitializedSwapRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder {
|
func (builder IncentiveGenesisBuilder) WithInitializedSwapRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder {
|
||||||
builder.Params.SwapRewardPeriods = append(builder.Params.SwapRewardPeriods, period)
|
builder.Params.SwapRewardPeriods = append(builder.Params.SwapRewardPeriods, period)
|
||||||
|
|
||||||
@ -118,11 +129,6 @@ func (builder IncentiveGenesisBuilder) WithInitializedSwapRewardPeriod(period ty
|
|||||||
accumulationTimeForPeriod,
|
accumulationTimeForPeriod,
|
||||||
)
|
)
|
||||||
|
|
||||||
builder.SwapRewardState.MultiRewardIndexes = builder.SwapRewardState.MultiRewardIndexes.With(
|
|
||||||
period.CollateralType,
|
|
||||||
newZeroRewardIndexesFromCoins(period.RewardsPerSecond...),
|
|
||||||
)
|
|
||||||
|
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,6 +136,8 @@ func (builder IncentiveGenesisBuilder) WithSimpleSwapRewardPeriod(poolID string,
|
|||||||
return builder.WithInitializedSwapRewardPeriod(builder.simpleRewardPeriod(poolID, rewardsPerSecond))
|
return builder.WithInitializedSwapRewardPeriod(builder.simpleRewardPeriod(poolID, rewardsPerSecond))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithInitializedUSDXRewardPeriod sets the genesis time as the previous accumulation time for the specified period.
|
||||||
|
// This can be helpful in tests. With no prev time set, the first block accrues no rewards as it just sets the prev time to the current.
|
||||||
func (builder IncentiveGenesisBuilder) WithInitializedUSDXRewardPeriod(period types.RewardPeriod) IncentiveGenesisBuilder {
|
func (builder IncentiveGenesisBuilder) WithInitializedUSDXRewardPeriod(period types.RewardPeriod) IncentiveGenesisBuilder {
|
||||||
builder.Params.USDXMintingRewardPeriods = append(builder.Params.USDXMintingRewardPeriods, period)
|
builder.Params.USDXMintingRewardPeriods = append(builder.Params.USDXMintingRewardPeriods, period)
|
||||||
|
|
||||||
@ -139,6 +147,7 @@ func (builder IncentiveGenesisBuilder) WithInitializedUSDXRewardPeriod(period ty
|
|||||||
accumulationTimeForPeriod,
|
accumulationTimeForPeriod,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO remove to better reflect real states
|
||||||
builder.USDXRewardState.MultiRewardIndexes = builder.USDXRewardState.MultiRewardIndexes.With(
|
builder.USDXRewardState.MultiRewardIndexes = builder.USDXRewardState.MultiRewardIndexes.With(
|
||||||
period.CollateralType,
|
period.CollateralType,
|
||||||
newZeroRewardIndexesFromCoins(period.RewardsPerSecond),
|
newZeroRewardIndexesFromCoins(period.RewardsPerSecond),
|
||||||
|
@ -29,10 +29,12 @@ func NewAccumulator(previousAccrual time.Time, indexes RewardIndexes) *Accumulat
|
|||||||
// Rewards are not accrued for times outside of the start and end times of a reward period.
|
// Rewards are not accrued for times outside of the start and end times of a reward period.
|
||||||
// If a period ends before currentTime, the PreviousAccrualTime is shortened to the end time. This allows accumulate to be called sequentially on consecutive reward periods.
|
// If a period ends before currentTime, the PreviousAccrualTime is shortened to the end time. This allows accumulate to be called sequentially on consecutive reward periods.
|
||||||
//
|
//
|
||||||
// rewardSourceTotal is the total of all user reward sources. For example: total shares in a swap pool, total btcb supplied to hard, or total usdx borrowed from all bnb CDPs.
|
// totalSourceShares is the sum of all users' source shares. For example:total btcb supplied to hard, total usdx borrowed from all bnb CDPs, or total shares in a swap pool.
|
||||||
func (acc *Accumulator) Accumulate(period MultiRewardPeriod, rewardSourceTotal sdk.Dec, currentTime time.Time) {
|
func (acc *Accumulator) Accumulate(period MultiRewardPeriod, totalSourceShares sdk.Dec, currentTime time.Time) {
|
||||||
|
|
||||||
accumulationDuration := acc.getTimeElapsedWithinLimits(acc.PreviousAccumulationTime, currentTime, period.Start, period.End)
|
accumulationDuration := acc.getTimeElapsedWithinLimits(acc.PreviousAccumulationTime, currentTime, period.Start, period.End)
|
||||||
indexesIncrement := acc.calculateNewRewards(period.RewardsPerSecond, rewardSourceTotal, accumulationDuration)
|
|
||||||
|
indexesIncrement := acc.calculateNewRewards(period.RewardsPerSecond, totalSourceShares, accumulationDuration)
|
||||||
|
|
||||||
acc.Indexes = acc.Indexes.Add(indexesIncrement)
|
acc.Indexes = acc.Indexes.Add(indexesIncrement)
|
||||||
acc.PreviousAccumulationTime = minTime(period.End, currentTime)
|
acc.PreviousAccumulationTime = minTime(period.End, currentTime)
|
||||||
@ -40,7 +42,7 @@ func (acc *Accumulator) Accumulate(period MultiRewardPeriod, rewardSourceTotal s
|
|||||||
|
|
||||||
// getTimeElapsedWithinLimits returns the duration between start and end times, capped by min and max times.
|
// getTimeElapsedWithinLimits returns the duration between start and end times, capped by min and max times.
|
||||||
// If the start and end range is outside the min to max time range then zero duration is returned.
|
// If the start and end range is outside the min to max time range then zero duration is returned.
|
||||||
func (acc *Accumulator) getTimeElapsedWithinLimits(start, end, limitMin, limitMax time.Time) time.Duration {
|
func (*Accumulator) getTimeElapsedWithinLimits(start, end, limitMin, limitMax time.Time) time.Duration {
|
||||||
if start.After(end) {
|
if start.After(end) {
|
||||||
panic(fmt.Sprintf("start time (%s) cannot be after end time (%s)", start, end))
|
panic(fmt.Sprintf("start time (%s) cannot be after end time (%s)", start, end))
|
||||||
}
|
}
|
||||||
@ -54,19 +56,25 @@ func (acc *Accumulator) getTimeElapsedWithinLimits(start, end, limitMin, limitMa
|
|||||||
return minTime(end, limitMax).Sub(maxTime(start, limitMin))
|
return minTime(end, limitMax).Sub(maxTime(start, limitMin))
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculateNewRewards calculates the amount to increase the global reward indexes for a given reward rate, duration, and source total.
|
// calculateNewRewards calculates the amount to increase the global reward indexes by, for a given reward rate, duration, and number of source shares.
|
||||||
// The total rewards to distribute in this block are given by reward rate * duration. This value divided by the source total to give
|
// The total rewards to distribute in this block are given by reward rate * duration. This value divided by the sum of all source shares to give
|
||||||
// total rewards per unit of source, which is what the indexes store.
|
// total rewards per source share, which is what the indexes store.
|
||||||
// Note, duration is rounded to the nearest second to keep rewards calculation the same as in kava-7.
|
// Note, duration is rounded to the nearest second to keep rewards calculation consistent with kava-7.
|
||||||
func (acc *Accumulator) calculateNewRewards(rewardsPerSecond sdk.Coins, rewardSourceTotal sdk.Dec, duration time.Duration) RewardIndexes {
|
func (*Accumulator) calculateNewRewards(rewardsPerSecond sdk.Coins, totalSourceShares sdk.Dec, duration time.Duration) RewardIndexes {
|
||||||
if rewardSourceTotal.IsZero() {
|
if totalSourceShares.LTE(sdk.ZeroDec()) {
|
||||||
// When the source total is zero, there is no users with deposits/borrows/delegations to pay out the current block's rewards to.
|
// When there is zero source shares, there is no users with deposits/borrows/delegations to pay out the current block's rewards to.
|
||||||
// So drop the rewards and pay out nothing.
|
// So drop the rewards and pay out nothing.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
durationSeconds := int64(math.RoundToEven(duration.Seconds()))
|
durationSeconds := int64(math.RoundToEven(duration.Seconds()))
|
||||||
|
if durationSeconds <= 0 {
|
||||||
|
// If the duration is zero, there will be no increment.
|
||||||
|
// So return an empty increment instead of one full of zeros.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
increment := newRewardIndexesFromCoins(rewardsPerSecond)
|
increment := newRewardIndexesFromCoins(rewardsPerSecond)
|
||||||
return increment.Mul(sdk.NewDec(durationSeconds)).Quo(rewardSourceTotal)
|
increment = increment.Mul(sdk.NewDec(durationSeconds)).Quo(totalSourceShares)
|
||||||
|
return increment
|
||||||
}
|
}
|
||||||
|
|
||||||
// minTime returns the earliest of two times.
|
// minTime returns the earliest of two times.
|
||||||
|
@ -104,7 +104,7 @@ func TestAccumulator(t *testing.T) {
|
|||||||
type args struct {
|
type args struct {
|
||||||
rewardsPerSecond sdk.Coins
|
rewardsPerSecond sdk.Coins
|
||||||
duration time.Duration
|
duration time.Duration
|
||||||
rewardSourceTotal sdk.Dec
|
totalSourceShares sdk.Dec
|
||||||
}
|
}
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
name string
|
name string
|
||||||
@ -116,7 +116,7 @@ func TestAccumulator(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
rewardsPerSecond: cs(c("hard", 1000), c("swap", 100)),
|
rewardsPerSecond: cs(c("hard", 1000), c("swap", 100)),
|
||||||
duration: 10 * time.Second,
|
duration: 10 * time.Second,
|
||||||
rewardSourceTotal: d("1000"),
|
totalSourceShares: d("1000"),
|
||||||
},
|
},
|
||||||
expected: RewardIndexes{
|
expected: RewardIndexes{
|
||||||
{CollateralType: "hard", RewardFactor: d("10")},
|
{CollateralType: "hard", RewardFactor: d("10")},
|
||||||
@ -128,27 +128,39 @@ func TestAccumulator(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
rewardsPerSecond: cs(c("hard", 1000)),
|
rewardsPerSecond: cs(c("hard", 1000)),
|
||||||
duration: 10*time.Second + 500*time.Millisecond,
|
duration: 10*time.Second + 500*time.Millisecond,
|
||||||
rewardSourceTotal: d("1000"),
|
totalSourceShares: d("1000"),
|
||||||
},
|
},
|
||||||
expected: RewardIndexes{
|
expected: RewardIndexes{
|
||||||
{CollateralType: "hard", RewardFactor: d("10")},
|
{CollateralType: "hard", RewardFactor: d("10")},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when duration is zero the rewards are zero",
|
name: "reward indexes have enough precision for extreme params",
|
||||||
|
args: args{
|
||||||
|
rewardsPerSecond: cs(c("anydenom", 1)), // minimum possible rewards
|
||||||
|
duration: 1 * time.Second, // minimum possible duration (beyond zero as it's rounded)
|
||||||
|
totalSourceShares: d("100000000000000000"), // approximate shares in a $1B pool of 10^8 precision assets
|
||||||
|
},
|
||||||
|
expected: RewardIndexes{
|
||||||
|
// smallest reward amount over smallest accumulation duration does not go past 10^-18 decimal precision
|
||||||
|
{CollateralType: "anydenom", RewardFactor: d("0.000000000000000010")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when duration is zero there is no rewards",
|
||||||
args: args{
|
args: args{
|
||||||
rewardsPerSecond: cs(c("hard", 1000)),
|
rewardsPerSecond: cs(c("hard", 1000)),
|
||||||
duration: 0,
|
duration: 0,
|
||||||
rewardSourceTotal: d("1000"),
|
totalSourceShares: d("1000"),
|
||||||
},
|
},
|
||||||
expected: RewardIndexes{{CollateralType: "hard", RewardFactor: d("0")}}, // TODO should this be nil?
|
expected: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when rewards per second are nil there is no rewards",
|
name: "when rewards per second are nil there is no rewards",
|
||||||
args: args{
|
args: args{
|
||||||
rewardsPerSecond: cs(),
|
rewardsPerSecond: cs(),
|
||||||
duration: 10 * time.Second,
|
duration: 10 * time.Second,
|
||||||
rewardSourceTotal: d("1000"),
|
totalSourceShares: d("1000"),
|
||||||
},
|
},
|
||||||
expected: nil,
|
expected: nil,
|
||||||
},
|
},
|
||||||
@ -157,7 +169,7 @@ func TestAccumulator(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
rewardsPerSecond: cs(c("hard", 1000)),
|
rewardsPerSecond: cs(c("hard", 1000)),
|
||||||
duration: 10 * time.Second,
|
duration: 10 * time.Second,
|
||||||
rewardSourceTotal: d("0"),
|
totalSourceShares: d("0"),
|
||||||
},
|
},
|
||||||
expected: nil,
|
expected: nil,
|
||||||
},
|
},
|
||||||
@ -166,7 +178,7 @@ func TestAccumulator(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
rewardsPerSecond: cs(),
|
rewardsPerSecond: cs(),
|
||||||
duration: 0,
|
duration: 0,
|
||||||
rewardSourceTotal: d("0"),
|
totalSourceShares: d("0"),
|
||||||
},
|
},
|
||||||
expected: nil,
|
expected: nil,
|
||||||
},
|
},
|
||||||
@ -175,7 +187,7 @@ func TestAccumulator(t *testing.T) {
|
|||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
acc := &Accumulator{}
|
acc := &Accumulator{}
|
||||||
indexes := acc.calculateNewRewards(tc.args.rewardsPerSecond, tc.args.rewardSourceTotal, tc.args.duration)
|
indexes := acc.calculateNewRewards(tc.args.rewardsPerSecond, tc.args.totalSourceShares, tc.args.duration)
|
||||||
|
|
||||||
require.Equal(t, tc.expected, indexes)
|
require.Equal(t, tc.expected, indexes)
|
||||||
})
|
})
|
||||||
@ -185,7 +197,7 @@ func TestAccumulator(t *testing.T) {
|
|||||||
type args struct {
|
type args struct {
|
||||||
accumulator Accumulator
|
accumulator Accumulator
|
||||||
period MultiRewardPeriod
|
period MultiRewardPeriod
|
||||||
rewardSourceTotal sdk.Dec
|
totalSourceShares sdk.Dec
|
||||||
currentTime time.Time
|
currentTime time.Time
|
||||||
}
|
}
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
@ -198,34 +210,40 @@ func TestAccumulator(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
accumulator: Accumulator{
|
accumulator: Accumulator{
|
||||||
PreviousAccumulationTime: time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC),
|
PreviousAccumulationTime: time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
Indexes: RewardIndexes{{CollateralType: "hard", RewardFactor: d("0.1")}},
|
Indexes: RewardIndexes{
|
||||||
|
{CollateralType: "hard", RewardFactor: d("0.1")},
|
||||||
|
{CollateralType: "swap", RewardFactor: d("0.2")},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
period: MultiRewardPeriod{
|
period: MultiRewardPeriod{
|
||||||
Start: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC),
|
Start: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
End: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
End: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
RewardsPerSecond: cs(c("hard", 1000)),
|
RewardsPerSecond: cs(c("hard", 1000)),
|
||||||
},
|
},
|
||||||
rewardSourceTotal: d("1000"),
|
totalSourceShares: d("1000"),
|
||||||
currentTime: time.Date(1998, 1, 1, 0, 0, 5, 0, time.UTC),
|
currentTime: time.Date(1998, 1, 1, 0, 0, 5, 0, time.UTC),
|
||||||
},
|
},
|
||||||
expected: Accumulator{
|
expected: Accumulator{
|
||||||
PreviousAccumulationTime: time.Date(1998, 1, 1, 0, 0, 5, 0, time.UTC),
|
PreviousAccumulationTime: time.Date(1998, 1, 1, 0, 0, 5, 0, time.UTC),
|
||||||
Indexes: RewardIndexes{{CollateralType: "hard", RewardFactor: d("5.1")}},
|
Indexes: RewardIndexes{
|
||||||
|
{CollateralType: "hard", RewardFactor: d("5.1")},
|
||||||
|
{CollateralType: "swap", RewardFactor: d("0.2")},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "nil reward indexes are treated as empty",
|
name: "empty reward indexes are added to correctly",
|
||||||
args: args{
|
args: args{
|
||||||
accumulator: Accumulator{
|
accumulator: Accumulator{
|
||||||
PreviousAccumulationTime: time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC),
|
PreviousAccumulationTime: time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
Indexes: nil,
|
Indexes: RewardIndexes{},
|
||||||
},
|
},
|
||||||
period: MultiRewardPeriod{
|
period: MultiRewardPeriod{
|
||||||
Start: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC),
|
Start: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
End: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
End: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
RewardsPerSecond: cs(c("hard", 1000)),
|
RewardsPerSecond: cs(c("hard", 1000)),
|
||||||
},
|
},
|
||||||
rewardSourceTotal: d("1000"),
|
totalSourceShares: d("1000"),
|
||||||
currentTime: time.Date(1998, 1, 1, 0, 0, 5, 0, time.UTC),
|
currentTime: time.Date(1998, 1, 1, 0, 0, 5, 0, time.UTC),
|
||||||
},
|
},
|
||||||
expected: Accumulator{
|
expected: Accumulator{
|
||||||
@ -233,6 +251,26 @@ func TestAccumulator(t *testing.T) {
|
|||||||
Indexes: RewardIndexes{{CollateralType: "hard", RewardFactor: d("5.0")}},
|
Indexes: RewardIndexes{{CollateralType: "hard", RewardFactor: d("5.0")}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "empty reward indexes are unchanged when there's no rewards",
|
||||||
|
args: args{
|
||||||
|
accumulator: Accumulator{
|
||||||
|
PreviousAccumulationTime: time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
Indexes: RewardIndexes{},
|
||||||
|
},
|
||||||
|
period: MultiRewardPeriod{
|
||||||
|
Start: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
End: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
RewardsPerSecond: cs(),
|
||||||
|
},
|
||||||
|
totalSourceShares: d("1000"),
|
||||||
|
currentTime: time.Date(1998, 1, 1, 0, 0, 5, 0, time.UTC),
|
||||||
|
},
|
||||||
|
expected: Accumulator{
|
||||||
|
PreviousAccumulationTime: time.Date(1998, 1, 1, 0, 0, 5, 0, time.UTC),
|
||||||
|
Indexes: RewardIndexes{},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "when a period is enclosed within block the accumulation time is set to the period end time",
|
name: "when a period is enclosed within block the accumulation time is set to the period end time",
|
||||||
args: args{
|
args: args{
|
||||||
@ -245,7 +283,7 @@ func TestAccumulator(t *testing.T) {
|
|||||||
End: time.Date(1998, 1, 1, 0, 0, 7, 0, time.UTC),
|
End: time.Date(1998, 1, 1, 0, 0, 7, 0, time.UTC),
|
||||||
RewardsPerSecond: cs(c("hard", 1000)),
|
RewardsPerSecond: cs(c("hard", 1000)),
|
||||||
},
|
},
|
||||||
rewardSourceTotal: d("1000"),
|
totalSourceShares: d("1000"),
|
||||||
currentTime: time.Date(1998, 1, 1, 0, 0, 10, 0, time.UTC),
|
currentTime: time.Date(1998, 1, 1, 0, 0, 10, 0, time.UTC),
|
||||||
},
|
},
|
||||||
expected: Accumulator{
|
expected: Accumulator{
|
||||||
@ -253,11 +291,33 @@ func TestAccumulator(t *testing.T) {
|
|||||||
Indexes: RewardIndexes{{CollateralType: "hard", RewardFactor: d("2.1")}},
|
Indexes: RewardIndexes{{CollateralType: "hard", RewardFactor: d("2.1")}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "accumulation duration is capped at param start when previous stored time is in the distant past",
|
||||||
|
// This could happend in the default time value time.Time{} was accidentally stored, or if a reward period was
|
||||||
|
// removed from the params, then added back a long time later.
|
||||||
|
args: args{
|
||||||
|
accumulator: Accumulator{
|
||||||
|
PreviousAccumulationTime: time.Time{},
|
||||||
|
Indexes: RewardIndexes{{CollateralType: "hard", RewardFactor: d("0.1")}},
|
||||||
|
},
|
||||||
|
period: MultiRewardPeriod{
|
||||||
|
Start: time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
End: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
RewardsPerSecond: cs(c("hard", 1000)),
|
||||||
|
},
|
||||||
|
totalSourceShares: d("1000"),
|
||||||
|
currentTime: time.Date(1998, 1, 1, 0, 0, 10, 0, time.UTC),
|
||||||
|
},
|
||||||
|
expected: Accumulator{
|
||||||
|
PreviousAccumulationTime: time.Date(1998, 1, 1, 0, 0, 10, 0, time.UTC),
|
||||||
|
Indexes: RewardIndexes{{CollateralType: "hard", RewardFactor: d("10.1")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
tc.args.accumulator.Accumulate(tc.args.period, tc.args.rewardSourceTotal, tc.args.currentTime)
|
tc.args.accumulator.Accumulate(tc.args.period, tc.args.totalSourceShares, tc.args.currentTime)
|
||||||
require.Equal(t, tc.expected, tc.args.accumulator)
|
require.Equal(t, tc.expected, tc.args.accumulator)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,6 @@ const (
|
|||||||
HardLiquidityProviderClaimType = "hard_liquidity_provider"
|
HardLiquidityProviderClaimType = "hard_liquidity_provider"
|
||||||
DelegatorClaimType = "delegator_claim"
|
DelegatorClaimType = "delegator_claim"
|
||||||
SwapClaimType = "swap"
|
SwapClaimType = "swap"
|
||||||
|
|
||||||
BondDenom = "ukava"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Claim is an interface for handling common claim actions
|
// Claim is an interface for handling common claim actions
|
||||||
|
@ -33,6 +33,4 @@ var (
|
|||||||
SwapClaimKeyPrefix = []byte{0x12} // prefix for keys that store swap claims
|
SwapClaimKeyPrefix = []byte{0x12} // prefix for keys that store swap claims
|
||||||
SwapRewardIndexesKeyPrefix = []byte{0x13} // prefix for key that stores swap reward indexes
|
SwapRewardIndexesKeyPrefix = []byte{0x13} // prefix for key that stores swap reward indexes
|
||||||
PreviousSwapRewardAccrualTimeKeyPrefix = []byte{0x14} // prefix for key that stores the previous time swap rewards accrued
|
PreviousSwapRewardAccrualTimeKeyPrefix = []byte{0x14} // prefix for key that stores the previous time swap rewards accrued
|
||||||
|
|
||||||
USDXMintingRewardDenom = "ukava"
|
|
||||||
)
|
)
|
||||||
|
@ -9,10 +9,8 @@ import (
|
|||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
"github.com/cosmos/cosmos-sdk/x/params"
|
"github.com/cosmos/cosmos-sdk/x/params"
|
||||||
|
|
||||||
tmtime "github.com/tendermint/tendermint/types/time"
|
tmtime "github.com/tendermint/tendermint/types/time"
|
||||||
|
|
||||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
|
||||||
kavadistTypes "github.com/kava-labs/kava/x/kavadist/types"
|
kavadistTypes "github.com/kava-labs/kava/x/kavadist/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,14 +30,17 @@ var (
|
|||||||
KeySwapRewardPeriods = []byte("SwapRewardPeriods")
|
KeySwapRewardPeriods = []byte("SwapRewardPeriods")
|
||||||
KeyClaimEnd = []byte("ClaimEnd")
|
KeyClaimEnd = []byte("ClaimEnd")
|
||||||
KeyMultipliers = []byte("ClaimMultipliers")
|
KeyMultipliers = []byte("ClaimMultipliers")
|
||||||
DefaultActive = false
|
|
||||||
DefaultRewardPeriods = RewardPeriods{}
|
DefaultActive = false
|
||||||
DefaultMultiRewardPeriods = MultiRewardPeriods{}
|
DefaultRewardPeriods = RewardPeriods{}
|
||||||
DefaultMultipliers = Multipliers{}
|
DefaultMultiRewardPeriods = MultiRewardPeriods{}
|
||||||
DefaultClaimEnd = tmtime.Canonical(time.Unix(1, 0))
|
DefaultMultipliers = Multipliers{}
|
||||||
GovDenom = cdptypes.DefaultGovDenom
|
DefaultClaimEnd = tmtime.Canonical(time.Unix(1, 0))
|
||||||
PrincipalDenom = "usdx"
|
|
||||||
IncentiveMacc = kavadistTypes.ModuleName
|
BondDenom = "ukava"
|
||||||
|
USDXMintingRewardDenom = "ukava"
|
||||||
|
|
||||||
|
IncentiveMacc = kavadistTypes.ModuleName
|
||||||
)
|
)
|
||||||
|
|
||||||
// Params governance parameters for the incentive module
|
// Params governance parameters for the incentive module
|
||||||
@ -210,6 +211,18 @@ func NewRewardPeriod(active bool, collateralType string, start time.Time, end ti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewMultiRewardPeriodFromRewardPeriod converts a RewardPeriod into a MultiRewardPeriod.
|
||||||
|
// It's useful for compatibility between single and multi denom rewards.
|
||||||
|
func NewMultiRewardPeriodFromRewardPeriod(period RewardPeriod) MultiRewardPeriod {
|
||||||
|
return NewMultiRewardPeriod(
|
||||||
|
period.Active,
|
||||||
|
period.CollateralType,
|
||||||
|
period.Start,
|
||||||
|
period.End,
|
||||||
|
sdk.NewCoins(period.RewardsPerSecond),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Validate performs a basic check of a RewardPeriod fields.
|
// Validate performs a basic check of a RewardPeriod fields.
|
||||||
func (rp RewardPeriod) Validate() error {
|
func (rp RewardPeriod) Validate() error {
|
||||||
if rp.Start.Unix() <= 0 {
|
if rp.Start.Unix() <= 0 {
|
||||||
@ -219,8 +232,12 @@ func (rp RewardPeriod) Validate() error {
|
|||||||
return errors.New("reward period end time cannot be 0")
|
return errors.New("reward period end time cannot be 0")
|
||||||
}
|
}
|
||||||
if rp.Start.After(rp.End) {
|
if rp.Start.After(rp.End) {
|
||||||
|
// This is needed to ensure that the begin blocker accumulation does not panic.
|
||||||
return fmt.Errorf("end period time %s cannot be before start time %s", rp.End, rp.Start)
|
return fmt.Errorf("end period time %s cannot be before start time %s", rp.End, rp.Start)
|
||||||
}
|
}
|
||||||
|
if rp.RewardsPerSecond.Denom != USDXMintingRewardDenom {
|
||||||
|
return fmt.Errorf("reward denom must be %s, got: %s", USDXMintingRewardDenom, rp.RewardsPerSecond.Denom)
|
||||||
|
}
|
||||||
if !rp.RewardsPerSecond.IsValid() {
|
if !rp.RewardsPerSecond.IsValid() {
|
||||||
return fmt.Errorf("invalid reward amount: %s", rp.RewardsPerSecond)
|
return fmt.Errorf("invalid reward amount: %s", rp.RewardsPerSecond)
|
||||||
}
|
}
|
||||||
@ -291,6 +308,7 @@ func (mrp MultiRewardPeriod) Validate() error {
|
|||||||
return errors.New("reward period end time cannot be 0")
|
return errors.New("reward period end time cannot be 0")
|
||||||
}
|
}
|
||||||
if mrp.Start.After(mrp.End) {
|
if mrp.Start.After(mrp.End) {
|
||||||
|
// This is needed to ensure that the begin blocker accumulation does not panic.
|
||||||
return fmt.Errorf("end period time %s cannot be before start time %s", mrp.End, mrp.Start)
|
return fmt.Errorf("end period time %s cannot be before start time %s", mrp.End, mrp.Start)
|
||||||
}
|
}
|
||||||
if !mrp.RewardsPerSecond.IsValid() {
|
if !mrp.RewardsPerSecond.IsValid() {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package types_test
|
package types_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -31,6 +32,20 @@ var rewardMultiPeriodWithInvalidRewardsPerSecond = types.NewMultiRewardPeriod(
|
|||||||
time.Date(2024, 10, 15, 14, 0, 0, 0, time.UTC),
|
time.Date(2024, 10, 15, 14, 0, 0, 0, time.UTC),
|
||||||
sdk.Coins{sdk.Coin{Denom: "INVALID!@#😫", Amount: sdk.ZeroInt()}},
|
sdk.Coins{sdk.Coin{Denom: "INVALID!@#😫", Amount: sdk.ZeroInt()}},
|
||||||
)
|
)
|
||||||
|
var validMultiRewardPeriod = types.NewMultiRewardPeriod(
|
||||||
|
true,
|
||||||
|
"bnb",
|
||||||
|
time.Date(2020, 10, 15, 14, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(2024, 10, 15, 14, 0, 0, 0, time.UTC),
|
||||||
|
sdk.NewCoins(sdk.NewInt64Coin("swap", 1e9)),
|
||||||
|
)
|
||||||
|
var validRewardPeriod = types.NewRewardPeriod(
|
||||||
|
true,
|
||||||
|
"bnb-a",
|
||||||
|
time.Date(2020, 10, 15, 14, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(2024, 10, 15, 14, 0, 0, 0, time.UTC),
|
||||||
|
sdk.NewInt64Coin(types.USDXMintingRewardDenom, 1e9),
|
||||||
|
)
|
||||||
|
|
||||||
func (suite *ParamTestSuite) TestParamValidation() {
|
func (suite *ParamTestSuite) TestParamValidation() {
|
||||||
type errArgs struct {
|
type errArgs struct {
|
||||||
@ -93,7 +108,7 @@ func (suite *ParamTestSuite) TestParamValidation() {
|
|||||||
},
|
},
|
||||||
errArgs{
|
errArgs{
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
contains: "invalid reward amount",
|
contains: fmt.Sprintf("reward denom must be %s", types.USDXMintingRewardDenom),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -176,6 +191,120 @@ func (suite *ParamTestSuite) TestParamValidation() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *ParamTestSuite) TestRewardPeriods() {
|
||||||
|
suite.Run("Validate", func() {
|
||||||
|
type err struct {
|
||||||
|
pass bool
|
||||||
|
contains string
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
periods types.RewardPeriods
|
||||||
|
expect err
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single period is valid",
|
||||||
|
periods: types.RewardPeriods{
|
||||||
|
validRewardPeriod,
|
||||||
|
},
|
||||||
|
expect: err{
|
||||||
|
pass: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicated reward period is invalid",
|
||||||
|
periods: types.RewardPeriods{
|
||||||
|
validRewardPeriod,
|
||||||
|
validRewardPeriod,
|
||||||
|
},
|
||||||
|
expect: err{
|
||||||
|
contains: "duplicated reward period",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid reward denom is invalid",
|
||||||
|
periods: types.RewardPeriods{
|
||||||
|
types.NewRewardPeriod(
|
||||||
|
true,
|
||||||
|
"bnb-a",
|
||||||
|
time.Date(2020, 10, 15, 14, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(2024, 10, 15, 14, 0, 0, 0, time.UTC),
|
||||||
|
sdk.NewInt64Coin("hard", 1e9),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expect: err{
|
||||||
|
contains: fmt.Sprintf("reward denom must be %s", types.USDXMintingRewardDenom),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
|
||||||
|
err := tc.periods.Validate()
|
||||||
|
|
||||||
|
if tc.expect.pass {
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err)
|
||||||
|
suite.Contains(err.Error(), tc.expect.contains)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ParamTestSuite) TestMultiRewardPeriods() {
|
||||||
|
suite.Run("Validate", func() {
|
||||||
|
type err struct {
|
||||||
|
pass bool
|
||||||
|
contains string
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
periods types.MultiRewardPeriods
|
||||||
|
expect err
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single period is valid",
|
||||||
|
periods: types.MultiRewardPeriods{
|
||||||
|
validMultiRewardPeriod,
|
||||||
|
},
|
||||||
|
expect: err{
|
||||||
|
pass: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicated reward period is invalid",
|
||||||
|
periods: types.MultiRewardPeriods{
|
||||||
|
validMultiRewardPeriod,
|
||||||
|
validMultiRewardPeriod,
|
||||||
|
},
|
||||||
|
expect: err{
|
||||||
|
contains: "duplicated reward period",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid reward period is invalid",
|
||||||
|
periods: types.MultiRewardPeriods{
|
||||||
|
rewardMultiPeriodWithInvalidRewardsPerSecond,
|
||||||
|
},
|
||||||
|
expect: err{
|
||||||
|
contains: "invalid reward amount",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
|
||||||
|
err := tc.periods.Validate()
|
||||||
|
|
||||||
|
if tc.expect.pass {
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err)
|
||||||
|
suite.Contains(err.Error(), tc.expect.contains)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestParamTestSuite(t *testing.T) {
|
func TestParamTestSuite(t *testing.T) {
|
||||||
suite.Run(t, new(ParamTestSuite))
|
suite.Run(t, new(ParamTestSuite))
|
||||||
}
|
}
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewPeriod returns a new vesting period
|
|
||||||
func NewPeriod(amount sdk.Coins, length int64) vesting.Period {
|
|
||||||
return vesting.Period{Amount: amount, Length: length}
|
|
||||||
}
|
|
@ -5,6 +5,11 @@ import (
|
|||||||
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NewPeriod returns a new vesting period
|
||||||
|
func NewPeriod(amount sdk.Coins, length int64) vesting.Period {
|
||||||
|
return vesting.Period{Amount: amount, Length: length}
|
||||||
|
}
|
||||||
|
|
||||||
// GetTotalVestingPeriodLength returns the summed length of all vesting periods
|
// GetTotalVestingPeriodLength returns the summed length of all vesting periods
|
||||||
func GetTotalVestingPeriodLength(periods vesting.Periods) int64 {
|
func GetTotalVestingPeriodLength(periods vesting.Periods) int64 {
|
||||||
length := int64(0)
|
length := int64(0)
|
||||||
|
Loading…
Reference in New Issue
Block a user