From 647a430c1446964972de2498547074ad145b0a73 Mon Sep 17 00:00:00 2001 From: toteki <63419657+toteki@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:13:35 -0700 Subject: [PATCH 1/4] adjust interest rate algorithm and associated token param rules --- x/leverage/keeper/interest.go | 18 +++++++++++------ x/leverage/keeper/interest_test.go | 13 ++++++++++--- x/leverage/keeper/msg_server_test.go | 6 ++++-- x/leverage/types/token.go | 29 ++++++++++++++++++++++++---- 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/x/leverage/keeper/interest.go b/x/leverage/keeper/interest.go index 8a339367ec..2e9b19a478 100644 --- a/x/leverage/keeper/interest.go +++ b/x/leverage/keeper/interest.go @@ -17,21 +17,27 @@ func (k Keeper) DeriveBorrowAPY(ctx sdk.Context, denom string) sdk.Dec { if err != nil { return sdk.ZeroDec() } - if token.Blacklist { // Regardless of params, AccrueAllInterest skips blacklisted denoms return sdk.ZeroDec() } + // Derive current supply utilization, which will always be between 0.0 and 1.0 utilization := k.SupplyUtilization(ctx, denom) + // Tokens which have reached or exceeded their max supply utilization always use max borrow APY + if utilization.GTE(token.MaxSupplyUtilization) { + return token.MaxBorrowRate + } + + // Tokens which are past kink value but have not reached max supply utilization interpolate between the two if utilization.GTE(token.KinkUtilization) { return Interpolate( - utilization, // x - token.KinkUtilization, // x1 - token.KinkBorrowRate, // y1 - sdk.OneDec(), // x2 - token.MaxBorrowRate, // y2 + utilization, // x + token.KinkUtilization, // x1 + token.KinkBorrowRate, // y1 + token.MaxSupplyUtilization, // x2 + token.MaxBorrowRate, // y2 ) } diff --git a/x/leverage/keeper/interest_test.go b/x/leverage/keeper/interest_test.go index e02fedcda0..6b825177cf 100644 --- a/x/leverage/keeper/interest_test.go +++ b/x/leverage/keeper/interest_test.go @@ -69,13 +69,20 @@ func (s *IntegrationTestSuite) TestDynamicInterest() { rate = app.LeverageKeeper.DeriveBorrowAPY(ctx, appparams.BondDenom) require.Equal(sdk.MustNewDecFromStr("0.22"), rate) - // user borrows 100 more umee (ignores collateral), utilization 900/1000 - s.forceBorrow(addr, coin.New(appparams.BondDenom, 100_000000)) + // user borrows 50 more umee (ignores collateral), utilization 850/1000 + s.forceBorrow(addr, coin.New(appparams.BondDenom, 50_000000)) - // Between kink interest and max (90% utilization) + // Between kink and max interest rate (85% utilization) rate = app.LeverageKeeper.DeriveBorrowAPY(ctx, appparams.BondDenom) require.Equal(sdk.MustNewDecFromStr("0.87"), rate) + // user borrows 50 more umee (ignores collateral), utilization 900/1000 = max supply utilization + s.forceBorrow(addr, coin.New(appparams.BondDenom, 50_000000)) + + // Max interest rate (90% utilization) + rate = app.LeverageKeeper.DeriveBorrowAPY(ctx, appparams.BondDenom) + require.Equal(sdk.MustNewDecFromStr("1.52"), rate) + // user borrows 100 more umee (ignores collateral), utilization 1000/1000 s.forceBorrow(addr, coin.New(appparams.BondDenom, 100_000000)) diff --git a/x/leverage/keeper/msg_server_test.go b/x/leverage/keeper/msg_server_test.go index ee874ab73e..75b87821b9 100644 --- a/x/leverage/keeper/msg_server_test.go +++ b/x/leverage/keeper/msg_server_test.go @@ -1976,9 +1976,10 @@ func (s *IntegrationTestSuite) TestMsgMaxBorrow_DecreasingMaxSupplyUtilization() app, ctx, srv, require := s.app, s.ctx, s.msgSrvr, s.Require() // overriding UMEE token settings, changing MinCollateralLiquidity to 0.2 - // and MaxSupplyUtilization to 0.7 + // and MaxSupplyUtilization to 0.7 (also adjusting kink for token validity) umeeToken := newToken(umeeDenom, "UMEE", 6) umeeToken.MinCollateralLiquidity = sdk.MustNewDecFromStr("0.2") + umeeToken.KinkUtilization = sdk.MustNewDecFromStr("0.6") umeeToken.MaxSupplyUtilization = sdk.MustNewDecFromStr("0.7") require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, umeeToken)) @@ -2022,9 +2023,10 @@ func (s *IntegrationTestSuite) TestMsgMaxBorrow_ZeroAvailableBasedOnMaxSupplyUti app, ctx, srv, require := s.app, s.ctx, s.msgSrvr, s.Require() // overriding UMEE token settings, changing MinCollateralLiquidity to 0.2 - // and MaxSupplyUtilization to 0.5 + // and MaxSupplyUtilization to 0.5 (also adjusting kink for token validity) umeeToken := newToken(umeeDenom, "UMEE", 6) umeeToken.MinCollateralLiquidity = sdk.MustNewDecFromStr("0.2") + umeeToken.KinkUtilization = sdk.MustNewDecFromStr("0.45") umeeToken.MaxSupplyUtilization = sdk.MustNewDecFromStr("0.5") require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, umeeToken)) diff --git a/x/leverage/types/token.go b/x/leverage/types/token.go index 3959efddd2..07620dd800 100644 --- a/x/leverage/types/token.go +++ b/x/leverage/types/token.go @@ -53,12 +53,33 @@ func (t Token) Validate() error { ) } - // Kink utilization rate ranges between 0 and 1, exclusive. This prevents - // multiple interest rates being defined at exactly 0% or 100% utilization - // e.g. kink at 0%, 2% base borrow rate, 4% borrow rate at kink. - if !t.KinkUtilization.IsPositive() || t.KinkUtilization.GTE(one) { + // Kink utilization rate ranges between 0 and 1, inclusive. + if t.KinkUtilization.IsNegative() || t.KinkUtilization.GT(one) { return fmt.Errorf("invalid kink utilization rate: %s", t.KinkUtilization) } + // The following rules ensure the utilization:APY graph is continuous + if t.KinkUtilization.GT(t.MaxSupplyUtilization) { + return fmt.Errorf("kink utilization (%s) cannot be greater than than max supply utilization (%s)", + t.KinkUtilization, t.MaxSupplyUtilization) + } + if t.KinkUtilization.Equal(t.MaxSupplyUtilization) && !t.MaxBorrowRate.Equal(t.KinkBorrowRate) { + return fmt.Errorf( + "since kink utilization equals max supply utilization, kink borrow rate must equal max borrow rate (%s)", + t.MaxBorrowRate, + ) + } + if t.KinkUtilization.IsZero() && !t.KinkBorrowRate.Equal(t.BaseBorrowRate) { + return fmt.Errorf( + "since kink utilization equals zero, kink borrow rate must equal base borrow rate (%s)", + t.BaseBorrowRate, + ) + } + if t.MaxSupplyUtilization.IsZero() && !t.MaxBorrowRate.Equal(t.BaseBorrowRate) { + return fmt.Errorf( + "since max supply utilization equals zero, max borrow rate must equal base borrow rate (%s)", + t.BaseBorrowRate, + ) + } // interest rates are non-negative; they do not need to have a maximum value if t.BaseBorrowRate.IsNegative() { From 09a5c2de778a1e8df24cc0a80f502b4d4f755bce Mon Sep 17 00:00:00 2001 From: toteki <63419657+toteki@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:22:14 -0700 Subject: [PATCH 2/4] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c169a7d36..56faaa77f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## Unreleased +### Improvements + +- [2388](https://github.com/umee-network/umee/pull/2388) Adjust interest rate algorithm and associated token parameter validation rules. + ## v6.3.0 - 2024-01-03 ### Improvements From c20a602c1754224a7ca3a486783296a3018b6394 Mon Sep 17 00:00:00 2001 From: toteki <63419657+toteki@users.noreply.github.com> Date: Thu, 11 Jan 2024 20:48:54 -0700 Subject: [PATCH 3/4] add migration for tokens --- app/upgrades.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/app/upgrades.go b/app/upgrades.go index b7e13e079f..92f4fe034d 100644 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -47,6 +47,29 @@ func (app UmeeApp) RegisterUpgradeHandlers() { app.registerOutdatedPlaceholderUpgrade("v6.1") app.registerOutdatedPlaceholderUpgrade("v6.2") app.registerUpgrade("v6.3", upgradeInfo) + app.registerUpgrade6_4(upgradeInfo) +} + +func (app *UmeeApp) registerUpgrade6_4(upgradeInfo upgradetypes.Plan) { + planName := "v6.4" + + app.UpgradeKeeper.SetUpgradeHandler(planName, + func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + printPlanName(planName, ctx.Logger()) + tokens := app.LeverageKeeper.GetAllRegisteredTokens(ctx) + for _, token := range tokens { + // this will allow existing interest rate curves to pass new Token validation + if token.KinkUtilization.GTE(token.MaxSupplyUtilization) { + token.KinkUtilization = token.MaxSupplyUtilization + token.KinkBorrowRate = token.MaxBorrowRate + if err := app.LeverageKeeper.SetTokenSettings(ctx, token); err != nil { + return fromVM, err + } + } + } + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }, + ) } func (app *UmeeApp) registerUpgrade6_0(upgradeInfo upgradetypes.Plan) { From 96da52814567dd0d690b162c340b79f3d2b49407 Mon Sep 17 00:00:00 2001 From: toteki <63419657+toteki@users.noreply.github.com> Date: Thu, 11 Jan 2024 20:51:58 -0700 Subject: [PATCH 4/4] lint --- app/upgrades.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/upgrades.go b/app/upgrades.go index 92f4fe034d..d5a057679c 100644 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -50,7 +50,7 @@ func (app UmeeApp) RegisterUpgradeHandlers() { app.registerUpgrade6_4(upgradeInfo) } -func (app *UmeeApp) registerUpgrade6_4(upgradeInfo upgradetypes.Plan) { +func (app *UmeeApp) registerUpgrade6_4(_ upgradetypes.Plan) { planName := "v6.4" app.UpgradeKeeper.SetUpgradeHandler(planName,