mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-24 23:35:19 +00:00
Hard: Kava delegators earn HARD rewards via the Incentive module (#776)
* add staking keeper to incentive module * update hard with delegator methods * add delegator methods to incentive * implement delegator hook scaffolds * implement hard delegator reward accumulation * update claim names to delegator * stakingKeeper expected keeper methods * accumulate delegator rewards * initialize delegator reward * synchronize delegator reward * add TODO comments to rewards * implement staking hooks interface * initial revisions * remove outdated TODO * update methods for test compatibility * update method names for test compatibility * implement initial accumulate delegator reward test * attempt validator set up in staking module * initial synchronize delegator reward test * delegator accumulation test passing * synchronize delegator rewards test (not passing) * synchronize delegator rewards passing * revisions
This commit is contained in:
parent
dc330d02bf
commit
72a6df17fd
@ -382,6 +382,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio
|
||||
&cdpKeeper,
|
||||
&hardKeeper,
|
||||
app.accountKeeper,
|
||||
&stakingKeeper,
|
||||
)
|
||||
app.issuanceKeeper = issuance.NewKeeper(
|
||||
app.cdc,
|
||||
@ -394,7 +395,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio
|
||||
// register the staking hooks
|
||||
// NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks
|
||||
app.stakingKeeper = *stakingKeeper.SetHooks(
|
||||
staking.NewMultiStakingHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks()))
|
||||
staking.NewMultiStakingHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks(), app.incentiveKeeper.Hooks()))
|
||||
|
||||
app.cdpKeeper = *cdpKeeper.SetHooks(cdp.NewMultiCDPHooks(app.incentiveKeeper.Hooks()))
|
||||
|
||||
|
@ -34,18 +34,19 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
PreviousBlockTimeKey = []byte{0x01}
|
||||
DepositsKeyPrefix = []byte{0x03}
|
||||
BorrowsKeyPrefix = []byte{0x05}
|
||||
BorrowedCoinsPrefix = []byte{0x06}
|
||||
SuppliedCoinsPrefix = []byte{0x07}
|
||||
MoneyMarketsPrefix = []byte{0x08}
|
||||
PreviousAccrualTimePrefix = []byte{0x09} // denom -> time
|
||||
TotalReservesPrefix = []byte{0x10} // denom -> sdk.Coin
|
||||
BorrowInterestFactorPrefix = []byte{0x11} // denom -> sdk.Dec
|
||||
SupplyInterestFactorPrefix = []byte{0x12} // denom -> sdk.Dec
|
||||
LtvIndexPrefix = []byte{0x13}
|
||||
sep = []byte(":")
|
||||
PreviousBlockTimeKey = []byte{0x01}
|
||||
DepositsKeyPrefix = []byte{0x03}
|
||||
BorrowsKeyPrefix = []byte{0x05}
|
||||
BorrowedCoinsPrefix = []byte{0x06}
|
||||
SuppliedCoinsPrefix = []byte{0x07}
|
||||
MoneyMarketsPrefix = []byte{0x08}
|
||||
PreviousAccrualTimePrefix = []byte{0x09} // denom -> time
|
||||
TotalReservesPrefix = []byte{0x10} // denom -> sdk.Coin
|
||||
BorrowInterestFactorPrefix = []byte{0x11} // denom -> sdk.Dec
|
||||
SupplyInterestFactorPrefix = []byte{0x12} // denom -> sdk.Dec
|
||||
DelegatorInterestFactorPrefix = []byte{0x12} // denom -> sdk.Dec
|
||||
LtvIndexPrefix = []byte{0x13}
|
||||
sep = []byte(":")
|
||||
)
|
||||
|
||||
// DepositTypeIteratorKey returns an interator prefix for interating over deposits by deposit denom
|
||||
|
@ -2,6 +2,8 @@ package keeper
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||
hardtypes "github.com/kava-labs/kava/x/hard/types"
|
||||
)
|
||||
@ -13,10 +15,13 @@ type Hooks struct {
|
||||
|
||||
var _ cdptypes.CDPHooks = Hooks{}
|
||||
var _ hardtypes.HARDHooks = Hooks{}
|
||||
var _ stakingtypes.StakingHooks = Hooks{}
|
||||
|
||||
// Hooks create new incentive hooks
|
||||
func (k Keeper) Hooks() Hooks { return Hooks{k} }
|
||||
|
||||
// ------------------- Cdp Module Hooks -------------------
|
||||
|
||||
// AfterCDPCreated function that runs after a cdp is created
|
||||
func (h Hooks) AfterCDPCreated(ctx sdk.Context, cdp cdptypes.CDP) {
|
||||
h.k.InitializeUSDXMintingClaim(ctx, cdp)
|
||||
@ -29,6 +34,8 @@ func (h Hooks) BeforeCDPModified(ctx sdk.Context, cdp cdptypes.CDP) {
|
||||
h.k.SynchronizeUSDXMintingReward(ctx, cdp)
|
||||
}
|
||||
|
||||
// ------------------- Hard Module Hooks -------------------
|
||||
|
||||
// AfterDepositCreated function that runs after a deposit is created
|
||||
func (h Hooks) AfterDepositCreated(ctx sdk.Context, deposit hardtypes.Deposit) {
|
||||
h.k.InitializeHardSupplyReward(ctx, deposit)
|
||||
@ -58,3 +65,46 @@ func (h Hooks) BeforeBorrowModified(ctx sdk.Context, borrow hardtypes.Borrow) {
|
||||
func (h Hooks) AfterBorrowModified(ctx sdk.Context, borrow hardtypes.Borrow) {
|
||||
h.k.UpdateHardBorrowIndexDenoms(ctx, borrow)
|
||||
}
|
||||
|
||||
// ------------------- Staking Module Hooks -------------------
|
||||
|
||||
// BeforeDelegationCreated runs before a delegation is created
|
||||
func (h Hooks) BeforeDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
|
||||
h.k.InitializeHardDelegatorReward(ctx, delAddr)
|
||||
}
|
||||
|
||||
// BeforeDelegationSharesModified runs before an existing delegation is modified
|
||||
func (h Hooks) BeforeDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
|
||||
h.k.SynchronizeHardDelegatorRewards(ctx, delAddr)
|
||||
}
|
||||
|
||||
// NOTE: following hooks are just implemented to ensure StakingHooks interface compliance
|
||||
|
||||
// BeforeValidatorSlashed is called before a validator is slashed
|
||||
func (h Hooks) BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) {}
|
||||
|
||||
// AfterValidatorBeginUnbonding is called after a validator begins unbonding
|
||||
func (h Hooks) AfterValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) {
|
||||
}
|
||||
|
||||
// AfterValidatorBonded is called after a validator is bonded
|
||||
func (h Hooks) AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) {
|
||||
}
|
||||
|
||||
// AfterDelegationModified runs after a delegation is modified
|
||||
func (h Hooks) AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
|
||||
}
|
||||
|
||||
// BeforeDelegationRemoved runs directly before a delegation is deleted
|
||||
func (h Hooks) BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
|
||||
}
|
||||
|
||||
// AfterValidatorCreated runs after a validator is created
|
||||
func (h Hooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) {}
|
||||
|
||||
// BeforeValidatorModified runs before a validator is modified
|
||||
func (h Hooks) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) {}
|
||||
|
||||
// AfterValidatorRemoved runs after a validator is removed
|
||||
func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) {
|
||||
}
|
||||
|
@ -20,12 +20,13 @@ type Keeper struct {
|
||||
key sdk.StoreKey
|
||||
paramSubspace subspace.Subspace
|
||||
supplyKeeper types.SupplyKeeper
|
||||
stakingKeeper types.StakingKeeper
|
||||
}
|
||||
|
||||
// NewKeeper creates a new keeper
|
||||
func NewKeeper(
|
||||
cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, sk types.SupplyKeeper,
|
||||
cdpk types.CdpKeeper, hk types.HardKeeper, ak types.AccountKeeper,
|
||||
cdpk types.CdpKeeper, hk types.HardKeeper, ak types.AccountKeeper, stk types.StakingKeeper,
|
||||
) Keeper {
|
||||
|
||||
return Keeper{
|
||||
@ -36,6 +37,7 @@ func NewKeeper(
|
||||
key: key,
|
||||
paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()),
|
||||
supplyKeeper: sk,
|
||||
stakingKeeper: stk,
|
||||
}
|
||||
}
|
||||
|
||||
@ -276,3 +278,20 @@ func (k Keeper) SetPreviousHardBorrowRewardAccrualTime(ctx sdk.Context, denom st
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousHardBorrowRewardAccrualTimeKeyPrefix)
|
||||
store.Set([]byte(denom), k.cdc.MustMarshalBinaryBare(blockTime))
|
||||
}
|
||||
|
||||
// GetPreviousHardDelegatorRewardAccrualTime returns the last time a denom accrued Hard protocol delegator rewards
|
||||
func (k Keeper) GetPreviousHardDelegatorRewardAccrualTime(ctx sdk.Context, denom string) (blockTime time.Time, found bool) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousHardDelegatorRewardAccrualTimeKeyPrefix)
|
||||
bz := store.Get([]byte(denom))
|
||||
if bz == nil {
|
||||
return time.Time{}, false
|
||||
}
|
||||
k.cdc.MustUnmarshalBinaryBare(bz, &blockTime)
|
||||
return blockTime, true
|
||||
}
|
||||
|
||||
// SetPreviousHardDelegatorRewardAccrualTime sets the last time a denom accrued Hard protocol delegator rewards
|
||||
func (k Keeper) SetPreviousHardDelegatorRewardAccrualTime(ctx sdk.Context, denom string, blockTime time.Time) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousHardDelegatorRewardAccrualTimeKeyPrefix)
|
||||
store.Set([]byte(denom), k.cdc.MustMarshalBinaryBare(blockTime))
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
||||
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
|
||||
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
@ -24,18 +25,22 @@ import (
|
||||
type KeeperTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
keeper keeper.Keeper
|
||||
hardKeeper hardkeeper.Keeper
|
||||
app app.TestApp
|
||||
ctx sdk.Context
|
||||
addrs []sdk.AccAddress
|
||||
keeper keeper.Keeper
|
||||
hardKeeper hardkeeper.Keeper
|
||||
stakingKeeper stakingkeeper.Keeper
|
||||
app app.TestApp
|
||||
ctx sdk.Context
|
||||
addrs []sdk.AccAddress
|
||||
validatorAddrs []sdk.ValAddress
|
||||
}
|
||||
|
||||
// The default state used by each test
|
||||
func (suite *KeeperTestSuite) SetupTest() {
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
||||
|
||||
tApp.InitializeFromGenesisStates()
|
||||
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(5)
|
||||
keeper := tApp.GetIncentiveKeeper()
|
||||
suite.app = tApp
|
||||
|
@ -53,6 +53,17 @@ func (k Keeper) GetHardBorrowRewardPeriod(ctx sdk.Context, denom string) (types.
|
||||
return types.RewardPeriod{}, false
|
||||
}
|
||||
|
||||
// GetHardDelegatorRewardPeriod returns the reward period with the specified collateral type if it's found in the params
|
||||
func (k Keeper) GetHardDelegatorRewardPeriod(ctx sdk.Context, denom string) (types.RewardPeriod, bool) {
|
||||
params := k.GetParams(ctx)
|
||||
for _, rp := range params.HardDelegatorRewardPeriods {
|
||||
if rp.CollateralType == denom {
|
||||
return rp, true
|
||||
}
|
||||
}
|
||||
return types.RewardPeriod{}, false
|
||||
}
|
||||
|
||||
// GetMultiplier returns the multiplier with the specified name if it's found in the params
|
||||
func (k Keeper) GetMultiplier(ctx sdk.Context, name types.MultiplierName) (types.Multiplier, bool) {
|
||||
params := k.GetParams(ctx)
|
||||
|
@ -54,7 +54,7 @@ func (suite *KeeperTestSuite) TestPayoutClaim() {
|
||||
multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
|
||||
multiplier: types.MultiplierName("large"),
|
||||
timeElapsed: 86400,
|
||||
expectedBalance: cs(c("usdx", 10000000000), c("ukava", 10571385600)),
|
||||
expectedBalance: cs(c("usdx", 10000000000), c("ukava", 10576385600)),
|
||||
expectedPeriods: vesting.Periods{vesting.Period{Length: 31536000, Amount: cs(c("ukava", 10571385600))}},
|
||||
isPeriodicVestingAccount: true,
|
||||
},
|
||||
|
@ -400,6 +400,124 @@ func (k Keeper) UpdateHardBorrowIndexDenoms(ctx sdk.Context, borrow hardtypes.Bo
|
||||
k.SetHardLiquidityProviderClaim(ctx, claim)
|
||||
}
|
||||
|
||||
// SynchronizeHardDelegatorRewards updates the claim object by adding any accumulated rewards
|
||||
func (k Keeper) SynchronizeHardDelegatorRewards(ctx sdk.Context, delegator sdk.AccAddress) {
|
||||
claim, found := k.GetHardLiquidityProviderClaim(ctx, delegator)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
delagatorFactor, found := k.GetHardDelegatorRewardFactor(ctx, types.BondDenom)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
delegatorIndex, hasDelegatorRewardIndex := claim.HasDelegatorRewardIndex(types.BondDenom)
|
||||
if !hasDelegatorRewardIndex {
|
||||
return
|
||||
}
|
||||
|
||||
userRewardFactor := claim.DelegatorRewardIndexes[delegatorIndex].RewardFactor
|
||||
rewardsAccumulatedFactor := delagatorFactor.Sub(userRewardFactor)
|
||||
if rewardsAccumulatedFactor.IsZero() {
|
||||
return
|
||||
}
|
||||
claim.DelegatorRewardIndexes[delegatorIndex].RewardFactor = delagatorFactor
|
||||
|
||||
totalDelegated := sdk.ZeroDec()
|
||||
|
||||
// TODO: set reasonable max limit on delegation iteration
|
||||
maxUInt := ^uint16(0)
|
||||
delegations := k.stakingKeeper.GetDelegatorDelegations(ctx, delegator, maxUInt)
|
||||
for _, delegation := range delegations {
|
||||
validator, found := k.stakingKeeper.GetValidator(ctx, delegation.GetValidatorAddr())
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
// Delegators don't accumulate rewards if their validator is unbonded/slashed
|
||||
if validator.GetStatus() != sdk.Bonded {
|
||||
continue
|
||||
}
|
||||
|
||||
if validator.GetTokens().IsZero() {
|
||||
continue
|
||||
}
|
||||
|
||||
delegatedTokens := validator.TokensFromShares(delegation.GetShares())
|
||||
if delegatedTokens.IsZero() || delegatedTokens.IsNegative() {
|
||||
continue
|
||||
}
|
||||
totalDelegated = totalDelegated.Add(delegatedTokens)
|
||||
}
|
||||
|
||||
rewardsEarned := rewardsAccumulatedFactor.Mul(totalDelegated).RoundInt()
|
||||
if rewardsEarned.IsZero() || rewardsEarned.IsNegative() {
|
||||
return
|
||||
}
|
||||
|
||||
// Add rewards to delegator's hard claim
|
||||
newRewardsCoin := sdk.NewCoin(types.HardLiquidityRewardDenom, rewardsEarned)
|
||||
claim.Reward = claim.Reward.Add(newRewardsCoin)
|
||||
k.SetHardLiquidityProviderClaim(ctx, claim)
|
||||
}
|
||||
|
||||
// AccumulateHardDelegatorRewards updates the rewards accumulated for the input reward period
|
||||
func (k Keeper) AccumulateHardDelegatorRewards(ctx sdk.Context, rewardPeriod types.RewardPeriod) error {
|
||||
previousAccrualTime, found := k.GetPreviousHardDelegatorRewardAccrualTime(ctx, rewardPeriod.CollateralType)
|
||||
if !found {
|
||||
k.SetPreviousHardDelegatorRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
||||
return nil
|
||||
}
|
||||
timeElapsed := CalculateTimeElapsed(rewardPeriod, ctx.BlockTime(), previousAccrualTime)
|
||||
if timeElapsed.IsZero() {
|
||||
return nil
|
||||
}
|
||||
if rewardPeriod.RewardsPerSecond.Amount.IsZero() {
|
||||
k.SetPreviousHardDelegatorRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
||||
return nil
|
||||
}
|
||||
|
||||
totalBonded := k.stakingKeeper.TotalBondedTokens(ctx).ToDec()
|
||||
if totalBonded.IsZero() {
|
||||
k.SetPreviousHardDelegatorRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
||||
return nil
|
||||
}
|
||||
|
||||
newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount)
|
||||
rewardFactor := newRewards.ToDec().Quo(totalBonded)
|
||||
|
||||
previousRewardFactor, found := k.GetHardDelegatorRewardFactor(ctx, rewardPeriod.CollateralType)
|
||||
if !found {
|
||||
previousRewardFactor = sdk.ZeroDec()
|
||||
}
|
||||
newRewardFactor := previousRewardFactor.Add(rewardFactor)
|
||||
k.SetHardDelegatorRewardFactor(ctx, rewardPeriod.CollateralType, newRewardFactor)
|
||||
k.SetPreviousHardDelegatorRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitializeHardDelegatorReward initializes the delegator reward index of a hard claim
|
||||
func (k Keeper) InitializeHardDelegatorReward(ctx sdk.Context, delegator sdk.AccAddress) {
|
||||
delegatorFactor, foundDelegatorFactor := k.GetHardDelegatorRewardFactor(ctx, types.BondDenom)
|
||||
if !foundDelegatorFactor { // Should always be found...
|
||||
delegatorFactor = sdk.ZeroDec()
|
||||
}
|
||||
|
||||
delegatorRewardIndexes := types.NewRewardIndex(types.BondDenom, delegatorFactor)
|
||||
|
||||
claim, found := k.GetHardLiquidityProviderClaim(ctx, delegator)
|
||||
if !found {
|
||||
// Instantiate claim object
|
||||
claim = types.NewHardLiquidityProviderClaim(delegator,
|
||||
sdk.NewCoin(types.HardLiquidityRewardDenom, sdk.ZeroInt()),
|
||||
nil, nil, nil)
|
||||
}
|
||||
|
||||
claim.DelegatorRewardIndexes = types.RewardIndexes{delegatorRewardIndexes}
|
||||
k.SetHardLiquidityProviderClaim(ctx, claim)
|
||||
}
|
||||
|
||||
// ZeroClaim zeroes out the claim object's rewards and returns the updated claim object
|
||||
func (k Keeper) ZeroClaim(ctx sdk.Context, claim types.USDXMintingClaim) types.USDXMintingClaim {
|
||||
claim.Reward = sdk.NewCoin(claim.Reward.Denom, sdk.ZeroInt())
|
||||
|
@ -4,8 +4,9 @@ import (
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
@ -866,36 +867,292 @@ func (suite *KeeperTestSuite) TestUpdateHardBorrowIndexDenoms() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestAccumulateHardDelegatorRewards() {
|
||||
type args struct {
|
||||
delegation sdk.Coin
|
||||
rewardsPerSecond sdk.Coin
|
||||
initialTime time.Time
|
||||
timeElapsed int
|
||||
expectedRewardFactor sdk.Dec
|
||||
}
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
}
|
||||
testCases := []test{
|
||||
{
|
||||
"7 seconds",
|
||||
args{
|
||||
delegation: c("ukava", 1_000_000),
|
||||
rewardsPerSecond: c("hard", 122354),
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
timeElapsed: 7,
|
||||
expectedRewardFactor: d("0.428239000000000000"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"1 day",
|
||||
args{
|
||||
delegation: c("ukava", 1_000_000),
|
||||
rewardsPerSecond: c("hard", 122354),
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
timeElapsed: 86400,
|
||||
expectedRewardFactor: d("5285.692800000000000000"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"0 seconds",
|
||||
args{
|
||||
delegation: c("ukava", 1_000_000),
|
||||
rewardsPerSecond: c("hard", 122354),
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
timeElapsed: 0,
|
||||
expectedRewardFactor: d("0.0"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
suite.SetupWithGenState()
|
||||
suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime)
|
||||
|
||||
// Mint coins to hard module account
|
||||
supplyKeeper := suite.app.GetSupplyKeeper()
|
||||
hardMaccCoins := sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(200000000)))
|
||||
supplyKeeper.MintCoins(suite.ctx, hardtypes.ModuleAccountName, hardMaccCoins)
|
||||
|
||||
// Set up incentive state
|
||||
params := types.NewParams(
|
||||
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
|
||||
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
|
||||
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
|
||||
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
|
||||
types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
|
||||
tc.args.initialTime.Add(time.Hour*24*365*5),
|
||||
)
|
||||
suite.keeper.SetParams(suite.ctx, params)
|
||||
suite.keeper.SetPreviousHardDelegatorRewardAccrualTime(suite.ctx, tc.args.delegation.Denom, tc.args.initialTime)
|
||||
suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, tc.args.delegation.Denom, sdk.ZeroDec())
|
||||
|
||||
// Set up hard state (interest factor for the relevant denom)
|
||||
suite.hardKeeper.SetPreviousAccrualTime(suite.ctx, tc.args.delegation.Denom, tc.args.initialTime)
|
||||
|
||||
err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], tc.args.delegation)
|
||||
suite.Require().NoError(err)
|
||||
suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[0], tc.args.delegation)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
staking.EndBlocker(suite.ctx, suite.stakingKeeper)
|
||||
|
||||
// Set up chain context at future time
|
||||
runAtTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed))
|
||||
runCtx := suite.ctx.WithBlockTime(runAtTime)
|
||||
|
||||
// Run Hard begin blocker in order to update the denom's index factor
|
||||
hard.BeginBlocker(runCtx, suite.hardKeeper)
|
||||
|
||||
rewardPeriod, found := suite.keeper.GetHardDelegatorRewardPeriod(runCtx, tc.args.delegation.Denom)
|
||||
suite.Require().True(found)
|
||||
err = suite.keeper.AccumulateHardDelegatorRewards(runCtx, rewardPeriod)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
rewardFactor, found := suite.keeper.GetHardDelegatorRewardFactor(runCtx, tc.args.delegation.Denom)
|
||||
suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestSynchronizeHardDelegatorReward() {
|
||||
type args struct {
|
||||
delegation sdk.Coin
|
||||
rewardsPerSecond sdk.Coin
|
||||
initialTime time.Time
|
||||
blockTimes []int
|
||||
expectedRewardFactor sdk.Dec
|
||||
expectedRewards sdk.Coin
|
||||
}
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
}
|
||||
|
||||
testCases := []test{
|
||||
{
|
||||
"10 blocks",
|
||||
args{
|
||||
delegation: c("ukava", 1_000_000),
|
||||
rewardsPerSecond: c("hard", 122354),
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
blockTimes: []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10},
|
||||
expectedRewardFactor: d("6.117700000000000000"),
|
||||
expectedRewards: c("hard", 6117700),
|
||||
},
|
||||
},
|
||||
{
|
||||
"10 blocks - long block time",
|
||||
args{
|
||||
delegation: c("ukava", 1_000_000),
|
||||
rewardsPerSecond: c("hard", 122354),
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
blockTimes: []int{86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400},
|
||||
expectedRewardFactor: d("52856.928000000000000000"),
|
||||
expectedRewards: c("hard", 52856928000),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
suite.SetupWithGenState()
|
||||
suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime)
|
||||
|
||||
// Mint coins to hard module account
|
||||
supplyKeeper := suite.app.GetSupplyKeeper()
|
||||
hardMaccCoins := sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(200000000)))
|
||||
supplyKeeper.MintCoins(suite.ctx, hardtypes.ModuleAccountName, hardMaccCoins)
|
||||
|
||||
// setup incentive state
|
||||
params := types.NewParams(
|
||||
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
|
||||
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
|
||||
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
|
||||
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.delegation.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
|
||||
types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
|
||||
tc.args.initialTime.Add(time.Hour*24*365*5),
|
||||
)
|
||||
suite.keeper.SetParams(suite.ctx, params)
|
||||
suite.keeper.SetPreviousHardDelegatorRewardAccrualTime(suite.ctx, tc.args.delegation.Denom, tc.args.initialTime)
|
||||
suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, tc.args.delegation.Denom, sdk.ZeroDec())
|
||||
|
||||
// Set up hard state (interest factor for the relevant denom)
|
||||
suite.hardKeeper.SetPreviousAccrualTime(suite.ctx, tc.args.delegation.Denom, tc.args.initialTime)
|
||||
|
||||
// Delegator delegates
|
||||
err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], tc.args.delegation)
|
||||
suite.Require().NoError(err)
|
||||
suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[0], tc.args.delegation)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
staking.EndBlocker(suite.ctx, suite.stakingKeeper)
|
||||
|
||||
// Check that Staking hooks initialized a HardLiquidityProviderClaim
|
||||
claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0])
|
||||
suite.Require().True(found)
|
||||
suite.Require().Equal(sdk.ZeroDec(), claim.DelegatorRewardIndexes[0].RewardFactor)
|
||||
|
||||
// Run accumulator at several intervals
|
||||
var timeElapsed int
|
||||
previousBlockTime := suite.ctx.BlockTime()
|
||||
for _, t := range tc.args.blockTimes {
|
||||
timeElapsed += t
|
||||
updatedBlockTime := previousBlockTime.Add(time.Duration(int(time.Second) * t))
|
||||
previousBlockTime = updatedBlockTime
|
||||
blockCtx := suite.ctx.WithBlockTime(updatedBlockTime)
|
||||
|
||||
// Run Hard begin blocker for each block ctx to update denom's interest factor
|
||||
hard.BeginBlocker(blockCtx, suite.hardKeeper)
|
||||
|
||||
rewardPeriod, found := suite.keeper.GetHardDelegatorRewardPeriod(blockCtx, tc.args.delegation.Denom)
|
||||
suite.Require().True(found)
|
||||
|
||||
err := suite.keeper.AccumulateHardDelegatorRewards(blockCtx, rewardPeriod)
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||
|
||||
// After we've accumulated, run synchronize
|
||||
suite.Require().NotPanics(func() {
|
||||
suite.keeper.SynchronizeHardDelegatorRewards(suite.ctx, suite.addrs[0])
|
||||
})
|
||||
|
||||
// Check that reward factor and claim have been updated as expected
|
||||
rewardFactor, found := suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, tc.args.delegation.Denom)
|
||||
suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor)
|
||||
|
||||
claim, found = suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0])
|
||||
suite.Require().True(found)
|
||||
suite.Require().Equal(tc.args.expectedRewardFactor, claim.DelegatorRewardIndexes[0].RewardFactor)
|
||||
suite.Require().Equal(tc.args.expectedRewards, claim.Reward)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) SetupWithGenState() {
|
||||
config := sdk.GetConfig()
|
||||
app.SetBech32AddressPrefixes(config)
|
||||
|
||||
_, allAddrs := app.GeneratePrivKeyAddressPairs(10)
|
||||
suite.addrs = allAddrs[:5]
|
||||
for _, a := range allAddrs[5:] {
|
||||
suite.validatorAddrs = append(suite.validatorAddrs, sdk.ValAddress(a))
|
||||
}
|
||||
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
||||
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(5)
|
||||
|
||||
authGS := app.NewAuthGenState(
|
||||
[]sdk.AccAddress{addrs[3]},
|
||||
[]sdk.Coins{
|
||||
sdk.NewCoins(
|
||||
sdk.NewCoin("bnb", sdk.NewInt(1000000000000000)),
|
||||
sdk.NewCoin("ukava", sdk.NewInt(1000000000000000)),
|
||||
sdk.NewCoin("btcb", sdk.NewInt(1000000000000000)),
|
||||
sdk.NewCoin("xrp", sdk.NewInt(1000000000000000)),
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
tApp.InitializeFromGenesisStates(
|
||||
authGS,
|
||||
coinsAuthGenState(allAddrs, cs(c("ukava", 5_000_000))),
|
||||
stakingGenesisState(),
|
||||
NewPricefeedGenStateMulti(),
|
||||
NewCDPGenStateMulti(),
|
||||
NewHardGenStateMulti(),
|
||||
)
|
||||
|
||||
keeper := tApp.GetIncentiveKeeper()
|
||||
hardKeeper := tApp.GetHardKeeper()
|
||||
suite.app = tApp
|
||||
suite.ctx = ctx
|
||||
suite.keeper = keeper
|
||||
suite.hardKeeper = hardKeeper
|
||||
suite.addrs = addrs
|
||||
suite.keeper = tApp.GetIncentiveKeeper()
|
||||
suite.hardKeeper = tApp.GetHardKeeper()
|
||||
suite.stakingKeeper = tApp.GetStakingKeeper()
|
||||
}
|
||||
|
||||
func coinsAuthGenState(addresses []sdk.AccAddress, coins sdk.Coins) app.GenesisState {
|
||||
coinsList := []sdk.Coins{}
|
||||
for range addresses {
|
||||
coinsList = append(coinsList, coins)
|
||||
}
|
||||
|
||||
// Load up our primary user address
|
||||
if len(addresses) >= 4 {
|
||||
coinsList[3] = sdk.NewCoins(
|
||||
sdk.NewCoin("bnb", sdk.NewInt(1000000000000000)),
|
||||
sdk.NewCoin("ukava", sdk.NewInt(1000000000000000)),
|
||||
sdk.NewCoin("btcb", sdk.NewInt(1000000000000000)),
|
||||
sdk.NewCoin("xrp", sdk.NewInt(1000000000000000)),
|
||||
)
|
||||
}
|
||||
|
||||
return app.NewAuthGenState(addresses, coinsList)
|
||||
}
|
||||
|
||||
func stakingGenesisState() app.GenesisState {
|
||||
genState := staking.DefaultGenesisState()
|
||||
genState.Params.BondDenom = "ukava"
|
||||
return app.GenesisState{
|
||||
staking.ModuleName: staking.ModuleCdc.MustMarshalJSON(genState),
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) deliverMsgCreateValidator(ctx sdk.Context, address sdk.ValAddress, selfDelegation sdk.Coin) error {
|
||||
msg := staking.NewMsgCreateValidator(
|
||||
address,
|
||||
ed25519.GenPrivKey().PubKey(),
|
||||
selfDelegation,
|
||||
staking.Description{},
|
||||
staking.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
|
||||
sdk.NewInt(1_000_000),
|
||||
)
|
||||
handleStakingMsg := staking.NewHandler(suite.stakingKeeper)
|
||||
_, err := handleStakingMsg(ctx, msg)
|
||||
return err
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) deliverMsgDelegate(ctx sdk.Context, delegator sdk.AccAddress, validator sdk.ValAddress, amount sdk.Coin) error {
|
||||
msg := staking.NewMsgDelegate(
|
||||
delegator,
|
||||
validator,
|
||||
amount,
|
||||
)
|
||||
handleStakingMsg := staking.NewHandler(suite.stakingKeeper)
|
||||
_, err := handleStakingMsg(ctx, msg)
|
||||
return err
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
const (
|
||||
USDXMintingClaimType = "usdx_minting"
|
||||
HardLiquidityProviderClaimType = "hard_liquidity_provider"
|
||||
BondDenom = "ukava"
|
||||
)
|
||||
|
||||
// Claim is an interface for handling common claim actions
|
||||
@ -122,23 +123,23 @@ func (cs USDXMintingClaims) Validate() error {
|
||||
|
||||
// HardLiquidityProviderClaim stores the hard liquidity provider rewards that can be claimed by owner
|
||||
type HardLiquidityProviderClaim struct {
|
||||
BaseClaim `json:"base_claim" yaml:"base_claim"`
|
||||
SupplyRewardIndexes RewardIndexes `json:"supply_reward_indexes" yaml:"supply_reward_indexes"`
|
||||
BorrowRewardIndexes RewardIndexes `json:"borrow_reward_indexes" yaml:"borrow_reward_indexes"`
|
||||
DelegationRewardIndexes RewardIndexes `json:"delegation_reward_indexes" yaml:"delegation_reward_indexes"`
|
||||
BaseClaim `json:"base_claim" yaml:"base_claim"`
|
||||
SupplyRewardIndexes RewardIndexes `json:"supply_reward_indexes" yaml:"supply_reward_indexes"`
|
||||
BorrowRewardIndexes RewardIndexes `json:"borrow_reward_indexes" yaml:"borrow_reward_indexes"`
|
||||
DelegatorRewardIndexes RewardIndexes `json:"delegator_reward_indexes" yaml:"delegator_reward_indexes"`
|
||||
}
|
||||
|
||||
// NewHardLiquidityProviderClaim returns a new HardLiquidityProviderClaim
|
||||
func NewHardLiquidityProviderClaim(owner sdk.AccAddress, reward sdk.Coin, supplyRewardIndexes,
|
||||
borrowRewardIndexes, delegationRewardIndexes RewardIndexes) HardLiquidityProviderClaim {
|
||||
borrowRewardIndexes, delegatorRewardIndexes RewardIndexes) HardLiquidityProviderClaim {
|
||||
return HardLiquidityProviderClaim{
|
||||
BaseClaim: BaseClaim{
|
||||
Owner: owner,
|
||||
Reward: reward,
|
||||
},
|
||||
SupplyRewardIndexes: supplyRewardIndexes,
|
||||
BorrowRewardIndexes: borrowRewardIndexes,
|
||||
DelegationRewardIndexes: delegationRewardIndexes,
|
||||
SupplyRewardIndexes: supplyRewardIndexes,
|
||||
BorrowRewardIndexes: borrowRewardIndexes,
|
||||
DelegatorRewardIndexes: delegatorRewardIndexes,
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,7 +156,7 @@ func (c HardLiquidityProviderClaim) Validate() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.DelegationRewardIndexes.Validate(); err != nil {
|
||||
if err := c.DelegatorRewardIndexes.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -167,8 +168,8 @@ func (c HardLiquidityProviderClaim) String() string {
|
||||
return fmt.Sprintf(`%s
|
||||
Supply Reward Indexes: %s,
|
||||
Borrow Reward Indexes: %s,
|
||||
Delegation Reward Indexes: %s,
|
||||
`, c.BaseClaim, c.SupplyRewardIndexes, c.BorrowRewardIndexes, c.DelegationRewardIndexes)
|
||||
Delegator Reward Indexes: %s,
|
||||
`, c.BaseClaim, c.SupplyRewardIndexes, c.BorrowRewardIndexes, c.DelegatorRewardIndexes)
|
||||
}
|
||||
|
||||
// HasSupplyRewardIndex check if a claim has a supply reward index for the input collateral type
|
||||
@ -191,9 +192,9 @@ func (c HardLiquidityProviderClaim) HasBorrowRewardIndex(denom string) (int64, b
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// HasDelegationRewardIndex check if a claim has a delegation reward index for the input collateral type
|
||||
func (c HardLiquidityProviderClaim) HasDelegationRewardIndex(collateralType string) (int64, bool) {
|
||||
for index, ri := range c.SupplyRewardIndexes {
|
||||
// HasDelegatorRewardIndex check if a claim has a delegator reward index for the input collateral type
|
||||
func (c HardLiquidityProviderClaim) HasDelegatorRewardIndex(collateralType string) (int64, bool) {
|
||||
for index, ri := range c.DelegatorRewardIndexes {
|
||||
if ri.CollateralType == collateralType {
|
||||
return int64(index), true
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package types
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
|
||||
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||
@ -15,6 +16,13 @@ type SupplyKeeper interface {
|
||||
SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
|
||||
}
|
||||
|
||||
// StakingKeeper defines the expected staking keeper for module accounts
|
||||
type StakingKeeper interface {
|
||||
GetDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAddress, maxRetrieve uint16) (delegations []stakingtypes.Delegation)
|
||||
GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator stakingtypes.Validator, found bool)
|
||||
TotalBondedTokens(ctx sdk.Context) sdk.Int
|
||||
}
|
||||
|
||||
// CdpKeeper defines the expected cdp keeper for interacting with cdps
|
||||
type CdpKeeper interface {
|
||||
GetInterestFactor(ctx sdk.Context, collateralType string) (sdk.Dec, bool)
|
||||
|
Loading…
Reference in New Issue
Block a user