mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-24 15:25:18 +00:00
Hard Audit: add minimum borrow USD value (#822)
* add module param MinimumBorrowUSDValue * borrow/repay min limit restrictions * add borrow/repay test cases * update tests with new module params * update timelock test with param * update withdraw LTV test * remove unused GetCurrentBorrowUSDValue method * commit to prompt CircleCI run
This commit is contained in:
parent
cd7a227030
commit
fe2a131b31
@ -208,6 +208,12 @@ func (k Keeper) ValidateBorrow(ctx sdk.Context, borrower sdk.AccAddress, amount
|
||||
}
|
||||
}
|
||||
|
||||
// Borrow's updated total USD value must be greater than the minimum global USD borrow limit
|
||||
totalBorrowUSDValue := proprosedBorrowUSDValue.Add(existingBorrowUSDValue)
|
||||
if totalBorrowUSDValue.LT(k.GetMinimumBorrowUSDValue(ctx)) {
|
||||
return sdkerrors.Wrapf(types.ErrBelowMinimumBorrowValue, "the proposed borrow's USD value $%s is below the minimum borrow limit $%s", totalBorrowUSDValue, k.GetMinimumBorrowUSDValue(ctx))
|
||||
}
|
||||
|
||||
// Validate that the proposed borrow's USD value is within user's borrowable limit
|
||||
if proprosedBorrowUSDValue.GT(totalBorrowableAmount.Sub(existingBorrowUSDValue)) {
|
||||
return sdkerrors.Wrapf(types.ErrInsufficientLoanToValue, "requested borrow %s exceeds the allowable amount as determined by the collateralization ratio", amount)
|
||||
|
@ -245,6 +245,50 @@ func (suite *KeeperTestSuite) TestBorrow() {
|
||||
contains: "fails global asset borrow limit validation",
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid: borrowing an individual coin type results in a borrow that's under the minimum USD 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.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(50*KAVA_CF))),
|
||||
previousBorrowCoins: sdk.NewCoins(),
|
||||
borrowCoins: sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(5*USDX_CF))),
|
||||
expectedAccountBalance: sdk.NewCoins(),
|
||||
expectedModAccountBalance: sdk.NewCoins(),
|
||||
},
|
||||
errArgs{
|
||||
expectPass: false,
|
||||
contains: "below the minimum borrow limit",
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid: borrowing multiple coins results in a borrow that's under the minimum USD 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.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(50*KAVA_CF))),
|
||||
previousBorrowCoins: sdk.NewCoins(),
|
||||
borrowCoins: sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(5*USDX_CF)), sdk.NewCoin("ukava", sdk.NewInt(2*USDX_CF))),
|
||||
expectedAccountBalance: sdk.NewCoins(),
|
||||
expectedModAccountBalance: sdk.NewCoins(),
|
||||
},
|
||||
errArgs{
|
||||
expectPass: false,
|
||||
contains: "below the minimum borrow limit",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
@ -269,6 +313,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
|
||||
types.NewMoneyMarket("bnb", types.NewBorrowLimit(false, sdk.NewDec(100000000*BNB_CF), tc.args.loanToValueBNB), "bnb:usd", sdk.NewInt(BNB_CF), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
|
||||
types.NewMoneyMarket("xyz", types.NewBorrowLimit(false, sdk.NewDec(1), tc.args.loanToValueBNB), "xyz:usd", sdk.NewInt(1), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
|
||||
},
|
||||
sdk.NewDec(10),
|
||||
), types.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows,
|
||||
types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves,
|
||||
)
|
||||
|
@ -111,6 +111,7 @@ func (suite *KeeperTestSuite) TestDeposit() {
|
||||
types.NewMoneyMarket("bnb", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "bnb:usd", sdk.NewInt(1000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
|
||||
types.NewMoneyMarket("btcb", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "btcb:usd", sdk.NewInt(1000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
|
||||
},
|
||||
sdk.NewDec(10),
|
||||
), types.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows,
|
||||
types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves,
|
||||
)
|
||||
|
@ -796,6 +796,7 @@ func (suite *KeeperTestSuite) TestBorrowInterest() {
|
||||
tc.args.reserveFactor, // Reserve Factor
|
||||
sdk.ZeroDec()), // Keeper Reward Percentage
|
||||
},
|
||||
sdk.NewDec(10),
|
||||
), types.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows,
|
||||
types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves,
|
||||
)
|
||||
@ -1209,6 +1210,7 @@ func (suite *KeeperTestSuite) TestSupplyInterest() {
|
||||
tc.args.reserveFactor, // Reserve Factor
|
||||
sdk.ZeroDec()), // Keeper Reward Percentage
|
||||
},
|
||||
sdk.NewDec(10),
|
||||
), types.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows,
|
||||
types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves,
|
||||
)
|
||||
|
@ -511,6 +511,7 @@ func (suite *KeeperTestSuite) TestKeeperLiquidation() {
|
||||
reserveFactor, // Reserve Factor
|
||||
tc.args.keeperRewardPercent), // Keeper Reward Percent
|
||||
},
|
||||
sdk.NewDec(10),
|
||||
), types.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows,
|
||||
types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves,
|
||||
)
|
||||
|
@ -28,3 +28,9 @@ func (k Keeper) GetMoneyMarketParam(ctx sdk.Context, denom string) (types.MoneyM
|
||||
}
|
||||
return types.MoneyMarket{}, false
|
||||
}
|
||||
|
||||
// GetMinimumBorrowUSDValue returns the minimum borrow USD value
|
||||
func (k Keeper) GetMinimumBorrowUSDValue(ctx sdk.Context) sdk.Dec {
|
||||
params := k.GetParams(ctx)
|
||||
return params.MinimumBorrowUSDValue
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ func (k Keeper) Repay(ctx sdk.Context, sender, owner sdk.AccAddress, coins sdk.C
|
||||
k.SyncBorrowInterest(ctx, owner)
|
||||
|
||||
// Validate that sender holds coins for repayment
|
||||
err := k.ValidateRepay(ctx, sender, coins)
|
||||
err := k.ValidateRepay(ctx, sender, owner, coins)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -78,14 +78,80 @@ func (k Keeper) Repay(ctx sdk.Context, sender, owner sdk.AccAddress, coins sdk.C
|
||||
}
|
||||
|
||||
// ValidateRepay validates a requested loan repay
|
||||
func (k Keeper) ValidateRepay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) error {
|
||||
func (k Keeper) ValidateRepay(ctx sdk.Context, sender, owner sdk.AccAddress, coins sdk.Coins) error {
|
||||
moneyMarketCache := map[string]types.MoneyMarket{}
|
||||
assetPriceCache := map[string]sdk.Dec{}
|
||||
|
||||
// Get the total USD value of user's existing borrows
|
||||
existingBorrowUSDValue := sdk.ZeroDec()
|
||||
existingBorrow, found := k.GetBorrow(ctx, owner)
|
||||
if found {
|
||||
for _, borrowedCoin := range existingBorrow.Amount {
|
||||
moneyMarket, ok := moneyMarketCache[borrowedCoin.Denom]
|
||||
if !ok { // Fetch money market and store in local cache
|
||||
newMoneyMarket, found := k.GetMoneyMarketParam(ctx, borrowedCoin.Denom)
|
||||
if !found {
|
||||
return sdkerrors.Wrapf(types.ErrMarketNotFound, "no market found for denom %s", borrowedCoin.Denom)
|
||||
}
|
||||
moneyMarketCache[borrowedCoin.Denom] = newMoneyMarket
|
||||
moneyMarket = newMoneyMarket
|
||||
}
|
||||
|
||||
assetPrice, ok := assetPriceCache[borrowedCoin.Denom]
|
||||
if !ok { // Fetch current asset price and store in local cache
|
||||
assetPriceInfo, err := k.pricefeedKeeper.GetCurrentPrice(ctx, moneyMarket.SpotMarketID)
|
||||
if err != nil {
|
||||
return sdkerrors.Wrapf(types.ErrPriceNotFound, "no price found for market %s", moneyMarket.SpotMarketID)
|
||||
}
|
||||
assetPriceCache[borrowedCoin.Denom] = assetPriceInfo.Price
|
||||
assetPrice = assetPriceInfo.Price
|
||||
}
|
||||
|
||||
// Calculate this borrow coin's USD value and add it to the total previous borrowed USD value
|
||||
coinUSDValue := sdk.NewDecFromInt(borrowedCoin.Amount).Quo(sdk.NewDecFromInt(moneyMarket.ConversionFactor)).Mul(assetPrice)
|
||||
existingBorrowUSDValue = existingBorrowUSDValue.Add(coinUSDValue)
|
||||
}
|
||||
}
|
||||
|
||||
senderAcc := k.accountKeeper.GetAccount(ctx, sender)
|
||||
senderCoins := senderAcc.SpendableCoins(ctx.BlockTime())
|
||||
|
||||
for _, coin := range coins {
|
||||
if senderCoins.AmountOf(coin.Denom).LT(coin.Amount) {
|
||||
return sdkerrors.Wrapf(types.ErrInsufficientBalanceForRepay, "account can only repay up to %s%s", senderCoins.AmountOf(coin.Denom), coin.Denom)
|
||||
repayTotalUSDValue := sdk.ZeroDec()
|
||||
for _, repayCoin := range coins {
|
||||
// Check that sender holds enough tokens to make the proposed payment
|
||||
if senderCoins.AmountOf(repayCoin.Denom).LT(repayCoin.Amount) {
|
||||
return sdkerrors.Wrapf(types.ErrInsufficientBalanceForRepay, "account can only repay up to %s%s", senderCoins.AmountOf(repayCoin.Denom), repayCoin.Denom)
|
||||
}
|
||||
|
||||
moneyMarket, ok := moneyMarketCache[repayCoin.Denom]
|
||||
if !ok { // Fetch money market and store in local cache
|
||||
newMoneyMarket, found := k.GetMoneyMarketParam(ctx, repayCoin.Denom)
|
||||
if !found {
|
||||
return sdkerrors.Wrapf(types.ErrMarketNotFound, "no market found for denom %s", repayCoin.Denom)
|
||||
}
|
||||
moneyMarketCache[repayCoin.Denom] = newMoneyMarket
|
||||
moneyMarket = newMoneyMarket
|
||||
}
|
||||
|
||||
// Calculate this coin's USD value and add it to the repay's total USD value
|
||||
assetPrice, ok := assetPriceCache[repayCoin.Denom]
|
||||
if !ok { // Fetch current asset price and store in local cache
|
||||
assetPriceInfo, err := k.pricefeedKeeper.GetCurrentPrice(ctx, moneyMarket.SpotMarketID)
|
||||
if err != nil {
|
||||
return sdkerrors.Wrapf(types.ErrPriceNotFound, "no price found for market %s", moneyMarket.SpotMarketID)
|
||||
}
|
||||
assetPriceCache[repayCoin.Denom] = assetPriceInfo.Price
|
||||
assetPrice = assetPriceInfo.Price
|
||||
}
|
||||
coinUSDValue := sdk.NewDecFromInt(repayCoin.Amount).Quo(sdk.NewDecFromInt(moneyMarket.ConversionFactor)).Mul(assetPrice)
|
||||
repayTotalUSDValue = repayTotalUSDValue.Add(coinUSDValue)
|
||||
}
|
||||
|
||||
// If the proposed repayment would results in a borrowed USD value below the minimum borrow USD value, reject it.
|
||||
// User can overpay their loan to close it out, but underpaying by such a margin that the USD value is in an
|
||||
// invalid range is not allowed
|
||||
proposedBorrowNewUSDValue := existingBorrowUSDValue.Sub(repayTotalUSDValue)
|
||||
if proposedBorrowNewUSDValue.IsPositive() && proposedBorrowNewUSDValue.LT(k.GetMinimumBorrowUSDValue(ctx)) {
|
||||
return sdkerrors.Wrapf(types.ErrBelowMinimumBorrowValue, "the proposed borrow's USD value $%s is below the minimum borrow limit $%s", proposedBorrowNewUSDValue, k.GetMinimumBorrowUSDValue(ctx))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -122,6 +122,38 @@ func (suite *KeeperTestSuite) TestRepay() {
|
||||
contains: "account can only repay up to 50000000ukava",
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid: repaying a single coin type results in borrow position below the minimum USD value",
|
||||
args{
|
||||
borrower: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
|
||||
initialBorrowerCoins: sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(100*USDX_CF))),
|
||||
initialModuleCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000*KAVA_CF)), sdk.NewCoin("usdx", sdk.NewInt(1000*USDX_CF))),
|
||||
depositCoins: []sdk.Coin{sdk.NewCoin("usdx", sdk.NewInt(100*USDX_CF))},
|
||||
borrowCoins: sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(50*USDX_CF))),
|
||||
repayCoins: sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(45*USDX_CF))),
|
||||
},
|
||||
errArgs{
|
||||
expectPass: false,
|
||||
expectDelete: false,
|
||||
contains: "proposed borrow's USD value $5.000000000000000000 is below the minimum borrow limit",
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid: repaying multiple coin types results in borrow position below the minimum USD value",
|
||||
args{
|
||||
borrower: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
|
||||
initialBorrowerCoins: sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(100*USDX_CF))),
|
||||
initialModuleCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000*KAVA_CF)), sdk.NewCoin("usdx", sdk.NewInt(1000*USDX_CF))),
|
||||
depositCoins: []sdk.Coin{sdk.NewCoin("usdx", sdk.NewInt(100*USDX_CF))},
|
||||
borrowCoins: sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(50*USDX_CF)), sdk.NewCoin("ukava", sdk.NewInt(10*KAVA_CF))), // (50*$1)+(10*$2) = $70
|
||||
repayCoins: sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(45*USDX_CF)), sdk.NewCoin("ukava", sdk.NewInt(8*KAVA_CF))), // (45*$1)+(8*2) = $61
|
||||
},
|
||||
errArgs{
|
||||
expectPass: false,
|
||||
expectDelete: false,
|
||||
contains: "proposed borrow's USD value $9.000000000000000000 is below the minimum borrow limit",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -153,6 +185,7 @@ func (suite *KeeperTestSuite) TestRepay() {
|
||||
sdk.MustNewDecFromStr("0.05"), // Reserve Factor
|
||||
sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
|
||||
},
|
||||
sdk.NewDec(10),
|
||||
), types.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows,
|
||||
types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves,
|
||||
)
|
||||
|
@ -285,6 +285,7 @@ func (suite *KeeperTestSuite) TestSendTimeLockedCoinsToAccount() {
|
||||
types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
|
||||
types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
|
||||
},
|
||||
sdk.NewDec(10),
|
||||
), types.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows,
|
||||
types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves,
|
||||
)
|
||||
|
@ -129,6 +129,7 @@ func (suite *KeeperTestSuite) TestWithdraw() {
|
||||
types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
|
||||
types.NewMoneyMarket("bnb", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "bnb:usd", sdk.NewInt(100000000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
|
||||
},
|
||||
sdk.NewDec(10),
|
||||
), types.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows,
|
||||
types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves,
|
||||
)
|
||||
@ -212,6 +213,7 @@ func (suite *KeeperTestSuite) TestLtvWithdraw() {
|
||||
initialBorrowerCoins sdk.Coins
|
||||
depositCoins sdk.Coins
|
||||
borrowCoins sdk.Coins
|
||||
repayCoins sdk.Coins
|
||||
futureTime int64
|
||||
}
|
||||
|
||||
@ -239,8 +241,9 @@ func (suite *KeeperTestSuite) TestLtvWithdraw() {
|
||||
borrower: borrower,
|
||||
initialModuleCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
|
||||
initialBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF)), sdk.NewCoin("usdx", sdk.NewInt(100*KAVA_CF))),
|
||||
depositCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(10*KAVA_CF))), // 10 * 2 = $20
|
||||
borrowCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(8*KAVA_CF))), // 8 * 2 = $16
|
||||
depositCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))), // 100 * 2 = $200
|
||||
borrowCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(80*KAVA_CF))), // 80 * 2 = $160
|
||||
repayCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(60*KAVA_CF))), // 60 * 2 = $120
|
||||
futureTime: oneMonthInSeconds,
|
||||
},
|
||||
errArgs{
|
||||
@ -280,6 +283,7 @@ func (suite *KeeperTestSuite) TestLtvWithdraw() {
|
||||
reserveFactor, // Reserve Factor
|
||||
sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
|
||||
},
|
||||
sdk.NewDec(10),
|
||||
), types.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows,
|
||||
types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves,
|
||||
)
|
||||
@ -353,8 +357,8 @@ func (suite *KeeperTestSuite) TestLtvWithdraw() {
|
||||
suite.Require().Error(err)
|
||||
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||
|
||||
// Repay the initial principal
|
||||
err = suite.keeper.Repay(suite.ctx, tc.args.borrower, tc.args.borrower, tc.args.borrowCoins)
|
||||
// Repay the initial principal. Over pay the position so the borrow is closed.
|
||||
err = suite.keeper.Repay(suite.ctx, tc.args.borrower, tc.args.borrower, tc.args.repayCoins)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Attempted withdraw of all deposited coins fails as user hasn't repaid interest debt
|
||||
@ -362,8 +366,8 @@ func (suite *KeeperTestSuite) TestLtvWithdraw() {
|
||||
suite.Require().Error(err)
|
||||
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||
|
||||
// Withdrawing half the coins should succeed
|
||||
withdrawCoins := sdk.NewCoins(sdk.NewCoin("ukava", tc.args.depositCoins[0].Amount.Quo(sdk.NewInt(2))))
|
||||
// Withdrawing 10% of the coins should succeed
|
||||
withdrawCoins := sdk.NewCoins(sdk.NewCoin("ukava", tc.args.depositCoins[0].Amount.Quo(sdk.NewInt(10))))
|
||||
err = suite.keeper.Withdraw(suite.ctx, tc.args.borrower, withdrawCoins)
|
||||
suite.Require().NoError(err)
|
||||
})
|
||||
|
@ -63,4 +63,6 @@ var (
|
||||
ErrInvalidRepaymentDenom = sdkerrors.Register(ModuleName, 28, "no coins of this type borrowed")
|
||||
// ErrInvalidIndexFactorDenom error for when index factor denom cannot be found
|
||||
ErrInvalidIndexFactorDenom = sdkerrors.Register(ModuleName, 29, "no index factor found for denom")
|
||||
// ErrBelowMinimumBorrowValue error for when a proposed borrow position is less than the minimum USD value
|
||||
ErrBelowMinimumBorrowValue = sdkerrors.Register(ModuleName, 30, "invalid proposed borrow value")
|
||||
)
|
||||
|
@ -61,6 +61,7 @@ func (suite *GenesisTestSuite) TestGenesisValidation() {
|
||||
types.MoneyMarkets{
|
||||
types.NewMoneyMarket("usdx", types.NewBorrowLimit(true, sdk.MustNewDecFromStr("100000000000"), sdk.MustNewDecFromStr("1")), "usdx:usd", sdk.NewInt(USDX_CF), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
|
||||
},
|
||||
sdk.MustNewDecFromStr("10"),
|
||||
),
|
||||
gats: types.GenesisAccumulationTimes{
|
||||
types.NewGenesisAccumulationTime("usdx", time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), sdk.OneDec(), sdk.OneDec()),
|
||||
|
@ -12,7 +12,9 @@ import (
|
||||
// Parameter keys and default values
|
||||
var (
|
||||
KeyMoneyMarkets = []byte("MoneyMarkets")
|
||||
KeyMinimumBorrowUSDValue = []byte("MinimumBorrowUSDValue")
|
||||
DefaultMoneyMarkets = MoneyMarkets{}
|
||||
DefaultMinimumBorrowUSDValue = sdk.NewDec(10) // $10 USD minimum borrow value
|
||||
GovDenom = cdptypes.DefaultGovDenom
|
||||
DefaultAccumulationTimes = GenesisAccumulationTimes{}
|
||||
DefaultTotalSupplied = sdk.Coins{}
|
||||
@ -25,6 +27,7 @@ var (
|
||||
// Params governance parameters for hard module
|
||||
type Params struct {
|
||||
MoneyMarkets MoneyMarkets `json:"money_markets" yaml:"money_markets"`
|
||||
MinimumBorrowUSDValue sdk.Dec `json:"minimum_borrow_usd_value" yaml:"minimum_borrow_usd_value"`
|
||||
}
|
||||
|
||||
// BorrowLimit enforces restrictions on a money market
|
||||
@ -220,22 +223,24 @@ func (irm InterestRateModel) Equal(irmCompareTo InterestRateModel) bool {
|
||||
type InterestRateModels []InterestRateModel
|
||||
|
||||
// NewParams returns a new params object
|
||||
func NewParams(moneyMarkets MoneyMarkets) Params {
|
||||
func NewParams(moneyMarkets MoneyMarkets, minimumBorrowUSDValue sdk.Dec) Params {
|
||||
return Params{
|
||||
MoneyMarkets: moneyMarkets,
|
||||
MinimumBorrowUSDValue: minimumBorrowUSDValue,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultParams returns default params for hard module
|
||||
func DefaultParams() Params {
|
||||
return NewParams(DefaultMoneyMarkets)
|
||||
return NewParams(DefaultMoneyMarkets, DefaultMinimumBorrowUSDValue)
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (p Params) String() string {
|
||||
return fmt.Sprintf(`Params:
|
||||
Money Markets %v`,
|
||||
p.MoneyMarkets)
|
||||
Minimum Borrow USD Value: %v
|
||||
Money Markets: %v`,
|
||||
p.MinimumBorrowUSDValue, p.MoneyMarkets)
|
||||
}
|
||||
|
||||
// ParamKeyTable Key declaration for parameters
|
||||
@ -247,14 +252,32 @@ func ParamKeyTable() params.KeyTable {
|
||||
func (p *Params) ParamSetPairs() params.ParamSetPairs {
|
||||
return params.ParamSetPairs{
|
||||
params.NewParamSetPair(KeyMoneyMarkets, &p.MoneyMarkets, validateMoneyMarketParams),
|
||||
params.NewParamSetPair(KeyMinimumBorrowUSDValue, &p.MinimumBorrowUSDValue, validateMinimumBorrowUSDValue),
|
||||
}
|
||||
}
|
||||
|
||||
// Validate checks that the parameters have valid values.
|
||||
func (p Params) Validate() error {
|
||||
if err := validateMinimumBorrowUSDValue(p.MinimumBorrowUSDValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return validateMoneyMarketParams(p.MoneyMarkets)
|
||||
}
|
||||
|
||||
func validateMinimumBorrowUSDValue(i interface{}) error {
|
||||
minBorrowVal, ok := i.(sdk.Dec)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid parameter type: %T", i)
|
||||
}
|
||||
|
||||
if minBorrowVal.IsNegative() {
|
||||
return fmt.Errorf("Minimum borrow USD value cannot be negative")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateMoneyMarketParams(i interface{}) error {
|
||||
mm, ok := i.(MoneyMarkets)
|
||||
if !ok {
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/kava-labs/kava/x/hard/types"
|
||||
@ -15,6 +16,7 @@ type ParamTestSuite struct {
|
||||
|
||||
func (suite *ParamTestSuite) TestParamValidation() {
|
||||
type args struct {
|
||||
minBorrowVal sdk.Dec
|
||||
mms types.MoneyMarkets
|
||||
}
|
||||
testCases := []struct {
|
||||
@ -26,6 +28,7 @@ func (suite *ParamTestSuite) TestParamValidation() {
|
||||
{
|
||||
name: "default",
|
||||
args: args{
|
||||
minBorrowVal: types.DefaultMinimumBorrowUSDValue,
|
||||
mms: types.DefaultMoneyMarkets,
|
||||
},
|
||||
expectPass: true,
|
||||
@ -34,7 +37,7 @@ func (suite *ParamTestSuite) TestParamValidation() {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
params := types.NewParams(tc.args.mms)
|
||||
params := types.NewParams(tc.args.mms, tc.args.minBorrowVal)
|
||||
err := params.Validate()
|
||||
if tc.expectPass {
|
||||
suite.NoError(err)
|
||||
|
@ -159,6 +159,7 @@ func NewHardGenStateMulti() app.GenesisState {
|
||||
hard.NewMoneyMarket("btcb", hard.NewBorrowLimit(false, borrowLimit, loanToValue), "btc:usd", sdk.NewInt(1000000), hard.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
|
||||
hard.NewMoneyMarket("xrp", hard.NewBorrowLimit(false, borrowLimit, loanToValue), "xrp:usd", sdk.NewInt(1000000), hard.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
|
||||
},
|
||||
sdk.NewDec(10),
|
||||
), hard.DefaultAccumulationTimes, hard.DefaultDeposits, hard.DefaultBorrows,
|
||||
hard.DefaultTotalSupplied, hard.DefaultTotalBorrowed, hard.DefaultTotalReserves,
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user