Skip to content

Fix kink curve utilization accounting#252

Merged
liobrasil merged 3 commits intomainfrom
lionel/kink-curve-utilization-fix
Mar 10, 2026
Merged

Fix kink curve utilization accounting#252
liobrasil merged 3 commits intomainfrom
lionel/kink-curve-utilization-fix

Conversation

@liobrasil
Copy link
Contributor

Summary

  • remove the remaining FlowALPMath dependency from FlowALPInterestRates
  • fix KinkCurve utilization to use total supplied semantics instead of double-counting debt in the denominator
  • update direct KinkCurve tests and add a regression test covering the live TokenState path

Testing

  • flow test cadence/tests/interest_curve_test.cdc
  • flow test cadence/tests/kink_curve_utilization_regression_test.cdc
  • flow test cadence/tests/update_interest_rate_test.cdc
  • flow test cadence/tests/interest_accrual_integration_test.cdc

Credit

  • Credit to @UlyanaAndrukhiv for finding the utilization bug and surfacing the mismatch in the current KinkCurve behavior.

@liobrasil liobrasil requested a review from a team as a code owner March 9, 2026 23:04
Copy link
Contributor

@UlyanaAndrukhiv UlyanaAndrukhiv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me 👍 The only thing I wanted to mention is that in Aave formula, when the denominator is 0, utilization is defined as 0, whereas in our implementation it becomes 100%. Just want to pointing this out.
image

@UlyanaAndrukhiv UlyanaAndrukhiv requested a review from a team March 10, 2026 12:32
@liobrasil
Copy link
Contributor Author

Looks good to me 👍 The only thing I wanted to mention is that in Aave formula, when the denominator is 0, utilization is defined as 0, whereas in our implementation it becomes 100%. Just want to pointing this out. image

I think the key distinction is what L_t = 0 means in the Aave notation.

In Aave:

  • totalDebt = total borrowed amount outstanding
  • availableLiquidity = idle liquidity still in the reserve
  • L_t corresponds to availableLiquidity + totalDebt, i.e. total reserve liquidity

So if L_t = 0, then both terms must be 0:

  • availableLiquidity = 0
  • totalDebt = 0

That is the empty-reserve case: no supplied liquidity and no outstanding borrows.

That is different from the fully-utilized case, where:

  • availableLiquidity = 0
  • totalDebt > 0

In that case, L_t = totalDebt > 0, so utilization is 100%, not undefined.

This is also how the Aave implementation behaves: it only computes borrowUsageRatio when totalDebt != 0, so the empty-reserve case stays at 0 utilization:
https://github.com/aave/aave-v3-core/blob/master/contracts/protocol/pool/DefaultReserveInterestRateStrategy.sol#L169-L177

So our defensive branch is making the same distinction:

  • creditBalance = 0, debitBalance = 0 -> empty reserve -> base-rate / 0-utilization behavior
  • creditBalance = 0, debitBalance > 0 -> positive debt with no credit-side balance -> saturate to 100%, i.e. effectively full utilization

@UlyanaAndrukhiv UlyanaAndrukhiv self-requested a review March 10, 2026 14:13
Copy link
Contributor

@UlyanaAndrukhiv UlyanaAndrukhiv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @liobrasil, got it! I missed that case is already handled by the early if debitBalance == 0.0 { return baseRate } check 😅

@liobrasil liobrasil merged commit d3743ad into main Mar 10, 2026
1 check passed
@liobrasil liobrasil deleted the lionel/kink-curve-utilization-fix branch March 10, 2026 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants