mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 08:15:19 +00:00
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:
parent
cfb1905ad3
commit
510b7e7c04
@ -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)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
if tc.args.previousBorrowCoins.IsZero() {
|
||||||
|
suite.Require().True(strings.Contains(err.Error(), "cannot borrow zero coins"))
|
||||||
|
} else {
|
||||||
suite.Require().NoError(err)
|
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)
|
||||||
|
@ -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)})
|
||||||
|
@ -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)})
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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, ¶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
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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)})
|
||||||
|
@ -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")
|
||||||
)
|
)
|
||||||
|
@ -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(":")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user