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

Revert on zero amounts #636

Merged
merged 10 commits into from
Mar 1, 2023
36 changes: 35 additions & 1 deletion docs/Functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
reverts on:
- deposits locked RemoveDepositLockedByAuctionDebt()
- LenderActions.moveQuoteToken():
- same index MoveToSamePrice()
- same index MoveToSameIndex()
- dust amount DustAmountNotExceeded()
- invalid index InvalidIndex()

Expand Down 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
25 changes: 25 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 @@ -326,6 +327,30 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool {
);
}

/// @inheritdoc IPoolLenderActions
function updateInterest() external override nonReentrant {
PoolState memory poolState = _accruePoolInterest();
_updateInterestState(poolState, _lup(poolState.debt));
}

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

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

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

_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
17 changes: 17 additions & 0 deletions src/interfaces/pool/commons/IPoolBorrowerActions.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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 restamp only the loan of `msg.sender`.
*/
function stampLoan() external;

}
14 changes: 12 additions & 2 deletions src/interfaces/pool/commons/IPoolErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,21 @@ interface IPoolErrors {
*/
error InsufficientLiquidity();

/**
* @notice When settling pool debt the number of buckets to use should be greater than 0.
*/
error InvalidBucketDepth();

/**
* @notice When transferring LPs between indices, the new index must be a valid index.
*/
error InvalidIndex();

/**
* @notice The amount used for performed action should be greater than 0.
*/
error InvalidAmount();

/**
* @notice Borrower is attempting to borrow more quote token than is available before the supplied limitIndex.
*/
Expand All @@ -129,9 +139,9 @@ interface IPoolErrors {
error LUPGreaterThanTP();

/**
* @notice FromIndex_ and toIndex_ arguments to move are the same.
* @notice From index and to index arguments to move are the same.
*/
error MoveToSamePrice();
error MoveToSameIndex();

/**
* @notice Owner of the LPs must have approved the new owner prior to transfer.
Expand Down
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
EdNoepel marked this conversation as resolved.
Show resolved Hide resolved
);

/**
* @notice Emitted when lender moves quote token from a bucket price to another.
* @param lender Recipient that moved quote tokens.
Expand Down
5 changes: 5 additions & 0 deletions src/interfaces/pool/commons/IPoolLenderActions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,9 @@ interface IPoolLenderActions {
address newOwner,
uint256[] calldata indexes
) external;

/**
* @notice Called by lenders to update pool interest rate (can be updated only once in a 12 hours period of time).
*/
function updateInterest() external;
}
12 changes: 11 additions & 1 deletion src/libraries/external/Auctions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ library Auctions {
error CollateralRoundingNeededButNotPossible();
error InsufficientLiquidity();
error InsufficientCollateral();
error InvalidBucketDepth();
error InvalidAmount();
error NoAuction();
error NoReserves();
error NoReservesAuction();
Expand Down Expand Up @@ -205,6 +207,9 @@ library Auctions {
uint256 collateralSettled_,
uint256 t0DebtSettled_
) {
// revert if no bucket to settle
if (params_.bucketDepth == 0 ) revert InvalidBucketDepth();
mattcushman marked this conversation as resolved.
Show resolved Hide resolved

uint256 kickTime = auctions_.liquidations[params_.borrower].kickTime;
if (kickTime == 0) revert NoAuction();

Expand Down Expand Up @@ -559,10 +564,12 @@ library Auctions {
uint256 collateral_,
uint256 collateralScale_
) external returns (TakeResult memory result_) {
// revert if no amount to take
if (collateral_ == 0) revert InvalidAmount();

Borrower memory borrower = loans_.borrowers[borrowerAddress_];

if (
(collateral_ == 0) || // revert if amount to take is 0
(poolState_.poolType == uint8(PoolType.ERC721) && borrower.collateral < 1e18) || // revert in case of NFT take when there isn't a full token to be taken
(poolState_.poolType == uint8(PoolType.ERC20) && borrower.collateral == 0) // revert in case of ERC20 take when no collateral to be taken
) {
Expand Down Expand Up @@ -693,6 +700,9 @@ library Auctions {
ReserveAuctionState storage reserveAuction_,
uint256 maxAmount_
) external returns (uint256 amount_, uint256 ajnaRequired_) {
// revert if no amount to be taken
if (maxAmount_ == 0) revert InvalidAmount();

uint256 kicked = reserveAuction_.kicked;

if (kicked != 0 && block.timestamp - kicked <= 72 hours) {
Expand Down
96 changes: 78 additions & 18 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 All @@ -73,6 +80,7 @@ library BorrowerActions {
error BorrowerNotSender();
error BorrowerUnderCollateralized();
error InsufficientCollateral();
error InvalidAmount();
error LimitIndexExceeded();
error NoDebt();

Expand Down Expand Up @@ -117,12 +125,15 @@ library BorrowerActions {
) external returns (
DrawDebtResult memory result_
) {
Borrower memory borrower = loans_.borrowers[borrowerAddress_];

DrawDebtLocalVars memory vars;
vars.pledge = collateralToPledge_ != 0;
vars.borrow = amountToBorrow_ != 0;

// revert if no amount to pledge or borrow
if (!vars.pledge && !vars.borrow) revert InvalidAmount();

Borrower memory borrower = loans_.borrowers[borrowerAddress_];

vars.pledge = collateralToPledge_ != 0;
vars.borrow = amountToBorrow_ != 0 || limitIndex_ != 0; // enable an intentional 0 borrow loan call to update borrower's loan state
vars.borrowerDebt = Maths.wmul(borrower.t0Debt, poolState_.inflator);
vars.inAuction = _inAuction(auctions_, borrowerAddress_);

Expand Down Expand Up @@ -219,11 +230,6 @@ library BorrowerActions {
vars.stampT0Np = true;
}

// calculate LUP if it wasn't calculated previously
if (!vars.pledge && !vars.borrow) {
result_.newLup = _lup(deposits_, result_.poolDebt);
}

// update loan state
Loans.update(
loans_,
Expand Down Expand Up @@ -277,12 +283,15 @@ library BorrowerActions {
) external returns (
RepayDebtResult memory result_
) {
Borrower memory borrower = loans_.borrowers[borrowerAddress_];

RepayDebtLocalVars memory vars;
vars.repay = maxQuoteTokenAmountToRepay_ != 0;
vars.pull = collateralAmountToPull_ != 0;

// revert if no amount to pledge or borrow
if (!vars.repay && !vars.pull) revert InvalidAmount();

Borrower memory borrower = loans_.borrowers[borrowerAddress_];

vars.repay = maxQuoteTokenAmountToRepay_ != 0;
vars.pull = collateralAmountToPull_ != 0;
vars.borrowerDebt = Maths.wmul(borrower.t0Debt, poolState_.inflator);
vars.inAuction = _inAuction(auctions_, borrowerAddress_);

Expand Down Expand Up @@ -379,11 +388,6 @@ library BorrowerActions {
result_.poolCollateral -= collateralAmountToPull_;
}

// calculate LUP if it wasn't calculated previously
if (!vars.repay && !vars.pull) {
result_.newLup = _lup(deposits_, result_.poolDebt);
}

// update loan state
Loans.update(
loans_,
Expand All @@ -399,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_
) external returns (
uint256 newLup_
) {
address borrowerAddress = msg.sender;
Copy link
Collaborator

@MikeHathaway MikeHathaway Mar 1, 2023

Choose a reason for hiding this comment

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

since borrowerAddress is always msg.sender, can we just remove that local variable entirely to save a bit of gas (CALLER vs MLOAD: https://stackoverflow.com/questions/64854256/how-much-accessing-msg-sender-costs-is-it-useful-to-store-it-in-a-variable-and)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call, will do

Copy link
Contributor Author

@grandizzy grandizzy Mar 1, 2023

Choose a reason for hiding this comment

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

changed with 55f44a1

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);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ugly but might want to also remove borrowerDebt local variable for gas savings since its only used once

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed all local vars with d82eda7


// 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
Loading