Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adjust interest rate algorithm and associated token param rules #2388

Merged
merged 6 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading