Incentive/Hard rebase to master (#773)

* spike: incentive/types

* spike: incentive/types tests

* spike: incentive/types/expected_keepers.go

* spike: incentive/keeper

* spike: incentive/keeper tests

* spike: incentive/sims and incentive/sims tests

* spike: incentive/module

* spike: incentive/module tests

* spike: hard/types

* spike: hard/types hooks

* spike: hard/types

* spike: hard/keeper basics

* spike: hard/keeper hooks

* integrate hard/keeper/borrow.go

* integrate hard/keeper/deposit.go

* integrate hard/keeper/liquidation.go

* integrate hard/keeper/withdraw.go

* integrate hard/keeper/repay.go

* spike: hard/sims

* spike: hard/sims tests

* spike: hard/client

* spike: hard/module

* integrate app.go

* spike: x/hard/keeper compile tests

* incentive/keeper test clean up

* validate usdx incentive types in genesis

* refactoring & fix deposit test

* fix liquidaton tests

* fix incentive tests for hard supply rewards

* fix hard genesis tests

* update incentive genesis state and params

* update cdp rewards accumulation

* update app init order and begin blocker order

Co-authored-by: karzak <kjydavis3@gmail.com>
This commit is contained in:
Denali Marsh 2021-01-21 14:52:09 +01:00 committed by GitHub
parent 67b58f555b
commit 4e6f6d1e9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 2065 additions and 2766 deletions

View File

@ -358,6 +358,16 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio
bep3Subspace, bep3Subspace,
app.ModuleAccountAddrs(), app.ModuleAccountAddrs(),
) )
hardKeeper := hard.NewKeeper(
app.cdc,
keys[hard.StoreKey],
hardSubspace,
app.accountKeeper,
app.supplyKeeper,
&stakingKeeper,
app.pricefeedKeeper,
app.auctionKeeper,
)
app.kavadistKeeper = kavadist.NewKeeper( app.kavadistKeeper = kavadist.NewKeeper(
app.cdc, app.cdc,
keys[kavadist.StoreKey], keys[kavadist.StoreKey],
@ -370,6 +380,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio
incentiveSubspace, incentiveSubspace,
app.supplyKeeper, app.supplyKeeper,
&cdpKeeper, &cdpKeeper,
&hardKeeper,
app.accountKeeper, app.accountKeeper,
) )
app.issuanceKeeper = issuance.NewKeeper( app.issuanceKeeper = issuance.NewKeeper(
@ -379,16 +390,6 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio
app.accountKeeper, app.accountKeeper,
app.supplyKeeper, app.supplyKeeper,
) )
app.hardKeeper = hard.NewKeeper(
app.cdc,
keys[hard.StoreKey],
hardSubspace,
app.accountKeeper,
app.supplyKeeper,
&stakingKeeper,
app.pricefeedKeeper,
app.auctionKeeper,
)
// register the staking hooks // register the staking hooks
// NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks
@ -397,6 +398,8 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio
app.cdpKeeper = *cdpKeeper.SetHooks(cdp.NewMultiCDPHooks(app.incentiveKeeper.Hooks())) app.cdpKeeper = *cdpKeeper.SetHooks(cdp.NewMultiCDPHooks(app.incentiveKeeper.Hooks()))
app.hardKeeper = *hardKeeper.SetHooks(hard.NewMultiHARDHooks(app.incentiveKeeper.Hooks()))
// create the module manager (Note: Any module instantiated in the module manager that is later modified // create the module manager (Note: Any module instantiated in the module manager that is later modified
// must be passed by reference here.) // must be passed by reference here.)
app.mm = module.NewManager( app.mm = module.NewManager(
@ -432,7 +435,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio
app.mm.SetOrderBeginBlockers( app.mm.SetOrderBeginBlockers(
upgrade.ModuleName, mint.ModuleName, distr.ModuleName, slashing.ModuleName, upgrade.ModuleName, mint.ModuleName, distr.ModuleName, slashing.ModuleName,
validatorvesting.ModuleName, kavadist.ModuleName, auction.ModuleName, cdp.ModuleName, validatorvesting.ModuleName, kavadist.ModuleName, auction.ModuleName, cdp.ModuleName,
bep3.ModuleName, incentive.ModuleName, committee.ModuleName, issuance.ModuleName, hard.ModuleName, bep3.ModuleName, hard.ModuleName, committee.ModuleName, issuance.ModuleName, incentive.ModuleName,
) )
app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName) app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName)
@ -442,8 +445,8 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio
validatorvesting.ModuleName, distr.ModuleName, validatorvesting.ModuleName, distr.ModuleName,
staking.ModuleName, bank.ModuleName, slashing.ModuleName, staking.ModuleName, bank.ModuleName, slashing.ModuleName,
gov.ModuleName, mint.ModuleName, evidence.ModuleName, gov.ModuleName, mint.ModuleName, evidence.ModuleName,
pricefeed.ModuleName, cdp.ModuleName, auction.ModuleName, pricefeed.ModuleName, cdp.ModuleName, hard.ModuleName, auction.ModuleName,
bep3.ModuleName, kavadist.ModuleName, incentive.ModuleName, committee.ModuleName, issuance.ModuleName, hard.ModuleName, bep3.ModuleName, kavadist.ModuleName, incentive.ModuleName, committee.ModuleName, issuance.ModuleName,
supply.ModuleName, // calculates the total supply from account - should run after modules that modify accounts in genesis supply.ModuleName, // calculates the total supply from account - should run after modules that modify accounts in genesis
crisis.ModuleName, // runs the invariants at genesis - should run after other modules crisis.ModuleName, // runs the invariants at genesis - should run after other modules
genutil.ModuleName, // genutils must occur after staking so that pools are properly initialized with tokens from genesis accounts. genutil.ModuleName, // genutils must occur after staking so that pools are properly initialized with tokens from genesis accounts.

View File

@ -4,13 +4,8 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
) )
// BeginBlocker applies rewards to liquidity providers and delegators according to params // BeginBlocker updates interest rates and attempts liquidations
func BeginBlocker(ctx sdk.Context, k Keeper) { func BeginBlocker(ctx sdk.Context, k Keeper) {
k.ApplyDepositRewards(ctx)
if k.ShouldDistributeValidatorRewards(ctx, k.BondDenom(ctx)) {
k.ApplyDelegationRewards(ctx, k.BondDenom(ctx))
k.SetPreviousDelegationDistribution(ctx, ctx.BlockTime(), k.BondDenom(ctx))
}
k.ApplyInterestRateUpdates(ctx) k.ApplyInterestRateUpdates(ctx)
k.AttemptIndexLiquidations(ctx) k.AttemptIndexLiquidations(ctx)
k.SetPreviousBlockTime(ctx, ctx.BlockTime()) k.SetPreviousBlockTime(ctx, ctx.BlockTime())

View File

@ -8,9 +8,6 @@ import (
) )
const ( const (
BeginningOfMonth = keeper.BeginningOfMonth
MidMonth = keeper.MidMonth
PaymentHour = keeper.PaymentHour
AttributeKeyBlockHeight = types.AttributeKeyBlockHeight AttributeKeyBlockHeight = types.AttributeKeyBlockHeight
AttributeKeyClaimAmount = types.AttributeKeyClaimAmount AttributeKeyClaimAmount = types.AttributeKeyClaimAmount
AttributeKeyClaimHolder = types.AttributeKeyClaimHolder AttributeKeyClaimHolder = types.AttributeKeyClaimHolder
@ -56,22 +53,22 @@ var (
CalculateBorrowInterestFactor = keeper.CalculateBorrowInterestFactor CalculateBorrowInterestFactor = keeper.CalculateBorrowInterestFactor
CalculateSupplyInterestFactor = keeper.CalculateSupplyInterestFactor CalculateSupplyInterestFactor = keeper.CalculateSupplyInterestFactor
APYToSPY = keeper.APYToSPY APYToSPY = keeper.APYToSPY
ClaimKey = types.ClaimKey
DefaultGenesisState = types.DefaultGenesisState DefaultGenesisState = types.DefaultGenesisState
DefaultParams = types.DefaultParams DefaultParams = types.DefaultParams
DepositTypeIteratorKey = types.DepositTypeIteratorKey DepositTypeIteratorKey = types.DepositTypeIteratorKey
GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength
NewClaim = types.NewClaim NewBorrowLimit = types.NewBorrowLimit
NewDelegatorDistributionSchedule = types.NewDelegatorDistributionSchedule NewInterestRateModel = types.NewInterestRateModel
NewDeposit = types.NewDeposit NewDeposit = types.NewDeposit
NewDistributionSchedule = types.NewDistributionSchedule
NewGenesisState = types.NewGenesisState NewGenesisState = types.NewGenesisState
NewMsgClaimReward = types.NewMsgClaimReward NewMsgClaimReward = types.NewMsgClaimReward
NewMsgDeposit = types.NewMsgDeposit NewMsgDeposit = types.NewMsgDeposit
NewMsgWithdraw = types.NewMsgWithdraw NewMsgWithdraw = types.NewMsgWithdraw
NewMultiHARDHooks = types.NewMultiHARDHooks
NewMultiplier = types.NewMultiplier NewMultiplier = types.NewMultiplier
NewParams = types.NewParams NewParams = types.NewParams
NewPeriod = types.NewPeriod NewPeriod = types.NewPeriod
NewMoneyMarket = types.NewMoneyMarket
NewQueryAccountParams = types.NewQueryAccountParams NewQueryAccountParams = types.NewQueryAccountParams
NewQueryClaimParams = types.NewQueryClaimParams NewQueryClaimParams = types.NewQueryClaimParams
ParamKeyTable = types.ParamKeyTable ParamKeyTable = types.ParamKeyTable
@ -79,12 +76,7 @@ var (
// variable aliases // variable aliases
BorrowsKeyPrefix = types.BorrowsKeyPrefix BorrowsKeyPrefix = types.BorrowsKeyPrefix
ClaimsKeyPrefix = types.ClaimsKeyPrefix
DefaultActive = types.DefaultActive DefaultActive = types.DefaultActive
DefaultDelegatorSchedules = types.DefaultDelegatorSchedules
DefaultDistributionTimes = types.DefaultDistributionTimes
DefaultGovSchedules = types.DefaultGovSchedules
DefaultLPSchedules = types.DefaultLPSchedules
DefaultPreviousBlockTime = types.DefaultPreviousBlockTime DefaultPreviousBlockTime = types.DefaultPreviousBlockTime
ClaimTypesClaimQuery = types.ClaimTypesClaimQuery ClaimTypesClaimQuery = types.ClaimTypesClaimQuery
DepositsKeyPrefix = types.DepositsKeyPrefix DepositsKeyPrefix = types.DepositsKeyPrefix
@ -103,11 +95,8 @@ var (
ErrZeroClaim = types.ErrZeroClaim ErrZeroClaim = types.ErrZeroClaim
GovDenom = types.GovDenom GovDenom = types.GovDenom
KeyActive = types.KeyActive KeyActive = types.KeyActive
KeyDelegatorSchedule = types.KeyDelegatorSchedule
KeyLPSchedules = types.KeyLPSchedules
ModuleCdc = types.ModuleCdc ModuleCdc = types.ModuleCdc
PreviousBlockTimeKey = types.PreviousBlockTimeKey PreviousBlockTimeKey = types.PreviousBlockTimeKey
PreviousDelegationDistributionKey = types.PreviousDelegationDistributionKey
) )
type ( type (
@ -116,14 +105,8 @@ type (
Borrow = types.Borrow Borrow = types.Borrow
MoneyMarket = types.MoneyMarket MoneyMarket = types.MoneyMarket
MoneyMarkets = types.MoneyMarkets MoneyMarkets = types.MoneyMarkets
DelegatorDistributionSchedule = types.DelegatorDistributionSchedule
DelegatorDistributionSchedules = types.DelegatorDistributionSchedules
Deposit = types.Deposit Deposit = types.Deposit
ClaimType = types.ClaimType ClaimType = types.ClaimType
DistributionSchedule = types.DistributionSchedule
DistributionSchedules = types.DistributionSchedules
GenesisDistributionTime = types.GenesisDistributionTime
GenesisDistributionTimes = types.GenesisDistributionTimes
GenesisState = types.GenesisState GenesisState = types.GenesisState
MsgClaimReward = types.MsgClaimReward MsgClaimReward = types.MsgClaimReward
MsgDeposit = types.MsgDeposit MsgDeposit = types.MsgDeposit

View File

@ -22,7 +22,6 @@ const (
flagName = "name" flagName = "name"
flagDenom = "denom" flagDenom = "denom"
flagOwner = "owner" flagOwner = "owner"
flagClaimType = "claim-type"
) )
// GetQueryCmd returns the cli query commands for the module // GetQueryCmd returns the cli query commands for the module
@ -40,7 +39,6 @@ func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
queryModAccountsCmd(queryRoute, cdc), queryModAccountsCmd(queryRoute, cdc),
queryDepositsCmd(queryRoute, cdc), queryDepositsCmd(queryRoute, cdc),
queryTotalDepositedCmd(queryRoute, cdc), queryTotalDepositedCmd(queryRoute, cdc),
queryClaimsCmd(queryRoute, cdc),
queryBorrowsCmd(queryRoute, cdc), queryBorrowsCmd(queryRoute, cdc),
queryTotalBorrowedCmd(queryRoute, cdc), queryTotalBorrowedCmd(queryRoute, cdc),
)...) )...)
@ -177,76 +175,6 @@ func queryDepositsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
return cmd return cmd
} }
func queryClaimsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "claims",
Short: "query hard module claims with optional filters",
Long: strings.TrimSpace(`query for all hard module claims or a specific claim using flags:
Example:
$ kvcli q hard claims
$ kvcli q hard claims --owner kava1l0xsq2z7gqd7yly0g40y5836g0appumark77ny --claim-type lp --denom bnb
$ kvcli q hard claims --claim-type stake --denom ukava
$ kvcli q hard claims --denom btcb`,
),
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
var owner sdk.AccAddress
var claimType types.ClaimType
ownerBech := viper.GetString(flagOwner)
denom := viper.GetString(flagDenom)
claimTypeStr := viper.GetString(flagClaimType)
if len(ownerBech) != 0 {
claimOwner, err := sdk.AccAddressFromBech32(ownerBech)
if err != nil {
return err
}
owner = claimOwner
}
if len(claimTypeStr) != 0 {
if err := types.ClaimType(claimTypeStr).IsValid(); err != nil {
return err
}
claimType = types.ClaimType(claimTypeStr)
}
page := viper.GetInt(flags.FlagPage)
limit := viper.GetInt(flags.FlagLimit)
params := types.NewQueryClaimParams(page, limit, denom, owner, claimType)
bz, err := cdc.MarshalJSON(params)
if err != nil {
return err
}
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetClaims)
res, height, err := cliCtx.QueryWithData(route, bz)
if err != nil {
return err
}
cliCtx = cliCtx.WithHeight(height)
var claims []types.Claim
if err := cdc.UnmarshalJSON(res, &claims); err != nil {
return fmt.Errorf("failed to unmarshal claims: %w", err)
}
return cliCtx.PrintOutput(claims)
},
}
cmd.Flags().Int(flags.FlagPage, 1, "pagination page to query for")
cmd.Flags().Int(flags.FlagLimit, 100, "pagination limit (max 100)")
cmd.Flags().String(flagOwner, "", "(optional) filter for claims by owner address")
cmd.Flags().String(flagDenom, "", "(optional) filter for claims by denom")
cmd.Flags().String(flagClaimType, "", "(optional) filter for claims by type (lp or staking)")
return cmd
}
func queryBorrowsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { func queryBorrowsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "borrows", Use: "borrows",

View File

@ -32,7 +32,6 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command {
hardTxCmd.AddCommand(flags.PostCommands( hardTxCmd.AddCommand(flags.PostCommands(
getCmdDeposit(cdc), getCmdDeposit(cdc),
getCmdWithdraw(cdc), getCmdWithdraw(cdc),
getCmdClaimReward(cdc),
getCmdBorrow(cdc), getCmdBorrow(cdc),
getCmdLiquidate(cdc), getCmdLiquidate(cdc),
getCmdRepay(cdc), getCmdRepay(cdc),
@ -90,35 +89,6 @@ func getCmdWithdraw(cdc *codec.Codec) *cobra.Command {
} }
} }
func getCmdClaimReward(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "claim [receiver-addr] [deposit-denom] [deposit-type] [multiplier]",
Short: "claim HARD tokens to receiver address",
Long: strings.TrimSpace(
`sends accumulated HARD tokens from the hard module account to the receiver address.
Note that receiver address should match the sender address,
unless the sender is a validator-vesting account`),
Args: cobra.ExactArgs(4),
Example: fmt.Sprintf(
`%s tx %s claim kava1hgcfsuwc889wtdmt8pjy7qffua9dd2tralu64j bnb lp large --from <key>`, version.ClientName, types.ModuleName,
),
RunE: func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
receiver, err := sdk.AccAddressFromBech32(args[0])
if err != nil {
return err
}
msg := types.NewMsgClaimReward(cliCtx.GetFromAddress(), receiver, args[1], args[2], args[3])
if err := msg.ValidateBasic(); err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
}
func getCmdBorrow(cdc *codec.Codec) *cobra.Command { func getCmdBorrow(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "borrow [1000000000ukava]", Use: "borrow [1000000000ukava]",

View File

@ -21,12 +21,6 @@ func InitGenesis(ctx sdk.Context, k Keeper, supplyKeeper types.SupplyKeeper, gs
k.SetPreviousBlockTime(ctx, gs.PreviousBlockTime) k.SetPreviousBlockTime(ctx, gs.PreviousBlockTime)
} }
for _, pdt := range gs.PreviousDistributionTimes {
if !pdt.PreviousDistributionTime.Equal(DefaultPreviousBlockTime) {
k.SetPreviousDelegationDistribution(ctx, pdt.PreviousDistributionTime, pdt.Denom)
}
}
for _, mm := range gs.Params.MoneyMarkets { for _, mm := range gs.Params.MoneyMarkets {
k.SetMoneyMarket(ctx, mm.Denom, mm) k.SetMoneyMarket(ctx, mm.Denom, mm)
} }
@ -64,12 +58,5 @@ func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState {
if !found { if !found {
previousBlockTime = DefaultPreviousBlockTime previousBlockTime = DefaultPreviousBlockTime
} }
previousDistTimes := GenesisDistributionTimes{} return NewGenesisState(params, previousBlockTime)
for _, dds := range params.DelegatorDistributionSchedules {
previousDistTime, found := k.GetPreviousDelegatorDistribution(ctx, dds.DistributionSchedule.DepositDenom)
if found {
previousDistTimes = append(previousDistTimes, GenesisDistributionTime{PreviousDistributionTime: previousDistTime, Denom: dds.DistributionSchedule.DepositDenom})
}
}
return NewGenesisState(params, previousBlockTime, previousDistTimes)
} }

View File

@ -1,8 +1,6 @@
package hard package hard
import ( import (
"strings"
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"
@ -15,8 +13,6 @@ func NewHandler(k Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
ctx = ctx.WithEventManager(sdk.NewEventManager()) ctx = ctx.WithEventManager(sdk.NewEventManager())
switch msg := msg.(type) { switch msg := msg.(type) {
case types.MsgClaimReward:
return handleMsgClaimReward(ctx, k, msg)
case types.MsgDeposit: case types.MsgDeposit:
return handleMsgDeposit(ctx, k, msg) return handleMsgDeposit(ctx, k, msg)
case types.MsgWithdraw: case types.MsgWithdraw:
@ -33,24 +29,6 @@ func NewHandler(k Keeper) sdk.Handler {
} }
} }
func handleMsgClaimReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimReward) (*sdk.Result, error) {
err := k.ClaimReward(ctx, msg.Sender, msg.Receiver, msg.DepositDenom, types.ClaimType(strings.ToLower(msg.ClaimType)), types.MultiplierName(strings.ToLower(msg.MultiplierName)))
if err != nil {
return nil, err
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender.String()),
),
)
return &sdk.Result{
Events: ctx.EventManager().Events(),
}, nil
}
func handleMsgDeposit(ctx sdk.Context, k keeper.Keeper, msg types.MsgDeposit) (*sdk.Result, error) { func handleMsgDeposit(ctx sdk.Context, k keeper.Keeper, msg types.MsgDeposit) (*sdk.Result, error) {
err := k.Deposit(ctx, msg.Depositor, msg.Amount) err := k.Deposit(ctx, msg.Depositor, msg.Amount)
if err != nil { if err != nil {

View File

@ -28,7 +28,12 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins
return err return err
} }
// Sync any outstanding interest // Call incentive hook
existingBorrow, hasExistingBorrow := k.GetBorrow(ctx, borrower)
if hasExistingBorrow {
k.BeforeBorrowModified(ctx, existingBorrow)
}
k.SyncBorrowInterest(ctx, borrower) k.SyncBorrowInterest(ctx, borrower)
// Validate borrow amount within user and protocol limits // Validate borrow amount within user and protocol limits
@ -84,7 +89,6 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins
} else { } else {
amount = coins amount = coins
} }
// Construct the user's new/updated borrow with amount and interest factors // Construct the user's new/updated borrow with amount and interest factors
borrow := types.NewBorrow(borrower, amount, borrowInterestFactors) borrow := types.NewBorrow(borrower, amount, borrowInterestFactors)
@ -104,6 +108,12 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins
// it has already been included in the total borrowed coins by the BeginBlocker. // it has already been included in the total borrowed coins by the BeginBlocker.
k.IncrementBorrowedCoins(ctx, coins) k.IncrementBorrowedCoins(ctx, coins)
if !hasExistingBorrow {
k.AfterBorrowCreated(ctx, borrow)
} else {
k.AfterBorrowModified(ctx, borrow)
}
ctx.EventManager().EmitEvent( ctx.EventManager().EmitEvent(
sdk.NewEvent( sdk.NewEvent(
types.EventTypeHardBorrow, types.EventTypeHardBorrow,

View File

@ -262,19 +262,6 @@ func (suite *KeeperTestSuite) TestBorrow() {
// hard module genesis state // hard module genesis state
hardGS := types.NewGenesisState(types.NewParams( hardGS := types.NewGenesisState(types.NewParams(
true, true,
types.DistributionSchedules{
types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "btcb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "busd", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "xyz", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
},
types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
time.Hour*24,
),
},
types.MoneyMarkets{ types.MoneyMarkets{
types.NewMoneyMarket("usdx", types.NewBorrowLimit(true, tc.args.usdxBorrowLimit, sdk.MustNewDecFromStr("1")), "usdx:usd", sdk.NewInt(USDX_CF), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), types.NewMoneyMarket("usdx", types.NewBorrowLimit(true, tc.args.usdxBorrowLimit, sdk.MustNewDecFromStr("1")), "usdx:usd", sdk.NewInt(USDX_CF), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
types.NewMoneyMarket("busd", types.NewBorrowLimit(false, sdk.NewDec(100000000*BUSD_CF), sdk.MustNewDecFromStr("1")), "busd:usd", sdk.NewInt(BUSD_CF), sdk.NewInt(BUSD_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), types.NewMoneyMarket("busd", types.NewBorrowLimit(false, sdk.NewDec(100000000*BUSD_CF), sdk.MustNewDecFromStr("1")), "busd:usd", sdk.NewInt(BUSD_CF), sdk.NewInt(BUSD_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
@ -284,7 +271,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
types.NewMoneyMarket("xyz", types.NewBorrowLimit(false, sdk.NewDec(1), tc.args.loanToValueBNB), "xyz:usd", sdk.NewInt(1), sdk.NewInt(1), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), types.NewMoneyMarket("xyz", types.NewBorrowLimit(false, sdk.NewDec(1), tc.args.loanToValueBNB), "xyz:usd", sdk.NewInt(1), sdk.NewInt(1), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
}, },
0, // LTV counter 0, // LTV counter
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) ), types.DefaultPreviousBlockTime)
// Pricefeed module genesis state // Pricefeed module genesis state
pricefeedGS := pricefeed.GenesisState{ pricefeedGS := pricefeed.GenesisState{

View File

@ -1,149 +0,0 @@
package keeper
import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/kava-labs/kava/x/hard/types"
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
)
const (
// BeginningOfMonth hard rewards that are claimed after the 15th at 14:00UTC of the month always vest on the first of the month
BeginningOfMonth = 1
// MidMonth hard rewards that are claimed before the 15th at 14:00UTC of the month always vest on the 15 of the month
MidMonth = 15
// PaymentHour hard rewards always vest at 14:00UTC
PaymentHour = 14
)
// ClaimReward sends the reward amount to the reward owner and deletes the claim from the store
func (k Keeper) ClaimReward(ctx sdk.Context, claimHolder sdk.AccAddress, receiver sdk.AccAddress, depositDenom string, claimType types.ClaimType, multiplier types.MultiplierName) error {
claim, found := k.GetClaim(ctx, claimHolder, depositDenom, claimType)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "no %s %s claim found for %s", depositDenom, claimType, claimHolder)
}
err := k.validateSenderReceiver(ctx, claimHolder, receiver)
if err != nil {
return err
}
switch claimType {
case types.LP:
err = k.claimLPReward(ctx, claim, receiver, multiplier)
case types.Stake:
err = k.claimDelegatorReward(ctx, claim, receiver, multiplier)
default:
return sdkerrors.Wrap(types.ErrInvalidClaimType, string(claimType))
}
if err != nil {
return err
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaimHardReward,
sdk.NewAttribute(sdk.AttributeKeyAmount, claim.Amount.String()),
sdk.NewAttribute(types.AttributeKeyClaimHolder, claimHolder.String()),
sdk.NewAttribute(types.AttributeKeyDepositDenom, depositDenom),
sdk.NewAttribute(types.AttributeKeyClaimType, string(claimType)),
sdk.NewAttribute(types.AttributeKeyClaimMultiplier, string(multiplier)),
),
)
k.DeleteClaim(ctx, claim)
return nil
}
// GetPeriodLength returns the length of the period based on the input blocktime and multiplier
// note that pay dates are always the 1st or 15th of the month at 14:00UTC.
func (k Keeper) GetPeriodLength(ctx sdk.Context, multiplier types.Multiplier) (int64, error) {
if multiplier.MonthsLockup == 0 {
return 0, nil
}
switch multiplier.Name {
case types.Small, types.Medium, types.Large:
currentDay := ctx.BlockTime().Day()
payDay := BeginningOfMonth
monthOffset := int64(1)
if currentDay < MidMonth || (currentDay == MidMonth && ctx.BlockTime().Hour() < PaymentHour) {
payDay = MidMonth
monthOffset = int64(0)
}
periodEndDate := time.Date(ctx.BlockTime().Year(), ctx.BlockTime().Month(), payDay, PaymentHour, 0, 0, 0, time.UTC).AddDate(0, int(multiplier.MonthsLockup+monthOffset), 0)
return periodEndDate.Unix() - ctx.BlockTime().Unix(), nil
}
return 0, types.ErrInvalidMultiplier
}
func (k Keeper) claimLPReward(ctx sdk.Context, claim types.Claim, receiver sdk.AccAddress, multiplierName types.MultiplierName) error {
lps, found := k.GetLPSchedule(ctx, claim.DepositDenom)
if !found {
return sdkerrors.Wrapf(types.ErrLPScheduleNotFound, claim.DepositDenom)
}
multiplier, found := lps.GetMultiplier(multiplierName)
if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName))
}
if ctx.BlockTime().After(lps.ClaimEnd) {
return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), lps.ClaimEnd)
}
rewardAmount := sdk.NewDecFromInt(claim.Amount.Amount).Mul(multiplier.Factor).RoundInt()
if rewardAmount.IsZero() {
return types.ErrZeroClaim
}
rewardCoin := sdk.NewCoin(claim.Amount.Denom, rewardAmount)
length, err := k.GetPeriodLength(ctx, multiplier)
if err != nil {
return err
}
return k.SendTimeLockedCoinsToAccount(ctx, types.LPAccount, receiver, sdk.NewCoins(rewardCoin), length)
}
func (k Keeper) claimDelegatorReward(ctx sdk.Context, claim types.Claim, receiver sdk.AccAddress, multiplierName types.MultiplierName) error {
dss, found := k.GetDelegatorSchedule(ctx, claim.DepositDenom)
if !found {
return sdkerrors.Wrapf(types.ErrLPScheduleNotFound, claim.DepositDenom)
}
multiplier, found := dss.DistributionSchedule.GetMultiplier(multiplierName)
if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName))
}
if ctx.BlockTime().After(dss.DistributionSchedule.ClaimEnd) {
return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), dss.DistributionSchedule.ClaimEnd)
}
rewardAmount := sdk.NewDecFromInt(claim.Amount.Amount).Mul(multiplier.Factor).RoundInt()
if rewardAmount.IsZero() {
return types.ErrZeroClaim
}
rewardCoin := sdk.NewCoin(claim.Amount.Denom, rewardAmount)
length, err := k.GetPeriodLength(ctx, multiplier)
if err != nil {
return err
}
return k.SendTimeLockedCoinsToAccount(ctx, types.DelegatorAccount, receiver, sdk.NewCoins(rewardCoin), length)
}
func (k Keeper) validateSenderReceiver(ctx sdk.Context, sender, receiver sdk.AccAddress) error {
senderAcc := k.accountKeeper.GetAccount(ctx, sender)
if senderAcc == nil {
return sdkerrors.Wrapf(types.ErrAccountNotFound, sender.String())
}
switch senderAcc.(type) {
case *validatorvesting.ValidatorVestingAccount:
if sender.Equals(receiver) {
return sdkerrors.Wrapf(types.ErrInvalidAccountType, "%T", senderAcc)
}
default:
if !sender.Equals(receiver) {
return sdkerrors.Wrapf(types.ErrInvalidReceiver, "%s", sender)
}
}
return nil
}

View File

@ -1,475 +0,0 @@
package keeper_test
import (
"strings"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/hard/types"
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
)
func (suite *KeeperTestSuite) TestClaim() {
type args struct {
claimOwner sdk.AccAddress
receiver sdk.AccAddress
denom string
claimType types.ClaimType
multiplier types.MultiplierName
blockTime time.Time
createClaim bool
claimAmount sdk.Coin
validatorVesting bool
expectedAccountBalance sdk.Coins
expectedModAccountBalance sdk.Coins
expectedVestingAccount bool
expectedVestingLength int64
}
type errArgs struct {
expectPass bool
contains string
}
type claimTest struct {
name string
args args
errArgs errArgs
}
testCases := []claimTest{
{
"valid liquid claim",
args{
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
receiver: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
denom: "bnb",
claimType: types.LP,
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
createClaim: true,
claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)),
validatorVesting: false,
expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(33)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(967))),
expectedVestingAccount: false,
expectedVestingLength: 0,
multiplier: types.Small,
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"valid liquid delegator claim",
args{
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
receiver: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
denom: "bnb",
claimType: types.Stake,
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
createClaim: true,
claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)),
validatorVesting: false,
expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(33)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(967))),
expectedVestingAccount: false,
expectedVestingLength: 0,
multiplier: types.Small,
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"valid medium vesting claim",
args{
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
receiver: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
denom: "bnb",
claimType: types.LP,
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
createClaim: true,
claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)),
validatorVesting: false,
expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(50)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(950))),
expectedVestingAccount: true,
expectedVestingLength: 16848000,
multiplier: types.Medium,
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"valid large vesting claim",
args{
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
receiver: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
denom: "bnb",
claimType: types.LP,
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
createClaim: true,
claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)),
validatorVesting: false,
expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(100)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(900))),
expectedVestingAccount: true,
expectedVestingLength: 64281600,
multiplier: types.Large,
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"valid validator vesting",
args{
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
receiver: sdk.AccAddress(crypto.AddressHash([]byte("test2"))),
denom: "bnb",
claimType: types.LP,
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
createClaim: true,
claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)),
validatorVesting: true,
expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(100)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(900))),
expectedVestingAccount: true,
expectedVestingLength: 64281600,
multiplier: types.Large,
},
errArgs{
expectPass: true,
contains: "",
},
},
{
"invalid validator vesting",
args{
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
receiver: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
denom: "bnb",
claimType: types.LP,
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
createClaim: true,
claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)),
validatorVesting: true,
expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(100)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(900))),
expectedVestingAccount: true,
expectedVestingLength: 64281600,
multiplier: types.Large,
},
errArgs{
expectPass: false,
contains: "receiver account type not supported",
},
},
{
"claim not found",
args{
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
receiver: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
denom: "bnb",
claimType: types.LP,
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
createClaim: false,
claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)),
validatorVesting: false,
expectedAccountBalance: sdk.Coins{},
expectedModAccountBalance: sdk.Coins{},
expectedVestingAccount: false,
expectedVestingLength: 0,
multiplier: types.Small,
},
errArgs{
expectPass: false,
contains: "claim not found",
},
},
{
"claim expired",
args{
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
receiver: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
denom: "bnb",
claimType: types.LP,
blockTime: time.Date(2022, 11, 1, 14, 0, 0, 0, time.UTC),
createClaim: true,
claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)),
validatorVesting: false,
expectedAccountBalance: sdk.Coins{},
expectedModAccountBalance: sdk.Coins{},
expectedVestingAccount: false,
expectedVestingLength: 0,
multiplier: types.Small,
},
errArgs{
expectPass: false,
contains: "claim period expired",
},
},
{
"different receiver address",
args{
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
receiver: sdk.AccAddress(crypto.AddressHash([]byte("test2"))),
denom: "bnb",
claimType: types.LP,
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
createClaim: true,
claimAmount: sdk.NewCoin("hard", sdk.NewInt(100)),
validatorVesting: false,
expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(100)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(900))),
expectedVestingAccount: true,
expectedVestingLength: 64281600,
multiplier: types.Large,
},
errArgs{
expectPass: false,
contains: "receiver account must match sender account",
},
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
// create new app with one funded account
// Initialize test app and set context
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tc.args.blockTime})
authGS := app.NewAuthGenState(
[]sdk.AccAddress{tc.args.claimOwner, tc.args.receiver},
[]sdk.Coins{
sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
})
loanToValue := sdk.MustNewDecFromStr("0.6")
hardGS := types.NewGenesisState(types.NewParams(
true,
types.DistributionSchedules{
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 24, sdk.OneDec())}),
},
types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 24, sdk.OneDec())}),
time.Hour*24,
),
},
types.MoneyMarkets{
types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
},
0, // LTV counter
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(hardGS)})
if tc.args.validatorVesting {
ak := tApp.GetAccountKeeper()
acc := ak.GetAccount(ctx, tc.args.claimOwner)
bacc := auth.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence())
bva, err := vesting.NewBaseVestingAccount(
bacc,
sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(20))), time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC).Unix()+100)
suite.Require().NoError(err)
vva := validatorvesting.NewValidatorVestingAccountRaw(
bva,
time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC).Unix(),
vesting.Periods{
vesting.Period{Length: 25, Amount: cs(c("bnb", 5))},
vesting.Period{Length: 25, Amount: cs(c("bnb", 5))},
vesting.Period{Length: 25, Amount: cs(c("bnb", 5))},
vesting.Period{Length: 25, Amount: cs(c("bnb", 5))}},
sdk.ConsAddress(crypto.AddressHash([]byte("test"))),
sdk.AccAddress{},
95,
)
err = vva.Validate()
suite.Require().NoError(err)
ak.SetAccount(ctx, vva)
}
supplyKeeper := tApp.GetSupplyKeeper()
supplyKeeper.MintCoins(ctx, types.LPAccount, sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(1000))))
supplyKeeper.MintCoins(ctx, types.DelegatorAccount, sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(1000))))
keeper := tApp.GetHardKeeper()
suite.app = tApp
suite.ctx = ctx
suite.keeper = keeper
if tc.args.createClaim {
claim := types.NewClaim(tc.args.claimOwner, tc.args.denom, tc.args.claimAmount, tc.args.claimType)
suite.Require().NotPanics(func() { suite.keeper.SetClaim(suite.ctx, claim) })
}
err := suite.keeper.ClaimReward(suite.ctx, tc.args.claimOwner, tc.args.receiver, tc.args.denom, tc.args.claimType, tc.args.multiplier)
if tc.errArgs.expectPass {
suite.Require().NoError(err)
acc := suite.getAccount(tc.args.receiver)
suite.Require().Equal(tc.args.expectedAccountBalance, acc.GetCoins())
mAcc := suite.getModuleAccount(types.LPAccount)
if tc.args.claimType == types.Stake {
mAcc = suite.getModuleAccount(types.DelegatorAccount)
}
suite.Require().Equal(tc.args.expectedModAccountBalance, mAcc.GetCoins())
vacc, ok := acc.(*vesting.PeriodicVestingAccount)
if tc.args.expectedVestingAccount {
suite.Require().True(ok)
suite.Require().Equal(tc.args.expectedVestingLength, vacc.VestingPeriods[0].Length)
} else {
suite.Require().False(ok)
}
_, f := suite.keeper.GetClaim(ctx, tc.args.claimOwner, tc.args.denom, tc.args.claimType)
suite.Require().False(f)
} else {
suite.Require().Error(err)
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
}
})
}
}
func (suite *KeeperTestSuite) TestGetPeriodLength() {
type args struct {
blockTime time.Time
multiplier types.Multiplier
expectedLength int64
}
type errArgs struct {
expectPass bool
contains string
}
type periodTest struct {
name string
args args
errArgs errArgs
}
testCases := []periodTest{
{
name: "first half of month",
args: args{
blockTime: time.Date(2020, 11, 2, 15, 0, 0, 0, time.UTC),
multiplier: types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.333333")),
expectedLength: time.Date(2021, 5, 15, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2020, 11, 2, 15, 0, 0, 0, time.UTC).Unix(),
},
errArgs: errArgs{
expectPass: true,
contains: "",
},
},
{
name: "first half of month long lockup",
args: args{
blockTime: time.Date(2020, 11, 2, 15, 0, 0, 0, time.UTC),
multiplier: types.NewMultiplier(types.Medium, 24, sdk.MustNewDecFromStr("0.333333")),
expectedLength: time.Date(2022, 11, 15, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2020, 11, 2, 15, 0, 0, 0, time.UTC).Unix(),
},
errArgs: errArgs{
expectPass: true,
contains: "",
},
},
{
name: "second half of month",
args: args{
blockTime: time.Date(2020, 12, 31, 15, 0, 0, 0, time.UTC),
multiplier: types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.333333")),
expectedLength: time.Date(2021, 7, 1, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2020, 12, 31, 15, 0, 0, 0, time.UTC).Unix(),
},
errArgs: errArgs{
expectPass: true,
contains: "",
},
},
{
name: "second half of month long lockup",
args: args{
blockTime: time.Date(2020, 12, 31, 15, 0, 0, 0, time.UTC),
multiplier: types.NewMultiplier(types.Large, 24, sdk.MustNewDecFromStr("0.333333")),
expectedLength: time.Date(2023, 1, 1, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2020, 12, 31, 15, 0, 0, 0, time.UTC).Unix(),
},
errArgs: errArgs{
expectPass: true,
contains: "",
},
},
{
name: "end of feb",
args: args{
blockTime: time.Date(2021, 2, 28, 15, 0, 0, 0, time.UTC),
multiplier: types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.333333")),
expectedLength: time.Date(2021, 9, 1, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2021, 2, 28, 15, 0, 0, 0, time.UTC).Unix(),
},
errArgs: errArgs{
expectPass: true,
contains: "",
},
},
{
name: "leap year",
args: args{
blockTime: time.Date(2020, 2, 29, 15, 0, 0, 0, time.UTC),
multiplier: types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.333333")),
expectedLength: time.Date(2020, 9, 1, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2020, 2, 29, 15, 0, 0, 0, time.UTC).Unix(),
},
errArgs: errArgs{
expectPass: true,
contains: "",
},
},
{
name: "leap year long lockup",
args: args{
blockTime: time.Date(2020, 2, 29, 15, 0, 0, 0, time.UTC),
multiplier: types.NewMultiplier(types.Large, 24, sdk.MustNewDecFromStr("1")),
expectedLength: time.Date(2022, 3, 1, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2020, 2, 29, 15, 0, 0, 0, time.UTC).Unix(),
},
errArgs: errArgs{
expectPass: true,
contains: "",
},
},
{
name: "exactly half of month",
args: args{
blockTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
multiplier: types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.333333")),
expectedLength: time.Date(2021, 7, 1, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC).Unix(),
},
errArgs: errArgs{
expectPass: true,
contains: "",
},
},
{
name: "just before half of month",
args: args{
blockTime: time.Date(2020, 12, 15, 13, 59, 59, 0, time.UTC),
multiplier: types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.333333")),
expectedLength: time.Date(2021, 6, 15, 14, 0, 0, 0, time.UTC).Unix() - time.Date(2020, 12, 15, 13, 59, 59, 0, time.UTC).Unix(),
},
errArgs: errArgs{
expectPass: true,
contains: "",
},
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
ctx := suite.ctx.WithBlockTime(tc.args.blockTime)
length, err := suite.keeper.GetPeriodLength(ctx, tc.args.multiplier)
if tc.errArgs.expectPass {
suite.Require().NoError(err)
suite.Require().Equal(tc.args.expectedLength, length)
} else {
suite.Require().Error(err)
}
})
}
}

View File

@ -29,6 +29,12 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coi
return err return err
} }
// Call incentive hook
existingDeposit, hasExistingDeposit := k.GetDeposit(ctx, depositor)
if hasExistingDeposit {
k.BeforeDepositModified(ctx, existingDeposit)
}
// Sync any outstanding interest // Sync any outstanding interest
k.SyncBorrowInterest(ctx, depositor) k.SyncBorrowInterest(ctx, depositor)
k.SyncSupplyInterest(ctx, depositor) k.SyncSupplyInterest(ctx, depositor)
@ -98,10 +104,12 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coi
} }
k.UpdateDepositAndLtvIndex(ctx, deposit, newLtv, prevLtv) k.UpdateDepositAndLtvIndex(ctx, deposit, newLtv, prevLtv)
// Update total supplied amount by newly supplied coins. Don't add user's pending interest as
// it has already been included in the total supplied coins by the BeginBlocker.
k.IncrementSuppliedCoins(ctx, coins) k.IncrementSuppliedCoins(ctx, coins)
if !foundDeposit { // User's first deposit
k.AfterDepositCreated(ctx, deposit)
} else {
k.AfterDepositModified(ctx, deposit)
}
ctx.EventManager().EmitEvent( ctx.EventManager().EmitEvent(
sdk.NewEvent( sdk.NewEvent(
@ -116,16 +124,10 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coi
// ValidateDeposit validates a deposit // ValidateDeposit validates a deposit
func (k Keeper) ValidateDeposit(ctx sdk.Context, coins sdk.Coins) error { func (k Keeper) ValidateDeposit(ctx sdk.Context, coins sdk.Coins) error {
params := k.GetParams(ctx)
for _, depCoin := range coins { for _, depCoin := range coins {
found := false _, foundMm := k.GetMoneyMarket(ctx, depCoin.Denom)
for _, lps := range params.LiquidityProviderSchedules { if !foundMm {
if lps.DepositDenom == depCoin.Denom { return sdkerrors.Wrapf(types.ErrInvalidDepositDenom, "money market denom %s not found", depCoin.Denom)
found = true
}
}
if !found {
return sdkerrors.Wrapf(types.ErrInvalidDepositDenom, "liquidity provider denom %s not found", depCoin.Denom)
} }
} }

View File

@ -68,7 +68,7 @@ func (suite *KeeperTestSuite) TestDeposit() {
"invalid deposit denom", "invalid deposit denom",
args{ args{
depositor: sdk.AccAddress(crypto.AddressHash([]byte("test"))), depositor: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
amount: sdk.NewCoins(sdk.NewCoin("btcb", sdk.NewInt(100))), amount: sdk.NewCoins(sdk.NewCoin("fake", sdk.NewInt(100))),
numberDeposits: 1, numberDeposits: 1,
expectedAccountBalance: sdk.Coins{}, expectedAccountBalance: sdk.Coins{},
expectedModAccountBalance: sdk.Coins{}, expectedModAccountBalance: sdk.Coins{},
@ -106,14 +106,6 @@ func (suite *KeeperTestSuite) TestDeposit() {
loanToValue, _ := sdk.NewDecFromStr("0.6") loanToValue, _ := sdk.NewDecFromStr("0.6")
hardGS := types.NewGenesisState(types.NewParams( hardGS := types.NewGenesisState(types.NewParams(
true, true,
types.DistributionSchedules{
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
},
types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
time.Hour*24,
),
},
types.MoneyMarkets{ types.MoneyMarkets{
types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
@ -121,7 +113,7 @@ func (suite *KeeperTestSuite) TestDeposit() {
types.NewMoneyMarket("btcb", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "btcb:usd", sdk.NewInt(1000000), sdk.NewInt(BTCB_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), types.NewMoneyMarket("btcb", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "btcb:usd", sdk.NewInt(1000000), sdk.NewInt(BTCB_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
}, },
0, // LTV counter 0, // LTV counter
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) ), types.DefaultPreviousBlockTime)
// Pricefeed module genesis state // Pricefeed module genesis state
pricefeedGS := pricefeed.GenesisState{ pricefeedGS := pricefeed.GenesisState{

52
x/hard/keeper/hooks.go Normal file
View File

@ -0,0 +1,52 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/hard/types"
)
// Implements StakingHooks interface
var _ types.HARDHooks = Keeper{}
// AfterDepositCreated - call hook if registered
func (k Keeper) AfterDepositCreated(ctx sdk.Context, deposit types.Deposit) {
if k.hooks != nil {
k.hooks.AfterDepositCreated(ctx, deposit)
}
}
// BeforeDepositModified - call hook if registered
func (k Keeper) BeforeDepositModified(ctx sdk.Context, deposit types.Deposit) {
if k.hooks != nil {
k.hooks.BeforeDepositModified(ctx, deposit)
}
}
// AfterDepositModified - call hook if registered
func (k Keeper) AfterDepositModified(ctx sdk.Context, deposit types.Deposit) {
if k.hooks != nil {
k.hooks.AfterDepositModified(ctx, deposit)
}
}
// AfterBorrowCreated - call hook if registered
func (k Keeper) AfterBorrowCreated(ctx sdk.Context, borrow types.Borrow) {
if k.hooks != nil {
k.hooks.AfterBorrowCreated(ctx, borrow)
}
}
// BeforeBorrowModified - call hook if registered
func (k Keeper) BeforeBorrowModified(ctx sdk.Context, borrow types.Borrow) {
if k.hooks != nil {
k.hooks.BeforeBorrowModified(ctx, borrow)
}
}
// AfterBorrowModified - call hook if registered
func (k Keeper) AfterBorrowModified(ctx sdk.Context, borrow types.Borrow) {
if k.hooks != nil {
k.hooks.AfterBorrowModified(ctx, borrow)
}
}

View File

@ -135,7 +135,6 @@ func (k Keeper) AccrueInterest(ctx sdk.Context, denom string) error {
k.IncrementBorrowedCoins(ctx, totalBorrowInterestAccumulated) k.IncrementBorrowedCoins(ctx, totalBorrowInterestAccumulated)
k.IncrementSuppliedCoins(ctx, sdk.NewCoins(sdk.NewCoin(denom, supplyInterestNew))) k.IncrementSuppliedCoins(ctx, sdk.NewCoins(sdk.NewCoin(denom, supplyInterestNew)))
k.SetTotalReserves(ctx, denom, reservesPrior.Add(sdk.NewCoin(mm.Denom, reservesNew))) k.SetTotalReserves(ctx, denom, reservesPrior.Add(sdk.NewCoin(mm.Denom, reservesNew)))
k.SetSupplyInterestFactor(ctx, denom, supplyInterestFactorNew)
k.SetPreviousAccrualTime(ctx, denom, ctx.BlockTime()) k.SetPreviousAccrualTime(ctx, denom, ctx.BlockTime())
return nil return nil

View File

@ -712,14 +712,6 @@ func (suite *KeeperTestSuite) TestBorrowInterest() {
// Hard module genesis state // Hard module genesis state
hardGS := types.NewGenesisState(types.NewParams( hardGS := types.NewGenesisState(types.NewParams(
true, true,
types.DistributionSchedules{
types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
},
types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
time.Hour*24,
),
},
types.MoneyMarkets{ types.MoneyMarkets{
types.NewMoneyMarket("ukava", types.NewMoneyMarket("ukava",
types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.8")), // Borrow Limit types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.8")), // Borrow Limit
@ -731,7 +723,7 @@ func (suite *KeeperTestSuite) TestBorrowInterest() {
sdk.ZeroDec()), // Keeper Reward Percentage sdk.ZeroDec()), // Keeper Reward Percentage
}, },
0, // LTV counter 0, // LTV counter
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) ), types.DefaultPreviousBlockTime)
// Pricefeed module genesis state // Pricefeed module genesis state
pricefeedGS := pricefeed.GenesisState{ pricefeedGS := pricefeed.GenesisState{
@ -1127,15 +1119,6 @@ func (suite *KeeperTestSuite) TestSupplyInterest() {
// Hard module genesis state // Hard module genesis state
hardGS := types.NewGenesisState(types.NewParams( hardGS := types.NewGenesisState(types.NewParams(
true, true,
types.DistributionSchedules{
types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
},
types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
time.Hour*24,
),
},
types.MoneyMarkets{ types.MoneyMarkets{
types.NewMoneyMarket("ukava", types.NewMoneyMarket("ukava",
types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.8")), // Borrow Limit types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.8")), // Borrow Limit
@ -1155,7 +1138,7 @@ func (suite *KeeperTestSuite) TestSupplyInterest() {
sdk.ZeroDec()), // Keeper Reward Percentage sdk.ZeroDec()), // Keeper Reward Percentage
}, },
0, // LTV counter 0, // LTV counter
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) ), types.DefaultPreviousBlockTime)
// Pricefeed module genesis state // Pricefeed module genesis state
pricefeedGS := pricefeed.GenesisState{ pricefeedGS := pricefeed.GenesisState{

View File

@ -21,6 +21,7 @@ type Keeper struct {
stakingKeeper types.StakingKeeper stakingKeeper types.StakingKeeper
pricefeedKeeper types.PricefeedKeeper pricefeedKeeper types.PricefeedKeeper
auctionKeeper types.AuctionKeeper auctionKeeper types.AuctionKeeper
hooks types.HARDHooks
} }
// NewKeeper creates a new keeper // NewKeeper creates a new keeper
@ -40,9 +41,19 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace,
stakingKeeper: stk, stakingKeeper: stk,
pricefeedKeeper: pfk, pricefeedKeeper: pfk,
auctionKeeper: auk, auctionKeeper: auk,
hooks: nil,
} }
} }
// SetHooks sets the cdp keeper hooks
func (k *Keeper) SetHooks(hooks types.HARDHooks) *Keeper {
if k.hooks != nil {
panic("cannot set validator hooks twice")
}
k.hooks = hooks
return k
}
// GetPreviousBlockTime get the blocktime for the previous block // GetPreviousBlockTime get the blocktime for the previous block
func (k Keeper) GetPreviousBlockTime(ctx sdk.Context) (blockTime time.Time, found bool) { func (k Keeper) GetPreviousBlockTime(ctx sdk.Context) (blockTime time.Time, found bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousBlockTimeKey) store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousBlockTimeKey)
@ -60,23 +71,6 @@ func (k Keeper) SetPreviousBlockTime(ctx sdk.Context, blockTime time.Time) {
store.Set([]byte{}, k.cdc.MustMarshalBinaryBare(blockTime)) store.Set([]byte{}, k.cdc.MustMarshalBinaryBare(blockTime))
} }
// GetPreviousDelegatorDistribution get the time of the previous delegator distribution
func (k Keeper) GetPreviousDelegatorDistribution(ctx sdk.Context, denom string) (distTime time.Time, found bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousDelegationDistributionKey)
bz := store.Get([]byte(denom))
if bz == nil {
return time.Time{}, false
}
k.cdc.MustUnmarshalBinaryBare(bz, &distTime)
return distTime, true
}
// SetPreviousDelegationDistribution set the time of the previous delegator distribution
func (k Keeper) SetPreviousDelegationDistribution(ctx sdk.Context, distTime time.Time, denom string) {
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousDelegationDistributionKey)
store.Set([]byte(denom), k.cdc.MustMarshalBinaryBare(distTime))
}
// GetDeposit returns a deposit from the store for a particular depositor address, deposit denom // GetDeposit returns a deposit from the store for a particular depositor address, deposit denom
func (k Keeper) GetDeposit(ctx sdk.Context, depositor sdk.AccAddress) (types.Deposit, bool) { func (k Keeper) GetDeposit(ctx sdk.Context, depositor sdk.AccAddress) (types.Deposit, bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.DepositsKeyPrefix) store := prefix.NewStore(ctx.KVStore(k.key), types.DepositsKeyPrefix)
@ -116,59 +110,6 @@ func (k Keeper) IterateDeposits(ctx sdk.Context, cb func(deposit types.Deposit)
} }
} }
// GetClaim returns a claim from the store for a particular claim owner, deposit denom, and claim type
func (k Keeper) GetClaim(ctx sdk.Context, owner sdk.AccAddress, depositDenom string, claimType types.ClaimType) (types.Claim, bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimsKeyPrefix)
bz := store.Get(types.ClaimKey(claimType, depositDenom, owner))
if bz == nil {
return types.Claim{}, false
}
var claim types.Claim
k.cdc.MustUnmarshalBinaryBare(bz, &claim)
return claim, true
}
// SetClaim stores the input claim in the store, prefixed by the deposit type, deposit denom, and owner address, in that order
func (k Keeper) SetClaim(ctx sdk.Context, claim types.Claim) {
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimsKeyPrefix)
bz := k.cdc.MustMarshalBinaryBare(claim)
store.Set(types.ClaimKey(claim.Type, claim.DepositDenom, claim.Owner), bz)
}
// DeleteClaim deletes a claim from the store
func (k Keeper) DeleteClaim(ctx sdk.Context, claim types.Claim) {
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimsKeyPrefix)
store.Delete(types.ClaimKey(claim.Type, claim.DepositDenom, claim.Owner))
}
// IterateClaims iterates over all claim objects in the store and performs a callback function
func (k Keeper) IterateClaims(ctx sdk.Context, cb func(claim types.Claim) (stop bool)) {
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimsKeyPrefix)
iterator := sdk.KVStorePrefixIterator(store, []byte{})
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var claim types.Claim
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &claim)
if cb(claim) {
break
}
}
}
// IterateClaimsByTypeAndDenom iterates over all claim objects in the store with the matching claim type and deposit denom and performs a callback function
func (k Keeper) IterateClaimsByTypeAndDenom(ctx sdk.Context, claimType types.ClaimType, depositDenom string, cb func(claim types.Claim) (stop bool)) {
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimsKeyPrefix)
iterator := sdk.KVStorePrefixIterator(store, types.ClaimTypeIteratorKey(claimType, depositDenom))
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var claim types.Claim
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &claim)
if cb(claim) {
break
}
}
}
// GetDepositsByUser gets all deposits for an individual user // GetDepositsByUser gets all deposits for an individual user
func (k Keeper) GetDepositsByUser(ctx sdk.Context, user sdk.AccAddress) []types.Deposit { func (k Keeper) GetDepositsByUser(ctx sdk.Context, user sdk.AccAddress) []types.Deposit {
var deposits []types.Deposit var deposits []types.Deposit

View File

@ -60,21 +60,6 @@ func (suite *KeeperTestSuite) TestGetSetPreviousBlockTime() {
suite.Equal(now, pbt) suite.Equal(now, pbt)
} }
func (suite *KeeperTestSuite) TestGetSetPreviousDelegatorDistribution() {
now := tmtime.Now()
_, f := suite.keeper.GetPreviousDelegatorDistribution(suite.ctx, suite.keeper.BondDenom(suite.ctx))
suite.Require().False(f)
suite.NotPanics(func() {
suite.keeper.SetPreviousDelegationDistribution(suite.ctx, now, suite.keeper.BondDenom(suite.ctx))
})
pdt, f := suite.keeper.GetPreviousDelegatorDistribution(suite.ctx, suite.keeper.BondDenom(suite.ctx))
suite.True(f)
suite.Equal(now, pdt)
}
func (suite *KeeperTestSuite) TestGetSetDeleteDeposit() { func (suite *KeeperTestSuite) TestGetSetDeleteDeposit() {
dep := types.NewDeposit(sdk.AccAddress("test"), sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(100))), dep := types.NewDeposit(sdk.AccAddress("test"), sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(100))),
types.SupplyInterestFactors{types.NewSupplyInterestFactor("", sdk.MustNewDecFromStr("0"))}) types.SupplyInterestFactors{types.NewSupplyInterestFactor("", sdk.MustNewDecFromStr("0"))})
@ -108,21 +93,6 @@ func (suite *KeeperTestSuite) TestIterateDeposits() {
suite.Require().Equal(5, len(deposits)) suite.Require().Equal(5, len(deposits))
} }
func (suite *KeeperTestSuite) TestGetSetDeleteClaim() {
claim := types.NewClaim(sdk.AccAddress("test"), "bnb", sdk.NewCoin("hard", sdk.NewInt(100)), "lp")
_, f := suite.keeper.GetClaim(suite.ctx, sdk.AccAddress("test"), "bnb", "lp")
suite.Require().False(f)
suite.Require().NotPanics(func() { suite.keeper.SetClaim(suite.ctx, claim) })
testClaim, f := suite.keeper.GetClaim(suite.ctx, sdk.AccAddress("test"), "bnb", "lp")
suite.Require().True(f)
suite.Require().Equal(claim, testClaim)
suite.Require().NotPanics(func() { suite.keeper.DeleteClaim(suite.ctx, claim) })
_, f = suite.keeper.GetClaim(suite.ctx, sdk.AccAddress("test"), "bnb", "lp")
suite.Require().False(f)
}
func (suite *KeeperTestSuite) TestGetSetDeleteInterestRateModel() { func (suite *KeeperTestSuite) TestGetSetDeleteInterestRateModel() {
denom := "test" denom := "test"
model := types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")) model := types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))

View File

@ -39,9 +39,6 @@ func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress,
return err return err
} }
k.SyncBorrowInterest(ctx, borrower)
k.SyncSupplyInterest(ctx, borrower)
deposit, found := k.GetDeposit(ctx, borrower) deposit, found := k.GetDeposit(ctx, borrower)
if !found { if !found {
return types.ErrDepositNotFound return types.ErrDepositNotFound
@ -52,6 +49,23 @@ func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress,
return types.ErrBorrowNotFound return types.ErrBorrowNotFound
} }
// Call incentive hooks
k.BeforeDepositModified(ctx, deposit)
k.BeforeBorrowModified(ctx, borrow)
k.SyncBorrowInterest(ctx, borrower)
k.SyncSupplyInterest(ctx, borrower)
deposit, found = k.GetDeposit(ctx, borrower)
if !found {
return types.ErrDepositNotFound
}
borrow, found = k.GetBorrow(ctx, borrower)
if !found {
return types.ErrBorrowNotFound
}
isWithinRange, err := k.IsWithinValidLtvRange(ctx, deposit, borrow) isWithinRange, err := k.IsWithinValidLtvRange(ctx, deposit, borrow)
if err != nil { if err != nil {
return err return err

View File

@ -123,20 +123,6 @@ func (suite *KeeperTestSuite) TestIndexLiquidation() {
// Hard module genesis state // Hard module genesis state
hardGS := types.NewGenesisState(types.NewParams( hardGS := types.NewGenesisState(types.NewParams(
true, true,
types.DistributionSchedules{
types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "usdc", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "usdt", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "dai", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "btc", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
},
types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
time.Hour*24,
),
},
types.MoneyMarkets{ types.MoneyMarkets{
types.NewMoneyMarket("usdx", types.NewMoneyMarket("usdx",
types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.9")), // Borrow Limit types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.9")), // Borrow Limit
@ -196,7 +182,7 @@ func (suite *KeeperTestSuite) TestIndexLiquidation() {
sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
}, },
tc.args.ltvIndexCount, // LTV counter tc.args.ltvIndexCount, // LTV counter
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) ), types.DefaultPreviousBlockTime)
// Pricefeed module genesis state // Pricefeed module genesis state
pricefeedGS := pricefeed.GenesisState{ pricefeedGS := pricefeed.GenesisState{
@ -549,15 +535,6 @@ func (suite *KeeperTestSuite) TestFullIndexLiquidation() {
// Hard module genesis state // Hard module genesis state
hardGS := types.NewGenesisState(types.NewParams( hardGS := types.NewGenesisState(types.NewParams(
true, true,
types.DistributionSchedules{
types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
},
types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
time.Hour*24,
),
},
types.MoneyMarkets{ types.MoneyMarkets{
types.NewMoneyMarket("usdx", types.NewMoneyMarket("usdx",
types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.9")), // Borrow Limit types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.9")), // Borrow Limit
@ -577,7 +554,7 @@ func (suite *KeeperTestSuite) TestFullIndexLiquidation() {
sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
}, },
tc.args.ltvIndexCount, // LTV counter tc.args.ltvIndexCount, // LTV counter
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) ), types.DefaultPreviousBlockTime)
// Pricefeed module genesis state // Pricefeed module genesis state
pricefeedGS := pricefeed.GenesisState{ pricefeedGS := pricefeed.GenesisState{
@ -1181,20 +1158,6 @@ func (suite *KeeperTestSuite) TestKeeperLiquidation() {
// Hard module genesis state // Hard module genesis state
hardGS := types.NewGenesisState(types.NewParams( hardGS := types.NewGenesisState(types.NewParams(
true, true,
types.DistributionSchedules{
types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "usdc", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "usdt", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "dai", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "btc", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
},
types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
time.Hour*24,
),
},
types.MoneyMarkets{ types.MoneyMarkets{
types.NewMoneyMarket("usdx", types.NewMoneyMarket("usdx",
types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.9")), // Borrow Limit types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.9")), // Borrow Limit
@ -1254,7 +1217,7 @@ func (suite *KeeperTestSuite) TestKeeperLiquidation() {
tc.args.keeperRewardPercent), // Keeper Reward Percent tc.args.keeperRewardPercent), // Keeper Reward Percent
}, },
0, // LTV counter 0, // LTV counter
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) ), types.DefaultPreviousBlockTime)
// Pricefeed module genesis state // Pricefeed module genesis state
pricefeedGS := pricefeed.GenesisState{ pricefeedGS := pricefeed.GenesisState{

View File

@ -18,28 +18,6 @@ func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
k.paramSubspace.SetParamSet(ctx, &params) k.paramSubspace.SetParamSet(ctx, &params)
} }
// GetLPSchedule gets the LP's schedule
func (k Keeper) GetLPSchedule(ctx sdk.Context, denom string) (types.DistributionSchedule, bool) {
params := k.GetParams(ctx)
for _, lps := range params.LiquidityProviderSchedules {
if lps.DepositDenom == denom {
return lps, true
}
}
return types.DistributionSchedule{}, false
}
// GetDelegatorSchedule gets the Delgator's schedule
func (k Keeper) GetDelegatorSchedule(ctx sdk.Context, denom string) (types.DelegatorDistributionSchedule, bool) {
params := k.GetParams(ctx)
for _, dds := range params.DelegatorDistributionSchedules {
if dds.DistributionSchedule.DepositDenom == denom {
return dds, true
}
}
return types.DelegatorDistributionSchedule{}, false
}
// GetMoneyMarketParam returns the corresponding Money Market param for a specific denom // GetMoneyMarketParam returns the corresponding Money Market param for a specific denom
func (k Keeper) GetMoneyMarketParam(ctx sdk.Context, denom string) (types.MoneyMarket, bool) { func (k Keeper) GetMoneyMarketParam(ctx sdk.Context, denom string) (types.MoneyMarket, bool) {
params := k.GetParams(ctx) params := k.GetParams(ctx)

View File

@ -24,8 +24,6 @@ func NewQuerier(k Keeper) sdk.Querier {
return queryGetDeposits(ctx, req, k) return queryGetDeposits(ctx, req, k)
case types.QueryGetTotalDeposited: case types.QueryGetTotalDeposited:
return queryGetTotalDeposited(ctx, req, k) return queryGetTotalDeposited(ctx, req, k)
case types.QueryGetClaims:
return queryGetClaims(ctx, req, k)
case types.QueryGetBorrows: case types.QueryGetBorrows:
return queryGetBorrows(ctx, req, k) return queryGetBorrows(ctx, req, k)
case types.QueryGetTotalBorrowed: case types.QueryGetTotalBorrowed:
@ -149,111 +147,6 @@ func queryGetDeposits(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte,
return bz, nil return bz, nil
} }
func queryGetClaims(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) {
var params types.QueryClaimParams
err := types.ModuleCdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
depositDenom := len(params.Denom) > 0
owner := len(params.Owner) > 0
claimType := len(params.ClaimType) > 0
var claims []types.Claim
switch {
case depositDenom && owner && claimType:
claim, found := k.GetClaim(ctx, params.Owner, params.Denom, params.ClaimType)
if found {
claims = append(claims, claim)
}
case depositDenom && owner:
for _, dt := range types.ClaimTypesClaimQuery {
claim, found := k.GetClaim(ctx, params.Owner, params.Denom, dt)
if found {
claims = append(claims, claim)
}
}
case depositDenom && claimType:
k.IterateClaimsByTypeAndDenom(ctx, params.ClaimType, params.Denom, func(claim types.Claim) (stop bool) {
claims = append(claims, claim)
return false
})
case owner && claimType:
hardParams := k.GetParams(ctx)
switch {
case params.ClaimType == types.LP:
for _, lps := range hardParams.LiquidityProviderSchedules {
claim, found := k.GetClaim(ctx, params.Owner, lps.DepositDenom, params.ClaimType)
if found {
claims = append(claims, claim)
}
}
case params.ClaimType == types.Stake:
for _, dss := range hardParams.DelegatorDistributionSchedules {
claim, found := k.GetClaim(ctx, params.Owner, dss.DistributionSchedule.DepositDenom, params.ClaimType)
if found {
claims = append(claims, claim)
}
}
}
case depositDenom:
for _, dt := range types.ClaimTypesClaimQuery {
k.IterateClaimsByTypeAndDenom(ctx, dt, params.Denom, func(claim types.Claim) (stop bool) {
claims = append(claims, claim)
return false
})
}
case owner:
hardParams := k.GetParams(ctx)
for _, lps := range hardParams.LiquidityProviderSchedules {
claim, found := k.GetClaim(ctx, params.Owner, lps.DepositDenom, types.LP)
if found {
claims = append(claims, claim)
}
}
for _, dds := range hardParams.DelegatorDistributionSchedules {
claim, found := k.GetClaim(ctx, params.Owner, dds.DistributionSchedule.DepositDenom, types.Stake)
if found {
claims = append(claims, claim)
}
}
case claimType:
hardParams := k.GetParams(ctx)
for _, lps := range hardParams.LiquidityProviderSchedules {
k.IterateClaimsByTypeAndDenom(ctx, params.ClaimType, lps.DepositDenom, func(claim types.Claim) (stop bool) {
claims = append(claims, claim)
return false
})
}
for _, dds := range hardParams.DelegatorDistributionSchedules {
k.IterateClaimsByTypeAndDenom(ctx, params.ClaimType, dds.DistributionSchedule.DepositDenom, func(claim types.Claim) (stop bool) {
claims = append(claims, claim)
return false
})
}
default:
k.IterateClaims(ctx, func(claim types.Claim) (stop bool) {
claims = append(claims, claim)
return false
})
}
start, end := client.Paginate(len(claims), params.Page, params.Limit, 100)
if start < 0 || end < 0 {
claims = []types.Claim{}
} else {
claims = claims[start:end]
}
bz, err := codec.MarshalJSONIndent(types.ModuleCdc, claims)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}
func queryGetBorrows(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { func queryGetBorrows(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) {
var params types.QueryBorrowsParams var params types.QueryBorrowsParams

View File

@ -15,6 +15,14 @@ func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) e
return err return err
} }
// Check borrow exists here to avoid duplicating store read in ValidateRepay
borrow, found := k.GetBorrow(ctx, sender)
if !found {
return types.ErrBorrowNotFound
}
// Call incentive hook
k.BeforeBorrowModified(ctx, borrow)
// Sync borrow interest so loan is up-to-date // Sync borrow interest so loan is up-to-date
k.SyncBorrowInterest(ctx, sender) k.SyncBorrowInterest(ctx, sender)
@ -24,12 +32,6 @@ func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) e
return err return err
} }
// Check borrow exists here to avoid duplicating store read in ValidateRepay
borrow, found := k.GetBorrow(ctx, sender)
if !found {
return types.ErrBorrowNotFound
}
payment, err := k.CalculatePaymentAmount(borrow.Amount, coins) payment, err := k.CalculatePaymentAmount(borrow.Amount, coins)
if err != nil { if err != nil {
return err return err
@ -59,6 +61,11 @@ func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) e
// Update total borrowed amount // Update total borrowed amount
k.DecrementBorrowedCoins(ctx, payment) k.DecrementBorrowedCoins(ctx, payment)
// Call incentive hook
if !borrow.Amount.Empty() {
k.AfterBorrowModified(ctx, borrow)
}
ctx.EventManager().EmitEvent( ctx.EventManager().EmitEvent(
sdk.NewEvent( sdk.NewEvent(
types.EventTypeHardRepay, types.EventTypeHardRepay,

View File

@ -138,15 +138,6 @@ func (suite *KeeperTestSuite) TestRepay() {
// Hard module genesis state // Hard module genesis state
hardGS := types.NewGenesisState(types.NewParams( hardGS := types.NewGenesisState(types.NewParams(
true, true,
types.DistributionSchedules{
types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
},
types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
time.Hour*24,
),
},
types.MoneyMarkets{ types.MoneyMarkets{
types.NewMoneyMarket("usdx", types.NewMoneyMarket("usdx",
types.NewBorrowLimit(false, sdk.NewDec(100000000*USDX_CF), sdk.MustNewDecFromStr("1")), // Borrow Limit types.NewBorrowLimit(false, sdk.NewDec(100000000*USDX_CF), sdk.MustNewDecFromStr("1")), // Borrow Limit
@ -166,7 +157,7 @@ func (suite *KeeperTestSuite) TestRepay() {
sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
}, },
0, // LTV counter 0, // LTV counter
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) ), types.DefaultPreviousBlockTime)
// Pricefeed module genesis state // Pricefeed module genesis state
pricefeedGS := pricefeed.GenesisState{ pricefeedGS := pricefeed.GenesisState{

View File

@ -1,175 +0,0 @@
package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingexported "github.com/cosmos/cosmos-sdk/x/staking/exported"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/kava-labs/kava/x/hard/types"
)
// ApplyDepositRewards iterates over lp and gov deposits and updates the amount of rewards for each depositor
func (k Keeper) ApplyDepositRewards(ctx sdk.Context) {
previousBlockTime, found := k.GetPreviousBlockTime(ctx)
if !found {
previousBlockTime = ctx.BlockTime()
k.SetPreviousBlockTime(ctx, previousBlockTime)
return
}
params := k.GetParams(ctx)
if !params.Active {
return
}
timeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousBlockTime.Unix())
for _, lps := range params.LiquidityProviderSchedules {
if !lps.Active {
continue
}
if lps.End.Before(ctx.BlockTime()) {
continue
}
if lps.Start.After(ctx.BlockTime()) {
continue
}
totalDeposited := k.GetTotalDeposited(ctx, lps.DepositDenom)
if totalDeposited.IsZero() {
continue
}
rewardsToDistribute := lps.RewardsPerSecond.Amount.Mul(timeElapsed)
if rewardsToDistribute.IsZero() {
continue
}
rewardsDistributed := sdk.ZeroInt()
k.IterateDeposits(ctx, func(dep types.Deposit) (stop bool) {
rewardsShare := sdk.NewDecFromInt(dep.Amount.AmountOf(lps.DepositDenom)).Quo(sdk.NewDecFromInt(totalDeposited))
if rewardsShare.IsZero() {
return false
}
rewardsEarned := rewardsShare.Mul(sdk.NewDecFromInt(rewardsToDistribute)).RoundInt()
if rewardsEarned.IsZero() {
return false
}
k.AddToClaim(ctx, dep.Depositor, lps.DepositDenom, types.LP, sdk.NewCoin(lps.RewardsPerSecond.Denom, rewardsEarned))
rewardsDistributed = rewardsDistributed.Add(rewardsEarned)
return false
})
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeHardLPDistribution,
sdk.NewAttribute(types.AttributeKeyBlockHeight, fmt.Sprintf("%d", ctx.BlockHeight())),
sdk.NewAttribute(types.AttributeKeyRewardsDistribution, rewardsDistributed.String()),
sdk.NewAttribute(types.AttributeKeyDepositDenom, lps.DepositDenom),
),
)
}
k.SetPreviousBlockTime(ctx, ctx.BlockTime())
}
// ShouldDistributeValidatorRewards returns true if enough time has elapsed such that rewards should be distributed to delegators
func (k Keeper) ShouldDistributeValidatorRewards(ctx sdk.Context, denom string) bool {
previousDistributionTime, found := k.GetPreviousDelegatorDistribution(ctx, denom)
if !found {
k.SetPreviousDelegationDistribution(ctx, ctx.BlockTime(), denom)
return false
}
params := k.GetParams(ctx)
if !params.Active {
return false
}
for _, dds := range params.DelegatorDistributionSchedules {
if denom != dds.DistributionSchedule.DepositDenom {
continue
}
if dds.DistributionSchedule.End.Before(ctx.BlockTime()) {
continue
}
timeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousDistributionTime.Unix())
if timeElapsed.GTE(sdk.NewInt(int64(dds.DistributionFrequency.Seconds()))) {
return true
}
}
return false
}
// ApplyDelegationRewards iterates over each delegation object in the staking store and applies rewards according to the input delegation distribution schedule
func (k Keeper) ApplyDelegationRewards(ctx sdk.Context, denom string) {
dds, found := k.GetDelegatorSchedule(ctx, denom)
if !found {
return
}
if !dds.DistributionSchedule.Active {
return
}
if dds.DistributionSchedule.Start.After(ctx.BlockTime()) {
return
}
bondMacc := k.stakingKeeper.GetBondedPool(ctx)
bondedCoinAmount := bondMacc.GetCoins().AmountOf(dds.DistributionSchedule.DepositDenom)
if bondedCoinAmount.IsZero() {
return
}
previousDistributionTime, found := k.GetPreviousDelegatorDistribution(ctx, dds.DistributionSchedule.DepositDenom)
if !found {
return
}
timeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousDistributionTime.Unix())
rewardsToDistribute := dds.DistributionSchedule.RewardsPerSecond.Amount.Mul(timeElapsed)
// create a map that has each validator address (sdk.ValAddress) as a key and the coversion factor for going from delegator shares to tokens for delegations to that validator.
// If a validator has never been slashed, the conversion factor will be 1.0, if they have been, it will be < 1.0
sharesToTokens := make(map[string]sdk.Dec)
k.stakingKeeper.IterateValidators(ctx, func(index int64, validator stakingexported.ValidatorI) (stop bool) {
if validator.GetTokens().IsZero() {
return false
}
// don't include a validator if it's unbonded or unbonding- ie delegators don't accumulate rewards when delegated to an unbonded/slashed validator
if validator.GetStatus() != sdk.Bonded {
return false
}
sharesToTokens[validator.GetOperator().String()] = sdk.NewDecFromInt(validator.GetTokens()).Quo(validator.GetDelegatorShares())
return false
})
rewardsDistributed := sdk.ZeroInt()
k.stakingKeeper.IterateAllDelegations(ctx, func(delegation stakingtypes.Delegation) (stop bool) {
conversionFactor, ok := sharesToTokens[delegation.ValidatorAddress.String()]
if ok {
delegationTokens := conversionFactor.Mul(delegation.Shares)
delegationShare := delegationTokens.Quo(sdk.NewDecFromInt(bondedCoinAmount))
rewardsEarned := delegationShare.Mul(sdk.NewDecFromInt(rewardsToDistribute)).RoundInt()
if rewardsEarned.IsZero() {
return false
}
k.AddToClaim(
ctx, delegation.DelegatorAddress, dds.DistributionSchedule.DepositDenom,
types.Stake, sdk.NewCoin(dds.DistributionSchedule.RewardsPerSecond.Denom, rewardsEarned))
rewardsDistributed = rewardsDistributed.Add(rewardsEarned)
}
return false
})
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeHardDelegatorDistribution,
sdk.NewAttribute(types.AttributeKeyBlockHeight, fmt.Sprintf("%d", ctx.BlockHeight())),
sdk.NewAttribute(types.AttributeKeyRewardsDistribution, rewardsDistributed.String()),
sdk.NewAttribute(types.AttributeKeyDepositDenom, denom),
),
)
}
// AddToClaim adds the input amount to an existing claim or creates a new one
func (k Keeper) AddToClaim(ctx sdk.Context, owner sdk.AccAddress, depositDenom string, claimType types.ClaimType, amountToAdd sdk.Coin) {
claim, found := k.GetClaim(ctx, owner, depositDenom, claimType)
if !found {
claim = types.NewClaim(owner, depositDenom, amountToAdd, claimType)
} else {
claim.Amount = claim.Amount.Add(amountToAdd)
}
k.SetClaim(ctx, claim)
}

View File

@ -1,476 +0,0 @@
package keeper_test
import (
"fmt"
"testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/hard/keeper"
"github.com/kava-labs/kava/x/hard/types"
)
func (suite *KeeperTestSuite) TestApplyDepositRewards() {
type args struct {
depositor sdk.AccAddress
denom string
depositAmount sdk.Coins
totalDeposits sdk.Coin
rewardRate sdk.Coin
claimType types.ClaimType
previousBlockTime time.Time
blockTime time.Time
expectedClaimBalance sdk.Coin
}
type errArgs struct {
expectPanic bool
contains string
}
type testCase struct {
name string
args args
errArgs errArgs
}
testCases := []testCase{
{
name: "distribute rewards",
args: args{
depositor: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
denom: "bnb",
rewardRate: c("hard", 500),
depositAmount: cs(c("bnb", 100)),
totalDeposits: c("bnb", 1000),
claimType: types.LP,
previousBlockTime: time.Date(2020, 11, 1, 13, 59, 50, 0, time.UTC),
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
expectedClaimBalance: c("hard", 500),
},
errArgs: errArgs{
expectPanic: false,
contains: "",
},
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
// Initialize test app and set context
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tc.args.blockTime})
loanToValue, _ := sdk.NewDecFromStr("0.6")
hardGS := types.NewGenesisState(types.NewParams(
true,
types.DistributionSchedules{
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), tc.args.rewardRate, time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 24, sdk.OneDec())}),
},
types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), tc.args.rewardRate, time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 24, sdk.OneDec())}),
time.Hour*24,
),
},
types.MoneyMarkets{
types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
},
0, // LTV counter
), tc.args.previousBlockTime, types.DefaultDistributionTimes)
tApp.InitializeFromGenesisStates(app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(hardGS)})
supplyKeeper := tApp.GetSupplyKeeper()
supplyKeeper.MintCoins(ctx, types.ModuleAccountName, cs(tc.args.totalDeposits))
keeper := tApp.GetHardKeeper()
deposit := types.NewDeposit(tc.args.depositor, tc.args.depositAmount, types.SupplyInterestFactors{})
keeper.SetDeposit(ctx, deposit)
suite.app = tApp
suite.ctx = ctx
suite.keeper = keeper
if tc.errArgs.expectPanic {
suite.Require().Panics(func() { suite.keeper.ApplyDepositRewards(suite.ctx) })
} else {
suite.Require().NotPanics(func() { suite.keeper.ApplyDepositRewards(suite.ctx) })
claim, f := suite.keeper.GetClaim(suite.ctx, tc.args.depositor, tc.args.denom, tc.args.claimType)
suite.Require().True(f)
suite.Require().Equal(tc.args.expectedClaimBalance, claim.Amount)
}
})
}
}
func TestApplyDelegatorRewardsTestSuite(t *testing.T) {
suite.Run(t, new(DelegatorRewardsTestSuite))
}
type DelegatorRewardsTestSuite struct {
suite.Suite
validatorAddrs []sdk.ValAddress
delegatorAddrs []sdk.AccAddress
keeper keeper.Keeper
stakingKeeper staking.Keeper
app app.TestApp
rewardRate int64
}
// The default state used by each test
func (suite *DelegatorRewardsTestSuite) SetupTest() {
config := sdk.GetConfig()
app.SetBech32AddressPrefixes(config)
_, allAddrs := app.GeneratePrivKeyAddressPairs(10)
suite.delegatorAddrs = allAddrs[:5]
for _, a := range allAddrs[5:] {
suite.validatorAddrs = append(suite.validatorAddrs, sdk.ValAddress(a))
}
suite.app = app.NewTestApp()
suite.rewardRate = 500
suite.app.InitializeFromGenesisStates(
equalCoinsAuthGenState(allAddrs, cs(c("ukava", 5_000_000))),
stakingGenesisState(),
hardGenesisState(c("hard", suite.rewardRate)),
)
suite.keeper = suite.app.GetHardKeeper()
suite.stakingKeeper = suite.app.GetStakingKeeper()
}
func (suite *DelegatorRewardsTestSuite) TestSlash() {
blockTime := time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC)
ctx := suite.app.NewContext(true, abci.Header{Height: 1, Time: blockTime})
const rewardDuration = 5
suite.keeper.SetPreviousDelegationDistribution(ctx, blockTime.Add(-1*rewardDuration*time.Second), "ukava")
suite.Require().NoError(
suite.deliverMsgCreateValidator(ctx, suite.validatorAddrs[0], c("ukava", 5_000_000)),
)
staking.EndBlocker(ctx, suite.stakingKeeper)
suite.Require().NoError(
suite.slashValidator(ctx, suite.validatorAddrs[0], "0.05"),
)
staking.EndBlocker(ctx, suite.stakingKeeper)
// Run function under test
suite.keeper.ApplyDelegationRewards(ctx, "ukava")
// Check claim amounts
suite.Require().NoError(
suite.verifyKavaClaimAmount(ctx, sdk.AccAddress(suite.validatorAddrs[0]), c("hard", suite.rewardRate*rewardDuration)),
)
}
func (suite *DelegatorRewardsTestSuite) TestUndelegation() {
blockTime := time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC)
ctx := suite.app.NewContext(true, abci.Header{Height: 1, Time: blockTime})
const rewardDuration = 5
suite.keeper.SetPreviousDelegationDistribution(ctx, blockTime.Add(-1*rewardDuration*time.Second), "ukava")
suite.Require().NoError(
suite.deliverMsgCreateValidator(ctx, suite.validatorAddrs[0], c("ukava", 1_000_000)),
)
suite.Require().NoError(
suite.deliverMsgDelegate(ctx, suite.delegatorAddrs[0], suite.validatorAddrs[0], c("ukava", 1_000_000)),
)
staking.EndBlocker(ctx, suite.stakingKeeper)
suite.Require().NoError(
suite.deliverMsgUndelegate(ctx, suite.delegatorAddrs[0], suite.validatorAddrs[0], c("ukava", 1_000_000)),
)
staking.EndBlocker(ctx, suite.stakingKeeper)
// Run function under test
suite.keeper.ApplyDelegationRewards(ctx, "ukava")
// Check claim amounts
suite.Require().NoError(
suite.verifyKavaClaimAmount(ctx, sdk.AccAddress(suite.validatorAddrs[0]), c("hard", suite.rewardRate*rewardDuration)),
)
suite.Require().False(
suite.kavaClaimExists(ctx, suite.delegatorAddrs[0]),
)
}
func (suite *DelegatorRewardsTestSuite) TestUnevenNumberDelegations() {
// Setup a context
blockTime := time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC)
ctx := suite.app.NewContext(true, abci.Header{Height: 1, Time: blockTime})
const rewardDuration = 5
suite.keeper.SetPreviousDelegationDistribution(ctx, blockTime.Add(-1*rewardDuration*time.Second), "ukava")
suite.Require().NoError(
suite.deliverMsgCreateValidator(ctx, suite.validatorAddrs[0], c("ukava", 1_000_000)),
)
suite.Require().NoError(
suite.deliverMsgDelegate(ctx, suite.delegatorAddrs[0], suite.validatorAddrs[0], c("ukava", 1_000_000)),
)
suite.Require().NoError(
suite.deliverMsgDelegate(ctx, suite.delegatorAddrs[1], suite.validatorAddrs[0], c("ukava", 1_000_000)),
)
staking.EndBlocker(ctx, suite.stakingKeeper)
// Run function under test
suite.keeper.ApplyDelegationRewards(ctx, "ukava")
// Check claim amounts
expectedReward := suite.rewardRate * rewardDuration / 3 // floor division
suite.Require().NoError(
suite.verifyKavaClaimAmount(ctx, sdk.AccAddress(suite.validatorAddrs[0]), c("hard", expectedReward)),
)
suite.Require().NoError(
suite.verifyKavaClaimAmount(ctx, suite.delegatorAddrs[0], c("hard", expectedReward)),
)
suite.Require().NoError(
suite.verifyKavaClaimAmount(ctx, suite.delegatorAddrs[1], c("hard", expectedReward)),
)
}
func (suite *DelegatorRewardsTestSuite) TestSlashWithUndelegated() {
// Setup a context
blockTime := time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC)
ctx := suite.app.NewContext(true, abci.Header{Height: 1, Time: blockTime})
const rewardDuration = 5
suite.keeper.SetPreviousDelegationDistribution(ctx, blockTime.Add(-1*rewardDuration*time.Second), "ukava")
suite.Require().NoError(
suite.deliverMsgCreateValidator(ctx, suite.validatorAddrs[0], c("ukava", 1_000_000)),
)
suite.Require().NoError(
suite.deliverMsgDelegate(ctx, suite.delegatorAddrs[0], suite.validatorAddrs[0], c("ukava", 1_000_000)),
)
suite.Require().NoError(
suite.deliverMsgDelegate(ctx, suite.delegatorAddrs[1], suite.validatorAddrs[0], c("ukava", 1_000_000)),
)
staking.EndBlocker(ctx, suite.stakingKeeper)
suite.Require().NoError(
suite.deliverMsgUndelegate(ctx, suite.delegatorAddrs[0], suite.validatorAddrs[0], c("ukava", 1_000_000)),
)
staking.EndBlocker(ctx, suite.stakingKeeper)
suite.Require().NoError(
suite.slashValidator(ctx, suite.validatorAddrs[0], "0.05"),
)
staking.EndBlocker(ctx, suite.stakingKeeper)
// Run function under test
suite.keeper.ApplyDelegationRewards(ctx, "ukava")
// Check claim amounts
suite.Require().NoError(
suite.verifyKavaClaimAmount(ctx, sdk.AccAddress(suite.validatorAddrs[0]), c("hard", suite.rewardRate*rewardDuration/2)),
)
suite.Require().False(
suite.kavaClaimExists(ctx, suite.delegatorAddrs[0]),
)
suite.Require().NoError(
suite.verifyKavaClaimAmount(ctx, suite.delegatorAddrs[1], c("hard", suite.rewardRate*rewardDuration/2)),
)
}
func (suite *DelegatorRewardsTestSuite) TestUnbondingValidator() {
// Setup a context
blockTime := time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC)
ctx := suite.app.NewContext(true, abci.Header{Height: 1, Time: blockTime})
const rewardDuration = 5
suite.keeper.SetPreviousDelegationDistribution(ctx, blockTime.Add(-1*rewardDuration*time.Second), "ukava")
suite.Require().NoError(
suite.deliverMsgCreateValidator(ctx, suite.validatorAddrs[0], c("ukava", 1_000_000)),
)
suite.Require().NoError(
suite.deliverMsgCreateValidator(ctx, suite.validatorAddrs[1], c("ukava", 1_000_000)),
)
suite.Require().NoError(
suite.deliverMsgDelegate(ctx, suite.delegatorAddrs[0], suite.validatorAddrs[0], c("ukava", 1_000_000)),
)
suite.Require().NoError(
suite.deliverMsgDelegate(ctx, suite.delegatorAddrs[1], suite.validatorAddrs[1], c("ukava", 1_000_000)),
)
staking.EndBlocker(ctx, suite.stakingKeeper)
suite.Require().NoError(
// jail the validator to put it into an unbonding state
suite.jailValidator(ctx, suite.validatorAddrs[0]),
)
staking.EndBlocker(ctx, suite.stakingKeeper)
// Run function under test
suite.keeper.ApplyDelegationRewards(ctx, "ukava")
// Check claim amounts
suite.Require().False(
// validator 0 will be unbonding and should not receive rewards
suite.kavaClaimExists(ctx, sdk.AccAddress(suite.validatorAddrs[0])),
)
suite.Require().NoError(
suite.verifyKavaClaimAmount(ctx, sdk.AccAddress(suite.validatorAddrs[1]), c("hard", suite.rewardRate*rewardDuration/2)),
)
suite.Require().False(
// delegations to unbonding validators and should not receive rewards
suite.kavaClaimExists(ctx, suite.delegatorAddrs[0]),
)
suite.Require().NoError(
suite.verifyKavaClaimAmount(ctx, suite.delegatorAddrs[1], c("hard", suite.rewardRate*rewardDuration/2)),
)
}
func (suite *DelegatorRewardsTestSuite) 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 *DelegatorRewardsTestSuite) 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
}
func (suite *DelegatorRewardsTestSuite) deliverMsgUndelegate(ctx sdk.Context, delegator sdk.AccAddress, validator sdk.ValAddress, amount sdk.Coin) error {
msg := staking.NewMsgUndelegate(
delegator,
validator,
amount,
)
handleStakingMsg := staking.NewHandler(suite.stakingKeeper)
_, err := handleStakingMsg(ctx, msg)
return err
}
func (suite *DelegatorRewardsTestSuite) slashValidator(ctx sdk.Context, validator sdk.ValAddress, slashPercent string) error {
// Assume slashable offence occurred at block 1. Note this might cause problems if tests are running at a block height higher than the unbonding period (default 3 weeks)
const infractionHeight int64 = 1
val, found := suite.stakingKeeper.GetValidator(ctx, validator)
if !found {
return fmt.Errorf("can't find validator in state")
}
suite.stakingKeeper.Slash(
ctx,
sdk.GetConsAddress(val.ConsPubKey),
infractionHeight,
val.GetConsensusPower(),
sdk.MustNewDecFromStr(slashPercent),
)
return nil
}
func (suite *DelegatorRewardsTestSuite) jailValidator(ctx sdk.Context, validator sdk.ValAddress) error {
val, found := suite.stakingKeeper.GetValidator(ctx, validator)
if !found {
return fmt.Errorf("can't find validator in state")
}
suite.stakingKeeper.Jail(ctx, sdk.GetConsAddress(val.ConsPubKey))
return nil
}
// verifyKavaClaimAmount looks up a ukava claim and checks the claim amount is equal to an expected value
func (suite *DelegatorRewardsTestSuite) verifyKavaClaimAmount(ctx sdk.Context, owner sdk.AccAddress, expectedAmount sdk.Coin) error {
claim, found := suite.keeper.GetClaim(ctx, owner, "ukava", types.Stake)
if !found {
return fmt.Errorf("could not find claim")
}
if !expectedAmount.IsEqual(claim.Amount) {
return fmt.Errorf("expected claim amount (%s) != actual claim amount (%s)", expectedAmount, claim.Amount)
}
return nil
}
// kavaClaimExists checks the store for a ukava claim
func (suite *DelegatorRewardsTestSuite) kavaClaimExists(ctx sdk.Context, owner sdk.AccAddress) bool {
_, found := suite.keeper.GetClaim(ctx, owner, "ukava", types.Stake)
return found
}
func hardGenesisState(rewardRate sdk.Coin) app.GenesisState {
loanToValue := sdk.MustNewDecFromStr("0.6")
genState := types.NewGenesisState(
types.NewParams(
true,
types.DistributionSchedules{
types.NewDistributionSchedule(
true,
"bnb",
time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC),
time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC),
rewardRate,
time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC),
types.Multipliers{
types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")),
types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")),
types.NewMultiplier(types.Large, 24, sdk.OneDec()),
},
),
},
types.DelegatorDistributionSchedules{
types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(
true,
"ukava",
time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC),
time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC),
rewardRate,
time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC),
types.Multipliers{
types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")),
types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")),
types.NewMultiplier(types.Large, 24, sdk.OneDec()),
},
),
time.Hour*24,
),
},
types.MoneyMarkets{
types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
},
0, // LTV counter
),
types.DefaultPreviousBlockTime,
types.DefaultDistributionTimes,
)
return app.GenesisState{
types.ModuleName: types.ModuleCdc.MustMarshalJSON(genState),
}
}
func stakingGenesisState() app.GenesisState {
genState := staking.DefaultGenesisState()
genState.Params.BondDenom = "ukava"
return app.GenesisState{
staking.ModuleName: staking.ModuleCdc.MustMarshalJSON(genState),
}
}
// equalCoinsAuthGenState returns an auth genesis state with the same coins for each account
func equalCoinsAuthGenState(addresses []sdk.AccAddress, coins sdk.Coins) app.GenesisState {
coinsList := []sdk.Coins{}
for range addresses {
coinsList = append(coinsList, coins)
}
return app.NewAuthGenState(addresses, coinsList)
}

View File

@ -282,20 +282,12 @@ func (suite *KeeperTestSuite) TestSendTimeLockedCoinsToAccount() {
loanToValue := sdk.MustNewDecFromStr("0.6") loanToValue := sdk.MustNewDecFromStr("0.6")
hardGS := types.NewGenesisState(types.NewParams( hardGS := types.NewGenesisState(types.NewParams(
true, true,
types.DistributionSchedules{
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 24, sdk.OneDec())}),
},
types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 24, sdk.OneDec())}),
time.Hour*24,
),
},
types.MoneyMarkets{ types.MoneyMarkets{
types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
}, },
0, // LTV counter 0, // LTV counter
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) ), types.DefaultPreviousBlockTime)
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(hardGS)}) tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(hardGS)})
if tc.args.accArgs.vestingAccountBefore { if tc.args.accArgs.vestingAccountBefore {
ak := tApp.GetAccountKeeper() ak := tApp.GetAccountKeeper()

View File

@ -15,13 +15,15 @@ func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Co
return err return err
} }
k.SyncBorrowInterest(ctx, depositor)
k.SyncSupplyInterest(ctx, depositor)
deposit, found := k.GetDeposit(ctx, depositor) deposit, found := k.GetDeposit(ctx, depositor)
if !found { if !found {
return sdkerrors.Wrapf(types.ErrDepositNotFound, "no deposit found for %s", depositor) return sdkerrors.Wrapf(types.ErrDepositNotFound, "no deposit found for %s", depositor)
} }
// Call incentive hook
k.BeforeDepositModified(ctx, deposit)
k.SyncBorrowInterest(ctx, depositor)
k.SyncSupplyInterest(ctx, depositor)
amount, err := k.CalculateWithdrawAmount(deposit.Amount, coins) amount, err := k.CalculateWithdrawAmount(deposit.Amount, coins)
if err != nil { if err != nil {
@ -47,19 +49,7 @@ func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Co
return err return err
} }
if deposit.Amount.IsEqual(amount) {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeDeleteHardDeposit,
sdk.NewAttribute(types.AttributeKeyDepositor, depositor.String()),
),
)
k.DeleteDeposit(ctx, deposit)
return nil
}
deposit.Amount = deposit.Amount.Sub(amount) deposit.Amount = deposit.Amount.Sub(amount)
newLtv, err := k.CalculateLtv(ctx, deposit, borrow) newLtv, err := k.CalculateLtv(ctx, deposit, borrow)
if err != nil { if err != nil {
return err return err
@ -69,6 +59,9 @@ func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Co
// Update total supplied amount // Update total supplied amount
k.DecrementBorrowedCoins(ctx, amount) k.DecrementBorrowedCoins(ctx, amount)
// Call incentive hook
k.AfterDepositModified(ctx, deposit)
ctx.EventManager().EmitEvent( ctx.EventManager().EmitEvent(
sdk.NewEvent( sdk.NewEvent(
types.EventTypeHardWithdrawal, types.EventTypeHardWithdrawal,
@ -76,7 +69,6 @@ func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Co
sdk.NewAttribute(types.AttributeKeyDepositor, depositor.String()), sdk.NewAttribute(types.AttributeKeyDepositor, depositor.String()),
), ),
) )
return nil return nil
} }

View File

@ -125,21 +125,13 @@ func (suite *KeeperTestSuite) TestWithdraw() {
loanToValue := sdk.MustNewDecFromStr("0.6") loanToValue := sdk.MustNewDecFromStr("0.6")
hardGS := types.NewGenesisState(types.NewParams( hardGS := types.NewGenesisState(types.NewParams(
true, true,
types.DistributionSchedules{
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
},
types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
time.Hour*24,
),
},
types.MoneyMarkets{ types.MoneyMarkets{
types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
types.NewMoneyMarket("bnb", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "bnb:usd", sdk.NewInt(100000000), sdk.NewInt(BNB_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), types.NewMoneyMarket("bnb", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "bnb:usd", sdk.NewInt(100000000), sdk.NewInt(BNB_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
}, },
0, // LTV counter 0, // LTV counter
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) ), types.DefaultPreviousBlockTime)
// Pricefeed module genesis state // Pricefeed module genesis state
pricefeedGS := pricefeed.GenesisState{ pricefeedGS := pricefeed.GenesisState{
@ -273,15 +265,6 @@ func (suite *KeeperTestSuite) TestLtvWithdraw() {
// Harvest module genesis state // Harvest module genesis state
harvestGS := types.NewGenesisState(types.NewParams( harvestGS := types.NewGenesisState(types.NewParams(
true, true,
types.DistributionSchedules{
types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
},
types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
time.Hour*24,
),
},
types.MoneyMarkets{ types.MoneyMarkets{
types.NewMoneyMarket("ukava", types.NewMoneyMarket("ukava",
types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.8")), // Borrow Limit types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.8")), // Borrow Limit
@ -301,7 +284,7 @@ func (suite *KeeperTestSuite) TestLtvWithdraw() {
sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
}, },
0, // LTV counter 0, // LTV counter
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) ), types.DefaultPreviousBlockTime)
// Pricefeed module genesis state // Pricefeed module genesis state
pricefeedGS := pricefeed.GenesisState{ pricefeedGS := pricefeed.GenesisState{

View File

@ -15,21 +15,16 @@ import (
// DecodeStore unmarshals the KVPair's Value to the corresponding hard type // DecodeStore unmarshals the KVPair's Value to the corresponding hard type
func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string { func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string {
switch { switch {
case bytes.Equal(kvA.Key[:1], types.PreviousBlockTimeKey), bytes.Equal(kvA.Key[:1], types.PreviousDelegationDistributionKey):
var timeA, timeB time.Time
cdc.MustUnmarshalBinaryBare(kvA.Value, &timeA)
cdc.MustUnmarshalBinaryBare(kvB.Value, &timeB)
return fmt.Sprintf("%s\n%s", timeA, timeB)
case bytes.Equal(kvA.Key[:1], types.DepositsKeyPrefix): case bytes.Equal(kvA.Key[:1], types.DepositsKeyPrefix):
var depA, depB types.Deposit var depA, depB types.Deposit
cdc.MustUnmarshalBinaryBare(kvA.Value, &depA) cdc.MustUnmarshalBinaryBare(kvA.Value, &depA)
cdc.MustUnmarshalBinaryBare(kvB.Value, &depB) cdc.MustUnmarshalBinaryBare(kvB.Value, &depB)
return fmt.Sprintf("%s\n%s", depA, depB) return fmt.Sprintf("%s\n%s", depA, depB)
case bytes.Equal(kvA.Key[:1], types.ClaimsKeyPrefix): case bytes.Equal(kvA.Key[:1], types.PreviousBlockTimeKey):
var claimA, claimB types.Claim var timeA, timeB time.Time
cdc.MustUnmarshalBinaryBare(kvA.Value, &claimA) cdc.MustUnmarshalBinaryBare(kvA.Value, &timeA)
cdc.MustUnmarshalBinaryBare(kvB.Value, &claimB) cdc.MustUnmarshalBinaryBare(kvB.Value, &timeB)
return fmt.Sprintf("%s\n%s", claimA, claimB) return fmt.Sprintf("%s\n%s", timeA, timeB)
default: default:
panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1])) panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1]))
} }

View File

@ -27,16 +27,11 @@ func TestDecodeDistributionStore(t *testing.T) {
cdc := makeTestCodec() cdc := makeTestCodec()
prevBlockTime := time.Now().UTC() prevBlockTime := time.Now().UTC()
deposit := types.NewDeposit(sdk.AccAddress("test"), deposit := types.NewDeposit(sdk.AccAddress("test"), sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1))), types.SupplyInterestFactors{})
sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1))),
types.SupplyInterestFactors{types.NewSupplyInterestFactor("bnb", sdk.OneDec())})
claim := types.NewClaim(sdk.AccAddress("test"), "bnb", sdk.NewCoin("hard", sdk.NewInt(100)), "stake")
kvPairs := kv.Pairs{ kvPairs := kv.Pairs{
kv.Pair{Key: []byte(types.PreviousBlockTimeKey), Value: cdc.MustMarshalBinaryBare(prevBlockTime)}, kv.Pair{Key: []byte(types.PreviousBlockTimeKey), Value: cdc.MustMarshalBinaryBare(prevBlockTime)},
kv.Pair{Key: []byte(types.PreviousDelegationDistributionKey), Value: cdc.MustMarshalBinaryBare(prevBlockTime)},
kv.Pair{Key: []byte(types.DepositsKeyPrefix), Value: cdc.MustMarshalBinaryBare(deposit)}, kv.Pair{Key: []byte(types.DepositsKeyPrefix), Value: cdc.MustMarshalBinaryBare(deposit)},
kv.Pair{Key: []byte(types.ClaimsKeyPrefix), Value: cdc.MustMarshalBinaryBare(claim)},
kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}}, kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}},
} }
@ -45,9 +40,7 @@ func TestDecodeDistributionStore(t *testing.T) {
expectedLog string expectedLog string
}{ }{
{"PreviousBlockTime", fmt.Sprintf("%s\n%s", prevBlockTime, prevBlockTime)}, {"PreviousBlockTime", fmt.Sprintf("%s\n%s", prevBlockTime, prevBlockTime)},
{"PreviousDistributionTime", fmt.Sprintf("%s\n%s", prevBlockTime, prevBlockTime)},
{"Deposit", fmt.Sprintf("%s\n%s", deposit, deposit)}, {"Deposit", fmt.Sprintf("%s\n%s", deposit, deposit)},
{"Claim", fmt.Sprintf("%s\n%s", claim, claim)},
{"other", ""}, {"other", ""},
} }
for i, tt := range tests { for i, tt := range tests {

View File

@ -1,23 +0,0 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Claim defines an amount of coins that the owner can claim
type Claim struct {
Owner sdk.AccAddress `json:"owner" yaml:"owner"`
DepositDenom string `json:"deposit_denom" yaml:"deposit_denom"`
Amount sdk.Coin `json:"amount" yaml:"amount"`
Type ClaimType `json:"claim_type" yaml:"claim_type"`
}
// NewClaim returns a new claim
func NewClaim(owner sdk.AccAddress, denom string, amount sdk.Coin, claimType ClaimType) Claim {
return Claim{
Owner: owner,
DepositDenom: denom,
Amount: amount,
Type: claimType,
}
}

View File

@ -20,5 +20,4 @@ func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(MsgBorrow{}, "hard/MsgBorrow", nil) cdc.RegisterConcrete(MsgBorrow{}, "hard/MsgBorrow", nil)
cdc.RegisterConcrete(MsgLiquidate{}, "hard/MsgLiquidate", nil) cdc.RegisterConcrete(MsgLiquidate{}, "hard/MsgLiquidate", nil)
cdc.RegisterConcrete(MsgRepay{}, "hard/MsgRepay", nil) cdc.RegisterConcrete(MsgRepay{}, "hard/MsgRepay", nil)
cdc.RegisterConcrete(DistributionSchedule{}, "hard/DistributionSchedule", nil)
} }

View File

@ -45,3 +45,13 @@ type PricefeedKeeper interface {
type AuctionKeeper interface { type AuctionKeeper interface {
StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int, debt sdk.Coin) (uint64, error) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int, debt sdk.Coin) (uint64, error)
} }
// HARDHooks event hooks for other keepers to run code in response to HARD modifications
type HARDHooks interface {
AfterDepositCreated(ctx sdk.Context, deposit Deposit)
BeforeDepositModified(ctx sdk.Context, deposit Deposit)
AfterDepositModified(ctx sdk.Context, deposit Deposit)
AfterBorrowCreated(ctx sdk.Context, borrow Borrow)
BeforeBorrowModified(ctx sdk.Context, borrow Borrow)
AfterBorrowModified(ctx sdk.Context, borrow Borrow)
}

View File

@ -5,30 +5,25 @@ import (
"fmt" "fmt"
"time" "time"
sdk "github.com/cosmos/cosmos-sdk/types"
tmtime "github.com/tendermint/tendermint/types/time" tmtime "github.com/tendermint/tendermint/types/time"
) )
// GenesisState default values // GenesisState default values
var ( var (
DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0)) DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0))
DefaultDistributionTimes = GenesisDistributionTimes{}
) )
// GenesisState is the state that must be provided at genesis. // GenesisState is the state that must be provided at genesis.
type GenesisState struct { type GenesisState struct {
Params Params `json:"params" yaml:"params"` Params Params `json:"params" yaml:"params"`
PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"` PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"`
PreviousDistributionTimes GenesisDistributionTimes `json:"previous_distribution_times" yaml:"previous_distribution_times"`
} }
// NewGenesisState returns a new genesis state // NewGenesisState returns a new genesis state
func NewGenesisState(params Params, previousBlockTime time.Time, previousDistTimes GenesisDistributionTimes) GenesisState { func NewGenesisState(params Params, previousBlockTime time.Time) GenesisState {
return GenesisState{ return GenesisState{
Params: params, Params: params,
PreviousBlockTime: previousBlockTime, PreviousBlockTime: previousBlockTime,
PreviousDistributionTimes: previousDistTimes,
} }
} }
@ -37,7 +32,6 @@ func DefaultGenesisState() GenesisState {
return GenesisState{ return GenesisState{
Params: DefaultParams(), Params: DefaultParams(),
PreviousBlockTime: DefaultPreviousBlockTime, PreviousBlockTime: DefaultPreviousBlockTime,
PreviousDistributionTimes: DefaultDistributionTimes,
} }
} }
@ -51,14 +45,6 @@ func (gs GenesisState) Validate() error {
if gs.PreviousBlockTime.Equal(time.Time{}) { if gs.PreviousBlockTime.Equal(time.Time{}) {
return fmt.Errorf("previous block time not set") return fmt.Errorf("previous block time not set")
} }
for _, gdt := range gs.PreviousDistributionTimes {
if gdt.PreviousDistributionTime.Equal(time.Time{}) {
return fmt.Errorf("previous distribution time not set for %s", gdt.Denom)
}
if err := sdk.ValidateDenom(gdt.Denom); err != nil {
return err
}
}
return nil return nil
} }
@ -73,12 +59,3 @@ func (gs GenesisState) Equal(gs2 GenesisState) bool {
func (gs GenesisState) IsEmpty() bool { func (gs GenesisState) IsEmpty() bool {
return gs.Equal(GenesisState{}) return gs.Equal(GenesisState{})
} }
// GenesisDistributionTime stores the previous distribution time and its corresponding denom
type GenesisDistributionTime struct {
Denom string `json:"denom" yaml:"denom"`
PreviousDistributionTime time.Time `json:"previous_distribution_time" yaml:"previous_distribution_time"`
}
// GenesisDistributionTimes slice of GenesisDistributionTime
type GenesisDistributionTimes []GenesisDistributionTime

View File

@ -5,8 +5,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/hard/types" "github.com/kava-labs/kava/x/hard/types"
@ -20,7 +18,6 @@ func (suite *GenesisTestSuite) TestGenesisValidation() {
type args struct { type args struct {
params types.Params params types.Params
pbt time.Time pbt time.Time
pdts types.GenesisDistributionTimes
} }
testCases := []struct { testCases := []struct {
name string name string
@ -33,7 +30,6 @@ func (suite *GenesisTestSuite) TestGenesisValidation() {
args: args{ args: args{
params: types.DefaultParams(), params: types.DefaultParams(),
pbt: types.DefaultPreviousBlockTime, pbt: types.DefaultPreviousBlockTime,
pdts: types.DefaultDistributionTimes,
}, },
expectPass: true, expectPass: true,
expectedErr: "", expectedErr: "",
@ -41,23 +37,8 @@ func (suite *GenesisTestSuite) TestGenesisValidation() {
{ {
name: "valid", name: "valid",
args: args{ args: args{
params: types.NewParams( params: types.NewParams(true, types.DefaultMoneyMarkets, types.DefaultCheckLtvIndexCount),
true,
types.DistributionSchedules{
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.OneDec()), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("1.5")), types.NewMultiplier(types.Medium, 24, sdk.MustNewDecFromStr("3"))}),
},
types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
time.Hour*24,
),
},
types.DefaultMoneyMarkets,
types.DefaultCheckLtvIndexCount,
),
pbt: time.Date(2020, 10, 8, 12, 0, 0, 0, time.UTC), pbt: time.Date(2020, 10, 8, 12, 0, 0, 0, time.UTC),
pdts: types.GenesisDistributionTimes{
{PreviousDistributionTime: time.Date(2020, 10, 8, 12, 0, 0, 0, time.UTC), Denom: "bnb"},
},
}, },
expectPass: true, expectPass: true,
expectedErr: "", expectedErr: "",
@ -65,55 +46,16 @@ func (suite *GenesisTestSuite) TestGenesisValidation() {
{ {
name: "invalid previous blocktime", name: "invalid previous blocktime",
args: args{ args: args{
params: types.NewParams( params: types.NewParams(true, types.DefaultMoneyMarkets, types.DefaultCheckLtvIndexCount),
true,
types.DistributionSchedules{
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.OneDec()), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("1.5")), types.NewMultiplier(types.Medium, 24, sdk.MustNewDecFromStr("3"))}),
},
types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
time.Hour*24,
),
},
types.DefaultMoneyMarkets,
types.DefaultCheckLtvIndexCount,
),
pbt: time.Time{}, pbt: time.Time{},
pdts: types.GenesisDistributionTimes{
{PreviousDistributionTime: time.Date(2020, 10, 8, 12, 0, 0, 0, time.UTC), Denom: "bnb"},
},
}, },
expectPass: false, expectPass: false,
expectedErr: "previous block time not set", expectedErr: "previous block time not set",
}, },
{
name: "invalid previous distribution time",
args: args{
params: types.NewParams(
true,
types.DistributionSchedules{
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.OneDec()), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("1.5")), types.NewMultiplier(types.Medium, 24, sdk.MustNewDecFromStr("3"))}),
},
types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
time.Hour*24,
),
},
types.DefaultMoneyMarkets,
types.DefaultCheckLtvIndexCount,
),
pbt: time.Date(2020, 10, 8, 12, 0, 0, 0, time.UTC),
pdts: types.GenesisDistributionTimes{
{PreviousDistributionTime: time.Time{}, Denom: "bnb"},
},
},
expectPass: false,
expectedErr: "previous distribution time not set",
},
} }
for _, tc := range testCases { for _, tc := range testCases {
suite.Run(tc.name, func() { suite.Run(tc.name, func() {
gs := types.NewGenesisState(tc.args.params, tc.args.pbt, tc.args.pdts) gs := types.NewGenesisState(tc.args.params, tc.args.pbt)
err := gs.Validate() err := gs.Validate()
if tc.expectPass { if tc.expectPass {
suite.NoError(err) suite.NoError(err)

53
x/hard/types/hooks.go Normal file
View File

@ -0,0 +1,53 @@
package types
import sdk "github.com/cosmos/cosmos-sdk/types"
// MultiHARDHooks combine multiple HARD hooks, all hook functions are run in array sequence
type MultiHARDHooks []HARDHooks
// NewMultiHARDHooks returns a new MultiHARDHooks
func NewMultiHARDHooks(hooks ...HARDHooks) MultiHARDHooks {
return hooks
}
// AfterDepositCreated runs after a deposit is created
func (h MultiHARDHooks) AfterDepositCreated(ctx sdk.Context, deposit Deposit) {
for i := range h {
h[i].AfterDepositCreated(ctx, deposit)
}
}
// BeforeDepositModified runs before a deposit is modified
func (h MultiHARDHooks) BeforeDepositModified(ctx sdk.Context, deposit Deposit) {
for i := range h {
h[i].BeforeDepositModified(ctx, deposit)
}
}
// AfterDepositModified runs after a deposit is modified
func (h MultiHARDHooks) AfterDepositModified(ctx sdk.Context, deposit Deposit) {
for i := range h {
h[i].AfterDepositModified(ctx, deposit)
}
}
// AfterBorrowCreated runs after a borrow is created
func (h MultiHARDHooks) AfterBorrowCreated(ctx sdk.Context, borrow Borrow) {
for i := range h {
h[i].AfterBorrowCreated(ctx, borrow)
}
}
// BeforeBorrowModified runs before a borrow is modified
func (h MultiHARDHooks) BeforeBorrowModified(ctx sdk.Context, borrow Borrow) {
for i := range h {
h[i].BeforeBorrowModified(ctx, borrow)
}
}
// AfterBorrowModified runs after a borrow is modified
func (h MultiHARDHooks) AfterBorrowModified(ctx sdk.Context, borrow Borrow) {
for i := range h {
h[i].AfterBorrowModified(ctx, borrow)
}
}

View File

@ -35,9 +35,7 @@ const (
var ( var (
PreviousBlockTimeKey = []byte{0x01} PreviousBlockTimeKey = []byte{0x01}
PreviousDelegationDistributionKey = []byte{0x02}
DepositsKeyPrefix = []byte{0x03} DepositsKeyPrefix = []byte{0x03}
ClaimsKeyPrefix = []byte{0x04}
BorrowsKeyPrefix = []byte{0x05} BorrowsKeyPrefix = []byte{0x05}
BorrowedCoinsPrefix = []byte{0x06} BorrowedCoinsPrefix = []byte{0x06}
SuppliedCoinsPrefix = []byte{0x07} SuppliedCoinsPrefix = []byte{0x07}
@ -55,16 +53,6 @@ func DepositTypeIteratorKey(denom string) []byte {
return createKey([]byte(denom)) return createKey([]byte(denom))
} }
// ClaimKey key of a specific deposit in the store
func ClaimKey(depositType ClaimType, denom string, owner sdk.AccAddress) []byte {
return createKey([]byte(depositType), sep, []byte(denom), sep, owner)
}
// ClaimTypeIteratorKey returns an interator prefix for interating over claims by deposit type and denom
func ClaimTypeIteratorKey(depositType ClaimType, denom string) []byte {
return createKey([]byte(depositType), sep, []byte(denom))
}
// GetBorrowByLtvKey is used by the LTV index // GetBorrowByLtvKey is used by the LTV index
func GetBorrowByLtvKey(ltv sdk.Dec, borrower sdk.AccAddress) []byte { func GetBorrowByLtvKey(ltv sdk.Dec, borrower sdk.AccAddress) []byte {
return append(ltv.Bytes(), borrower...) return append(ltv.Bytes(), borrower...)

View File

@ -1,9 +1,7 @@
package types package types
import ( import (
"errors"
"fmt" "fmt"
"time"
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"
@ -14,14 +12,9 @@ import (
// Parameter keys and default values // Parameter keys and default values
var ( var (
KeyActive = []byte("Active") KeyActive = []byte("Active")
KeyLPSchedules = []byte("LPSchedules")
KeyDelegatorSchedule = []byte("DelegatorSchedule")
KeyMoneyMarkets = []byte("MoneyMarkets") KeyMoneyMarkets = []byte("MoneyMarkets")
KeyCheckLtvIndexCount = []byte("CheckLtvIndexCount") KeyCheckLtvIndexCount = []byte("CheckLtvIndexCount")
DefaultActive = true DefaultActive = true
DefaultGovSchedules = DistributionSchedules{}
DefaultLPSchedules = DistributionSchedules{}
DefaultDelegatorSchedules = DelegatorDistributionSchedules{}
DefaultMoneyMarkets = MoneyMarkets{} DefaultMoneyMarkets = MoneyMarkets{}
DefaultCheckLtvIndexCount = 10 DefaultCheckLtvIndexCount = 10
GovDenom = cdptypes.DefaultGovDenom GovDenom = cdptypes.DefaultGovDenom
@ -30,156 +23,10 @@ var (
// Params governance parameters for hard module // Params governance parameters for hard module
type Params struct { type Params struct {
Active bool `json:"active" yaml:"active"` Active bool `json:"active" yaml:"active"`
LiquidityProviderSchedules DistributionSchedules `json:"liquidity_provider_schedules" yaml:"liquidity_provider_schedules"`
DelegatorDistributionSchedules DelegatorDistributionSchedules `json:"delegator_distribution_schedules" yaml:"delegator_distribution_schedules"`
MoneyMarkets MoneyMarkets `json:"money_markets" yaml:"money_markets"` MoneyMarkets MoneyMarkets `json:"money_markets" yaml:"money_markets"`
CheckLtvIndexCount int `json:"check_ltv_index_count" yaml:"check_ltv_index_count"` CheckLtvIndexCount int `json:"check_ltv_index_count" yaml:"check_ltv_index_count"`
} }
// DistributionSchedule distribution schedule for liquidity providers
type DistributionSchedule struct {
Active bool `json:"active" yaml:"active"`
DepositDenom string `json:"deposit_denom" yaml:"deposit_denom"`
Start time.Time `json:"start" yaml:"start"`
End time.Time `json:"end" yaml:"end"`
RewardsPerSecond sdk.Coin `json:"rewards_per_second" yaml:"rewards_per_second"`
ClaimEnd time.Time `json:"claim_end" yaml:"claim_end"`
ClaimMultipliers Multipliers `json:"claim_multipliers" yaml:"claim_multipliers"`
}
// NewDistributionSchedule returns a new DistributionSchedule
func NewDistributionSchedule(active bool, denom string, start, end time.Time, reward sdk.Coin, claimEnd time.Time, multipliers Multipliers) DistributionSchedule {
return DistributionSchedule{
Active: active,
DepositDenom: denom,
Start: start,
End: end,
RewardsPerSecond: reward,
ClaimEnd: claimEnd,
ClaimMultipliers: multipliers,
}
}
// String implements fmt.Stringer
func (ds DistributionSchedule) String() string {
return fmt.Sprintf(`Liquidity Provider Distribution Schedule:
Deposit Denom: %s,
Start: %s,
End: %s,
Rewards Per Second: %s,
Claim End: %s,
Active: %t
`, ds.DepositDenom, ds.Start, ds.End, ds.RewardsPerSecond, ds.ClaimEnd, ds.Active)
}
// Validate performs a basic check of a distribution schedule.
func (ds DistributionSchedule) Validate() error {
if !ds.RewardsPerSecond.IsValid() {
return fmt.Errorf("invalid reward coins %s for %s", ds.RewardsPerSecond, ds.DepositDenom)
}
if !ds.RewardsPerSecond.IsPositive() {
return fmt.Errorf("reward amount must be positive, is %s for %s", ds.RewardsPerSecond, ds.DepositDenom)
}
if ds.RewardsPerSecond.Denom != "hard" {
return fmt.Errorf("reward denom should be hard, is %s", ds.RewardsPerSecond.Denom)
}
if ds.Start.IsZero() {
return errors.New("reward period start time cannot be 0")
}
if ds.End.IsZero() {
return errors.New("reward period end time cannot be 0")
}
if ds.Start.After(ds.End) {
return fmt.Errorf("end period time %s cannot be before start time %s", ds.End, ds.Start)
}
if ds.ClaimEnd.Before(ds.End) {
return fmt.Errorf("claim end time %s cannot be before end time %s", ds.ClaimEnd, ds.End)
}
for _, multiplier := range ds.ClaimMultipliers {
if err := multiplier.Validate(); err != nil {
return err
}
}
return nil
}
// DistributionSchedules slice of DistributionSchedule
type DistributionSchedules []DistributionSchedule
// Validate checks if all the LiquidityProviderSchedules are valid and there are no duplicated
// entries.
func (dss DistributionSchedules) Validate() error {
seenPeriods := make(map[string]bool)
for _, ds := range dss {
if seenPeriods[ds.DepositDenom] {
return fmt.Errorf("duplicated distribution provider schedule with deposit denom %s", ds.DepositDenom)
}
if err := ds.Validate(); err != nil {
return err
}
seenPeriods[ds.DepositDenom] = true
}
return nil
}
// String implements fmt.Stringer
func (dss DistributionSchedules) String() string {
out := "Distribution Schedules\n"
for _, ds := range dss {
out += fmt.Sprintf("%s\n", ds)
}
return out
}
// DelegatorDistributionSchedule distribution schedule for delegators
type DelegatorDistributionSchedule struct {
DistributionSchedule DistributionSchedule `json:"distribution_schedule" yaml:"distribution_schedule"`
DistributionFrequency time.Duration `json:"distribution_frequency" yaml:"distribution_frequency"`
}
// NewDelegatorDistributionSchedule returns a new DelegatorDistributionSchedule
func NewDelegatorDistributionSchedule(ds DistributionSchedule, frequency time.Duration) DelegatorDistributionSchedule {
return DelegatorDistributionSchedule{
DistributionSchedule: ds,
DistributionFrequency: frequency,
}
}
// Validate performs a basic check of a reward fields.
func (dds DelegatorDistributionSchedule) Validate() error {
if err := dds.DistributionSchedule.Validate(); err != nil {
return err
}
if dds.DistributionFrequency <= 0 {
return fmt.Errorf("distribution frequency should be positive, got %d", dds.DistributionFrequency)
}
return nil
}
// DelegatorDistributionSchedules slice of DelegatorDistributionSchedule
type DelegatorDistributionSchedules []DelegatorDistributionSchedule
// Validate checks if all the LiquidityProviderSchedules are valid and there are no duplicated
// entries.
func (dds DelegatorDistributionSchedules) Validate() error {
seenPeriods := make(map[string]bool)
for _, ds := range dds {
if seenPeriods[ds.DistributionSchedule.DepositDenom] {
return fmt.Errorf("duplicated liquidity provider schedule with deposit denom %s", ds.DistributionSchedule.DepositDenom)
}
if err := ds.Validate(); err != nil {
return err
}
seenPeriods[ds.DistributionSchedule.DepositDenom] = true
}
return nil
}
// Multiplier amount the claim rewards get increased by, along with how long the claim rewards are locked // Multiplier amount the claim rewards get increased by, along with how long the claim rewards are locked
type Multiplier struct { type Multiplier struct {
Name MultiplierName `json:"name" yaml:"name"` Name MultiplierName `json:"name" yaml:"name"`
@ -211,16 +58,6 @@ func (m Multiplier) Validate() error {
return nil return nil
} }
// GetMultiplier returns the named multiplier from the input distribution schedule
func (ds DistributionSchedule) GetMultiplier(name MultiplierName) (Multiplier, bool) {
for _, multiplier := range ds.ClaimMultipliers {
if multiplier.Name == name {
return multiplier, true
}
}
return Multiplier{}, false
}
// Multipliers slice of Multiplier // Multipliers slice of Multiplier
type Multipliers []Multiplier type Multipliers []Multiplier
@ -426,12 +263,9 @@ func (irm InterestRateModel) Equal(irmCompareTo InterestRateModel) bool {
type InterestRateModels []InterestRateModel type InterestRateModels []InterestRateModel
// NewParams returns a new params object // NewParams returns a new params object
func NewParams(active bool, lps DistributionSchedules, dds DelegatorDistributionSchedules, func NewParams(active bool, moneyMarkets MoneyMarkets, checkLtvIndexCount int) Params {
moneyMarkets MoneyMarkets, checkLtvIndexCount int) Params {
return Params{ return Params{
Active: active, Active: active,
LiquidityProviderSchedules: lps,
DelegatorDistributionSchedules: dds,
MoneyMarkets: moneyMarkets, MoneyMarkets: moneyMarkets,
CheckLtvIndexCount: checkLtvIndexCount, CheckLtvIndexCount: checkLtvIndexCount,
} }
@ -439,20 +273,16 @@ func NewParams(active bool, lps DistributionSchedules, dds DelegatorDistribution
// DefaultParams returns default params for hard module // DefaultParams returns default params for hard module
func DefaultParams() Params { func DefaultParams() Params {
return NewParams(DefaultActive, DefaultLPSchedules, DefaultDelegatorSchedules, return NewParams(DefaultActive, DefaultMoneyMarkets, DefaultCheckLtvIndexCount)
DefaultMoneyMarkets, DefaultCheckLtvIndexCount)
} }
// String implements fmt.Stringer // String implements fmt.Stringer
func (p Params) String() string { func (p Params) String() string {
return fmt.Sprintf(`Params: return fmt.Sprintf(`Params:
Active: %t Active: %t
Liquidity Provider Distribution Schedules %s
Delegator Distribution Schedule %s
Money Markets %v Money Markets %v
Check LTV Index Count: %v`, Check LTV Index Count: %v`,
p.Active, p.LiquidityProviderSchedules, p.DelegatorDistributionSchedules, p.Active, p.MoneyMarkets, p.CheckLtvIndexCount)
p.MoneyMarkets, p.CheckLtvIndexCount)
} }
// ParamKeyTable Key declaration for parameters // ParamKeyTable Key declaration for parameters
@ -464,8 +294,6 @@ func ParamKeyTable() params.KeyTable {
func (p *Params) ParamSetPairs() params.ParamSetPairs { func (p *Params) ParamSetPairs() params.ParamSetPairs {
return params.ParamSetPairs{ return params.ParamSetPairs{
params.NewParamSetPair(KeyActive, &p.Active, validateActiveParam), params.NewParamSetPair(KeyActive, &p.Active, validateActiveParam),
params.NewParamSetPair(KeyLPSchedules, &p.LiquidityProviderSchedules, validateLPParams),
params.NewParamSetPair(KeyDelegatorSchedule, &p.DelegatorDistributionSchedules, validateDelegatorParams),
params.NewParamSetPair(KeyMoneyMarkets, &p.MoneyMarkets, validateMoneyMarketParams), params.NewParamSetPair(KeyMoneyMarkets, &p.MoneyMarkets, validateMoneyMarketParams),
params.NewParamSetPair(KeyCheckLtvIndexCount, &p.CheckLtvIndexCount, validateCheckLtvIndexCount), params.NewParamSetPair(KeyCheckLtvIndexCount, &p.CheckLtvIndexCount, validateCheckLtvIndexCount),
} }
@ -477,14 +305,6 @@ func (p Params) Validate() error {
return err return err
} }
if err := validateDelegatorParams(p.DelegatorDistributionSchedules); err != nil {
return err
}
if err := validateLPParams(p.LiquidityProviderSchedules); err != nil {
return err
}
if err := validateMoneyMarketParams(p.MoneyMarkets); err != nil { if err := validateMoneyMarketParams(p.MoneyMarkets); err != nil {
return err return err
} }
@ -501,31 +321,6 @@ func validateActiveParam(i interface{}) error {
return nil return nil
} }
func validateLPParams(i interface{}) error {
dss, ok := i.(DistributionSchedules)
if !ok {
return fmt.Errorf("invalid parameter type: %T", i)
}
for _, ds := range dss {
err := ds.Validate()
if err != nil {
return err
}
}
return nil
}
func validateDelegatorParams(i interface{}) error {
dds, ok := i.(DelegatorDistributionSchedules)
if !ok {
return fmt.Errorf("invalid parameter type: %T", i)
}
return dds.Validate()
}
func validateMoneyMarketParams(i interface{}) error { func validateMoneyMarketParams(i interface{}) error {
mm, ok := i.(MoneyMarkets) mm, ok := i.(MoneyMarkets)
if !ok { if !ok {

View File

@ -3,12 +3,9 @@ package types_test
import ( import (
"strings" "strings"
"testing" "testing"
"time"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/hard/types" "github.com/kava-labs/kava/x/hard/types"
) )
@ -18,9 +15,6 @@ type ParamTestSuite struct {
func (suite *ParamTestSuite) TestParamValidation() { func (suite *ParamTestSuite) TestParamValidation() {
type args struct { type args struct {
lps types.DistributionSchedules
gds types.DistributionSchedules
dds types.DelegatorDistributionSchedules
mms types.MoneyMarkets mms types.MoneyMarkets
ltvCounter int ltvCounter int
active bool active bool
@ -34,8 +28,6 @@ func (suite *ParamTestSuite) TestParamValidation() {
{ {
name: "default", name: "default",
args: args{ args: args{
lps: types.DefaultLPSchedules,
dds: types.DefaultDelegatorSchedules,
active: types.DefaultActive, active: types.DefaultActive,
}, },
expectPass: true, expectPass: true,
@ -44,14 +36,6 @@ func (suite *ParamTestSuite) TestParamValidation() {
{ {
name: "valid", name: "valid",
args: args{ args: args{
lps: types.DistributionSchedules{
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
},
dds: types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
time.Hour*24,
),
},
mms: types.DefaultMoneyMarkets, mms: types.DefaultMoneyMarkets,
ltvCounter: 10, ltvCounter: 10,
active: true, active: true,
@ -59,28 +43,10 @@ func (suite *ParamTestSuite) TestParamValidation() {
expectPass: true, expectPass: true,
expectedErr: "", expectedErr: "",
}, },
{
name: "invalid rewards",
args: args{
lps: types.DistributionSchedules{
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("busd", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
},
dds: types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
time.Hour*24,
),
},
mms: types.DefaultMoneyMarkets,
ltvCounter: 10,
active: true,
},
expectPass: false,
expectedErr: "reward denom should be hard",
},
} }
for _, tc := range testCases { for _, tc := range testCases {
suite.Run(tc.name, func() { suite.Run(tc.name, func() {
params := types.NewParams(tc.args.active, tc.args.lps, tc.args.dds, tc.args.mms, tc.args.ltvCounter) params := types.NewParams(tc.args.active, tc.args.mms, tc.args.ltvCounter)
err := params.Validate() err := params.Validate()
if tc.expectPass { if tc.expectPass {
suite.NoError(err) suite.NoError(err)

View File

@ -8,8 +8,21 @@ 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) {
for _, rp := range k.GetParams(ctx).RewardPeriods { params := k.GetParams(ctx)
err := k.AccumulateRewards(ctx, rp) for _, rp := range params.USDXMintingRewardPeriods {
err := k.AccumulateUSDXMintingRewards(ctx, rp)
if err != nil {
panic(err)
}
}
for _, rp := range params.HardSupplyRewardPeriods {
err := k.AccumulateHardSupplyRewards(ctx, rp)
if err != nil {
panic(err)
}
}
for _, rp := range params.HardBorrowRewardPeriods {
err := k.AccumulateHardBorrowRewards(ctx, rp)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -55,8 +55,8 @@ var (
RegisterCodec = types.RegisterCodec RegisterCodec = types.RegisterCodec
// variable aliases // variable aliases
BlockTimeKey = types.BlockTimeKey PreviousUSDXMintingRewardAccrualTimeKeyPrefix = types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix
ClaimKeyPrefix = types.ClaimKeyPrefix USDXMintingClaimKeyPrefix = types.USDXMintingClaimKeyPrefix
DefaultActive = types.DefaultActive DefaultActive = types.DefaultActive
DefaultClaimEnd = types.DefaultClaimEnd DefaultClaimEnd = types.DefaultClaimEnd
DefaultClaims = types.DefaultClaims DefaultClaims = types.DefaultClaims
@ -74,13 +74,12 @@ var (
ErrZeroClaim = types.ErrZeroClaim ErrZeroClaim = types.ErrZeroClaim
GovDenom = types.GovDenom GovDenom = types.GovDenom
IncentiveMacc = types.IncentiveMacc IncentiveMacc = types.IncentiveMacc
KeyActive = types.KeyActive
KeyClaimEnd = types.KeyClaimEnd KeyClaimEnd = types.KeyClaimEnd
KeyMultipliers = types.KeyMultipliers KeyMultipliers = types.KeyMultipliers
KeyRewards = types.KeyRewards KeyUSDXMintingRewardPeriods = types.KeyUSDXMintingRewardPeriods
ModuleCdc = types.ModuleCdc ModuleCdc = types.ModuleCdc
PrincipalDenom = types.PrincipalDenom PrincipalDenom = types.PrincipalDenom
RewardFactorKey = types.RewardFactorKey USDXMintingRewardFactorKeyPrefix = types.USDXMintingRewardFactorKeyPrefix
USDXMintingRewardDenom = types.USDXMintingRewardDenom USDXMintingRewardDenom = types.USDXMintingRewardDenom
) )
@ -89,6 +88,7 @@ type (
Keeper = keeper.Keeper Keeper = keeper.Keeper
AccountKeeper = types.AccountKeeper AccountKeeper = types.AccountKeeper
CDPHooks = types.CDPHooks CDPHooks = types.CDPHooks
HARDHooks = types.HARDHooks
CdpKeeper = types.CdpKeeper CdpKeeper = types.CdpKeeper
GenesisAccumulationTime = types.GenesisAccumulationTime GenesisAccumulationTime = types.GenesisAccumulationTime
GenesisAccumulationTimes = types.GenesisAccumulationTimes GenesisAccumulationTimes = types.GenesisAccumulationTimes

View File

@ -22,7 +22,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeep
panic(fmt.Sprintf("failed to validate %s genesis state: %s", types.ModuleName, err)) panic(fmt.Sprintf("failed to validate %s genesis state: %s", types.ModuleName, err))
} }
for _, rp := range gs.Params.RewardPeriods { for _, rp := range gs.Params.USDXMintingRewardPeriods {
_, found := cdpKeeper.GetCollateral(ctx, rp.CollateralType) _, found := cdpKeeper.GetCollateral(ctx, rp.CollateralType)
if !found { if !found {
panic(fmt.Sprintf("usdx minting collateral type %s not found in cdp collateral types", rp.CollateralType)) panic(fmt.Sprintf("usdx minting collateral type %s not found in cdp collateral types", rp.CollateralType))
@ -31,13 +31,15 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeep
k.SetParams(ctx, gs.Params) k.SetParams(ctx, gs.Params)
// TODO: previous hard module accrual times/indexes should be set here
for _, gat := range gs.PreviousAccumulationTimes { for _, gat := range gs.PreviousAccumulationTimes {
k.SetPreviousAccrualTime(ctx, gat.CollateralType, gat.PreviousAccumulationTime) k.SetPreviousUSDXMintingAccrualTime(ctx, gat.CollateralType, gat.PreviousAccumulationTime)
k.SetRewardFactor(ctx, gat.CollateralType, gat.RewardFactor) k.SetUSDXMintingRewardFactor(ctx, gat.CollateralType, gat.RewardFactor)
} }
for _, claim := range gs.USDXMintingClaims { for _, claim := range gs.USDXMintingClaims {
k.SetClaim(ctx, claim) k.SetUSDXMintingClaim(ctx, claim)
} }
} }
@ -46,16 +48,16 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeep
func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState { func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState {
params := k.GetParams(ctx) params := k.GetParams(ctx)
claims := k.GetAllClaims(ctx) claims := k.GetAllUSDXMintingClaims(ctx)
var gats GenesisAccumulationTimes var gats GenesisAccumulationTimes
for _, rp := range params.RewardPeriods { for _, rp := range params.USDXMintingRewardPeriods {
pat, found := k.GetPreviousAccrualTime(ctx, rp.CollateralType) pat, found := k.GetPreviousUSDXMintingAccrualTime(ctx, rp.CollateralType)
if !found { if !found {
pat = ctx.BlockTime() pat = ctx.BlockTime()
} }
factor, found := k.GetRewardFactor(ctx, rp.CollateralType) factor, found := k.GetUSDXMintingRewardFactor(ctx, rp.CollateralType)
if !found { if !found {
factor = sdk.ZeroDec() factor = sdk.ZeroDec()
} }

View File

@ -44,6 +44,9 @@ func (suite *HandlerTestSuite) SetupTest() {
authGS := app.NewAuthGenState(addrs, coins) authGS := app.NewAuthGenState(addrs, coins)
incentiveGS := incentive.NewGenesisState( incentiveGS := incentive.NewGenesisState(
incentive.NewParams( incentive.NewParams(
incentive.RewardPeriods{incentive.NewRewardPeriod(true, "bnb-a", time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 12, 15, 14, 0, 0, 0, time.UTC), c("ukava", 122354))},
incentive.RewardPeriods{incentive.NewRewardPeriod(true, "bnb-a", time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 12, 15, 14, 0, 0, 0, time.UTC), c("ukava", 122354))},
incentive.RewardPeriods{incentive.NewRewardPeriod(true, "bnb-a", time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 12, 15, 14, 0, 0, 0, time.UTC), c("ukava", 122354))},
incentive.RewardPeriods{incentive.NewRewardPeriod(true, "bnb-a", time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 12, 15, 14, 0, 0, 0, time.UTC), c("ukava", 122354))}, incentive.RewardPeriods{incentive.NewRewardPeriod(true, "bnb-a", time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 12, 15, 14, 0, 0, 0, time.UTC), c("ukava", 122354))},
incentive.Multipliers{incentive.NewMultiplier(incentive.MultiplierName("small"), 1, d("0.25")), incentive.NewMultiplier(incentive.MultiplierName("large"), 12, d("1.0"))}, incentive.Multipliers{incentive.NewMultiplier(incentive.MultiplierName("small"), 1, d("0.25")), incentive.NewMultiplier(incentive.MultiplierName("large"), 12, d("1.0"))},
time.Date(2025, 12, 15, 14, 0, 0, 0, time.UTC), time.Date(2025, 12, 15, 14, 0, 0, 0, time.UTC),
@ -66,7 +69,7 @@ func (suite *HandlerTestSuite) addClaim() {
suite.Require().NoError(err) suite.Require().NoError(err)
c1 := incentive.NewUSDXMintingClaim(suite.addrs[0], c("ukava", 1000000), types.RewardIndexes{types.NewRewardIndex("bnb-s", sdk.ZeroDec())}) c1 := incentive.NewUSDXMintingClaim(suite.addrs[0], c("ukava", 1000000), types.RewardIndexes{types.NewRewardIndex("bnb-s", sdk.ZeroDec())})
suite.NotPanics(func() { suite.NotPanics(func() {
suite.keeper.SetClaim(suite.ctx, c1) suite.keeper.SetUSDXMintingClaim(suite.ctx, c1)
}) })
} }

View File

@ -8,6 +8,7 @@ import (
"github.com/kava-labs/kava/app" "github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/cdp" "github.com/kava-labs/kava/x/cdp"
"github.com/kava-labs/kava/x/incentive" "github.com/kava-labs/kava/x/incentive"
"github.com/kava-labs/kava/x/incentive/types"
"github.com/kava-labs/kava/x/pricefeed" "github.com/kava-labs/kava/x/pricefeed"
) )
@ -155,6 +156,9 @@ func NewIncentiveGenState(previousAccumTime, endTime time.Time, rewardPeriods ..
genesis := incentive.NewGenesisState( genesis := incentive.NewGenesisState(
incentive.NewParams( incentive.NewParams(
rewardPeriods, rewardPeriods,
types.RewardPeriods{},
types.RewardPeriods{},
types.RewardPeriods{},
incentive.Multipliers{ incentive.Multipliers{
incentive.NewMultiplier(incentive.Small, 1, d("0.25")), incentive.NewMultiplier(incentive.Small, 1, d("0.25")),
incentive.NewMultiplier(incentive.Large, 12, d("1.0")), incentive.NewMultiplier(incentive.Large, 12, d("1.0")),

View File

@ -3,6 +3,7 @@ package keeper
import ( import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
cdptypes "github.com/kava-labs/kava/x/cdp/types" cdptypes "github.com/kava-labs/kava/x/cdp/types"
hardtypes "github.com/kava-labs/kava/x/hard/types"
) )
// Hooks wrapper struct for hooks // Hooks wrapper struct for hooks
@ -11,18 +12,49 @@ type Hooks struct {
} }
var _ cdptypes.CDPHooks = Hooks{} var _ cdptypes.CDPHooks = Hooks{}
var _ hardtypes.HARDHooks = Hooks{}
// Hooks create new incentive hooks // Hooks create new incentive hooks
func (k Keeper) Hooks() Hooks { return Hooks{k} } func (k Keeper) Hooks() Hooks { return Hooks{k} }
// AfterCDPCreated function that runs after a cdp is created // AfterCDPCreated function that runs after a cdp is created
func (h Hooks) AfterCDPCreated(ctx sdk.Context, cdp cdptypes.CDP) { func (h Hooks) AfterCDPCreated(ctx sdk.Context, cdp cdptypes.CDP) {
h.k.InitializeClaim(ctx, cdp) h.k.InitializeUSDXMintingClaim(ctx, cdp)
} }
// BeforeCDPModified function that runs before a cdp is modified // BeforeCDPModified function that runs before a cdp is modified
// note that this is called immediately after interest is synchronized, and so could potentially // note that this is called immediately after interest is synchronized, and so could potentially
// be called AfterCDPInterestUpdated or something like that, if we we're to expand the scope of cdp hooks // be called AfterCDPInterestUpdated or something like that, if we we're to expand the scope of cdp hooks
func (h Hooks) BeforeCDPModified(ctx sdk.Context, cdp cdptypes.CDP) { func (h Hooks) BeforeCDPModified(ctx sdk.Context, cdp cdptypes.CDP) {
h.k.SynchronizeReward(ctx, cdp) h.k.SynchronizeUSDXMintingReward(ctx, cdp)
}
// AfterDepositCreated function that runs after a deposit is created
func (h Hooks) AfterDepositCreated(ctx sdk.Context, deposit hardtypes.Deposit) {
h.k.InitializeHardSupplyReward(ctx, deposit)
}
// BeforeDepositModified function that runs before a deposit is modified
func (h Hooks) BeforeDepositModified(ctx sdk.Context, deposit hardtypes.Deposit) {
h.k.SynchronizeHardSupplyReward(ctx, deposit)
}
// AfterDepositModified function that runs after a deposit is modified
func (h Hooks) AfterDepositModified(ctx sdk.Context, deposit hardtypes.Deposit) {
h.k.UpdateHardSupplyIndexDenoms(ctx, deposit)
}
// AfterBorrowCreated function that runs after a borrow is created
func (h Hooks) AfterBorrowCreated(ctx sdk.Context, borrow hardtypes.Borrow) {
h.k.InitializeHardBorrowReward(ctx, borrow)
}
// BeforeBorrowModified function that runs before a borrow is modified
func (h Hooks) BeforeBorrowModified(ctx sdk.Context, borrow hardtypes.Borrow) {
h.k.SynchronizeHardBorrowReward(ctx, borrow)
}
// AfterBorrowModified function that runs after a borrow is modified
func (h Hooks) AfterBorrowModified(ctx sdk.Context, borrow hardtypes.Borrow) {
h.k.UpdateHardBorrowIndexDenoms(ctx, borrow)
} }

View File

@ -7,7 +7,7 @@ import (
"github.com/kava-labs/kava/app" "github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/cdp" "github.com/kava-labs/kava/x/cdp"
"github.com/kava-labs/kava/x/incentive" "github.com/kava-labs/kava/x/hard"
"github.com/kava-labs/kava/x/pricefeed" "github.com/kava-labs/kava/x/pricefeed"
) )
@ -104,6 +104,7 @@ func NewPricefeedGenStateMulti() app.GenesisState {
pfGenesis := pricefeed.GenesisState{ pfGenesis := pricefeed.GenesisState{
Params: pricefeed.Params{ Params: pricefeed.Params{
Markets: []pricefeed.Market{ Markets: []pricefeed.Market{
{MarketID: "kava:usd", BaseAsset: "kava", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, {MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, {MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
{MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, {MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
@ -111,6 +112,12 @@ func NewPricefeedGenStateMulti() app.GenesisState {
}, },
}, },
PostedPrices: []pricefeed.PostedPrice{ PostedPrices: []pricefeed.PostedPrice{
{
MarketID: "kava:usd",
OracleAddress: sdk.AccAddress{},
Price: sdk.MustNewDecFromStr("2.00"),
Expiry: time.Now().Add(1 * time.Hour),
},
{ {
MarketID: "btc:usd", MarketID: "btc:usd",
OracleAddress: sdk.AccAddress{}, OracleAddress: sdk.AccAddress{},
@ -140,73 +147,26 @@ func NewPricefeedGenStateMulti() app.GenesisState {
return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)} return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)}
} }
func NewIncentiveGenState(previousAccumTime, endTime time.Time, rewardPeriods ...incentive.RewardPeriod) app.GenesisState { func NewHardGenStateMulti() app.GenesisState {
var accumulationTimes incentive.GenesisAccumulationTimes KAVA_CF := int64(1000000)
for _, rp := range rewardPeriods { USDX_CF := int64(1000000)
accumulationTimes = append( BNB_CF := int64(100000000)
accumulationTimes, BTCB_CF := int64(100000000)
incentive.NewGenesisAccumulationTime(
rp.CollateralType,
previousAccumTime,
sdk.ZeroDec(),
),
)
}
genesis := incentive.NewGenesisState(
incentive.NewParams(
rewardPeriods,
incentive.Multipliers{
incentive.NewMultiplier(incentive.Small, 1, d("0.25")),
incentive.NewMultiplier(incentive.Large, 12, d("1.0")),
},
endTime,
),
accumulationTimes,
incentive.USDXMintingClaims{},
)
return app.GenesisState{incentive.ModuleName: incentive.ModuleCdc.MustMarshalJSON(genesis)}
}
func NewCDPGenStateHighInterest() app.GenesisState { loanToValue, _ := sdk.NewDecFromStr("0.6")
cdpGenesis := cdp.GenesisState{ borrowLimit := sdk.NewDec(1000000000000000)
Params: cdp.Params{
GlobalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000), hardGS := hard.NewGenesisState(hard.NewParams(
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold, true,
SurplusAuctionLot: cdp.DefaultSurplusLot, hard.MoneyMarkets{
DebtAuctionThreshold: cdp.DefaultDebtThreshold, hard.NewMoneyMarket("usdx", hard.NewBorrowLimit(false, borrowLimit, loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), hard.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
DebtAuctionLot: cdp.DefaultDebtLot, hard.NewMoneyMarket("ukava", hard.NewBorrowLimit(false, borrowLimit, loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), hard.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
CollateralParams: cdp.CollateralParams{ hard.NewMoneyMarket("bnb", hard.NewBorrowLimit(false, borrowLimit, loanToValue), "bnb:usd", sdk.NewInt(1000000), sdk.NewInt(BNB_CF*1000), hard.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
{ hard.NewMoneyMarket("btcb", hard.NewBorrowLimit(false, borrowLimit, loanToValue), "btc:usd", sdk.NewInt(1000000), sdk.NewInt(BTCB_CF*1000), hard.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
Denom: "bnb", hard.NewMoneyMarket("xrp", hard.NewBorrowLimit(false, borrowLimit, loanToValue), "xrp:usd", sdk.NewInt(1000000), sdk.NewInt(BTCB_CF*1000), hard.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
Type: "bnb-a",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 500000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000051034942716"), // 500% APR
LiquidationPenalty: d("0.05"),
AuctionSize: i(50000000000),
Prefix: 0x22,
SpotMarketID: "bnb:usd",
LiquidationMarketID: "bnb:usd",
ConversionFactor: i(8),
},
},
DebtParam: cdp.DebtParam{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: i(6),
DebtFloor: i(10000000),
},
},
StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousAccumulationTimes: cdp.GenesisAccumulationTimes{
cdp.NewGenesisAccumulationTime("bnb-a", time.Time{}, sdk.OneDec()),
},
TotalPrincipals: cdp.GenesisTotalPrincipals{
cdp.NewGenesisTotalPrincipal("bnb-a", sdk.ZeroInt()),
}, },
} 0, // LTV counter
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)} ), hard.DefaultPreviousBlockTime)
return app.GenesisState{hard.ModuleName: hard.ModuleCdc.MustMarshalJSON(hardGS)}
} }

View File

@ -16,6 +16,7 @@ type Keeper struct {
accountKeeper types.AccountKeeper accountKeeper types.AccountKeeper
cdc *codec.Codec cdc *codec.Codec
cdpKeeper types.CdpKeeper cdpKeeper types.CdpKeeper
hardKeeper types.HardKeeper
key sdk.StoreKey key sdk.StoreKey
paramSubspace subspace.Subspace paramSubspace subspace.Subspace
supplyKeeper types.SupplyKeeper supplyKeeper types.SupplyKeeper
@ -24,22 +25,23 @@ type Keeper struct {
// NewKeeper creates a new keeper // NewKeeper creates a new keeper
func NewKeeper( func NewKeeper(
cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, sk types.SupplyKeeper, cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, sk types.SupplyKeeper,
cdpk types.CdpKeeper, ak types.AccountKeeper, cdpk types.CdpKeeper, hk types.HardKeeper, ak types.AccountKeeper,
) Keeper { ) Keeper {
return Keeper{ return Keeper{
accountKeeper: ak, accountKeeper: ak,
cdc: cdc, cdc: cdc,
cdpKeeper: cdpk, cdpKeeper: cdpk,
hardKeeper: hk,
key: key, key: key,
paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()), paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()),
supplyKeeper: sk, supplyKeeper: sk,
} }
} }
// GetClaim returns the claim in the store corresponding the the input address collateral type and id and a boolean for if the claim was found // GetUSDXMintingClaim returns the claim in the store corresponding the the input address collateral type and id and a boolean for if the claim was found
func (k Keeper) GetClaim(ctx sdk.Context, addr sdk.AccAddress) (types.USDXMintingClaim, bool) { func (k Keeper) GetUSDXMintingClaim(ctx sdk.Context, addr sdk.AccAddress) (types.USDXMintingClaim, bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix) store := prefix.NewStore(ctx.KVStore(k.key), types.USDXMintingClaimKeyPrefix)
bz := store.Get(addr) bz := store.Get(addr)
if bz == nil { if bz == nil {
return types.USDXMintingClaim{}, false return types.USDXMintingClaim{}, false
@ -49,23 +51,23 @@ func (k Keeper) GetClaim(ctx sdk.Context, addr sdk.AccAddress) (types.USDXMintin
return c, true return c, true
} }
// SetClaim sets the claim in the store corresponding to the input address, collateral type, and id // SetUSDXMintingClaim sets the claim in the store corresponding to the input address, collateral type, and id
func (k Keeper) SetClaim(ctx sdk.Context, c types.USDXMintingClaim) { func (k Keeper) SetUSDXMintingClaim(ctx sdk.Context, c types.USDXMintingClaim) {
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix) store := prefix.NewStore(ctx.KVStore(k.key), types.USDXMintingClaimKeyPrefix)
bz := k.cdc.MustMarshalBinaryBare(c) bz := k.cdc.MustMarshalBinaryBare(c)
store.Set(c.Owner, bz) store.Set(c.Owner, bz)
} }
// DeleteClaim deletes the claim in the store corresponding to the input address, collateral type, and id // DeleteUSDXMintingClaim deletes the claim in the store corresponding to the input address, collateral type, and id
func (k Keeper) DeleteClaim(ctx sdk.Context, owner sdk.AccAddress) { func (k Keeper) DeleteUSDXMintingClaim(ctx sdk.Context, owner sdk.AccAddress) {
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix) store := prefix.NewStore(ctx.KVStore(k.key), types.USDXMintingClaimKeyPrefix)
store.Delete(owner) store.Delete(owner)
} }
// IterateClaims iterates over all claim objects in the store and preforms a callback function // IterateUSDXMintingClaims iterates over all claim objects in the store and preforms a callback function
func (k Keeper) IterateClaims(ctx sdk.Context, cb func(c types.USDXMintingClaim) (stop bool)) { func (k Keeper) IterateUSDXMintingClaims(ctx sdk.Context, cb func(c types.USDXMintingClaim) (stop bool)) {
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix) store := prefix.NewStore(ctx.KVStore(k.key), types.USDXMintingClaimKeyPrefix)
iterator := sdk.KVStorePrefixIterator(store, []byte{}) iterator := sdk.KVStorePrefixIterator(store, []byte{})
defer iterator.Close() defer iterator.Close()
for ; iterator.Valid(); iterator.Next() { for ; iterator.Valid(); iterator.Next() {
@ -77,19 +79,19 @@ func (k Keeper) IterateClaims(ctx sdk.Context, cb func(c types.USDXMintingClaim)
} }
} }
// GetAllClaims returns all Claim objects in the store // GetAllUSDXMintingClaims returns all Claim objects in the store
func (k Keeper) GetAllClaims(ctx sdk.Context) types.USDXMintingClaims { func (k Keeper) GetAllUSDXMintingClaims(ctx sdk.Context) types.USDXMintingClaims {
cs := types.USDXMintingClaims{} cs := types.USDXMintingClaims{}
k.IterateClaims(ctx, func(c types.USDXMintingClaim) (stop bool) { k.IterateUSDXMintingClaims(ctx, func(c types.USDXMintingClaim) (stop bool) {
cs = append(cs, c) cs = append(cs, c)
return false return false
}) })
return cs return cs
} }
// GetPreviousAccrualTime returns the last time a collateral type accrued rewards // GetPreviousUSDXMintingAccrualTime returns the last time a collateral type accrued USDX minting rewards
func (k Keeper) GetPreviousAccrualTime(ctx sdk.Context, ctype string) (blockTime time.Time, found bool) { func (k Keeper) GetPreviousUSDXMintingAccrualTime(ctx sdk.Context, ctype string) (blockTime time.Time, found bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.BlockTimeKey) store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix)
bz := store.Get([]byte(ctype)) bz := store.Get([]byte(ctype))
if bz == nil { if bz == nil {
return time.Time{}, false return time.Time{}, false
@ -98,15 +100,15 @@ func (k Keeper) GetPreviousAccrualTime(ctx sdk.Context, ctype string) (blockTime
return blockTime, true return blockTime, true
} }
// SetPreviousAccrualTime sets the last time a collateral type accrued rewards // SetPreviousUSDXMintingAccrualTime sets the last time a collateral type accrued USDX minting rewards
func (k Keeper) SetPreviousAccrualTime(ctx sdk.Context, ctype string, blockTime time.Time) { func (k Keeper) SetPreviousUSDXMintingAccrualTime(ctx sdk.Context, ctype string, blockTime time.Time) {
store := prefix.NewStore(ctx.KVStore(k.key), types.BlockTimeKey) store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix)
store.Set([]byte(ctype), k.cdc.MustMarshalBinaryBare(blockTime)) store.Set([]byte(ctype), k.cdc.MustMarshalBinaryBare(blockTime))
} }
// IterateAccrualTimes iterates over all previous accrual times and preforms a callback function // IterateUSDXMintingAccrualTimes iterates over all previous USDX minting accrual times and preforms a callback function
func (k Keeper) IterateAccrualTimes(ctx sdk.Context, cb func(string, time.Time) (stop bool)) { func (k Keeper) IterateUSDXMintingAccrualTimes(ctx sdk.Context, cb func(string, time.Time) (stop bool)) {
store := prefix.NewStore(ctx.KVStore(k.key), types.BlockTimeKey) store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix)
iterator := sdk.KVStorePrefixIterator(store, []byte{}) iterator := sdk.KVStorePrefixIterator(store, []byte{})
defer iterator.Close() defer iterator.Close()
for ; iterator.Valid(); iterator.Next() { for ; iterator.Valid(); iterator.Next() {
@ -120,9 +122,9 @@ func (k Keeper) IterateAccrualTimes(ctx sdk.Context, cb func(string, time.Time)
} }
} }
// GetRewardFactor returns the current reward factor for an individual collateral type // GetUSDXMintingRewardFactor returns the current reward factor for an individual collateral type
func (k Keeper) GetRewardFactor(ctx sdk.Context, ctype string) (factor sdk.Dec, found bool) { func (k Keeper) GetUSDXMintingRewardFactor(ctx sdk.Context, ctype string) (factor sdk.Dec, found bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardFactorKey) store := prefix.NewStore(ctx.KVStore(k.key), types.USDXMintingRewardFactorKeyPrefix)
bz := store.Get([]byte(ctype)) bz := store.Get([]byte(ctype))
if bz == nil { if bz == nil {
return sdk.ZeroDec(), false return sdk.ZeroDec(), false
@ -131,8 +133,146 @@ func (k Keeper) GetRewardFactor(ctx sdk.Context, ctype string) (factor sdk.Dec,
return factor, true return factor, true
} }
// SetRewardFactor sets the current reward factor for an individual collateral type // SetUSDXMintingRewardFactor sets the current reward factor for an individual collateral type
func (k Keeper) SetRewardFactor(ctx sdk.Context, ctype string, factor sdk.Dec) { func (k Keeper) SetUSDXMintingRewardFactor(ctx sdk.Context, ctype string, factor sdk.Dec) {
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardFactorKey) store := prefix.NewStore(ctx.KVStore(k.key), types.USDXMintingRewardFactorKeyPrefix)
store.Set([]byte(ctype), k.cdc.MustMarshalBinaryBare(factor)) store.Set([]byte(ctype), k.cdc.MustMarshalBinaryBare(factor))
} }
// GetHardLiquidityProviderClaim returns the claim in the store corresponding the the input address collateral type and id and a boolean for if the claim was found
func (k Keeper) GetHardLiquidityProviderClaim(ctx sdk.Context, addr sdk.AccAddress) (types.HardLiquidityProviderClaim, bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.HardLiquidityClaimKeyPrefix)
bz := store.Get(addr)
if bz == nil {
return types.HardLiquidityProviderClaim{}, false
}
var c types.HardLiquidityProviderClaim
k.cdc.MustUnmarshalBinaryBare(bz, &c)
return c, true
}
// SetHardLiquidityProviderClaim sets the claim in the store corresponding to the input address, collateral type, and id
func (k Keeper) SetHardLiquidityProviderClaim(ctx sdk.Context, c types.HardLiquidityProviderClaim) {
store := prefix.NewStore(ctx.KVStore(k.key), types.HardLiquidityClaimKeyPrefix)
bz := k.cdc.MustMarshalBinaryBare(c)
store.Set(c.Owner, bz)
}
// DeleteHardLiquidityProviderClaim deletes the claim in the store corresponding to the input address, collateral type, and id
func (k Keeper) DeleteHardLiquidityProviderClaim(ctx sdk.Context, owner sdk.AccAddress) {
store := prefix.NewStore(ctx.KVStore(k.key), types.HardLiquidityClaimKeyPrefix)
store.Delete(owner)
}
// IterateHardLiquidityProviderClaims iterates over all claim objects in the store and preforms a callback function
func (k Keeper) IterateHardLiquidityProviderClaims(ctx sdk.Context, cb func(c types.HardLiquidityProviderClaim) (stop bool)) {
store := prefix.NewStore(ctx.KVStore(k.key), types.HardLiquidityClaimKeyPrefix)
iterator := sdk.KVStorePrefixIterator(store, []byte{})
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var c types.HardLiquidityProviderClaim
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &c)
if cb(c) {
break
}
}
}
// GetAllHardLiquidityProviderClaims returns all Claim objects in the store
func (k Keeper) GetAllHardLiquidityProviderClaims(ctx sdk.Context) types.HardLiquidityProviderClaims {
cs := types.HardLiquidityProviderClaims{}
k.IterateHardLiquidityProviderClaims(ctx, func(c types.HardLiquidityProviderClaim) (stop bool) {
cs = append(cs, c)
return false
})
return cs
}
// SetHardSupplyRewardFactor sets the current interest factor for an individual market
func (k Keeper) SetHardSupplyRewardFactor(ctx sdk.Context, denom string, borrowIndex sdk.Dec) {
store := prefix.NewStore(ctx.KVStore(k.key), types.HardSupplyRewardFactorKeyPrefix)
bz := k.cdc.MustMarshalBinaryBare(borrowIndex)
store.Set([]byte(denom), bz)
}
// GetHardSupplyRewardFactor returns the current interest factor for an individual market
func (k Keeper) GetHardSupplyRewardFactor(ctx sdk.Context, denom string) (sdk.Dec, bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.HardSupplyRewardFactorKeyPrefix)
bz := store.Get([]byte(denom))
if bz == nil {
return sdk.ZeroDec(), false
}
var interestFactor sdk.Dec
k.cdc.MustUnmarshalBinaryBare(bz, &interestFactor)
return interestFactor, true
}
// SetHardBorrowRewardFactor sets the current interest factor for an individual market
func (k Keeper) SetHardBorrowRewardFactor(ctx sdk.Context, denom string, borrowIndex sdk.Dec) {
store := prefix.NewStore(ctx.KVStore(k.key), types.HardBorrowRewardFactorKeyPrefix)
bz := k.cdc.MustMarshalBinaryBare(borrowIndex)
store.Set([]byte(denom), bz)
}
// GetHardBorrowRewardFactor returns the current interest factor for an individual market
func (k Keeper) GetHardBorrowRewardFactor(ctx sdk.Context, denom string) (sdk.Dec, bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.HardBorrowRewardFactorKeyPrefix)
bz := store.Get([]byte(denom))
if bz == nil {
return sdk.ZeroDec(), false
}
var interestFactor sdk.Dec
k.cdc.MustUnmarshalBinaryBare(bz, &interestFactor)
return interestFactor, true
}
// GetHardDelegatorRewardFactor returns the current reward factor for an individual collateral type
func (k Keeper) GetHardDelegatorRewardFactor(ctx sdk.Context, ctype string) (factor sdk.Dec, found bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.HardDelegatorRewardFactorKeyPrefix)
bz := store.Get([]byte(ctype))
if bz == nil {
return sdk.ZeroDec(), false
}
k.cdc.MustUnmarshalBinaryBare(bz, &factor)
return factor, true
}
// SetHardDelegatorRewardFactor sets the current reward factor for an individual collateral type
func (k Keeper) SetHardDelegatorRewardFactor(ctx sdk.Context, ctype string, factor sdk.Dec) {
store := prefix.NewStore(ctx.KVStore(k.key), types.HardDelegatorRewardFactorKeyPrefix)
store.Set([]byte(ctype), k.cdc.MustMarshalBinaryBare(factor))
}
// GetPreviousHardSupplyRewardAccrualTime returns the last time a denom accrued Hard protocol supply-side rewards
func (k Keeper) GetPreviousHardSupplyRewardAccrualTime(ctx sdk.Context, denom string) (blockTime time.Time, found bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousHardSupplyRewardAccrualTimeKeyPrefix)
bz := store.Get([]byte(denom))
if bz == nil {
return time.Time{}, false
}
k.cdc.MustUnmarshalBinaryBare(bz, &blockTime)
return blockTime, true
}
// SetPreviousHardSupplyRewardAccrualTime sets the last time a denom accrued Hard protocol supply-side rewards
func (k Keeper) SetPreviousHardSupplyRewardAccrualTime(ctx sdk.Context, denom string, blockTime time.Time) {
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousHardSupplyRewardAccrualTimeKeyPrefix)
store.Set([]byte(denom), k.cdc.MustMarshalBinaryBare(blockTime))
}
// GetPreviousHardBorrowRewardAccrualTime returns the last time a denom accrued Hard protocol borrow-side rewards
func (k Keeper) GetPreviousHardBorrowRewardAccrualTime(ctx sdk.Context, denom string) (blockTime time.Time, found bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousHardBorrowRewardAccrualTimeKeyPrefix)
bz := store.Get([]byte(denom))
if bz == nil {
return time.Time{}, false
}
k.cdc.MustUnmarshalBinaryBare(bz, &blockTime)
return blockTime, true
}
// SetPreviousHardBorrowRewardAccrualTime sets the last time a denom accrued Hard protocol borrow-side rewards
func (k Keeper) SetPreviousHardBorrowRewardAccrualTime(ctx sdk.Context, denom string, blockTime time.Time) {
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousHardBorrowRewardAccrualTimeKeyPrefix)
store.Set([]byte(denom), k.cdc.MustMarshalBinaryBare(blockTime))
}

View File

@ -15,6 +15,7 @@ import (
tmtime "github.com/tendermint/tendermint/types/time" tmtime "github.com/tendermint/tendermint/types/time"
"github.com/kava-labs/kava/app" "github.com/kava-labs/kava/app"
hardkeeper "github.com/kava-labs/kava/x/hard/keeper"
"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"
) )
@ -24,6 +25,7 @@ type KeeperTestSuite struct {
suite.Suite suite.Suite
keeper keeper.Keeper keeper keeper.Keeper
hardKeeper hardkeeper.Keeper
app app.TestApp app app.TestApp
ctx sdk.Context ctx sdk.Context
addrs []sdk.AccAddress addrs []sdk.AccAddress
@ -52,45 +54,41 @@ func (suite *KeeperTestSuite) getModuleAccount(name string) supplyexported.Modul
return sk.GetModuleAccount(suite.ctx, name) return sk.GetModuleAccount(suite.ctx, name)
} }
func (suite *KeeperTestSuite) TestGetSetDeleteClaim() { func (suite *KeeperTestSuite) TestGetSetDeleteUSDXMintingClaim() {
c := types.NewUSDXMintingClaim(suite.addrs[0], c("ukava", 1000000), types.RewardIndexes{types.NewRewardIndex("bnb-a", sdk.ZeroDec())}) c := types.NewUSDXMintingClaim(suite.addrs[0], c("ukava", 1000000), types.RewardIndexes{types.NewRewardIndex("bnb-a", sdk.ZeroDec())})
_, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0]) _, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0])
suite.Require().False(found) suite.Require().False(found)
suite.Require().NotPanics(func() { suite.Require().NotPanics(func() {
suite.keeper.SetClaim(suite.ctx, c) suite.keeper.SetUSDXMintingClaim(suite.ctx, c)
}) })
testC, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0]) testC, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0])
suite.Require().True(found) suite.Require().True(found)
suite.Require().Equal(c, testC) suite.Require().Equal(c, testC)
suite.Require().NotPanics(func() { suite.Require().NotPanics(func() {
suite.keeper.DeleteClaim(suite.ctx, suite.addrs[0]) suite.keeper.DeleteUSDXMintingClaim(suite.ctx, suite.addrs[0])
}) })
_, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0]) _, found = suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0])
suite.Require().False(found) suite.Require().False(found)
} }
func (suite *KeeperTestSuite) TestIterateClaims() { func (suite *KeeperTestSuite) TestIterateUSDXMintingClaims() {
for i := 0; i < len(suite.addrs); i++ { for i := 0; i < len(suite.addrs); i++ {
c := types.NewUSDXMintingClaim(suite.addrs[i], c("ukava", 100000), types.RewardIndexes{types.NewRewardIndex("bnb-a", sdk.ZeroDec())}) c := types.NewUSDXMintingClaim(suite.addrs[i], c("ukava", 100000), types.RewardIndexes{types.NewRewardIndex("bnb-a", sdk.ZeroDec())})
suite.Require().NotPanics(func() { suite.Require().NotPanics(func() {
suite.keeper.SetClaim(suite.ctx, c) suite.keeper.SetUSDXMintingClaim(suite.ctx, c)
}) })
} }
claims := types.USDXMintingClaims{} claims := types.USDXMintingClaims{}
suite.keeper.IterateClaims(suite.ctx, func(c types.USDXMintingClaim) bool { suite.keeper.IterateUSDXMintingClaims(suite.ctx, func(c types.USDXMintingClaim) bool {
claims = append(claims, c) claims = append(claims, c)
return false return false
}) })
suite.Require().Equal(len(suite.addrs), len(claims)) suite.Require().Equal(len(suite.addrs), len(claims))
claims = suite.keeper.GetAllClaims(suite.ctx) claims = suite.keeper.GetAllUSDXMintingClaims(suite.ctx)
suite.Require().Equal(len(suite.addrs), len(claims)) suite.Require().Equal(len(suite.addrs), len(claims))
} }
func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}
func createPeriodicVestingAccount(origVesting sdk.Coins, periods vesting.Periods, startTime, endTime int64) (*vesting.PeriodicVestingAccount, error) { func createPeriodicVestingAccount(origVesting sdk.Coins, periods vesting.Periods, startTime, endTime int64) (*vesting.PeriodicVestingAccount, error) {
_, addr := app.GeneratePrivKeyAddressPairs(1) _, addr := app.GeneratePrivKeyAddressPairs(1)
bacc := auth.NewBaseAccountWithAddress(addr[0]) bacc := auth.NewBaseAccountWithAddress(addr[0])
@ -112,3 +110,7 @@ func i(in int64) sdk.Int { return sdk.NewInt(in) }
func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) } func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) }
func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) }
func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) }
func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}

View File

@ -20,10 +20,10 @@ func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
k.paramSubspace.SetParamSet(ctx, &params) k.paramSubspace.SetParamSet(ctx, &params)
} }
// GetRewardPeriod returns the reward period with the specified collateral type if it's found in the params // GetUSDXMintingRewardPeriod returns the reward period with the specified collateral type if it's found in the params
func (k Keeper) GetRewardPeriod(ctx sdk.Context, collateralType string) (types.RewardPeriod, bool) { func (k Keeper) GetUSDXMintingRewardPeriod(ctx sdk.Context, collateralType string) (types.RewardPeriod, bool) {
params := k.GetParams(ctx) params := k.GetParams(ctx)
for _, rp := range params.RewardPeriods { for _, rp := range params.USDXMintingRewardPeriods {
if rp.CollateralType == collateralType { if rp.CollateralType == collateralType {
return rp, true return rp, true
} }
@ -31,6 +31,28 @@ func (k Keeper) GetRewardPeriod(ctx sdk.Context, collateralType string) (types.R
return types.RewardPeriod{}, false return types.RewardPeriod{}, false
} }
// GetHardSupplyRewardPeriod returns the reward period with the specified collateral type if it's found in the params
func (k Keeper) GetHardSupplyRewardPeriod(ctx sdk.Context, denom string) (types.RewardPeriod, bool) {
params := k.GetParams(ctx)
for _, rp := range params.HardSupplyRewardPeriods {
if rp.CollateralType == denom {
return rp, true
}
}
return types.RewardPeriod{}, false
}
// GetHardBorrowRewardPeriod returns the reward period with the specified collateral type if it's found in the params
func (k Keeper) GetHardBorrowRewardPeriod(ctx sdk.Context, denom string) (types.RewardPeriod, bool) {
params := k.GetParams(ctx)
for _, rp := range params.HardBorrowRewardPeriods {
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 // 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) { func (k Keeper) GetMultiplier(ctx sdk.Context, name types.MultiplierName) (types.Multiplier, bool) {
params := k.GetParams(ctx) params := k.GetParams(ctx)

View File

@ -14,7 +14,7 @@ import (
// ClaimReward sends the reward amount to the input address and zero's out the claim in the store // ClaimReward sends the reward amount to the input address and zero's out the claim in the store
func (k Keeper) ClaimReward(ctx sdk.Context, addr sdk.AccAddress, multiplierName types.MultiplierName) error { func (k Keeper) ClaimReward(ctx sdk.Context, addr sdk.AccAddress, multiplierName types.MultiplierName) error {
claim, found := k.GetClaim(ctx, addr) claim, found := k.GetUSDXMintingClaim(ctx, addr)
if !found { if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", addr) return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", addr)
} }

View File

@ -86,18 +86,21 @@ func (suite *KeeperTestSuite) TestPayoutClaim() {
} }
for _, tc := range testCases { for _, tc := range testCases {
suite.Run(tc.name, func() { suite.Run(tc.name, func() {
suite.SetupWithCDPGenState() suite.SetupWithGenState()
suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime)
// setup incentive state // setup incentive state
params := types.NewParams( params := types.NewParams(
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
tc.args.multipliers, tc.args.multipliers,
tc.args.initialTime.Add(time.Hour*24*365*5), tc.args.initialTime.Add(time.Hour*24*365*5),
) )
suite.keeper.SetParams(suite.ctx, params) suite.keeper.SetParams(suite.ctx, params)
suite.keeper.SetPreviousAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime) suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime)
suite.keeper.SetRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec()) suite.keeper.SetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec())
// setup account state // setup account state
sk := suite.app.GetSupplyKeeper() sk := suite.app.GetSupplyKeeper()
@ -115,15 +118,15 @@ func (suite *KeeperTestSuite) TestPayoutClaim() {
err = cdpKeeper.AddCdp(suite.ctx, suite.addrs[0], tc.args.initialCollateral, tc.args.initialPrincipal, tc.args.ctype) err = cdpKeeper.AddCdp(suite.ctx, suite.addrs[0], tc.args.initialCollateral, tc.args.initialPrincipal, tc.args.ctype)
suite.Require().NoError(err) suite.Require().NoError(err)
claim, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0]) claim, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0])
suite.Require().True(found) suite.Require().True(found)
suite.Require().Equal(sdk.ZeroDec(), claim.RewardIndexes[0].RewardFactor) suite.Require().Equal(sdk.ZeroDec(), claim.RewardIndexes[0].RewardFactor)
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed)) updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed))
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
rewardPeriod, found := suite.keeper.GetRewardPeriod(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.AccumulateRewards(suite.ctx, rewardPeriod) err = suite.keeper.AccumulateUSDXMintingRewards(suite.ctx, rewardPeriod)
suite.Require().NoError(err) suite.Require().NoError(err)
err = suite.keeper.ClaimReward(suite.ctx, suite.addrs[0], tc.args.multiplier) err = suite.keeper.ClaimReward(suite.ctx, suite.addrs[0], tc.args.multiplier)
@ -140,7 +143,7 @@ func (suite *KeeperTestSuite) TestPayoutClaim() {
suite.Require().Equal(tc.args.expectedPeriods, vacc.VestingPeriods) suite.Require().Equal(tc.args.expectedPeriods, vacc.VestingPeriods)
} }
claim, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0]) claim, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0])
fmt.Println(claim) fmt.Println(claim)
suite.Require().True(found) suite.Require().True(found)
suite.Require().Equal(c("ukava", 0), claim.Reward) suite.Require().Equal(c("ukava", 0), claim.Reward)

View File

@ -46,10 +46,10 @@ func queryGetClaims(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, e
} }
var claims types.USDXMintingClaims var claims types.USDXMintingClaims
if len(requestParams.Owner) > 0 { if len(requestParams.Owner) > 0 {
claim, _ := k.GetClaim(ctx, requestParams.Owner) claim, _ := k.GetUSDXMintingClaim(ctx, requestParams.Owner)
claims = append(claims, claim) claims = append(claims, claim)
} else { } else {
claims = k.GetAllClaims(ctx) claims = k.GetAllUSDXMintingClaims(ctx)
} }
var paginatedClaims types.USDXMintingClaims var paginatedClaims types.USDXMintingClaims

View File

@ -1,24 +1,22 @@
package keeper package keeper
import ( import (
"fmt"
"math" "math"
"time" "time"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
cdptypes "github.com/kava-labs/kava/x/cdp/types" 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/types" "github.com/kava-labs/kava/x/incentive/types"
) )
// AccumulateRewards updates the rewards accumulated for the input reward period // AccumulateUSDXMintingRewards updates the rewards accumulated for the input reward period
func (k Keeper) AccumulateRewards(ctx sdk.Context, rewardPeriod types.RewardPeriod) error { func (k Keeper) AccumulateUSDXMintingRewards(ctx sdk.Context, rewardPeriod types.RewardPeriod) error {
if !rewardPeriod.Active { previousAccrualTime, found := k.GetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType)
k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
return nil
}
previousAccrualTime, found := k.GetPreviousAccrualTime(ctx, rewardPeriod.CollateralType)
if !found { if !found {
k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
return nil return nil
} }
timeElapsed := CalculateTimeElapsed(rewardPeriod, ctx.BlockTime(), previousAccrualTime) timeElapsed := CalculateTimeElapsed(rewardPeriod, ctx.BlockTime(), previousAccrualTime)
@ -26,50 +24,135 @@ func (k Keeper) AccumulateRewards(ctx sdk.Context, rewardPeriod types.RewardPeri
return nil return nil
} }
if rewardPeriod.RewardsPerSecond.Amount.IsZero() { if rewardPeriod.RewardsPerSecond.Amount.IsZero() {
k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
return nil return nil
} }
totalPrincipal := k.cdpKeeper.GetTotalPrincipal(ctx, rewardPeriod.CollateralType, types.PrincipalDenom).ToDec() totalPrincipal := k.cdpKeeper.GetTotalPrincipal(ctx, rewardPeriod.CollateralType, types.PrincipalDenom).ToDec()
if totalPrincipal.IsZero() { if totalPrincipal.IsZero() {
k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
return nil return nil
} }
newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount) newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount)
cdpFactor, found := k.cdpKeeper.GetInterestFactor(ctx, rewardPeriod.CollateralType) cdpFactor, found := k.cdpKeeper.GetInterestFactor(ctx, rewardPeriod.CollateralType)
if !found { if !found {
k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
return nil return nil
} }
rewardFactor := newRewards.ToDec().Mul(cdpFactor).Quo(totalPrincipal) rewardFactor := newRewards.ToDec().Mul(cdpFactor).Quo(totalPrincipal)
previousRewardFactor, found := k.GetRewardFactor(ctx, rewardPeriod.CollateralType) previousRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, rewardPeriod.CollateralType)
if !found { if !found {
previousRewardFactor = sdk.ZeroDec() previousRewardFactor = sdk.ZeroDec()
} }
newRewardFactor := previousRewardFactor.Add(rewardFactor) newRewardFactor := previousRewardFactor.Add(rewardFactor)
k.SetRewardFactor(ctx, rewardPeriod.CollateralType, newRewardFactor) k.SetUSDXMintingRewardFactor(ctx, rewardPeriod.CollateralType, newRewardFactor)
k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime()) k.SetPreviousUSDXMintingAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
return nil return nil
} }
// InitializeClaim creates or updates a claim such that no new rewards are accrued, but any existing rewards are not lost. // AccumulateHardBorrowRewards updates the rewards accumulated for the input reward period
func (k Keeper) AccumulateHardBorrowRewards(ctx sdk.Context, rewardPeriod types.RewardPeriod) error {
previousAccrualTime, found := k.GetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType)
if !found {
k.SetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
return nil
}
timeElapsed := CalculateTimeElapsed(rewardPeriod, ctx.BlockTime(), previousAccrualTime)
if timeElapsed.IsZero() {
return nil
}
if rewardPeriod.RewardsPerSecond.Amount.IsZero() {
k.SetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
return nil
}
totalBorrowedCoins, foundTotalBorrowedCoins := k.hardKeeper.GetBorrowedCoins(ctx)
if foundTotalBorrowedCoins {
totalBorrowed := totalBorrowedCoins.AmountOf(rewardPeriod.CollateralType).ToDec()
if totalBorrowed.IsZero() {
k.SetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
return nil
}
newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount)
hardFactor, found := k.hardKeeper.GetBorrowInterestFactor(ctx, rewardPeriod.CollateralType)
if !found {
k.SetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
return nil
}
rewardFactor := newRewards.ToDec().Mul(hardFactor).Quo(totalBorrowed)
previousRewardFactor, found := k.GetHardBorrowRewardFactor(ctx, rewardPeriod.CollateralType)
if !found {
previousRewardFactor = sdk.ZeroDec()
}
newRewardFactor := previousRewardFactor.Add(rewardFactor)
k.SetHardBorrowRewardFactor(ctx, rewardPeriod.CollateralType, newRewardFactor)
}
k.SetPreviousHardBorrowRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
return nil
}
// AccumulateHardSupplyRewards updates the rewards accumulated for the input reward period
func (k Keeper) AccumulateHardSupplyRewards(ctx sdk.Context, rewardPeriod types.RewardPeriod) error {
previousAccrualTime, found := k.GetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType)
if !found {
k.SetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
return nil
}
timeElapsed := CalculateTimeElapsed(rewardPeriod, ctx.BlockTime(), previousAccrualTime)
if timeElapsed.IsZero() {
return nil
}
if rewardPeriod.RewardsPerSecond.Amount.IsZero() {
k.SetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
return nil
}
totalSuppliedCoins, foundTotalSuppliedCoins := k.hardKeeper.GetSuppliedCoins(ctx)
if foundTotalSuppliedCoins {
totalSupplied := totalSuppliedCoins.AmountOf(rewardPeriod.CollateralType).ToDec()
if totalSupplied.IsZero() {
k.SetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
return nil
}
newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount)
hardFactor, found := k.hardKeeper.GetSupplyInterestFactor(ctx, rewardPeriod.CollateralType)
if !found {
k.SetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
return nil
}
rewardFactor := newRewards.ToDec().Mul(hardFactor).Quo(totalSupplied)
previousRewardFactor, found := k.GetHardSupplyRewardFactor(ctx, rewardPeriod.CollateralType)
if !found {
previousRewardFactor = sdk.ZeroDec()
}
newRewardFactor := previousRewardFactor.Add(rewardFactor)
k.SetHardSupplyRewardFactor(ctx, rewardPeriod.CollateralType, newRewardFactor)
}
k.SetPreviousHardSupplyRewardAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
return nil
}
// InitializeUSDXMintingClaim creates or updates a claim such that no new rewards are accrued, but any existing rewards are not lost.
// this function should be called after a cdp is created. If a user previously had a cdp, then closed it, they shouldn't // this function should be called after a cdp is created. If a user previously had a cdp, then closed it, they shouldn't
// accrue rewards during the period the cdp was closed. By setting the reward factor to the current global reward factor, // accrue rewards during the period the cdp was closed. By setting the reward factor to the current global reward factor,
// any unclaimed rewards are preserved, but no new rewards are added. // any unclaimed rewards are preserved, but no new rewards are added.
func (k Keeper) InitializeClaim(ctx sdk.Context, cdp cdptypes.CDP) { func (k Keeper) InitializeUSDXMintingClaim(ctx sdk.Context, cdp cdptypes.CDP) {
_, found := k.GetRewardPeriod(ctx, cdp.Type) _, found := k.GetUSDXMintingRewardPeriod(ctx, cdp.Type)
if !found { if !found {
// this collateral type is not incentivized, do nothing // this collateral type is not incentivized, do nothing
return return
} }
rewardFactor, found := k.GetRewardFactor(ctx, cdp.Type) rewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, cdp.Type)
if !found { if !found {
rewardFactor = sdk.ZeroDec() rewardFactor = sdk.ZeroDec()
} }
claim, found := k.GetClaim(ctx, cdp.Owner) claim, found := k.GetUSDXMintingClaim(ctx, cdp.Owner)
if !found { // this is the owner's first usdx minting reward claim if !found { // this is the owner's first usdx minting reward claim
claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{types.NewRewardIndex(cdp.Type, rewardFactor)}) claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{types.NewRewardIndex(cdp.Type, rewardFactor)})
k.SetClaim(ctx, claim) k.SetUSDXMintingClaim(ctx, claim)
return return
} }
// the owner has an existing usdx minting reward claim // the owner has an existing usdx minting reward claim
@ -79,26 +162,26 @@ func (k Keeper) InitializeClaim(ctx sdk.Context, cdp cdptypes.CDP) {
} else { // the owner has a previous usdx minting reward for this collateral type } else { // the owner has a previous usdx minting reward for this collateral type
claim.RewardIndexes[index] = types.NewRewardIndex(cdp.Type, rewardFactor) claim.RewardIndexes[index] = types.NewRewardIndex(cdp.Type, rewardFactor)
} }
k.SetClaim(ctx, claim) k.SetUSDXMintingClaim(ctx, claim)
} }
// SynchronizeReward updates the claim object by adding any accumulated rewards and updating the reward index value. // SynchronizeUSDXMintingReward updates the claim object by adding any accumulated rewards and updating the reward index value.
// this should be called before a cdp is modified, immediately after the 'SynchronizeInterest' method is called in the cdp module // this should be called before a cdp is modified, immediately after the 'SynchronizeInterest' method is called in the cdp module
func (k Keeper) SynchronizeReward(ctx sdk.Context, cdp cdptypes.CDP) { func (k Keeper) SynchronizeUSDXMintingReward(ctx sdk.Context, cdp cdptypes.CDP) {
_, found := k.GetRewardPeriod(ctx, cdp.Type) _, found := k.GetUSDXMintingRewardPeriod(ctx, cdp.Type)
if !found { if !found {
// this collateral type is not incentivized, do nothing // this collateral type is not incentivized, do nothing
return return
} }
globalRewardFactor, found := k.GetRewardFactor(ctx, cdp.Type) globalRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, cdp.Type)
if !found { if !found {
globalRewardFactor = sdk.ZeroDec() globalRewardFactor = sdk.ZeroDec()
} }
claim, found := k.GetClaim(ctx, cdp.Owner) claim, found := k.GetUSDXMintingClaim(ctx, cdp.Owner)
if !found { if !found {
claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{types.NewRewardIndex(cdp.Type, globalRewardFactor)}) claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{types.NewRewardIndex(cdp.Type, globalRewardFactor)})
k.SetClaim(ctx, claim) k.SetUSDXMintingClaim(ctx, claim)
return return
} }
@ -106,7 +189,7 @@ func (k Keeper) SynchronizeReward(ctx sdk.Context, cdp cdptypes.CDP) {
index, hasRewardIndex := claim.HasRewardIndex(cdp.Type) index, hasRewardIndex := claim.HasRewardIndex(cdp.Type)
if !hasRewardIndex { // this is the owner's first usdx minting reward for this collateral type if !hasRewardIndex { // this is the owner's first usdx minting reward for this collateral type
claim.RewardIndexes = append(claim.RewardIndexes, types.NewRewardIndex(cdp.Type, globalRewardFactor)) claim.RewardIndexes = append(claim.RewardIndexes, types.NewRewardIndex(cdp.Type, globalRewardFactor))
k.SetClaim(ctx, claim) k.SetUSDXMintingClaim(ctx, claim)
return return
} }
userRewardFactor := claim.RewardIndexes[index].RewardFactor userRewardFactor := claim.RewardIndexes[index].RewardFactor
@ -115,21 +198,212 @@ func (k Keeper) SynchronizeReward(ctx sdk.Context, cdp cdptypes.CDP) {
return return
} }
claim.RewardIndexes[index].RewardFactor = globalRewardFactor claim.RewardIndexes[index].RewardFactor = globalRewardFactor
newRewardsAmount := cdp.GetTotalPrincipal().Amount.ToDec().Quo(cdp.InterestFactor).Mul(rewardsAccumulatedFactor).RoundInt() newRewardsAmount := rewardsAccumulatedFactor.Mul(cdp.GetTotalPrincipal().Amount.ToDec()).RoundInt()
if newRewardsAmount.IsZero() { if newRewardsAmount.IsZero() {
k.SetClaim(ctx, claim) k.SetUSDXMintingClaim(ctx, claim)
return return
} }
newRewardsCoin := sdk.NewCoin(types.USDXMintingRewardDenom, newRewardsAmount) newRewardsCoin := sdk.NewCoin(types.USDXMintingRewardDenom, newRewardsAmount)
claim.Reward = claim.Reward.Add(newRewardsCoin) claim.Reward = claim.Reward.Add(newRewardsCoin)
k.SetClaim(ctx, claim) k.SetUSDXMintingClaim(ctx, claim)
return return
} }
// InitializeHardSupplyReward initializes the supply-side of a hard liquidity provider claim
// by creating the claim and setting the supply reward factor index
func (k Keeper) InitializeHardSupplyReward(ctx sdk.Context, deposit hardtypes.Deposit) {
var supplyRewardIndexes types.RewardIndexes
for _, coin := range deposit.Amount {
_, rpFound := k.GetHardSupplyRewardPeriod(ctx, coin.Denom)
if !rpFound {
continue
}
supplyFactor, foundSupplyFactor := k.GetHardSupplyRewardFactor(ctx, coin.Denom)
if !foundSupplyFactor {
supplyFactor = sdk.ZeroDec()
}
supplyRewardIndexes = append(supplyRewardIndexes, types.NewRewardIndex(coin.Denom, supplyFactor))
}
claim, found := k.GetHardLiquidityProviderClaim(ctx, deposit.Depositor)
if found {
// Reset borrow reward indexes
claim.BorrowRewardIndexes = types.RewardIndexes{}
} else {
// Instantiate claim object
claim = types.NewHardLiquidityProviderClaim(deposit.Depositor,
sdk.NewCoin(types.HardLiquidityRewardDenom, sdk.ZeroInt()),
nil, nil, nil)
}
claim.SupplyRewardIndexes = supplyRewardIndexes
k.SetHardLiquidityProviderClaim(ctx, claim)
}
// SynchronizeHardSupplyReward updates the claim object by adding any accumulated rewards
// and updating the reward index value
func (k Keeper) SynchronizeHardSupplyReward(ctx sdk.Context, deposit hardtypes.Deposit) {
claim, found := k.GetHardLiquidityProviderClaim(ctx, deposit.Depositor)
if !found {
return
}
for _, coin := range deposit.Amount {
supplyFactor, found := k.GetHardSupplyRewardFactor(ctx, coin.Denom)
if !found {
fmt.Printf("\n[LOG]: %s does not have a supply factor", coin.Denom) // TODO: remove before production
continue
}
supplyIndex, hasSupplyRewardIndex := claim.HasSupplyRewardIndex(coin.Denom)
if !hasSupplyRewardIndex {
continue
}
userRewardFactor := claim.SupplyRewardIndexes[supplyIndex].RewardFactor
rewardsAccumulatedFactor := supplyFactor.Sub(userRewardFactor)
if rewardsAccumulatedFactor.IsZero() {
continue
}
claim.SupplyRewardIndexes[supplyIndex].RewardFactor = supplyFactor
newRewardsAmount := rewardsAccumulatedFactor.Mul(deposit.Amount.AmountOf(coin.Denom).ToDec()).RoundInt()
if newRewardsAmount.IsZero() || newRewardsAmount.IsNegative() {
continue
}
newRewardsCoin := sdk.NewCoin(types.HardLiquidityRewardDenom, newRewardsAmount)
claim.Reward = claim.Reward.Add(newRewardsCoin)
}
k.SetHardLiquidityProviderClaim(ctx, claim)
}
// InitializeHardBorrowReward initializes the borrow-side of a hard liquidity provider claim
// by creating the claim and setting the borrow reward factor index
func (k Keeper) InitializeHardBorrowReward(ctx sdk.Context, borrow hardtypes.Borrow) {
claim, found := k.GetHardLiquidityProviderClaim(ctx, borrow.Borrower)
if !found {
claim = types.NewHardLiquidityProviderClaim(borrow.Borrower,
sdk.NewCoin(types.HardLiquidityRewardDenom, sdk.ZeroInt()),
nil, nil, nil)
}
var borrowRewardIndexes types.RewardIndexes
for _, coin := range borrow.Amount {
_, rpFound := k.GetHardBorrowRewardPeriod(ctx, coin.Denom)
if !rpFound {
continue
}
borrowFactor, foundBorrowFactor := k.GetHardBorrowRewardFactor(ctx, coin.Denom)
if !foundBorrowFactor {
borrowFactor = sdk.ZeroDec()
}
borrowRewardIndexes = append(borrowRewardIndexes, types.NewRewardIndex(coin.Denom, borrowFactor))
}
claim.BorrowRewardIndexes = borrowRewardIndexes
k.SetHardLiquidityProviderClaim(ctx, claim)
}
// SynchronizeHardBorrowReward updates the claim object by adding any accumulated rewards
// and updating the reward index value
func (k Keeper) SynchronizeHardBorrowReward(ctx sdk.Context, borrow hardtypes.Borrow) {
claim, found := k.GetHardLiquidityProviderClaim(ctx, borrow.Borrower)
if !found {
return
}
for _, coin := range borrow.Amount {
borrowFactor, found := k.GetHardBorrowRewardFactor(ctx, coin.Denom)
if !found {
continue
}
borrowIndex, BorrowRewardIndex := claim.HasBorrowRewardIndex(coin.Denom)
if !BorrowRewardIndex {
continue
}
userRewardFactor := claim.BorrowRewardIndexes[borrowIndex].RewardFactor
rewardsAccumulatedFactor := borrowFactor.Sub(userRewardFactor)
if rewardsAccumulatedFactor.IsZero() {
continue
}
claim.BorrowRewardIndexes[borrowIndex].RewardFactor = borrowFactor
newRewardsAmount := rewardsAccumulatedFactor.Mul(borrow.Amount.AmountOf(coin.Denom).ToDec()).RoundInt()
if newRewardsAmount.IsZero() || newRewardsAmount.IsNegative() {
continue
}
newRewardsCoin := sdk.NewCoin(types.HardLiquidityRewardDenom, newRewardsAmount)
claim.Reward = claim.Reward.Add(newRewardsCoin)
}
k.SetHardLiquidityProviderClaim(ctx, claim)
}
// UpdateHardSupplyIndexDenoms adds any new deposit denoms to the claim's supply reward index
func (k Keeper) UpdateHardSupplyIndexDenoms(ctx sdk.Context, deposit hardtypes.Deposit) {
claim, found := k.GetHardLiquidityProviderClaim(ctx, deposit.Depositor)
if !found {
claim = types.NewHardLiquidityProviderClaim(deposit.Depositor,
sdk.NewCoin(types.HardLiquidityRewardDenom, sdk.ZeroInt()),
nil, nil, nil)
}
supplyRewardIndexes := claim.SupplyRewardIndexes
for _, coin := range deposit.Amount {
_, hasIndex := claim.HasSupplyRewardIndex(coin.Denom)
if !hasIndex {
supplyFactor, foundSupplyFactor := k.GetHardSupplyRewardFactor(ctx, coin.Denom)
if foundSupplyFactor {
supplyRewardIndexes = append(supplyRewardIndexes, types.NewRewardIndex(coin.Denom, supplyFactor))
}
}
}
if len(supplyRewardIndexes) == 0 {
return
}
claim.SupplyRewardIndexes = supplyRewardIndexes
k.SetHardLiquidityProviderClaim(ctx, claim)
}
// UpdateHardBorrowIndexDenoms adds any new borrow denoms to the claim's supply reward index
func (k Keeper) UpdateHardBorrowIndexDenoms(ctx sdk.Context, borrow hardtypes.Borrow) {
claim, found := k.GetHardLiquidityProviderClaim(ctx, borrow.Borrower)
if !found {
claim = types.NewHardLiquidityProviderClaim(borrow.Borrower,
sdk.NewCoin(types.HardLiquidityRewardDenom, sdk.ZeroInt()),
nil, nil, nil)
}
borrowRewardIndexes := claim.BorrowRewardIndexes
for _, coin := range borrow.Amount {
_, hasIndex := claim.HasBorrowRewardIndex(coin.Denom)
if !hasIndex {
borrowFactor, foundBorrowFactor := k.GetHardBorrowRewardFactor(ctx, coin.Denom)
if foundBorrowFactor {
borrowRewardIndexes = append(borrowRewardIndexes, types.NewRewardIndex(coin.Denom, borrowFactor))
}
}
}
if len(borrowRewardIndexes) == 0 {
return
}
claim.BorrowRewardIndexes = borrowRewardIndexes
k.SetHardLiquidityProviderClaim(ctx, claim)
}
// ZeroClaim zeroes out the claim object's rewards and returns the updated claim object // 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 { func (k Keeper) ZeroClaim(ctx sdk.Context, claim types.USDXMintingClaim) types.USDXMintingClaim {
claim.Reward = sdk.NewCoin(claim.Reward.Denom, sdk.ZeroInt()) claim.Reward = sdk.NewCoin(claim.Reward.Denom, sdk.ZeroInt())
k.SetClaim(ctx, claim) k.SetUSDXMintingClaim(ctx, claim)
return claim return claim
} }
@ -149,8 +423,8 @@ func (k Keeper) SynchronizeClaim(ctx sdk.Context, claim types.USDXMintingClaim)
// this function assumes a claim already exists, so don't call it if that's not the case // this function assumes a claim already exists, so don't call it if that's not the case
func (k Keeper) synchronizeRewardAndReturnClaim(ctx sdk.Context, cdp cdptypes.CDP) types.USDXMintingClaim { func (k Keeper) synchronizeRewardAndReturnClaim(ctx sdk.Context, cdp cdptypes.CDP) types.USDXMintingClaim {
k.SynchronizeReward(ctx, cdp) k.SynchronizeUSDXMintingReward(ctx, cdp)
claim, _ := k.GetClaim(ctx, cdp.Owner) claim, _ := k.GetUSDXMintingClaim(ctx, cdp.Owner)
return claim return claim
} }

View File

@ -1,23 +1,21 @@
package keeper_test package keeper_test
import ( import (
"fmt"
"testing"
"time" "time"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
tmtime "github.com/tendermint/tendermint/types/time" tmtime "github.com/tendermint/tendermint/types/time"
"github.com/kava-labs/kava/app" "github.com/kava-labs/kava/app"
cdpkeeper "github.com/kava-labs/kava/x/cdp/keeper"
cdptypes "github.com/kava-labs/kava/x/cdp/types" cdptypes "github.com/kava-labs/kava/x/cdp/types"
"github.com/kava-labs/kava/x/hard"
hardtypes "github.com/kava-labs/kava/x/hard/types"
"github.com/kava-labs/kava/x/incentive/types" "github.com/kava-labs/kava/x/incentive/types"
) )
func (suite *KeeperTestSuite) TestAccumulateRewards() { func (suite *KeeperTestSuite) TestAccumulateUSDXMintingRewards() {
type args struct { type args struct {
ctype string ctype string
rewardsPerSecond sdk.Coin rewardsPerSecond sdk.Coin
@ -67,7 +65,7 @@ func (suite *KeeperTestSuite) TestAccumulateRewards() {
} }
for _, tc := range testCases { for _, tc := range testCases {
suite.Run(tc.name, func() { suite.Run(tc.name, func() {
suite.SetupWithCDPGenState() suite.SetupWithGenState()
suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime)
// setup cdp state // setup cdp state
@ -76,28 +74,31 @@ func (suite *KeeperTestSuite) TestAccumulateRewards() {
// setup incentive state // setup incentive state
params := types.NewParams( params := types.NewParams(
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, 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"))}, 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), tc.args.initialTime.Add(time.Hour*24*365*5),
) )
suite.keeper.SetParams(suite.ctx, params) suite.keeper.SetParams(suite.ctx, params)
suite.keeper.SetPreviousAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime) suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime)
suite.keeper.SetRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec()) suite.keeper.SetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec())
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed)) updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed))
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime) suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
rewardPeriod, found := suite.keeper.GetRewardPeriod(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.AccumulateRewards(suite.ctx, rewardPeriod) err := suite.keeper.AccumulateUSDXMintingRewards(suite.ctx, rewardPeriod)
suite.Require().NoError(err) suite.Require().NoError(err)
rewardFactor, found := suite.keeper.GetRewardFactor(suite.ctx, tc.args.ctype) rewardFactor, found := suite.keeper.GetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype)
suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor) suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor)
}) })
} }
} }
func (suite *KeeperTestSuite) TestSyncRewards() { func (suite *KeeperTestSuite) TestSynchronizeUSDXMintingReward() {
type args struct { type args struct {
ctype string ctype string
rewardsPerSecond sdk.Coin rewardsPerSecond sdk.Coin
@ -143,18 +144,21 @@ func (suite *KeeperTestSuite) TestSyncRewards() {
} }
for _, tc := range testCases { for _, tc := range testCases {
suite.Run(tc.name, func() { suite.Run(tc.name, func() {
suite.SetupWithCDPGenState() suite.SetupWithGenState()
suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime) suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime)
// setup incentive state // setup incentive state
params := types.NewParams( params := types.NewParams(
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)}, types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, 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"))}, 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), tc.args.initialTime.Add(time.Hour*24*365*5),
) )
suite.keeper.SetParams(suite.ctx, params) suite.keeper.SetParams(suite.ctx, params)
suite.keeper.SetPreviousAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime) suite.keeper.SetPreviousUSDXMintingAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime)
suite.keeper.SetRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec()) suite.keeper.SetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec())
// setup account state // setup account state
sk := suite.app.GetSupplyKeeper() sk := suite.app.GetSupplyKeeper()
@ -166,7 +170,7 @@ func (suite *KeeperTestSuite) TestSyncRewards() {
err := cdpKeeper.AddCdp(suite.ctx, suite.addrs[0], tc.args.initialCollateral, tc.args.initialPrincipal, tc.args.ctype) err := cdpKeeper.AddCdp(suite.ctx, suite.addrs[0], tc.args.initialCollateral, tc.args.initialPrincipal, tc.args.ctype)
suite.Require().NoError(err) suite.Require().NoError(err)
claim, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0]) claim, found := suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0])
suite.Require().True(found) suite.Require().True(found)
suite.Require().Equal(sdk.ZeroDec(), claim.RewardIndexes[0].RewardFactor) suite.Require().Equal(sdk.ZeroDec(), claim.RewardIndexes[0].RewardFactor)
@ -177,9 +181,9 @@ func (suite *KeeperTestSuite) TestSyncRewards() {
updatedBlockTime := previousBlockTime.Add(time.Duration(int(time.Second) * t)) updatedBlockTime := previousBlockTime.Add(time.Duration(int(time.Second) * t))
previousBlockTime = updatedBlockTime previousBlockTime = updatedBlockTime
blockCtx := suite.ctx.WithBlockTime(updatedBlockTime) blockCtx := suite.ctx.WithBlockTime(updatedBlockTime)
rewardPeriod, found := suite.keeper.GetRewardPeriod(blockCtx, tc.args.ctype) rewardPeriod, found := suite.keeper.GetUSDXMintingRewardPeriod(blockCtx, tc.args.ctype)
suite.Require().True(found) suite.Require().True(found)
err := suite.keeper.AccumulateRewards(blockCtx, rewardPeriod) err := suite.keeper.AccumulateUSDXMintingRewards(blockCtx, rewardPeriod)
suite.Require().NoError(err) 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))
@ -187,137 +191,711 @@ func (suite *KeeperTestSuite) TestSyncRewards() {
cdp, found := cdpKeeper.GetCdpByOwnerAndCollateralType(suite.ctx, suite.addrs[0], tc.args.ctype) cdp, found := cdpKeeper.GetCdpByOwnerAndCollateralType(suite.ctx, suite.addrs[0], tc.args.ctype)
suite.Require().True(found) suite.Require().True(found)
suite.Require().NotPanics(func() { suite.Require().NotPanics(func() {
suite.keeper.SynchronizeReward(suite.ctx, cdp) suite.keeper.SynchronizeUSDXMintingReward(suite.ctx, cdp)
}) })
rewardFactor, found := suite.keeper.GetRewardFactor(suite.ctx, tc.args.ctype) rewardFactor, found := suite.keeper.GetUSDXMintingRewardFactor(suite.ctx, tc.args.ctype)
suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor) suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor)
claim, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0]) claim, found = suite.keeper.GetUSDXMintingClaim(suite.ctx, suite.addrs[0])
fmt.Println(claim)
suite.Require().True(found) suite.Require().True(found)
suite.Require().Equal(tc.args.expectedRewardFactor, claim.RewardIndexes[0].RewardFactor) suite.Require().Equal(tc.args.expectedRewardFactor, claim.RewardIndexes[0].RewardFactor)
suite.Require().Equal(tc.args.expectedRewards, claim.Reward) suite.Require().Equal(tc.args.expectedRewards, claim.Reward)
}) })
} }
} }
func TestRewardCalculation(t *testing.T) { func (suite *KeeperTestSuite) TestAccumulateHardBorrowRewards() {
type args struct {
// Test Params borrow sdk.Coin
ctype := "bnb-a" rewardsPerSecond sdk.Coin
initialTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) initialTime time.Time
rewardsPerSecond := c("ukava", 122_354) timeElapsed int
initialCollateral := c("bnb", 10_000_000_000) expectedRewardFactor sdk.Dec
initialPrincipal := c("usdx", 100_000_000)
oneYear := time.Hour * 24 * 365
rewardPeriod := types.NewRewardPeriod(
true,
ctype,
initialTime,
initialTime.Add(4*oneYear),
rewardsPerSecond,
)
// Setup app and module params
_, addrs := app.GeneratePrivKeyAddressPairs(5)
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: initialTime})
tApp.InitializeFromGenesisStates(
app.NewAuthGenState(addrs[:1], []sdk.Coins{cs(initialCollateral)}),
NewPricefeedGenStateMulti(),
NewCDPGenStateHighInterest(),
NewIncentiveGenState(initialTime, initialTime.Add(oneYear), rewardPeriod),
)
// Create a CDP
cdpKeeper := tApp.GetCDPKeeper()
err := cdpKeeper.AddCdp(
ctx,
addrs[0],
initialCollateral,
initialPrincipal,
ctype,
)
require.NoError(t, err)
// Calculate expected cdp reward using iteration
// Use 10 blocks, each a very long 630720s, to total 6307200s or 1/5th of a year
// The cdp stability fee is set to the max value 500%, so this time ensures the debt increases a significant amount (doubles)
// High stability fees increase the chance of catching calculation bugs.
blockTimes := newRepeatingSliceInt(630720, 10)
expectedCDPReward := sdk.ZeroDec() //c(rewardPeriod.RewardsPerSecond.Denom, 0)
for _, bt := range blockTimes {
ctx = ctx.WithBlockTime(ctx.BlockTime().Add(time.Duration(int(time.Second) * bt)))
// run cdp and incentive begin blockers to update factors
tApp.BeginBlocker(ctx, abci.RequestBeginBlock{})
// calculate expected cdp reward
cdpBlockReward, err := calculateCDPBlockReward(ctx, cdpKeeper, addrs[0], ctype, sdk.NewInt(int64(bt)), rewardPeriod)
require.NoError(t, err)
expectedCDPReward = expectedCDPReward.Add(cdpBlockReward)
} }
type test struct {
name string
args args
}
testCases := []test{
{
"7 seconds",
args{
borrow: c("bnb", 1000000000000),
rewardsPerSecond: c("hard", 122354),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
timeElapsed: 7,
expectedRewardFactor: d("0.000000856478000001"),
},
},
{
"1 day",
args{
borrow: c("bnb", 1000000000000),
rewardsPerSecond: c("hard", 122354),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
timeElapsed: 86400,
expectedRewardFactor: d("0.010571385600010177"),
},
},
{
"0 seconds",
args{
borrow: c("bnb", 1000000000000),
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)
// calculate cdp reward using factor // Mint coins to hard module account
cdp, found := cdpKeeper.GetCdpByOwnerAndCollateralType(ctx, addrs[0], ctype) supplyKeeper := suite.app.GetSupplyKeeper()
require.True(t, found) hardMaccCoins := sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(200000000)))
incentiveKeeper := tApp.GetIncentiveKeeper() supplyKeeper.MintCoins(suite.ctx, hardtypes.ModuleAccountName, hardMaccCoins)
require.NotPanics(t, func() {
incentiveKeeper.SynchronizeReward(ctx, cdp) // setup incentive state
params := types.NewParams(
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.borrow.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.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, tc.args.borrow.Denom, tc.args.initialTime)
suite.keeper.SetHardBorrowRewardFactor(suite.ctx, tc.args.borrow.Denom, sdk.ZeroDec())
// Set up hard state (interest factor for the relevant denom)
suite.hardKeeper.SetSupplyInterestFactor(suite.ctx, tc.args.borrow.Denom, sdk.MustNewDecFromStr("1.0"))
suite.hardKeeper.SetBorrowInterestFactor(suite.ctx, tc.args.borrow.Denom, sdk.MustNewDecFromStr("1.0"))
suite.hardKeeper.SetPreviousAccrualTime(suite.ctx, tc.args.borrow.Denom, tc.args.initialTime)
// User deposits and borrows to increase total borrowed amount
hardKeeper := suite.app.GetHardKeeper()
userAddr := suite.addrs[3]
err := hardKeeper.Deposit(suite.ctx, userAddr, sdk.NewCoins(sdk.NewCoin(tc.args.borrow.Denom, tc.args.borrow.Amount.Mul(sdk.NewInt(2)))))
suite.Require().NoError(err)
err = hardKeeper.Borrow(suite.ctx, userAddr, sdk.NewCoins(tc.args.borrow))
suite.Require().NoError(err)
// 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.GetHardBorrowRewardPeriod(runCtx, tc.args.borrow.Denom)
suite.Require().True(found)
err = suite.keeper.AccumulateHardBorrowRewards(runCtx, rewardPeriod)
suite.Require().NoError(err)
rewardFactor, found := suite.keeper.GetHardBorrowRewardFactor(runCtx, tc.args.borrow.Denom)
suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor)
}) })
claim, found := incentiveKeeper.GetClaim(ctx, addrs[0]) }
require.True(t, found) }
// Compare two methods of calculation func (suite *KeeperTestSuite) TestSynchronizeHardBorrowReward() {
relativeError := expectedCDPReward.Sub(claim.Reward.Amount.ToDec()).Quo(expectedCDPReward).Abs() type args struct {
maxError := d("0.0001") borrow sdk.Coin
require.Truef(t, relativeError.LT(maxError), rewardsPerSecond sdk.Coin
"percent diff %s > %s , expected: %s, actual %s,", relativeError, maxError, expectedCDPReward, claim.Reward.Amount, initialTime time.Time
blockTimes []int
expectedRewardFactor sdk.Dec
expectedRewards sdk.Coin
}
type test struct {
name string
args args
}
testCases := []test{
{
"10 blocks",
args{
borrow: c("bnb", 10000000000), // TODO: 2 decimal diff from TestAccumulateHardBorrowRewards's borrow
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("0.001223540000173228"),
expectedRewards: c("hard", 12235400),
},
},
{
"10 blocks - long block time",
args{
borrow: c("bnb", 10000000000),
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("10.571385603126235340"),
expectedRewards: c("hard", 105713856031),
},
},
}
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.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.borrow.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.borrow.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.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, tc.args.borrow.Denom, tc.args.initialTime)
suite.keeper.SetHardBorrowRewardFactor(suite.ctx, tc.args.borrow.Denom, sdk.ZeroDec())
// Set up hard state (interest factor for the relevant denom)
suite.hardKeeper.SetSupplyInterestFactor(suite.ctx, tc.args.borrow.Denom, sdk.MustNewDecFromStr("1.0"))
suite.hardKeeper.SetBorrowInterestFactor(suite.ctx, tc.args.borrow.Denom, sdk.MustNewDecFromStr("1.0"))
suite.hardKeeper.SetPreviousAccrualTime(suite.ctx, tc.args.borrow.Denom, tc.args.initialTime)
// User deposits and borrows to increase total borrowed amount
hardKeeper := suite.app.GetHardKeeper()
userAddr := suite.addrs[3]
err := hardKeeper.Deposit(suite.ctx, userAddr, sdk.NewCoins(sdk.NewCoin(tc.args.borrow.Denom, tc.args.borrow.Amount.Mul(sdk.NewInt(2)))))
suite.Require().NoError(err)
err = hardKeeper.Borrow(suite.ctx, userAddr, sdk.NewCoins(tc.args.borrow))
suite.Require().NoError(err)
// Check that Hard hooks initialized a HardLiquidityProviderClaim
claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3])
suite.Require().True(found)
suite.Require().Equal(sdk.ZeroDec(), claim.BorrowRewardIndexes[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.GetHardBorrowRewardPeriod(blockCtx, tc.args.borrow.Denom)
suite.Require().True(found)
err := suite.keeper.AccumulateHardBorrowRewards(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
borrow, found := hardKeeper.GetBorrow(suite.ctx, suite.addrs[3])
suite.Require().True(found)
suite.Require().NotPanics(func() {
suite.keeper.SynchronizeHardBorrowReward(suite.ctx, borrow)
})
// Check that reward factor and claim have been updated as expected
rewardFactor, found := suite.keeper.GetHardBorrowRewardFactor(suite.ctx, tc.args.borrow.Denom)
suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor)
claim, found = suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3])
suite.Require().True(found)
suite.Require().Equal(tc.args.expectedRewardFactor, claim.BorrowRewardIndexes[0].RewardFactor)
suite.Require().Equal(tc.args.expectedRewards, claim.Reward)
})
}
} }
// calculateCDPBlockReward computes the reward that should be distributed to a cdp for the current block. func (suite *KeeperTestSuite) TestAccumulateHardSupplyRewards() {
func calculateCDPBlockReward(ctx sdk.Context, cdpKeeper cdpkeeper.Keeper, owner sdk.AccAddress, ctype string, timeElapsed sdk.Int, rewardPeriod types.RewardPeriod) (sdk.Dec, error) { type args struct {
// Calculate total rewards to distribute this block deposit sdk.Coin
newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount) rewardsPerSecond sdk.Coin
initialTime time.Time
// Calculate cdp's share of total debt timeElapsed int
totalPrincipal := cdpKeeper.GetTotalPrincipal(ctx, ctype, types.PrincipalDenom).ToDec() expectedRewardFactor sdk.Dec
// cdpDebt
cdp, found := cdpKeeper.GetCdpByOwnerAndCollateralType(ctx, owner, ctype)
if !found {
return sdk.Dec{}, fmt.Errorf("couldn't find cdp for owner '%s' and collateral type '%s'", owner, ctype)
} }
accumulatedInterest := cdpKeeper.CalculateNewInterest(ctx, cdp) type test struct {
cdpDebt := cdp.Principal.Add(cdp.AccumulatedFees).Add(accumulatedInterest).Amount name string
args args
}
testCases := []test{
{
"7 seconds",
args{
deposit: c("bnb", 1000000000000),
rewardsPerSecond: c("hard", 122354),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
timeElapsed: 7,
expectedRewardFactor: d("0.000000856478000000"),
},
},
{
"1 day",
args{
deposit: c("bnb", 1000000000000),
rewardsPerSecond: c("hard", 122354),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
timeElapsed: 86400,
expectedRewardFactor: d("0.010571385600000000"),
},
},
{
"0 seconds",
args{
deposit: c("bnb", 1000000000000),
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)
// Calculate cdp's reward // Mint coins to hard module account
return newRewards.Mul(cdpDebt).ToDec().Quo(totalPrincipal), nil 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.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.deposit.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.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, tc.args.deposit.Denom, tc.args.initialTime)
suite.keeper.SetHardSupplyRewardFactor(suite.ctx, tc.args.deposit.Denom, sdk.ZeroDec())
// Set up hard state (interest factor for the relevant denom)
suite.hardKeeper.SetSupplyInterestFactor(suite.ctx, tc.args.deposit.Denom, sdk.MustNewDecFromStr("1.0"))
suite.hardKeeper.SetPreviousAccrualTime(suite.ctx, tc.args.deposit.Denom, tc.args.initialTime)
// User deposits to increase total supplied amount
hardKeeper := suite.app.GetHardKeeper()
userAddr := suite.addrs[3]
err := hardKeeper.Deposit(suite.ctx, userAddr, sdk.NewCoins(tc.args.deposit))
suite.Require().NoError(err)
// 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.GetHardSupplyRewardPeriod(runCtx, tc.args.deposit.Denom)
suite.Require().True(found)
err = suite.keeper.AccumulateHardSupplyRewards(runCtx, rewardPeriod)
suite.Require().NoError(err)
rewardFactor, found := suite.keeper.GetHardSupplyRewardFactor(runCtx, tc.args.deposit.Denom)
suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor)
})
}
} }
func (suite *KeeperTestSuite) SetupWithCDPGenState() { func (suite *KeeperTestSuite) TestSynchronizeHardSupplyReward() {
type args struct {
deposit 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{
deposit: c("bnb", 10000000000), // TODO: 2 decimal diff
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("0.001223540000000000"),
expectedRewards: c("hard", 12235400),
},
},
{
"10 blocks - long block time",
args{
deposit: c("bnb", 10000000000),
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("10.571385600000000000"),
expectedRewards: c("hard", 105713856000),
},
},
}
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.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.deposit.Denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.deposit.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.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, tc.args.deposit.Denom, tc.args.initialTime)
suite.keeper.SetHardSupplyRewardFactor(suite.ctx, tc.args.deposit.Denom, sdk.ZeroDec())
// Set up hard state (interest factor for the relevant denom)
suite.hardKeeper.SetSupplyInterestFactor(suite.ctx, tc.args.deposit.Denom, sdk.MustNewDecFromStr("1.0"))
suite.hardKeeper.SetBorrowInterestFactor(suite.ctx, tc.args.deposit.Denom, sdk.MustNewDecFromStr("1.0"))
suite.hardKeeper.SetPreviousAccrualTime(suite.ctx, tc.args.deposit.Denom, tc.args.initialTime)
// User deposits and borrows to increase total borrowed amount
hardKeeper := suite.app.GetHardKeeper()
userAddr := suite.addrs[3]
err := hardKeeper.Deposit(suite.ctx, userAddr, sdk.NewCoins(tc.args.deposit))
suite.Require().NoError(err)
// Check that Hard hooks initialized a HardLiquidityProviderClaim
claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3])
suite.Require().True(found)
suite.Require().Equal(sdk.ZeroDec(), claim.SupplyRewardIndexes[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.GetHardSupplyRewardPeriod(blockCtx, tc.args.deposit.Denom)
suite.Require().True(found)
err := suite.keeper.AccumulateHardSupplyRewards(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
deposit, found := hardKeeper.GetDeposit(suite.ctx, suite.addrs[3])
suite.Require().True(found)
suite.Require().NotPanics(func() {
suite.keeper.SynchronizeHardSupplyReward(suite.ctx, deposit)
})
// Check that reward factor and claim have been updated as expected
rewardFactor, found := suite.keeper.GetHardSupplyRewardFactor(suite.ctx, tc.args.deposit.Denom)
suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor)
claim, found = suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3])
suite.Require().True(found)
suite.Require().Equal(tc.args.expectedRewardFactor, claim.SupplyRewardIndexes[0].RewardFactor)
suite.Require().Equal(tc.args.expectedRewards, claim.Reward)
})
}
}
func (suite *KeeperTestSuite) TestUpdateHardSupplyIndexDenoms() {
type args struct {
firstDeposit sdk.Coins
secondDeposit sdk.Coins
rewardsPerSecond sdk.Coin
initialTime time.Time
expectedSupplyIndexDenoms []string
}
type test struct {
name string
args args
}
testCases := []test{
{
"update adds one supply reward index",
args{
firstDeposit: cs(c("bnb", 10000000000)),
secondDeposit: cs(c("ukava", 10000000000)),
rewardsPerSecond: c("hard", 122354),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
expectedSupplyIndexDenoms: []string{"bnb", "ukava"},
},
},
{
"update adds multiple supply reward indexes",
args{
firstDeposit: cs(c("bnb", 10000000000)),
secondDeposit: cs(c("ukava", 10000000000), c("btcb", 10000000000), c("xrp", 10000000000)),
rewardsPerSecond: c("hard", 122354),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
expectedSupplyIndexDenoms: []string{"bnb", "ukava", "btcb", "xrp"},
},
},
{
"update doesn't add duplicate supply reward index for same denom",
args{
firstDeposit: cs(c("bnb", 10000000000)),
secondDeposit: cs(c("bnb", 5000000000)),
rewardsPerSecond: c("hard", 122354),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
expectedSupplyIndexDenoms: []string{"bnb"},
},
},
}
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 generic reward periods
var rewardPeriods types.RewardPeriods
for _, denom := range tc.args.expectedSupplyIndexDenoms {
rewardPeriod := types.NewRewardPeriod(true, denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)
rewardPeriods = append(rewardPeriods, rewardPeriod)
}
// Setup incentive state
params := types.NewParams(
rewardPeriods, rewardPeriods, rewardPeriods, rewardPeriods,
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)
// Set each denom's previous accrual time and supply reward factor
for _, denom := range tc.args.expectedSupplyIndexDenoms {
suite.keeper.SetPreviousHardSupplyRewardAccrualTime(suite.ctx, denom, tc.args.initialTime)
suite.keeper.SetHardSupplyRewardFactor(suite.ctx, denom, sdk.ZeroDec())
}
// User deposits (first time)
hardKeeper := suite.app.GetHardKeeper()
userAddr := suite.addrs[3]
err := hardKeeper.Deposit(suite.ctx, userAddr, tc.args.firstDeposit)
suite.Require().NoError(err)
// Confirm that a claim was created and populated with the correct supply indexes
claimAfterFirstDeposit, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3])
suite.Require().True(found)
for _, coin := range tc.args.firstDeposit {
_, hasIndex := claimAfterFirstDeposit.HasSupplyRewardIndex(coin.Denom)
suite.Require().True(hasIndex)
}
suite.Require().True(len(claimAfterFirstDeposit.SupplyRewardIndexes) == len(tc.args.firstDeposit))
// User deposits (second time)
err = hardKeeper.Deposit(suite.ctx, userAddr, tc.args.secondDeposit)
suite.Require().NoError(err)
// Confirm that the claim contains all expected supply indexes
claimAfterSecondDeposit, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3])
suite.Require().True(found)
for _, denom := range tc.args.expectedSupplyIndexDenoms {
_, hasIndex := claimAfterSecondDeposit.HasSupplyRewardIndex(denom)
suite.Require().True(hasIndex)
}
suite.Require().True(len(claimAfterSecondDeposit.SupplyRewardIndexes) == len(tc.args.expectedSupplyIndexDenoms))
})
}
}
func (suite *KeeperTestSuite) TestUpdateHardBorrowIndexDenoms() {
type args struct {
initialDeposit sdk.Coins
firstBorrow sdk.Coins
secondBorrow sdk.Coins
rewardsPerSecond sdk.Coin
initialTime time.Time
expectedBorrowIndexDenoms []string
}
type test struct {
name string
args args
}
testCases := []test{
{
"update adds one borrow reward index",
args{
initialDeposit: cs(c("bnb", 10000000000)),
firstBorrow: cs(c("bnb", 50000000)),
secondBorrow: cs(c("ukava", 500000000)),
rewardsPerSecond: c("hard", 122354),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
expectedBorrowIndexDenoms: []string{"bnb", "ukava"},
},
},
{
"update adds multiple borrow supply reward indexes",
args{
initialDeposit: cs(c("btcb", 10000000000)),
firstBorrow: cs(c("btcb", 50000000)),
secondBorrow: cs(c("ukava", 500000000), c("bnb", 50000000000), c("xrp", 50000000000)),
rewardsPerSecond: c("hard", 122354),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
expectedBorrowIndexDenoms: []string{"btcb", "ukava", "bnb", "xrp"},
},
},
{
"update doesn't add duplicate borrow reward index for same denom",
args{
initialDeposit: cs(c("bnb", 100000000000)),
firstBorrow: cs(c("bnb", 50000000)),
secondBorrow: cs(c("bnb", 50000000000)),
rewardsPerSecond: c("hard", 122354),
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
expectedBorrowIndexDenoms: []string{"bnb"},
},
},
}
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 so it can service borrow requests
supplyKeeper := suite.app.GetSupplyKeeper()
hardMaccCoins := tc.args.firstBorrow.Add(tc.args.secondBorrow...)
supplyKeeper.MintCoins(suite.ctx, hardtypes.ModuleAccountName, hardMaccCoins)
// Set up generic reward periods
var rewardPeriods types.RewardPeriods
for _, denom := range tc.args.expectedBorrowIndexDenoms {
rewardPeriod := types.NewRewardPeriod(true, denom, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)
rewardPeriods = append(rewardPeriods, rewardPeriod)
}
// Setup incentive state
params := types.NewParams(
rewardPeriods, rewardPeriods, rewardPeriods, rewardPeriods,
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)
// Set each initial deposit denom's previous accrual time and supply reward factor
for _, coin := range tc.args.initialDeposit {
suite.keeper.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, coin.Denom, tc.args.initialTime)
suite.keeper.SetHardBorrowRewardFactor(suite.ctx, coin.Denom, sdk.ZeroDec())
}
// Set each expected borrow denom's previous accrual time and borrow reward factor
for _, denom := range tc.args.expectedBorrowIndexDenoms {
suite.keeper.SetPreviousHardBorrowRewardAccrualTime(suite.ctx, denom, tc.args.initialTime)
suite.keeper.SetHardBorrowRewardFactor(suite.ctx, denom, sdk.ZeroDec())
}
// User deposits initial funds (so that user can borrow)
hardKeeper := suite.app.GetHardKeeper()
userAddr := suite.addrs[3]
err := hardKeeper.Deposit(suite.ctx, userAddr, tc.args.initialDeposit)
suite.Require().NoError(err)
// Confirm that claim exists but no borrow reward indexes have been added
claimAfterDeposit, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3])
suite.Require().True(found)
suite.Require().Equal(0, len(claimAfterDeposit.BorrowRewardIndexes))
// User borrows (first time)
err = hardKeeper.Borrow(suite.ctx, userAddr, tc.args.firstBorrow)
suite.Require().NoError(err)
// Confirm that claim's borrow reward indexes have been updated
claimAfterFirstBorrow, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3])
suite.Require().True(found)
for _, coin := range tc.args.firstBorrow {
_, hasIndex := claimAfterFirstBorrow.HasBorrowRewardIndex(coin.Denom)
suite.Require().True(hasIndex)
}
suite.Require().True(len(claimAfterFirstBorrow.BorrowRewardIndexes) == len(tc.args.firstBorrow))
// User borrows (second time)
err = hardKeeper.Borrow(suite.ctx, userAddr, tc.args.secondBorrow)
suite.Require().NoError(err)
// Confirm that claim's borrow reward indexes contain expected values
claimAfterSecondBorrow, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[3])
suite.Require().True(found)
for _, coin := range tc.args.secondBorrow {
_, hasIndex := claimAfterSecondBorrow.HasBorrowRewardIndex(coin.Denom)
suite.Require().True(hasIndex)
}
suite.Require().True(len(claimAfterSecondBorrow.BorrowRewardIndexes) == len(tc.args.expectedBorrowIndexDenoms))
})
}
}
func (suite *KeeperTestSuite) SetupWithGenState() {
tApp := app.NewTestApp() tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()}) 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( tApp.InitializeFromGenesisStates(
authGS,
NewPricefeedGenStateMulti(), NewPricefeedGenStateMulti(),
NewCDPGenStateMulti(), NewCDPGenStateMulti(),
NewHardGenStateMulti(),
) )
_, addrs := app.GeneratePrivKeyAddressPairs(5)
keeper := tApp.GetIncentiveKeeper() keeper := tApp.GetIncentiveKeeper()
hardKeeper := tApp.GetHardKeeper()
suite.app = tApp suite.app = tApp
suite.ctx = ctx suite.ctx = ctx
suite.keeper = keeper suite.keeper = keeper
suite.hardKeeper = hardKeeper
suite.addrs = addrs suite.addrs = addrs
} }
// newRepeatingSliceInt creates a slice of the specified length containing a single repeating element.
func newRepeatingSliceInt(element int, length int) []int {
slice := make([]int, length)
for i := 0; i < length; i++ {
slice[i] = element
}
return slice
}

View File

@ -17,24 +17,66 @@ import (
func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string { func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string {
switch { switch {
case bytes.Equal(kvA.Key[:1], types.ClaimKeyPrefix): case bytes.Equal(kvA.Key[:1], types.USDXMintingClaimKeyPrefix):
var claimA, claimB types.USDXMintingClaim var claimA, claimB types.USDXMintingClaim
cdc.MustUnmarshalBinaryBare(kvA.Value, &claimA) cdc.MustUnmarshalBinaryBare(kvA.Value, &claimA)
cdc.MustUnmarshalBinaryBare(kvB.Value, &claimB) cdc.MustUnmarshalBinaryBare(kvB.Value, &claimB)
return fmt.Sprintf("%v\n%v", claimA, claimB) return fmt.Sprintf("%v\n%v", claimA, claimB)
case bytes.Equal(kvA.Key[:1], types.BlockTimeKey): case bytes.Equal(kvA.Key[:1], types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix):
var timeA, timeB time.Time var timeA, timeB time.Time
cdc.MustUnmarshalBinaryBare(kvA.Value, &timeA) cdc.MustUnmarshalBinaryBare(kvA.Value, &timeA)
cdc.MustUnmarshalBinaryBare(kvB.Value, &timeB) cdc.MustUnmarshalBinaryBare(kvB.Value, &timeB)
return fmt.Sprintf("%s\n%s", timeA, timeB) return fmt.Sprintf("%s\n%s", timeA, timeB)
case bytes.Equal(kvA.Key[:1], types.RewardFactorKey): case bytes.Equal(kvA.Key[:1], types.USDXMintingRewardFactorKeyPrefix):
var factorA, factorB sdk.Dec var factorA, factorB sdk.Dec
cdc.MustUnmarshalBinaryBare(kvA.Value, &factorA) cdc.MustUnmarshalBinaryBare(kvA.Value, &factorA)
cdc.MustUnmarshalBinaryBare(kvB.Value, &factorB) cdc.MustUnmarshalBinaryBare(kvB.Value, &factorB)
return fmt.Sprintf("%s\n%s", factorA, factorB) return fmt.Sprintf("%s\n%s", factorA, factorB)
// case bytes.Equal(kvA.Key[:1], types.HardLiquidityClaimKeyPrefix):
// var claimA, claimB types.HardLiquidityProviderClaim
// cdc.MustUnmarshalBinaryBare(kvA.Value, &claimA)
// cdc.MustUnmarshalBinaryBare(kvB.Value, &claimB)
// return fmt.Sprintf("%v\n%v", claimA, claimB)
// case bytes.Equal(kvA.Key[:1], types.PreviousHardSupplyRewardAccrualTimeKeyPrefix):
// var timeA, timeB time.Time
// cdc.MustUnmarshalBinaryBare(kvA.Value, &timeA)
// cdc.MustUnmarshalBinaryBare(kvB.Value, &timeB)
// return fmt.Sprintf("%s\n%s", timeA, timeB)
// case bytes.Equal(kvA.Key[:1], types.HardSupplyRewardFactorKeyPrefix):
// var factorA, factorB sdk.Dec
// cdc.MustUnmarshalBinaryBare(kvA.Value, &factorA)
// cdc.MustUnmarshalBinaryBare(kvB.Value, &factorB)
// return fmt.Sprintf("%s\n%s", factorA, factorB)
// case bytes.Equal(kvA.Key[:1], types.PreviousHardBorrowRewardAccrualTimeKeyPrefix):
// var timeA, timeB time.Time
// cdc.MustUnmarshalBinaryBare(kvA.Value, &timeA)
// cdc.MustUnmarshalBinaryBare(kvB.Value, &timeB)
// return fmt.Sprintf("%s\n%s", timeA, timeB)
// case bytes.Equal(kvA.Key[:1], types.HardSupplyRewardFactorKeyPrefix):
// var factorA, factorB sdk.Dec
// cdc.MustUnmarshalBinaryBare(kvA.Value, &factorA)
// cdc.MustUnmarshalBinaryBare(kvB.Value, &factorB)
// return fmt.Sprintf("%s\n%s", factorA, factorB)
// case bytes.Equal(kvA.Key[:1], types.PreviousHardDelegatorRewardAccrualTimeKeyPrefix):
// var timeA, timeB time.Time
// cdc.MustUnmarshalBinaryBare(kvA.Value, &timeA)
// cdc.MustUnmarshalBinaryBare(kvB.Value, &timeB)
// return fmt.Sprintf("%s\n%s", timeA, timeB)
// case bytes.Equal(kvA.Key[:1], types.HardDelegatorRewardFactorKeyPrefix):
// var factorA, factorB sdk.Dec
// cdc.MustUnmarshalBinaryBare(kvA.Value, &factorA)
// cdc.MustUnmarshalBinaryBare(kvB.Value, &factorB)
// return fmt.Sprintf("%s\n%s", factorA, factorB)
default: default:
panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1])) panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1]))
} }

View File

@ -30,9 +30,16 @@ func TestDecodeDistributionStore(t *testing.T) {
factor := sdk.ZeroDec() factor := sdk.ZeroDec()
kvPairs := kv.Pairs{ kvPairs := kv.Pairs{
kv.Pair{Key: types.ClaimKeyPrefix, Value: cdc.MustMarshalBinaryBare(claim)}, kv.Pair{Key: types.USDXMintingClaimKeyPrefix, Value: cdc.MustMarshalBinaryBare(claim)},
kv.Pair{Key: []byte(types.BlockTimeKey), Value: cdc.MustMarshalBinaryBare(prevBlockTime)}, kv.Pair{Key: []byte(types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix), Value: cdc.MustMarshalBinaryBare(prevBlockTime)},
kv.Pair{Key: []byte(types.RewardFactorKey), Value: cdc.MustMarshalBinaryBare(factor)}, kv.Pair{Key: []byte(types.USDXMintingRewardFactorKeyPrefix), Value: cdc.MustMarshalBinaryBare(factor)},
// kv.Pair{Key: types.HardLiquidityClaimKeyPrefix, Value: cdc.MustMarshalBinaryBare(claim)},
// kv.Pair{Key: []byte(types.HardSupplyRewardFactorKeyPrefix), Value: cdc.MustMarshalBinaryBare(factor)},
// kv.Pair{Key: []byte(types.PreviousHardSupplyRewardAccrualTimeKeyPrefix), Value: cdc.MustMarshalBinaryBare(prevBlockTime)},
// kv.Pair{Key: []byte(types.HardBorrowRewardFactorKeyPrefix), Value: cdc.MustMarshalBinaryBare(factor)},
// kv.Pair{Key: []byte(types.PreviousHardBorrowRewardAccrualTimeKeyPrefix), Value: cdc.MustMarshalBinaryBare(prevBlockTime)},
// kv.Pair{Key: []byte(types.HardDelegatorRewardFactorKeyPrefix), Value: cdc.MustMarshalBinaryBare(factor)},
// kv.Pair{Key: []byte(types.PreviousHardDelegatorRewardAccrualTimeKeyPrefix), Value: cdc.MustMarshalBinaryBare(prevBlockTime)},
kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}}, kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}},
} }
@ -40,9 +47,16 @@ func TestDecodeDistributionStore(t *testing.T) {
name string name string
expectedLog string expectedLog string
}{ }{
{"Claim", fmt.Sprintf("%v\n%v", claim, claim)}, {"USDXMintingClaim", fmt.Sprintf("%v\n%v", claim, claim)},
{"PreviousBlockTime", fmt.Sprintf("%v\n%v", prevBlockTime, prevBlockTime)}, {"PreviousUSDXMintingRewardAccrualTime", fmt.Sprintf("%v\n%v", prevBlockTime, prevBlockTime)},
{"RewardFactor", fmt.Sprintf("%v\n%v", factor, factor)}, {"USDXMintingRewardFactor", fmt.Sprintf("%v\n%v", factor, factor)},
// {"HardLiquidityClaim", fmt.Sprintf("%v\n%v", claim, claim)},
// {"PreviousHardSupplyRewardAccrualTime", fmt.Sprintf("%v\n%v", prevBlockTime, prevBlockTime)},
// {"HardSupplyRewardFactor", fmt.Sprintf("%v\n%v", factor, factor)},
// {"PreviousHardBorrowRewardAccrualTime", fmt.Sprintf("%v\n%v", prevBlockTime, prevBlockTime)},
// {"HardBorrowRewardFactor", fmt.Sprintf("%v\n%v", factor, factor)},
// {"PreviousHardDelegatorRewardAccrualTime", fmt.Sprintf("%v\n%v", prevBlockTime, prevBlockTime)},
// {"HardSupplyDelegatorFactor", fmt.Sprintf("%v\n%v", factor, factor)},
{"other", ""}, {"other", ""},
} }
for i, tt := range tests { for i, tt := range tests {

View File

@ -8,40 +8,91 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
) )
// USDXMintingClaim stores the usdx mintng rewards that can be claimed by owner const (
type USDXMintingClaim struct { USDXMintingClaimType = "usdx_minting"
HardLiquidityProviderClaimType = "hard_liquidity_provider"
)
// Claim is an interface for handling common claim actions
type Claim interface {
GetOwner() sdk.AccAddress
GetReward() sdk.Coin
GetType() string
}
// Claims is a slice of Claim
type Claims []Claim
// BaseClaim is a common type shared by all Claims
type BaseClaim struct {
Owner sdk.AccAddress `json:"owner" yaml:"owner"` Owner sdk.AccAddress `json:"owner" yaml:"owner"`
Reward sdk.Coin `json:"reward" yaml:"reward"` Reward sdk.Coin `json:"reward" yaml:"reward"`
RewardIndexes RewardIndexes `json:"reward_indexes" yaml:"reward_indexes"`
} }
// NewUSDXMintingClaim returns a new USDXMintingClaim // GetOwner is a getter for Claim Owner
func NewUSDXMintingClaim(owner sdk.AccAddress, reward sdk.Coin, rewardIndexes RewardIndexes) USDXMintingClaim { func (c BaseClaim) GetOwner() sdk.AccAddress { return c.Owner }
return USDXMintingClaim{
Owner: owner,
Reward: reward,
RewardIndexes: rewardIndexes,
}
}
// Validate performs a basic check of a Claim fields. // GetReward is a getter for Claim Reward
func (c USDXMintingClaim) Validate() error { func (c BaseClaim) GetReward() sdk.Coin { return c.Reward }
// GetType returns the claim type, used to identify auctions in event attributes
func (c BaseClaim) GetType() string { return "base" }
// Validate performs a basic check of a BaseClaim fields
func (c BaseClaim) Validate() error {
if c.Owner.Empty() { if c.Owner.Empty() {
return errors.New("claim owner cannot be empty") return errors.New("claim owner cannot be empty")
} }
if !c.Reward.IsValid() { if !c.Reward.IsValid() {
return fmt.Errorf("invalid reward amount: %s", c.Reward) return fmt.Errorf("invalid reward amount: %s", c.Reward)
} }
return c.RewardIndexes.Validate() return nil
}
// String implements fmt.Stringer
func (c BaseClaim) String() string {
return fmt.Sprintf(`Claim:
Owner: %s,
Reward: %s,
`, c.Owner, c.Reward)
}
// -------------- Custom Claim Types --------------
// USDXMintingClaim is for USDX minting rewards
type USDXMintingClaim struct {
BaseClaim `json:"base_claim" yaml:"base_claim"`
RewardIndexes RewardIndexes `json:"reward_indexes" yaml:"reward_indexes"`
}
// NewUSDXMintingClaim returns a new USDXMintingClaim
func NewUSDXMintingClaim(owner sdk.AccAddress, reward sdk.Coin, rewardIndexes RewardIndexes) USDXMintingClaim {
return USDXMintingClaim{
BaseClaim: BaseClaim{
Owner: owner,
Reward: reward,
},
RewardIndexes: rewardIndexes,
}
}
// GetType returns the claim type, used to identify auctions in event attributes
func (c USDXMintingClaim) GetType() string { return USDXMintingClaimType }
// Validate performs a basic check of a Claim fields
func (c USDXMintingClaim) Validate() error {
if err := c.RewardIndexes.Validate(); err != nil {
return err
}
return c.BaseClaim.Validate()
} }
// String implements fmt.Stringer // String implements fmt.Stringer
func (c USDXMintingClaim) String() string { func (c USDXMintingClaim) String() string {
return fmt.Sprintf(`Claim: return fmt.Sprintf(`%s
Owner: %s,
Reward: %s,
Reward Indexes: %s, Reward Indexes: %s,
`, c.Owner, c.Reward, c.RewardIndexes) `, c.BaseClaim, c.RewardIndexes)
} }
// HasRewardIndex check if a claim has a reward index for the input collateral type // HasRewardIndex check if a claim has a reward index for the input collateral type
@ -54,7 +105,7 @@ func (c USDXMintingClaim) HasRewardIndex(collateralType string) (int64, bool) {
return 0, false return 0, false
} }
// USDXMintingClaims array of USDXMintingClaim // USDXMintingClaims slice of USDXMintingClaim
type USDXMintingClaims []USDXMintingClaim type USDXMintingClaims []USDXMintingClaim
// Validate checks if all the claims are valid and there are no duplicated // Validate checks if all the claims are valid and there are no duplicated
@ -69,6 +120,106 @@ func (cs USDXMintingClaims) Validate() error {
return nil return nil
} }
// 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"`
}
// NewHardLiquidityProviderClaim returns a new HardLiquidityProviderClaim
func NewHardLiquidityProviderClaim(owner sdk.AccAddress, reward sdk.Coin, supplyRewardIndexes,
borrowRewardIndexes, delegationRewardIndexes RewardIndexes) HardLiquidityProviderClaim {
return HardLiquidityProviderClaim{
BaseClaim: BaseClaim{
Owner: owner,
Reward: reward,
},
SupplyRewardIndexes: supplyRewardIndexes,
BorrowRewardIndexes: borrowRewardIndexes,
DelegationRewardIndexes: delegationRewardIndexes,
}
}
// GetType returns the claim type, used to identify auctions in event attributes
func (c HardLiquidityProviderClaim) GetType() string { return HardLiquidityProviderClaimType }
// Validate performs a basic check of a HardLiquidityProviderClaim fields
func (c HardLiquidityProviderClaim) Validate() error {
if err := c.SupplyRewardIndexes.Validate(); err != nil {
return err
}
if err := c.BorrowRewardIndexes.Validate(); err != nil {
return err
}
if err := c.DelegationRewardIndexes.Validate(); err != nil {
return err
}
return c.BaseClaim.Validate()
}
// String implements fmt.Stringer
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)
}
// HasSupplyRewardIndex check if a claim has a supply reward index for the input collateral type
func (c HardLiquidityProviderClaim) HasSupplyRewardIndex(denom string) (int64, bool) {
for index, ri := range c.SupplyRewardIndexes {
if ri.CollateralType == denom {
return int64(index), true
}
}
return 0, false
}
// HasBorrowRewardIndex check if a claim has a borrow reward index for the input collateral type
func (c HardLiquidityProviderClaim) HasBorrowRewardIndex(denom string) (int64, bool) {
for index, ri := range c.BorrowRewardIndexes {
if ri.CollateralType == denom {
return int64(index), true
}
}
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 {
if ri.CollateralType == collateralType {
return int64(index), true
}
}
return 0, false
}
// HardLiquidityProviderClaims slice of HardLiquidityProviderClaim
type HardLiquidityProviderClaims []HardLiquidityProviderClaim
// Validate checks if all the claims are valid and there are no duplicated
// entries.
func (cs HardLiquidityProviderClaims) Validate() error {
for _, c := range cs {
if err := c.Validate(); err != nil {
return err
}
}
return nil
}
// -------------- Subcomponents of Custom Claim Types --------------
// TODO: refactor RewardPeriod name from 'collateralType' to 'denom'
// RewardIndex stores reward accumulation information // RewardIndex stores reward accumulation information
type RewardIndex struct { type RewardIndex struct {
CollateralType string `json:"collateral_type" yaml:"collateral_type"` CollateralType string `json:"collateral_type" yaml:"collateral_type"`

View File

@ -28,7 +28,11 @@ func TestClaimsValidate(t *testing.T) {
{ {
"invalid owner", "invalid owner",
USDXMintingClaims{ USDXMintingClaims{
{Owner: nil}, USDXMintingClaim{
BaseClaim: BaseClaim{
Owner: nil,
},
},
}, },
false, false,
}, },
@ -36,18 +40,22 @@ func TestClaimsValidate(t *testing.T) {
"invalid reward", "invalid reward",
USDXMintingClaims{ USDXMintingClaims{
{ {
BaseClaim: BaseClaim{
Owner: owner, Owner: owner,
Reward: sdk.Coin{Denom: "", Amount: sdk.ZeroInt()}, Reward: sdk.Coin{Denom: "", Amount: sdk.ZeroInt()},
}, },
}, },
},
false, false,
}, },
{ {
"invalid collateral type", "invalid collateral type",
USDXMintingClaims{ USDXMintingClaims{
{ {
BaseClaim: BaseClaim{
Owner: owner, Owner: owner,
Reward: sdk.NewCoin("bnb", sdk.OneInt()), Reward: sdk.NewCoin("bnb", sdk.OneInt()),
},
RewardIndexes: []RewardIndex{{"", sdk.ZeroDec()}}, RewardIndexes: []RewardIndex{{"", sdk.ZeroDec()}},
}, },
}, },

View File

@ -14,5 +14,10 @@ func init() {
// RegisterCodec registers the necessary types for incentive module // RegisterCodec registers the necessary types for incentive module
func RegisterCodec(cdc *codec.Codec) { func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterInterface((*Claim)(nil), nil)
cdc.RegisterConcrete(USDXMintingClaim{}, "incentive/USDXMintingClaim", nil)
cdc.RegisterConcrete(HardLiquidityProviderClaim{}, "incentive/HardLiquidityProviderClaim", nil)
// Register msgs
cdc.RegisterConcrete(MsgClaimUSDXMintingReward{}, "incentive/MsgClaimUSDXMintingReward", nil) cdc.RegisterConcrete(MsgClaimUSDXMintingReward{}, "incentive/MsgClaimUSDXMintingReward", nil)
} }

View File

@ -6,23 +6,31 @@ import (
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported" supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
cdptypes "github.com/kava-labs/kava/x/cdp/types" cdptypes "github.com/kava-labs/kava/x/cdp/types"
hardtypes "github.com/kava-labs/kava/x/hard/types"
) )
// SupplyKeeper defines the expected supply keeper for module accounts // SupplyKeeper defines the expected supply keeper for module accounts
type SupplyKeeper interface { type SupplyKeeper interface {
GetModuleAccount(ctx sdk.Context, name string) supplyexported.ModuleAccountI GetModuleAccount(ctx sdk.Context, name string) supplyexported.ModuleAccountI
SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
} }
// CdpKeeper defines the expected cdp keeper for interacting with cdps // CdpKeeper defines the expected cdp keeper for interacting with cdps
type CdpKeeper interface { type CdpKeeper interface {
GetInterestFactor(ctx sdk.Context, collateralType string) (sdk.Dec, bool)
GetTotalPrincipal(ctx sdk.Context, collateralType string, principalDenom string) (total sdk.Int) GetTotalPrincipal(ctx sdk.Context, collateralType string, principalDenom string) (total sdk.Int)
GetCdpByOwnerAndCollateralType(ctx sdk.Context, owner sdk.AccAddress, collateralType string) (cdptypes.CDP, bool) GetCdpByOwnerAndCollateralType(ctx sdk.Context, owner sdk.AccAddress, collateralType string) (cdptypes.CDP, bool)
GetInterestFactor(ctx sdk.Context, collateralType string) (sdk.Dec, bool)
GetCollateral(ctx sdk.Context, collateralType string) (cdptypes.CollateralParam, bool) GetCollateral(ctx sdk.Context, collateralType string) (cdptypes.CollateralParam, bool)
} }
// HardKeeper defines the expected hard keeper for interacting with Hard protocol
type HardKeeper interface {
GetSupplyInterestFactor(ctx sdk.Context, denom string) (sdk.Dec, bool)
GetBorrowInterestFactor(ctx sdk.Context, denom string) (sdk.Dec, bool)
GetBorrowedCoins(ctx sdk.Context) (coins sdk.Coins, found bool)
GetSuppliedCoins(ctx sdk.Context) (coins sdk.Coins, found bool)
}
// AccountKeeper defines the expected keeper interface for interacting with account // AccountKeeper defines the expected keeper interface for interacting with account
type AccountKeeper interface { type AccountKeeper interface {
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account
@ -34,3 +42,13 @@ type CDPHooks interface {
AfterCDPCreated(ctx sdk.Context, cdp cdptypes.CDP) AfterCDPCreated(ctx sdk.Context, cdp cdptypes.CDP)
BeforeCDPModified(ctx sdk.Context, cdp cdptypes.CDP) BeforeCDPModified(ctx sdk.Context, cdp cdptypes.CDP)
} }
// HARDHooks event hooks for other keepers to run code in response to HARD modifications
type HARDHooks interface {
AfterDepositCreated(ctx sdk.Context, deposit hardtypes.Deposit)
BeforeDepositModified(ctx sdk.Context, deposit hardtypes.Deposit)
AfterDepositModified(ctx sdk.Context, deposit hardtypes.Deposit)
AfterBorrowCreated(ctx sdk.Context, borrow hardtypes.Borrow)
BeforeBorrowModified(ctx sdk.Context, borrow hardtypes.Borrow)
AfterBorrowModified(ctx sdk.Context, deposit hardtypes.Deposit)
}

View File

@ -89,9 +89,6 @@ func (gats GenesisAccumulationTimes) Validate() error {
// Validate performs validation of GenesisAccumulationTime // Validate performs validation of GenesisAccumulationTime
func (gat GenesisAccumulationTime) Validate() error { func (gat GenesisAccumulationTime) Validate() error {
if gat.RewardFactor == (sdk.Dec{}) {
return fmt.Errorf("reward factor not initialized for %s", gat.CollateralType)
}
if gat.RewardFactor.LT(sdk.ZeroDec()) { if gat.RewardFactor.LT(sdk.ZeroDec()) {
return fmt.Errorf("reward factor should be ≥ 0.0, is %s for %s", gat.RewardFactor, gat.CollateralType) return fmt.Errorf("reward factor should be ≥ 0.0, is %s for %s", gat.RewardFactor, gat.CollateralType)
} }

View File

@ -5,11 +5,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto"
) )
func TestGenesisStateValidate(t *testing.T) { func TestGenesisStateValidate(t *testing.T) {
@ -44,7 +42,24 @@ func TestGenesisStateValidate(t *testing.T) {
{ {
name: "valid", name: "valid",
args: args{ args: args{
params: NewParams(RewardPeriods{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.NewCoin("ukava", sdk.NewInt(25000)))}, Multipliers{NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33"))}, time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC)), params: NewParams(
RewardPeriods{
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.NewCoin("ukava", sdk.NewInt(25000)),
),
},
DefaultRewardPeriods,
DefaultRewardPeriods,
DefaultRewardPeriods,
Multipliers{
NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33")),
},
time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC),
),
genAccTimes: GenesisAccumulationTimes{GenesisAccumulationTime{ genAccTimes: GenesisAccumulationTimes{GenesisAccumulationTime{
CollateralType: "bnb-a", CollateralType: "bnb-a",
PreviousAccumulationTime: time.Date(2020, 10, 15, 14, 0, 0, 0, time.UTC), PreviousAccumulationTime: time.Date(2020, 10, 15, 14, 0, 0, 0, time.UTC),
@ -52,9 +67,10 @@ func TestGenesisStateValidate(t *testing.T) {
}}, }},
claims: USDXMintingClaims{ claims: USDXMintingClaims{
{ {
BaseClaim: BaseClaim{
Owner: sdk.AccAddress(crypto.AddressHash([]byte("KavaTestUser1"))), Owner: sdk.AccAddress(crypto.AddressHash([]byte("KavaTestUser1"))),
Reward: sdk.NewCoin("ukava", sdk.NewInt(100000000)), Reward: sdk.NewCoin("ukava", sdk.NewInt(100000000)),
},
RewardIndexes: []RewardIndex{ RewardIndexes: []RewardIndex{
{ {
CollateralType: "bnb-a", CollateralType: "bnb-a",
@ -86,24 +102,6 @@ func TestGenesisStateValidate(t *testing.T) {
contains: "reward factor should be ≥ 0.0", contains: "reward factor should be ≥ 0.0",
}, },
}, },
{
name: "invalid genesis accumulation time",
args: args{
params: DefaultParams(),
genAccTimes: GenesisAccumulationTimes{
{
CollateralType: "btcb-a",
PreviousAccumulationTime: time.Date(2020, 10, 15, 14, 0, 0, 0, time.UTC),
RewardFactor: sdk.Dec{},
},
},
claims: DefaultClaims,
},
errArgs: errArgs{
expectPass: false,
contains: "reward factor not initialized",
},
},
{ {
name: "invalid claim", name: "invalid claim",
args: args{ args: args{
@ -111,9 +109,10 @@ func TestGenesisStateValidate(t *testing.T) {
genAccTimes: DefaultGenesisAccumulationTimes, genAccTimes: DefaultGenesisAccumulationTimes,
claims: USDXMintingClaims{ claims: USDXMintingClaims{
{ {
BaseClaim: BaseClaim{
Owner: sdk.AccAddress{}, Owner: sdk.AccAddress{},
Reward: sdk.NewCoin("ukava", sdk.NewInt(100000000)), Reward: sdk.NewCoin("ukava", sdk.NewInt(100000000)),
},
RewardIndexes: []RewardIndex{ RewardIndexes: []RewardIndex{
{ {
CollateralType: "bnb-a", CollateralType: "bnb-a",

View File

@ -17,11 +17,23 @@ const (
QuerierRoute = ModuleName QuerierRoute = ModuleName
) )
// TODO: Refactor so that each incentive type has:
// 1. [Incentive]ClaimKeyPrefix
// 2. [Incentve]AccumulatorKeyPrefix { PreviousAccrualTime block.Time, IndexFactors types.IndexFactors }
// Key Prefixes // Key Prefixes
var ( var (
ClaimKeyPrefix = []byte{0x01} // prefix for keys that store claims USDXMintingClaimKeyPrefix = []byte{0x01} // prefix for keys that store USDX minting claims
BlockTimeKey = []byte{0x02} // prefix for key that stores the blocktime USDXMintingRewardFactorKeyPrefix = []byte{0x02} // prefix for key that stores USDX minting reward factors
RewardFactorKey = []byte{0x03} // prefix for key that stores reward factors PreviousUSDXMintingRewardAccrualTimeKeyPrefix = []byte{0x03} // prefix for key that stores the blocktime
HardLiquidityClaimKeyPrefix = []byte{0x04} // prefix for keys that store Hard liquidity claims
HardSupplyRewardFactorKeyPrefix = []byte{0x05} // prefix for key that stores Hard supply reward factors
PreviousHardSupplyRewardAccrualTimeKeyPrefix = []byte{0x06} // prefix for key that stores the previous time Hard supply rewards accrued
HardBorrowRewardFactorKeyPrefix = []byte{0x07} // prefix for key that stores Hard borrow reward factors
PreviousHardBorrowRewardAccrualTimeKeyPrefix = []byte{0x08} // prefix for key that stores the previous time Hard borrow rewards accrued
HardDelegatorRewardFactorKeyPrefix = []byte{0x09} // prefix for key that stores Hard delegator reward factors
PreviousHardDelegatorRewardAccrualTimeKeyPrefix = []byte{0x10} // prefix for key that stores the previous time Hard delegator rewards accrued
USDXMintingRewardDenom = "ukava" USDXMintingRewardDenom = "ukava"
HardLiquidityRewardDenom = "hard"
) )

View File

@ -24,8 +24,10 @@ const (
// Parameter keys and default values // Parameter keys and default values
var ( var (
KeyActive = []byte("Active") KeyUSDXMintingRewardPeriods = []byte("USDXMintingRewardPeriods")
KeyRewards = []byte("RewardPeriods") KeyHardSupplyRewardPeriods = []byte("HardSupplyRewardPeriods")
KeyHardBorrowRewardPeriods = []byte("HardBorrowRewardPeriods")
KeyHardDelegatorRewardPeriods = []byte("HardDelegatorRewardPeriods")
KeyClaimEnd = []byte("ClaimEnd") KeyClaimEnd = []byte("ClaimEnd")
KeyMultipliers = []byte("ClaimMultipliers") KeyMultipliers = []byte("ClaimMultipliers")
DefaultActive = false DefaultActive = false
@ -41,15 +43,22 @@ var (
// Params governance parameters for the incentive module // Params governance parameters for the incentive module
type Params struct { type Params struct {
RewardPeriods RewardPeriods `json:"reward_periods" yaml:"reward_periods"` USDXMintingRewardPeriods RewardPeriods `json:"usdx_minting_reward_periods" yaml:"usdx_minting_reward_periods"`
HardSupplyRewardPeriods RewardPeriods `json:"hard_supply_reward_periods" yaml:"hard_supply_reward_periods"`
HardBorrowRewardPeriods RewardPeriods `json:"hard_borrow_reward_periods" yaml:"hard_borrow_reward_periods"`
HardDelegatorRewardPeriods RewardPeriods `json:"hard_delegator_reward_periods" yaml:"hard_delegator_reward_periods"`
ClaimMultipliers Multipliers `json:"claim_multipliers" yaml:"claim_multipliers"` ClaimMultipliers Multipliers `json:"claim_multipliers" yaml:"claim_multipliers"`
ClaimEnd time.Time `json:"claim_end" yaml:"claim_end"` ClaimEnd time.Time `json:"claim_end" yaml:"claim_end"`
} }
// NewParams returns a new params object // NewParams returns a new params object
func NewParams(rewards RewardPeriods, multipliers Multipliers, claimEnd time.Time) Params { func NewParams(usdxMinting, hardSupply, hardBorrow, hardDelegator RewardPeriods,
multipliers Multipliers, claimEnd time.Time) Params {
return Params{ return Params{
RewardPeriods: rewards, USDXMintingRewardPeriods: usdxMinting,
HardSupplyRewardPeriods: hardSupply,
HardBorrowRewardPeriods: hardBorrow,
HardDelegatorRewardPeriods: hardDelegator,
ClaimMultipliers: multipliers, ClaimMultipliers: multipliers,
ClaimEnd: claimEnd, ClaimEnd: claimEnd,
} }
@ -57,16 +66,21 @@ func NewParams(rewards RewardPeriods, multipliers Multipliers, claimEnd time.Tim
// DefaultParams returns default params for incentive module // DefaultParams returns default params for incentive module
func DefaultParams() Params { func DefaultParams() Params {
return NewParams(DefaultRewardPeriods, DefaultMultipliers, DefaultClaimEnd) return NewParams(DefaultRewardPeriods, DefaultRewardPeriods,
DefaultRewardPeriods, DefaultRewardPeriods, DefaultMultipliers, DefaultClaimEnd)
} }
// String implements fmt.Stringer // String implements fmt.Stringer
func (p Params) String() string { func (p Params) String() string {
return fmt.Sprintf(`Params: return fmt.Sprintf(`Params:
Rewards: %s USDX Minting Reward Periods: %s
Hard Supply Reward Periods: %s
Hard Borrow Reward Periods: %s
Hard Delegator Reward Periods: %s
Claim Multipliers :%s Claim Multipliers :%s
Claim End Time: %s Claim End Time: %s
`, p.RewardPeriods, p.ClaimMultipliers, p.ClaimEnd) `, p.USDXMintingRewardPeriods, p.HardSupplyRewardPeriods, p.HardBorrowRewardPeriods,
p.HardDelegatorRewardPeriods, p.ClaimMultipliers, p.ClaimEnd)
} }
// ParamKeyTable Key declaration for parameters // ParamKeyTable Key declaration for parameters
@ -77,7 +91,10 @@ func ParamKeyTable() params.KeyTable {
// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs // ParamSetPairs implements the ParamSet interface and returns all the key/value pairs
func (p *Params) ParamSetPairs() params.ParamSetPairs { func (p *Params) ParamSetPairs() params.ParamSetPairs {
return params.ParamSetPairs{ return params.ParamSetPairs{
params.NewParamSetPair(KeyRewards, &p.RewardPeriods, validateRewardsParam), params.NewParamSetPair(KeyUSDXMintingRewardPeriods, &p.USDXMintingRewardPeriods, validateRewardPeriodsParam),
params.NewParamSetPair(KeyHardSupplyRewardPeriods, &p.HardSupplyRewardPeriods, validateRewardPeriodsParam),
params.NewParamSetPair(KeyHardBorrowRewardPeriods, &p.HardBorrowRewardPeriods, validateRewardPeriodsParam),
params.NewParamSetPair(KeyHardDelegatorRewardPeriods, &p.HardDelegatorRewardPeriods, validateRewardPeriodsParam),
params.NewParamSetPair(KeyClaimEnd, &p.ClaimEnd, validateClaimEndParam), params.NewParamSetPair(KeyClaimEnd, &p.ClaimEnd, validateClaimEndParam),
params.NewParamSetPair(KeyMultipliers, &p.ClaimMultipliers, validateMultipliersParam), params.NewParamSetPair(KeyMultipliers, &p.ClaimMultipliers, validateMultipliersParam),
} }
@ -85,22 +102,27 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs {
// Validate checks that the parameters have valid values. // Validate checks that the parameters have valid values.
func (p Params) Validate() error { func (p Params) Validate() error {
if err := validateMultipliersParam(p.ClaimMultipliers); err != nil { if err := validateMultipliersParam(p.ClaimMultipliers); err != nil {
return err return err
} }
return validateRewardsParam(p.RewardPeriods) if err := validateRewardPeriodsParam(p.USDXMintingRewardPeriods); err != nil {
return err
} }
func validateActiveParam(i interface{}) error { if err := validateRewardPeriodsParam(p.HardSupplyRewardPeriods); err != nil {
_, ok := i.(bool) return err
if !ok {
return fmt.Errorf("invalid parameter type: %T", i)
}
return nil
} }
func validateRewardsParam(i interface{}) error { if err := validateRewardPeriodsParam(p.HardBorrowRewardPeriods); err != nil {
return err
}
return validateRewardPeriodsParam(p.HardDelegatorRewardPeriods)
}
func validateRewardPeriodsParam(i interface{}) error {
rewards, ok := i.(RewardPeriods) rewards, ok := i.(RewardPeriods)
if !ok { if !ok {
return fmt.Errorf("invalid parameter type: %T", i) return fmt.Errorf("invalid parameter type: %T", i)
@ -224,9 +246,6 @@ func (m Multiplier) Validate() error {
if m.MonthsLockup < 0 { if m.MonthsLockup < 0 {
return fmt.Errorf("expected non-negative lockup, got %d", m.MonthsLockup) return fmt.Errorf("expected non-negative lockup, got %d", m.MonthsLockup)
} }
if m.Factor == (sdk.Dec{}) {
return fmt.Errorf("claim multiplier factor not initialized for %s", m.Name)
}
if m.Factor.IsNegative() { if m.Factor.IsNegative() {
return fmt.Errorf("expected non-negative factor, got %s", m.Factor.String()) return fmt.Errorf("expected non-negative factor, got %s", m.Factor.String())
} }

View File

@ -20,7 +20,10 @@ func (suite *ParamTestSuite) SetupTest() {}
func (suite *ParamTestSuite) TestParamValidation() { func (suite *ParamTestSuite) TestParamValidation() {
type args struct { type args struct {
rewardPeriods types.RewardPeriods usdxMintingRewardPeriods types.RewardPeriods
hardSupplyRewardPeriods types.RewardPeriods
hardBorrowRewardPeriods types.RewardPeriods
hardDelegatorRewardPeriods types.RewardPeriods
multipliers types.Multipliers multipliers types.Multipliers
end time.Time end time.Time
} }
@ -39,7 +42,10 @@ func (suite *ParamTestSuite) TestParamValidation() {
{ {
"default", "default",
args{ args{
rewardPeriods: types.DefaultRewardPeriods, usdxMintingRewardPeriods: types.DefaultRewardPeriods,
hardSupplyRewardPeriods: types.DefaultRewardPeriods,
hardBorrowRewardPeriods: types.DefaultRewardPeriods,
hardDelegatorRewardPeriods: types.DefaultRewardPeriods,
multipliers: types.DefaultMultipliers, multipliers: types.DefaultMultipliers,
end: types.DefaultClaimEnd, end: types.DefaultClaimEnd,
}, },
@ -51,7 +57,7 @@ func (suite *ParamTestSuite) TestParamValidation() {
{ {
"valid", "valid",
args{ args{
rewardPeriods: types.RewardPeriods{types.NewRewardPeriod( usdxMintingRewardPeriods: 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), 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.NewCoin(types.USDXMintingRewardDenom, sdk.NewInt(122354)))}, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.NewInt(122354)))},
multipliers: types.Multipliers{ multipliers: types.Multipliers{
@ -62,6 +68,9 @@ func (suite *ParamTestSuite) TestParamValidation() {
types.Large, 1, sdk.MustNewDecFromStr("1.0"), types.Large, 1, sdk.MustNewDecFromStr("1.0"),
), ),
}, },
hardSupplyRewardPeriods: types.DefaultRewardPeriods,
hardBorrowRewardPeriods: types.DefaultRewardPeriods,
hardDelegatorRewardPeriods: types.DefaultRewardPeriods,
end: time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC), end: time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC),
}, },
errArgs{ errArgs{
@ -69,33 +78,12 @@ func (suite *ParamTestSuite) TestParamValidation() {
contains: "", contains: "",
}, },
}, },
{
"invalid: empty reward factor",
args{
rewardPeriods: 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.NewCoin(types.USDXMintingRewardDenom, sdk.NewInt(122354)))},
multipliers: types.Multipliers{
types.NewMultiplier(
types.Small, 1, sdk.MustNewDecFromStr("0.25"),
),
types.NewMultiplier(
types.Large, 1, sdk.Dec{},
),
},
end: time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC),
},
errArgs{
expectPass: false,
contains: "claim multiplier factor not initialized",
},
},
} }
for _, tc := range testCases { for _, tc := range testCases {
suite.Run(tc.name, func() { suite.Run(tc.name, func() {
params := types.NewParams( params := types.NewParams(tc.args.usdxMintingRewardPeriods, tc.args.hardSupplyRewardPeriods,
tc.args.rewardPeriods, tc.args.multipliers, tc.args.end, tc.args.hardBorrowRewardPeriods, tc.args.hardDelegatorRewardPeriods, tc.args.multipliers, tc.args.end,
) )
err := params.Validate() err := params.Validate()
if tc.errArgs.expectPass { if tc.errArgs.expectPass {