Skip to content

Commit

Permalink
Eliminate reserves bad debt allocation and add margin to TP (#962)
Browse files Browse the repository at this point in the history
* this underflows instead of giving expected revert

* move isCollateralized check after updating borrower collateral

* remove local calculation of encumbered collateral

* trying to properly fix testBorrowRepayPrecision

* resolve rounding issue in fuzz test

* testCollateralization improvements

* updated unit tests for new _collateralization implementation

* more collateralization tests

* Add 1.04 factor in borrower collateralization

* Update nptp ratio to '1 + sqrt(r)/2'

* Remove Settle debt with pool reserves

* Remove 0.995 factor from claimable reserves calculation

* Update bond factor calculation to minimum 0.005

* added testcase where debt exceeds deposit

* updated test so debt exceeds deposit

* allow  up to half of current orig fee to be used to settle bad debt

* updated testTakeAndSettle

* more test fixes

* Enabled settling with all reserves if
 Deposits.treeSum==0 or 72 hrs pass

* cleanup

* Half orig fee res | Matt example (#966)

* added Matts test as proof that attack no longer works on his branch

* Revert "Remove multicall from position manager (#948)" (#961)

This reverts commit f540c8a.

* added test testSpendOrigFeePushBadDebtToBorrowers test

* cleaned up testStealReservesWithMarginm to match minted balances

* responded to Matts comments

---------

Co-authored-by: Ian Harvey <iharvey@comcast.net>
Co-authored-by: Mike Hathaway <mahathaway93@gmail.com>

* Revert "Remove Settle debt with pool reserves"

This reverts commit 290d6cf.

* Update half origination fees reserves settlement time to 144 hours from kickTime

* Fix alignment and extra spaces

* Fix some unit tests

* PR feedback

* Update encumberance and collateralization method in poolInfoUtils

* Fix some unit tests

---------

Co-authored-by: Ed Noepel <ed@noepel.net>
Co-authored-by: Ian Harvey <iharvey@comcast.net>
Co-authored-by: mwc <matt@ajna.finance>
Co-authored-by: Ian Harvey <ith.harvey@gmail.com>
Co-authored-by: Mike Hathaway <mahathaway93@gmail.com>
  • Loading branch information
6 people committed Nov 20, 2023
1 parent eb76ca7 commit ce7b42b
Show file tree
Hide file tree
Showing 25 changed files with 1,011 additions and 526 deletions.
4 changes: 2 additions & 2 deletions src/PoolInfoUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ contract PoolInfoUtils {
uint256 debt_,
uint256 price_
) pure returns (uint256 encumberance_) {
return price_ != 0 ? Maths.wdiv(debt_, price_) : 0;
return price_ != 0 ? Maths.wdiv(Maths.wmul(1.04 * 1e18 , debt_), price_) : 0;
}

/**
Expand All @@ -515,7 +515,7 @@ contract PoolInfoUtils {

// borrower is undercollateralized when lup at MIN_PRICE
if (price_ == MIN_PRICE) return 0;
return Maths.wdiv(Maths.wmul(collateral_, price_), debt_);
return Maths.wdiv(Maths.wmul(collateral_, price_), Maths.wmul(1.04 * 1e18, debt_));
}

/**
Expand Down
7 changes: 4 additions & 3 deletions src/interfaces/pool/commons/IPoolState.sol
Original file line number Diff line number Diff line change
Expand Up @@ -384,9 +384,10 @@ struct Loan {

/// @dev Struct holding borrower state.
struct Borrower {
uint256 t0Debt; // [WAD] Borrower debt time-adjusted as if it was incurred upon first loan of pool.
uint256 collateral; // [WAD] Collateral deposited by borrower.
uint256 npTpRatio; // [WAD] Np to Tp ratio at the time of last borrow or pull collateral.
uint256 t0Debt; // [WAD] Borrower debt time-adjusted as if it was incurred upon first loan of pool.
uint256 collateral; // [WAD] Collateral deposited by borrower.
uint256 npTpRatio; // [WAD] Np to Tp ratio at the time of last borrow or pull collateral.
uint256 t0ReserveSettleAmount; // [WAD] Amount of t0Debt that could be settled via reserves in an auction
}

/**********************/
Expand Down
1 change: 1 addition & 0 deletions src/libraries/external/BorrowerActions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ library BorrowerActions {
vars.t0DebtChange = Maths.wmul(vars.t0BorrowAmount, _borrowFeeRate(poolState_.rate) + Maths.WAD);

borrower.t0Debt += vars.t0DebtChange;
borrower.t0ReserveSettleAmount += Maths.wmul(vars.t0BorrowAmount, _borrowFeeRate(poolState_.rate)) / 2;

vars.borrowerDebt = Maths.wmul(borrower.t0Debt, poolState_.inflator);

Expand Down
10 changes: 9 additions & 1 deletion src/libraries/external/SettlerActions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,16 @@ library SettlerActions {
reserveAuction_.unclaimed;

// settle debt from reserves (assets - liabilities) if reserves positive, round reserves down however
// capped at half of the origination fee rate, based on current book fees
if (assets > liabilities) {
borrower.t0Debt -= Maths.min(borrower.t0Debt, Maths.floorWdiv(assets - liabilities, poolState_.inflator));
uint256 t0ReserveSettleAmount = Maths.min(Maths.floorWdiv(assets - liabilities, poolState_.inflator), borrower.t0Debt);

// if the settlement phase of 144 hours has not ended, settle up to the borrower reserve limit
if((block.timestamp - kickTime < 144 hours) && (Deposits.treeSum(deposits_) > 0)) {
t0ReserveSettleAmount = Maths.min(t0ReserveSettleAmount, borrower.t0ReserveSettleAmount);
borrower.t0ReserveSettleAmount -= t0ReserveSettleAmount;
}
borrower.t0Debt -= t0ReserveSettleAmount;
}

// 3. forgive bad debt from next HPB
Expand Down
17 changes: 10 additions & 7 deletions src/libraries/helpers/PoolHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,10 @@ import { Maths } from '../internal/Maths.sol';
// Use collateral floor for NFT pools
if (type_ == uint8(PoolType.ERC721)) {
//slither-disable-next-line divide-before-multiply
collateral_ = (collateral_ / Maths.WAD) * Maths.WAD;
collateral_ = (collateral_ / Maths.WAD) * Maths.WAD; // use collateral floor
}

return Maths.wmul(collateral_, price_) >= debt_;
return Maths.wmul(collateral_, price_) >= Maths.wmul(1.04 * 1e18, debt_);
}

/**
Expand Down Expand Up @@ -339,7 +339,7 @@ import { Maths } from '../internal/Maths.sol';

// calculate claimable reserves if there's quote token excess
if (quoteTokenBalance_ > guaranteedFunds) {
claimable_ = Maths.wmul(0.995 * 1e18, debt_) + quoteTokenBalance_;
claimable_ = debt_ + quoteTokenBalance_;

claimable_ -= Maths.min(
claimable_,
Expand Down Expand Up @@ -447,10 +447,13 @@ import { Maths } from '../internal/Maths.sol';
uint256 borrowerDebt_,
uint256 npTpRatio_
) pure returns (uint256 bondFactor_, uint256 bondSize_) {
// bondFactor = min((NP-to-TP-ratio - 1)/10, 0.03)
bondFactor_ = Maths.min(
0.03 * 1e18,
(npTpRatio_ - 1e18) / 10
// bondFactor = max(min(0.03,(((NP/TP_ratio)-1)/10)),0.005)
bondFactor_ = Maths.max(
Maths.min(
0.03 * 1e18,
(npTpRatio_ - 1e18) / 10
),
0.005 * 1e18
);

bondSize_ = Maths.wmul(bondFactor_, borrowerDebt_);
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/internal/Loans.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ library Loans {

// update Np to Tp ratio of borrower
if (npTpRatioUpdate_) {
borrower_.npTpRatio = 1.04 * 1e18 + uint256(PRBMathSD59x18.sqrt(int256(poolRate_))) / 2;
borrower_.npTpRatio = 1e18 + uint256(PRBMathSD59x18.sqrt(int256(poolRate_))) / 2;
}

// save borrower state
Expand Down
2 changes: 1 addition & 1 deletion tests/forge/interactions/BalancerUniswapExample.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ contract BalancerUniswapTaker {

// take auction from Ajna pool, give USDC, receive WETH
IAjnaPool(decoded.ajnaPool).take(decoded.borrower, decoded.maxAmount, address(this), new bytes(0));
uint256 usdcBalanceAfterTake = 81080126;
uint256 usdcBalanceAfterTake = 81974358;
assert(tokens[0].balanceOf(address(this)) == usdcBalanceAfterTake); // USDC balance after Ajna take
assert(tokens[1].balanceOf(address(this)) == 2000000000000000000); // WETH balance after Ajna take

Expand Down
10 changes: 5 additions & 5 deletions tests/forge/interactions/ERC20TakeWithExternalLiquidity.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ contract ERC20TakeWithExternalLiquidityTest is Test {
vm.startPrank(_borrower);
weth.approve(address(_ajnaPool), type(uint256).max);
usdc.approve(address(_ajnaPool), type(uint256).max);
_ajnaPool.drawDebt(_borrower, 19.25 * 1e18, 3696, 2 * 1e18);
_ajnaPool.drawDebt(_borrower, 19 * 1e18, 3696, 2 * 1e18);
vm.stopPrank();

// borrower2 draws debt
Expand Down Expand Up @@ -105,7 +105,7 @@ contract ERC20TakeWithExternalLiquidityTest is Test {
})
);
vm.expectEmit(true, true, false, true);
emit Take(_borrower, 18.919873153126569032 * 1e18, 2.0 * 1e18, 0.287210105092827748 * 1e18, true);
emit Take(_borrower, 18.025641486856350208 * 1e18, 2.0 * 1e18, 0.201532798513255896 * 1e18, true);
taker.take(tokens, amounts, data);

assertGt(usdc.balanceOf(address(this)), 0); // could vary
Expand All @@ -126,7 +126,7 @@ contract ERC20TakeWithExternalLiquidityTest is Test {
// call take using taker contract
bytes memory data = abi.encode(address(_ajnaPool));
vm.expectEmit(true, true, false, true);
emit Take(_borrower, 18.919873153126569032 * 1e18, 2.0 * 1e18, 0.287210105092827748 * 1e18, true);
emit Take(_borrower, 18.025641486856350208 * 1e18, 2.0 * 1e18, 0.201532798513255896 * 1e18, true);
_ajnaPool.take(_borrower, takeAmount, address(taker), data);

// confirm we earned some quote token
Expand All @@ -136,7 +136,7 @@ contract ERC20TakeWithExternalLiquidityTest is Test {
function testTakeCalleeDiffersFromSender() external {

// _lender is msg.sender, QT & CT balances pre take
assertEq(usdc.balanceOf(_lender), 119_999.999999926999703463 * 1e18);
assertEq(usdc.balanceOf(_lender), 119_999.999999926999784436 * 1e18);
assertEq(weth.balanceOf(_lender), 0);

// callee, _lender1 QT & CT balances pre take
Expand All @@ -148,7 +148,7 @@ contract ERC20TakeWithExternalLiquidityTest is Test {
_ajnaPool.take(_borrower, 1_001 * 1e18, _lender1, new bytes(0));

// _lender is has QT deducted from balance
assertEq(usdc.balanceOf(_lender), 119_999.999999926980783589 * 1e18);
assertEq(usdc.balanceOf(_lender), 119_999.999999926981758794 * 1e18);
assertEq(weth.balanceOf(_lender), 0);

// callee, _lender1 receives CT from take
Expand Down
16 changes: 8 additions & 8 deletions tests/forge/interactions/ERC721TakeWithExternalLiquidity.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ contract ERC721TakeWithExternalLiquidityTest is ERC721HelperContract {
_drawDebt({
from: _borrower,
borrower: _borrower,
amountToBorrow: 1_999 * 1e18,
amountToBorrow: 1_930 * 1e18,
limitIndex: 3232,
tokenIds: tokenIdsToAdd,
newLup: _p1004_98
Expand Down Expand Up @@ -80,14 +80,14 @@ contract ERC721TakeWithExternalLiquidityTest is ERC721HelperContract {
// call take using taker contract
bytes memory data = abi.encode(address(_pool));
vm.expectEmit(true, true, false, true);
uint256 quoteTokenPaid = 1_161.844718489711575688 * 1e18;
uint256 quoteTokenPaid = 1_082.785034492073132320 * 1e18;
uint256 collateralPurchased = 2 * 1e18;
uint256 bondChange = 17.637197723169355130 * 1e18;
uint256 bondChange = 12.105904710718649454 * 1e18;
emit Take(_borrower, quoteTokenPaid, collateralPurchased, bondChange, true);
_pool.take(_borrower, 2, address(taker), data);

// confirm we earned some quote token
assertEq(_quote.balanceOf(address(taker)), 338.155281510288424312 * 1e18);
assertEq(_quote.balanceOf(address(taker)), 417.214965507926867680 * 1e18);
}

function testTakeNFTCalleeDiffersFromSender() external {
Expand All @@ -103,21 +103,21 @@ contract ERC721TakeWithExternalLiquidityTest is ERC721HelperContract {
_collateral.setApprovalForAll(address(marketPlace), true);

// _lender is msg.sender, QT & CT balances pre take
assertEq(_quote.balanceOf(_lender), 49_969.374638518350819260 * 1e18);
assertEq(_quote.balanceOf(_lender), 49_978.222939913714312699 * 1e18);
assertEq(_quote.balanceOf(address(taker)), 0);

// call take using taker contract
changePrank(_lender);
bytes memory data = abi.encode(address(_pool));
vm.expectEmit(true, true, false, true);
uint256 quoteTokenPaid = 1_161.844718489711575688 * 1e18;
uint256 quoteTokenPaid = 1_082.785034492073132320 * 1e18;
uint256 collateralPurchased = 2 * 1e18;
uint256 bondChange = 17.637197723169355130 * 1e18;
uint256 bondChange = 12.105904710718649454 * 1e18;
emit Take(_borrower, quoteTokenPaid, collateralPurchased, bondChange, true);
_pool.take(_borrower, 2, address(taker), data);

// _lender is msg.sender, QT & CT balances post take
assertEq(_quote.balanceOf(_lender), 48_807.529920028639243572 * 1e18);
assertEq(_quote.balanceOf(_lender), 48_895.437905421641180379 * 1e18);
assertEq(_quote.balanceOf(address(taker)), 1_500.0 * 1e18); // QT is increased as NFTTakeExample contract sells the NFT
}
}
14 changes: 7 additions & 7 deletions tests/forge/unit/ERC20Pool/ERC20PoolBorrow.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract {
borrower: _borrower,
borrowerDebt: expectedDebt,
borrowerCollateral: 50 * 1e18,
borrowert0Np: 484.222578900118175410 * 1e18,
borrowert0Np: 467.406425053964329248 * 1e18,
borrowerCollateralization: 7.090818626082626625 * 1e18
});

Expand Down Expand Up @@ -389,7 +389,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract {
borrower: _borrower,
borrowerDebt: expectedDebt,
borrowerCollateral: 60 * 1e18,
borrowert0Np: 403.518815750098479508 * 1e18,
borrowert0Np: 389.505354211636941040 * 1e18,
borrowerCollateralization: 8.498498289218581680 * 1e18
});
_assertLenderInterest(liquidityAdded, 22.041594239314650000 * 1e18);
Expand Down Expand Up @@ -427,7 +427,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract {
borrower: _borrower,
borrowerDebt: expectedDebt,
borrowerCollateral: 50 * 1e18,
borrowert0Np: 483.986975517230275430 * 1e18,
borrowert0Np: 467.170821671076429268 * 1e18,
borrowerCollateralization: 7.072483950112624325 * 1e18
});
_assertLenderInterest(liquidityAdded, 47.030954189176600000 * 1e18);
Expand Down Expand Up @@ -462,7 +462,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract {
borrower: _borrower,
borrowerDebt: expectedDebt,
borrowerCollateral: 50 * 1e18,
borrowert0Np: 486.269617724627962428 * 1e18,
borrowert0Np: 469.453463878474116266 * 1e18,
borrowerCollateralization: 7.061941219869076860 * 1e18
});
_assertLenderInterest(liquidityAdded, 74.559175998268400000 * 1e18);
Expand Down Expand Up @@ -494,7 +494,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract {
borrower: _borrower,
borrowerDebt: expectedDebt,
borrowerCollateral: 50 * 1e18,
borrowert0Np: 486.269617724627962428 * 1e18,
borrowert0Np: 469.453463878474116266 * 1e18,
borrowerCollateralization: 7.050362367352844516 * 1e18
});
_assertLenderInterest(liquidityAdded, 104.888588646515350000 * 1e18);
Expand Down Expand Up @@ -524,7 +524,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract {
borrower: _borrower,
borrowerDebt: expectedDebt,
borrowerCollateral: 50 * 1e18,
borrowert0Np: 486.269617724627962428 * 1e18,
borrowert0Np: 469.453463878474116266 * 1e18,
borrowerCollateralization: 7.037647555876562588 * 1e18
});
}
Expand Down Expand Up @@ -932,7 +932,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract {
borrower: _borrower,
borrowerDebt: 500.480769230769231 * 1e18,
borrowerCollateral: 50 * 1e18,
borrowert0Np: 11.529109021431385128 * 1e18,
borrowert0Np: 11.128724406046769744 * 1e18,
borrowerCollateralization: 300.799971477982403335 * 1e18
});

Expand Down
Loading

0 comments on commit ce7b42b

Please sign in to comment.