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") cmd.Flags().String(flagOwner, "", "(optional) filter for borrows by owner address")
return cmd 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 // Borrow funds
func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins) error { 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) err := k.ValidateBorrow(ctx, borrower, coins)
if err != nil { if err != nil {
return err return err
} }
// Sends coins from Harvest module account to user
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, borrower, coins) err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, borrower, coins)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "insufficient account funds") { 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) borrow, found := k.GetBorrow(ctx, borrower)
if !found { if !found {
borrow = types.NewBorrow(borrower, coins) 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) k.SetBorrow(ctx, borrow)
// Update total borrowed amount
k.IncrementBorrowedCoins(ctx, coins)
ctx.EventManager().EmitEvent( ctx.EventManager().EmitEvent(
sdk.NewEvent( sdk.NewEvent(
types.EventTypeHarvestBorrow, 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 // ValidateBorrow validates a borrow request against borrower and protocol requirements
func (k Keeper) ValidateBorrow(ctx sdk.Context, borrower sdk.AccAddress, amount sdk.Coins) error { 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 // Get the proposed borrow USD value
moneyMarketCache := map[string]types.MoneyMarket{} moneyMarketCache := map[string]types.MoneyMarket{}
proprosedBorrowUSDValue := sdk.ZeroDec() 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) 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) 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) proprosedBorrowUSDValue = proprosedBorrowUSDValue.Add(coinUSDValue)
} }
@ -137,3 +164,29 @@ func (k Keeper) ValidateBorrow(ctx sdk.Context, borrower sdk.AccAddress, amount
} }
return nil 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() { func (suite *KeeperTestSuite) TestBorrow() {
type args struct { type args struct {
usdxBorrowLimit sdk.Dec
priceKAVA sdk.Dec priceKAVA sdk.Dec
loanToValueKAVA sdk.Dec loanToValueKAVA sdk.Dec
priceBTCB sdk.Dec priceBTCB sdk.Dec
@ -51,6 +52,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
{ {
"valid", "valid",
args{ args{
usdxBorrowLimit: sdk.MustNewDecFromStr("100000000000"),
priceKAVA: sdk.MustNewDecFromStr("5.00"), priceKAVA: sdk.MustNewDecFromStr("5.00"),
loanToValueKAVA: sdk.MustNewDecFromStr("0.6"), loanToValueKAVA: sdk.MustNewDecFromStr("0.6"),
priceBTCB: sdk.MustNewDecFromStr("0.00"), priceBTCB: sdk.MustNewDecFromStr("0.00"),
@ -72,6 +74,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
{ {
"invalid: loan-to-value limited", "invalid: loan-to-value limited",
args{ args{
usdxBorrowLimit: sdk.MustNewDecFromStr("100000000000"),
priceKAVA: sdk.MustNewDecFromStr("5.00"), priceKAVA: sdk.MustNewDecFromStr("5.00"),
loanToValueKAVA: sdk.MustNewDecFromStr("0.6"), loanToValueKAVA: sdk.MustNewDecFromStr("0.6"),
priceBTCB: sdk.MustNewDecFromStr("0.00"), priceBTCB: sdk.MustNewDecFromStr("0.00"),
@ -92,6 +95,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
{ {
"valid: multiple deposits", "valid: multiple deposits",
args{ args{
usdxBorrowLimit: sdk.MustNewDecFromStr("100000000000"),
priceKAVA: sdk.MustNewDecFromStr("2.00"), priceKAVA: sdk.MustNewDecFromStr("2.00"),
loanToValueKAVA: sdk.MustNewDecFromStr("0.80"), loanToValueKAVA: sdk.MustNewDecFromStr("0.80"),
priceBTCB: sdk.MustNewDecFromStr("10000.00"), priceBTCB: sdk.MustNewDecFromStr("10000.00"),
@ -112,6 +116,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
{ {
"invalid: multiple deposits", "invalid: multiple deposits",
args{ args{
usdxBorrowLimit: sdk.MustNewDecFromStr("100000000000"),
priceKAVA: sdk.MustNewDecFromStr("2.00"), priceKAVA: sdk.MustNewDecFromStr("2.00"),
loanToValueKAVA: sdk.MustNewDecFromStr("0.80"), loanToValueKAVA: sdk.MustNewDecFromStr("0.80"),
priceBTCB: sdk.MustNewDecFromStr("10000.00"), priceBTCB: sdk.MustNewDecFromStr("10000.00"),
@ -132,6 +137,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
{ {
"valid: multiple previous borrows", "valid: multiple previous borrows",
args{ args{
usdxBorrowLimit: sdk.MustNewDecFromStr("100000000000"),
priceKAVA: sdk.MustNewDecFromStr("2.00"), priceKAVA: sdk.MustNewDecFromStr("2.00"),
loanToValueKAVA: sdk.MustNewDecFromStr("0.8"), loanToValueKAVA: sdk.MustNewDecFromStr("0.8"),
priceBTCB: sdk.MustNewDecFromStr("0.00"), priceBTCB: sdk.MustNewDecFromStr("0.00"),
@ -153,6 +159,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
{ {
"invalid: over loan-to-value with multiple previous borrows", "invalid: over loan-to-value with multiple previous borrows",
args{ args{
usdxBorrowLimit: sdk.MustNewDecFromStr("100000000000"),
priceKAVA: sdk.MustNewDecFromStr("2.00"), priceKAVA: sdk.MustNewDecFromStr("2.00"),
loanToValueKAVA: sdk.MustNewDecFromStr("0.8"), loanToValueKAVA: sdk.MustNewDecFromStr("0.8"),
priceBTCB: sdk.MustNewDecFromStr("0.00"), priceBTCB: sdk.MustNewDecFromStr("0.00"),
@ -174,6 +181,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
{ {
"invalid: no price for asset", "invalid: no price for asset",
args{ args{
usdxBorrowLimit: sdk.MustNewDecFromStr("100000000000"),
priceKAVA: sdk.MustNewDecFromStr("5.00"), priceKAVA: sdk.MustNewDecFromStr("5.00"),
loanToValueKAVA: sdk.MustNewDecFromStr("0.6"), loanToValueKAVA: sdk.MustNewDecFromStr("0.6"),
priceBTCB: sdk.MustNewDecFromStr("0.00"), priceBTCB: sdk.MustNewDecFromStr("0.00"),
@ -195,6 +203,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
{ {
"invalid: borrow exceed module account balance", "invalid: borrow exceed module account balance",
args{ args{
usdxBorrowLimit: sdk.MustNewDecFromStr("100000000000"),
priceKAVA: sdk.MustNewDecFromStr("2.00"), priceKAVA: sdk.MustNewDecFromStr("2.00"),
loanToValueKAVA: sdk.MustNewDecFromStr("0.8"), loanToValueKAVA: sdk.MustNewDecFromStr("0.8"),
priceBTCB: sdk.MustNewDecFromStr("0.00"), priceBTCB: sdk.MustNewDecFromStr("0.00"),
@ -213,6 +222,28 @@ func (suite *KeeperTestSuite) TestBorrow() {
contains: "exceeds module account balance:", 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 { for _, tc := range testCases {
suite.Run(tc.name, func() { suite.Run(tc.name, func() {
@ -244,12 +275,12 @@ func (suite *KeeperTestSuite) TestBorrow() {
), ),
}, },
types.MoneyMarkets{ types.MoneyMarkets{
types.NewMoneyMarket("usdx", sdk.NewInt(100000000*USDX_CF), sdk.MustNewDecFromStr("1"), "usdx:usd", sdk.NewInt(USDX_CF)), types.NewMoneyMarket("usdx", true, tc.args.usdxBorrowLimit, 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("busd", false, sdk.NewDec(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("ukava", false, sdk.NewDec(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("btcb", false, sdk.NewDec(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("bnb", false, sdk.NewDec(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("xyz", false, sdk.NewDec(1), tc.args.loanToValueBNB, "xyz:usd", sdk.NewInt(1)),
}, },
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) ), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
@ -327,7 +358,11 @@ func (suite *KeeperTestSuite) TestBorrow() {
// Execute user's previous borrows // Execute user's previous borrows
err = suite.keeper.Borrow(suite.ctx, tc.args.borrower, tc.args.previousBorrowCoins) 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 // Now that our state is properly set up, execute the last borrow
err = suite.keeper.Borrow(suite.ctx, tc.args.borrower, tc.args.borrowCoins) 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.MoneyMarkets{
types.NewMoneyMarket("usdx", sdk.NewInt(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)), types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", sdk.NewInt(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)), types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
}, },
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) ), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)}) tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})

View File

@ -128,8 +128,8 @@ func (suite *KeeperTestSuite) TestDeposit() {
), ),
}, },
types.MoneyMarkets{ types.MoneyMarkets{
types.NewMoneyMarket("usdx", sdk.NewInt(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)), types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", sdk.NewInt(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)), types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
}, },
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) ), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)}) tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})
@ -300,8 +300,8 @@ func (suite *KeeperTestSuite) TestWithdraw() {
), ),
}, },
types.MoneyMarkets{ types.MoneyMarkets{
types.NewMoneyMarket("usdx", sdk.NewInt(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)), types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", sdk.NewInt(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)), types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
}, },
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) ), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)}) 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) return queryGetClaims(ctx, req, k)
case types.QueryGetBorrows: case types.QueryGetBorrows:
return queryGetBorrows(ctx, req, k) return queryGetBorrows(ctx, req, k)
case types.QueryGetBorrowed:
return queryGetBorrowed(ctx, req, k)
default: default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint", types.ModuleName) 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 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.MoneyMarkets{
types.NewMoneyMarket("usdx", sdk.NewInt(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)), types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", sdk.NewInt(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)), types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
}, },
), tc.args.previousBlockTime, types.DefaultDistributionTimes) ), tc.args.previousBlockTime, types.DefaultDistributionTimes)
tApp.InitializeFromGenesisStates(app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)}) tApp.InitializeFromGenesisStates(app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})
@ -443,8 +443,8 @@ func harvestGenesisState(rewardRate sdk.Coin) app.GenesisState {
), ),
}, },
types.MoneyMarkets{ types.MoneyMarkets{
types.NewMoneyMarket("usdx", sdk.NewInt(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)), types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", sdk.NewInt(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)), types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
}, },
), ),
types.DefaultPreviousBlockTime, types.DefaultPreviousBlockTime,

View File

@ -291,8 +291,8 @@ func (suite *KeeperTestSuite) TestSendTimeLockedCoinsToAccount() {
), ),
}, },
types.MoneyMarkets{ types.MoneyMarkets{
types.NewMoneyMarket("usdx", sdk.NewInt(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)), types.NewMoneyMarket("usdx", false, sdk.NewDec(1000000000000000), loanToValue, "usdx:usd", sdk.NewInt(1000000)),
types.NewMoneyMarket("ukava", sdk.NewInt(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)), types.NewMoneyMarket("ukava", false, sdk.NewDec(1000000000000000), loanToValue, "kava:usd", sdk.NewInt(1000000)),
}, },
), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) ), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)}) 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") ErrPriceNotFound = sdkerrors.Register(ModuleName, 20, "no price found for market")
// ErrBorrowExceedsAvailableBalance for when a requested borrow exceeds available module acc balances // ErrBorrowExceedsAvailableBalance for when a requested borrow exceeds available module acc balances
ErrBorrowExceedsAvailableBalance = sdkerrors.Register(ModuleName, 21, "exceeds module account balance") 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} DepositsKeyPrefix = []byte{0x03}
ClaimsKeyPrefix = []byte{0x04} ClaimsKeyPrefix = []byte{0x04}
BorrowsKeyPrefix = []byte{0x05} BorrowsKeyPrefix = []byte{0x05}
BorrowedCoinsPrefix = []byte{0x06}
sep = []byte(":") sep = []byte(":")
) )

View File

@ -223,13 +223,15 @@ type Multipliers []Multiplier
// BorrowLimit enforces restrictions on a money market // BorrowLimit enforces restrictions on a money market
type BorrowLimit struct { 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"` LoanToValue sdk.Dec `json:"loan_to_value" yaml:"loan_to_value"`
} }
// NewBorrowLimit returns a new BorrowLimit // 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{ return BorrowLimit{
HasMaxLimit: hasMaxLimit,
MaximumLimit: maximumLimit, MaximumLimit: maximumLimit,
LoanToValue: loanToValue, LoanToValue: loanToValue,
} }
@ -238,7 +240,7 @@ func NewBorrowLimit(maximumLimit sdk.Int, loanToValue sdk.Dec) BorrowLimit {
// Validate BorrowLimit // Validate BorrowLimit
func (bl BorrowLimit) Validate() error { func (bl BorrowLimit) Validate() error {
if bl.MaximumLimit.IsNegative() { 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() { if !bl.LoanToValue.IsPositive() {
return fmt.Errorf("loan-to-value must be a positive integer: %s", bl.LoanToValue) 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 // 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 { spotMarketID string, conversionFactor sdk.Int) MoneyMarket {
return MoneyMarket{ return MoneyMarket{
Denom: denom, Denom: denom,
BorrowLimit: NewBorrowLimit(maximumLimit, loanToValue), BorrowLimit: NewBorrowLimit(hasMaxLimit, maximumLimit, loanToValue),
SpotMarketID: spotMarketID, SpotMarketID: spotMarketID,
ConversionFactor: conversionFactor, ConversionFactor: conversionFactor,
} }
@ -314,7 +316,7 @@ func (p Params) String() string {
Active: %t Active: %t
Liquidity Provider Distribution Schedules %s Liquidity Provider Distribution Schedules %s
Delegator Distribution Schedule %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 // ParamKeyTable Key declaration for parameters

View File

@ -11,6 +11,7 @@ const (
QueryGetDeposits = "deposits" QueryGetDeposits = "deposits"
QueryGetClaims = "claims" QueryGetClaims = "claims"
QueryGetBorrows = "borrows" QueryGetBorrows = "borrows"
QueryGetBorrowed = "borrowed"
) )
// QueryDepositParams is the params for a filtered deposit query // 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, 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,
}
}