diff --git a/x/harvest/client/cli/query.go b/x/harvest/client/cli/query.go index adb7c8f4..a93cc809 100644 --- a/x/harvest/client/cli/query.go +++ b/x/harvest/client/cli/query.go @@ -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) + }, + } +} diff --git a/x/harvest/keeper/borrow.go b/x/harvest/keeper/borrow.go index f56a6b69..adc3a063 100644 --- a/x/harvest/keeper/borrow.go +++ b/x/harvest/keeper/borrow.go @@ -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 +} diff --git a/x/harvest/keeper/borrow_test.go b/x/harvest/keeper/borrow_test.go index bb8fa330..d3a49222 100644 --- a/x/harvest/keeper/borrow_test.go +++ b/x/harvest/keeper/borrow_test.go @@ -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) diff --git a/x/harvest/keeper/claim_test.go b/x/harvest/keeper/claim_test.go index 4510cdd0..82d504cf 100644 --- a/x/harvest/keeper/claim_test.go +++ b/x/harvest/keeper/claim_test.go @@ -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)}) diff --git a/x/harvest/keeper/deposit_test.go b/x/harvest/keeper/deposit_test.go index 2f1ce448..bfef336e 100644 --- a/x/harvest/keeper/deposit_test.go +++ b/x/harvest/keeper/deposit_test.go @@ -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)}) diff --git a/x/harvest/keeper/keeper.go b/x/harvest/keeper/keeper.go index 7221fa55..b9fb983e 100644 --- a/x/harvest/keeper/keeper.go +++ b/x/harvest/keeper/keeper.go @@ -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 +} diff --git a/x/harvest/keeper/querier.go b/x/harvest/keeper/querier.go index 756bc0bf..aed0822e 100644 --- a/x/harvest/keeper/querier.go +++ b/x/harvest/keeper/querier.go @@ -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, ¶ms) + 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 +} diff --git a/x/harvest/keeper/rewards_test.go b/x/harvest/keeper/rewards_test.go index 215fdea5..29002bbd 100644 --- a/x/harvest/keeper/rewards_test.go +++ b/x/harvest/keeper/rewards_test.go @@ -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, diff --git a/x/harvest/keeper/timelock_test.go b/x/harvest/keeper/timelock_test.go index 312464ca..4665f74c 100644 --- a/x/harvest/keeper/timelock_test.go +++ b/x/harvest/keeper/timelock_test.go @@ -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)}) diff --git a/x/harvest/types/errors.go b/x/harvest/types/errors.go index 9c09cb17..5146285d 100644 --- a/x/harvest/types/errors.go +++ b/x/harvest/types/errors.go @@ -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") ) diff --git a/x/harvest/types/keys.go b/x/harvest/types/keys.go index d1d5cf3c..46da2845 100644 --- a/x/harvest/types/keys.go +++ b/x/harvest/types/keys.go @@ -36,6 +36,7 @@ var ( DepositsKeyPrefix = []byte{0x03} ClaimsKeyPrefix = []byte{0x04} BorrowsKeyPrefix = []byte{0x05} + BorrowedCoinsPrefix = []byte{0x06} sep = []byte(":") ) diff --git a/x/harvest/types/params.go b/x/harvest/types/params.go index f65d00df..3fd6f44a 100644 --- a/x/harvest/types/params.go +++ b/x/harvest/types/params.go @@ -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 diff --git a/x/harvest/types/querier.go b/x/harvest/types/querier.go index 65675cb6..14c38a42 100644 --- a/x/harvest/types/querier.go +++ b/x/harvest/types/querier.go @@ -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, + } +}