Skip to content

Commit

Permalink
feat: add discount loan function (#50)
Browse files Browse the repository at this point in the history
* feat: implement `discountLoanBatch` function
* test: add `discountLoanBatch` function tests
* feat: update preview structs
* fix: reorder fields of the loan preview structure
* test: fix a bug in new tests
* test: fix formatting issues in tests
* fix: minor formatting issues and reorder codee lines
* fix: a minor formatting issue
* feat: rename fields in `LoanDiscounted` event
* feat: rename `discountLoanBatch` -> `discountLoanForBatch`
* feat: update contracts version to `v1.7.0`
---------
Co-authored-by: Evgenii Zaitsev <[email protected]>
  • Loading branch information
igorsenych-cw authored Jan 3, 2025
1 parent 7cfefaa commit 73e9569
Show file tree
Hide file tree
Showing 8 changed files with 332 additions and 7 deletions.
61 changes: 61 additions & 0 deletions contracts/LendingMarket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,24 @@ contract LendingMarket is
}
}

/// @inheritdoc ILendingMarket
function discountLoanForBatch(
uint256[] calldata loanIds, // Tools: this comment prevents Prettier from formatting into a single line.
uint256[] calldata discountAmounts
) external whenNotPaused {
uint256 len = loanIds.length;
if (len != discountAmounts.length) {
revert Error.ArrayLengthMismatch();
}
for (uint256 i = 0; i < len; ++i) {
uint256 loanId = loanIds[i];
Loan.State storage loan = _loans[loanId];
_checkIfLoanOngoing(loanId);
_checkSender(msg.sender, loan.programId);
_discountLoan(loanId, loan, discountAmounts[i]);
}
}

/// @dev Updates the loan state and makes the necessary transfers when repaying a loan.
/// @param loanId The unique identifier of the loan to repay.
/// @param repaymentAmount The amount to repay.
Expand Down Expand Up @@ -388,6 +406,47 @@ contract LendingMarket is
emit LoanRepayment(loanId, repayer, loan.borrower, repaymentAmount, newOutstandingBalance);
}

/// @dev Discounts a loan.
/// @param loanId The unique identifier of the loan to discount.
/// @param loan The storage state of the loan.
/// @param discountAmount The amount of the discount.
function _discountLoan(
uint256 loanId, // Tools: this comment prevents Prettier from formatting into a single line.
Loan.State storage loan,
uint256 discountAmount
) internal {
if (discountAmount == 0) {
revert Error.InvalidAmount();
}

(uint256 oldOutstandingBalance, uint256 lateFeeAmount, ) = _outstandingBalance(loan, _blockTimestamp());
uint256 roundedOutstandingBalance = Rounding.roundMath(oldOutstandingBalance, Constants.ACCURACY_FACTOR);
uint256 newOutstandingBalance = 0; // Full discount by default

if (discountAmount == type(uint256).max) {
discountAmount = roundedOutstandingBalance;
} else {
if (discountAmount != Rounding.roundMath(discountAmount, Constants.ACCURACY_FACTOR)) {
revert Error.InvalidAmount();
}
if (discountAmount > roundedOutstandingBalance) {
revert Error.InvalidAmount();
}
// Not a full discount
if (discountAmount < roundedOutstandingBalance) {
newOutstandingBalance = oldOutstandingBalance - discountAmount;
}
// Else full discount
}

loan.discountAmount += discountAmount.toUint64();
loan.trackedBalance = newOutstandingBalance.toUint64();
loan.trackedTimestamp = _blockTimestamp().toUint32();
_updateStoredLateFee(lateFeeAmount, loan);

emit LoanDiscounted(loanId, discountAmount, newOutstandingBalance);
}

// -------------------------------------------- //
// Lender functions //
// -------------------------------------------- //
Expand Down Expand Up @@ -1036,6 +1095,7 @@ contract LendingMarket is
preview.addonAmount = loan.addonAmount;
preview.repaidAmount = loan.repaidAmount;
preview.lateFeeAmount += loan.lateFeeAmount;
preview.discountAmount = loan.discountAmount;
preview.programId = loan.programId;
preview.borrower = loan.borrower;
preview.previewTimestamp = timestamp;
Expand Down Expand Up @@ -1084,6 +1144,7 @@ contract LendingMarket is
preview.totalAddonAmount += singleLoanPreview.addonAmount;
preview.totalRepaidAmount += singleLoanPreview.repaidAmount;
preview.totalLateFeeAmount += singleLoanPreview.lateFeeAmount;
preview.totalDiscountAmount += singleLoanPreview.discountAmount;
preview.installmentPreviews[i] = singleLoanPreview;
++loanId;
}
Expand Down
2 changes: 1 addition & 1 deletion contracts/common/Versionable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ abstract contract Versionable is IVersionable {
* @inheritdoc IVersionable
*/
function $__VERSION() external pure returns (Version memory) {
return Version(1, 6, 0);
return Version(1, 7, 0);
}
}
18 changes: 18 additions & 0 deletions contracts/common/interfaces/core/ILendingMarket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ interface ILendingMarket {
/// @param loanId The unique identifier of the loan.
event LoanRevoked(uint256 indexed loanId);

/// @dev Emitted when a loan is discounted.
/// @param loanId The unique identifier of the loan.
/// @param discountAmount The amount of the discount.
/// @param newTrackedBalance The new tracked balance of the loan after the discount.
event LoanDiscounted(
uint256 indexed loanId, // Tools: this comment prevents Prettier from formatting into a single line.
uint256 discountAmount,
uint256 newTrackedBalance
);

/// @dev Emitted when an installment loan is revoked.
/// @param firstInstallmentId The ID of the first sub-loan of the installment loan.
/// @param installmentCount The total number of installments.
Expand Down Expand Up @@ -234,6 +244,14 @@ interface ILendingMarket {
address repayer
) external;

/// @dev Discounts a batch of loans.
/// @param loanIds The unique identifiers of the loans to discount.
/// @param discountAmounts The amounts to discount for each loan.
function discountLoanForBatch(
uint256[] calldata loanIds, // Tools: this comment prevents Prettier from formatting into a single line.
uint256[] calldata discountAmounts
) external;

// -------------------------------------------- //
// Lender functions //
// -------------------------------------------- //
Expand Down
6 changes: 6 additions & 0 deletions contracts/common/libraries/Loan.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ library Loan {
// uint16 __reserved; // Reserved for future use.
// Slot 5
uint64 lateFeeAmount; // The late fee amount of the loan or zero if the loan is not defaulted.
uint64 discountAmount; // The discount amount of the loan or zero if the loan is not discounted.
}

/// @dev A struct that defines the terms of a loan.
Expand Down Expand Up @@ -73,6 +74,7 @@ library Loan {
/// - addonAmount ------------ The addon amount of the loan at the previewed period.
/// - repaidAmount ----------- The repaid amount of the loan at the previewed period.
/// - lateFeeAmount ---------- The late fee amount of the loan at the previewed period.
/// - discountAmount --------- The discount amount of the loan at the previewed period.
/// - programId -------------- The program ID of the loan.
/// - borrower --------------- The borrower of the loan.
/// - previewTimestamp ------- The preview timestamp.
Expand All @@ -92,6 +94,7 @@ library Loan {
uint256 addonAmount;
uint256 repaidAmount;
uint256 lateFeeAmount;
uint256 discountAmount;
uint256 programId;
address borrower;
uint256 previewTimestamp;
Expand Down Expand Up @@ -121,6 +124,7 @@ library Loan {
/// - totalRepaidAmount -------- The total repaid amount of all installments.
/// - totalLateFeeAmount ------- The total late fee amount of all installments.
/// - installmentPreviews ------ The extended previews of all installments.
/// - totalDiscountAmount ------ The total discount amount of all installments.
///
/// The purpose of the fields in the case of ordinary loans:
///
Expand All @@ -134,6 +138,7 @@ library Loan {
/// - totalRepaidAmount -------- The repaid amount of the loan.
/// - totalLateFeeAmount ------- The late fee amount of the loan.
/// - installmentPreviews ------ The extended preview of the loan as a single item array.
/// - totalDiscountAmount ------ The total discount amount of the loan.

/// Notes:
///
Expand All @@ -150,6 +155,7 @@ library Loan {
uint256 totalAddonAmount;
uint256 totalRepaidAmount;
uint256 totalLateFeeAmount;
uint256 totalDiscountAmount;
PreviewExtended[] installmentPreviews;
}
}
9 changes: 9 additions & 0 deletions contracts/mocks/LendingMarketMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,15 @@ contract LendingMarketMock is ILendingMarket {
revert Error.NotImplemented();
}

function discountLoanForBatch(
uint256[] calldata loanIds,
uint256[] calldata discountAmounts
) external pure {
loanIds; // To prevent compiler warning about unused variable
discountAmounts; // To prevent compiler warning about unused variable
revert Error.NotImplemented();
}

function freeze(uint256 loanId) external pure {
loanId; // To prevent compiler warning about unused variable
revert Error.NotImplemented();
Expand Down
Loading

0 comments on commit 73e9569

Please sign in to comment.