-
Notifications
You must be signed in to change notification settings - Fork 2
Fix kink curve utilization accounting #252
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
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| import Test | ||
| import "FlowToken" | ||
| import "FlowALPv0" | ||
| import "FlowALPModels" | ||
| import "FlowALPInterestRates" | ||
| import "test_helpers.cdc" | ||
|
|
||
| // This file guards against the historical KinkCurve utilization bug and keeps | ||
| // the original semantic mismatch documented in one place. | ||
| // | ||
| // Historical bug: | ||
| // - The old KinkCurve formula computed utilization as: | ||
| // debitBalance / (creditBalance + debitBalance) | ||
| // - That only works if `creditBalance` means REMAINING AVAILABLE LIQUIDITY. | ||
| // - But FlowALP's live accounting uses `totalCreditBalance` to mean total | ||
| // creditor claims, i.e. total supplied. | ||
| // | ||
| // Example of the mismatch: | ||
| // - 100 supplied | ||
| // - 90 borrowed | ||
| // - 10 idle liquidity left in the pool | ||
| // | ||
| // If `creditBalance` means remaining liquidity: | ||
| // - utilization = 90 / (10 + 90) = 90% | ||
| // | ||
| // If `creditBalance` means total supplied: | ||
| // - utilization = 90 / (100 + 90) = 47.4% | ||
| // | ||
| // The bug survived because direct curve tests were easy to write using | ||
| // hand-picked "remaining liquidity" inputs, while the live pool always passed | ||
| // total supplied, i.e. creditor claims, into the same parameter. | ||
| // | ||
| // After the fix, both direct KinkCurve calls and TokenState accounting must use | ||
| // the same meaning: | ||
| // creditBalance = total creditor claims, i.e. total supplied | ||
| // | ||
| // These tests ensure a pool with 100 supplied and 90 borrowed is priced as 90% | ||
| // utilization in both paths. | ||
|
|
||
| access(all) | ||
| fun setup() { | ||
| deployContracts() | ||
| } | ||
|
|
||
| access(all) | ||
| fun test_regression_KinkCurve_direct_curve_uses_total_supplied_semantics() { | ||
| let curve = FlowALPInterestRates.KinkCurve( | ||
| optimalUtilization: 0.80, | ||
| baseRate: 0.01, | ||
| slope1: 0.04, | ||
| slope2: 0.60 | ||
| ) | ||
|
|
||
| // Direct curve calls must use total supplied semantics, matching the pool. | ||
| let rate = curve.interestRate(creditBalance: 100.0, debitBalance: 90.0) | ||
|
|
||
| Test.assertEqual(0.35 as UFix128, rate) | ||
| } | ||
|
|
||
| access(all) | ||
| fun test_regression_TokenState_90_borrow_of_100_supply_should_price_at_90_percent_utilization() { | ||
| let curve = FlowALPInterestRates.KinkCurve( | ||
| optimalUtilization: 0.80, | ||
| baseRate: 0.01, | ||
| slope1: 0.04, | ||
| slope2: 0.60 | ||
| ) | ||
|
|
||
| var tokenState = FlowALPModels.TokenStateImplv1( | ||
| tokenType: Type<@FlowToken.Vault>(), | ||
| interestCurve: curve, | ||
| depositRate: 1.0, | ||
| depositCapacityCap: 1_000.0 | ||
| ) | ||
|
|
||
| // Realistic pool state: 100 total supplied, 90 total borrowed. | ||
| // TokenState stores those values directly as total credit and total debt, | ||
| // so this path verifies the live accounting matches the direct curve path. | ||
| tokenState.increaseCreditBalance(by: 100.0) | ||
| tokenState.increaseDebitBalance(by: 90.0) | ||
|
|
||
| let actualYearlyRate = curve.interestRate( | ||
| creditBalance: tokenState.getTotalCreditBalance(), | ||
| debitBalance: tokenState.getTotalDebitBalance() | ||
| ) | ||
|
|
||
| // The live pool path should match the direct curve path above. If it does | ||
| // not, `creditBalance` semantics have drifted again. | ||
| Test.assert( | ||
| actualYearlyRate == 0.35, | ||
| message: | ||
| "Regression: 100 supplied / 90 borrowed should price at 90% utilization (0.35 APY), but current accounting passed creditBalance=\(tokenState.getTotalCreditBalance()) and debitBalance=\(tokenState.getTotalDebitBalance()), producing \(actualYearlyRate) instead" | ||
| ) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.