Skip to content

Commit

Permalink
Add stampLoan for borrowers to be able to restamp the Neutral Price o…
Browse files Browse the repository at this point in the history
…f the loan
  • Loading branch information
grandizzy committed Feb 28, 2023
1 parent 51e0182 commit e07f6bd
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 4 deletions.
34 changes: 34 additions & 0 deletions docs/Functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,40 @@
- Auctions.takeReserves():
- ReserveAuction

### stampLoan
external libraries call:
- BorrowerActions.stampLoan()

write state:
- _accruePoolInterest():
- PoolCommons.accrueInterest():
- Deposits.mult (scale Fenwick tree with new interest accrued):
- update scaling array state
- increment reserveAuction.totalInterestEarned accumulator
- BorrowerActions.stampLoan():
- Loans.update():
- _upsert():
- insert or update loan in loans array
- remove():
- remove loan from loans array
- update borrower in address => borrower mapping
- _updateInterestState():
- PoolCommons.updateInterestRate():
- interest debt and lup * collateral EMAs accumulators
- interest rate accumulator and interestRateUpdate state
- pool inflator and inflatorUpdate state

reverts on:
- BorrowerActions.stampLoan()
- loan in auction AuctionActive()
- borrower under collateralized BorrowerUnderCollateralized()

emit events:
- BorrowerActions.stampLoan():
- LoanStamped
- PoolCommons.updateInterestRate():
- UpdateInterestRate

## ERC20Pool contract

### addCollateral
Expand Down
20 changes: 20 additions & 0 deletions src/base/Pool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"
import {
IPool,
IPoolImmutables,
IPoolBorrowerActions,
IPoolLenderActions,
IPoolState,
IPoolLiquidationActions,
Expand Down Expand Up @@ -332,6 +333,25 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool {
_updateInterestState(poolState, _lup(poolState.debt));
}

/***********************************/
/*** Borrower External Functions ***/
/***********************************/

/// @inheritdoc IPoolBorrowerActions
function stampLoan(address borrowerAddress_) external override nonReentrant {
PoolState memory poolState = _accruePoolInterest();

uint256 newLup = BorrowerActions.stampLoan(
auctions,
deposits,
loans,
poolState,
borrowerAddress_
);

_updateInterestState(poolState, newLup);
}

/*****************************/
/*** Liquidation Functions ***/
/*****************************/
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces/pool/IPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

pragma solidity 0.8.14;

import { IPoolBorrowerActions } from './commons/IPoolBorrowerActions.sol';
import { IPoolLenderActions } from './commons/IPoolLenderActions.sol';
import { IPoolLiquidationActions } from './commons/IPoolLiquidationActions.sol';
import { IPoolReserveAuctionActions } from './commons/IPoolReserveAuctionActions.sol';
Expand All @@ -16,6 +17,7 @@ import { IERC3156FlashLender } from './IERC3156FlashLender.sol';
* @title Base Pool
*/
interface IPool is
IPoolBorrowerActions,
IPoolLenderActions,
IPoolLiquidationActions,
IPoolReserveAuctionActions,
Expand Down
20 changes: 20 additions & 0 deletions src/interfaces/pool/commons/IPoolBorrowerActions.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.14;

/**
* @title Pool Borrower Actions
*/
interface IPoolBorrowerActions {

/**
* @notice Called by fully colalteralized borrowers to restamp the Neutral Price of the loan (only if loan is fully collateralized and not in auction).
* @notice The reason for stamping the neutral price on the loan is to provide some certainty to the borrower as to at what price they can expect to be liquidated.
* @notice This action can be initiated by borrower itself or by a different actor on behalf of borrower.
* @param borrowerAddress The borrower address to restamp Neutral Price for.
*/
function stampLoan(
address borrowerAddress
) external;

}
8 changes: 8 additions & 0 deletions src/interfaces/pool/commons/IPoolEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ interface IPoolEvents {
uint256 bond
);

/**
* @notice Emitted when a loan Neutral Price is restamped.
* @param borrower Identifies the loan to update the Neutral Price.
*/
event LoanStamped(
address indexed borrower
);

/**
* @notice Emitted when lender moves quote token from a bucket price to another.
* @param lender Recipient that moved quote tokens.
Expand Down
63 changes: 63 additions & 0 deletions src/libraries/external/BorrowerActions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ library BorrowerActions {
uint256 t0RepaidDebt; // [WAD] t0 debt repaid
}

/**************/
/*** Events ***/
/**************/

// See `IPoolEvents` for descriptions
event LoanStamped(address indexed borrowerAddress);

/**************/
/*** Errors ***/
/**************/
Expand Down Expand Up @@ -396,6 +403,62 @@ library BorrowerActions {
);
}

/**
* @notice See `IPoolBorrowerActions` for descriptions
* @dev write state:
* - Loans.update:
* - _upsert:
* - insert or update loan in loans array
* - remove:
* - remove loan from loans array
* - update borrower in address => borrower mapping
* @dev reverts on:
* - auction active AuctionActive()
* - loan not fully collateralized BorrowerUnderCollateralized()
* @dev emit events:
* - LoanStamped
*/
function stampLoan(
AuctionsState storage auctions_,
DepositsState storage deposits_,
LoansState storage loans_,
PoolState calldata poolState_,
address borrowerAddress_
) external returns (
uint256 newLup_
) {
Borrower memory borrower = loans_.borrowers[borrowerAddress_];

bool inAuction = _inAuction(auctions_, borrowerAddress_);

// revert if loan is in auction
if (inAuction) revert AuctionActive();

newLup_ = _lup(deposits_, poolState_.debt);

uint256 borrowerDebt = Maths.wmul(borrower.t0Debt, poolState_.inflator);
bool isCollateralized = _isCollateralized(borrowerDebt, borrower.collateral, newLup_, poolState_.poolType);

// revert if loan is not fully collateralized at current LUP
if (!isCollateralized) revert BorrowerUnderCollateralized();

// update loan state to stamp Neutral Price
Loans.update(
loans_,
auctions_,
deposits_,
borrower,
borrowerAddress_,
poolState_.debt,
poolState_.rate,
newLup_,
false, // loan not in auction
true // stamp Neutral Price of the loan
);

emit LoanStamped(borrowerAddress_);
}

/**********************/
/*** View Functions ***/
/**********************/
Expand Down
19 changes: 15 additions & 4 deletions tests/forge/ERC20Pool/ERC20PoolBorrow.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,10 @@ contract ERC20PoolBorrowTest is ERC20HelperContract {

skip(10 days);

_updateInterest();
// accrue debt and restamp Neutral Price of the loan
vm.expectEmit(true, true, true, true);
emit LoanStamped(_borrower);
_pool.stampLoan(_borrower);

expectedDebt = 21_157.152643010853304038 * 1e18;

Expand All @@ -457,7 +460,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract {
borrower: _borrower,
borrowerDebt: expectedDebt,
borrowerCollateral: 50 * 1e18,
borrowert0Np: 445.838278846153846359 * 1e18,
borrowert0Np: 448.381722115384615591 * 1e18,
borrowerCollateralization: 7.044916376706357984 * 1e18
});
_assertLenderInterest(liquidityAdded, 119.836959946754650000 * 1e18);
Expand Down Expand Up @@ -489,7 +492,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract {
borrower: _borrower,
borrowerDebt: expectedDebt,
borrowerCollateral: 50 * 1e18,
borrowert0Np: 445.838278846153846359 * 1e18,
borrowert0Np: 448.381722115384615591 * 1e18,
borrowerCollateralization: 7.030801136225104190 * 1e18
});
_assertLenderInterest(liquidityAdded, 157.005764521268350000 * 1e18);
Expand Down Expand Up @@ -520,7 +523,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract {
borrower: _borrower,
borrowerDebt: expectedDebt,
borrowerCollateral: 50 * 1e18,
borrowert0Np: 445.838278846153846359 * 1e18,
borrowert0Np: 448.381722115384615591 * 1e18,
borrowerCollateralization: 7.015307034516347067 * 1e18
});
}
Expand Down Expand Up @@ -596,6 +599,14 @@ contract ERC20PoolBorrowTest is ERC20HelperContract {
indexLimit: 3_000,
newLup: 2_995.912459898389633881 * 1e18
});

// skip to make loan undercolalteralized
skip(10000 days);

// should not allow borrower to restamp the Neutral Price of the loan if under collateralized
_assertStampLoanBorrowerUnderCollateralizedRevert({
borrower: _borrower2
});
}

function testMinBorrowAmountCheck() external tearDown {
Expand Down
5 changes: 5 additions & 0 deletions tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,11 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract {
amount: 1 * 1e18,
indexLimit: 7000
});

// should not allow borrower to restamp the Neutral Price of the loan if auction kicked
_assertStampLoanAuctionActiveRevert({
borrower: _borrower
});
}

function testInterestsAccumulationWithAllLoansAuctioned() external tearDown {
Expand Down
16 changes: 16 additions & 0 deletions tests/forge/utils/DSTestPlus.sol
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,22 @@ abstract contract DSTestPlus is Test, IPoolEvents {
_pool.bucketTake(borrower, true, index);
}

function _assertStampLoanAuctionActiveRevert(
address borrower
) internal {
changePrank(borrower);
vm.expectRevert(IPoolErrors.AuctionActive.selector);
_pool.stampLoan(borrower);
}

function _assertStampLoanBorrowerUnderCollateralizedRevert(
address borrower
) internal {
changePrank(borrower);
vm.expectRevert(IPoolErrors.BorrowerUnderCollateralized.selector);
_pool.stampLoan(borrower);
}

function _assertBorrowAuctionActiveRevert(
address from,
uint256,
Expand Down

0 comments on commit e07f6bd

Please sign in to comment.