Skip to content

Commit

Permalink
Fix LOAN_05 bug with partial repay
Browse files Browse the repository at this point in the history
  • Loading branch information
aviggiano committed Jan 12, 2024
1 parent 218bef7 commit 4fc0d9c
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 9 deletions.
1 change: 1 addition & 0 deletions src/SizeView.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ abstract contract SizeView is SizeStorage, ISizeView {
function getUserView(address user) public view returns (UserView memory) {
return UserView({
user: state.users[user],
account: user,
collateralAmount: state.tokens.collateralToken.balanceOf(user),
borrowAmount: state.tokens.borrowToken.balanceOf(user),
debtAmount: state.tokens.debtToken.balanceOf(user)
Expand Down
1 change: 1 addition & 0 deletions src/libraries/UserLibrary.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ struct User {

struct UserView {
User user;
address account;
uint256 collateralAmount;
uint256 borrowAmount;
uint256 debtAmount;
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/actions/Common.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ library Common {
state.tokens.debtToken.burn(fol.borrower, amount);

loan.faceValue -= amount;
validateMinimumCredit(state, loan.getCredit());

if (!loan.isFOL()) {
fol.faceValue -= amount;
fol.faceValueExited -= amount;
validateMinimumCredit(state, fol.getCredit());
}
}

Expand Down
1 change: 0 additions & 1 deletion src/libraries/actions/SelfLiquidateLoan.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ library SelfLiquidateLoan {

Loan storage loan = state.loans[params.loanId];

// credit := faceValue - exited :>= state.config.minimumCredit (by construction, see createSOL)
uint256 credit = loan.getCredit();
Loan storage fol = state.getFOL(loan);

Expand Down
1 change: 0 additions & 1 deletion test/BorrowAsMarketOrder.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,6 @@ contract BorrowAsMarketOrderTest is BaseTest {
_deposit(alice, 100e18, 100e18);
_deposit(bob, 100e18, 100e18);
_lendAsLimitOrder(alice, 100e18, 12, 0.1e18, 12);
LoanOffer memory offerBefore = size.getUserView(alice).user.loanOffer;

Vars memory _before = _state();

Expand Down
34 changes: 34 additions & 0 deletions test/Repay.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ pragma solidity 0.8.20;
import {BaseTest, Vars} from "./BaseTest.sol";

import {Errors} from "@src/libraries/Errors.sol";

import {LoanStatus} from "@src/libraries/LoanLibrary.sol";
import {PERCENT} from "@src/libraries/MathLibrary.sol";
import {RepayParams} from "@src/libraries/actions/Repay.sol";

import {Math} from "@src/libraries/MathLibrary.sol";

Expand Down Expand Up @@ -154,4 +156,36 @@ contract RepayTest is BaseTest {
assertEq(_after.candy.borrowAmount, _before.candy.borrowAmount + faceValue / 2);
assertTrue(!size.getLoan(loanId).repaid);
}

function test_Repay_repay_partial_cannot_leave_loan_below_minimumCredit() public {
_setPrice(1e18);
_deposit(alice, usdc, 100e6);
_deposit(bob, weth, 150e18);
_lendAsLimitOrder(alice, 100e18, 12, 0, 12);
uint256 amount = 10e18;
uint256 loanId = _borrowAsMarketOrder(bob, alice, amount, 12);

vm.expectRevert(
abi.encodeWithSelector(Errors.CREDIT_LOWER_THAN_MINIMUM_CREDIT.selector, 4e18, size.config().minimumCredit)
);
_repay(bob, loanId, 6e18);
assertGt(size.getCredit(loanId), size.config().minimumCredit);
}

function test_Repay_repay_partial_cannot_leave_loan_below_minimumCredit(uint256 borrowAmount, uint256 repayAmount)
public
{
borrowAmount = bound(borrowAmount, size.config().minimumCredit, 100e18);
repayAmount = bound(repayAmount, 0, borrowAmount);

_setPrice(1e18);
_deposit(alice, usdc, 100e6);
_deposit(bob, weth, 150e18);
_lendAsLimitOrder(alice, 100e18, 12, 0, 12);
uint256 loanId = _borrowAsMarketOrder(bob, alice, borrowAmount, 12);

vm.prank(bob);
try size.repay(RepayParams({loanId: loanId, amount: repayAmount})) {} catch {}
assertGe(size.getCredit(loanId), size.config().minimumCredit);
}
}
39 changes: 39 additions & 0 deletions test/invariants/CryticToFoundry.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,43 @@ contract CryticToFoundry is Test, TargetFunctions, FoundryAsserts {
47700905178432190716842576859681767948209730775316858409394951552214610302274
);
}

function test_REPAY_01() public {
deposit(address(0x0), 0);
deposit(address(0xdeadbeef), 91460117);
borrowAsLimitOrder(4907270871702042502, 5894179853816920);
lendAsMarketOrder(address(0x0), 19031769674707116036212, 5004820998700516064, false);
repay(115792089237316195423570985008687907853269984665640564039457584007913129639935, 400);
}

function test_REPAY_01_2() public {
deposit(address(0xdeadbeef), 0);
deposit(address(0x0), 0);
lendAsLimitOrder(0, 3471498, 0);
borrowAsMarketOrder(
address(0x0),
24574995402710635646614640190156820935535387820397,
90229103640261999611339698052518587384522780478451,
false,
0,
17145422572889695645779948228995886701899200731
);
repay(0, 23008551643797667129);
}

function test_LOAN_05() public {
deposit(address(0xdeadbeef), 0);
deposit(address(0x0), 0);
lendAsLimitOrder(0, 4640020, 0);
borrowAsMarketOrder(
address(0x0),
7104076085823381970015930982381262816774162739483,
152749325598820291654851530442320910523826499512205,
false,
0,
20428906037535863295274218098285099272042586872
);
try this.repay(0, 14698502976581688395) {} catch {}
assertTrue(invariant_LOAN(), LOAN_05);
}
}
2 changes: 2 additions & 0 deletions test/invariants/Helper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ abstract contract Helper is Deploy, PropertiesConstants {
uint256 internal constant MAX_DURATION = 180 days;
uint256 internal constant MAX_RATE = 2e18;
uint256 internal constant MAX_TIME_BUCKETS = 24;
uint256 internal constant MIN_PRICE = 0.01e18;
uint256 internal constant MAX_PRICE = 10_000e18;

function _getRandomUser(address user) internal pure returns (address) {
return uint160(user) % 3 == 0 ? USER1 : uint160(user) % 3 == 1 ? USER2 : USER3;
Expand Down
14 changes: 7 additions & 7 deletions test/invariants/TargetFunctions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {SelfLiquidateLoanParams} from "@src/libraries/actions/SelfLiquidateLoan.
import {WithdrawParams} from "@src/libraries/actions/Withdraw.sol";

abstract contract TargetFunctions is Deploy, Helper, Properties, BaseTargetFunctions {
event L1(uint256 a);
event L4(uint256 a, uint256 b, uint256 c, uint256 d);

function setup() internal override {
Expand Down Expand Up @@ -220,8 +221,8 @@ abstract contract TargetFunctions is Deploy, Helper, Properties, BaseTargetFunct

__after(loanId);

lt(_after.sender.borrowAmount, _before.sender.borrowAmount, REPAY_01);
gt(_after.protocolBorrowAmount, _before.protocolBorrowAmount, REPAY_01);
lte(_after.sender.borrowAmount, _before.sender.borrowAmount, REPAY_01);
gte(_after.protocolBorrowAmount, _before.protocolBorrowAmount, REPAY_01);
lt(_after.sender.debtAmount, _before.sender.debtAmount, REPAY_02);
}

Expand Down Expand Up @@ -259,7 +260,7 @@ abstract contract TargetFunctions is Deploy, Helper, Properties, BaseTargetFunct
t(_before.isSenderLiquidatable, LIQUIDATE_03);
}

function selfLiquidateLoan(uint256 loanId) public getSender {
function selfLiquidateLoan(uint256 loanId) internal getSender {
__before(loanId);

precondition(_before.activeLoans > 0);
Expand All @@ -275,7 +276,7 @@ abstract contract TargetFunctions is Deploy, Helper, Properties, BaseTargetFunct
lt(_after.sender.debtAmount, _before.sender.debtAmount, LIQUIDATE_02);
}

function liquidateLoanWithReplacement(uint256 loanId, address borrower) public getSender {
function liquidateLoanWithReplacement(uint256 loanId, address borrower) internal getSender {
__before(loanId);

precondition(_before.activeLoans > 0);
Expand All @@ -293,7 +294,7 @@ abstract contract TargetFunctions is Deploy, Helper, Properties, BaseTargetFunct
lt(_after.borrower.debtAmount, _before.borrower.debtAmount, LIQUIDATE_02);
}

function moveToVariablePool(uint256 loanId) public getSender {
function moveToVariablePool(uint256 loanId) internal getSender {
__before(loanId);

precondition(_before.activeLoans > 0);
Expand All @@ -307,8 +308,7 @@ abstract contract TargetFunctions is Deploy, Helper, Properties, BaseTargetFunct
}

function setPrice(uint256 price) public {
uint256 oldPrice = priceFeed.getPrice();
price = between(price, oldPrice * 1e18 / 1.2e18, priceFeed.getPrice() * 1.2e18 / 1e18);
price = between(price, MIN_PRICE, MAX_PRICE);

priceFeed.setPrice(price);
}
Expand Down

0 comments on commit 4fc0d9c

Please sign in to comment.