Harvest: interest rate model params (#719)

* add interest rate models to params

* move interest rate models to money market param

* add interest rate models to store

* update store interest rate models from params

* refactor money market init function, update tests

* use cmp package for optimized comparison

* implement equal function, remove gocmp dep

* delete unseen interest rate model param from store
This commit is contained in:
Denali Marsh 2020-11-13 16:51:52 +01:00 committed by GitHub
parent fba4860331
commit e1ad9569a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 211 additions and 28 deletions

1
go.sum
View File

@ -159,7 +159,6 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=

View File

@ -11,5 +11,6 @@ func BeginBlocker(ctx sdk.Context, k Keeper) {
k.ApplyDelegationRewards(ctx, k.BondDenom(ctx))
k.SetPreviousDelegationDistribution(ctx, ctx.BlockTime(), k.BondDenom(ctx))
}
k.ApplyInterestRateUpdates(ctx)
k.SetPreviousBlockTime(ctx, ctx.BlockTime())
}

View File

@ -275,12 +275,12 @@ func (suite *KeeperTestSuite) TestBorrow() {
),
},
types.MoneyMarkets{
types.NewMoneyMarket("usdx", true, tc.args.usdxBorrowLimit, sdk.MustNewDecFromStr("1"), "usdx:usd", sdk.NewInt(USDX_CF)),
types.NewMoneyMarket("busd", false, sdk.NewDec(100000000*BUSD_CF), sdk.MustNewDecFromStr("1"), "busd:usd", sdk.NewInt(BUSD_CF)),
types.NewMoneyMarket("ukava", false, sdk.NewDec(100000000*KAVA_CF), tc.args.loanToValueKAVA, "kava:usd", sdk.NewInt(KAVA_CF)),
types.NewMoneyMarket("btcb", false, sdk.NewDec(100000000*BTCB_CF), tc.args.loanToValueBTCB, "btcb:usd", sdk.NewInt(BTCB_CF)),
types.NewMoneyMarket("bnb", false, sdk.NewDec(100000000*BNB_CF), tc.args.loanToValueBNB, "bnb:usd", sdk.NewInt(BNB_CF)),
types.NewMoneyMarket("xyz", false, sdk.NewDec(1), tc.args.loanToValueBNB, "xyz:usd", sdk.NewInt(1)),
types.NewMoneyMarket("usdx", true, tc.args.usdxBorrowLimit, sdk.MustNewDecFromStr("1"), "usdx:usd", sdk.NewInt(USDX_CF), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
types.NewMoneyMarket("busd", false, sdk.NewDec(100000000*BUSD_CF), sdk.MustNewDecFromStr("1"), "busd:usd", sdk.NewInt(BUSD_CF), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
types.NewMoneyMarket("ukava", false, sdk.NewDec(100000000*KAVA_CF), tc.args.loanToValueKAVA, "kava:usd", sdk.NewInt(KAVA_CF), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
types.NewMoneyMarket("btcb", false, sdk.NewDec(100000000*BTCB_CF), tc.args.loanToValueBTCB, "btcb:usd", sdk.NewInt(BTCB_CF), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
types.NewMoneyMarket("bnb", false, sdk.NewDec(100000000*BNB_CF), tc.args.loanToValueBNB, "bnb:usd", sdk.NewInt(BNB_CF), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
types.NewMoneyMarket("xyz", false, sdk.NewDec(1), tc.args.loanToValueBNB, "xyz:usd", sdk.NewInt(1), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
},
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)

View File

@ -265,8 +265,8 @@ func (suite *KeeperTestSuite) TestClaim() {
),
},
types.MoneyMarkets{
types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
},
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})

View File

@ -108,8 +108,8 @@ func (suite *KeeperTestSuite) TestDeposit() {
),
},
types.MoneyMarkets{
types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
},
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})
@ -251,8 +251,8 @@ func (suite *KeeperTestSuite) TestWithdraw() {
),
},
types.MoneyMarkets{
types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
},
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})

View File

@ -0,0 +1,31 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/harvest/types"
)
// ApplyInterestRateUpdates translates the current interest rate models from the params to the store
func (k Keeper) ApplyInterestRateUpdates(ctx sdk.Context) {
denomSet := map[string]bool{}
params := k.GetParams(ctx)
for _, mm := range params.MoneyMarkets {
model, found := k.GetInterestRateModel(ctx, mm.Denom)
if !found {
k.SetInterestRateModel(ctx, mm.Denom, mm.InterestRateModel)
continue
}
if !model.Equal(mm.InterestRateModel) {
k.SetInterestRateModel(ctx, mm.Denom, mm.InterestRateModel)
}
denomSet[mm.Denom] = true
}
k.IterateInterestRateModels(ctx, func(denom string, i types.InterestRateModel) bool {
if !denomSet[denom] {
k.DeleteInterestRateModel(ctx, denom)
}
return false
})
}

View File

@ -255,3 +255,43 @@ func (k Keeper) GetBorrowedCoins(ctx sdk.Context) (sdk.Coins, bool) {
k.cdc.MustUnmarshalBinaryBare(bz, &borrowedCoins)
return borrowedCoins, true
}
// GetInterestRateModel returns an interest rate model from the store for a denom
func (k Keeper) GetInterestRateModel(ctx sdk.Context, denom string) (types.InterestRateModel, bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.InterestRateModelsPrefix)
bz := store.Get([]byte(denom))
if bz == nil {
return types.InterestRateModel{}, false
}
var interestRateModel types.InterestRateModel
k.cdc.MustUnmarshalBinaryBare(bz, &interestRateModel)
return interestRateModel, true
}
// SetInterestRateModel sets an interest rate model in the store for a denom
func (k Keeper) SetInterestRateModel(ctx sdk.Context, denom string, interestRateModel types.InterestRateModel) {
store := prefix.NewStore(ctx.KVStore(k.key), types.InterestRateModelsPrefix)
bz := k.cdc.MustMarshalBinaryBare(interestRateModel)
store.Set([]byte(denom), bz)
}
// DeleteInterestRateModel deletes an interest rate model from the store
func (k Keeper) DeleteInterestRateModel(ctx sdk.Context, denom string) {
store := prefix.NewStore(ctx.KVStore(k.key), types.InterestRateModelsPrefix)
store.Delete([]byte(denom))
}
// IterateInterestRateModels iterates over all interest rate model objects in the store and performs a callback function
// that returns both the interest rate model value and the key it's stored under
func (k Keeper) IterateInterestRateModels(ctx sdk.Context, cb func(denom string, interestRateModel types.InterestRateModel) (stop bool)) {
store := prefix.NewStore(ctx.KVStore(k.key), types.InterestRateModelsPrefix)
iterator := sdk.KVStorePrefixIterator(store, []byte{})
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var interestRateModel types.InterestRateModel
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &interestRateModel)
if cb(string(iterator.Key()), interestRateModel) {
break
}
}
}

View File

@ -1,6 +1,7 @@
package keeper_test
import (
"strconv"
"testing"
"github.com/stretchr/testify/suite"
@ -153,6 +154,51 @@ func (suite *KeeperTestSuite) TestGetSetDeleteClaim() {
suite.Require().False(f)
}
func (suite *KeeperTestSuite) TestGetSetDeleteInterestRateModel() {
denom := "test"
model := types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))
_, f := suite.keeper.GetInterestRateModel(suite.ctx, denom)
suite.Require().False(f)
suite.keeper.SetInterestRateModel(suite.ctx, denom, model)
testInterestRateModel, f := suite.keeper.GetInterestRateModel(suite.ctx, denom)
suite.Require().True(f)
suite.Require().Equal(model, testInterestRateModel)
suite.Require().NotPanics(func() { suite.keeper.DeleteInterestRateModel(suite.ctx, denom) })
_, f = suite.keeper.GetInterestRateModel(suite.ctx, denom)
suite.Require().False(f)
}
func (suite *KeeperTestSuite) TestIterateInterestRateModels() {
testDenom := "test"
var setModels types.InterestRateModels
var setDenoms []string
for i := 0; i < 5; i++ {
denom := testDenom + strconv.Itoa(i)
model := types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))
suite.Require().NotPanics(func() { suite.keeper.SetInterestRateModel(suite.ctx, denom, model) })
// Save the denom and model
setDenoms = append(setDenoms, denom)
setModels = append(setModels, model)
}
var seenModels types.InterestRateModels
var seenDenoms []string
suite.keeper.IterateInterestRateModels(suite.ctx, func(denom string, i types.InterestRateModel) bool {
seenDenoms = append(seenDenoms, denom)
seenModels = append(seenModels, i)
return false
})
suite.Require().Equal(setModels, seenModels)
suite.Require().Equal(setDenoms, seenDenoms)
}
func (suite *KeeperTestSuite) getAccount(addr sdk.AccAddress) authexported.Account {
ak := suite.app.GetAccountKeeper()
return ak.GetAccount(suite.ctx, addr)

View File

@ -75,8 +75,8 @@ func (suite *KeeperTestSuite) TestApplyDepositRewards() {
),
},
types.MoneyMarkets{
types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
},
), tc.args.previousBlockTime, types.DefaultDistributionTimes)
tApp.InitializeFromGenesisStates(app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})
@ -443,8 +443,8 @@ func harvestGenesisState(rewardRate sdk.Coin) app.GenesisState {
),
},
types.MoneyMarkets{
types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
},
),
types.DefaultPreviousBlockTime,

View File

@ -291,8 +291,8 @@ func (suite *KeeperTestSuite) TestSendTimeLockedCoinsToAccount() {
),
},
types.MoneyMarkets{
types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))),
},
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})

View File

@ -37,6 +37,7 @@ var (
ClaimsKeyPrefix = []byte{0x04}
BorrowsKeyPrefix = []byte{0x05}
BorrowedCoinsPrefix = []byte{0x06}
InterestRateModelsPrefix = []byte{0x07}
sep = []byte(":")
)

View File

@ -253,20 +253,22 @@ func (bl BorrowLimit) Validate() error {
// MoneyMarket is a money market for an individual asset
type MoneyMarket struct {
Denom string `json:"denom" yaml:"denom"`
BorrowLimit BorrowLimit `json:"borrow_limit" yaml:"borrow_limit"`
SpotMarketID string `json:"spot_market_id" yaml:"spot_market_id"`
ConversionFactor sdk.Int `json:"conversion_factor" yaml:"conversion_factor"`
Denom string `json:"denom" yaml:"denom"`
BorrowLimit BorrowLimit `json:"borrow_limit" yaml:"borrow_limit"`
SpotMarketID string `json:"spot_market_id" yaml:"spot_market_id"`
ConversionFactor sdk.Int `json:"conversion_factor" yaml:"conversion_factor"`
InterestRateModel InterestRateModel `json:"interest_rate_model" yaml:"interest_rate_model"`
}
// NewMoneyMarket returns a new MoneyMarket
func NewMoneyMarket(denom string, hasMaxLimit bool, maximumLimit, loanToValue sdk.Dec,
spotMarketID string, conversionFactor sdk.Int) MoneyMarket {
spotMarketID string, conversionFactor sdk.Int, interestRateModel InterestRateModel) MoneyMarket {
return MoneyMarket{
Denom: denom,
BorrowLimit: NewBorrowLimit(hasMaxLimit, maximumLimit, loanToValue),
SpotMarketID: spotMarketID,
ConversionFactor: conversionFactor,
Denom: denom,
BorrowLimit: NewBorrowLimit(hasMaxLimit, maximumLimit, loanToValue),
SpotMarketID: spotMarketID,
ConversionFactor: conversionFactor,
InterestRateModel: interestRateModel,
}
}
@ -279,6 +281,10 @@ func (mm MoneyMarket) Validate() error {
if err := mm.BorrowLimit.Validate(); err != nil {
return err
}
if err := mm.InterestRateModel.Validate(); err != nil {
return err
}
return nil
}
@ -295,6 +301,65 @@ func (mms MoneyMarkets) Validate() error {
return nil
}
// InterestRateModel contains information about an asset's interest rate
type InterestRateModel struct {
BaseRateAPY sdk.Dec `json:"base_rate_apy" yaml:"base_rate_apy"`
BaseMultiplier sdk.Dec `json:"base_multiplier" yaml:"base_multiplier"`
Kink sdk.Dec `json:"kink" yaml:"kink"`
JumpMultiplier sdk.Dec `json:"jump_multiplier" yaml:"jump_multiplier"`
}
// NewInterestRateModel returns a new InterestRateModel
func NewInterestRateModel(baseRateAPY, baseMultiplier, kink, jumpMultiplier sdk.Dec) InterestRateModel {
return InterestRateModel{
BaseRateAPY: baseRateAPY,
BaseMultiplier: baseMultiplier,
Kink: kink,
JumpMultiplier: jumpMultiplier,
}
}
// Validate InterestRateModel param
func (irm InterestRateModel) Validate() error {
if irm.BaseRateAPY.IsNegative() || irm.BaseRateAPY.GT(sdk.OneDec()) {
return fmt.Errorf("Base rate APY must be between 0.0-1.0")
}
if irm.BaseMultiplier.IsNegative() {
return fmt.Errorf("Base multiplier must be positive")
}
if irm.Kink.IsNegative() || irm.Kink.GT(sdk.OneDec()) {
return fmt.Errorf("Kink must be between 0.0-1.0")
}
if irm.JumpMultiplier.IsNegative() {
return fmt.Errorf("Jump multiplier must be positive")
}
return nil
}
// Equal returns a boolean indicating if an InterestRateModel is equal to another InterestRateModel
func (irm InterestRateModel) Equal(comparisonIRM InterestRateModel) bool {
if !irm.BaseRateAPY.Equal(comparisonIRM.BaseRateAPY) {
return false
}
if !irm.BaseMultiplier.Equal(comparisonIRM.BaseMultiplier) {
return false
}
if !irm.Kink.Equal(comparisonIRM.Kink) {
return false
}
if !irm.JumpMultiplier.Equal(comparisonIRM.JumpMultiplier) {
return false
}
return true
}
// InterestRateModels slice of InterestRateModel
type InterestRateModels []InterestRateModel
// NewParams returns a new params object
func NewParams(active bool, lps DistributionSchedules, dds DelegatorDistributionSchedules, moneyMarkets MoneyMarkets) Params {
return Params{