-
Notifications
You must be signed in to change notification settings - Fork 11
fix: junior tranche absorbs insurance losses first via effective_junior_balance() #77
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -257,9 +257,30 @@ impl StakePool { | |||||||||||||||||||||||||||||||||||||||||||||||||||
| self.total_lp_supply.saturating_sub(self.junior_total_lp()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Derived: senior balance = total_pool_value - junior_balance. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Junior balance after absorbing insurance losses. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// When `total_flushed > total_returned` there is an outstanding loss. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Junior tranche absorbs that loss first (up to its full balance). | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// `distribute_loss` is the canonical implementation: junior absorbs first, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// senior only loses once junior is wiped out. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub fn effective_junior_balance(&self) -> u64 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let jb = self.junior_balance(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let loss = self.total_flushed.saturating_sub(self.total_returned); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if loss == 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return jb; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // senior_balance is the pool value not attributed to junior — used by | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // distribute_loss to cap the allocation of loss to the junior tranche. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let pv = self.total_pool_value().unwrap_or(jb); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let sb = pv.saturating_sub(jb); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let (junior_loss, _) = crate::math::distribute_loss(jb, sb, loss); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| jb.saturating_sub(junior_loss) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+266
to
+277
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fail closed when Line 274 falls back with Proposed fix- pub fn effective_junior_balance(&self) -> u64 {
+ pub fn effective_junior_balance(&self) -> Option<u64> {
let jb = self.junior_balance();
let loss = self.total_flushed.saturating_sub(self.total_returned);
if loss == 0 {
- return jb;
+ return Some(jb);
}
@@
- let pv = self.total_pool_value().unwrap_or(jb);
+ let pv = self.total_pool_value()?;
let sb = pv.saturating_sub(jb);
let (junior_loss, _) = crate::math::distribute_loss(jb, sb, loss);
- jb.saturating_sub(junior_loss)
+ Some(jb.saturating_sub(junior_loss))
}- pub fn senior_balance(&self) -> Option<u64> {
- let pv = self.total_pool_value()?;
- Some(pv.saturating_sub(self.effective_junior_balance()))
- }
+ pub fn senior_balance(&self) -> Option<u64> {
+ let pv = self.total_pool_value()?;
+ let ejb = self.effective_junior_balance()?;
+ Some(pv.saturating_sub(ejb))
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Derived: senior balance = total_pool_value - effective_junior_balance. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub fn senior_balance(&self) -> Option<u64> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.total_pool_value()?.checked_sub(self.junior_balance()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let pv = self.total_pool_value()?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Some(pv.saturating_sub(self.effective_junior_balance())) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Current struct version. Increment when layout changes. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Junior tranche valuation is now asymmetric across deposit vs withdraw.
Line 671 prices junior withdrawals with
effective_junior_balance(), but junior deposits still mint against rawjunior_balance()(Line 1465). After a loss, this creates inconsistent share pricing between entry and exit.Proposed fix
If you keep
effective_junior_balance() -> u64, use it directly; if you apply the fail-closed change insrc/state.rs, propagateOptionhere as above.🤖 Prompt for AI Agents