Skip to content

Commit

Permalink
feat: adjust interest rate algorithm and associated token param rules (
Browse files Browse the repository at this point in the history
…#2388)

* adjust interest rate algorithm and associated token param rules

* changelog

* add migration for tokens

* lint

---------

Co-authored-by: Robert Zaremba <[email protected]>
  • Loading branch information
toteki and robert-zaremba authored Jan 25, 2024
1 parent 1a3f2ef commit 4e72509
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 15 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions app/upgrades.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(_ 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) {
Expand Down
18 changes: 12 additions & 6 deletions x/leverage/keeper/interest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}

Expand Down
13 changes: 10 additions & 3 deletions x/leverage/keeper/interest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
6 changes: 4 additions & 2 deletions x/leverage/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down Expand Up @@ -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))

Expand Down
29 changes: 25 additions & 4 deletions x/leverage/types/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down

0 comments on commit 4e72509

Please sign in to comment.