Skip to content
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

Merged
merged 17 commits into from
Jun 18, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 15 additions & 110 deletions src/libraries/external/KickerActions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,6 @@ library KickerActions {
uint256 kickPenalty; // [WAD] current debt added as kick penalty
}

/// @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
}

/**************/
/*** Events ***/
/**************/
Expand All @@ -101,7 +88,6 @@ library KickerActions {
error InsufficientLP();
error InvalidAmount();
error NoReserves();
error PriceBelowLUP();
error ReserveAuctionTooSoon();

/***************************/
Expand Down Expand Up @@ -160,115 +146,34 @@ library KickerActions {
Bucket storage bucket = buckets_[index_];
Lender storage lender = bucket.lenders[msg.sender];

KickWithDepositLocalVars memory vars;

if (bucket.bankruptcyTime < lender.depositTime) vars.lenderLP = lender.lps;
uint256 lenderLP = bucket.bankruptcyTime < lender.depositTime ? lender.lps : 0;
uint256 bucketDeposit = Deposits.valueAt(deposits_, index_);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should add a check that index_ is greater to or equal to the lup here -- otherwise the provisional removal of the deposit would not actually lower the lup. In that case, the lender can just call removeQuoteToken if they want

Copy link
Contributor Author

Choose a reason for hiding this comment

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


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);

// calculate amount to remove based on lender LP in bucket
vars.amountToDebitFromDeposit = Buckets.lpToQuoteTokens(
vars.bucketCollateral,
vars.bucketLP,
vars.bucketDeposit,
vars.lenderLP,
vars.bucketPrice,
// calculate amount lender is entitled in current bucket (based on lender LP in bucket)
uint256 entitledAmount = Buckets.lpToQuoteTokens(
bucket.collateral,
bucket.lps,
bucketDeposit,
lenderLP,
_priceAt(index_),
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 (entitledAmount > bucketDeposit) entitledAmount = bucketDeposit;

// revert if no amount that can be removed
if (vars.amountToDebitFromDeposit == 0) revert InsufficientLiquidity();
// revert if no entitled amount
if (entitledAmount == 0) revert InsufficientLiquidity();

// kick top borrower
// kick borrower
kickResult_ = _kick(
auctions_,
deposits_,
loans_,
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
Copy link
Contributor

Choose a reason for hiding this comment

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

Quite happy to eliminate all this logic.

entitledAmount
);
}

Expand Down