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

c4-017 BorrowOffers do not have a maxDueDate #136

Merged
merged 4 commits into from
Jul 15, 2024
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
6 changes: 3 additions & 3 deletions src/SizeStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IER
import {IWETH} from "@src/interfaces/IWETH.sol";

import {CreditPosition, DebtPosition} from "@src/libraries/LoanLibrary.sol";
import {BorrowOffer, LoanOffer} from "@src/libraries/OfferLibrary.sol";
import {LimitOrder} from "@src/libraries/OfferLibrary.sol";

import {IPriceFeed} from "@src/oracle/IPriceFeed.sol";

Expand All @@ -15,9 +15,9 @@ import {NonTransferrableToken} from "@src/token/NonTransferrableToken.sol";

struct User {
// The user's loan offer
LoanOffer loanOffer;
LimitOrder loanOffer;
// The user's borrow offer
BorrowOffer borrowOffer;
LimitOrder borrowOffer;
// The user-defined opening limit CR. If not set, the protocol's crOpening is used.
uint256 openingLimitBorrowCR;
// Whether the user has disabled all credit positions for sale
Expand Down
9 changes: 4 additions & 5 deletions src/SizeView.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {DataView, UserView} from "@src/SizeViewData.sol";

import {ISizeView} from "@src/interfaces/ISizeView.sol";
import {Errors} from "@src/libraries/Errors.sol";
import {BorrowOffer, LoanOffer, OfferLibrary} from "@src/libraries/OfferLibrary.sol";
import {LimitOrder, OfferLibrary} from "@src/libraries/OfferLibrary.sol";
import {
InitializeDataParams,
InitializeFeeConfigParams,
Expand All @@ -35,8 +35,7 @@ import {
/// @author Size (https://size.credit/)
/// @notice View methods for the Size protocol
abstract contract SizeView is SizeStorage, ISizeView {
using OfferLibrary for LoanOffer;
using OfferLibrary for BorrowOffer;
using OfferLibrary for LimitOrder;
using LoanLibrary for DebtPosition;
using LoanLibrary for CreditPosition;
using LoanLibrary for State;
Expand Down Expand Up @@ -139,7 +138,7 @@ abstract contract SizeView is SizeStorage, ISizeView {

/// @inheritdoc ISizeView
function getBorrowOfferAPR(address borrower, uint256 tenor) external view returns (uint256) {
BorrowOffer memory offer = state.data.users[borrower].borrowOffer;
LimitOrder memory offer = state.data.users[borrower].borrowOffer;
if (offer.isNull()) {
revert Errors.NULL_OFFER();
}
Expand All @@ -155,7 +154,7 @@ abstract contract SizeView is SizeStorage, ISizeView {

/// @inheritdoc ISizeView
function getLoanOfferAPR(address lender, uint256 tenor) external view returns (uint256) {
LoanOffer memory offer = state.data.users[lender].loanOffer;
LimitOrder memory offer = state.data.users[lender].loanOffer;
if (offer.isNull()) {
revert Errors.NULL_OFFER();
}
Expand Down
1 change: 1 addition & 0 deletions src/libraries/Events.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ library Events {
bool exactAmountIn
);
event SellCreditLimit(
uint256 indexed maxDueDate,
uint256[] curveRelativeTimeTenors,
int256[] curveRelativeTimeAprs,
uint256[] curveRelativeTimeMarketRateMultipliers
Expand Down
78 changes: 27 additions & 51 deletions src/libraries/OfferLibrary.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,75 +5,51 @@ import {Errors} from "@src/libraries/Errors.sol";
import {Math} from "@src/libraries/Math.sol";
import {VariablePoolBorrowRateParams, YieldCurve, YieldCurveLibrary} from "@src/libraries/YieldCurveLibrary.sol";

struct LoanOffer {
// The maximum due date of the loan offer
// Since the yield curve is defined in relative terms, lenders can protect themselves by
struct LimitOrder {
// The maximum due date of the limit order
// Since the yield curve is defined in relative terms, users can protect themselves by
// setting a maximum timestamp for a loan to be matched
uint256 maxDueDate;
// The yield curve in relative terms
YieldCurve curveRelativeTime;
}

struct BorrowOffer {
// The yield curve in relative terms
// Borrowers can protect themselves by setting an opening limit CR for a loan to be matched
YieldCurve curveRelativeTime;
}

/// @title OfferLibrary
/// @custom:security-contact [email protected]
/// @author Size (https://size.credit/)
library OfferLibrary {
using YieldCurveLibrary for YieldCurve;

/// @notice Check if the loan offer is null
/// @param self The loan offer
/// @return True if the loan offer is null, false otherwise
function isNull(LoanOffer memory self) internal pure returns (bool) {
/// @notice Check if the limit order is null
/// @param self The limit order
/// @return True if the limit order is null, false otherwise
function isNull(LimitOrder memory self) internal pure returns (bool) {
return self.maxDueDate == 0 && self.curveRelativeTime.isNull();
}

/// @notice Check if the borrow offer is null
/// @param self The borrow offer
/// @return True if the borrow offer is null, false otherwise
function isNull(BorrowOffer memory self) internal pure returns (bool) {
return self.curveRelativeTime.isNull();
}
/// @notice Validate the limit order
/// @param self The limit order
/// @param minTenor The minimum tenor
/// @param maxTenor The maximum tenor
function validateLimitOrder(LimitOrder memory self, uint256 minTenor, uint256 maxTenor) internal view {
// validate maxDueDate
if (self.maxDueDate == 0) {
revert Errors.NULL_MAX_DUE_DATE();
}
if (self.maxDueDate < block.timestamp + minTenor) {
revert Errors.PAST_MAX_DUE_DATE(self.maxDueDate);
}

/// @notice Get the APR by tenor of a loan offer
/// @param self The loan offer
/// @param params The variable pool borrow rate params
/// @param tenor The tenor
/// @return The APR
function getAPRByTenor(LoanOffer memory self, VariablePoolBorrowRateParams memory params, uint256 tenor)
internal
view
returns (uint256)
{
if (tenor == 0) revert Errors.NULL_TENOR();
return YieldCurveLibrary.getAPR(self.curveRelativeTime, params, tenor);
}

/// @notice Get the absolute rate per tenor of a loan offer
/// @param self The loan offer
/// @param params The variable pool borrow rate params
/// @param tenor The tenor
/// @return The absolute rate
function getRatePerTenor(LoanOffer memory self, VariablePoolBorrowRateParams memory params, uint256 tenor)
internal
view
returns (uint256)
{
uint256 apr = getAPRByTenor(self, params, tenor);
return Math.aprToRatePerTenor(apr, tenor);
// validate curveRelativeTime
YieldCurveLibrary.validateYieldCurve(self.curveRelativeTime, minTenor, maxTenor);
}

/// @notice Get the APR by tenor of a borrow offer
/// @param self The borrow offer
/// @notice Get the APR by tenor of a limit order
/// @param self The limit order
/// @param params The variable pool borrow rate params
/// @param tenor The tenor
/// @return The APR
function getAPRByTenor(BorrowOffer memory self, VariablePoolBorrowRateParams memory params, uint256 tenor)
function getAPRByTenor(LimitOrder memory self, VariablePoolBorrowRateParams memory params, uint256 tenor)
internal
view
returns (uint256)
Expand All @@ -82,12 +58,12 @@ library OfferLibrary {
return YieldCurveLibrary.getAPR(self.curveRelativeTime, params, tenor);
}

/// @notice Get the absolute rate per tenor of a borrow offer
/// @param self The borrow offer
/// @notice Get the absolute rate per tenor of a limit order
/// @param self The limit order
/// @param params The variable pool borrow rate params
/// @param tenor The tenor
/// @return The absolute rate
function getRatePerTenor(BorrowOffer memory self, VariablePoolBorrowRateParams memory params, uint256 tenor)
function getRatePerTenor(LimitOrder memory self, VariablePoolBorrowRateParams memory params, uint256 tenor)
internal
view
returns (uint256)
Expand Down
27 changes: 8 additions & 19 deletions src/libraries/actions/BuyCreditLimit.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import {LoanOffer, OfferLibrary} from "@src/libraries/OfferLibrary.sol";
import {YieldCurve, YieldCurveLibrary} from "@src/libraries/YieldCurveLibrary.sol";
import {LimitOrder, OfferLibrary} from "@src/libraries/OfferLibrary.sol";
import {YieldCurve} from "@src/libraries/YieldCurveLibrary.sol";

import {State} from "@src/SizeStorage.sol";

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

struct BuyCreditLimitParams {
Expand All @@ -21,32 +20,22 @@ struct BuyCreditLimitParams {
/// @author Size (https://size.credit/)
/// @notice Contains the logic for buying credit (lending) as a limit order
library BuyCreditLimit {
using OfferLibrary for LoanOffer;
using OfferLibrary for LimitOrder;

/// @notice Validates the input parameters for buying credit as a limit order
/// @param state The state
/// @param params The input parameters for buying credit as a limit order
function validateBuyCreditLimit(State storage state, BuyCreditLimitParams calldata params) external view {
LoanOffer memory loanOffer =
LoanOffer({maxDueDate: params.maxDueDate, curveRelativeTime: params.curveRelativeTime});
LimitOrder memory loanOffer =
LimitOrder({maxDueDate: params.maxDueDate, curveRelativeTime: params.curveRelativeTime});

// a null offer mean clearing their limit order
if (!loanOffer.isNull()) {
// validate msg.sender
// N/A

// validate maxDueDate
if (params.maxDueDate == 0) {
revert Errors.NULL_MAX_DUE_DATE();
}
if (params.maxDueDate < block.timestamp + state.riskConfig.minTenor) {
revert Errors.PAST_MAX_DUE_DATE(params.maxDueDate);
}

// validate curveRelativeTime
YieldCurveLibrary.validateYieldCurve(
params.curveRelativeTime, state.riskConfig.minTenor, state.riskConfig.maxTenor
);
// validate loanOffer
loanOffer.validateLimitOrder(state.riskConfig.minTenor, state.riskConfig.maxTenor);
}
}

Expand All @@ -56,7 +45,7 @@ library BuyCreditLimit {
/// @dev A null offer means clearing a user's loan limit order
function executeBuyCreditLimit(State storage state, BuyCreditLimitParams calldata params) external {
state.data.users[msg.sender].loanOffer =
LoanOffer({maxDueDate: params.maxDueDate, curveRelativeTime: params.curveRelativeTime});
LimitOrder({maxDueDate: params.maxDueDate, curveRelativeTime: params.curveRelativeTime});
emit Events.BuyCreditLimit(
params.maxDueDate,
params.curveRelativeTime.tenors,
Expand Down
11 changes: 8 additions & 3 deletions src/libraries/actions/BuyCreditMarket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {Errors} from "@src/libraries/Errors.sol";
import {Events} from "@src/libraries/Events.sol";
import {CreditPosition, DebtPosition, LoanLibrary, RESERVED_ID} from "@src/libraries/LoanLibrary.sol";
import {Math, PERCENT} from "@src/libraries/Math.sol";
import {BorrowOffer, OfferLibrary} from "@src/libraries/OfferLibrary.sol";
import {LimitOrder, OfferLibrary} from "@src/libraries/OfferLibrary.sol";

import {RiskLibrary} from "@src/libraries/RiskLibrary.sol";
import {VariablePoolBorrowRateParams} from "@src/libraries/YieldCurveLibrary.sol";
Expand Down Expand Up @@ -38,7 +38,7 @@ struct BuyCreditMarketParams {
/// @author Size (https://size.credit/)
/// @notice Contains the logic for buying credit (lending) as a market order
library BuyCreditMarket {
using OfferLibrary for BorrowOffer;
using OfferLibrary for LimitOrder;
using AccountingLibrary for State;
using LoanLibrary for State;
using LoanLibrary for DebtPosition;
Expand Down Expand Up @@ -80,7 +80,7 @@ library BuyCreditMarket {
tenor = debtPosition.dueDate - block.timestamp; // positive since the credit position is transferrable, so the loan must be ACTIVE
}

BorrowOffer memory borrowOffer = state.data.users[borrower].borrowOffer;
LimitOrder memory borrowOffer = state.data.users[borrower].borrowOffer;

// validate borrower
if (borrowOffer.isNull()) {
Expand All @@ -92,6 +92,11 @@ library BuyCreditMarket {
revert Errors.CREDIT_LOWER_THAN_MINIMUM_CREDIT(params.amount, state.riskConfig.minimumCreditBorrowAToken);
}

// validate tenor
if (block.timestamp + tenor > borrowOffer.maxDueDate) {
revert Errors.DUE_DATE_GREATER_THAN_MAX_DUE_DATE(block.timestamp + tenor, borrowOffer.maxDueDate);
}

// validate deadline
if (params.deadline < block.timestamp) {
revert Errors.PAST_DEADLINE(params.deadline);
Expand Down
19 changes: 11 additions & 8 deletions src/libraries/actions/LiquidateWithReplacement.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {Math} from "@src/libraries/Math.sol";
import {PERCENT} from "@src/libraries/Math.sol";

import {CreditPosition, DebtPosition, LoanLibrary, LoanStatus} from "@src/libraries/LoanLibrary.sol";
import {BorrowOffer, OfferLibrary} from "@src/libraries/OfferLibrary.sol";
import {LimitOrder, OfferLibrary} from "@src/libraries/OfferLibrary.sol";
import {VariablePoolBorrowRateParams} from "@src/libraries/YieldCurveLibrary.sol";

import {State} from "@src/SizeStorage.sol";
Expand Down Expand Up @@ -35,7 +35,7 @@ struct LiquidateWithReplacementParams {
/// @notice Contains the logic for liquidating a debt position with a replacement borrower
library LiquidateWithReplacement {
using LoanLibrary for CreditPosition;
using OfferLibrary for BorrowOffer;
using OfferLibrary for LimitOrder;

using LoanLibrary for State;
using Liquidate for State;
Expand All @@ -49,7 +49,7 @@ library LiquidateWithReplacement {
view
{
DebtPosition storage debtPosition = state.getDebtPosition(params.debtPositionId);
BorrowOffer storage borrowOffer = state.data.users[params.borrower].borrowOffer;
LimitOrder storage borrowOffer = state.data.users[params.borrower].borrowOffer;

// validate liquidate
state.validateLiquidate(
Expand All @@ -59,6 +59,11 @@ library LiquidateWithReplacement {
})
);

// validate borrower
if (borrowOffer.isNull()) {
revert Errors.INVALID_BORROW_OFFER(params.borrower);
}

// validate debtPositionId
if (state.getLoanStatus(params.debtPositionId) != LoanStatus.ACTIVE) {
revert Errors.LOAN_NOT_ACTIVE(params.debtPositionId);
Expand All @@ -67,10 +72,8 @@ library LiquidateWithReplacement {
if (tenor < state.riskConfig.minTenor || tenor > state.riskConfig.maxTenor) {
revert Errors.TENOR_OUT_OF_RANGE(tenor, state.riskConfig.minTenor, state.riskConfig.maxTenor);
}

// validate borrower
if (borrowOffer.isNull()) {
revert Errors.INVALID_BORROW_OFFER(params.borrower);
if (block.timestamp + tenor > borrowOffer.maxDueDate) {
revert Errors.DUE_DATE_GREATER_THAN_MAX_DUE_DATE(block.timestamp + tenor, borrowOffer.maxDueDate);
}

// validate deadline
Expand Down Expand Up @@ -125,7 +128,7 @@ library LiquidateWithReplacement {

DebtPosition storage debtPosition = state.getDebtPosition(params.debtPositionId);
DebtPosition memory debtPositionCopy = debtPosition;
BorrowOffer storage borrowOffer = state.data.users[params.borrower].borrowOffer;
LimitOrder storage borrowOffer = state.data.users[params.borrower].borrowOffer;
uint256 tenor = debtPositionCopy.dueDate - block.timestamp;

liquidatorProfitCollateralToken = state.executeLiquidate(
Expand Down
Loading
Loading