Skip to content

moving bad debts to isolate bad debts#2799

Open
gsk967 wants to merge 6 commits intov6.7from
sai/isolate_bad_debts_2795
Open

moving bad debts to isolate bad debts#2799
gsk967 wants to merge 6 commits intov6.7from
sai/isolate_bad_debts_2795

Conversation

@gsk967
Copy link
Copy Markdown
Collaborator

@gsk967 gsk967 commented Apr 10, 2026

Description

closes: #2795
moving bad debts to isolate bad debts

  1. add tx cmd - repay-isolated-bad-debt
  2. add cli query cmd - isolated-bad-debts, last-interest-time , interest-scalars

Author Checklist

All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.

I have...

  • included the correct type prefix in the PR title
  • added ! to the type prefix if API or client breaking change
  • added appropriate labels to the PR
  • targeted the correct branch (see PR Targeting)
  • provided a link to the relevant issue or specification
  • added a changelog entry to CHANGELOG.md
  • included comments for documenting Go code
  • updated the relevant documentation or specification
  • reviewed "Files changed" and left comments if necessary
  • confirmed all CI checks have passed

Reviewers Checklist

All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.

I have...

  • confirmed the correct type prefix in the PR title
  • confirmed all author checklist items have been addressed
  • reviewed state machine logic
  • reviewed API design and naming
  • reviewed documentation is accurate
  • reviewed tests and test coverage
  • manually tested (if applicable)

@gsk967 gsk967 requested a review from a team as a code owner April 10, 2026 11:20
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 10, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d49439cf-cc00-40dc-bf88-798e768f0dd8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sai/isolate_bad_debts_2795

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment on lines +38 to +48
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
string interest_scalar = 4 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
string interest_accrued = 5 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I recommend adding comments to clarify these fields

For examples: to indicate if amout is before or after scalar is applied; if scalar stored here is the value of the scalar at the moment the debt was isolated or some other value; and if interest accrued is part of the amount field, separate from it, and before or after scalar.

Comment on lines +76 to +100
// GetBorrowerBorrowsWithoutInterest returns an sdk.Coins object containing all open borrows
// associated with an address.
func (k Keeper) GetBorrowerBorrowsWithoutInterest(
ctx sdk.Context, borrowerAddr sdk.AccAddress,
) []types.BorrowWithInterest {
prefix := types.KeyAdjustedBorrowNoDenom(borrowerAddr)
totalBorrowed := []types.BorrowWithInterest{}
iterator := func(key, val []byte) error {
borrowDenom := types.DenomFromKeyWithAddress(key, types.KeyPrefixAdjustedBorrow)
var adjustedAmount sdk.Dec
if err := adjustedAmount.Unmarshal(val); err != nil {
// improperly marshaled borrow amount should never happen
return err
}

interestScalar := k.getInterestScalar(ctx, borrowDenom)
// interestScalar always >=1
interestAccured := adjustedAmount.Mul(interestScalar.Sub(sdk.OneDec())).Ceil().TruncateInt()
b := types.NewBorrowWithInterest(borrowDenom, adjustedAmount.Ceil().TruncateInt(), interestScalar, interestAccured)
totalBorrowed = append(totalBorrowed, b)
return nil
}
util.Panic(k.iterate(ctx, prefix, iterator))
return totalBorrowed
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This function is where the danger is.

For context, InterestScalar for a given denom starts at 1.0 the first time that denom is registered with the leverage module, and then begins increasing as borrowers accrue interest. New borrows created after that do not start at InterestScalar=1.0 but at a higher value, even before they accrue interest.

Therefore, this function incorrectly separates principal and interest.

Example scenario:

On Feb 1, we register denom CoinA with the leverage module

  • At that moment, InterestScalar(CoinA) = 1.0

Unspecified borrowing and lending activity happens between Feb 1 and Jul 31

  • Due to borrow interest, InterestScalar(CoinA) = 1.2 now

On Aug 1, borrower Z borrows 180 CoinA

  • This is happening at a time when InterestScalar(CoinA) = 1.2
  • At this instant, the leverage module stores adjustedAmount=150 because 150*1.2 = 180 CoinA

Unspecified borrowing and lending activity happens between Aug 1 and Dec 31

  • Due to borrow interest, InterestScalar(CoinA) = 1.4 now
  • The leverage module still stores adjustedAmount=150 assuming borrower Z did nothing else
  • Therefore the current debt owed is 150*1.4 = 210 CoinA. Principal is 180 and interest is 30.

If we applied this GetBorrowerBorrowsWithoutInterest to borrower Z on Dec 31 it would incorrectly state that principal is 150 and interest is 60.

Copy link
Copy Markdown
Contributor

@toteki toteki left a comment

Choose a reason for hiding this comment

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

See the problem with interest scalar.

We would need to crawl the chain with an archive node to separate interest from principal accurately. A migration couldn't do that.

We should clarify that what we're storing here is a snapshot of the bad debts with the current interest scalar so it stops increasing for them. The real values of (principal,interest) will be (snapshotPrincipal+X,snapshotInterest-X) for some X >= 0 for each borrower.

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.

2 participants