Harvest: borrows limited by global asset borrow limit (#715)

* update MaximumLimit param to MaximumLimitUSD

* track total borrowed coins in the store

* implement total borrowed coins querier

* add maximum value usd check

* update test suite, add zero coins check

* add test case, update error msg

* max limit in native amount

* remove debug logging

* prepare for master rebase

* master rebase

* fix build
This commit is contained in:
Denali Marsh 2020-11-12 16:50:54 +01:00 committed by GitHub
parent cfb1905ad3
commit 510b7e7c04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 210 additions and 25 deletions

View File

@ -308,3 +308,30 @@ func queryBorrowsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
cmd.Flags().String(flagOwner, "", "(optional) filter for borrows by owner address")
return cmd
}
func queryBorrowedCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "borrowed",
Short: "get total current borrowed amount",
Long: "get the total amount of coins currently borrowed for the Harvest protocol",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
// Query
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetBorrowed)
res, height, err := cliCtx.QueryWithData(route, nil)
if err != nil {
return err
}
cliCtx = cliCtx.WithHeight(height)
// Decode and print results
var borrowedCoins sdk.Coins
if err := cdc.UnmarshalJSON(res, &borrowedCoins); err != nil {
return fmt.Errorf("failed to unmarshal borrowed coins: %w", err)
}
return cliCtx.PrintOutput(borrowedCoins)
},
}
}

View File

@ -11,11 +11,13 @@ import (
// Borrow funds
func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins) error {
// Validate borrow amount within user and protocol limits
err := k.ValidateBorrow(ctx, borrower, coins)
if err != nil {
return err
}
// Sends coins from Harvest module account to user
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, borrower, coins)
if err != nil {
if strings.Contains(err.Error(), "insufficient account funds") {
@ -32,6 +34,7 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins
}
}
// Update user's borrow in store
borrow, found := k.GetBorrow(ctx, borrower)
if !found {
borrow = types.NewBorrow(borrower, coins)
@ -40,6 +43,9 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins
}
k.SetBorrow(ctx, borrow)
// Update total borrowed amount
k.IncrementBorrowedCoins(ctx, coins)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeHarvestBorrow,
@ -53,6 +59,10 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins
// ValidateBorrow validates a borrow request against borrower and protocol requirements
func (k Keeper) ValidateBorrow(ctx sdk.Context, borrower sdk.AccAddress, amount sdk.Coins) error {
if amount.IsZero() {
return types.ErrBorrowEmptyCoins
}
// Get the proposed borrow USD value
moneyMarketCache := map[string]types.MoneyMarket{}
proprosedBorrowUSDValue := sdk.ZeroDec()
@ -74,6 +84,23 @@ func (k Keeper) ValidateBorrow(ctx sdk.Context, borrower sdk.AccAddress, amount
return sdkerrors.Wrapf(types.ErrPriceNotFound, "no price found for market %s", moneyMarket.SpotMarketID)
}
coinUSDValue := sdk.NewDecFromInt(coin.Amount).Quo(sdk.NewDecFromInt(moneyMarket.ConversionFactor)).Mul(assetPriceInfo.Price)
// Validate the requested borrow value for the asset against the money market's global borrow limit
if moneyMarket.BorrowLimit.HasMaxLimit {
var assetTotalBorrowedAmount sdk.Int
totalBorrowedCoins, found := k.GetBorrowedCoins(ctx)
if !found {
assetTotalBorrowedAmount = sdk.ZeroInt()
} else {
assetTotalBorrowedAmount = totalBorrowedCoins.AmountOf(coin.Denom)
}
newProposedAssetTotalBorrowedAmount := sdk.NewDecFromInt(assetTotalBorrowedAmount.Add(coin.Amount))
if newProposedAssetTotalBorrowedAmount.GT(moneyMarket.BorrowLimit.MaximumLimit) {
return sdkerrors.Wrapf(types.ErrGreaterThanAssetBorrowLimit,
"proposed borrow would result in %s borrowed, but the maximum global asset borrow limit is %s",
newProposedAssetTotalBorrowedAmount, moneyMarket.BorrowLimit.MaximumLimit)
}
}
proprosedBorrowUSDValue = proprosedBorrowUSDValue.Add(coinUSDValue)
}
@ -137,3 +164,29 @@ func (k Keeper) ValidateBorrow(ctx sdk.Context, borrower sdk.AccAddress, amount
}
return nil
}
// IncrementBorrowedCoins increments the amount of borrowed coins by the newCoins parameter
func (k Keeper) IncrementBorrowedCoins(ctx sdk.Context, newCoins sdk.Coins) {
borrowedCoins, found := k.GetBorrowedCoins(ctx)
if !found {
k.SetBorrowedCoins(ctx, newCoins)
} else {
k.SetBorrowedCoins(ctx, borrowedCoins.Add(newCoins...))
}
}
// DecrementBorrowedCoins decrements the amount of borrowed coins by the coins parameter
func (k Keeper) DecrementBorrowedCoins(ctx sdk.Context, coins sdk.Coins) error {
borrowedCoins, found := k.GetBorrowedCoins(ctx)
if !found {
return sdkerrors.Wrapf(types.ErrBorrowedCoinsNotFound, "cannot repay coins if no coins are currently borrowed")
}
updatedBorrowedCoins, isAnyNegative := borrowedCoins.SafeSub(coins)
if isAnyNegative {
return types.ErrNegativeBorrowedCoins
}
k.SetBorrowedCoins(ctx, updatedBorrowedCoins)
return nil
}

View File

@ -25,6 +25,7 @@ const (
func (suite *KeeperTestSuite) TestBorrow() {
type args struct {
usdxBorrowLimit sdk.Dec
priceKAVA sdk.Dec
loanToValueKAVA sdk.Dec
priceBTCB sdk.Dec
@ -51,6 +52,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
{
"valid",
args{
usdxBorrowLimit: sdk.MustNewDecFromStr("100000000000"),
priceKAVA: sdk.MustNewDecFromStr("5.00"),
loanToValueKAVA: sdk.MustNewDecFromStr("0.6"),
priceBTCB: sdk.MustNewDecFromStr("0.00"),
@ -72,6 +74,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
{
"invalid: loan-to-value limited",
args{
usdxBorrowLimit: sdk.MustNewDecFromStr("100000000000"),
priceKAVA: sdk.MustNewDecFromStr("5.00"),
loanToValueKAVA: sdk.MustNewDecFromStr("0.6"),
priceBTCB: sdk.MustNewDecFromStr("0.00"),
@ -92,6 +95,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
{
"valid: multiple deposits",
args{
usdxBorrowLimit: sdk.MustNewDecFromStr("100000000000"),
priceKAVA: sdk.MustNewDecFromStr("2.00"),
loanToValueKAVA: sdk.MustNewDecFromStr("0.80"),
priceBTCB: sdk.MustNewDecFromStr("10000.00"),
@ -112,6 +116,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
{
"invalid: multiple deposits",
args{
usdxBorrowLimit: sdk.MustNewDecFromStr("100000000000"),
priceKAVA: sdk.MustNewDecFromStr("2.00"),
loanToValueKAVA: sdk.MustNewDecFromStr("0.80"),
priceBTCB: sdk.MustNewDecFromStr("10000.00"),
@ -132,6 +137,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
{
"valid: multiple previous borrows",
args{
usdxBorrowLimit: sdk.MustNewDecFromStr("100000000000"),
priceKAVA: sdk.MustNewDecFromStr("2.00"),
loanToValueKAVA: sdk.MustNewDecFromStr("0.8"),
priceBTCB: sdk.MustNewDecFromStr("0.00"),
@ -153,6 +159,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
{
"invalid: over loan-to-value with multiple previous borrows",
args{
usdxBorrowLimit: sdk.MustNewDecFromStr("100000000000"),
priceKAVA: sdk.MustNewDecFromStr("2.00"),
loanToValueKAVA: sdk.MustNewDecFromStr("0.8"),
priceBTCB: sdk.MustNewDecFromStr("0.00"),
@ -174,6 +181,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
{
"invalid: no price for asset",
args{
usdxBorrowLimit: sdk.MustNewDecFromStr("100000000000"),
priceKAVA: sdk.MustNewDecFromStr("5.00"),
loanToValueKAVA: sdk.MustNewDecFromStr("0.6"),
priceBTCB: sdk.MustNewDecFromStr("0.00"),
@ -195,6 +203,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
{
"invalid: borrow exceed module account balance",
args{
usdxBorrowLimit: sdk.MustNewDecFromStr("100000000000"),
priceKAVA: sdk.MustNewDecFromStr("2.00"),
loanToValueKAVA: sdk.MustNewDecFromStr("0.8"),
priceBTCB: sdk.MustNewDecFromStr("0.00"),
@ -213,6 +222,28 @@ func (suite *KeeperTestSuite) TestBorrow() {
contains: "exceeds module account balance:",
},
},
{
"invalid: over global asset borrow limit",
args{
usdxBorrowLimit: sdk.MustNewDecFromStr("20000000"),
priceKAVA: sdk.MustNewDecFromStr("2.00"),
loanToValueKAVA: sdk.MustNewDecFromStr("0.8"),
priceBTCB: sdk.MustNewDecFromStr("0.00"),
loanToValueBTCB: sdk.MustNewDecFromStr("0.01"),
priceBNB: sdk.MustNewDecFromStr("0.00"),
loanToValueBNB: sdk.MustNewDecFromStr("0.01"),
borrower: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
depositCoins: []sdk.Coin{sdk.NewCoin("ukava", sdk.NewInt(50*KAVA_CF))},
previousBorrowCoins: sdk.NewCoins(),
borrowCoins: sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(25*USDX_CF))),
expectedAccountBalance: sdk.NewCoins(),
expectedModAccountBalance: sdk.NewCoins(),
},
errArgs{
expectPass: false,
contains: "fails global asset borrow limit validation",
},
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
@ -244,12 +275,12 @@ func (suite *KeeperTestSuite) TestBorrow() {
),
},
types.MoneyMarkets{
types.NewMoneyMarket("usdx", sdk.NewInt(100000000*USDX_CF), sdk.MustNewDecFromStr("1"), "usdx:usd", sdk.NewInt(USDX_CF)),
types.NewMoneyMarket("busd", sdk.NewInt(100000000*BUSD_CF), sdk.MustNewDecFromStr("1"), "busd:usd", sdk.NewInt(BUSD_CF)),
types.NewMoneyMarket("ukava", sdk.NewInt(100000000*KAVA_CF), tc.args.loanToValueKAVA, "kava:usd", sdk.NewInt(KAVA_CF)),
types.NewMoneyMarket("btcb", sdk.NewInt(100000000*BTCB_CF), tc.args.loanToValueBTCB, "btcb:usd", sdk.NewInt(BTCB_CF)),
types.NewMoneyMarket("bnb", sdk.NewInt(100000000*BNB_CF), tc.args.loanToValueBNB, "bnb:usd", sdk.NewInt(BNB_CF)),
types.NewMoneyMarket("xyz", sdk.NewInt(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.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.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
@ -327,7 +358,11 @@ func (suite *KeeperTestSuite) TestBorrow() {
// Execute user's previous borrows
err = suite.keeper.Borrow(suite.ctx, tc.args.borrower, tc.args.previousBorrowCoins)
suite.Require().NoError(err)
if tc.args.previousBorrowCoins.IsZero() {
suite.Require().True(strings.Contains(err.Error(), "cannot borrow zero coins"))
} else {
suite.Require().NoError(err)
}
// Now that our state is properly set up, execute the last borrow
err = suite.keeper.Borrow(suite.ctx, tc.args.borrower, tc.args.borrowCoins)

View File

@ -265,8 +265,8 @@ func (suite *KeeperTestSuite) TestClaim() {
),
},
types.MoneyMarkets{
types.NewMoneyMarket("usdx", sdk.NewInt(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", sdk.NewInt(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
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.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})

View File

@ -128,8 +128,8 @@ func (suite *KeeperTestSuite) TestDeposit() {
),
},
types.MoneyMarkets{
types.NewMoneyMarket("usdx", sdk.NewInt(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", sdk.NewInt(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
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.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})
@ -300,8 +300,8 @@ func (suite *KeeperTestSuite) TestWithdraw() {
),
},
types.MoneyMarkets{
types.NewMoneyMarket("usdx", sdk.NewInt(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", sdk.NewInt(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
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.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})

View File

@ -237,3 +237,22 @@ func (k Keeper) IterateBorrows(ctx sdk.Context, cb func(borrow types.Borrow) (st
}
}
}
// SetBorrowedCoins sets the total amount of coins currently borrowed in the store
func (k Keeper) SetBorrowedCoins(ctx sdk.Context, borrowedCoins sdk.Coins) {
store := prefix.NewStore(ctx.KVStore(k.key), types.BorrowedCoinsPrefix)
bz := k.cdc.MustMarshalBinaryBare(borrowedCoins)
store.Set([]byte{}, bz)
}
// GetBorrowedCoins returns an sdk.Coins object from the store representing all currently borrowed coins
func (k Keeper) GetBorrowedCoins(ctx sdk.Context) (sdk.Coins, bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.BorrowedCoinsPrefix)
bz := store.Get([]byte{})
if bz == nil {
return sdk.Coins{}, false
}
var borrowedCoins sdk.Coins
k.cdc.MustUnmarshalBinaryBare(bz, &borrowedCoins)
return borrowedCoins, true
}

View File

@ -26,6 +26,8 @@ func NewQuerier(k Keeper) sdk.Querier {
return queryGetClaims(ctx, req, k)
case types.QueryGetBorrows:
return queryGetBorrows(ctx, req, k)
case types.QueryGetBorrowed:
return queryGetBorrowed(ctx, req, k)
default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint", types.ModuleName)
}
@ -295,3 +297,28 @@ func queryGetBorrows(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte,
return bz, nil
}
func queryGetBorrowed(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) {
var params types.QueryBorrowedParams
err := types.ModuleCdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
borrowedCoins, found := k.GetBorrowedCoins(ctx)
if !found {
return nil, types.ErrBorrowedCoinsNotFound
}
// If user specified a denom only return coins of that denom type
if len(params.Denom) > 0 {
borrowedCoins = sdk.NewCoins(sdk.NewCoin(params.Denom, borrowedCoins.AmountOf(params.Denom)))
}
bz, err := codec.MarshalJSONIndent(types.ModuleCdc, borrowedCoins)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}

View File

@ -75,8 +75,8 @@ func (suite *KeeperTestSuite) TestApplyDepositRewards() {
),
},
types.MoneyMarkets{
types.NewMoneyMarket("usdx", sdk.NewInt(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", sdk.NewInt(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
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)),
},
), 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", sdk.NewInt(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", sdk.NewInt(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
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.DefaultPreviousBlockTime,

View File

@ -291,8 +291,8 @@ func (suite *KeeperTestSuite) TestSendTimeLockedCoinsToAccount() {
),
},
types.MoneyMarkets{
types.NewMoneyMarket("usdx", sdk.NewInt(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", sdk.NewInt(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
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.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})

View File

@ -47,4 +47,12 @@ var (
ErrPriceNotFound = sdkerrors.Register(ModuleName, 20, "no price found for market")
// ErrBorrowExceedsAvailableBalance for when a requested borrow exceeds available module acc balances
ErrBorrowExceedsAvailableBalance = sdkerrors.Register(ModuleName, 21, "exceeds module account balance")
// ErrBorrowedCoinsNotFound error for when the total amount of borrowed coins cannot be found
ErrBorrowedCoinsNotFound = sdkerrors.Register(ModuleName, 22, "no borrowed coins found")
// ErrNegativeBorrowedCoins error for when substracting coins from the total borrowed balance results in a negative amount
ErrNegativeBorrowedCoins = sdkerrors.Register(ModuleName, 23, "subtraction results in negative borrow amount")
// ErrGreaterThanAssetBorrowLimit error for when a proposed borrow would increase borrowed amount over the asset's global borrow limit
ErrGreaterThanAssetBorrowLimit = sdkerrors.Register(ModuleName, 24, "fails global asset borrow limit validation")
// ErrBorrowEmptyCoins error for when you cannot borrow empty coins
ErrBorrowEmptyCoins = sdkerrors.Register(ModuleName, 25, "cannot borrow zero coins")
)

View File

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

View File

@ -223,13 +223,15 @@ type Multipliers []Multiplier
// BorrowLimit enforces restrictions on a money market
type BorrowLimit struct {
MaximumLimit sdk.Int `json:"maximum_limit" yaml:"maximum_limit"`
HasMaxLimit bool `json:"has_max_limit" yaml:"has_max_limit"`
MaximumLimit sdk.Dec `json:"maximum_limit" yaml:"maximum_limit"`
LoanToValue sdk.Dec `json:"loan_to_value" yaml:"loan_to_value"`
}
// NewBorrowLimit returns a new BorrowLimit
func NewBorrowLimit(maximumLimit sdk.Int, loanToValue sdk.Dec) BorrowLimit {
func NewBorrowLimit(hasMaxLimit bool, maximumLimit, loanToValue sdk.Dec) BorrowLimit {
return BorrowLimit{
HasMaxLimit: hasMaxLimit,
MaximumLimit: maximumLimit,
LoanToValue: loanToValue,
}
@ -238,7 +240,7 @@ func NewBorrowLimit(maximumLimit sdk.Int, loanToValue sdk.Dec) BorrowLimit {
// Validate BorrowLimit
func (bl BorrowLimit) Validate() error {
if bl.MaximumLimit.IsNegative() {
return fmt.Errorf("maximum limit cannot be negative: %s", bl.MaximumLimit)
return fmt.Errorf("maximum limit USD cannot be negative: %s", bl.MaximumLimit)
}
if !bl.LoanToValue.IsPositive() {
return fmt.Errorf("loan-to-value must be a positive integer: %s", bl.LoanToValue)
@ -258,11 +260,11 @@ type MoneyMarket struct {
}
// NewMoneyMarket returns a new MoneyMarket
func NewMoneyMarket(denom string, maximumLimit sdk.Int, loanToValue sdk.Dec,
func NewMoneyMarket(denom string, hasMaxLimit bool, maximumLimit, loanToValue sdk.Dec,
spotMarketID string, conversionFactor sdk.Int) MoneyMarket {
return MoneyMarket{
Denom: denom,
BorrowLimit: NewBorrowLimit(maximumLimit, loanToValue),
BorrowLimit: NewBorrowLimit(hasMaxLimit, maximumLimit, loanToValue),
SpotMarketID: spotMarketID,
ConversionFactor: conversionFactor,
}
@ -314,7 +316,7 @@ func (p Params) String() string {
Active: %t
Liquidity Provider Distribution Schedules %s
Delegator Distribution Schedule %s
Money Markets %s`, p.Active, p.LiquidityProviderSchedules, p.DelegatorDistributionSchedules, p.MoneyMarkets)
Money Markets %v`, p.Active, p.LiquidityProviderSchedules, p.DelegatorDistributionSchedules, p.MoneyMarkets)
}
// ParamKeyTable Key declaration for parameters

View File

@ -11,6 +11,7 @@ const (
QueryGetDeposits = "deposits"
QueryGetClaims = "claims"
QueryGetBorrows = "borrows"
QueryGetBorrowed = "borrowed"
)
// QueryDepositParams is the params for a filtered deposit query
@ -86,3 +87,15 @@ func NewQueryBorrowParams(page, limit int, owner sdk.AccAddress, depositDenom st
BorrowDenom: depositDenom,
}
}
// QueryBorrowedParams is the params for a filtered borrowed coins query
type QueryBorrowedParams struct {
Denom string `json:"denom" yaml:"denom"`
}
// NewQueryBorrowedParams creates a new QueryBorrowedParams
func NewQueryBorrowedParams(denom string) QueryBorrowedParams {
return QueryBorrowedParams{
Denom: denom,
}
}