-
Notifications
You must be signed in to change notification settings - Fork 10
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
Bug fix: kickWithDeposit
likely push LUP
below HTP
affecting healthy loans
#894
Changes from 4 commits
b301603
e76de2b
e98167b
375f52d
7dd9939
a9a983c
670c21f
f38431e
531d311
cf9459e
8843778
cc83947
f9ea0c6
4a268bc
3ff9ca5
ccfb7d7
aebee03
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 |
---|---|---|
|
@@ -69,15 +69,11 @@ library KickerActions { | |
|
||
/// @dev Struct used for `kickWithDeposit` function local vars. | ||
struct KickWithDepositLocalVars { | ||
uint256 amountToDebitFromDeposit; // [WAD] the amount of quote tokens used to kick and debited from lender deposit | ||
uint256 bucketCollateral; // [WAD] amount of collateral in bucket | ||
uint256 bucketDeposit; // [WAD] amount of quote tokens in bucket | ||
uint256 bucketLP; // [WAD] LP of the bucket | ||
uint256 bucketPrice; // [WAD] bucket price | ||
uint256 bucketScale; // [WAD] bucket scales | ||
uint256 bucketUnscaledDeposit; // [WAD] unscaled amount of quote tokens in bucket | ||
uint256 lenderLP; // [WAD] LP of lender in bucket | ||
uint256 redeemedLP; // [WAD] LP used by kick action | ||
uint256 bucketDeposit; // [WAD] amount of quote tokens in bucket | ||
uint256 bucketPrice; // [WAD] bucket price | ||
uint256 currentLup; // [WAD] current LUP in pool | ||
uint256 entitledAmount; // [WAD] amount that lender is entitled to remove at specified index | ||
uint256 lenderLP; // [WAD] LP of lender in bucket | ||
} | ||
|
||
/**************/ | ||
|
@@ -140,6 +136,7 @@ library KickerActions { | |
* @dev - decrement `lender.lps` accumulator | ||
* @dev - decrement `bucket.lps` accumulator | ||
* @dev === Reverts on === | ||
* @dev bucket price bellow current pool `LUP` `PriceBelowLUP()` | ||
* @dev insufficient deposit to kick auction `InsufficientLiquidity()` | ||
* @dev no `LP` redeemed to kick auction `InsufficientLP()` | ||
* @dev === Emit events === | ||
|
@@ -157,35 +154,35 @@ library KickerActions { | |
) external returns ( | ||
KickResult memory kickResult_ | ||
) { | ||
Bucket storage bucket = buckets_[index_]; | ||
Lender storage lender = bucket.lenders[msg.sender]; | ||
|
||
KickWithDepositLocalVars memory vars; | ||
|
||
if (bucket.bankruptcyTime < lender.depositTime) vars.lenderLP = lender.lps; | ||
vars.bucketPrice = _priceAt(index_); | ||
vars.currentLup = Deposits.getLup(deposits_, poolState_.debt); | ||
|
||
// revert if the bucket price is below LUP | ||
if (vars.bucketPrice < vars.currentLup) revert PriceBelowLUP(); | ||
|
||
Bucket storage bucket = buckets_[index_]; | ||
Lender storage lender = bucket.lenders[msg.sender]; | ||
|
||
vars.bucketLP = bucket.lps; | ||
vars.bucketCollateral = bucket.collateral; | ||
vars.bucketPrice = _priceAt(index_); | ||
vars.bucketUnscaledDeposit = Deposits.unscaledValueAt(deposits_, index_); | ||
vars.bucketScale = Deposits.scale(deposits_, index_); | ||
vars.bucketDeposit = Maths.wmul(vars.bucketUnscaledDeposit, vars.bucketScale); | ||
vars.lenderLP = bucket.bankruptcyTime < lender.depositTime ? lender.lps : 0; | ||
vars.bucketDeposit = Deposits.valueAt(deposits_, index_); | ||
|
||
// calculate amount to remove based on lender LP in bucket | ||
vars.amountToDebitFromDeposit = Buckets.lpToQuoteTokens( | ||
vars.bucketCollateral, | ||
vars.bucketLP, | ||
// calculate amount lender is entitled in current bucket (based on lender LP in bucket) | ||
vars.entitledAmount = Buckets.lpToQuoteTokens( | ||
bucket.collateral, | ||
bucket.lps, | ||
vars.bucketDeposit, | ||
vars.lenderLP, | ||
vars.bucketPrice, | ||
Math.Rounding.Down | ||
); | ||
|
||
// cap the amount to remove at bucket deposit | ||
if (vars.amountToDebitFromDeposit > vars.bucketDeposit) vars.amountToDebitFromDeposit = vars.bucketDeposit; | ||
// cap the amount entitled at bucket deposit | ||
if (vars.entitledAmount > vars.bucketDeposit) vars.entitledAmount = vars.bucketDeposit; | ||
|
||
// revert if no amount that can be removed | ||
if (vars.amountToDebitFromDeposit == 0) revert InsufficientLiquidity(); | ||
// revert if no entitled amount | ||
if (vars.entitledAmount == 0) revert InsufficientLiquidity(); | ||
|
||
// kick top borrower | ||
kickResult_ = _kick( | ||
|
@@ -195,81 +192,10 @@ library KickerActions { | |
poolState_, | ||
Loans.getMax(loans_).borrower, | ||
limitIndex_, | ||
vars.amountToDebitFromDeposit | ||
); | ||
|
||
// amount to remove from deposit covers entire bond amount | ||
if (vars.amountToDebitFromDeposit > kickResult_.amountToCoverBond) { | ||
// cap amount to remove from deposit at amount to cover bond | ||
vars.amountToDebitFromDeposit = kickResult_.amountToCoverBond; | ||
|
||
// recalculate the LUP with the amount to cover bond | ||
kickResult_.lup = Deposits.getLup(deposits_, poolState_.debt + vars.amountToDebitFromDeposit); | ||
// entire bond is covered from deposit, no additional amount to be send by lender | ||
kickResult_.amountToCoverBond = 0; | ||
} else { | ||
// lender should send additional amount to cover bond | ||
kickResult_.amountToCoverBond -= vars.amountToDebitFromDeposit; | ||
} | ||
|
||
// revert if the bucket price used to kick and remove is below new LUP | ||
if (vars.bucketPrice < kickResult_.lup) revert PriceBelowLUP(); | ||
|
||
// remove amount from deposits | ||
if (vars.amountToDebitFromDeposit == vars.bucketDeposit && vars.bucketCollateral == 0) { | ||
// In this case we are redeeming the entire bucket exactly, and need to ensure bucket LP are set to 0 | ||
vars.redeemedLP = vars.bucketLP; | ||
|
||
Deposits.unscaledRemove(deposits_, index_, vars.bucketUnscaledDeposit); | ||
vars.bucketUnscaledDeposit = 0; | ||
|
||
} else { | ||
vars.redeemedLP = Buckets.quoteTokensToLP( | ||
vars.bucketCollateral, | ||
vars.bucketLP, | ||
vars.bucketDeposit, | ||
vars.amountToDebitFromDeposit, | ||
vars.bucketPrice, | ||
Math.Rounding.Up | ||
); | ||
|
||
uint256 unscaledAmountToRemove = Maths.floorWdiv(vars.amountToDebitFromDeposit, vars.bucketScale); | ||
|
||
// revert if calculated unscaled amount is 0 | ||
if (unscaledAmountToRemove == 0) revert InsufficientLiquidity(); | ||
|
||
Deposits.unscaledRemove(deposits_, index_, unscaledAmountToRemove); | ||
vars.bucketUnscaledDeposit -= unscaledAmountToRemove; | ||
} | ||
|
||
vars.redeemedLP = Maths.min(vars.lenderLP, vars.redeemedLP); | ||
|
||
// revert if LP redeemed amount to kick auction is 0 | ||
if (vars.redeemedLP == 0) revert InsufficientLP(); | ||
|
||
uint256 bucketRemainingLP = vars.bucketLP - vars.redeemedLP; | ||
|
||
if (vars.bucketCollateral == 0 && vars.bucketUnscaledDeposit == 0 && bucketRemainingLP != 0) { | ||
bucket.lps = 0; | ||
bucket.bankruptcyTime = block.timestamp; | ||
|
||
emit BucketBankruptcy( | ||
index_, | ||
bucketRemainingLP | ||
); | ||
} else { | ||
// update lender and bucket LP balances | ||
lender.lps -= vars.redeemedLP; | ||
bucket.lps -= vars.redeemedLP; | ||
} | ||
|
||
emit RemoveQuoteToken( | ||
msg.sender, | ||
index_, | ||
vars.amountToDebitFromDeposit, | ||
vars.redeemedLP, | ||
kickResult_.lup | ||
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. Quite happy to eliminate all this logic. |
||
vars.entitledAmount | ||
); | ||
// set LUP to current pool value | ||
kickResult_.lup = vars.currentLup; | ||
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. Technically, this won't be correct, as the pool's debt will increase slightly. I believe to be totally correct we should recompute the LUP here including the kick penalty interest. 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. makes sense, though should be done for regular kick too, will add this with a commit 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. done in |
||
} | ||
|
||
/*************************/ | ||
|
@@ -381,8 +307,11 @@ library KickerActions { | |
kickResult_.debtPreAction = borrower.t0Debt; | ||
kickResult_.collateralPreAction = borrower.collateral; | ||
kickResult_.t0KickedDebt = kickResult_.debtPreAction ; | ||
|
||
// add amount to remove to pool debt in order to calculate proposed LUP | ||
kickResult_.lup = Deposits.getLup(deposits_, poolState_.debt + additionalDebt_); | ||
// for regular kick this is the currrent LUP in pool | ||
// for provisional kick this just simulates LUP movement and needs to be reset to current LUP in pool | ||
kickResult_.lup = Deposits.getLup(deposits_, poolState_.debt + additionalDebt_); | ||
|
||
KickLocalVars memory vars; | ||
vars.borrowerDebt = Maths.wmul(kickResult_.t0KickedDebt, poolState_.inflator); | ||
|
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.
kickWithDeposit
could be called in the same block as bucket bankruptcy. It would uselender.lps
even though the bucket is bankrupt. Recommend adding a revert like we have inmoveQuoteToken
->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.
if bucket bankruptcy happens in same block but after kick with deposit is called then
vars.lenderLP
will be 0 making entitled amount to be 0 and tx to revert withInsufficientLiquidity
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.
I was thinking before, what if a bucket bankruptcy occurs in the same block but before
kickWithDeposit
is called.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.
I don't think block when kick happens is the constraint here but the block / time of last deposit?
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.
I was misinterpretting the logic...
if bucket.bankruptcyTime < lender.depositTime ? lender.lps : 0;
-> if the bucket was bankrupted in the same block this would be 0 as it should be.All good : )