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

Take underflows when full pool debt repaid #551

Merged
merged 14 commits into from
Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
37 changes: 37 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

<!---
No need to add special tag
src/ & non src/ changes you need the following (that apply):
-->
# Description of change
## High level
* <DESCRIP_OF_CHANGE>
* <DESCRIP_OF_SUBCHANGE>

<!---
Add the `Status: Needs Auditor Approval` tags
CHANGES IN /SRC DIR:
- renaming (not retyping or resizing) of variables & methods
- reordering and moving of functions in files
- lite moving of functions accross files
- comments

src/ changes you need the following (that apply):
-->

# Description of bug or vulnerability and solution
* <PARAGRAPH_EXP_OF_VULN_BUG>
* <PARAGRAPH_EXP_OF_HOW_CHANGE_SOLVES_VULN_OR_BUG>

# Contract size
## Pre Change
<PASTE_OUTPUT_HERE>
## Post Change
<PASTE_OUTPUT_HERE>

# Gas usage
## Pre Change
<PASTE_OUTPUT_HERE>
## Post Change
<PASTE_OUTPUT_HERE>

26 changes: 6 additions & 20 deletions src/ERC20Pool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool {

if (amountToBorrow_ != 0) {
// update pool balances state
poolBalances.t0Debt += result.t0DebtChange;
poolBalances.t0Debt = result.t0PoolDebt;

// move borrowed amount from pool to sender
_transferQuoteToken(msg.sender, amountToBorrow_);
Expand Down Expand Up @@ -212,7 +212,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool {

if (result.quoteTokenToRepay != 0) {
// update pool balances state
poolBalances.t0Debt -= result.t0RepaidDebt;
poolBalances.t0Debt = result.t0PoolDebt;
if (result.t0DebtInAuctionChange != 0) {
poolBalances.t0DebtInAuction -= result.t0DebtInAuctionChange;
}
Expand Down Expand Up @@ -427,18 +427,11 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool {
result.quoteTokenAmount = _roundUpToScale(result.quoteTokenAmount, _getArgUint256(QUOTE_SCALE));

// update pool balances state
uint256 t0PoolDebt = poolBalances.t0Debt;
uint256 t0DebtInAuction = poolBalances.t0DebtInAuction;

if (result.t0DebtPenalty != 0) {
t0PoolDebt += result.t0DebtPenalty;
t0DebtInAuction += result.t0DebtPenalty;
}

t0PoolDebt -= result.t0RepayAmount;
t0DebtInAuction += result.t0DebtPenalty;
t0DebtInAuction -= result.t0DebtInAuctionChange;

poolBalances.t0Debt = t0PoolDebt;
poolBalances.t0Debt = result.t0PoolDebt;
poolBalances.t0DebtInAuction = t0DebtInAuction;
poolBalances.pledgedCollateral -= result.collateralAmount;

Expand Down Expand Up @@ -488,18 +481,11 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool {
);

// update pool balances state
uint256 t0PoolDebt = poolBalances.t0Debt;
uint256 t0DebtInAuction = poolBalances.t0DebtInAuction;

if (result.t0DebtPenalty != 0) {
t0PoolDebt += result.t0DebtPenalty;
t0DebtInAuction += result.t0DebtPenalty;
}

t0PoolDebt -= result.t0RepayAmount;
t0DebtInAuction += result.t0DebtPenalty;
t0DebtInAuction -= result.t0DebtInAuctionChange;

poolBalances.t0Debt = t0PoolDebt;
poolBalances.t0Debt = result.t0PoolDebt;
poolBalances.t0DebtInAuction = t0DebtInAuction;
poolBalances.pledgedCollateral -= result.collateralAmount;

Expand Down
26 changes: 6 additions & 20 deletions src/ERC721Pool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool {
// move borrowed amount from pool to sender
if (amountToBorrow_ != 0) {
// update pool balances state
poolBalances.t0Debt += result.t0DebtChange;
poolBalances.t0Debt = result.t0PoolDebt;

// move borrowed amount from pool to sender
_transferQuoteToken(msg.sender, amountToBorrow_);
Expand Down Expand Up @@ -220,7 +220,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool {

if (result.quoteTokenToRepay != 0) {
// update pool balances state
poolBalances.t0Debt -= result.t0RepaidDebt;
poolBalances.t0Debt = result.t0PoolDebt;
if (result.t0DebtInAuctionChange != 0) {
poolBalances.t0DebtInAuction -= result.t0DebtInAuctionChange;
}
Expand Down Expand Up @@ -422,18 +422,11 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool {
);

// update pool balances state
uint256 t0PoolDebt = poolBalances.t0Debt;
uint256 t0DebtInAuction = poolBalances.t0DebtInAuction;

if (result.t0DebtPenalty != 0) {
t0PoolDebt += result.t0DebtPenalty;
t0DebtInAuction += result.t0DebtPenalty;
}

t0PoolDebt -= result.t0RepayAmount;
t0DebtInAuction += result.t0DebtPenalty;
t0DebtInAuction -= result.t0DebtInAuctionChange;

poolBalances.t0Debt = t0PoolDebt;
poolBalances.t0Debt = result.t0PoolDebt;
poolBalances.t0DebtInAuction = t0DebtInAuction;
poolBalances.pledgedCollateral -= result.collateralAmount;

Expand Down Expand Up @@ -494,18 +487,11 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool {
);

// update pool balances state
uint256 t0PoolDebt = poolBalances.t0Debt;
uint256 t0DebtInAuction = poolBalances.t0DebtInAuction;

if (result.t0DebtPenalty != 0) {
t0PoolDebt += result.t0DebtPenalty;
t0DebtInAuction += result.t0DebtPenalty;
}

t0PoolDebt -= result.t0RepayAmount;
t0DebtInAuction += result.t0DebtPenalty;
t0DebtInAuction -= result.t0DebtInAuctionChange;

poolBalances.t0Debt = t0PoolDebt;
poolBalances.t0Debt = result.t0PoolDebt;
poolBalances.t0DebtInAuction = t0DebtInAuction;
poolBalances.pledgedCollateral -= result.collateralAmount;

Expand Down
21 changes: 9 additions & 12 deletions src/base/Pool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -273,11 +273,11 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool {
);

// update pool balances state
poolBalances.t0Debt = result.t0PoolDebt;
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 this is a fine way to do it, but curious did you see a problem with the previous way, of adding the t0kickpenalty to the global debt?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

nope, just wanted to be consistent

poolBalances.t0DebtInAuction += result.t0KickedDebt;
poolBalances.t0Debt += result.t0KickPenalty;

// update pool interest rate state
poolState.debt += result.kickPenalty;
poolState.debt = Maths.wmul(result.t0PoolDebt, poolState.inflator);
_updateInterestState(poolState, result.lup);

if(result.amountToCoverBond != 0) _transferQuoteTokenFrom(msg.sender, result.amountToCoverBond);
Expand All @@ -304,11 +304,11 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool {
);

// update pool balances state
poolBalances.t0Debt += result.t0KickPenalty;
poolBalances.t0Debt = result.t0PoolDebt;
poolBalances.t0DebtInAuction += result.t0KickedDebt;

// update pool interest rate state
poolState.debt += result.kickPenalty;
poolState.debt = Maths.wmul(result.t0PoolDebt, poolState.inflator);
_updateInterestState(poolState, result.lup);

// transfer from kicker to pool the difference to cover bond
Expand Down Expand Up @@ -356,7 +356,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool {
reserveAuction,
StartReserveAuctionParams({
poolSize: Deposits.treeSum(deposits),
poolDebt: poolBalances.t0Debt,
t0PoolDebt: poolBalances.t0Debt,
poolBalance: _getPoolQuoteTokenBalance(),
inflator: inflatorState.inflator
})
Expand Down Expand Up @@ -424,20 +424,17 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool {
* @return poolState_ Struct containing pool details.
*/
function _accruePoolInterest() internal returns (PoolState memory poolState_) {
// retrieve t0Debt amount from poolBalances struct
uint256 t0Debt = poolBalances.t0Debt;

// initialize fields of poolState_ struct with initial values
poolState_.t0Debt = poolBalances.t0Debt;
poolState_.collateral = poolBalances.pledgedCollateral;
poolState_.inflator = inflatorState.inflator;
poolState_.rate = interestState.interestRate;
poolState_.poolType = _getArgUint8(POOL_TYPE);
poolState_.quoteDustLimit = _getArgUint256(QUOTE_SCALE);

// check if t0Debt is not equal to 0, indicating that there is debt to be tracked for the pool
if (t0Debt != 0) {
if (poolState_.t0Debt != 0) {
// Calculate prior pool debt
poolState_.debt = Maths.wmul(t0Debt, poolState_.inflator);
poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator);

// calculate elapsed time since inflator was last updated
uint256 elapsed = block.timestamp - inflatorState.inflatorUpdate;
Expand All @@ -455,7 +452,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool {
);
poolState_.inflator = newInflator;
// After debt owed to lenders has accrued, calculate current debt owed by borrowers
poolState_.debt = Maths.wmul(t0Debt, poolState_.inflator);
poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator);

// update total interest earned accumulator with the newly accrued interest
reserveAuction.totalInterestEarned += newInterest;
Expand Down
49 changes: 24 additions & 25 deletions src/interfaces/pool/commons/IPoolInternals.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,21 @@ pragma solidity 0.8.14;
/*****************************/

struct BucketTakeResult {
uint256 collateralAmount;
uint256 t0RepayAmount;
uint256 t0DebtPenalty;
uint256 remainingCollateral;
uint256 poolDebt;
uint256 newLup;
uint256 t0DebtInAuctionChange;
bool settledAuction;
uint256 collateralAmount; // [WAD] amount of collateral taken
uint256 t0DebtPenalty; // [WAD] t0 penalty applied on first take
uint256 remainingCollateral; // [WAD] amount of borrower collateral remaining after take
uint256 poolDebt; // [WAD] current pool debt
uint256 t0PoolDebt; // [WAD] t0 pool debt
uint256 newLup; // [WAD] current lup
uint256 t0DebtInAuctionChange; // [WAD] the amount of t0 debt recovered by take action
bool settledAuction; // true if auction is settled by take action
}

struct KickResult {
uint256 amountToCoverBond; // amount of bond that needs to be covered
uint256 kickPenalty; // kick penalty
uint256 t0KickPenalty; // t0 kick penalty
uint256 t0KickedDebt; // new t0 debt after kick
uint256 lup; // current lup
uint256 amountToCoverBond; // [WAD] amount of bond that needs to be covered
uint256 t0PoolDebt; // [WAD] t0 debt in pool after kick
uint256 t0KickedDebt; // [WAD] new t0 debt after kick
uint256 lup; // [WAD] current lup
}

struct SettleParams {
Expand All @@ -38,16 +37,16 @@ struct SettleParams {
}

struct TakeResult {
uint256 collateralAmount;
uint256 quoteTokenAmount;
uint256 t0RepayAmount;
uint256 t0DebtPenalty;
uint256 excessQuoteToken;
uint256 remainingCollateral;
uint256 poolDebt;
uint256 newLup;
uint256 t0DebtInAuctionChange;
bool settledAuction;
uint256 collateralAmount; // [WAD] amount of collateral taken
uint256 quoteTokenAmount; // [WAD] amount of quote tokens paid by taker for taken collateral
uint256 t0DebtPenalty; // [WAD] t0 penalty applied on first take
uint256 excessQuoteToken; // [WAD] (NFT only) amount of quote tokens to be paid by taker to borrower for fractional collateral
uint256 remainingCollateral; // [WAD] amount of borrower collateral remaining after take
uint256 poolDebt; // [WAD] current pool debt
uint256 t0PoolDebt; // [WAD] t0 pool debt
uint256 newLup; // [WAD] current lup
uint256 t0DebtInAuctionChange; // [WAD] the amount of t0 debt recovered by take action
bool settledAuction; // true if auction is settled by take action
}

/******************************************/
Expand Down Expand Up @@ -83,7 +82,7 @@ struct DrawDebtResult {
uint256 remainingCollateral; // [WAD] amount of borrower collateral after draw debt (for NFT can be diminished if auction settled)
bool settledAuction; // true if collateral pledged settles auction
uint256 t0DebtInAuctionChange; // [WAD] change of t0 pool debt in auction after pledge collateral
uint256 t0DebtChange; // [WAD] change of total t0 pool debt after after draw debt
uint256 t0PoolDebt; // [WAD] amount of t0 debt in pool after draw debt
}

struct RepayDebtResult {
Expand All @@ -93,6 +92,6 @@ struct RepayDebtResult {
uint256 remainingCollateral; // [WAD] amount of borrower collateral after pull collateral
bool settledAuction; // true if repay debt settles auction
uint256 t0DebtInAuctionChange; // [WAD] change of t0 pool debt in auction after repay debt
uint256 t0RepaidDebt; // [WAD] amount of t0 repaid debt
uint256 t0PoolDebt; // [WAD] amount of t0 debt in pool after repay
uint256 quoteTokenToRepay; // [WAD] quote token amount to be transferred from sender to pool
}
2 changes: 1 addition & 1 deletion src/interfaces/pool/commons/IPoolReserveAuctionActions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface IPoolReserveAuctionActions {

struct StartReserveAuctionParams {
uint256 poolSize; // [WAD] total deposits in pool (with accrued debt)
uint256 poolDebt; // [WAD] current t0 pool debt
uint256 t0PoolDebt; // [WAD] current t0 pool debt
uint256 poolBalance; // [WAD] pool quote token balance
uint256 inflator; // [WAD] pool current inflator
}
1 change: 1 addition & 0 deletions src/interfaces/pool/commons/IPoolState.sol
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ struct PoolBalancesState {

struct PoolState {
uint8 poolType; // pool type, can be ERC20 or ERC721
uint256 t0Debt; // [WAD] t0 debt in pool
uint256 debt; // [WAD] total debt in pool, accrued in current block
uint256 collateral; // [WAD] total collateral pledged in pool
uint256 inflator; // [WAD] current pool inflator
Expand Down
Loading