From e03ebca4c4b17ef58b767415be8b915eac8495f3 Mon Sep 17 00:00:00 2001 From: vbidin Date: Sat, 14 Jan 2023 00:32:27 +0200 Subject: [PATCH 01/47] feat: Add GitBook link (#261) --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5cf927b..a038bb1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # Maple Loan +![Foundry CI](https://github.com/maple-labs/loan-private/actions/workflows/forge.yml/badge.svg) +[![GitBook - Documentation](https://img.shields.io/badge/GitBook-Documentation-orange?logo=gitbook&logoColor=white)](https://maplefinance.gitbook.io/maple/maple-for-developers/protocol-overview) [![Foundry][foundry-badge]][foundry] -![Foundry CI](https://github.com/maple-labs/loan/actions/workflows/forge.yml/badge.svg) +[![License: BUSL 1.1](https://img.shields.io/badge/License-BUSL%201.1-blue.svg)](https://github.com/maple-labs/loan-private/blob/main/LICENSE) [foundry]: https://getfoundry.sh/ [foundry-badge]: https://img.shields.io/badge/Built%20with-Foundry-FFDB1C.svg @@ -38,8 +40,8 @@ Versions of dependencies can be checked with `git submodule status`. This project was built using [Foundry](https://book.getfoundry.sh/). Refer to installation instructions [here](https://github.com/foundry-rs/foundry#installation). ```sh -git clone git@github.com:maple-labs/loan.git -cd loan +git clone git@github.com:maple-labs/loan-private.git +cd loan-private forge install ``` @@ -66,9 +68,8 @@ forge install For all information related to the ongoing bug bounty for these contracts run by [Immunefi](https://immunefi.com/), please visit this [site](https://immunefi.com/bounty/maple/). ## About Maple -[Maple Finance](https://maple.finance) is a decentralized corporate credit market. Maple provides capital to institutional borrowers through globally accessible fixed-income yield opportunities. -For all technical documentation related to the currently deployed Maple protocol, please refer to the maple-core GitHub [wiki](https://github.com/maple-labs/maple-core-v2/wiki). +[Maple Finance](https://maple.finance) is a decentralized corporate credit market. Maple provides capital to institutional borrowers through globally accessible fixed-income yield opportunities. --- From ded7e67a05428e9415cb520dbec630dd06c2e376 Mon Sep 17 00:00:00 2001 From: Michael De Luca <35537333+deluca-mike@users.noreply.github.com> Date: Sun, 15 Jan 2023 17:16:47 -0500 Subject: [PATCH 02/47] fix: Audit Reports Table (#262) --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a038bb1..99efa16 100644 --- a/README.md +++ b/README.md @@ -53,15 +53,15 @@ forge install ## Audit Reports -| Auditor | Report Link | -|---|---| -| Trail of Bits - LoanV2 | [`2021-12-28 - Trail of Bits Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-core/files/7847684/Maple.Finance.-.Final.Report_v3.pdf) | -| Code 4rena - LoanV2 | [`2022-01-05 - C4 Report`](https://code4rena.com/reports/2021-12-maple/) | -| Trail of Bits - LoanV3 | [`2022-04-12 - Trail of Bits Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-core/files/8507237/Maple.Finance.-.Final.Report.-.Fixes.pdf) | -| Code 4rena - LoanV3 | [`2022-04-20 - C4 Report`](https://code4rena.com/reports/2022-03-maple/) | -| Trail of Bits | [`2022-08-24 - Trail of Bits Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/10246688/Maple.Finance.v2.-.Final.Report.-.Fixed.-.2022.pdf) | -| Spearbit | [`2022-10-17 - Spearbit Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/10223545/Maple.Finance.v2.-.Spearbit.pdf) | -| Three Sigma | [`2022-10-24 - Three Sigma Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/10223541/three-sigma_maple-finance_code-audit_v1.1.1.pdf) | +| Release Version | Auditor | Report Link | Date | +|---|---|---|---| +| v2.0.0 | Trail of Bits | [`Trail of Bits Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-core/files/7847684/Maple.Finance.-.Final.Report_v3.pdf) | 2021-12-28 | +| v2.0.0 | Code 4rena | [`C4 Report`](https://code4rena.com/reports/2021-12-maple/) | 2022-01-05 | +| v3.0.0 | Trail of Bits | [`Trail of Bits Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-core/files/8507237/Maple.Finance.-.Final.Report.-.Fixes.pdf) | 2022-04-12 | +| v3.0.0 | Code 4rena | [`C4 Report`](https://code4rena.com/reports/2022-03-maple/) | 2022-04-20 | +| v3.0.1 - v4.0.0 | Trail of Bits | [`Trail of Bits Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/10246688/Maple.Finance.v2.-.Final.Report.-.Fixed.-.2022.pdf) | 2022-08-24 | +| v3.0.1 - v4.0.0 | Spearbit | [`Spearbit Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/10223545/Maple.Finance.v2.-.Spearbit.pdf) | 2022-10-17 | +| v3.0.1 - v4.0.0 | Three Sigma | [`Three Sigma Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/10223541/three-sigma_maple-finance_code-audit_v1.1.1.pdf) | 2022-10-24 | ## Bug Bounty From ed0a409aecfc30763ad724c3f05a394e3b8f2e0a Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Tue, 24 Jan 2023 16:46:06 +0000 Subject: [PATCH 03/47] feat: Loan add `_isCollateralMaintained` check to makePayment (SC-11034) (#265) * feat: add isCollateralMaintained check in makePayment() * feat: Add test for collateralNotMaintained for makePayment * feat: Update test based on JGs suggestion - Expose collateral not maintained check when using drawable funds only * fix: Update based on PR comments * feat: Update test to include collateralAsset and calc for collateral requirement * refactor: Move mint of collateral asset --- contracts/MapleLoan.sol | 2 ++ tests/MapleLoanLogic.t.sol | 52 +++++++++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index ef449f7..3fd7bea 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -197,6 +197,8 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { ILenderLike(_lender).claim(principal_, interest_, previousPaymentDueDate_, nextPaymentDueDate_); emit FundsClaimed(principalAndInterest, _lender); + + require(_isCollateralMaintained(), "ML:MP:INSUFFICIENT_COLLATERAL"); } function postCollateral(uint256 amount_) public override whenProtocolNotPaused returns (uint256 collateralPosted_) { diff --git a/tests/MapleLoanLogic.t.sol b/tests/MapleLoanLogic.t.sol index 94f80b4..e7f1477 100644 --- a/tests/MapleLoanLogic.t.sol +++ b/tests/MapleLoanLogic.t.sol @@ -1761,15 +1761,17 @@ contract MapleLoanLogic_MakePaymentTests is TestUtils { MapleGlobalsMock internal globals; MapleLoanHarness internal loan; + MockERC20 internal collateralAsset; MockERC20 internal fundsAsset; MockFactory internal factory; MockFeeManager internal feeManager; function setUp() external { - feeManager = new MockFeeManager(); - fundsAsset = new MockERC20("FundsAsset", "FA", 0); - lender = address(new MockLoanManager()); - loan = new MapleLoanHarness(); + collateralAsset = new MockERC20("collateralAsset", "CA", 0); + feeManager = new MockFeeManager(); + fundsAsset = new MockERC20("FundsAsset", "FA", 0); + lender = address(new MockLoanManager()); + loan = new MapleLoanHarness(); globals = new MapleGlobalsMock(governor, MockLoanManager(lender).factory()); @@ -2074,6 +2076,48 @@ contract MapleLoanLogic_MakePaymentTests is TestUtils { assertEq(loan.drawableFunds(), 0); } + function test_makePayment_collateralNotMaintained() external { + setupLoan(address(loan), 1_000_000, 2, 365 days, 0.1e18, 1_000_000); + + // Need to set fees because if principal = drawableFunds the collateral required is 0 + feeManager.__setDelegateServiceFee(100); + feeManager.__setPlatformServiceFee(100); + feeManager.__setServiceFeesToPay(200); + + vm.prank(address(loan)); + fundsAsset.approve(address(feeManager), type(uint256).max); + + uint256 collateralRequired = 1_000; + + loan.__setCollateralAsset(address(collateralAsset)); + loan.__setCollateralRequired(collateralRequired); + + ( , uint256 interest, uint256 fees ) = loan.getNextPaymentBreakdown(); + + vm.startPrank(borrower); + vm.expectRevert("ML:MP:INSUFFICIENT_COLLATERAL"); + loan.makePayment(0); + + // _getCollateralRequiredFor() = (collateralRequired_ * (principal_ - drawableFunds_) + principalRequested_ - 1) / principalRequested_; + uint256 getCollateralRequiredFor = (1_000 * (1_000_000 - (1_000_000 - fees - interest)) + 1_000_000 - 1) / 1_000_000; + + assertEq(getCollateralRequiredFor, 101); + + collateralAsset.mint(borrower, getCollateralRequiredFor); + collateralAsset.approve(address(loan), getCollateralRequiredFor); + + loan.postCollateral(getCollateralRequiredFor - 1); + + vm.expectRevert("ML:MP:INSUFFICIENT_COLLATERAL"); + loan.makePayment(0); + + loan.postCollateral(1); + + loan.makePayment(0); + + assertEq(loan.drawableFunds(), 1_000_000 - fees - interest); + } + } contract MapleLoanLogic_PostCollateralTests is TestUtils { From 3e3c6bfc70780b4fa64b12f6bf6b0abc03914914 Mon Sep 17 00:00:00 2001 From: vbidin Date: Wed, 25 Jan 2023 04:40:14 +0200 Subject: [PATCH 04/47] feat: Add support for pool specific loan funding (SC-11033) (#267) * feat: set lender during initialization * fix: update globals mock * fix: update mocks and add missing tests * fix: address all pr comments * fix: update spacing Co-authored-by: lucas-manuel --- contracts/MapleLoan.sol | 12 +- contracts/MapleLoanInitializer.sol | 22 ++- contracts/interfaces/IMapleLoan.sol | 3 +- contracts/interfaces/IMapleLoanEvents.sol | 4 +- .../interfaces/IMapleLoanInitializer.sol | 8 +- tests/MapleLoan.t.sol | 51 +++-- tests/MapleLoanFactory.t.sol | 115 +++++++++++- tests/MapleLoanFeeManager.t.sol | 87 +++++---- tests/MapleLoanLogic.t.sol | 176 ++++++++++-------- tests/MapleLoanScenarios.t.sol | 9 +- tests/Payments.t.sol | 6 +- tests/Refinancer.t.sol | 53 +++--- tests/harnesses/MapleLoanHarnesses.sol | 3 +- tests/mocks/Mocks.sol | 36 +++- 14 files changed, 371 insertions(+), 214 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index 3fd7bea..21230ab 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -338,16 +338,12 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { require(getUnaccountedAmount(fundsAsset_) == uint256(0), "ML:ANT:UNEXPECTED_FUNDS"); } - function fundLoan(address lender_) external override returns (uint256 fundsLent_) { - require((_lender = lender_) != address(0), "ML:FL:INVALID_LENDER"); + function fundLoan() external override returns (uint256 fundsLent_) { + address lender_ = _lender; - address loanManagerFactory_ = ILenderLike(lender_).factory(); + require(msg.sender == lender_, "ML:FL:NOT_LENDER"); - require(IMapleGlobalsLike(globals()).isFactory("LOAN_MANAGER", loanManagerFactory_), "ML:FL:INVALID_FACTORY"); - require(IMapleProxyFactoryLike(loanManagerFactory_).isInstance(lender_), "ML:FL:INVALID_INSTANCE"); - - // Can only fund loan if there are payments remaining (as defined by the initialization) - // and no payment is due yet (as set by a funding). + // Can only fund loan if there are payments remaining (as defined by the initialization) and no payment is due yet (as set by a funding). require((_nextPaymentDueDate == uint256(0)) && (_paymentsRemaining != uint256(0)), "ML:FL:LOAN_ACTIVE"); address fundsAsset_ = _fundsAsset; diff --git a/contracts/MapleLoanInitializer.sol b/contracts/MapleLoanInitializer.sol index 5712f12..143a1af 100644 --- a/contracts/MapleLoanInitializer.sol +++ b/contracts/MapleLoanInitializer.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.7; import { IMapleLoanInitializer } from "./interfaces/IMapleLoanInitializer.sol"; import { IMapleLoanFeeManager } from "./interfaces/IMapleLoanFeeManager.sol"; -import { IMapleGlobalsLike, IMapleProxyFactoryLike } from "./interfaces/Interfaces.sol"; +import { ILenderLike, IMapleGlobalsLike, IMapleProxyFactoryLike } from "./interfaces/Interfaces.sol"; import { MapleLoanStorage } from "./MapleLoanStorage.sol"; @@ -12,6 +12,7 @@ contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { function encodeArguments( address borrower_, + address lender_, address feeManager_, address[2] memory assets_, uint256[3] memory termDetails_, @@ -19,12 +20,13 @@ contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { uint256[4] memory rates_, uint256[2] memory fees_ ) external pure override returns (bytes memory encodedArguments_) { - return abi.encode(borrower_, feeManager_, assets_, termDetails_, amounts_, rates_, fees_); + return abi.encode(borrower_, lender_, feeManager_, assets_, termDetails_, amounts_, rates_, fees_); } function decodeArguments(bytes calldata encodedArguments_) public pure override returns ( address borrower_, + address lender_, address feeManager_, address[2] memory assets_, uint256[3] memory termDetails_, @@ -35,18 +37,20 @@ contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { { ( borrower_, + lender_, feeManager_, assets_, termDetails_, amounts_, rates_, fees_ - ) = abi.decode(encodedArguments_, (address, address, address[2], uint256[3], uint256[3], uint256[4], uint256[2])); + ) = abi.decode(encodedArguments_, (address, address, address, address[2], uint256[3], uint256[3], uint256[4], uint256[2])); } fallback() external { ( address borrower_, + address lender_, address feeManager_, address[2] memory assets_, uint256[3] memory termDetails_, @@ -55,9 +59,9 @@ contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { uint256[2] memory fees_ ) = decodeArguments(msg.data); - _initialize(borrower_, feeManager_, assets_, termDetails_, amounts_, rates_, fees_); + _initialize(borrower_, lender_, feeManager_, assets_, termDetails_, amounts_, rates_, fees_); - emit Initialized(borrower_, feeManager_, assets_, termDetails_, amounts_, rates_, fees_); + emit Initialized(borrower_, lender_, feeManager_, assets_, termDetails_, amounts_, rates_, fees_); } /** @@ -86,6 +90,7 @@ contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { */ function _initialize( address borrower_, + address lender_, address feeManager_, address[2] memory assets_, uint256[3] memory termDetails_, @@ -112,6 +117,13 @@ contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { require(IMapleGlobalsLike(globals_).isPoolAsset(assets_[1]), "MLI:I:INVALID_FUNDS_ASSET"); require(IMapleGlobalsLike(globals_).isCollateralAsset(assets_[0]), "MLI:I:INVALID_COLLATERAL_ASSET"); + require((_lender = lender_) != address(0), "MLI:I:ZERO_LENDER"); + + address loanManagerFactory_ = ILenderLike(lender_).factory(); + + require(IMapleGlobalsLike(globals_).isFactory("LOAN_MANAGER", loanManagerFactory_), "MLI:I:INVALID_FACTORY"); + require(IMapleProxyFactoryLike(loanManagerFactory_).isInstance(lender_), "MLI:I:INVALID_INSTANCE"); + require((_feeManager = feeManager_) != address(0), "MLI:I:INVALID_MANAGER"); _collateralAsset = assets_[0]; diff --git a/contracts/interfaces/IMapleLoan.sol b/contracts/interfaces/IMapleLoan.sol index a481ae6..3a5ac09 100644 --- a/contracts/interfaces/IMapleLoan.sol +++ b/contracts/interfaces/IMapleLoan.sol @@ -190,10 +190,9 @@ interface IMapleLoan is IMapleProxied, IMapleLoanEvents { /** * @dev Lend funds to the loan/borrower. - * @param lender_ The address to be registered as the lender. * @return fundsLent_ The amount funded. */ - function fundLoan(address lender_) external returns (uint256 fundsLent_); + function fundLoan() external returns (uint256 fundsLent_); /** * @dev Make a payment to the loan. diff --git a/contracts/interfaces/IMapleLoanEvents.sol b/contracts/interfaces/IMapleLoanEvents.sol index ffde4d3..84f673a 100644 --- a/contracts/interfaces/IMapleLoanEvents.sol +++ b/contracts/interfaces/IMapleLoanEvents.sol @@ -54,7 +54,8 @@ interface IMapleLoanEvents { /** * @dev Loan was initialized. * @param borrower_ The address of the borrower. - * @param feeManager_ The address of the entity responsible for calculating fees + * @param lender_ The address of the lender. + * @param feeManager_ The address of the entity responsible for calculating fees. * @param assets_ Array of asset addresses. * [0]: collateralAsset, * [1]: fundsAsset. @@ -77,6 +78,7 @@ interface IMapleLoanEvents { */ event Initialized( address indexed borrower_, + address indexed lender_, address indexed feeManager_, address[2] assets_, uint256[3] termDetails_, diff --git a/contracts/interfaces/IMapleLoanInitializer.sol b/contracts/interfaces/IMapleLoanInitializer.sol index 2ffec9e..d509bcc 100644 --- a/contracts/interfaces/IMapleLoanInitializer.sol +++ b/contracts/interfaces/IMapleLoanInitializer.sol @@ -8,7 +8,8 @@ interface IMapleLoanInitializer is IMapleLoanEvents { /** * @dev Encodes the initialization arguments for a MapleLoan. * @param borrower_ The address of the borrower. - * @param feeManager_ The address of the entity responsible for calculating fees + * @param lender_ The address of the lender. + * @param feeManager_ The address of the entity responsible for calculating fees. * @param assets_ Array of asset addresses. * [0]: collateralAsset, * [1]: fundsAsset @@ -31,6 +32,7 @@ interface IMapleLoanInitializer is IMapleLoanEvents { */ function encodeArguments( address borrower_, + address lender_, address feeManager_, address[2] memory assets_, uint256[3] memory termDetails_, @@ -42,7 +44,8 @@ interface IMapleLoanInitializer is IMapleLoanEvents { /** * @dev Decodes the initialization arguments for a MapleLoan. * @return borrower_ The address of the borrower. - * @return feeManager_ The address of the entity responsible for calculating fees + * @return lender_ The address of the lender. + * @return feeManager_ The address of the entity responsible for calculating fees. * @return assets_ Array of asset addresses. * [0]: collateralAsset, * [1]: fundsAsset @@ -66,6 +69,7 @@ interface IMapleLoanInitializer is IMapleLoanEvents { function decodeArguments(bytes calldata encodedArguments_) external pure returns ( address borrower_, + address lender_, address feeManager_, address[2] memory assets_, uint256[3] memory termDetails_, diff --git a/tests/MapleLoan.t.sol b/tests/MapleLoan.t.sol index e1c99cc..dc5101a 100644 --- a/tests/MapleLoan.t.sol +++ b/tests/MapleLoan.t.sol @@ -27,9 +27,7 @@ contract MapleLoanTests is TestUtils { feeManager = new MockFeeManager(); lender = address(new MockLoanManager()); loan = new MapleLoanHarness(); - - globals = new MapleGlobalsMock(governor, MockLoanManager(lender).factory()); - + globals = new MapleGlobalsMock(governor); factoryMock = new MockFactory(address(globals)); loan.__setBorrower(borrower); @@ -562,15 +560,17 @@ contract MapleLoanTests is TestUtils { loan.__setPrincipalRequested(amount); // Fails without pushing funds + vm.prank(lender); vm.expectRevert(ARITHMETIC_ERROR); - loan.fundLoan(lender); + loan.fundLoan(); fundsAsset.mint(address(loan), amount); assertEq(fundsAsset.balanceOf(address(loan)), amount); assertEq(loan.principal(), 0); - loan.fundLoan(lender); + vm.prank(lender); + loan.fundLoan(); assertEq(fundsAsset.balanceOf(address(loan)), amount); assertEq(loan.principal(), amount); @@ -704,7 +704,7 @@ contract MapleLoanTests is TestUtils { fundsAsset.mint(address(borrower), amount); assertEq(fundsAsset.balanceOf(address(borrower)), amount); - assertEq(fundsAsset.balanceOf(address(lender)), 0); + assertEq(fundsAsset.balanceOf(lender), 0); assertEq(loan.principal(), amount); vm.startPrank(borrower); @@ -716,7 +716,7 @@ contract MapleLoanTests is TestUtils { loan.closeLoan(amount); assertEq(fundsAsset.balanceOf(address(borrower)), 0); - assertEq(fundsAsset.balanceOf(address(lender)), amount); + assertEq(fundsAsset.balanceOf(lender), amount); assertEq(loan.paymentsRemaining(), 0); assertEq(loan.principal(), 0); } @@ -735,7 +735,7 @@ contract MapleLoanTests is TestUtils { fundsAsset.mint(address(borrower), amount); assertEq(fundsAsset.balanceOf(address(borrower)), amount); - assertEq(fundsAsset.balanceOf(address(lender)), 0); + assertEq(fundsAsset.balanceOf(lender), 0); assertEq(loan.principal(), amount); vm.startPrank(borrower); @@ -747,7 +747,7 @@ contract MapleLoanTests is TestUtils { loan.closeLoan(0); assertEq(fundsAsset.balanceOf(address(borrower)), 0); - assertEq(fundsAsset.balanceOf(address(lender)), amount); + assertEq(fundsAsset.balanceOf(lender), amount); assertEq(loan.paymentsRemaining(), 0); assertEq(loan.principal(), 0); } @@ -765,7 +765,7 @@ contract MapleLoanTests is TestUtils { fundsAsset.mint(address(user), amount); assertEq(fundsAsset.balanceOf(address(user)), amount); - assertEq(fundsAsset.balanceOf(address(lender)), 0); + assertEq(fundsAsset.balanceOf(lender), 0); assertEq(loan.principal(), amount); vm.startPrank(user); @@ -777,7 +777,7 @@ contract MapleLoanTests is TestUtils { loan.closeLoan(amount); assertEq(fundsAsset.balanceOf(address(user)), 0); - assertEq(fundsAsset.balanceOf(address(lender)), amount); + assertEq(fundsAsset.balanceOf(lender), amount); assertEq(loan.paymentsRemaining(), 0); assertEq(loan.principal(), 0); } @@ -795,7 +795,7 @@ contract MapleLoanTests is TestUtils { fundsAsset.mint(address(user), amount); assertEq(fundsAsset.balanceOf(address(user)), amount); - assertEq(fundsAsset.balanceOf(address(lender)), 0); + assertEq(fundsAsset.balanceOf(lender), 0); assertEq(loan.principal(), amount); vm.startPrank(user); @@ -807,7 +807,7 @@ contract MapleLoanTests is TestUtils { loan.closeLoan(0); assertEq(fundsAsset.balanceOf(address(user)), 0); - assertEq(fundsAsset.balanceOf(address(lender)), amount); + assertEq(fundsAsset.balanceOf(lender), amount); assertEq(loan.paymentsRemaining(), 0); assertEq(loan.principal(), 0); } @@ -896,7 +896,7 @@ contract MapleLoanTests is TestUtils { fundsAsset.mint(address(borrower), totalPayment); assertEq(fundsAsset.balanceOf(address(borrower)), totalPayment); - assertEq(fundsAsset.balanceOf(address(lender)), 0); + assertEq(fundsAsset.balanceOf(lender), 0); assertEq(loan.paymentsRemaining(), 3); assertEq(loan.principal(), startingPrincipal); @@ -909,7 +909,7 @@ contract MapleLoanTests is TestUtils { loan.makePayment(totalPayment); assertEq(fundsAsset.balanceOf(address(borrower)), 0); - assertEq(fundsAsset.balanceOf(address(lender)), totalPayment); + assertEq(fundsAsset.balanceOf(lender), totalPayment); assertEq(loan.paymentsRemaining(), 2); assertEq(loan.principal(), startingPrincipal - principal); } @@ -931,7 +931,7 @@ contract MapleLoanTests is TestUtils { fundsAsset.mint(address(borrower), totalPayment); assertEq(fundsAsset.balanceOf(address(borrower)), totalPayment); - assertEq(fundsAsset.balanceOf(address(lender)), 0); + assertEq(fundsAsset.balanceOf(lender), 0); assertEq(loan.paymentsRemaining(), 3); assertEq(loan.principal(), startingPrincipal); @@ -944,7 +944,7 @@ contract MapleLoanTests is TestUtils { loan.makePayment(0); assertEq(fundsAsset.balanceOf(address(borrower)), 0); - assertEq(fundsAsset.balanceOf(address(lender)), totalPayment); + assertEq(fundsAsset.balanceOf(lender), totalPayment); assertEq(loan.paymentsRemaining(), 2); assertEq(loan.principal(), startingPrincipal - principal); } @@ -966,7 +966,7 @@ contract MapleLoanTests is TestUtils { fundsAsset.mint(address(user), totalPayment); assertEq(fundsAsset.balanceOf(address(user)), totalPayment); - assertEq(fundsAsset.balanceOf(address(lender)), 0); + assertEq(fundsAsset.balanceOf(lender), 0); assertEq(loan.paymentsRemaining(), 3); assertEq(loan.principal(), startingPrincipal); @@ -979,7 +979,7 @@ contract MapleLoanTests is TestUtils { loan.makePayment(totalPayment); assertEq(fundsAsset.balanceOf(address(user)), 0); - assertEq(fundsAsset.balanceOf(address(lender)), totalPayment); + assertEq(fundsAsset.balanceOf(lender), totalPayment); assertEq(loan.paymentsRemaining(), 2); assertEq(loan.principal(), startingPrincipal - principal); } @@ -1001,7 +1001,7 @@ contract MapleLoanTests is TestUtils { fundsAsset.mint(address(user), totalPayment); assertEq(fundsAsset.balanceOf(address(user)), totalPayment); - assertEq(fundsAsset.balanceOf(address(lender)), 0); + assertEq(fundsAsset.balanceOf(lender), 0); assertEq(loan.paymentsRemaining(), 3); assertEq(loan.principal(), startingPrincipal); @@ -1014,7 +1014,7 @@ contract MapleLoanTests is TestUtils { loan.makePayment(0); assertEq(fundsAsset.balanceOf(address(user)), 0); - assertEq(fundsAsset.balanceOf(address(lender)), totalPayment); + assertEq(fundsAsset.balanceOf(lender), totalPayment); assertEq(loan.paymentsRemaining(), 2); assertEq(loan.principal(), startingPrincipal - principal); } @@ -1489,10 +1489,9 @@ contract MapleLoanTests is TestUtils { contract MapleLoanRoleTests is TestUtils { - address lender; - address borrower = address(new Address()); address governor = address(new Address()); + address lender; ConstructableMapleLoan loan; MapleGlobalsMock globals; @@ -1502,8 +1501,8 @@ contract MapleLoanRoleTests is TestUtils { function setUp() public { lender = address(new MockLoanManager()); - globals = new MapleGlobalsMock(governor, MockLoanManager(lender).factory()); feeManager = new MockFeeManager(); + globals = new MapleGlobalsMock(governor); token = new MockERC20("Token", "T", 0); factory = new MockFactory(address(globals)); @@ -1519,7 +1518,7 @@ contract MapleLoanRoleTests is TestUtils { globals.setValidPoolAsset(address(token), true); vm.prank(address(factory)); - loan = new ConstructableMapleLoan(address(factory), borrower, address(feeManager), assets, termDetails, amounts, rates, fees); + loan = new ConstructableMapleLoan(address(factory), borrower, lender, address(feeManager), assets, termDetails, amounts, rates, fees); } function test_transferBorrowerRole_failIfInvalidBorrower() public { @@ -1585,7 +1584,7 @@ contract MapleLoanRoleTests is TestUtils { token.mint(address(loan), 1_000_000); vm.prank(lender); - loan.fundLoan(lender); + loan.fundLoan(); address newLender = address(new Address()); diff --git a/tests/MapleLoanFactory.t.sol b/tests/MapleLoanFactory.t.sol index ac78e5d..87f382b 100644 --- a/tests/MapleLoanFactory.t.sol +++ b/tests/MapleLoanFactory.t.sol @@ -7,15 +7,17 @@ import { MapleLoan } from "../contracts/MapleLoan.sol"; import { MapleLoanFactory } from "../contracts/MapleLoanFactory.sol"; import { MapleLoanInitializer } from "../contracts/MapleLoanInitializer.sol"; -import { MapleGlobalsMock, MockFeeManager } from "./mocks/Mocks.sol"; +import { MapleGlobalsMock, MockFeeManager, MockLoanManager, MockLoanManagerFactory } from "./mocks/Mocks.sol"; import { Proxy } from "../modules/maple-proxy-factory/modules/proxy-factory/contracts/Proxy.sol"; contract MapleLoanFactoryTest is TestUtils { - MapleGlobalsMock internal globals; - MapleLoanFactory internal factory; - MockFeeManager internal feeManager; + MapleGlobalsMock internal globals; + MapleLoanFactory internal factory; + MockFeeManager internal feeManager; + MockLoanManager internal lender; + MockLoanManagerFactory internal loanManagerFactory; address internal governor = address(new Address()); @@ -23,10 +25,12 @@ contract MapleLoanFactoryTest is TestUtils { address internal initializer; function setUp() external { - feeManager = new MockFeeManager(); - globals = new MapleGlobalsMock(governor, address(0)); - implementation = address(new MapleLoan()); - initializer = address(new MapleLoanInitializer()); + lender = new MockLoanManager(); + loanManagerFactory = MockLoanManagerFactory(lender.factory()); + feeManager = new MockFeeManager(); + globals = new MapleGlobalsMock(governor); + implementation = address(new MapleLoan()); + initializer = address(new MapleLoanInitializer()); factory = new MapleLoanFactory(address(globals)); @@ -49,6 +53,7 @@ contract MapleLoanFactoryTest is TestUtils { bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( address(1), + address(lender), address(feeManager), assets, termDetails, @@ -76,6 +81,7 @@ contract MapleLoanFactoryTest is TestUtils { bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( address(1), + address(lender), address(feeManager), assets, termDetails, @@ -94,6 +100,97 @@ contract MapleLoanFactoryTest is TestUtils { factory.createInstance(arguments, salt); } + function test_createInstance_zeroLender() external { + address[2] memory assets = [address(1), address(1)]; + uint256[3] memory termDetails = [uint256(1), uint256(1), uint256(1)]; + uint256[3] memory amounts = [uint256(1), uint256(1), uint256(0)]; + uint256[4] memory rates = [uint256(0), uint256(0), uint256(0), uint256(0)]; + uint256[2] memory fees = [uint256(0), uint256(0)]; + + bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( + address(1), + address(0), + address(feeManager), + assets, + termDetails, + amounts, + rates, + fees + ); + + vm.expectRevert("MPF:CI:FAILED"); + factory.createInstance(arguments, "SALT"); + + arguments = MapleLoanInitializer(initializer).encodeArguments( + address(1), + address(lender), + address(feeManager), + assets, + termDetails, + amounts, + rates, + fees + ); + + factory.createInstance(arguments, "SALT"); + } + + function test_createInstance_invalidFactory() external { + address[2] memory assets = [address(1), address(1)]; + uint256[3] memory termDetails = [uint256(1), uint256(1), uint256(1)]; + uint256[3] memory amounts = [uint256(1), uint256(1), uint256(0)]; + uint256[4] memory rates = [uint256(0), uint256(0), uint256(0), uint256(0)]; + uint256[2] memory fees = [uint256(0), uint256(0)]; + + bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( + address(1), + address(lender), + address(feeManager), + assets, + termDetails, + amounts, + rates, + fees + ); + + globals.__setIsFactory(false); + + vm.expectRevert("MPF:CI:FAILED"); + factory.createInstance(arguments, "SALT"); + + globals.__setIsFactory(true); + + factory.createInstance(arguments, "SALT"); + } + + function test_createInstance_invalidInstance() external { + address[2] memory assets = [address(1), address(1)]; + uint256[3] memory termDetails = [uint256(1), uint256(1), uint256(1)]; + uint256[3] memory amounts = [uint256(1), uint256(1), uint256(0)]; + uint256[4] memory rates = [uint256(0), uint256(0), uint256(0), uint256(0)]; + uint256[2] memory fees = [uint256(0), uint256(0)]; + + bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( + address(1), + address(lender), + address(feeManager), + assets, + termDetails, + amounts, + rates, + fees + ); + + loanManagerFactory.__setIsInstance(false); + + vm.expectRevert("MPF:CI:FAILED"); + factory.createInstance(arguments, "SALT"); + + loanManagerFactory.__setIsInstance(true); + + factory.createInstance(arguments, "SALT"); + } + function testFail_createInstance_saltAndArgumentsCollision() external { address[2] memory assets = [address(1), address(1)]; uint256[3] memory termDetails = [uint256(1), uint256(1), uint256(1)]; @@ -103,6 +200,7 @@ contract MapleLoanFactoryTest is TestUtils { bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( address(1), + address(lender), address(feeManager), assets, termDetails, @@ -128,6 +226,7 @@ contract MapleLoanFactoryTest is TestUtils { bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( address(1), + address(lender), address(feeManager), assets, termDetails, diff --git a/tests/MapleLoanFeeManager.t.sol b/tests/MapleLoanFeeManager.t.sol index f0f8b51..23aa36b 100644 --- a/tests/MapleLoanFeeManager.t.sol +++ b/tests/MapleLoanFeeManager.t.sol @@ -26,7 +26,7 @@ contract FeeManagerBase is TestUtils { MapleLoanFeeManager internal feeManager; MockERC20 internal collateralAsset; MockERC20 internal fundsAsset; - MockLoanManager internal loanManager; + MockLoanManager internal lender; MockPoolManager internal poolManager; address[2] internal defaultAssets; @@ -43,12 +43,12 @@ contract FeeManagerBase is TestUtils { fundsAsset = new MockERC20("MockAsset", "MA", 18); poolManager = new MockPoolManager(PD); - loanManager = new MockLoanManager(); - globals = new MapleGlobalsMock(GOVERNOR, loanManager.factory()); - factory = new MapleLoanFactory(address(globals)); - feeManager = new MapleLoanFeeManager(address(globals)); + lender = new MockLoanManager(); + globals = new MapleGlobalsMock(GOVERNOR); + factory = new MapleLoanFactory(address(globals)); + feeManager = new MapleLoanFeeManager(address(globals)); - loanManager.__setPoolManager(address(poolManager)); + lender.__setPoolManager(address(poolManager)); vm.startPrank(GOVERNOR); factory.registerImplementation(1, implementation, initializer); @@ -69,6 +69,7 @@ contract FeeManagerBase is TestUtils { function _createLoan( address borrower_, + address lender_, address feeManager_, address[2] memory assets_, uint256[3] memory termDetails_, @@ -82,6 +83,7 @@ contract FeeManagerBase is TestUtils { loan_ = factory.createInstance({ arguments_: MapleLoanInitializer(initializer).encodeArguments( borrower_, + lender_, feeManager_, assets_, termDetails_, @@ -89,14 +91,15 @@ contract FeeManagerBase is TestUtils { rates_, fees_ ), - salt_: keccak256(abi.encodePacked(salt_)) + salt_: keccak256(abi.encodePacked(salt_)) }); } function _fundLoan(address loan_, address lender_, uint256 amount_) internal { fundsAsset.mint(address(loan_), amount_); + vm.prank(lender_); - MapleLoan(loan_).fundLoan(lender_); + MapleLoan(loan_).fundLoan(); } function _drawdownLoan(address loan_, address borrower_) internal { @@ -115,12 +118,12 @@ contract PayClosingFeesTests is FeeManagerBase { super.setUp(); loan = MapleLoan( - _createLoan(BORROWER, address(feeManager), defaultAssets, defaultTermDetails, defaultAmounts, defaultRates, defaultFees, "salt") + _createLoan(BORROWER, address(lender), address(feeManager), defaultAssets, defaultTermDetails, defaultAmounts, defaultRates, defaultFees, "salt") ); globals.setPlatformServiceFeeRate(address(poolManager), 3000); // 0.3% - _fundLoan(address(loan), address(loanManager), loan.principalRequested()); + _fundLoan(address(loan), address(lender), loan.principalRequested()); _drawdownLoan(address(loan), BORROWER); } @@ -142,17 +145,17 @@ contract PayClosingFeesTests is FeeManagerBase { fundsAsset.approve(address(loan), 1_022_250e18); // 1m + 20k + 2.25k = 1_022_250 - assertEq(fundsAsset.balanceOf(BORROWER), 1_022_250e18); // 1m + 20k + 2.25k + = 1_022_250 - assertEq(fundsAsset.balanceOf(address(loanManager)), 0); - assertEq(fundsAsset.balanceOf(PD), 50_000e18); // Origination fees - assertEq(fundsAsset.balanceOf(TREASURY), 0); + assertEq(fundsAsset.balanceOf(BORROWER), 1_022_250e18); // 1m + 20k + 2.25k + = 1_022_250 + assertEq(fundsAsset.balanceOf(address(lender)), 0); + assertEq(fundsAsset.balanceOf(PD), 50_000e18); // Origination fees + assertEq(fundsAsset.balanceOf(TREASURY), 0); loan.closeLoan(1_022_250e18); - assertEq(fundsAsset.balanceOf(BORROWER), 0); - assertEq(fundsAsset.balanceOf(address(loanManager)), 1_020_000e18); // Principal + interest - assertEq(fundsAsset.balanceOf(PD), 50_000e18 + 1_500e18); - assertEq(fundsAsset.balanceOf(TREASURY), 750e18); + assertEq(fundsAsset.balanceOf(BORROWER), 0); + assertEq(fundsAsset.balanceOf(address(lender)), 1_020_000e18); // Principal + interest + assertEq(fundsAsset.balanceOf(PD), 50_000e18 + 1_500e18); + assertEq(fundsAsset.balanceOf(TREASURY), 750e18); } } @@ -167,7 +170,7 @@ contract PayOriginationFeesTests is FeeManagerBase { super.setUp(); loan = MapleLoan( - _createLoan(BORROWER, address(feeManager), defaultAssets, defaultTermDetails, defaultAmounts, defaultRates, defaultFees, "salt") + _createLoan(BORROWER, address(lender), address(feeManager), defaultAssets, defaultTermDetails, defaultAmounts, defaultRates, defaultFees, "salt") ); globals.setPlatformOriginationFeeRate(address(poolManager), 3000); // 0.3% @@ -176,17 +179,17 @@ contract PayOriginationFeesTests is FeeManagerBase { function test_payOriginationFees_insufficientFunds_poolDelegate() external { fundsAsset.mint(address(loan), 50_00e18 - 1); - vm.prank(address(loanManager)); + vm.prank(address(lender)); vm.expectRevert("MLFM:POF:PD_TRANSFER"); - loan.fundLoan(address(loanManager)); + loan.fundLoan(); } function test_payOriginationFees_insufficientFunds_treasury() external { fundsAsset.mint(address(loan), 50_750e18 - 1); // 50k + (1m * 0.3% / 12 * 3) = 50_750 - vm.prank(address(loanManager)); + vm.prank(address(lender)); vm.expectRevert("MLFM:POF:TREASURY_TRANSFER"); - loan.fundLoan(address(loanManager)); + loan.fundLoan(); } function test_payOriginationFees_zeroTreasury() external { @@ -195,9 +198,9 @@ contract PayOriginationFeesTests is FeeManagerBase { fundsAsset.mint(address(loan), 1_000_000e18); // 1m + 50k + (1m * 0.3% = 3_000) = 1_053_000 - vm.prank(address(loanManager)); + vm.prank(address(lender)); vm.expectRevert("MLFM:TT:ZERO_DESTINATION"); - loan.fundLoan(address(loanManager)); + loan.fundLoan(); } function test_payOriginationFees() external { @@ -207,8 +210,8 @@ contract PayOriginationFeesTests is FeeManagerBase { assertEq(fundsAsset.balanceOf(PD), 0); assertEq(fundsAsset.balanceOf(TREASURY), 0); - vm.prank(address(loanManager)); - loan.fundLoan(address(loanManager)); + vm.prank(address(lender)); + loan.fundLoan(); assertEq(fundsAsset.balanceOf(address(loan)), 949_250e18); // Principal - both origination fees assertEq(fundsAsset.balanceOf(PD), 50_000e18); // 50k origination fee to PD @@ -227,12 +230,12 @@ contract PayServiceFeesTests is FeeManagerBase { super.setUp(); loan = MapleLoan( - _createLoan(BORROWER, address(feeManager), defaultAssets, defaultTermDetails, defaultAmounts, defaultRates, defaultFees, "salt") + _createLoan(BORROWER, address(lender), address(feeManager), defaultAssets, defaultTermDetails, defaultAmounts, defaultRates, defaultFees, "salt") ); globals.setPlatformServiceFeeRate(address(poolManager), platformServiceFeeRate); - _fundLoan(address(loan), address(loanManager), loan.principalRequested()); + _fundLoan(address(loan), address(lender), loan.principalRequested()); _drawdownLoan(address(loan), BORROWER); } @@ -269,17 +272,17 @@ contract PayServiceFeesTests is FeeManagerBase { fundsAsset.approve(address(loan), 10_750e18); - assertEq(fundsAsset.balanceOf(BORROWER), 950_000e18); - assertEq(fundsAsset.balanceOf(address(loanManager)), 0); - assertEq(fundsAsset.balanceOf(PD), 50_000e18); // Origination fees - assertEq(fundsAsset.balanceOf(TREASURY), 0); + assertEq(fundsAsset.balanceOf(BORROWER), 950_000e18); + assertEq(fundsAsset.balanceOf(address(lender)), 0); + assertEq(fundsAsset.balanceOf(PD), 50_000e18); // Origination fees + assertEq(fundsAsset.balanceOf(TREASURY), 0); loan.makePayment(10_750e18); - assertEq(fundsAsset.balanceOf(BORROWER), 939_250e18); // 950k - 10.75k - assertEq(fundsAsset.balanceOf(address(loanManager)), 10_000e18); // Interest - assertEq(fundsAsset.balanceOf(PD), 50_000e18 + 500e18); - assertEq(fundsAsset.balanceOf(TREASURY), 250e18); + assertEq(fundsAsset.balanceOf(BORROWER), 939_250e18); // 950k - 10.75k + assertEq(fundsAsset.balanceOf(address(lender)), 10_000e18); // Interest + assertEq(fundsAsset.balanceOf(PD), 50_000e18 + 500e18); + assertEq(fundsAsset.balanceOf(TREASURY), 250e18); } } @@ -289,6 +292,7 @@ contract UpdatePlatformServiceFeeTests is FeeManagerBase { function test_updatePlatformServiceFee() external { address loan1 = _createLoan( BORROWER, + address(lender), address(feeManager), defaultAssets, defaultTermDetails, @@ -300,6 +304,7 @@ contract UpdatePlatformServiceFeeTests is FeeManagerBase { address loan2 = _createLoan( BORROWER, + address(lender), address(feeManager), defaultAssets, defaultTermDetails, @@ -309,8 +314,8 @@ contract UpdatePlatformServiceFeeTests is FeeManagerBase { "salt2" ); - _fundLoan(loan1, address(loanManager), 1_000_000e18); - _fundLoan(loan2, address(loanManager), 1_000_000e18); + _fundLoan(loan1, address(lender), 1_000_000e18); + _fundLoan(loan2, address(lender), 1_000_000e18); assertEq(feeManager.platformServiceFee(loan1), 0); assertEq(feeManager.platformServiceFee(loan2), 0); @@ -362,6 +367,7 @@ contract FeeManager_Getters is FeeManagerBase { address loan1 = _createLoan( BORROWER, + address(lender), address(feeManager), defaultAssets, defaultTermDetails, @@ -389,6 +395,7 @@ contract FeeManager_Getters is FeeManagerBase { address loan1 = _createLoan( BORROWER, + address(lender), address(feeManager), defaultAssets, defaultTermDetails, @@ -398,7 +405,7 @@ contract FeeManager_Getters is FeeManagerBase { "salt1" ); - _fundLoan(loan1, address(loanManager), 1_000_000e18); + _fundLoan(loan1, address(lender), 1_000_000e18); globals.setPlatformServiceFeeRate(address(poolManager), 1_0000); diff --git a/tests/MapleLoanLogic.t.sol b/tests/MapleLoanLogic.t.sol index e7f1477..1f74fcc 100644 --- a/tests/MapleLoanLogic.t.sol +++ b/tests/MapleLoanLogic.t.sol @@ -15,8 +15,6 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { address internal borrower = address(new Address()); address internal governor = address(new Address()); - address internal lender; - address internal defaultBorrower; address[2] internal defaultAssets; uint256[3] internal defaultTermDetails; @@ -32,17 +30,17 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { MockERC20 internal fundsAsset; MockFactory internal factory; MockFeeManager internal feeManager; + MockLoanManager internal lender; Refinancer internal refinancer; function setUp() external { collateralAsset = new MockERC20("Token0", "T0", 0); feeManager = new MockFeeManager(); fundsAsset = new MockERC20("Token1", "T1", 0); - lender = address(new MockLoanManager()); + lender = new MockLoanManager(); refinancer = new Refinancer(); - globals = new MapleGlobalsMock(governor, MockLoanManager(lender).factory()); - + globals = new MapleGlobalsMock(governor); factory = new MockFactory(address(globals)); // Set _initialize() parameters. @@ -61,6 +59,7 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { loan = new ConstructableMapleLoan( address(factory), defaultBorrower, + address(lender), address(feeManager), defaultAssets, defaultTermDetails, @@ -75,7 +74,7 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { loan.__setBorrower(borrower); loan.__setDrawableFunds(defaultAmounts[1]); loan.__setFactory(address(factory)); - loan.__setLender(lender); + loan.__setLender(address(lender)); loan.__setNextPaymentDueDate(start + 25 days); // 5 days into a loan loan.__setPrincipal(defaultAmounts[1]); @@ -92,7 +91,7 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { loan.proposeNewTerms(address(refinancer), deadline, calls); // Try with empty calls array. - vm.prank(lender); + vm.prank(address(lender)); vm.expectRevert("ML:ANT:COMMITMENT_MISMATCH"); loan.acceptNewTerms(address(refinancer), deadline, new bytes[](0)); } @@ -109,7 +108,7 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { // Try with different calls array. calls[0] = abi.encodeWithSignature("setCollateralRequired(uint256)", uint256(2)); - vm.prank(lender); + vm.prank(address(lender)); vm.expectRevert("ML:ANT:COMMITMENT_MISMATCH"); loan.acceptNewTerms(address(refinancer), deadline, calls); } @@ -124,7 +123,7 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { loan.proposeNewTerms(address(refinancer), deadline, calls); // Try with different refinancer. - vm.prank(lender); + vm.prank(address(lender)); vm.expectRevert("ML:ANT:COMMITMENT_MISMATCH"); loan.acceptNewTerms(address(1111), deadline, calls); } @@ -139,7 +138,7 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { loan.proposeNewTerms(address(refinancer), deadline, calls); // Try with different deadline. - vm.prank(lender); + vm.prank(address(lender)); vm.expectRevert("ML:ANT:COMMITMENT_MISMATCH"); loan.acceptNewTerms(address(refinancer), deadline + 1, calls); } @@ -154,7 +153,7 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { loan.proposeNewTerms(address(0), deadline, calls); // Try with invalid refinancer. - vm.prank(lender); + vm.prank(address(lender)); vm.expectRevert("ML:ANT:INVALID_REFINANCER"); loan.acceptNewTerms(address(0), deadline, calls); } @@ -171,7 +170,7 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { vm.warp(deadline + 1); // Try after deadline. - vm.prank(lender); + vm.prank(address(lender)); vm.expectRevert("ML:ANT:EXPIRED_COMMITMENT"); loan.acceptNewTerms(address(refinancer), deadline, calls); } @@ -187,7 +186,7 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { loan.proposeNewTerms(address(refinancer), deadline, calls); // Try with invalid new term. - vm.prank(lender); + vm.prank(address(lender)); vm.expectRevert("ML:ANT:FAILED"); loan.acceptNewTerms(address(refinancer), deadline, calls); } @@ -209,7 +208,7 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { loan.proposeNewTerms(address(refinancer), deadline, calls); // Try with insufficient collateral. - vm.startPrank(lender); + vm.startPrank(address(lender)); vm.expectRevert("ML:ANT:INSUFFICIENT_COLLATERAL"); loan.acceptNewTerms(address(refinancer), deadline, calls); @@ -229,7 +228,7 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { vm.prank(borrower); loan.proposeNewTerms(address(refinancer), deadline, calls); - vm.prank(lender); + vm.prank(address(lender)); loan.acceptNewTerms(address(refinancer), deadline, calls); } @@ -243,21 +242,20 @@ contract MapleLoanLogic_CloseLoanTests is TestUtils { address internal borrower = address(new Address()); address internal governor = address(new Address()); - address internal lender; - MapleGlobalsMock internal globals; MapleLoanHarness internal loan; MockERC20 internal fundsAsset; MockFactory internal factory; MockFeeManager internal feeManager; + MockLoanManager internal lender; function setUp() external { feeManager = new MockFeeManager(); fundsAsset = new MockERC20("FundsAsset", "FA", 0); - lender = address(new MockLoanManager()); + lender = new MockLoanManager(); loan = new MapleLoanHarness(); - globals = new MapleGlobalsMock(governor, MockLoanManager(lender).factory()); + globals = new MapleGlobalsMock(governor); factory = new MockFactory(address(globals)); @@ -265,7 +263,7 @@ contract MapleLoanLogic_CloseLoanTests is TestUtils { loan.__setFactory(address(factory)); loan.__setFeeManager(address(feeManager)); loan.__setFundsAsset(address(fundsAsset)); - loan.__setLender(lender); + loan.__setLender(address(lender)); } function setupLoan( @@ -697,7 +695,7 @@ contract MapleLoanLogic_DrawdownFundsTests is TestUtils { function setUp() external { collateralAsset = new MockERC20("Collateral Asset", "CA", 0); - globals = new MapleGlobalsMock(address(0), address(0)); + globals = new MapleGlobalsMock(address(0)); fundsAsset = new MockERC20("Funds Asset", "FA", 0); loan = new MapleLoanHarness(); @@ -843,32 +841,32 @@ contract MapleLoanLogic_FundLoanTests is TestUtils { uint256 constant MAX_PRINCIPAL = 1_000_000_000 * 1e18; uint256 constant MIN_PRINCIPAL = 1; - address lender; - MapleGlobalsMock globals; MapleLoanHarness loan; MockERC20 fundsAsset; MockFactory factory; MockFeeManager feeManager; + MockLoanManager lender; address governor = address(new Address()); function setUp() external { feeManager = new MockFeeManager(); fundsAsset = new MockERC20("FundsAsset", "FA", 0); - lender = address(new MockLoanManager()); - globals = new MapleGlobalsMock(governor, MockLoanManager(lender).factory()); + lender = new MockLoanManager(); + globals = new MapleGlobalsMock(governor); loan = new MapleLoanHarness(); factory = new MockFactory(address(globals)); loan.__setFactory(address(factory)); loan.__setFeeManager(address(feeManager)); + loan.__setLender(address(lender)); } - function test_fundLoan_withInvalidLender() external { - vm.expectRevert("ML:FL:INVALID_LENDER"); - loan.fundLoan(address(0)); + function test_fundLoan_notLender() external { + vm.expectRevert("ML:FL:NOT_LENDER"); + loan.fundLoan(); } function test_fundLoan_withoutSendingAsset() external { @@ -876,8 +874,9 @@ contract MapleLoanLogic_FundLoanTests is TestUtils { loan.__setPaymentsRemaining(1); loan.__setPrincipalRequested(1); + vm.prank(address(lender)); vm.expectRevert(ARITHMETIC_ERROR); - loan.fundLoan(lender); + loan.fundLoan(); } function test_fundLoan_fullFunding(uint256 principalRequested_) external { @@ -890,8 +889,11 @@ contract MapleLoanLogic_FundLoanTests is TestUtils { fundsAsset.mint(address(loan), principalRequested_); - assertEq(loan.fundLoan(lender), principalRequested_); - assertEq(loan.lender(), lender); + vm.prank(address(lender)); + uint256 fundsLent_ = loan.fundLoan(); + + assertEq(fundsLent_, principalRequested_); + assertEq(loan.lender(), address(lender)); assertEq(loan.nextPaymentDueDate(), block.timestamp + loan.paymentInterval()); assertEq(loan.principal(), principalRequested_); assertEq(loan.drawableFunds(), principalRequested_); @@ -907,8 +909,9 @@ contract MapleLoanLogic_FundLoanTests is TestUtils { fundsAsset.mint(address(loan), principalRequested_ - 1); + vm.prank(address(lender)); vm.expectRevert(ARITHMETIC_ERROR); - loan.fundLoan(lender); + loan.fundLoan(); } function test_fundLoan_doubleFund(uint256 principalRequested_) external { @@ -920,12 +923,14 @@ contract MapleLoanLogic_FundLoanTests is TestUtils { fundsAsset.mint(address(loan), principalRequested_); - loan.fundLoan(lender); + vm.prank(address(lender)); + loan.fundLoan(); fundsAsset.mint(address(loan), 1); + vm.prank(address(lender)); vm.expectRevert("ML:FL:LOAN_ACTIVE"); - loan.fundLoan(lender); + loan.fundLoan(); } function test_fundLoan_invalidFundsAsset() external { @@ -935,8 +940,9 @@ contract MapleLoanLogic_FundLoanTests is TestUtils { fundsAsset.mint(address(loan), 1); + vm.prank(address(lender)); vm.expectRevert(ARITHMETIC_ERROR); - loan.fundLoan(lender); + loan.fundLoan(); } function test_fundLoan_withUnaccountedCollateralAsset() external { @@ -950,7 +956,8 @@ contract MapleLoanLogic_FundLoanTests is TestUtils { collateralAsset.mint(address(loan), 1); fundsAsset.mint(address(loan), 1); - loan.fundLoan(lender); + vm.prank(address(lender)); + loan.fundLoan(); assertEq(loan.getUnaccountedAmount(address(collateralAsset)), 1); } @@ -963,8 +970,9 @@ contract MapleLoanLogic_FundLoanTests is TestUtils { fundsAsset.mint(address(loan), 1); + vm.prank(address(lender)); vm.expectRevert("ML:FL:LOAN_ACTIVE"); - loan.fundLoan(lender); + loan.fundLoan(); } function test_fundLoan_noPaymentsRemaining() external { @@ -972,8 +980,9 @@ contract MapleLoanLogic_FundLoanTests is TestUtils { loan.__setPaymentsRemaining(0); loan.__setPrincipalRequested(1); + vm.prank(address(lender)); vm.expectRevert("ML:FL:LOAN_ACTIVE"); - loan.fundLoan(lender); + loan.fundLoan(); } function test_fundLoan_approveFail() external { @@ -982,8 +991,9 @@ contract MapleLoanLogic_FundLoanTests is TestUtils { loan.__setPaymentsRemaining(1); loan.__setPrincipalRequested(1); + vm.prank(address(lender)); vm.expectRevert("ML:FL:APPROVE_FAIL"); - loan.fundLoan(address(lender)); + loan.fundLoan(); } } @@ -1028,12 +1038,14 @@ contract MapleLoanLogic_GetClosingPaymentBreakdownTests is TestUtils { MockERC20 token2; MockFactory factory; MockFeeManager feeManager; + MockLoanManager lender; address governor = address(new Address()); function setUp() external { - globals = new MapleGlobalsMock(governor, address(0)); + globals = new MapleGlobalsMock(governor); feeManager = new MockFeeManager(); + lender = new MockLoanManager(); token1 = new MockERC20("Token0", "T0", 0); token2 = new MockERC20("Token1", "T1", 0); @@ -1065,6 +1077,7 @@ contract MapleLoanLogic_GetClosingPaymentBreakdownTests is TestUtils { loan = new ConstructableMapleLoan( address(factory), defaultBorrower, + address(lender), address(feeManager), defaultAssets, defaultTermDetails, @@ -1576,12 +1589,14 @@ contract MapleLoanLogic_InitializeTests is TestUtils { MockERC20 internal token2; MockFactory internal factory; MockFeeManager internal feeManager; + MockLoanManager internal lender; address governor = address(new Address()); function setUp() external { feeManager = new MockFeeManager(); - globals = new MapleGlobalsMock(governor, address(0)); + lender = new MockLoanManager(); + globals = new MapleGlobalsMock(governor); token1 = new MockERC20("Token0", "T0", 0); token2 = new MockERC20("Token1", "T1", 0); @@ -1606,6 +1621,7 @@ contract MapleLoanLogic_InitializeTests is TestUtils { loan = new ConstructableMapleLoan( address(factory), defaultBorrower, + address(lender), address(feeManager), defaultAssets, defaultTermDetails, @@ -1644,6 +1660,7 @@ contract MapleLoanLogic_InitializeTests is TestUtils { new ConstructableMapleLoan( address(factory), defaultBorrower, + address(lender), address(feeManager), defaultAssets, defaultTermDetails, @@ -1666,6 +1683,7 @@ contract MapleLoanLogic_InitializeTests is TestUtils { new ConstructableMapleLoan( address(factory), defaultBorrower, + address(lender), address(feeManager), defaultAssets, defaultTermDetails, @@ -1686,6 +1704,7 @@ contract MapleLoanLogic_InitializeTests is TestUtils { new ConstructableMapleLoan( address(factory), defaultBorrower, + address(lender), address(feeManager), defaultAssets, termDetails, @@ -1706,6 +1725,7 @@ contract MapleLoanLogic_InitializeTests is TestUtils { new ConstructableMapleLoan( address(factory), defaultBorrower, + address(lender), address(feeManager), defaultAssets, termDetails, @@ -1722,6 +1742,7 @@ contract MapleLoanLogic_InitializeTests is TestUtils { new ConstructableMapleLoan( address(factory), address(0), + address(lender), address(feeManager), defaultAssets, defaultTermDetails, @@ -1738,6 +1759,7 @@ contract MapleLoanLogic_InitializeTests is TestUtils { new ConstructableMapleLoan( address(factory), address(1234), + address(lender), address(feeManager), defaultAssets, defaultTermDetails, @@ -1757,23 +1779,22 @@ contract MapleLoanLogic_MakePaymentTests is TestUtils { address internal borrower = address(new Address()); address internal governor = address(new Address()); - address internal lender; - MapleGlobalsMock internal globals; MapleLoanHarness internal loan; MockERC20 internal collateralAsset; MockERC20 internal fundsAsset; MockFactory internal factory; MockFeeManager internal feeManager; + MockLoanManager internal lender; function setUp() external { collateralAsset = new MockERC20("collateralAsset", "CA", 0); feeManager = new MockFeeManager(); fundsAsset = new MockERC20("FundsAsset", "FA", 0); - lender = address(new MockLoanManager()); + lender = new MockLoanManager(); loan = new MapleLoanHarness(); - globals = new MapleGlobalsMock(governor, MockLoanManager(lender).factory()); + globals = new MapleGlobalsMock(governor); factory = new MockFactory(address(globals)); @@ -1781,7 +1802,7 @@ contract MapleLoanLogic_MakePaymentTests is TestUtils { loan.__setFactory(address(factory)); loan.__setFeeManager(address(feeManager)); loan.__setFundsAsset(address(fundsAsset)); - loan.__setLender(lender); + loan.__setLender(address(lender)); } function setupLoan( @@ -2134,7 +2155,7 @@ contract MapleLoanLogic_PostCollateralTests is TestUtils { function setUp() external { loan = new MapleLoanHarness(); collateralAsset = new MockERC20("CollateralAsset", "CA", 0); - globals = new MapleGlobalsMock(address(0), address(0)); + globals = new MapleGlobalsMock(address(0)); factory = new MockFactory(address(globals)); @@ -2196,7 +2217,7 @@ contract MapleLoanLogic_ProposeNewTermsTests is TestUtils { address internal borrower = address(new Address()); function setUp() external { - globals = new MapleGlobalsMock(address(0), address(0)); + globals = new MapleGlobalsMock(address(0)); loan = new MapleLoanHarness(); factory = new MockFactory(address(globals)); @@ -2249,7 +2270,7 @@ contract MapleLoanLogic_RejectNewTermsTests is TestUtils { address borrower = address(new Address()); function setUp() external { - globals = new MapleGlobalsMock(address(0), address(0)); + globals = new MapleGlobalsMock(address(0)); factory = new MockFactory(address(globals)); loan = new MapleLoanHarness(); refinancer = new Refinancer(); @@ -2343,7 +2364,7 @@ contract MapleLoanLogic_RemoveCollateralTests is TestUtils { function setUp() external { collateralAsset = new MockERC20("CollateralAsset", "CA", 0); - globals = new MapleGlobalsMock(address(0), address(0)); + globals = new MapleGlobalsMock(address(0)); loan = new MapleLoanHarness(); factory = new MockFactory(address(globals)); @@ -2553,19 +2574,18 @@ contract MapleLoanLogic_RemoveCollateralTests is TestUtils { contract MapleLoanLogic_RepossessTests is TestUtils { - address internal lender; - MapleGlobalsMock internal globals; MapleLoanHarness internal loan; MockERC20 internal collateralAsset; MockERC20 internal fundsAsset; MockFactory internal factory; + MockLoanManager internal lender; function setUp() external { collateralAsset = new MockERC20("Collateral Asset", "CA", 0); - globals = new MapleGlobalsMock(address(0), address(0)); fundsAsset = new MockERC20("Funds Asset", "FA", 0); - lender = address(new MockLoanManager()); + globals = new MapleGlobalsMock(address(0)); + lender = new MockLoanManager(); loan = new MapleLoanHarness(); factory = new MockFactory(address(globals)); @@ -2576,7 +2596,7 @@ contract MapleLoanLogic_RepossessTests is TestUtils { loan.__setFactory(address(factory)); loan.__setFundsAsset(address(fundsAsset)); loan.__setGracePeriod(10); - loan.__setLender(lender); + loan.__setLender(address(lender)); loan.__setPaymentsRemaining(1); loan.__setPrincipal(1); @@ -2587,48 +2607,48 @@ contract MapleLoanLogic_RepossessTests is TestUtils { function test_repossess() external { loan.__setNextPaymentDueDate(block.timestamp - 11); - vm.prank(lender); - loan.repossess(lender); + vm.prank(address(lender)); + loan.repossess(address(lender)); - assertEq(loan.drawableFunds(), 0); - assertEq(loan.collateral(), 0); - assertEq(loan.nextPaymentDueDate(), 0); - assertEq(loan.paymentsRemaining(), 0); - assertEq(loan.principal(), 0); - assertEq(collateralAsset.balanceOf(lender), 1); - assertEq(fundsAsset.balanceOf(lender), 2); + assertEq(loan.drawableFunds(), 0); + assertEq(loan.collateral(), 0); + assertEq(loan.nextPaymentDueDate(), 0); + assertEq(loan.paymentsRemaining(), 0); + assertEq(loan.principal(), 0); + assertEq(collateralAsset.balanceOf(address(lender)), 1); + assertEq(fundsAsset.balanceOf(address(lender)), 2); } function test_repossess_beforePaymentDue() external { loan.__setNextPaymentDueDate(block.timestamp + 1); - vm.prank(lender); + vm.prank(address(lender)); vm.expectRevert("ML:R:NOT_IN_DEFAULT"); - loan.repossess(lender); + loan.repossess(address(lender)); } function test_repossess_onPaymentDue() external { loan.__setNextPaymentDueDate(block.timestamp); - vm.prank(lender); + vm.prank(address(lender)); vm.expectRevert("ML:R:NOT_IN_DEFAULT"); - loan.repossess(lender); + loan.repossess(address(lender)); } function test_repossess_withinGracePeriod() external { loan.__setNextPaymentDueDate(block.timestamp - 5); - vm.prank(lender); + vm.prank(address(lender)); vm.expectRevert("ML:R:NOT_IN_DEFAULT"); - loan.repossess(lender); + loan.repossess(address(lender)); } function test_repossess_onGracePeriod() external { loan.__setNextPaymentDueDate(block.timestamp - 10); - vm.prank(lender); + vm.prank(address(lender)); vm.expectRevert("ML:R:NOT_IN_DEFAULT"); - loan.repossess(lender); + loan.repossess(address(lender)); } function test_repossess_fundsTransferFailed() external { @@ -2639,9 +2659,9 @@ contract MapleLoanLogic_RepossessTests is TestUtils { token.mint(address(loan), 1); - vm.prank(lender); + vm.prank(address(lender)); vm.expectRevert("ML:R:F_TRANSFER_FAILED"); - loan.repossess(lender); + loan.repossess(address(lender)); } function test_repossess_collateralTransferFailed() external { @@ -2652,9 +2672,9 @@ contract MapleLoanLogic_RepossessTests is TestUtils { token.mint(address(loan), 1); - vm.prank(lender); + vm.prank(address(lender)); vm.expectRevert("ML:R:C_TRANSFER_FAILED"); - loan.repossess(lender); + loan.repossess(address(lender)); } } @@ -2667,7 +2687,7 @@ contract MapleLoanLogic_ReturnFundsTests is TestUtils { MockFactory internal factory; function setUp() external { - globals = new MapleGlobalsMock(address(0), address(0)); + globals = new MapleGlobalsMock(address(0)); factory = new MockFactory(address(globals)); fundsAsset = new MockERC20("Funds Asset", "FA", 0); loan = new MapleLoanHarness(); @@ -2793,7 +2813,7 @@ contract MapleLoanLogic_SkimTests is TestUtils { function setUp() external { collateralAsset = new MockERC20("Collateral Asset", "CA", 0); - globals = new MapleGlobalsMock(address(0), address(0)); + globals = new MapleGlobalsMock(address(0)); fundsAsset = new MockERC20("Funds Asset", "FA", 0); loan = new MapleLoanHarness(); diff --git a/tests/MapleLoanScenarios.t.sol b/tests/MapleLoanScenarios.t.sol index 040e164..f2c4216 100644 --- a/tests/MapleLoanScenarios.t.sol +++ b/tests/MapleLoanScenarios.t.sol @@ -23,7 +23,7 @@ contract MapleLoanScenariosTests is TestUtils { function setUp() external { feeManager = new MockFeeManager(); lender = new MockLoanManager(); - globals = new MapleGlobalsMock(governor, lender.factory()); + globals = new MapleGlobalsMock(governor); token = new MockERC20("Test", "TST", 0); factory = new MockFactory(address(globals)); @@ -47,6 +47,7 @@ contract MapleLoanScenariosTests is TestUtils { ConstructableMapleLoan loan = new ConstructableMapleLoan( address(factory), borrower, + address(lender), address(feeManager), assets, termDetails, @@ -58,7 +59,7 @@ contract MapleLoanScenariosTests is TestUtils { // Fund via a 1M transfer vm.startPrank(address(lender)); token.transfer(address(loan), 1_000_000); - loan.fundLoan(address(lender)); + loan.fundLoan(); vm.stopPrank(); assertEq(loan.drawableFunds(), 1_000_000, "Different drawable funds"); @@ -207,12 +208,12 @@ contract MapleLoanScenariosTests is TestUtils { uint256[2] memory fees = [uint256(0), uint256(0)]; vm.prank(address(factory)); - ConstructableMapleLoan loan = new ConstructableMapleLoan(address(factory), borrower, address(feeManager), assets, termDetails, amounts, rates, fees); + ConstructableMapleLoan loan = new ConstructableMapleLoan(address(factory), borrower, address(lender), address(feeManager), assets, termDetails, amounts, rates, fees); // Fund via a 1M transfer vm.startPrank(address(lender)); token.transfer(address(loan), 1_000_000); - loan.fundLoan(address(lender)); + loan.fundLoan(); vm.stopPrank(); assertEq(loan.drawableFunds(), 1_000_000, "Different drawable funds"); diff --git a/tests/Payments.t.sol b/tests/Payments.t.sol index 117f3b2..488afbd 100644 --- a/tests/Payments.t.sol +++ b/tests/Payments.t.sol @@ -40,7 +40,7 @@ contract MapleLoanPaymentsTestBase is TestUtils { initializer = new MapleLoanInitializer(); poolManager = new MockPoolManager(address(poolDelegate)); - globals = new MapleGlobalsMock(governor, lender.factory()); + globals = new MapleGlobalsMock(governor); feeManager = new MapleLoanFeeManager(address(globals)); @@ -76,7 +76,7 @@ contract MapleLoanPaymentsTestBase is TestUtils { fundsAsset.mint(address(lender), amounts[1]); fundsAsset.mint(borrower, amounts[1]); // Mint more than enough for borrower to make payments - bytes memory arguments = initializer.encodeArguments(borrower, address(feeManager), assets, termDetails, amounts, rates, fees); + bytes memory arguments = initializer.encodeArguments(borrower, address(lender), address(feeManager), assets, termDetails, amounts, rates, fees); bytes32 salt = keccak256(abi.encodePacked("salt")); // Create Loan @@ -85,7 +85,7 @@ contract MapleLoanPaymentsTestBase is TestUtils { // Approve and fund Loan vm.startPrank(address(lender)); fundsAsset.transfer(address(loan), amounts[1]); - loan.fundLoan(address(lender)); + loan.fundLoan(); vm.stopPrank(); // Transfer and post collateral and drawdown diff --git a/tests/Refinancer.t.sol b/tests/Refinancer.t.sol index 25022bd..22b8df3 100644 --- a/tests/Refinancer.t.sol +++ b/tests/Refinancer.t.sol @@ -44,8 +44,7 @@ contract RefinancerTestBase is TestUtils { refinancer = new Refinancer(); token = new MockERC20("Test", "TST", 0); - globals = new MapleGlobalsMock(governor, lender.factory()); - + globals = new MapleGlobalsMock(governor); factory = new MockFactory(address(globals)); globals.setValidBorrower(borrower, true); @@ -63,7 +62,6 @@ contract RefinancerTestBase is TestUtils { ) internal { - globals.setValidCollateralAsset(address(token), true); address[2] memory assets = [address(token), address(token)]; @@ -73,12 +71,12 @@ contract RefinancerTestBase is TestUtils { uint256[2] memory fees = [uint256(0), uint256(0)]; vm.prank(address(factory)); - loan = new ConstructableMapleLoan(address(factory), borrower, address(feeManager), assets, termDetails, amounts, rates, fees); + loan = new ConstructableMapleLoan(address(factory), borrower, address(lender), address(feeManager), assets, termDetails, amounts, rates, fees); token.mint(address(loan), principalRequested_); vm.prank(address(lender)); - loan.fundLoan(address(lender)); + loan.fundLoan(); token.mint(address(loan), collateralRequired_); loan.postCollateral(0); @@ -689,13 +687,12 @@ contract RefinancerInterestTests is TestUtils { address internal governor = address(new Address()); function setUp() external { - lender = new MockLoanManager(); feeManager = new MockFeeManager(); + lender = new MockLoanManager(); refinancer = new Refinancer(); token = new MockERC20("Test", "TST", 0); - globals = new MapleGlobalsMock(address(governor), lender.factory()); - + globals = new MapleGlobalsMock(address(governor)); factory = new MockFactory(address(globals)); globals.setValidBorrower(borrower, true); @@ -736,7 +733,7 @@ contract RefinancerInterestTests is TestUtils { token.mint(address(loan), interestPortion); // Interest only payment loan.makePayment(0); - assertEq(interestPortion, 8_219_178_082); + assertEq(interestPortion, 8_219_178_082); assertEq(token.balanceOf(address(lender)), interestPortion); bytes[] memory data = new bytes[](4); @@ -791,10 +788,12 @@ contract RefinancerInterestTests is TestUtils { uint256[2] memory fees = [uint256(0), uint256(0)]; vm.prank(address(factory)); - loan = new ConstructableMapleLoan(address(factory), borrower, address(feeManager), assets, termDetails, amounts, rates, fees); + loan = new ConstructableMapleLoan(address(factory), borrower, address(lender), address(feeManager), assets, termDetails, amounts, rates, fees); token.mint(address(loan), principalRequested_); - loan.fundLoan(address(lender)); + + vm.prank(address(lender)); + loan.fundLoan(); token.mint(address(loan), collateralRequired_); loan.postCollateral(0); @@ -1226,7 +1225,7 @@ contract RefinancingFeesTerms is TestUtils { MapleLoanFeeManager internal feeManager; MockERC20 internal token; MockFactory internal factory; - MockLoanManager internal loanManager; + MockLoanManager internal lender; MockPoolManager internal poolManager; Refinancer internal refinancer; @@ -1234,9 +1233,8 @@ contract RefinancingFeesTerms is TestUtils { address internal governor = address(new Address()); function setUp() public virtual { - loanManager = new MockLoanManager(); - - globals = new MapleGlobalsMock(governor, loanManager.factory()); + lender = new MockLoanManager(); + globals = new MapleGlobalsMock(governor); poolManager = new MockPoolManager(address(POOL_DELEGATE)); refinancer = new Refinancer(); @@ -1244,7 +1242,7 @@ contract RefinancingFeesTerms is TestUtils { feeManager = new MapleLoanFeeManager(address(globals)); token = new MockERC20("Test", "TST", 0); - loanManager.__setPoolManager(address(poolManager)); // Set so correct PD address is used. + lender.__setPoolManager(address(poolManager)); // Set so correct PD address is used. globals.setValidBorrower(borrower, true); globals.setValidPoolAsset(address(token), true); @@ -1262,7 +1260,6 @@ contract RefinancingFeesTerms is TestUtils { ) internal { - MockERC20 collateralToken = new MockERC20("Collateral", "COL", 0); globals.setValidCollateralAsset(address(collateralToken), true); @@ -1274,10 +1271,12 @@ contract RefinancingFeesTerms is TestUtils { uint256[2] memory fees = [uint256(0), uint256(0)]; vm.prank(address(factory)); - loan = new ConstructableMapleLoan(address(factory), borrower, address(feeManager), assets, termDetails, amounts, rates, fees); + loan = new ConstructableMapleLoan(address(factory), borrower, address(lender), address(feeManager), assets, termDetails, amounts, rates, fees); token.mint(address(loan), principalRequested_); - loan.fundLoan(address(loanManager)); + + vm.prank(address(lender)); + loan.fundLoan(); collateralToken.mint(address(loan), collateralRequired_); loan.postCollateral(0); @@ -1317,7 +1316,7 @@ contract RefinancingFeesTerms is TestUtils { vm.prank(borrower); loan.proposeNewTerms(address(refinancer), deadline_, calls); - vm.prank(address(loanManager)); + vm.prank(address(lender)); loan.acceptNewTerms(address(refinancer), deadline_, calls); assertEq(feeManager.platformRefinanceServiceFee(address(loan)), 1_000_000e18 * 0.01 / 10); // Saved fee is 1/10 of annual refinance fee @@ -1345,7 +1344,7 @@ contract RefinancingFeesTerms is TestUtils { vm.prank(borrower); loan.proposeNewTerms(address(refinancer), deadline_, calls); - vm.prank(address(loanManager)); + vm.prank(address(lender)); loan.acceptNewTerms(address(refinancer), deadline_, calls); assertEq(feeManager.platformRefinanceServiceFee(address(loan)), 1_000_000e18 * 0.01 / 10); // Saved fee is 1/10 of annual refinance fee @@ -1358,7 +1357,7 @@ contract RefinancingFeesTerms is TestUtils { vm.prank(borrower); loan.proposeNewTerms(address(refinancer), deadline_, calls); - vm.prank(address(loanManager)); + vm.prank(address(lender)); loan.acceptNewTerms(address(refinancer), deadline_, calls); // Now assert that fees include 2 periods @@ -1386,7 +1385,7 @@ contract RefinancingFeesTerms is TestUtils { vm.prank(borrower); loan.proposeNewTerms(address(refinancer), deadline_, calls); - vm.prank(address(loanManager)); + vm.prank(address(lender)); vm.expectRevert("MLFM:POF:PD_TRANSFER"); loan.acceptNewTerms(address(refinancer), deadline_, calls); } @@ -1424,7 +1423,7 @@ contract RefinancingFeesTerms is TestUtils { vm.prank(address(borrower)); loan.proposeNewTerms(address(refinancer), deadline_, calls); - vm.prank(address(loanManager)); + vm.prank(address(lender)); vm.expectRevert("MLFM:POF:TREASURY_TRANSFER"); loan.acceptNewTerms(address(refinancer), deadline_, calls); } @@ -1461,7 +1460,7 @@ contract RefinancingFeesTerms is TestUtils { vm.prank(borrower); loan.proposeNewTerms(address(refinancer), deadline_, calls); - vm.prank(address(loanManager)); + vm.prank(address(lender)); loan.acceptNewTerms(address(refinancer), deadline_, calls); // Fees were paid during loan origination @@ -1492,7 +1491,7 @@ contract RefinancingFeesTerms is TestUtils { vm.prank(borrower); loan.proposeNewTerms(address(refinancer), deadline_, calls); - vm.prank(address(loanManager)); + vm.prank(address(lender)); loan.acceptNewTerms(address(refinancer), deadline_, calls); assertEq(feeManager.platformServiceFee(address(loan)), 1_000_000e18 * newPlatformServiceFeeRate_ * 30 days / 365 days / 100_0000); @@ -1544,7 +1543,7 @@ contract RefinancingFeesTerms is TestUtils { vm.prank(borrower); loan.proposeNewTerms(address(refinancer), deadline_, calls); - vm.prank(address(loanManager)); + vm.prank(address(lender)); loan.acceptNewTerms(address(refinancer), deadline_, calls); assertEq(feeManager.delegateOriginationFee(address(loan)), newOriginationFee_); diff --git a/tests/harnesses/MapleLoanHarnesses.sol b/tests/harnesses/MapleLoanHarnesses.sol index 66e4222..b0ba4ad 100644 --- a/tests/harnesses/MapleLoanHarnesses.sol +++ b/tests/harnesses/MapleLoanHarnesses.sol @@ -216,6 +216,7 @@ contract ConstructableMapleLoan is MapleLoanHarness { constructor( address factory_, address borrower_, + address lender_, address feeManager_, address[2] memory assets_, uint256[3] memory termDetails_, @@ -229,7 +230,7 @@ contract ConstructableMapleLoan is MapleLoanHarness { _delegateCall( address(initializer), - initializer.encodeArguments(borrower_, feeManager_, assets_, termDetails_, amounts_, rates_, fees_) + initializer.encodeArguments(borrower_, lender_, feeManager_, assets_, termDetails_, amounts_, rates_, fees_) ); } diff --git a/tests/mocks/Mocks.sol b/tests/mocks/Mocks.sol index 97e4d21..b52cfa3 100644 --- a/tests/mocks/Mocks.sol +++ b/tests/mocks/Mocks.sol @@ -11,8 +11,6 @@ contract MapleGlobalsMock { bool public protocolPaused; - mapping(bytes32 => mapping(address => bool)) public isFactory; - mapping(address => uint256) public platformOriginationFeeRate; mapping(address => uint256) public platformServiceFeeRate; @@ -20,9 +18,15 @@ contract MapleGlobalsMock { mapping(address => bool) public isCollateralAsset; mapping(address => bool) public isPoolAsset; - constructor (address governor_, address loanManagerFactory_) { - governor = governor_; - isFactory["LOAN_MANAGER"][loanManagerFactory_] = true; + bool internal _isFactory; + + constructor (address governor_) { + governor = governor_; + _isFactory = true; + } + + function isFactory(bytes32, address) external view returns (bool) { + return _isFactory; } function setGovernor(address governor_) external { @@ -61,6 +65,10 @@ contract MapleGlobalsMock { isPoolAsset[poolAsset_] = isValid_; } + function __setIsFactory(bool isFactory_) external { + _isFactory = isFactory_; + } + } contract MockFactory { @@ -87,8 +95,18 @@ contract MockFactory { contract MockLoanManagerFactory { - function isInstance(address) external pure returns (bool) { - return true; + bool internal _isInstance; + + constructor() { + _isInstance = true; + } + + function isInstance(address) external view returns (bool) { + return _isInstance; + } + + function __setIsInstance(bool isInstance_) external { + _isInstance = isInstance_; } } @@ -189,12 +207,12 @@ contract MockLoanManager { poolManager = address(new MockPoolManager(address(1))); } + function claim(uint256 principal_, uint256 interest_, uint256 previousPaymentDueDate_, uint256 nextPaymentDueDate_) external { } + function __setPoolManager(address poolManager_) external { poolManager = poolManager_; } - function claim(uint256 principal_, uint256 interest_, uint256 previousPaymentDueDate_, uint256 nextPaymentDueDate_) external { } - } contract MockPoolManager { From 95b10b58fe8d6c9f3575914909e1c44c18fccdf4 Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Wed, 25 Jan 2023 02:45:07 +0000 Subject: [PATCH 05/47] feat: Loan update calculation of refinance interest (SC-11050) (#268) * fix: update to not accrue interest into the next payment interval when the loan is late on a refinance * feat: Test to ensure late loan refi interest calculated correctly * feat: Add literal assertions for interest amounts * fix: Update based on PR comments * fix: update params Co-authored-by: Lucas Manuel --- contracts/MapleLoan.sol | 8 +++-- tests/MapleLoanScenarios.t.sol | 55 +++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index 21230ab..3aa8467 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -834,13 +834,13 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { // If the user has made an early payment, there is no refinance interest owed. if (currentTime_ + paymentInterval_ < nextPaymentDueDate_) return 0; - uint256 timeSinceLastPaymentDueDate_ = currentTime_ - (nextPaymentDueDate_ - paymentInterval_); + uint256 refinanceInterestInterval_ = _min(currentTime_ - (nextPaymentDueDate_ - paymentInterval_), paymentInterval_); ( , refinanceInterest_ ) = _getInstallment( principal_, endingPrincipal_, interestRate_, - timeSinceLastPaymentDueDate_, + refinanceInterestInterval_, paymentsRemaining_ ); @@ -908,6 +908,10 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { _originalNextPaymentDueDate = uint256(0); } + function _min(uint256 a_, uint256 b_) internal pure returns (uint256 minimum_) { + minimum_ = a_ < b_ ? a_ : b_; + } + /** * @dev Returns exponentiation of a scaled base value. * diff --git a/tests/MapleLoanScenarios.t.sol b/tests/MapleLoanScenarios.t.sol index f2c4216..10ed0b7 100644 --- a/tests/MapleLoanScenarios.t.sol +++ b/tests/MapleLoanScenarios.t.sol @@ -6,7 +6,7 @@ import { MockERC20 } from "../modules/erc20/contracts/test/mocks/MockER import { ConstructableMapleLoan } from "./harnesses/MapleLoanHarnesses.sol"; -import { MapleGlobalsMock, MockFactory, MockFeeManager, MockLoanManager } from "./mocks/Mocks.sol"; +import { EmptyContract, MapleGlobalsMock, MockFactory, MockFeeManager, MockLoanManager } from "./mocks/Mocks.sol"; // TODO: Add fees contract MapleLoanScenariosTests is TestUtils { @@ -339,4 +339,57 @@ contract MapleLoanScenariosTests is TestUtils { assertEq(loan.collateral(), 0, "Different collateral"); } + function test_scenario_lateLoanRefinanceInterest() external { + uint256 start = block.timestamp; + + token.mint(address(lender), 1_000_000); + + address[2] memory assets = [address(token), address(token)]; + uint256[3] memory termDetails = [uint256(0), uint256(30 days), uint256(3)]; + uint256[3] memory amounts = [uint256(0), uint256(1_000_000), uint256(1_000_000)]; + uint256[4] memory rates = [uint256(0.1e18), uint256(0), uint256(0), uint256(0.1e18)]; + uint256[2] memory fees = [uint256(0), uint256(0)]; + + vm.prank(address(factory)); + ConstructableMapleLoan loan = new ConstructableMapleLoan(address(factory), borrower, address(lender), address(feeManager), assets, termDetails, amounts, rates, fees); + + // Fund via a 1M transfer + vm.startPrank(address(lender)); + token.transfer(address(loan), 1_000_000); + loan.fundLoan(); + vm.stopPrank(); + + assertEq(loan.drawableFunds(), 1_000_000); + + // 4 days late on payment #1 + vm.warp(start + 34 days); + + address mockRefinancer = address(new EmptyContract()); + + uint256 deadline = start + 45 days; + + bytes[] memory emptyCalls = new bytes[](1); + + emptyCalls[0] = ""; + + // Borrower proposes new terms + vm.prank(borrower); + loan.proposeNewTerms(mockRefinancer, deadline, emptyCalls); // No calls required + + assertEq(loan.refinanceInterest(), 0); + + // Lender accepts new terms + vm.warp(start + 35 days); + vm.prank(address(lender)); + loan.acceptNewTerms(mockRefinancer, deadline, emptyCalls); + + uint256 normalInterest = 30 days * uint256(1_000_000) * 0.1e18 / 1e18 / 365 days; // at 10% interest annualized + uint256 lateInterest = 5 days * uint256(1_000_000) * 0.2e18 / 1e18 / 365 days; // at 20% interest annualized + + assertEq(normalInterest, 8_219); + assertEq(lateInterest, 2_739); + assertEq(loan.refinanceInterest(), 10_958); + assertEq(loan.refinanceInterest(), normalInterest + lateInterest); + } + } From 8f47955a4fce6a8826354c9870818b00e398ec9f Mon Sep 17 00:00:00 2001 From: Michael De Luca <35537333+deluca-mike@users.noreply.github.com> Date: Thu, 26 Jan 2023 20:07:05 -0500 Subject: [PATCH 06/47] fix: Incorrect comments (#270) --- contracts/MapleLoanV4Migrator.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/MapleLoanV4Migrator.sol b/contracts/MapleLoanV4Migrator.sol index 45000da..5a101d8 100644 --- a/contracts/MapleLoanV4Migrator.sol +++ b/contracts/MapleLoanV4Migrator.sol @@ -8,7 +8,7 @@ import { IMapleLoanV4Migrator } from "./interfaces/IMapleLoanV4Migrator.sol"; import { MapleLoanStorage } from "./MapleLoanStorage.sol"; -/// @title DebtLockerV4Migrator is intended to initialize the storage of a DebtLocker proxy. +/// @title MapleLoanV4Migrator is intended to initialize the storage of a MapleLoan proxy. contract MapleLoanV4Migrator is IMapleLoanV4Migrator, MapleLoanStorage { function encodeArguments(address feeManager_) external pure override returns (bytes memory encodedArguments_) { @@ -20,9 +20,8 @@ contract MapleLoanV4Migrator is IMapleLoanV4Migrator, MapleLoanStorage { } fallback() external { - // Taking the feeManager_ address as argument for now - // but ideally this would be hardcoded in the debtLocker migrator registered in the factory. + // but ideally this would be hardcoded in the loan migrator registered in the factory. ( address feeManager_ ) = decodeArguments(msg.data); _feeManager = feeManager_; From db68c14b38c2089a9e6acdbf7ca292074feb3575 Mon Sep 17 00:00:00 2001 From: vbidin Date: Tue, 7 Feb 2023 20:58:56 +0200 Subject: [PATCH 07/47] feat: Enable borrower to perform upgrades (SC-11129) (#271) * feat: enable borrower to perform upgrades * fix: update error message * fix: address PR comments * fix: update failing upgrade pause test --- contracts/MapleLoan.sol | 2 +- tests/MapleLoan.t.sol | 21 +++++---------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index 3aa8467..049e95c 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -70,7 +70,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } function upgrade(uint256 toVersion_, bytes calldata arguments_) external override whenProtocolNotPaused { - require(msg.sender == IMapleGlobalsLike(globals()).securityAdmin(), "ML:U:NOT_SECURITY_ADMIN"); + require(msg.sender == _borrower, "ML:U:NOT_BORROWER"); emit Upgraded(toVersion_, arguments_); diff --git a/tests/MapleLoan.t.sol b/tests/MapleLoan.t.sol index dc5101a..288fd8f 100644 --- a/tests/MapleLoan.t.sol +++ b/tests/MapleLoan.t.sol @@ -433,19 +433,12 @@ contract MapleLoanTests is TestUtils { } function test_upgrade_acl() external { - MockFactory factory = new MockFactory(address(globals)); - - loan.__setFactory(address(factory)); - - address securityAdmin = address(new Address()); address newImplementation = address(new MapleLoanHarness()); - globals.setSecurityAdmin(securityAdmin); - - vm.expectRevert("ML:U:NOT_SECURITY_ADMIN"); + vm.expectRevert("ML:U:NOT_BORROWER"); loan.upgrade(1, abi.encode(newImplementation)); - vm.prank(securityAdmin); + vm.prank(borrower); loan.upgrade(1, abi.encode(newImplementation)); assertEq(loan.implementation(), newImplementation); @@ -1463,23 +1456,19 @@ contract MapleLoanTests is TestUtils { } function test_upgrade_failWhenPaused() external { - MockFactory factory = new MockFactory(address(globals)); + loan.__setFactory(address(new MockFactory(address(globals)))); - loan.__setFactory(address(factory)); - - address securityAdmin = address(new Address()); address newImplementation = address(new MapleLoanHarness()); - globals.setSecurityAdmin(securityAdmin); globals.setProtocolPaused(true); - vm.prank(securityAdmin); + vm.prank(borrower); vm.expectRevert("L:PROTOCOL_PAUSED"); loan.upgrade(1, abi.encode(newImplementation)); globals.setProtocolPaused(false); - vm.prank(securityAdmin); + vm.prank(borrower); loan.upgrade(1, abi.encode(newImplementation)); assertEq(loan.implementation(), newImplementation); From e365c0bd33dae2aa177f4399b6e54b737e902af5 Mon Sep 17 00:00:00 2001 From: Joao Gabriel Carvalho Date: Wed, 15 Mar 2023 13:13:39 -0300 Subject: [PATCH 08/47] feat: Validate refinancer address on proposeNewTerms (#273) --- contracts/MapleLoan.sol | 5 +++-- contracts/interfaces/Interfaces.sol | 2 ++ tests/MapleLoan.t.sol | 2 ++ tests/MapleLoanLogic.t.sol | 19 +++++++++++++++++++ tests/MapleLoanScenarios.t.sol | 2 ++ tests/Refinancer.t.sol | 6 ++++++ tests/mocks/Mocks.sol | 9 +++++++++ 7 files changed, 43 insertions(+), 2 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index 049e95c..0e2f48d 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -217,8 +217,9 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { function proposeNewTerms(address refinancer_, uint256 deadline_, bytes[] calldata calls_) external override whenProtocolNotPaused returns (bytes32 refinanceCommitment_) { - require(msg.sender == _borrower, "ML:PNT:NOT_BORROWER"); - require(deadline_ >= block.timestamp, "ML:PNT:INVALID_DEADLINE"); + require(msg.sender == _borrower, "ML:PNT:NOT_BORROWER"); + require(deadline_ >= block.timestamp, "ML:PNT:INVALID_DEADLINE"); + require(IMapleGlobalsLike(globals()).isInstanceOf("FT_REFINANCER", refinancer_), "ML:PNT:INVALID_REFINANCER"); emit NewTermsProposed( refinanceCommitment_ = _refinanceCommitment = calls_.length > uint256(0) diff --git a/contracts/interfaces/Interfaces.sol b/contracts/interfaces/Interfaces.sol index fd78859..cd121d2 100644 --- a/contracts/interfaces/Interfaces.sol +++ b/contracts/interfaces/Interfaces.sol @@ -11,6 +11,8 @@ interface IMapleGlobalsLike { function isFactory(bytes32 factoryId_, address factory_) external view returns (bool isValid_); + function isInstanceOf(bytes32 instanceId_, address intance_) external view returns (bool isValid_); + function isPoolAsset(address poolAsset_) external view returns (bool isValid_); function mapleTreasury() external view returns (address governor_); diff --git a/tests/MapleLoan.t.sol b/tests/MapleLoan.t.sol index 288fd8f..58d69c7 100644 --- a/tests/MapleLoan.t.sol +++ b/tests/MapleLoan.t.sol @@ -34,6 +34,8 @@ contract MapleLoanTests is TestUtils { loan.__setFactory(address(factoryMock)); loan.__setFeeManager(address(feeManager)); loan.__setLender(lender); + + globals.__setIsInstanceOf(true); } /***********************************/ diff --git a/tests/MapleLoanLogic.t.sol b/tests/MapleLoanLogic.t.sol index 1f74fcc..5f7220e 100644 --- a/tests/MapleLoanLogic.t.sol +++ b/tests/MapleLoanLogic.t.sol @@ -55,6 +55,8 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { globals.setValidCollateralAsset(address(collateralAsset), true); globals.setValidPoolAsset(address(fundsAsset), true); + globals.__setIsInstanceOf(true); + vm.startPrank(address(factory)); loan = new ConstructableMapleLoan( address(factory), @@ -2222,6 +2224,8 @@ contract MapleLoanLogic_ProposeNewTermsTests is TestUtils { factory = new MockFactory(address(globals)); + globals.__setIsInstanceOf(true); + loan.__setBorrower(borrower); loan.__setFactory(address(factory)); } @@ -2258,6 +2262,19 @@ contract MapleLoanLogic_ProposeNewTermsTests is TestUtils { assertEq(proposedRefinanceCommitment, bytes32(0)); assertEq(loan.refinanceCommitment(), bytes32(0)); } + + function test_proposeNewTerms_invalidRefinancer() external { + address refinancer = address(new Address()); + + globals.__setIsInstanceOf(false); + + bytes[] memory data = new bytes[](0); + + vm.prank(borrower); + vm.expectRevert("ML:PNT:INVALID_REFINANCER"); + loan.proposeNewTerms(refinancer, block.timestamp + 1, data); + } + } contract MapleLoanLogic_RejectNewTermsTests is TestUtils { @@ -2277,6 +2294,8 @@ contract MapleLoanLogic_RejectNewTermsTests is TestUtils { loan.__setBorrower(borrower); loan.__setFactory(address(factory)); + + globals.__setIsInstanceOf(true); } function test_rejectNewTerms_commitmentMismatch_emptyCallsArray() external { diff --git a/tests/MapleLoanScenarios.t.sol b/tests/MapleLoanScenarios.t.sol index 10ed0b7..0133b33 100644 --- a/tests/MapleLoanScenarios.t.sol +++ b/tests/MapleLoanScenarios.t.sol @@ -31,6 +31,8 @@ contract MapleLoanScenariosTests is TestUtils { globals.setValidBorrower(borrower, true); globals.setValidCollateralAsset(address(token), true); globals.setValidPoolAsset(address(token), true); + + globals.__setIsInstanceOf(true); } function test_scenario_fullyAmortized() external { diff --git a/tests/Refinancer.t.sol b/tests/Refinancer.t.sol index 22b8df3..e5444f9 100644 --- a/tests/Refinancer.t.sol +++ b/tests/Refinancer.t.sol @@ -49,6 +49,8 @@ contract RefinancerTestBase is TestUtils { globals.setValidBorrower(borrower, true); globals.setValidPoolAsset(address(token), true); + + globals.__setIsInstanceOf(true); } function setUpOngoingLoan( @@ -697,6 +699,8 @@ contract RefinancerInterestTests is TestUtils { globals.setValidBorrower(borrower, true); globals.setValidPoolAsset(address(token), true); + + globals.__setIsInstanceOf(true); } function test_acceptNewTerms_makePayment_withRefinanceInterest() external { @@ -1247,6 +1251,8 @@ contract RefinancingFeesTerms is TestUtils { globals.setValidBorrower(borrower, true); globals.setValidPoolAsset(address(token), true); globals.setMapleTreasury(TREASURY); + + globals.__setIsInstanceOf(true); } function setUpOngoingLoan( diff --git a/tests/mocks/Mocks.sol b/tests/mocks/Mocks.sol index b52cfa3..6c7a52e 100644 --- a/tests/mocks/Mocks.sol +++ b/tests/mocks/Mocks.sol @@ -19,6 +19,7 @@ contract MapleGlobalsMock { mapping(address => bool) public isPoolAsset; bool internal _isFactory; + bool internal _isInstanceOf; constructor (address governor_) { governor = governor_; @@ -29,6 +30,10 @@ contract MapleGlobalsMock { return _isFactory; } + function isInstanceOf(bytes32, address) external view returns (bool) { + return _isInstanceOf; + } + function setGovernor(address governor_) external { governor = governor_; } @@ -68,6 +73,10 @@ contract MapleGlobalsMock { function __setIsFactory(bool isFactory_) external { _isFactory = isFactory_; } + + function __setIsInstanceOf(bool isInstanceOf_) external { + _isInstanceOf = isInstanceOf_; + } } From cf6a3b5b4a070525920a61b1315898e82b9b847f Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Thu, 16 Mar 2023 16:50:40 +0000 Subject: [PATCH 09/47] feat: Rename lateInterestPremiumRate variable (#274) * feat: rename lateInterest var * fix: name --- contracts/MapleLoan.sol | 18 +++++------ contracts/MapleLoanInitializer.sol | 10 +++---- contracts/MapleLoanStorage.sol | 8 ++--- contracts/Refinancer.sol | 4 +-- contracts/interfaces/IMapleLoan.sol | 2 +- contracts/interfaces/IMapleLoanEvents.sol | 2 +- .../interfaces/IMapleLoanInitializer.sol | 4 +-- contracts/interfaces/IRefinancer.sol | 12 ++++---- tests/MapleLoanLogic.t.sol | 30 +++++++++---------- tests/Refinancer.t.sol | 12 ++++---- tests/harnesses/MapleLoanHarnesses.sol | 4 +-- 11 files changed, 53 insertions(+), 53 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index 0e2f48d..3d1a6aa 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -502,7 +502,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { _paymentsRemaining, _interestRate, _lateFeeRate, - _lateInterestPremium + _lateInterestPremiumRate ); } @@ -519,7 +519,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { _paymentsRemaining, _interestRate, _lateFeeRate, - _lateInterestPremium + _lateInterestPremiumRate ); interest_ = interestArray_[0] + interestArray_[1] + interestArray_[2]; @@ -536,7 +536,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { _paymentsRemaining, _nextPaymentDueDate, _lateFeeRate, - _lateInterestPremium + _lateInterestPremiumRate ); } @@ -621,8 +621,8 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { return _lateFeeRate; } - function lateInterestPremium() external view override returns (uint256 lateInterestPremium_) { - return _lateInterestPremium; + function lateInterestPremiumRate() external view override returns (uint256 lateInterestPremiumRate_) { + return _lateInterestPremiumRate; } function lender() external view override returns (address lender_) { @@ -682,10 +682,10 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { _gracePeriod = uint256(0); _paymentInterval = uint256(0); - _interestRate = uint256(0); - _closingRate = uint256(0); - _lateFeeRate = uint256(0); - _lateInterestPremium = uint256(0); + _interestRate = uint256(0); + _closingRate = uint256(0); + _lateFeeRate = uint256(0); + _lateInterestPremiumRate = uint256(0); _endingPrincipal = uint256(0); diff --git a/contracts/MapleLoanInitializer.sol b/contracts/MapleLoanInitializer.sol index 143a1af..38972f3 100644 --- a/contracts/MapleLoanInitializer.sol +++ b/contracts/MapleLoanInitializer.sol @@ -83,7 +83,7 @@ contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { * [0]: interestRate, * [1]: closingFeeRate, * [2]: lateFeeRate, - * [3]: lateInterestPremium, + * [3]: lateInterestPremiumRate, * @param fees_ Array of fees: * [0]: delegateOriginationFee, * [1]: delegateServiceFee @@ -137,10 +137,10 @@ contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { _principalRequested = amounts_[1]; _endingPrincipal = amounts_[2]; - _interestRate = rates_[0]; - _closingRate = rates_[1]; - _lateFeeRate = rates_[2]; - _lateInterestPremium = rates_[3]; + _interestRate = rates_[0]; + _closingRate = rates_[1]; + _lateFeeRate = rates_[2]; + _lateInterestPremiumRate = rates_[3]; // Set fees for the loan. IMapleLoanFeeManager(feeManager_).updateDelegateFeeTerms(fees_[0], fees_[1]); diff --git a/contracts/MapleLoanStorage.sol b/contracts/MapleLoanStorage.sol index 0223944..ba041a7 100644 --- a/contracts/MapleLoanStorage.sol +++ b/contracts/MapleLoanStorage.sol @@ -19,10 +19,10 @@ abstract contract MapleLoanStorage { uint256 internal _paymentInterval; // The number of seconds between payments. // Rates - uint256 internal _interestRate; // The annualized interest rate of the loan. - uint256 internal _closingRate; // The fee rate (applied to principal) to close the loan. - uint256 internal _lateFeeRate; // The fee rate for late payments. - uint256 internal _lateInterestPremium; // The amount to increase the interest rate by for late payments. + uint256 internal _interestRate; // The annualized interest rate of the loan. + uint256 internal _closingRate; // The fee rate (applied to principal) to close the loan. + uint256 internal _lateFeeRate; // The fee rate for late payments. + uint256 internal _lateInterestPremiumRate; // The amount to increase the interest rate by for late payments. // Requested Amounts uint256 internal _collateralRequired; // The collateral the borrower is expected to put up to draw down all _principalRequested. diff --git a/contracts/Refinancer.sol b/contracts/Refinancer.sol index f09ec1d..49f70f9 100644 --- a/contracts/Refinancer.sol +++ b/contracts/Refinancer.sol @@ -58,8 +58,8 @@ contract Refinancer is IRefinancer, MapleLoanStorage { emit LateFeeRateSet(_lateFeeRate = lateFeeRate_); } - function setLateInterestPremium(uint256 lateInterestPremium_) external override { - emit LateInterestPremiumSet(_lateInterestPremium = lateInterestPremium_); + function setLateInterestPremiumRate(uint256 lateInterestPremiumRate_) external override { + emit LateInterestPremiumRateSet(_lateInterestPremiumRate = lateInterestPremiumRate_); } function setPaymentInterval(uint256 paymentInterval_) external override { diff --git a/contracts/interfaces/IMapleLoan.sol b/contracts/interfaces/IMapleLoan.sol index 3a5ac09..8905b0f 100644 --- a/contracts/interfaces/IMapleLoan.sol +++ b/contracts/interfaces/IMapleLoan.sol @@ -89,7 +89,7 @@ interface IMapleLoan is IMapleProxied, IMapleLoanEvents { /** * @dev The premium over the regular interest rate applied when paying late. */ - function lateInterestPremium() external view returns (uint256 lateInterestPremium_); + function lateInterestPremiumRate() external view returns (uint256 lateInterestPremiumRate_); /** * @dev The lender of the Loan. diff --git a/contracts/interfaces/IMapleLoanEvents.sol b/contracts/interfaces/IMapleLoanEvents.sol index 84f673a..7cf15bf 100644 --- a/contracts/interfaces/IMapleLoanEvents.sol +++ b/contracts/interfaces/IMapleLoanEvents.sol @@ -71,7 +71,7 @@ interface IMapleLoanEvents { * [0]: interestRate, * [1]: closingFeeRate, * [2]: lateFeeRate, - * [3]: lateInterestPremium + * [3]: lateInterestPremiumRate * @param fees_ Array of fees: * [0]: delegateOriginationFee, * [1]: delegateServiceFee diff --git a/contracts/interfaces/IMapleLoanInitializer.sol b/contracts/interfaces/IMapleLoanInitializer.sol index d509bcc..20ef76c 100644 --- a/contracts/interfaces/IMapleLoanInitializer.sol +++ b/contracts/interfaces/IMapleLoanInitializer.sol @@ -25,7 +25,7 @@ interface IMapleLoanInitializer is IMapleLoanEvents { * [0]: interestRate, * [1]: closingFeeRate, * [2]: lateFeeRate, - * [3]: lateInterestPremium, + * [3]: lateInterestPremiumRate, * @param fees_ Array of fees: * [0]: delegateOriginationFee, * [1]: delegateServiceFee @@ -61,7 +61,7 @@ interface IMapleLoanInitializer is IMapleLoanEvents { * [0]: interestRate, * [1]: closingFeeRate, * [2]: lateFeeRate, - * [3]: lateInterestPremium, + * [3]: lateInterestPremiumRate, * @return fees_ Array of fees: * [0]: delegateOriginationFee, * [1]: delegateServiceFee diff --git a/contracts/interfaces/IRefinancer.sol b/contracts/interfaces/IRefinancer.sol index 50e5bab..09b3739 100644 --- a/contracts/interfaces/IRefinancer.sol +++ b/contracts/interfaces/IRefinancer.sol @@ -45,10 +45,10 @@ interface IRefinancer { event LateFeeRateSet(uint256 lateFeeRate_); /** - * @dev A new value for lateInterestPremium has been set. - * @param lateInterestPremium_ The new value for lateInterestPremium. + * @dev A new value for lateInterestPremiumRate has been set. + * @param lateInterestPremiumRate_ The new value for lateInterestPremiumRate. */ - event LateInterestPremiumSet(uint256 lateInterestPremium_); + event LateInterestPremiumRateSet(uint256 lateInterestPremiumRate_); /** * @dev A new value for paymentInterval has been set. @@ -116,10 +116,10 @@ interface IRefinancer { function setLateFeeRate(uint256 lateFeeRate_) external; /** - * @dev Function to set the lateInterestPremium during a refinance. - * @param lateInterestPremium_ The new value for lateInterestPremium. + * @dev Function to set the lateInterestPremiumRate during a refinance. + * @param lateInterestPremiumRate_ The new value for lateInterestPremiumRate. */ - function setLateInterestPremium(uint256 lateInterestPremium_) external; + function setLateInterestPremiumRate(uint256 lateInterestPremiumRate_) external; /** * @dev Function to set the paymentInterval during a refinance. diff --git a/tests/MapleLoanLogic.t.sol b/tests/MapleLoanLogic.t.sol index 5f7220e..bc8a059 100644 --- a/tests/MapleLoanLogic.t.sol +++ b/tests/MapleLoanLogic.t.sol @@ -1212,7 +1212,7 @@ contract MapleLoanLogic_GetNextPaymentBreakdownTests is TestUtils { loan.__setPaymentsRemaining(paymentsRemaining_); loan.__setInterestRate(interestRate_); loan.__setLateFeeRate(lateFeeRate_); - loan.__setLateInterestPremium(lateInterestPremium_); + loan.__setLateInterestPremiumRate(lateInterestPremium_); loan.__setRefinanceInterest(refinanceInterest_); loan.__setFeeManager(address(new MockFeeManager())); @@ -1644,10 +1644,10 @@ contract MapleLoanLogic_InitializeTests is TestUtils { assertEq(loan.principalRequested(), defaultAmounts[1]); assertEq(loan.endingPrincipal(), defaultAmounts[2]); - assertEq(loan.interestRate(), defaultRates[0]); - assertEq(loan.closingRate(), defaultRates[1]); - assertEq(loan.lateFeeRate(), defaultRates[2]); - assertEq(loan.lateInterestPremium(), defaultRates[3]); + assertEq(loan.interestRate(), defaultRates[0]); + assertEq(loan.closingRate(), defaultRates[1]); + assertEq(loan.lateFeeRate(), defaultRates[2]); + assertEq(loan.lateInterestPremiumRate(), defaultRates[3]); } function test_initialize_invalidPrincipal() external { @@ -1992,16 +1992,16 @@ contract MapleLoanLogic_MakePaymentTests is TestUtils { assertEq(loan.drawableFunds(), 0); // Make sure loan accounting is cleared from _clearLoanAccounting(). - assertEq(loan.gracePeriod(), 0); - assertEq(loan.paymentInterval(), 0); - assertEq(loan.interestRate(), 0); - assertEq(loan.closingRate(), 0); - assertEq(loan.lateFeeRate(), 0); - assertEq(loan.lateInterestPremium(), 0); - assertEq(loan.endingPrincipal(), 0); - assertEq(loan.nextPaymentDueDate(), 0); - assertEq(loan.paymentsRemaining(), 0); - assertEq(loan.principal(), 0); + assertEq(loan.gracePeriod(), 0); + assertEq(loan.paymentInterval(), 0); + assertEq(loan.interestRate(), 0); + assertEq(loan.closingRate(), 0); + assertEq(loan.lateFeeRate(), 0); + assertEq(loan.lateInterestPremiumRate(), 0); + assertEq(loan.endingPrincipal(), 0); + assertEq(loan.nextPaymentDueDate(), 0); + assertEq(loan.paymentsRemaining(), 0); + assertEq(loan.principal(), 0); } function test_makePayment_withRefinanceInterest( diff --git a/tests/Refinancer.t.sol b/tests/Refinancer.t.sol index e5444f9..8fa4432 100644 --- a/tests/Refinancer.t.sol +++ b/tests/Refinancer.t.sol @@ -520,7 +520,7 @@ contract RefinancerFeeTests is RefinancerTestBase { uint256 lateFeeRate_, uint256 paymentInterval_, uint256 paymentsRemaining_, - uint256 newLateInterestPremium_, + uint256 newLateInterestPremiumRate_, uint256 deadline_ ) external @@ -544,12 +544,12 @@ contract RefinancerFeeTests is RefinancerTestBase { paymentsRemaining_ ); - newLateInterestPremium_ = constrictToRange(newLateInterestPremium_, 0, MAX_RATE); - deadline_ = constrictToRange(deadline_, block.timestamp, type(uint256).max); + newLateInterestPremiumRate_ = constrictToRange(newLateInterestPremiumRate_, 0, MAX_RATE); + deadline_ = constrictToRange(deadline_, block.timestamp, type(uint256).max); - assertEq(loan.lateInterestPremium(), 0); + assertEq(loan.lateInterestPremiumRate(), 0); - bytes[] memory data = _encodeWithSignatureAndUint("setLateInterestPremium(uint256)", newLateInterestPremium_); + bytes[] memory data = _encodeWithSignatureAndUint("setLateInterestPremiumRate(uint256)", newLateInterestPremiumRate_); vm.prank(borrower); loan.proposeNewTerms(address(refinancer), deadline_, data); @@ -557,7 +557,7 @@ contract RefinancerFeeTests is RefinancerTestBase { vm.prank(address(lender)); loan.acceptNewTerms(address(refinancer), deadline_, data); - assertEq(loan.lateInterestPremium(), newLateInterestPremium_); + assertEq(loan.lateInterestPremiumRate(), newLateInterestPremiumRate_); } } diff --git a/tests/harnesses/MapleLoanHarnesses.sol b/tests/harnesses/MapleLoanHarnesses.sol index b0ba4ad..6f9bb33 100644 --- a/tests/harnesses/MapleLoanHarnesses.sol +++ b/tests/harnesses/MapleLoanHarnesses.sol @@ -78,8 +78,8 @@ contract MapleLoanHarness is MapleLoan { _lateFeeRate = lateFeeRate_; } - function __setLateInterestPremium(uint256 lateInterestPremium_) external { - _lateInterestPremium = lateInterestPremium_; + function __setLateInterestPremiumRate(uint256 lateInterestPremiumRate_) external { + _lateInterestPremiumRate = lateInterestPremiumRate_; } function __setLender(address lender_) external { From 67aba066b06fd9f64cb8048fd608bc97e4eaea12 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Thu, 16 Mar 2023 13:14:03 -0400 Subject: [PATCH 10/47] ci: Add coverage (#272) --- .github/workflows/forge-pr.yaml | 27 +++++++++++++++++++++++++++ .github/workflows/forge.yml | 27 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/.github/workflows/forge-pr.yaml b/.github/workflows/forge-pr.yaml index 9dddc1c..ff9f801 100644 --- a/.github/workflows/forge-pr.yaml +++ b/.github/workflows/forge-pr.yaml @@ -20,3 +20,30 @@ jobs: - name: Run forge tests run: ./scripts/test.sh -p deep + + coverage_report: + name: Generate coverage report + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install Foundry + uses: onbjerg/foundry-toolchain@v1 + with: + version: nightly + + - name: Install submodules + run: | + git config --global url."https://github.com/".insteadOf "git@github.com:" + git submodule update --init --recursive + - name: Generate coverage report + run: | + forge coverage --report lcov + - name: Report code coverage + uses: zgosalvez/github-actions-report-lcov@v1 + with: + coverage-files: lcov.info + minimum-coverage: 90 + artifact-name: code-coverage-report + github-token: ${{ secrets.GITHUB_TOKEN }} + working-directory: ./ diff --git a/.github/workflows/forge.yml b/.github/workflows/forge.yml index d58f9a0..9c7cb6b 100644 --- a/.github/workflows/forge.yml +++ b/.github/workflows/forge.yml @@ -23,3 +23,30 @@ jobs: - name: Run forge tests run: ./scripts/test.sh -p super_deep + + coverage_report: + name: Generate coverage report + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install Foundry + uses: onbjerg/foundry-toolchain@v1 + with: + version: nightly + + - name: Install submodules + run: | + git config --global url."https://github.com/".insteadOf "git@github.com:" + git submodule update --init --recursive + - name: Generate coverage report + run: | + forge coverage --report lcov + - name: Report code coverage + uses: zgosalvez/github-actions-report-lcov@v1 + with: + coverage-files: lcov.info + minimum-coverage: 90 + artifact-name: code-coverage-report + github-token: ${{ secrets.GITHUB_TOKEN }} + working-directory: ./ From c99c0c3cddf196b0fb1a20cb17b06af197d53e33 Mon Sep 17 00:00:00 2001 From: Michael De Luca <35537333+deluca-mike@users.noreply.github.com> Date: Fri, 17 Mar 2023 19:58:36 -0400 Subject: [PATCH 11/47] feat: Prevent empty calls in `proposeNewTerms` (#275) --- contracts/MapleLoan.sol | 5 ++--- tests/MapleLoanLogic.t.sol | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index 3d1a6aa..cc2c31e 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -220,11 +220,10 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { require(msg.sender == _borrower, "ML:PNT:NOT_BORROWER"); require(deadline_ >= block.timestamp, "ML:PNT:INVALID_DEADLINE"); require(IMapleGlobalsLike(globals()).isInstanceOf("FT_REFINANCER", refinancer_), "ML:PNT:INVALID_REFINANCER"); + require(calls_.length > uint256(0), "ML:PNT:EMPTY_CALLS"); emit NewTermsProposed( - refinanceCommitment_ = _refinanceCommitment = calls_.length > uint256(0) - ? _getRefinanceCommitment(refinancer_, deadline_, calls_) - : bytes32(0), + _refinanceCommitment = refinanceCommitment_ = _getRefinanceCommitment(refinancer_, deadline_, calls_), refinancer_, deadline_, calls_ diff --git a/tests/MapleLoanLogic.t.sol b/tests/MapleLoanLogic.t.sol index bc8a059..32dd526 100644 --- a/tests/MapleLoanLogic.t.sol +++ b/tests/MapleLoanLogic.t.sol @@ -2257,10 +2257,8 @@ contract MapleLoanLogic_ProposeNewTermsTests is TestUtils { bytes[] memory data = new bytes[](0); vm.prank(borrower); + vm.expectRevert("ML:PNT:EMPTY_CALLS"); bytes32 proposedRefinanceCommitment = loan.proposeNewTerms(refinancer_, deadline_, data); - - assertEq(proposedRefinanceCommitment, bytes32(0)); - assertEq(loan.refinanceCommitment(), bytes32(0)); } function test_proposeNewTerms_invalidRefinancer() external { From d852481b98bb25ec539e53420a432302845a290a Mon Sep 17 00:00:00 2001 From: Joao Gabriel Carvalho Date: Thu, 30 Mar 2023 09:07:15 -0300 Subject: [PATCH 12/47] fix: Internal audit findings (SC-11782) (#276) * fix: internal audit findings * ci: update coverage threshold --------- Co-authored-by: lucas-manuel --- .github/workflows/forge-pr.yaml | 2 +- .github/workflows/forge.yml | 2 +- contracts/MapleLoan.sol | 2 +- contracts/interfaces/Interfaces.sol | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/forge-pr.yaml b/.github/workflows/forge-pr.yaml index ff9f801..478c6fc 100644 --- a/.github/workflows/forge-pr.yaml +++ b/.github/workflows/forge-pr.yaml @@ -43,7 +43,7 @@ jobs: uses: zgosalvez/github-actions-report-lcov@v1 with: coverage-files: lcov.info - minimum-coverage: 90 + minimum-coverage: 65 artifact-name: code-coverage-report github-token: ${{ secrets.GITHUB_TOKEN }} working-directory: ./ diff --git a/.github/workflows/forge.yml b/.github/workflows/forge.yml index 9c7cb6b..2d729df 100644 --- a/.github/workflows/forge.yml +++ b/.github/workflows/forge.yml @@ -46,7 +46,7 @@ jobs: uses: zgosalvez/github-actions-report-lcov@v1 with: coverage-files: lcov.info - minimum-coverage: 90 + minimum-coverage: 65 artifact-name: code-coverage-report github-token: ${{ secrets.GITHUB_TOKEN }} working-directory: ./ diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index cc2c31e..8c0de78 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -343,7 +343,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { require(msg.sender == lender_, "ML:FL:NOT_LENDER"); - // Can only fund loan if there are payments remaining (as defined by the initialization) and no payment is due yet (as set by a funding). + // Can only fund loan if there are payments remaining (defined in the initialization) and no payment is due (as set by a funding). require((_nextPaymentDueDate == uint256(0)) && (_paymentsRemaining != uint256(0)), "ML:FL:LOAN_ACTIVE"); address fundsAsset_ = _fundsAsset; diff --git a/contracts/interfaces/Interfaces.sol b/contracts/interfaces/Interfaces.sol index cd121d2..ba38bbb 100644 --- a/contracts/interfaces/Interfaces.sol +++ b/contracts/interfaces/Interfaces.sol @@ -11,7 +11,7 @@ interface IMapleGlobalsLike { function isFactory(bytes32 factoryId_, address factory_) external view returns (bool isValid_); - function isInstanceOf(bytes32 instanceId_, address intance_) external view returns (bool isValid_); + function isInstanceOf(bytes32 instanceId_, address instance_) external view returns (bool isValid_); function isPoolAsset(address poolAsset_) external view returns (bool isValid_); From 17fe1d0b2d7b094441aa842f67a48a045f988864 Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Fri, 31 Mar 2023 17:57:47 +0100 Subject: [PATCH 13/47] feat: Update to use isInstanceOf in lieu of isFactory (SC-11812) (#278) * feat: update to use isInstanceOf in leiu of isFactory * feat: update key * feat: update key * chore: update var name --- contracts/MapleLoanInitializer.sol | 4 ++-- contracts/interfaces/Interfaces.sol | 2 +- tests/MapleLoan.t.sol | 2 ++ tests/MapleLoanFactory.t.sol | 6 ++++-- tests/MapleLoanFeeManager.t.sol | 1 + tests/MapleLoanLogic.t.sol | 4 ++++ tests/Payments.t.sol | 2 ++ 7 files changed, 16 insertions(+), 5 deletions(-) diff --git a/contracts/MapleLoanInitializer.sol b/contracts/MapleLoanInitializer.sol index 38972f3..c29b02f 100644 --- a/contracts/MapleLoanInitializer.sol +++ b/contracts/MapleLoanInitializer.sol @@ -121,8 +121,8 @@ contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { address loanManagerFactory_ = ILenderLike(lender_).factory(); - require(IMapleGlobalsLike(globals_).isFactory("LOAN_MANAGER", loanManagerFactory_), "MLI:I:INVALID_FACTORY"); - require(IMapleProxyFactoryLike(loanManagerFactory_).isInstance(lender_), "MLI:I:INVALID_INSTANCE"); + require(IMapleGlobalsLike(globals_).isInstanceOf("FT_LOAN_MANAGER_FACTORY", loanManagerFactory_), "MLI:I:INVALID_FACTORY"); + require(IMapleProxyFactoryLike(loanManagerFactory_).isInstance(lender_), "MLI:I:INVALID_INSTANCE"); require((_feeManager = feeManager_) != address(0), "MLI:I:INVALID_MANAGER"); diff --git a/contracts/interfaces/Interfaces.sol b/contracts/interfaces/Interfaces.sol index ba38bbb..8709242 100644 --- a/contracts/interfaces/Interfaces.sol +++ b/contracts/interfaces/Interfaces.sol @@ -11,7 +11,7 @@ interface IMapleGlobalsLike { function isFactory(bytes32 factoryId_, address factory_) external view returns (bool isValid_); - function isInstanceOf(bytes32 instanceId_, address instance_) external view returns (bool isValid_); + function isInstanceOf(bytes32 instanceId_, address instance_) external view returns (bool isInstance_); function isPoolAsset(address poolAsset_) external view returns (bool isValid_); diff --git a/tests/MapleLoan.t.sol b/tests/MapleLoan.t.sol index 58d69c7..77258ae 100644 --- a/tests/MapleLoan.t.sol +++ b/tests/MapleLoan.t.sol @@ -1507,6 +1507,8 @@ contract MapleLoanRoleTests is TestUtils { globals.setValidBorrower(borrower, true); globals.setValidCollateralAsset(address(token), true); globals.setValidPoolAsset(address(token), true); + + globals.__setIsInstanceOf(true); vm.prank(address(factory)); loan = new ConstructableMapleLoan(address(factory), borrower, lender, address(feeManager), assets, termDetails, amounts, rates, fees); diff --git a/tests/MapleLoanFactory.t.sol b/tests/MapleLoanFactory.t.sol index 87f382b..e542aff 100644 --- a/tests/MapleLoanFactory.t.sol +++ b/tests/MapleLoanFactory.t.sol @@ -38,6 +38,8 @@ contract MapleLoanFactoryTest is TestUtils { globals.setValidCollateralAsset(address(1), true); globals.setValidPoolAsset(address(1), true); + globals.__setIsInstanceOf(true); + vm.startPrank(governor); factory.registerImplementation(1, implementation, initializer); factory.setDefaultVersion(1); @@ -153,12 +155,12 @@ contract MapleLoanFactoryTest is TestUtils { fees ); - globals.__setIsFactory(false); + globals.__setIsInstanceOf(false); vm.expectRevert("MPF:CI:FAILED"); factory.createInstance(arguments, "SALT"); - globals.__setIsFactory(true); + globals.__setIsInstanceOf(true); factory.createInstance(arguments, "SALT"); } diff --git a/tests/MapleLoanFeeManager.t.sol b/tests/MapleLoanFeeManager.t.sol index 23aa36b..b595f72 100644 --- a/tests/MapleLoanFeeManager.t.sol +++ b/tests/MapleLoanFeeManager.t.sol @@ -55,6 +55,7 @@ contract FeeManagerBase is TestUtils { factory.setDefaultVersion(1); globals.setMapleTreasury(TREASURY); + globals.__setIsInstanceOf(true); globals.setValidBorrower(BORROWER, true); globals.setValidCollateralAsset(address(collateralAsset), true); globals.setValidPoolAsset(address(fundsAsset), true); diff --git a/tests/MapleLoanLogic.t.sol b/tests/MapleLoanLogic.t.sol index 32dd526..7c04a2a 100644 --- a/tests/MapleLoanLogic.t.sol +++ b/tests/MapleLoanLogic.t.sol @@ -1061,6 +1061,8 @@ contract MapleLoanLogic_GetClosingPaymentBreakdownTests is TestUtils { globals.setValidBorrower(defaultBorrower, true); globals.setValidCollateralAsset(address(token1), true); globals.setValidPoolAsset(address(token2), true); + + globals.__setIsInstanceOf(true); } function test_getClosingPaymentBreakdown(uint256 principal_, uint256 closingRate_, uint256 refinanceInterest_) external { @@ -1615,6 +1617,8 @@ contract MapleLoanLogic_InitializeTests is TestUtils { globals.setValidBorrower(defaultBorrower, true); globals.setValidCollateralAsset(address(token1), true); globals.setValidPoolAsset(address(token2), true); + + globals.__setIsInstanceOf(true); } function test_initialize() external { diff --git a/tests/Payments.t.sol b/tests/Payments.t.sol index 488afbd..21fa7ad 100644 --- a/tests/Payments.t.sol +++ b/tests/Payments.t.sol @@ -57,6 +57,8 @@ contract MapleLoanPaymentsTestBase is TestUtils { globals.setValidCollateralAsset(address(collateralAsset), true); globals.setValidPoolAsset(address(fundsAsset), true); + globals.__setIsInstanceOf(true); + vm.startPrank(governor); factory.registerImplementation(1, address(implementation), address(initializer)); factory.setDefaultVersion(1); From f7f33eba6eef235eab4056d21e7a8db74eeb15e5 Mon Sep 17 00:00:00 2001 From: Joao Gabriel Carvalho Date: Tue, 4 Apr 2023 18:16:36 -0300 Subject: [PATCH 14/47] feat: Add V5 migrator and tests (SC-11796) (#279) * feat: add V5 migrator and tests * fix: missing word on comment * test: remove loan v4 mock * fix: update rates in tests as well as use scaled precision in payments calc * fix: tests after rate change * test: added test for initializer * fix: adjust PR comments --------- Co-authored-by: lucas-manuel --- contracts/MapleLoan.sol | 26 ++-- contracts/MapleLoanV4Migrator.sol | 35 ----- contracts/MapleLoanV5Migrator.sol | 19 +++ contracts/interfaces/IMapleLoanV4Migrator.sol | 10 -- contracts/interfaces/IOwnable.sol | 40 ------ tests/InitializerAndMigrator.t.sol | 127 ++++++++++++++++++ tests/MapleLoanFeeManager.t.sol | 2 +- tests/MapleLoanLogic.t.sol | 54 ++++---- tests/MapleLoanScenarios.t.sol | 6 +- tests/Payments.t.sol | 26 ++-- tests/Refinancer.t.sol | 30 ++--- 11 files changed, 219 insertions(+), 156 deletions(-) delete mode 100644 contracts/MapleLoanV4Migrator.sol create mode 100644 contracts/MapleLoanV5Migrator.sol delete mode 100644 contracts/interfaces/IMapleLoanV4Migrator.sol delete mode 100644 contracts/interfaces/IOwnable.sol create mode 100644 tests/InitializerAndMigrator.t.sol diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index 8c0de78..da90c75 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -15,19 +15,21 @@ import { MapleLoanStorage } from "./MapleLoanStorage.sol"; /* - ███╗ ███╗ █████╗ ██████╗ ██╗ ███████╗ ██╗ ██████╗ █████╗ ███╗ ██╗ ██╗ ██╗██╗ ██╗ - ████╗ ████║██╔══██╗██╔══██╗██║ ██╔════╝ ██║ ██╔═══██╗██╔══██╗████╗ ██║ ██║ ██║██║ ██║ - ██╔████╔██║███████║██████╔╝██║ █████╗ ██║ ██║ ██║███████║██╔██╗ ██║ ██║ ██║███████║ + ███╗ ███╗ █████╗ ██████╗ ██╗ ███████╗ ██╗ ██████╗ █████╗ ███╗ ██╗ ██╗ ██╗███████╗ + ████╗ ████║██╔══██╗██╔══██╗██║ ██╔════╝ ██║ ██╔═══██╗██╔══██╗████╗ ██║ ██║ ██║██╔════╝ + ██╔████╔██║███████║██████╔╝██║ █████╗ ██║ ██║ ██║███████║██╔██╗ ██║ ██║ ██║███████╗ ██║╚██╔╝██║██╔══██║██╔═══╝ ██║ ██╔══╝ ██║ ██║ ██║██╔══██║██║╚██╗██║ ╚██╗ ██╔╝╚════██║ - ██║ ╚═╝ ██║██║ ██║██║ ███████╗███████╗ ███████╗╚██████╔╝██║ ██║██║ ╚████║ ╚████╔╝ ██║ - ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═══╝ ╚═╝ + ██║ ╚═╝ ██║██║ ██║██║ ███████╗███████╗ ███████╗╚██████╔╝██║ ██║██║ ╚████║ ╚████╔╝ ███████║ + ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═══╝ ╚══════╝ + */ /// @title MapleLoan implements a primitive loan with additional functionality, and is intended to be proxied. contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { - uint256 private constant SCALED_ONE = uint256(10 ** 18); + uint256 private constant HUNDRED_PERCENT = 1e6; + uint256 private constant SCALED_ONE = 1e18; modifier limitDrawableUse() { if (msg.sender == _borrower) { @@ -485,7 +487,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { fees_ = delegateServiceFee_ + platformServiceFee_ + delegateRefinanceFee_ + platformRefinanceFee_; // Compute interest and include any uncaptured interest from refinance. - interest_ = (((principal_ = _principal) * _closingRate) / SCALED_ONE) + _refinanceInterest; + interest_ = (((principal_ = _principal) * _closingRate) / HUNDRED_PERCENT) + _refinanceInterest; } function getNextPaymentDetailedBreakdown() @@ -749,8 +751,8 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { * - Both of these rates are scaled by 1e18 (e.g., 12% => 0.12 * 10 ** 18) * \*************************************************************************************************/ - uint256 periodicRate = _getPeriodicInterestRate(interestRate_, paymentInterval_); - uint256 raisedRate = _scaledExponent(SCALED_ONE + periodicRate, totalPayments_, SCALED_ONE); + uint256 periodicRate = _getPeriodicInterestRate(interestRate_, paymentInterval_); // 1e18 decimal precision + uint256 raisedRate = _scaledExponent(SCALED_ONE + periodicRate, totalPayments_, SCALED_ONE); // 1e18 decimal precision // NOTE: If a lack of precision in `_scaledExponent` results in a `raisedRate` smaller than one, // assume it to be one and simplify the equation. @@ -874,12 +876,12 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { uint256 fullDaysLate = ((currentTime_ - nextPaymentDueDate_ + (1 days - 1)) / 1 days) * 1 days; lateInterest_ += _getInterest(principal_, interestRate_ + lateInterestPremium_, fullDaysLate); - lateInterest_ += (lateFeeRate_ * principal_) / SCALED_ONE; + lateInterest_ += (lateFeeRate_ * principal_) / HUNDRED_PERCENT; } - /// @dev Returns the interest rate over an interval, given an annualized interest rate. + /// @dev Returns the interest rate over an interval, given an annualized interest rate, scaled to 1e18. function _getPeriodicInterestRate(uint256 interestRate_, uint256 interval_) internal pure returns (uint256 periodicInterestRate_) { - return (interestRate_ * interval_) / uint256(365 days); + return (interestRate_ * (SCALED_ONE / HUNDRED_PERCENT) * interval_) / uint256(365 days); } /// @dev Returns refinance commitment given refinance parameters. diff --git a/contracts/MapleLoanV4Migrator.sol b/contracts/MapleLoanV4Migrator.sol deleted file mode 100644 index 5a101d8..0000000 --- a/contracts/MapleLoanV4Migrator.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.7; - -import { IERC20 } from "../modules/erc20/contracts/interfaces/IERC20.sol"; - -import { IMapleFeeManagerLike } from "./interfaces/Interfaces.sol"; -import { IMapleLoanV4Migrator } from "./interfaces/IMapleLoanV4Migrator.sol"; - -import { MapleLoanStorage } from "./MapleLoanStorage.sol"; - -/// @title MapleLoanV4Migrator is intended to initialize the storage of a MapleLoan proxy. -contract MapleLoanV4Migrator is IMapleLoanV4Migrator, MapleLoanStorage { - - function encodeArguments(address feeManager_) external pure override returns (bytes memory encodedArguments_) { - return abi.encode(feeManager_); - } - - function decodeArguments(bytes calldata encodedArguments_) public pure override returns (address feeManager_) { - ( feeManager_ ) = abi.decode(encodedArguments_, (address)); - } - - fallback() external { - // Taking the feeManager_ address as argument for now - // but ideally this would be hardcoded in the loan migrator registered in the factory. - ( address feeManager_ ) = decodeArguments(msg.data); - - _feeManager = feeManager_; - - IMapleFeeManagerLike(feeManager_).updateDelegateFeeTerms(0, __deprecated_delegateFee); - IMapleFeeManagerLike(feeManager_).updatePlatformServiceFee(_principalRequested, _paymentInterval); - - IERC20(_fundsAsset).approve(_feeManager, type(uint256).max); - } - -} diff --git a/contracts/MapleLoanV5Migrator.sol b/contracts/MapleLoanV5Migrator.sol new file mode 100644 index 0000000..a6489c8 --- /dev/null +++ b/contracts/MapleLoanV5Migrator.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.7; + +import { MapleLoanStorage } from "./MapleLoanStorage.sol"; + +/// @title MapleLoanV5Migrator is to adjust all the rates to 1e6 precision. +contract MapleLoanV5Migrator is MapleLoanStorage { + + uint256 private constant HUNDRED_PERCENT = 1e6; + uint256 private constant SCALED_ONE = 1e18; + + fallback() external { + _interestRate /= (SCALED_ONE / HUNDRED_PERCENT); + _closingRate /= (SCALED_ONE / HUNDRED_PERCENT); + _lateFeeRate /= (SCALED_ONE / HUNDRED_PERCENT); + _lateInterestPremiumRate /= (SCALED_ONE / HUNDRED_PERCENT); + } + +} diff --git a/contracts/interfaces/IMapleLoanV4Migrator.sol b/contracts/interfaces/IMapleLoanV4Migrator.sol deleted file mode 100644 index 41467e3..0000000 --- a/contracts/interfaces/IMapleLoanV4Migrator.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.7; - -interface IMapleLoanV4Migrator { - - function encodeArguments(address feeManager_) external returns (bytes memory encodedArguments_); - - function decodeArguments(bytes calldata encodedArguments_) external returns (address feeManager_); - -} diff --git a/contracts/interfaces/IOwnable.sol b/contracts/interfaces/IOwnable.sol deleted file mode 100644 index ee97414..0000000 --- a/contracts/interfaces/IOwnable.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.7; - -/// @title Ownable interface. -interface IOwnable { - - /** - * @dev Ownership of the contact has been transferred. - * @param owner_ The address that accepted ownership of the contract. - */ - event OwnershipAccepted(address indexed owner_); - - /** - * @dev Ownership of the contact has been transferred and is pending acceptance. - * @param account_ The address that can accept ownership. - */ - event OwnershipTransferPending(address indexed account_); - - /** - * @dev Accept the ownership of the contract. - */ - function acceptOwnership() external; - - /** - * @dev The owner of the contract. - */ - function owner() external view returns (address owner_); - - /** - * @dev The account that can accept ownership. - */ - function pendingOwner() external view returns (address owner_); - - /** - * @dev Transfer the ownership of the contract to an account. - * @param account_ The address to transfer ownership of the contract to. - */ - function transferOwnership(address account_) external; - -} diff --git a/tests/InitializerAndMigrator.t.sol b/tests/InitializerAndMigrator.t.sol new file mode 100644 index 0000000..4ba9ba5 --- /dev/null +++ b/tests/InitializerAndMigrator.t.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.7; + +import { Address, TestUtils } from "../modules/contract-test-utils/contracts/test.sol"; +import { MockERC20 } from "../modules/erc20/contracts/test/mocks/MockERC20.sol"; + +import { MapleLoan } from "../contracts/MapleLoan.sol"; +import { MapleLoanFactory} from "../contracts/MapleLoanFactory.sol"; +import { MapleLoanInitializer } from "../contracts/MapleLoanInitializer.sol"; +import { MapleLoanV5Migrator } from "../contracts/MapleLoanV5Migrator.sol"; + +import { MapleGlobalsMock, MockFeeManager, MockLoanManager, MockLoanManagerFactory } from "./mocks/Mocks.sol"; + +contract MapleLoanInitializerAndMigratorTests is TestUtils { + + address internal governor = address(new Address()); + address internal implementation4; + address internal implementation5; + address internal initializer; + + MapleGlobalsMock globals; + MockFeeManager feeManager; + MapleLoan loan; + MapleLoanFactory factory; + MapleLoanV5Migrator migrator; + MockERC20 asset; + MockLoanManager lender; + MockLoanManagerFactory loanManagerFactory; + + function setUp() external { + asset = new MockERC20("Asset", "ASSET", 18); + globals = new MapleGlobalsMock(governor); + factory = new MapleLoanFactory(address(globals)); + feeManager = new MockFeeManager(); + implementation4 = address(new MapleLoan()); + implementation5 = address(new MapleLoan()); + initializer = address(new MapleLoanInitializer()); + lender = new MockLoanManager(); + loanManagerFactory = MockLoanManagerFactory(lender.factory()); + migrator = new MapleLoanV5Migrator(); + + globals.setValidBorrower(address(1), true); + globals.setValidCollateralAsset(address(asset), true); + globals.setValidPoolAsset(address(asset), true); + globals.__setIsInstanceOf(true); + + vm.startPrank(governor); + factory.registerImplementation(1, implementation4, initializer); + factory.setDefaultVersion(1); + factory.registerImplementation(2, implementation5, initializer); + factory.enableUpgradePath(1, 2, address(migrator)); + vm.stopPrank(); + + address[2] memory assets = [address(asset), address(asset)]; + uint256[3] memory termDetails = [uint256(1), uint256(365 days), uint256(1)]; + uint256[3] memory amounts = [uint256(0), uint256(1_000_000e6), uint256(1_000_000e6)]; + uint256[4] memory rates = [uint256(0.1e18), uint256(0.02e18), uint256(0.03e18), uint256(0.04e18)]; + uint256[2] memory fees = [uint256(0), uint256(0)]; + + bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( + address(1), + address(lender), + address(feeManager), + assets, + termDetails, + amounts, + rates, + fees + ); + + loan = MapleLoan(factory.createInstance(arguments, "SALT")); + + asset.mint(address(loan), 1_000_000e6); + + vm.prank(address(lender)); + loan.fundLoan(); + } + + function test_initializer_setters() external { + // Failure modes are tested on the MapleLoanFactory.t.sol, so this is just test that state is properly set. + + // Check addresses + assertEq(loan.borrower(), address(1)); + assertEq(loan.collateralAsset(), address(asset)); + assertEq(loan.factory(), address(factory)); + assertEq(loan.feeManager(), address(feeManager)); + assertEq(loan.fundsAsset(), address(asset)); + assertEq(loan.lender(), address(lender)); + assertEq(loan.pendingBorrower(), address(0)); + assertEq(loan.pendingLender(), address(0)); + + // Check amounts + assertEq(loan.collateral(), 0); + assertEq(loan.collateralRequired(), 0); + assertEq(loan.endingPrincipal(), 1_000_000e6); + assertEq(loan.principal(), 1_000_000e6); + assertEq(loan.principalRequested(), 1_000_000e6); + + // Check term details + assertEq(loan.gracePeriod(), 1); + assertEq(loan.paymentInterval(), 365 days); + assertEq(loan.paymentsRemaining(), 1); + + // Check rates + assertEq(loan.interestRate(), 0.1e18); + assertEq(loan.closingRate(), 0.02e18); + assertEq(loan.lateFeeRate(), 0.03e18); + assertEq(loan.lateInterestPremiumRate(), 0.04e18); + } + + function test_migration_ratesChange() external { + assertEq(loan.interestRate(), 0.1e18); + assertEq(loan.closingRate(), 0.02e18); + assertEq(loan.lateFeeRate(), 0.03e18); + assertEq(loan.lateInterestPremiumRate(), 0.04e18); + + // Upgrade + vm.prank(loan.borrower()); + loan.upgrade(2, new bytes(0)); + + assertEq(loan.interestRate(), 0.1e6); + assertEq(loan.closingRate(), 0.02e6); + assertEq(loan.lateFeeRate(), 0.03e6); + assertEq(loan.lateInterestPremiumRate(), 0.04e6); + } + +} diff --git a/tests/MapleLoanFeeManager.t.sol b/tests/MapleLoanFeeManager.t.sol index b595f72..67da31a 100644 --- a/tests/MapleLoanFeeManager.t.sol +++ b/tests/MapleLoanFeeManager.t.sol @@ -64,7 +64,7 @@ contract FeeManagerBase is TestUtils { defaultAssets = [address(collateralAsset), address(fundsAsset)]; defaultTermDetails = [uint256(10 days), uint256(365 days / 12), uint256(3)]; defaultAmounts = [uint256(0), uint256(1_000_000e18), uint256(1_000_000e18)]; - defaultRates = [uint256(0.12e18), uint256(0.02e18), uint256(0), uint256(0.02e18)]; + defaultRates = [uint256(0.12e6), uint256(0.02e6), uint256(0), uint256(0.02e6)]; defaultFees = [uint256(50_000e18), uint256(500e18)]; } diff --git a/tests/MapleLoanLogic.t.sol b/tests/MapleLoanLogic.t.sol index 7c04a2a..f7738ae 100644 --- a/tests/MapleLoanLogic.t.sol +++ b/tests/MapleLoanLogic.t.sol @@ -48,7 +48,7 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { defaultAssets = [address(collateralAsset), address(fundsAsset)]; defaultTermDetails = [uint256(1), uint256(30 days), uint256(12)]; defaultAmounts = [uint256(0), uint256(1000), uint256(0)]; - defaultRates = [uint256(0.10e18), uint256(7), uint256(8), uint256(9)]; + defaultRates = [uint256(0.10e6), uint256(7), uint256(8), uint256(9)]; defaultFees = [uint256(0), uint256(0)]; globals.setValidBorrower(defaultBorrower, true); @@ -343,7 +343,7 @@ contract MapleLoanLogic_CloseLoanTests is TestUtils { { paymentInterval_ = constrictToRange(paymentInterval_, 100, 365 days); paymentsRemaining_ = constrictToRange(paymentsRemaining_, 1, 120); - closingRate_ = constrictToRange(closingRate_, 0.01e18, 1.00e18); + closingRate_ = constrictToRange(closingRate_, 0.01e6, 1.00e6); principalRequested_ = constrictToRange(principalRequested_, 1, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); @@ -422,7 +422,7 @@ contract MapleLoanLogic_CloseLoanTests is TestUtils { { paymentInterval_ = constrictToRange(paymentInterval_, 100, 365 days); paymentsRemaining_ = constrictToRange(paymentsRemaining_, 1, 120); - closingRate_ = constrictToRange(closingRate_, 0.01e18, 1.00e18); + closingRate_ = constrictToRange(closingRate_, 0.01e6, 1.00e6); principalRequested_ = constrictToRange(principalRequested_, 1, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); @@ -453,7 +453,7 @@ contract MapleLoanLogic_CloseLoanTests is TestUtils { { paymentInterval_ = constrictToRange(paymentInterval_, 100, 365 days); paymentsRemaining_ = constrictToRange(paymentsRemaining_, 1, 120); - closingRate_ = constrictToRange(closingRate_, 0.01e18, 1.00e18); + closingRate_ = constrictToRange(closingRate_, 0.01e6, 1.00e6); principalRequested_ = constrictToRange(principalRequested_, 100, MAX_TOKEN_AMOUNT); refinanceInterest_ = constrictToRange(refinanceInterest_, 100, MAX_TOKEN_AMOUNT); @@ -1028,7 +1028,7 @@ contract MapleLoanLogic_GetCollateralRequiredForTests is TestUtils { } contract MapleLoanLogic_GetClosingPaymentBreakdownTests is TestUtils { - uint256 private constant SCALED_ONE = uint256(10 ** 18); + uint256 private constant HUNDRED_PERCENT = uint256(10 ** 6); address defaultBorrower; address[2] defaultAssets; @@ -1066,7 +1066,7 @@ contract MapleLoanLogic_GetClosingPaymentBreakdownTests is TestUtils { } function test_getClosingPaymentBreakdown(uint256 principal_, uint256 closingRate_, uint256 refinanceInterest_) external { - uint256 maxClosingRateForTestCase = 1 * SCALED_ONE; // 100% + uint256 maxClosingRateForTestCase = 1 * HUNDRED_PERCENT; // 100% principal_ = constrictToRange(principal_, 1, type(uint256).max / maxClosingRateForTestCase); closingRate_ = constrictToRange(closingRate_, 1, maxClosingRateForTestCase); @@ -1074,7 +1074,7 @@ contract MapleLoanLogic_GetClosingPaymentBreakdownTests is TestUtils { // Set principal and closingRate for _initialize(). uint256[3] memory amounts = [uint256(5), principal_, uint256(0)]; - uint256[4] memory rates = [uint256(0.05 ether), closingRate_, uint256(0.15 ether), uint256(20)]; + uint256[4] memory rates = [uint256(0.05e6), closingRate_, uint256(0.15e6), uint256(20)]; uint256[2] memory fees = [uint256(0), uint256(0)]; vm.startPrank(address(factory)); @@ -1097,7 +1097,7 @@ contract MapleLoanLogic_GetClosingPaymentBreakdownTests is TestUtils { ( uint256 principal, uint256 interest, ) = loan.getClosingPaymentBreakdown(); uint256 expectedPrincipal = amounts[1]; - uint256 expectedInterest = (expectedPrincipal * rates[1] / SCALED_ONE) + refinanceInterest_; + uint256 expectedInterest = (expectedPrincipal * rates[1] / HUNDRED_PERCENT) + refinanceInterest_; assertEq(principal, expectedPrincipal); assertEq(interest, expectedInterest); @@ -1116,7 +1116,7 @@ contract MapleLoanLogic_GetInstallmentTests is TestUtils { } function test_getInstallment_withFixtures() external { - ( uint256 principalAmount, uint256 interestAmount ) = loan.__getInstallment(1_000_000, 0, 0.12 ether, 365 days / 12, 12); + ( uint256 principalAmount, uint256 interestAmount ) = loan.__getInstallment(1_000_000, 0, 0.12e6 , 365 days / 12, 12); assertEq(principalAmount, 78_848); assertEq(interestAmount, 10_000); @@ -1131,7 +1131,7 @@ contract MapleLoanLogic_GetInstallmentTests is TestUtils { ) external { principal_ = constrictToRange(principal_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principal_); - interestRate_ = constrictToRange(interestRate_, 0, 1.00 ether); // 0% - 100% APY + interestRate_ = constrictToRange(interestRate_, 0, 1e6); // 0% - 100% APY paymentInterval_ = constrictToRange(paymentInterval_, 1 hours, 365 days); totalPayments_ = constrictToRange(totalPayments_, 1, 50); @@ -1145,13 +1145,13 @@ contract MapleLoanLogic_GetInstallmentTests is TestUtils { uint256 interestAmount_; // 100,000% APY charged all at once in one payment - ( principalAmount_, interestAmount_ ) = loan.__getInstallment(MAX_TOKEN_AMOUNT, 0, 1000.00 ether, 365 days, 1); + ( principalAmount_, interestAmount_ ) = loan.__getInstallment(MAX_TOKEN_AMOUNT, 0, 1000e6, 365 days, 1); assertEq(principalAmount_, 1e30); assertEq(interestAmount_, 1000e30); // A payment a day for 30 years (10950 payments) at 100% APY - ( principalAmount_, interestAmount_ ) = loan.__getInstallment(MAX_TOKEN_AMOUNT, 0, 1.00 ether, 1 days, 10950); + ( principalAmount_, interestAmount_ ) = loan.__getInstallment(MAX_TOKEN_AMOUNT, 0, 1e6, 1 days, 10950); assertEq(principalAmount_, 267108596355467); assertEq(interestAmount_, 2739726027397260000000000000); @@ -1168,8 +1168,8 @@ contract MapleLoanLogic_GetInterestTests is TestUtils { } function test_getInterest() external { - assertEq(loan.__getInterest(1_000_000, 0.12e18, 365 days / 12), 10_000); // 12% APY on 1M - assertEq(loan.__getInterest(10_000, 1.20e18, 365 days / 12), 1_000); // 120% APY on 10k + assertEq(loan.__getInterest(1_000_000, 0.12e6, 365 days / 12), 10_000); // 12% APY on 1M + assertEq(loan.__getInterest(10_000, 1.20e6, 365 days / 12), 1_000); // 120% APY on 10k } } @@ -1200,9 +1200,9 @@ contract MapleLoanLogic_GetNextPaymentBreakdownTests is TestUtils { principal_ = constrictToRange(principal_, 1, 1e12 * 1e18); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principal_); paymentsRemaining_ = constrictToRange(paymentsRemaining_, 1, 100); - interestRate_ = constrictToRange(interestRate_, 0, 1.00e18); - lateFeeRate_ = constrictToRange(lateFeeRate_, interestRate_, 1.00e18); - lateInterestPremium_ = constrictToRange(lateInterestPremium_, interestRate_, 1.00e18); + interestRate_ = constrictToRange(interestRate_, 0, 1.00e6); + lateFeeRate_ = constrictToRange(lateFeeRate_, interestRate_, 1.00e6); + lateInterestPremium_ = constrictToRange(lateInterestPremium_, interestRate_, 1.00e6); refinanceInterest_ = constrictToRange(refinanceInterest_, 0, 1e12 * 1e18); uint256 paymentInterval = termLength_ / paymentsRemaining_; @@ -1269,9 +1269,9 @@ contract MapleLoanLogic_GetPaymentBreakdownTests is TestUtils { 1_000_000, // Principal 0, // Ending principal 12, // 12 payments - 0.12e18, // 12% interest + 0.12e6, // 12% interest 0, - 0.04e18 // 4% late premium interest + 0.04e6 // 4% late premium interest ); } @@ -1352,8 +1352,8 @@ contract MapleLoanLogic_GetPeriodicInterestRateTests is TestUtils { } function test_getPeriodicInterestRate() external { - assertEq(loan.__getPeriodicInterestRate(0.12 ether, 365 days), 0.12 ether); // 12% - assertEq(loan.__getPeriodicInterestRate(0.12 ether, 365 days / 12), 0.01 ether); // 1% + assertEq(loan.__getPeriodicInterestRate(0.12e6, 365 days), 0.12e18); // 12% + assertEq(loan.__getPeriodicInterestRate(0.12e6, 365 days / 12), 0.01e18); // 1% } } @@ -1842,7 +1842,7 @@ contract MapleLoanLogic_MakePaymentTests is TestUtils { { paymentInterval_ = constrictToRange(paymentInterval_, 100, 365 days); paymentsRemaining_ = constrictToRange(paymentsRemaining_, 2, 50); - interestRate_ = constrictToRange(interestRate_, 0, 1.00e18); + interestRate_ = constrictToRange(interestRate_, 0, 1.00e6); principalRequested_ = constrictToRange(principalRequested_, 1, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); @@ -1886,7 +1886,7 @@ contract MapleLoanLogic_MakePaymentTests is TestUtils { { paymentInterval_ = constrictToRange(paymentInterval_, 100, 365 days); paymentsRemaining_ = constrictToRange(paymentsRemaining_, 2, 50); - interestRate_ = constrictToRange(interestRate_, 0, 1.00e18); + interestRate_ = constrictToRange(interestRate_, 0, 1.00e6); principalRequested_ = constrictToRange(principalRequested_, 1, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); @@ -1927,7 +1927,7 @@ contract MapleLoanLogic_MakePaymentTests is TestUtils { { paymentInterval_ = constrictToRange(paymentInterval_, 100, 365 days); paymentsRemaining_ = constrictToRange(paymentsRemaining_, 1, 50); - interestRate_ = constrictToRange(interestRate_, 1, 1.00e18); + interestRate_ = constrictToRange(interestRate_, 1, 1.00e6); principalRequested_ = constrictToRange(principalRequested_, 10_000_000, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); @@ -1965,7 +1965,7 @@ contract MapleLoanLogic_MakePaymentTests is TestUtils { external { paymentInterval_ = constrictToRange(paymentInterval_, 100, 365 days); - interestRate_ = constrictToRange(interestRate_, 0, 1.00e18); + interestRate_ = constrictToRange(interestRate_, 0, 1.00e6); principalRequested_ = constrictToRange(principalRequested_, 1, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); @@ -2019,7 +2019,7 @@ contract MapleLoanLogic_MakePaymentTests is TestUtils { { paymentInterval_ = constrictToRange(paymentInterval_, 100, 365 days); paymentsRemaining_ = constrictToRange(paymentsRemaining_, 2, 50); - interestRate_ = constrictToRange(interestRate_, 0, 1.00e18); + interestRate_ = constrictToRange(interestRate_, 0, 1.00e6); principalRequested_ = constrictToRange(principalRequested_, 100, MAX_TOKEN_AMOUNT); refinanceInterest_ = constrictToRange(refinanceInterest_, 100, MAX_TOKEN_AMOUNT); @@ -2104,7 +2104,7 @@ contract MapleLoanLogic_MakePaymentTests is TestUtils { } function test_makePayment_collateralNotMaintained() external { - setupLoan(address(loan), 1_000_000, 2, 365 days, 0.1e18, 1_000_000); + setupLoan(address(loan), 1_000_000, 2, 365 days, 0.1e6, 1_000_000); // Need to set fees because if principal = drawableFunds the collateral required is 0 feeManager.__setDelegateServiceFee(100); diff --git a/tests/MapleLoanScenarios.t.sol b/tests/MapleLoanScenarios.t.sol index 0133b33..59eb8b1 100644 --- a/tests/MapleLoanScenarios.t.sol +++ b/tests/MapleLoanScenarios.t.sol @@ -42,7 +42,7 @@ contract MapleLoanScenariosTests is TestUtils { address[2] memory assets = [address(token), address(token)]; uint256[3] memory termDetails = [uint256(10 days), uint256(365 days / 6), uint256(6)]; uint256[3] memory amounts = [uint256(300_000), uint256(1_000_000), uint256(0)]; - uint256[4] memory rates = [uint256(0.12 ether), uint256(0), uint256(0), uint256(0)]; + uint256[4] memory rates = [uint256(0.12e6), uint256(0), uint256(0), uint256(0)]; uint256[2] memory fees = [uint256(0), uint256(0)]; vm.prank(address(factory)); @@ -206,7 +206,7 @@ contract MapleLoanScenariosTests is TestUtils { address[2] memory assets = [address(token), address(token)]; uint256[3] memory termDetails = [uint256(10 days), uint256(365 days / 6), uint256(6)]; uint256[3] memory amounts = [uint256(300_000), uint256(1_000_000), uint256(1_000_000)]; - uint256[4] memory rates = [uint256(0.12 ether), uint256(0), uint256(0), uint256(0)]; + uint256[4] memory rates = [uint256(0.12e6), uint256(0), uint256(0), uint256(0)]; uint256[2] memory fees = [uint256(0), uint256(0)]; vm.prank(address(factory)); @@ -349,7 +349,7 @@ contract MapleLoanScenariosTests is TestUtils { address[2] memory assets = [address(token), address(token)]; uint256[3] memory termDetails = [uint256(0), uint256(30 days), uint256(3)]; uint256[3] memory amounts = [uint256(0), uint256(1_000_000), uint256(1_000_000)]; - uint256[4] memory rates = [uint256(0.1e18), uint256(0), uint256(0), uint256(0.1e18)]; + uint256[4] memory rates = [uint256(0.1e6), uint256(0), uint256(0), uint256(0.1e6)]; uint256[2] memory fees = [uint256(0), uint256(0)]; vm.prank(address(factory)); diff --git a/tests/Payments.t.sol b/tests/Payments.t.sol index 21fa7ad..cd7c101 100644 --- a/tests/Payments.t.sol +++ b/tests/Payments.t.sol @@ -235,7 +235,7 @@ contract ClosingTests is MapleLoanPaymentsTestBase { uint256[3] memory amounts = [uint256(300_000e6), uint256(1_000_000e6), uint256(800_000e6)]; - uint256[4] memory rates = [uint256(0.13e18), uint256(0.02e18), uint256(0), uint256(0)]; // 2% flat rate for closing loan + uint256[4] memory rates = [uint256(0.13e6), uint256(0.02e6), uint256(0), uint256(0)]; // 2% flat rate for closing loan uint256[2] memory fees = [uint256(500e6), uint256(300e6)]; @@ -366,7 +366,7 @@ contract ClosingTests is MapleLoanPaymentsTestBase { uint256[3] memory amounts = [uint256(300_000e6), uint256(1_000_000e6), uint256(0)]; - uint256[4] memory rates = [uint256(0.13e18), uint256(0.02e18), uint256(0), uint256(0)]; // 2% flat rate for closing loan + uint256[4] memory rates = [uint256(0.13e6), uint256(0.02e6), uint256(0), uint256(0)]; // 2% flat rate for closing loan uint256[2] memory fees = [uint256(500e6), uint256(300e6)]; @@ -498,7 +498,7 @@ contract FullyAmortizedPaymentsTests is MapleLoanPaymentsTestBase { uint256[3] memory amounts = [uint256(300_000e6), uint256(1_000_000e6), uint256(0)]; - uint256[4] memory rates = [uint256(0.1e18), uint256(0), uint256(0), uint256(0)]; + uint256[4] memory rates = [uint256(0.1e6), uint256(0), uint256(0), uint256(0)]; uint256[2] memory fees = [uint256(500e6), uint256(300e6)]; @@ -598,7 +598,7 @@ contract FullyAmortizedPaymentsTests is MapleLoanPaymentsTestBase { uint256[3] memory amounts = [uint256(300_000e6), uint256(1_000_000e6), uint256(0)]; - uint256[4] memory rates = [uint256(0.15e18), uint256(0), uint256(0), uint256(0)]; + uint256[4] memory rates = [uint256(0.15e6), uint256(0), uint256(0), uint256(0)]; uint256[2] memory fees = [uint256(500e6), uint256(300e6)]; @@ -700,7 +700,7 @@ contract InterestOnlyPaymentsTests is MapleLoanPaymentsTestBase { uint256[3] memory amounts = [uint256(300_000e6), uint256(1_000_000e6), uint256(1_000_000e6)]; - uint256[4] memory rates = [uint256(0.1e18), uint256(0), uint256(0), uint256(0)]; + uint256[4] memory rates = [uint256(0.1e6), uint256(0), uint256(0), uint256(0)]; uint256[2] memory fees = [uint256(500e6), uint256(300e6)]; @@ -798,7 +798,7 @@ contract InterestOnlyPaymentsTests is MapleLoanPaymentsTestBase { uint256[3] memory amounts = [uint256(300_000e6), uint256(1_000_000e6), uint256(1_000_000e6)]; - uint256[4] memory rates = [uint256(0.15e18), uint256(0), uint256(0), uint256(0)]; + uint256[4] memory rates = [uint256(0.15e6), uint256(0), uint256(0), uint256(0)]; uint256[2] memory fees = [uint256(500e6), uint256(300e6)]; @@ -902,7 +902,7 @@ contract LateRepaymentsTests is MapleLoanPaymentsTestBase { uint256[3] memory amounts = [uint256(300_000e6), uint256(1_000_000e6), uint256(350_000e6)]; - uint256[4] memory rates = [uint256(0.13e18), uint256(0), uint256(0.05e18), uint256(0)]; // 5% Late fee flat rate on principal + uint256[4] memory rates = [uint256(0.13e6), uint256(0), uint256(0.05e6), uint256(0)]; // 5% Late fee flat rate on principal uint256[2] memory fees = [uint256(500e6), uint256(300e6)]; @@ -1038,7 +1038,7 @@ contract LateRepaymentsTests is MapleLoanPaymentsTestBase { uint256[3] memory amounts = [uint256(300_000e6), uint256(1_000_000e6), uint256(1_000_000e6)]; - uint256[4] memory rates = [uint256(0.1e18), uint256(0), uint256(0.05e18), uint256(0)]; // 5% Late fee flat rate on principal + uint256[4] memory rates = [uint256(0.1e6), uint256(0), uint256(0.05e6), uint256(0)]; // 5% Late fee flat rate on principal uint256[2] memory fees = [uint256(500e6), uint256(300e6)]; @@ -1187,7 +1187,7 @@ contract LateRepaymentsTests is MapleLoanPaymentsTestBase { uint256[3] memory amounts = [uint256(300_000e6), uint256(1_000_000e6), uint256(350_000e6)]; - uint256[4] memory rates = [uint256(0.13e18), uint256(0), uint256(0.05e18), uint256(0.05e18)]; // 5% Late fee flat rate on principal, 5% premium + uint256[4] memory rates = [uint256(0.13e6), uint256(0), uint256(0.05e6), uint256(0.05e6)]; // 5% Late fee flat rate on principal, 5% premium uint256[2] memory fees = [uint256(500e6), uint256(300e6)]; @@ -1374,7 +1374,7 @@ contract LateRepaymentsTests is MapleLoanPaymentsTestBase { uint256[3] memory amounts = [uint256(300_000e6), uint256(1_000_000e6), uint256(1_000_000e6)]; - uint256[4] memory rates = [uint256(0.10e18), uint256(0), uint256(0.02e18), uint256(0.05e18)]; // 2% Late fee rate on principal + uint256[4] memory rates = [uint256(0.10e6), uint256(0), uint256(0.02e6), uint256(0.05e6)]; // 2% Late fee rate on principal uint256[2] memory fees = [uint256(500e6), uint256(300e6)]; @@ -1494,7 +1494,7 @@ contract LateRepaymentsTests is MapleLoanPaymentsTestBase { uint256[3] memory amounts = [uint256(300_000e6), uint256(1_000_000e6), uint256(1_000_000e6)]; - uint256[4] memory rates = [uint256(0.10e18), uint256(0), uint256(0.02e18), uint256(0.05e18)]; // 2% Late fee rate on principal + uint256[4] memory rates = [uint256(0.10e6), uint256(0), uint256(0.02e6), uint256(0.05e6)]; // 2% Late fee rate on principal uint256[2] memory fees = [uint256(0), uint256(0)]; @@ -1549,7 +1549,7 @@ contract PartiallyAmortizedPaymentsTests is MapleLoanPaymentsTestBase { uint256[3] memory amounts = [uint256(300_000e6), uint256(1_000_000e6), uint256(800_000e6)]; - uint256[4] memory rates = [uint256(0.1e18), uint256(0), uint256(0), uint256(0)]; + uint256[4] memory rates = [uint256(0.1e6), uint256(0), uint256(0), uint256(0)]; uint256[2] memory fees = [uint256(500e6), uint256(300e6)]; @@ -1649,7 +1649,7 @@ contract PartiallyAmortizedPaymentsTests is MapleLoanPaymentsTestBase { uint256[3] memory amounts = [uint256(300_000e6), uint256(1_000_000e6), uint256(350_000e6)]; - uint256[4] memory rates = [uint256(0.13e18), uint256(0), uint256(0), uint256(0)]; + uint256[4] memory rates = [uint256(0.13e6), uint256(0), uint256(0), uint256(0)]; uint256[2] memory fees = [uint256(500e6), uint256(300e6)]; diff --git a/tests/Refinancer.t.sol b/tests/Refinancer.t.sol index 8fa4432..2c7fb8d 100644 --- a/tests/Refinancer.t.sol +++ b/tests/Refinancer.t.sol @@ -22,7 +22,7 @@ contract RefinancerTestBase is TestUtils { // Loan Boundaries uint256 internal constant MAX_PAYMENTS = 20; - uint256 internal constant MAX_RATE = 1e18; // 100 % + uint256 internal constant MAX_RATE = 1e6; // 100 % uint256 internal constant MAX_TIME = 90 days; // Assumed reasonable upper limit for payment intervals and grace periods uint256 internal constant MAX_TOKEN_AMOUNT = 1e12 * 10 ** 18; // 1 trillion of a token with 18 decimals (assumed reasonable upper limit for token amounts) uint256 internal constant MIN_TOKEN_AMOUNT = 10 ** 6; // Needed so payments don't round down to zero @@ -670,7 +670,7 @@ contract RefinancerInterestTests is TestUtils { // Loan Boundaries uint256 internal constant MAX_PAYMENTS = 20; - uint256 internal constant MAX_RATE = 1e18; // 100 % + uint256 internal constant MAX_RATE = 1e6; // 100 % uint256 internal constant MAX_TIME = 90 days; // Assumed reasonable upper limit for payment intervals and grace periods uint256 internal constant MAX_TOKEN_AMOUNT = 1e12 * 10 ** 18; // 1 trillion of a token with 18 decimals (assumed reasonable upper limit for token amounts) uint256 internal constant MIN_TOKEN_AMOUNT = 10 ** 6; // Needed so payments don't round down to zero @@ -711,7 +711,7 @@ contract RefinancerInterestTests is TestUtils { collateralRequired_: 0, endingPrincipal_: 1_000_000 * USD, gracePeriod_: 10 days, - interestRate_: 0.1e18, + interestRate_: 0.1e6, paymentInterval_: 30 days, paymentsRemaining_ : 3 }); @@ -720,7 +720,7 @@ contract RefinancerInterestTests is TestUtils { assertEq(loan.collateralRequired(), 0); assertEq(loan.endingPrincipal(), 1_000_000 * USD); assertEq(loan.gracePeriod(), 10 days); - assertEq(loan.interestRate(), 0.1e18); + assertEq(loan.interestRate(), 0.1e6); assertEq(loan.paymentInterval(), 30 days); assertEq(loan.nextPaymentDueDate(), start + 30 days); assertEq(loan.paymentsRemaining(), 3); @@ -741,7 +741,7 @@ contract RefinancerInterestTests is TestUtils { assertEq(token.balanceOf(address(lender)), interestPortion); bytes[] memory data = new bytes[](4); - data[0] = abi.encodeWithSignature("setInterestRate(uint256)", 0.12e18); + data[0] = abi.encodeWithSignature("setInterestRate(uint256)", 0.12e6); data[1] = abi.encodeWithSignature("setPaymentInterval(uint256)", 60 days); data[2] = abi.encodeWithSignature("increasePrincipal(uint256)", 1_000_000 * USD); // Ending principal stays the same so switches to amortized data[3] = abi.encodeWithSignature("setPaymentsRemaining(uint256)", 3); // Ending principal stays the same so switches to amortized @@ -763,7 +763,7 @@ contract RefinancerInterestTests is TestUtils { assertEq(loan.collateralRequired(), 0); assertEq(loan.endingPrincipal(), 1_000_000 * USD); assertEq(loan.gracePeriod(), 10 days); - assertEq(loan.interestRate(), 0.12e18); + assertEq(loan.interestRate(), 0.12e6); assertEq(loan.paymentInterval(), 60 days); assertEq(loan.nextPaymentDueDate(), start + 40 days + 60 days); // New payment interval from refinance date assertEq(loan.paymentsRemaining(), 3); @@ -924,7 +924,7 @@ contract RefinancerMultipleParameterTests is RefinancerTestBase { contract RefinancerPaymentIntervalTests is RefinancerTestBase { function test_refinance_paymentInterval_zeroAmount() external { - setUpOngoingLoan(MIN_TOKEN_AMOUNT, 0, MIN_TOKEN_AMOUNT, 0, 0.1e18, 30 days, 6); + setUpOngoingLoan(MIN_TOKEN_AMOUNT, 0, MIN_TOKEN_AMOUNT, 0, 0.1e6, 30 days, 6); uint256 deadline = block.timestamp + 10 days; bytes[] memory data = _encodeWithSignatureAndUint("setPaymentInterval(uint256)", 0); @@ -998,7 +998,7 @@ contract RefinancerPaymentIntervalTests is RefinancerTestBase { contract RefinancerPaymentsRemainingTests is RefinancerTestBase { function test_refinance_paymentRemaining_zeroAmount() external { - setUpOngoingLoan(MIN_TOKEN_AMOUNT, 0, MIN_TOKEN_AMOUNT, 0, 0.1e18, 30 days, 6); + setUpOngoingLoan(MIN_TOKEN_AMOUNT, 0, MIN_TOKEN_AMOUNT, 0, 0.1e6, 30 days, 6); uint256 deadline = block.timestamp + 10 days; bytes[] memory data = _encodeWithSignatureAndUint("setPaymentsRemaining(uint256)", 0); @@ -1219,7 +1219,7 @@ contract RefinancingFeesTerms is TestUtils { // Loan Boundaries uint256 internal constant MAX_FEE_RATE = 100_0000; // 100 % uint256 internal constant MAX_PAYMENTS = 20; - uint256 internal constant MAX_RATE = 1e18; // 100 % + uint256 internal constant MAX_RATE = 1e6; // 100 % uint256 internal constant MAX_TIME = 90 days; // Assumed reasonable upper limit for payment intervals and grace periods uint256 internal constant MAX_TOKEN_AMOUNT = 1e12 * 10 ** 18; // 1 trillion of a token with 18 decimals (assumed reasonable upper limit for token amounts) uint256 internal constant MIN_TOKEN_AMOUNT = 10 ** 6; // Needed so payments don't round down to zero @@ -1302,7 +1302,7 @@ contract RefinancingFeesTerms is TestUtils { } function test_refinance_updateRefinanceServiceFees() external { - setUpOngoingLoan(1_000_000e18, 50_000e18, 1_000_000e18, 10 days, 0.01e18, 365 days, 6); + setUpOngoingLoan(1_000_000e18, 50_000e18, 1_000_000e18, 10 days, 0.01e6, 365 days, 6); // Set Globals values globals.setPlatformServiceFeeRate(address(poolManager), 1_0000); @@ -1330,7 +1330,7 @@ contract RefinancingFeesTerms is TestUtils { } function test_refinance_updateRefinanceServiceFeesOnDoubleRefinance() external { - setUpOngoingLoan(1_000_000e18, 50_000e18, 1_000_000e18, 10 days, 0.01e18, 365 days, 6); + setUpOngoingLoan(1_000_000e18, 50_000e18, 1_000_000e18, 10 days, 0.01e6, 365 days, 6); // Set Globals values globals.setPlatformServiceFeeRate(address(poolManager), 1_0000); @@ -1372,7 +1372,7 @@ contract RefinancingFeesTerms is TestUtils { } function testFuzz_refinance_pdOriginationFeeTransferFail(uint256 newDelegateOriginationFee_) external { - setUpOngoingLoan(1_000_000e18, 50_000e18, 1_000_000e18, 10 days, 0.01e18, 30 days, 6); + setUpOngoingLoan(1_000_000e18, 50_000e18, 1_000_000e18, 10 days, 0.01e6, 30 days, 6); uint256 deadline_ = type(uint256).max; @@ -1402,7 +1402,7 @@ contract RefinancingFeesTerms is TestUtils { ) external { - setUpOngoingLoan(1_000_000e18, 50_000e18, 1_000_000e18, 10 days, 0.01e18, 30 days, 6); + setUpOngoingLoan(1_000_000e18, 50_000e18, 1_000_000e18, 10 days, 0.01e6, 30 days, 6); uint256 deadline_ = type(uint256).max; @@ -1435,7 +1435,7 @@ contract RefinancingFeesTerms is TestUtils { } function testFuzz_refinance_payOriginationFees(uint256 newDelegateOriginationFee_, uint256 newPlatformOriginationFeeRate_) external { - setUpOngoingLoan(1_000_000e18, 50_000e18, 1_000_000e18, 10 days, 0.01e18, 30 days, 6); + setUpOngoingLoan(1_000_000e18, 50_000e18, 1_000_000e18, 10 days, 0.01e6, 30 days, 6); uint256 deadline_ = type(uint256).max; @@ -1479,7 +1479,7 @@ contract RefinancingFeesTerms is TestUtils { } function testFuzz_refinance_updatesPlatformServiceFees(uint256 newPlatformServiceFeeRate_) external { - setUpOngoingLoan(1_000_000e18, 50_000e18, 1_000_000e18, 10 days, 0.01e18, 30 days, 6); + setUpOngoingLoan(1_000_000e18, 50_000e18, 1_000_000e18, 10 days, 0.01e6, 30 days, 6); newPlatformServiceFeeRate_ = constrictToRange(newPlatformServiceFeeRate_, 1, MAX_FEE_RATE); From f7a933ac06dd36e5286b1af5f5d136bbc6407bc3 Mon Sep 17 00:00:00 2001 From: Michael De Luca <35537333+deluca-mike@users.noreply.github.com> Date: Wed, 5 Apr 2023 13:04:23 -0400 Subject: [PATCH 15/47] feat: Globals `isFunctionPaused` (SC-11842) (#280) * feat: globals `isFunctionPaused` (sc-11842) * feat: remove `isFactory` * feat: add pause on migrate and setImplementation * refactor: undo function reordering --------- Co-authored-by: 0xfarhaan <59924029+0xfarhaan@users.noreply.github.com> --- contracts/MapleLoan.sol | 56 ++--- contracts/interfaces/Interfaces.sol | 6 +- tests/InitializerAndMigrator.t.sol | 25 ++- tests/MapleLoan.t.sol | 331 ++++++++-------------------- tests/MapleLoanFactory.t.sol | 6 +- tests/MapleLoanFeeManager.t.sol | 9 +- tests/MapleLoanLogic.t.sol | 66 +++--- tests/MapleLoanScenarios.t.sol | 14 +- tests/Payments.t.sol | 9 +- tests/Refinancer.t.sol | 14 +- tests/mocks/Mocks.sol | 16 +- 11 files changed, 194 insertions(+), 358 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index da90c75..87a1445 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -45,15 +45,8 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { require(_drawableFunds >= drawableFundsBeforePayment, "ML:CANNOT_USE_DRAWABLE"); } - // NOTE: The following functions already check for paused state in the poolManager/loanManager, therefore no need to check here. - // * acceptNewTerms - // * fundLoan - // * impairLoan - // * removeLoanImpairment - // * repossess - // * setPendingLender -> Not implemented - modifier whenProtocolNotPaused() { - require(!IMapleGlobalsLike(globals()).protocolPaused(), "L:PROTOCOL_PAUSED"); + modifier whenNotPaused() { + require(!IMapleGlobalsLike(globals()).isFunctionPaused(msg.sig), "L:PAUSED"); _; } @@ -61,17 +54,17 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { /*** Administrative Functions ***/ /**************************************************************************************************************************************/ - function migrate(address migrator_, bytes calldata arguments_) external override { + function migrate(address migrator_, bytes calldata arguments_) external override whenNotPaused { require(msg.sender == _factory(), "ML:M:NOT_FACTORY"); require(_migrate(migrator_, arguments_), "ML:M:FAILED"); } - function setImplementation(address newImplementation_) external override { + function setImplementation(address newImplementation_) external override whenNotPaused { require(msg.sender == _factory(), "ML:SI:NOT_FACTORY"); require(_setImplementation(newImplementation_), "ML:SI:FAILED"); } - function upgrade(uint256 toVersion_, bytes calldata arguments_) external override whenProtocolNotPaused { + function upgrade(uint256 toVersion_, bytes calldata arguments_) external override whenNotPaused { require(msg.sender == _borrower, "ML:U:NOT_BORROWER"); emit Upgraded(toVersion_, arguments_); @@ -83,7 +76,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { /*** Borrow Functions ***/ /**************************************************************************************************************************************/ - function acceptBorrower() external override whenProtocolNotPaused { + function acceptBorrower() external override whenNotPaused { require(msg.sender == _pendingBorrower, "ML:AB:NOT_PENDING_BORROWER"); _pendingBorrower = address(0); @@ -92,7 +85,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } function closeLoan(uint256 amount_) - external override limitDrawableUse whenProtocolNotPaused returns (uint256 principal_, uint256 interest_, uint256 fees_) + external override whenNotPaused limitDrawableUse returns (uint256 principal_, uint256 interest_, uint256 fees_) { // The amount specified is an optional amount to be transferred from the caller, as a convenience for EOAs. // NOTE: FUNDS SHOULD NOT BE TRANSFERRED TO THIS CONTRACT NON-ATOMICALLY. IF THEY ARE, THE BALANCE MAY BE STOLEN USING `skim`. @@ -130,9 +123,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { emit FundsClaimed(principalAndInterest, _lender); } - function drawdownFunds(uint256 amount_, address destination_) - external override whenProtocolNotPaused returns (uint256 collateralPosted_) - { + function drawdownFunds(uint256 amount_, address destination_) external override whenNotPaused returns (uint256 collateralPosted_) { require(msg.sender == _borrower, "ML:DF:NOT_BORROWER"); emit FundsDrawnDown(amount_, destination_); @@ -157,7 +148,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } function makePayment(uint256 amount_) - external override limitDrawableUse whenProtocolNotPaused returns (uint256 principal_, uint256 interest_, uint256 fees_) + external override whenNotPaused limitDrawableUse returns (uint256 principal_, uint256 interest_, uint256 fees_) { // The amount specified is an optional amount to be transfer from the caller, as a convenience for EOAs. // NOTE: FUNDS SHOULD NOT BE TRANSFERRED TO THIS CONTRACT NON-ATOMICALLY. IF THEY ARE, THE BALANCE MAY BE STOLEN USING `skim`. @@ -203,7 +194,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { require(_isCollateralMaintained(), "ML:MP:INSUFFICIENT_COLLATERAL"); } - function postCollateral(uint256 amount_) public override whenProtocolNotPaused returns (uint256 collateralPosted_) { + function postCollateral(uint256 amount_) public override whenNotPaused returns (uint256 collateralPosted_) { // The amount specified is an optional amount to be transfer from the caller, as a convenience for EOAs. // NOTE: FUNDS SHOULD NOT BE TRANSFERRED TO THIS CONTRACT NON-ATOMICALLY. IF THEY ARE, THE BALANCE MAY BE STOLEN USING `skim`. require( @@ -217,7 +208,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } function proposeNewTerms(address refinancer_, uint256 deadline_, bytes[] calldata calls_) - external override whenProtocolNotPaused returns (bytes32 refinanceCommitment_) + external override whenNotPaused returns (bytes32 refinanceCommitment_) { require(msg.sender == _borrower, "ML:PNT:NOT_BORROWER"); require(deadline_ >= block.timestamp, "ML:PNT:INVALID_DEADLINE"); @@ -232,7 +223,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { ); } - function removeCollateral(uint256 amount_, address destination_) external override whenProtocolNotPaused { + function removeCollateral(uint256 amount_, address destination_) external override whenNotPaused { require(msg.sender == _borrower, "ML:RC:NOT_BORROWER"); emit CollateralRemoved(amount_, destination_); @@ -243,7 +234,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { require(_isCollateralMaintained(), "ML:RC:INSUFFICIENT_COLLATERAL"); } - function returnFunds(uint256 amount_) external override whenProtocolNotPaused returns (uint256 fundsReturned_) { + function returnFunds(uint256 amount_) external override whenNotPaused returns (uint256 fundsReturned_) { // The amount specified is an optional amount to be transfer from the caller, as a convenience for EOAs. // NOTE: FUNDS SHOULD NOT BE TRANSFERRED TO THIS CONTRACT NON-ATOMICALLY. IF THEY ARE, THE BALANCE MAY BE STOLEN USING `skim`. require( @@ -256,7 +247,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { emit FundsReturned(fundsReturned_); } - function setPendingBorrower(address pendingBorrower_) external override whenProtocolNotPaused { + function setPendingBorrower(address pendingBorrower_) external override whenNotPaused { require(msg.sender == _borrower, "ML:SPB:NOT_BORROWER"); require(IMapleGlobalsLike(globals()).isBorrower(pendingBorrower_), "ML:SPB:INVALID_BORROWER"); @@ -267,7 +258,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { /*** Lend Functions ***/ /**************************************************************************************************************************************/ - function acceptLender() external override whenProtocolNotPaused { + function acceptLender() external override whenNotPaused { require(msg.sender == _pendingLender, "ML:AL:NOT_PENDING_LENDER"); _pendingLender = address(0); @@ -276,7 +267,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } function acceptNewTerms(address refinancer_, uint256 deadline_, bytes[] calldata calls_) - external override returns (bytes32 refinanceCommitment_) + external override whenNotPaused returns (bytes32 refinanceCommitment_) { require(msg.sender == _lender, "ML:ANT:NOT_LENDER"); @@ -340,7 +331,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { require(getUnaccountedAmount(fundsAsset_) == uint256(0), "ML:ANT:UNEXPECTED_FUNDS"); } - function fundLoan() external override returns (uint256 fundsLent_) { + function fundLoan() external override whenNotPaused returns (uint256 fundsLent_) { address lender_ = _lender; require(msg.sender == lender_, "ML:FL:NOT_LENDER"); @@ -370,7 +361,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { ); } - function removeLoanImpairment() external override { + function removeLoanImpairment() external override whenNotPaused { uint256 originalNextPaymentDueDate_ = _originalNextPaymentDueDate; require(msg.sender == _lender, "ML:RLI:NOT_LENDER"); @@ -383,7 +374,8 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { emit NextPaymentDueDateRestored(originalNextPaymentDueDate_); } - function repossess(address destination_) external override returns (uint256 collateralRepossessed_, uint256 fundsRepossessed_) { + function repossess(address destination_) + external override whenNotPaused returns (uint256 collateralRepossessed_, uint256 fundsRepossessed_) { require(msg.sender == _lender, "ML:R:NOT_LENDER"); uint256 nextPaymentDueDate_ = _nextPaymentDueDate; @@ -420,13 +412,13 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { emit Repossessed(collateralRepossessed_, fundsRepossessed_, destination_); } - function setPendingLender(address pendingLender_) external override { + function setPendingLender(address pendingLender_) external override whenNotPaused { require(msg.sender == _lender, "ML:SPL:NOT_LENDER"); emit PendingLenderSet(_pendingLender = pendingLender_); } - function impairLoan() external override { + function impairLoan() external override whenNotPaused { uint256 originalNextPaymentDueDate_ = _nextPaymentDueDate; require(msg.sender == _lender, "ML:IL:NOT_LENDER"); @@ -445,7 +437,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { /**************************************************************************************************************************************/ function rejectNewTerms(address refinancer_, uint256 deadline_, bytes[] calldata calls_) - external override whenProtocolNotPaused returns (bytes32 refinanceCommitment_) + external override whenNotPaused returns (bytes32 refinanceCommitment_) { require((msg.sender == _borrower) || (msg.sender == _lender), "ML:RNT:NO_AUTH"); @@ -459,7 +451,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { emit NewTermsRejected(refinanceCommitment_, refinancer_, deadline_, calls_); } - function skim(address token_, address destination_) external override whenProtocolNotPaused returns (uint256 skimmed_) { + function skim(address token_, address destination_) external override whenNotPaused returns (uint256 skimmed_) { emit Skimmed(token_, skimmed_ = getUnaccountedAmount(token_), destination_); require(ERC20Helper.transfer(token_, destination_, skimmed_), "ML:S:TRANSFER_FAILED"); } diff --git a/contracts/interfaces/Interfaces.sol b/contracts/interfaces/Interfaces.sol index 8709242..89506b7 100644 --- a/contracts/interfaces/Interfaces.sol +++ b/contracts/interfaces/Interfaces.sol @@ -9,7 +9,7 @@ interface IMapleGlobalsLike { function isCollateralAsset(address collateralAsset_) external view returns (bool isCollateralAsset_); - function isFactory(bytes32 factoryId_, address factory_) external view returns (bool isValid_); + function isFunctionPaused(bytes4 sig_) external view returns (bool isFunctionPaused_); function isInstanceOf(bytes32 instanceId_, address instance_) external view returns (bool isInstance_); @@ -21,10 +21,6 @@ interface IMapleGlobalsLike { function platformServiceFeeRate(address pool_) external view returns (uint256 platformFee_); - function protocolPaused() external view returns (bool protocolPaused_); - - function securityAdmin() external view returns (address securityAdmin_); - } interface ILenderLike { diff --git a/tests/InitializerAndMigrator.t.sol b/tests/InitializerAndMigrator.t.sol index 4ba9ba5..abb8a8d 100644 --- a/tests/InitializerAndMigrator.t.sol +++ b/tests/InitializerAndMigrator.t.sol @@ -9,7 +9,7 @@ import { MapleLoanFactory} from "../contracts/MapleLoanFactory.sol"; import { MapleLoanInitializer } from "../contracts/MapleLoanInitializer.sol"; import { MapleLoanV5Migrator } from "../contracts/MapleLoanV5Migrator.sol"; -import { MapleGlobalsMock, MockFeeManager, MockLoanManager, MockLoanManagerFactory } from "./mocks/Mocks.sol"; +import { MockGlobals, MockFeeManager, MockLoanManager, MockLoanManagerFactory } from "./mocks/Mocks.sol"; contract MapleLoanInitializerAndMigratorTests is TestUtils { @@ -18,26 +18,27 @@ contract MapleLoanInitializerAndMigratorTests is TestUtils { address internal implementation5; address internal initializer; - MapleGlobalsMock globals; - MockFeeManager feeManager; MapleLoan loan; MapleLoanFactory factory; MapleLoanV5Migrator migrator; MockERC20 asset; + MockFeeManager feeManager; + MockGlobals globals; MockLoanManager lender; MockLoanManagerFactory loanManagerFactory; function setUp() external { - asset = new MockERC20("Asset", "ASSET", 18); - globals = new MapleGlobalsMock(governor); + asset = new MockERC20("Asset", "ASSET", 18); + globals = new MockGlobals(governor); + feeManager = new MockFeeManager(); + implementation4 = address(new MapleLoan()); + implementation5 = address(new MapleLoan()); + initializer = address(new MapleLoanInitializer()); + lender = new MockLoanManager(); + migrator = new MapleLoanV5Migrator(); + factory = new MapleLoanFactory(address(globals)); - feeManager = new MockFeeManager(); - implementation4 = address(new MapleLoan()); - implementation5 = address(new MapleLoan()); - initializer = address(new MapleLoanInitializer()); - lender = new MockLoanManager(); loanManagerFactory = MockLoanManagerFactory(lender.factory()); - migrator = new MapleLoanV5Migrator(); globals.setValidBorrower(address(1), true); globals.setValidCollateralAsset(address(asset), true); @@ -80,7 +81,7 @@ contract MapleLoanInitializerAndMigratorTests is TestUtils { // Failure modes are tested on the MapleLoanFactory.t.sol, so this is just test that state is properly set. // Check addresses - assertEq(loan.borrower(), address(1)); + assertEq(loan.borrower(), address(1)); assertEq(loan.collateralAsset(), address(asset)); assertEq(loan.factory(), address(factory)); assertEq(loan.feeManager(), address(feeManager)); diff --git a/tests/MapleLoan.t.sol b/tests/MapleLoan.t.sol index 77258ae..8eb064d 100644 --- a/tests/MapleLoan.t.sol +++ b/tests/MapleLoan.t.sol @@ -6,14 +6,14 @@ import { MockERC20 } from "../modules/erc20/contracts/test/mocks/MockER import { ConstructableMapleLoan, MapleLoanHarness } from "./harnesses/MapleLoanHarnesses.sol"; -import { EmptyContract, MapleGlobalsMock, MockFactory, MockFeeManager, MockLoanManager } from "./mocks/Mocks.sol"; +import { EmptyContract, MockFactory, MockFeeManager, MockGlobals, MockLoanManager } from "./mocks/Mocks.sol"; contract MapleLoanTests is TestUtils { - MapleGlobalsMock internal globals; MapleLoanHarness internal loan; - MockFactory internal factoryMock; + MockFactory internal factory; MockFeeManager internal feeManager; + MockGlobals internal globals; address internal borrower = address(new Address()); address internal governor = address(new Address()); @@ -25,13 +25,14 @@ contract MapleLoanTests is TestUtils { function setUp() external { feeManager = new MockFeeManager(); + globals = new MockGlobals(governor); lender = address(new MockLoanManager()); loan = new MapleLoanHarness(); - globals = new MapleGlobalsMock(governor); - factoryMock = new MockFactory(address(globals)); + + factory = new MockFactory(address(globals)); loan.__setBorrower(borrower); - loan.__setFactory(address(factoryMock)); + loan.__setFactory(address(factory)); loan.__setFeeManager(address(feeManager)); loan.__setLender(lender); @@ -221,7 +222,7 @@ contract MapleLoanTests is TestUtils { vm.expectRevert("ML:M:NOT_FACTORY"); loan.migrate(mockMigrator, new bytes(0)); - vm.prank(address(factoryMock)); + vm.prank(address(factory)); loan.migrate(mockMigrator, new bytes(0)); } @@ -231,7 +232,7 @@ contract MapleLoanTests is TestUtils { vm.expectRevert("ML:SI:NOT_FACTORY"); loan.setImplementation(someContract); - vm.prank(address(factoryMock)); + vm.prank(address(factory)); loan.setImplementation(someContract); } @@ -1175,305 +1176,151 @@ contract MapleLoanTests is TestUtils { /*** Pause Tests ***/ /**************************************************************************************************************************************/ - function test_acceptBorrower_failWhenPaused() external { - // Set up - address newBorrower = address(new Address()); - loan.__setPendingBorrower(newBorrower); + function test_migrate_failWhenPaused() external { + globals.__setFunctionPaused(true); - // Trigger pause and assert failure - globals.setProtocolPaused(true); + vm.expectRevert("L:PAUSED"); + loan.migrate(address(0), bytes("")); + } - vm.prank(address(newBorrower)); - vm.expectRevert("L:PROTOCOL_PAUSED"); - loan.acceptBorrower(); + function test_setImplementation_failWhenPaused() external { + globals.__setFunctionPaused(true); - // Success case - globals.setProtocolPaused(false); + vm.expectRevert("L:PAUSED"); + loan.setImplementation(address(0)); + } - vm.prank(newBorrower); + function test_acceptBorrower_failWhenPaused() external { + globals.__setFunctionPaused(true); + + vm.expectRevert("L:PAUSED"); loan.acceptBorrower(); } function test_acceptLender_failWhenPaused() external { - // Set up - address newLender = address(new Address()); - loan.__setPendingLender(newLender); + globals.__setFunctionPaused(true); - // Trigger pause and assert failure - globals.setProtocolPaused(true); - - vm.prank(address(newLender)); - vm.expectRevert("L:PROTOCOL_PAUSED"); + vm.expectRevert("L:PAUSED"); loan.acceptLender(); + } - // Success case - globals.setProtocolPaused(false); + function test_acceptNewTerms_failWhenPaused() external { + globals.__setFunctionPaused(true); - vm.prank(newLender); - loan.acceptLender(); + vm.expectRevert("L:PAUSED"); + loan.acceptNewTerms(address(0), 0, new bytes[](0)); } function test_closeLoan_failWhenPaused() external { - // Set up - MockERC20 fundsAsset = new MockERC20("FA", "FA", 18); - - uint256 amount = 1_000_000; + globals.__setFunctionPaused(true); - loan.__setBorrower(address(borrower)); - loan.__setFundsAsset(address(fundsAsset)); - loan.__setPrincipal(amount); - loan.__setPrincipalRequested(amount); - loan.__setNextPaymentDueDate(block.timestamp + 1); - - fundsAsset.mint(address(loan), amount); - - // Trigger pause and assert failure - globals.setProtocolPaused(true); - - vm.prank(borrower); - vm.expectRevert("L:PROTOCOL_PAUSED"); - loan.closeLoan(uint256(0)); - - // Success case - globals.setProtocolPaused(false); - - vm.prank(borrower); + vm.expectRevert("L:PAUSED"); loan.closeLoan(uint256(0)); } - function test_drawdown_failWhenPaused() external { - // Set up - MockERC20 fundsAsset = new MockERC20("FA", "FA", 18); - MockERC20 collateralAsset = new MockERC20("CA", "CA", 18); - - uint256 amount = 1_000_000; - - loan.__setCollateralAsset(address(collateralAsset)); - loan.__setDrawableFunds(amount); - loan.__setFundsAsset(address(fundsAsset)); - loan.__setPrincipal(amount); - loan.__setPrincipalRequested(amount); + function test_drawdownFunds_failWhenPaused() external { + globals.__setFunctionPaused(true); - // Send amount to loan - fundsAsset.mint(address(loan), amount); + vm.expectRevert("L:PAUSED"); + loan.drawdownFunds(0, address(0)); + } - // Trigger pause and assert failure - globals.setProtocolPaused(true); + function test_fundLoan_failWhenPaused() external { + globals.__setFunctionPaused(true); - vm.prank(borrower); - vm.expectRevert("L:PROTOCOL_PAUSED"); - loan.drawdownFunds(amount, borrower); + vm.expectRevert("L:PAUSED"); + loan.fundLoan(); + } - // Success case - globals.setProtocolPaused(false); + function test_impairLoan_failWhenPaused() external { + globals.__setFunctionPaused(true); - vm.prank(borrower); - loan.drawdownFunds(amount, borrower); + vm.expectRevert("L:PAUSED"); + loan.impairLoan(); } function test_makePayment_failWhenPaused() external { - // Set up - MockERC20 fundsAsset = new MockERC20("FA", "FA", 18); - - uint256 startingPrincipal = 1_000_000; + globals.__setFunctionPaused(true); - loan.__setEndingPrincipal(uint256(0)); - loan.__setFundsAsset(address(fundsAsset)); - loan.__setPaymentsRemaining(3); - loan.__setPrincipal(startingPrincipal); - loan.__setPrincipalRequested(startingPrincipal); - - ( uint256 principal, uint256 interest, ) = loan.getNextPaymentBreakdown(); - uint256 totalPayment = principal + interest; - - fundsAsset.mint(address(loan), totalPayment); - - // Trigger pause and assert failure - globals.setProtocolPaused(true); - - vm.prank(borrower); - vm.expectRevert("L:PROTOCOL_PAUSED"); - loan.makePayment(0); - - // Success case - globals.setProtocolPaused(false); - - vm.prank(borrower); + vm.expectRevert("L:PAUSED"); loan.makePayment(0); } function test_postCollateral_failWhenPaused() external { - // Set up - MockERC20 collateralAsset = new MockERC20("CA", "CA", 18); - - loan.__setCollateralAsset(address(collateralAsset)); - - uint256 amount = 1_000_000; - - collateralAsset.mint(address(loan), amount); - - // Trigger pause and assert failure - globals.setProtocolPaused(true); - - vm.expectRevert("L:PROTOCOL_PAUSED"); - loan.postCollateral(0); - - // Success case - globals.setProtocolPaused(false); + globals.__setFunctionPaused(true); + vm.expectRevert("L:PAUSED"); loan.postCollateral(0); } function test_proposeNewTerms_failWhenPaused() external { - // Set up - address refinancer = address(new EmptyContract()); - uint256 deadline = block.timestamp + 10 days; - bytes[] memory calls = new bytes[](1); - calls[0] = abi.encodeWithSignature("increasePrincipal(uint256)", 1); + globals.__setFunctionPaused(true); - // Trigger pause and assert failure - globals.setProtocolPaused(true); - - vm.prank(borrower); - vm.expectRevert("L:PROTOCOL_PAUSED"); - loan.proposeNewTerms(refinancer, deadline, calls); - - // Success case - globals.setProtocolPaused(false); - - vm.prank(borrower); - loan.proposeNewTerms(refinancer, deadline, calls); + vm.expectRevert("L:PAUSED"); + loan.proposeNewTerms(address(0), 0, new bytes[](0)); } function test_rejectNewTerms_failWhenPaused() external { - // Set up - address refinancer = address(new EmptyContract()); - uint256 deadline = block.timestamp + 10 days; - bytes[] memory calls = new bytes[](1); - calls[0] = abi.encodeWithSignature("increasePrincipal(uint256)", 1); + globals.__setFunctionPaused(true); - loan.__setRefinanceCommitment(keccak256(abi.encode(refinancer, deadline, calls))); - - // Trigger pause and assert failure - globals.setProtocolPaused(true); - - vm.prank(borrower); - vm.expectRevert("L:PROTOCOL_PAUSED"); - loan.rejectNewTerms(refinancer, deadline, calls); - - // Success case - globals.setProtocolPaused(false); - - vm.prank(borrower); - loan.rejectNewTerms(refinancer, deadline, calls); + vm.expectRevert("L:PAUSED"); + loan.rejectNewTerms(address(0), 0, new bytes[](0)); } function test_removeCollateral_failWhenPaused() external { - // Set up - MockERC20 collateralAsset = new MockERC20("CA", "CA", 18); + globals.__setFunctionPaused(true); - uint256 amount = 1_000_000; - - loan.__setCollateralAsset(address(collateralAsset)); - loan.__setCollateral(amount); - - collateralAsset.mint(address(loan), amount); + vm.expectRevert("L:PAUSED"); + loan.removeCollateral(0, address(0)); + } - // Trigger pause and assert failure - globals.setProtocolPaused(true); + function test_removeLoanImpairment_failWhenPaused() external { + globals.__setFunctionPaused(true); - vm.prank(borrower); - vm.expectRevert("L:PROTOCOL_PAUSED"); - loan.removeCollateral(amount, address(borrower)); + vm.expectRevert("L:PAUSED"); + loan.removeLoanImpairment(); + } - // Success case - globals.setProtocolPaused(false); + function test_repossess_failWhenPaused() external { + globals.__setFunctionPaused(true); - vm.prank(borrower); - loan.removeCollateral(amount, address(borrower)); + vm.expectRevert("L:PAUSED"); + loan.repossess(address(0)); } function test_returnFunds_failWhenPaused() external { - // Set up - MockERC20 fundsAsset = new MockERC20("FA", "FA", 18); - - loan.__setFundsAsset(address(fundsAsset)); - - uint256 amount = 1_000_000; - - fundsAsset.mint(address(loan), amount); - - // Trigger pause and assert failure - globals.setProtocolPaused(true); - - vm.expectRevert("L:PROTOCOL_PAUSED"); - loan.returnFunds(0); - - // Success case - globals.setProtocolPaused(false); + globals.__setFunctionPaused(true); + vm.expectRevert("L:PAUSED"); loan.returnFunds(0); } function test_setPendingBorrower_failWhenPaused() external { - // Set up - address newBorrower = address(new Address()); + globals.__setFunctionPaused(true); - globals.setValidBorrower(newBorrower, true); - - // Trigger pause and assert failure - globals.setProtocolPaused(true); - - vm.prank(borrower); - vm.expectRevert("L:PROTOCOL_PAUSED"); - loan.setPendingBorrower(newBorrower); + vm.expectRevert("L:PAUSED"); + loan.setPendingBorrower(address(0)); + } - // Success case - globals.setProtocolPaused(false); + function test_setPendingLender_failWhenPaused() external { + globals.__setFunctionPaused(true); - vm.prank(borrower); - loan.setPendingBorrower(newBorrower); + vm.expectRevert("L:PAUSED"); + loan.setPendingLender(address(0)); } function test_skim_failWhenPaused() external { - // Set up - MockERC20 asset = new MockERC20("CA", "CA", 18); - - uint256 amount = 1_000_000; - - asset.mint(address(loan), amount); + globals.__setFunctionPaused(true); - // Trigger pause and assert failure - globals.setProtocolPaused(true); - - vm.prank(borrower); - vm.expectRevert("L:PROTOCOL_PAUSED"); - loan.skim(address(asset), address(this)); - - // Success case - globals.setProtocolPaused(false); - - vm.prank(borrower); - loan.skim(address(asset), address(this)); + vm.expectRevert("L:PAUSED"); + loan.skim(address(0), address(0)); } function test_upgrade_failWhenPaused() external { - loan.__setFactory(address(new MockFactory(address(globals)))); + globals.__setFunctionPaused(true); - address newImplementation = address(new MapleLoanHarness()); - - globals.setProtocolPaused(true); - - vm.prank(borrower); - vm.expectRevert("L:PROTOCOL_PAUSED"); - loan.upgrade(1, abi.encode(newImplementation)); - - globals.setProtocolPaused(false); - - vm.prank(borrower); - loan.upgrade(1, abi.encode(newImplementation)); - - assertEq(loan.implementation(), newImplementation); + vm.expectRevert("L:PAUSED"); + loan.upgrade(0, ""); } } @@ -1485,15 +1332,15 @@ contract MapleLoanRoleTests is TestUtils { address lender; ConstructableMapleLoan loan; - MapleGlobalsMock globals; MockERC20 token; MockFactory factory; MockFeeManager feeManager; + MockGlobals globals; function setUp() public { - lender = address(new MockLoanManager()); feeManager = new MockFeeManager(); - globals = new MapleGlobalsMock(governor); + globals = new MockGlobals(governor); + lender = address(new MockLoanManager()); token = new MockERC20("Token", "T", 0); factory = new MockFactory(address(globals)); @@ -1507,7 +1354,7 @@ contract MapleLoanRoleTests is TestUtils { globals.setValidBorrower(borrower, true); globals.setValidCollateralAsset(address(token), true); globals.setValidPoolAsset(address(token), true); - + globals.__setIsInstanceOf(true); vm.prank(address(factory)); diff --git a/tests/MapleLoanFactory.t.sol b/tests/MapleLoanFactory.t.sol index e542aff..a65af0a 100644 --- a/tests/MapleLoanFactory.t.sol +++ b/tests/MapleLoanFactory.t.sol @@ -7,15 +7,15 @@ import { MapleLoan } from "../contracts/MapleLoan.sol"; import { MapleLoanFactory } from "../contracts/MapleLoanFactory.sol"; import { MapleLoanInitializer } from "../contracts/MapleLoanInitializer.sol"; -import { MapleGlobalsMock, MockFeeManager, MockLoanManager, MockLoanManagerFactory } from "./mocks/Mocks.sol"; +import { MockFeeManager, MockGlobals, MockLoanManager, MockLoanManagerFactory } from "./mocks/Mocks.sol"; import { Proxy } from "../modules/maple-proxy-factory/modules/proxy-factory/contracts/Proxy.sol"; contract MapleLoanFactoryTest is TestUtils { - MapleGlobalsMock internal globals; MapleLoanFactory internal factory; MockFeeManager internal feeManager; + MockGlobals internal globals; MockLoanManager internal lender; MockLoanManagerFactory internal loanManagerFactory; @@ -28,7 +28,7 @@ contract MapleLoanFactoryTest is TestUtils { lender = new MockLoanManager(); loanManagerFactory = MockLoanManagerFactory(lender.factory()); feeManager = new MockFeeManager(); - globals = new MapleGlobalsMock(governor); + globals = new MockGlobals(governor); implementation = address(new MapleLoan()); initializer = address(new MapleLoanInitializer()); diff --git a/tests/MapleLoanFeeManager.t.sol b/tests/MapleLoanFeeManager.t.sol index 67da31a..b95b86b 100644 --- a/tests/MapleLoanFeeManager.t.sol +++ b/tests/MapleLoanFeeManager.t.sol @@ -9,7 +9,7 @@ import { MapleLoanFactory } from "../contracts/MapleLoanFactory.sol"; import { MapleLoanInitializer } from "../contracts/MapleLoanInitializer.sol"; import { MapleLoanFeeManager } from "../contracts/MapleLoanFeeManager.sol"; -import { MapleGlobalsMock, MockLoanManager, MockPoolManager } from "./mocks/Mocks.sol"; +import { MockGlobals, MockLoanManager, MockPoolManager } from "./mocks/Mocks.sol"; contract FeeManagerBase is TestUtils { @@ -21,11 +21,11 @@ contract FeeManagerBase is TestUtils { address internal implementation; address internal initializer; - MapleGlobalsMock internal globals; MapleLoanFactory internal factory; MapleLoanFeeManager internal feeManager; MockERC20 internal collateralAsset; MockERC20 internal fundsAsset; + MockGlobals internal globals; MockLoanManager internal lender; MockPoolManager internal poolManager; @@ -43,8 +43,9 @@ contract FeeManagerBase is TestUtils { fundsAsset = new MockERC20("MockAsset", "MA", 18); poolManager = new MockPoolManager(PD); - lender = new MockLoanManager(); - globals = new MapleGlobalsMock(GOVERNOR); + globals = new MockGlobals(GOVERNOR); + lender = new MockLoanManager(); + factory = new MapleLoanFactory(address(globals)); feeManager = new MapleLoanFeeManager(address(globals)); diff --git a/tests/MapleLoanLogic.t.sol b/tests/MapleLoanLogic.t.sol index f7738ae..7af2859 100644 --- a/tests/MapleLoanLogic.t.sol +++ b/tests/MapleLoanLogic.t.sol @@ -6,7 +6,7 @@ import { MockERC20 } from "../modules/erc20/contracts/test/mocks/MockER import { ConstructableMapleLoan, MapleLoanHarness } from "./harnesses/MapleLoanHarnesses.sol"; -import { MapleGlobalsMock, MockFactory, MockFeeManager, MockLoanManager, RevertingERC20 } from "./mocks/Mocks.sol"; +import { MockGlobals, MockFactory, MockFeeManager, MockLoanManager, RevertingERC20 } from "./mocks/Mocks.sol"; import { Refinancer } from "../contracts/Refinancer.sol"; @@ -25,11 +25,11 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { uint256 internal start; ConstructableMapleLoan internal loan; - MapleGlobalsMock internal globals; MockERC20 internal collateralAsset; MockERC20 internal fundsAsset; MockFactory internal factory; MockFeeManager internal feeManager; + MockGlobals internal globals; MockLoanManager internal lender; Refinancer internal refinancer; @@ -37,10 +37,10 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { collateralAsset = new MockERC20("Token0", "T0", 0); feeManager = new MockFeeManager(); fundsAsset = new MockERC20("Token1", "T1", 0); + globals = new MockGlobals(governor); lender = new MockLoanManager(); refinancer = new Refinancer(); - globals = new MapleGlobalsMock(governor); factory = new MockFactory(address(globals)); // Set _initialize() parameters. @@ -244,21 +244,20 @@ contract MapleLoanLogic_CloseLoanTests is TestUtils { address internal borrower = address(new Address()); address internal governor = address(new Address()); - MapleGlobalsMock internal globals; MapleLoanHarness internal loan; MockERC20 internal fundsAsset; MockFactory internal factory; MockFeeManager internal feeManager; + MockGlobals internal globals; MockLoanManager internal lender; function setUp() external { feeManager = new MockFeeManager(); fundsAsset = new MockERC20("FundsAsset", "FA", 0); + globals = new MockGlobals(governor); lender = new MockLoanManager(); loan = new MapleLoanHarness(); - globals = new MapleGlobalsMock(governor); - factory = new MockFactory(address(globals)); loan.__setBorrower(borrower); @@ -687,18 +686,18 @@ contract MapleLoanLogic_DrawdownFundsTests is TestUtils { uint256 internal constant MAX_TOKEN_AMOUNT = 1e12 * 10 ** 18; // 1 trillion of a token with 18 decimals (assumed reasonable upper limit for token amounts) - MapleGlobalsMock internal globals; MapleLoanHarness internal loan; MockERC20 internal collateralAsset; MockERC20 internal fundsAsset; MockFactory internal factory; + MockGlobals internal globals; address borrower = address(new Address()); function setUp() external { collateralAsset = new MockERC20("Collateral Asset", "CA", 0); - globals = new MapleGlobalsMock(address(0)); fundsAsset = new MockERC20("Funds Asset", "FA", 0); + globals = new MockGlobals(address(0)); loan = new MapleLoanHarness(); factory = new MockFactory(address(globals)); @@ -843,11 +842,11 @@ contract MapleLoanLogic_FundLoanTests is TestUtils { uint256 constant MAX_PRINCIPAL = 1_000_000_000 * 1e18; uint256 constant MIN_PRINCIPAL = 1; - MapleGlobalsMock globals; MapleLoanHarness loan; MockERC20 fundsAsset; MockFactory factory; MockFeeManager feeManager; + MockGlobals globals; MockLoanManager lender; address governor = address(new Address()); @@ -855,8 +854,8 @@ contract MapleLoanLogic_FundLoanTests is TestUtils { function setUp() external { feeManager = new MockFeeManager(); fundsAsset = new MockERC20("FundsAsset", "FA", 0); + globals = new MockGlobals(governor); lender = new MockLoanManager(); - globals = new MapleGlobalsMock(governor); loan = new MapleLoanHarness(); factory = new MockFactory(address(globals)); @@ -1034,19 +1033,19 @@ contract MapleLoanLogic_GetClosingPaymentBreakdownTests is TestUtils { address[2] defaultAssets; uint256[3] defaultTermDetails; - MapleGlobalsMock globals; ConstructableMapleLoan loan; MockERC20 token1; MockERC20 token2; MockFactory factory; MockFeeManager feeManager; + MockGlobals globals; MockLoanManager lender; address governor = address(new Address()); function setUp() external { - globals = new MapleGlobalsMock(governor); feeManager = new MockFeeManager(); + globals = new MockGlobals(governor); lender = new MockLoanManager(); token1 = new MockERC20("Token0", "T0", 0); token2 = new MockERC20("Token1", "T1", 0); @@ -1588,19 +1587,19 @@ contract MapleLoanLogic_InitializeTests is TestUtils { uint256[4] internal defaultRates; ConstructableMapleLoan internal loan; - MapleGlobalsMock internal globals; MockERC20 internal token1; MockERC20 internal token2; MockFactory internal factory; MockFeeManager internal feeManager; + MockGlobals internal globals; MockLoanManager internal lender; address governor = address(new Address()); function setUp() external { feeManager = new MockFeeManager(); + globals = new MockGlobals(governor); lender = new MockLoanManager(); - globals = new MapleGlobalsMock(governor); token1 = new MockERC20("Token0", "T0", 0); token2 = new MockERC20("Token1", "T1", 0); @@ -1785,23 +1784,22 @@ contract MapleLoanLogic_MakePaymentTests is TestUtils { address internal borrower = address(new Address()); address internal governor = address(new Address()); - MapleGlobalsMock internal globals; MapleLoanHarness internal loan; MockERC20 internal collateralAsset; MockERC20 internal fundsAsset; MockFactory internal factory; MockFeeManager internal feeManager; + MockGlobals internal globals; MockLoanManager internal lender; function setUp() external { collateralAsset = new MockERC20("collateralAsset", "CA", 0); feeManager = new MockFeeManager(); fundsAsset = new MockERC20("FundsAsset", "FA", 0); + globals = new MockGlobals(governor); lender = new MockLoanManager(); loan = new MapleLoanHarness(); - globals = new MapleGlobalsMock(governor); - factory = new MockFactory(address(globals)); loan.__setBorrower(borrower); @@ -2152,16 +2150,16 @@ contract MapleLoanLogic_PostCollateralTests is TestUtils { uint256 internal constant MAX_COLLATERAL = type(uint256).max - 1; uint256 internal constant MIN_COLLATERAL = 0; - MapleGlobalsMock internal globals; MapleLoanHarness internal loan; MockERC20 internal collateralAsset; MockFactory internal factory; + MockGlobals internal globals; function setUp() external { loan = new MapleLoanHarness(); collateralAsset = new MockERC20("CollateralAsset", "CA", 0); - globals = new MapleGlobalsMock(address(0)); + globals = new MockGlobals(address(0)); factory = new MockFactory(address(globals)); @@ -2216,14 +2214,14 @@ contract MapleLoanLogic_PostCollateralTests is TestUtils { contract MapleLoanLogic_ProposeNewTermsTests is TestUtils { - MapleGlobalsMock internal globals; MapleLoanHarness internal loan; MockFactory internal factory; + MockGlobals internal globals; address internal borrower = address(new Address()); function setUp() external { - globals = new MapleGlobalsMock(address(0)); + globals = new MockGlobals(address(0)); loan = new MapleLoanHarness(); factory = new MockFactory(address(globals)); @@ -2281,19 +2279,20 @@ contract MapleLoanLogic_ProposeNewTermsTests is TestUtils { contract MapleLoanLogic_RejectNewTermsTests is TestUtils { - MapleGlobalsMock globals; MapleLoanHarness loan; MockFactory factory; + MockGlobals globals; Refinancer refinancer; address borrower = address(new Address()); function setUp() external { - globals = new MapleGlobalsMock(address(0)); - factory = new MockFactory(address(globals)); + globals = new MockGlobals(address(0)); loan = new MapleLoanHarness(); refinancer = new Refinancer(); + factory = new MockFactory(address(globals)); + loan.__setBorrower(borrower); loan.__setFactory(address(factory)); @@ -2376,16 +2375,16 @@ contract MapleLoanLogic_RemoveCollateralTests is TestUtils { uint256 internal constant MAX_PRINCIPAL = type(uint256).max - 1; uint256 internal constant MIN_PRINCIPAL = 1; - MapleGlobalsMock internal globals; MapleLoanHarness internal loan; MockERC20 internal collateralAsset; MockFactory internal factory; + MockGlobals internal globals; address internal borrower = address(new Address()); function setUp() external { collateralAsset = new MockERC20("CollateralAsset", "CA", 0); - globals = new MapleGlobalsMock(address(0)); + globals = new MockGlobals(address(0)); loan = new MapleLoanHarness(); factory = new MockFactory(address(globals)); @@ -2595,17 +2594,17 @@ contract MapleLoanLogic_RemoveCollateralTests is TestUtils { contract MapleLoanLogic_RepossessTests is TestUtils { - MapleGlobalsMock internal globals; MapleLoanHarness internal loan; MockERC20 internal collateralAsset; MockERC20 internal fundsAsset; MockFactory internal factory; + MockGlobals internal globals; MockLoanManager internal lender; function setUp() external { collateralAsset = new MockERC20("Collateral Asset", "CA", 0); fundsAsset = new MockERC20("Funds Asset", "FA", 0); - globals = new MapleGlobalsMock(address(0)); + globals = new MockGlobals(address(0)); lender = new MockLoanManager(); loan = new MapleLoanHarness(); @@ -2702,17 +2701,18 @@ contract MapleLoanLogic_RepossessTests is TestUtils { contract MapleLoanLogic_ReturnFundsTests is TestUtils { - MapleGlobalsMock internal globals; MapleLoanHarness internal loan; MockERC20 internal fundsAsset; MockFactory internal factory; + MockGlobals internal globals; function setUp() external { - globals = new MapleGlobalsMock(address(0)); - factory = new MockFactory(address(globals)); fundsAsset = new MockERC20("Funds Asset", "FA", 0); + globals = new MockGlobals(address(0)); loan = new MapleLoanHarness(); + factory = new MockFactory(address(globals)); + loan.__setFactory(address(factory)); loan.__setFundsAsset(address(fundsAsset)); } @@ -2824,18 +2824,18 @@ contract MapleLoanLogic_ScaledExponentTests is TestUtils { contract MapleLoanLogic_SkimTests is TestUtils { - MapleGlobalsMock internal globals; MapleLoanHarness internal loan; MockERC20 internal collateralAsset; MockERC20 internal fundsAsset; MockFactory internal factory; + MockGlobals internal globals; address user = address(new Address()); function setUp() external { collateralAsset = new MockERC20("Collateral Asset", "CA", 0); - globals = new MapleGlobalsMock(address(0)); fundsAsset = new MockERC20("Funds Asset", "FA", 0); + globals = new MockGlobals(address(0)); loan = new MapleLoanHarness(); factory = new MockFactory(address(globals)); diff --git a/tests/MapleLoanScenarios.t.sol b/tests/MapleLoanScenarios.t.sol index 59eb8b1..3b635e4 100644 --- a/tests/MapleLoanScenarios.t.sol +++ b/tests/MapleLoanScenarios.t.sol @@ -6,24 +6,24 @@ import { MockERC20 } from "../modules/erc20/contracts/test/mocks/MockER import { ConstructableMapleLoan } from "./harnesses/MapleLoanHarnesses.sol"; -import { EmptyContract, MapleGlobalsMock, MockFactory, MockFeeManager, MockLoanManager } from "./mocks/Mocks.sol"; +import { EmptyContract, MockFactory, MockFeeManager, MockGlobals, MockLoanManager } from "./mocks/Mocks.sol"; // TODO: Add fees contract MapleLoanScenariosTests is TestUtils { - MapleGlobalsMock internal globals; - MockERC20 internal token; - MockFactory internal factory; - MockFeeManager internal feeManager; - MockLoanManager internal lender; + MockERC20 internal token; + MockFactory internal factory; + MockFeeManager internal feeManager; + MockGlobals internal globals; + MockLoanManager internal lender; address internal borrower = address(new Address()); address internal governor = address(new Address()); function setUp() external { feeManager = new MockFeeManager(); + globals = new MockGlobals(governor); lender = new MockLoanManager(); - globals = new MapleGlobalsMock(governor); token = new MockERC20("Test", "TST", 0); factory = new MockFactory(address(globals)); diff --git a/tests/Payments.t.sol b/tests/Payments.t.sol index cd7c101..b7d41a4 100644 --- a/tests/Payments.t.sol +++ b/tests/Payments.t.sol @@ -9,19 +9,19 @@ import { MapleLoan } from "../contracts/MapleLoan.sol"; import { MapleLoanFeeManager } from "../contracts/MapleLoanFeeManager.sol"; import { MapleLoanInitializer } from "../contracts/MapleLoanInitializer.sol"; -import { MapleGlobalsMock, MockLoanManager, MockPoolManager } from "./mocks/Mocks.sol"; +import { MockGlobals, MockLoanManager, MockPoolManager } from "./mocks/Mocks.sol"; contract MapleLoanPaymentsTestBase is TestUtils { uint256 internal start; - MapleGlobalsMock internal globals; MapleLoan internal implementation; MapleLoanFeeManager internal feeManager; MapleLoanInitializer internal initializer; MapleProxyFactory internal factory; MockERC20 internal collateralAsset; MockERC20 internal fundsAsset; + MockGlobals internal globals; MockLoanManager internal lender; MockPoolManager internal poolManager; @@ -35,13 +35,12 @@ contract MapleLoanPaymentsTestBase is TestUtils { collateralAsset = new MockERC20("Collateral Asset", "CA", 18); fundsAsset = new MockERC20("Funds Asset", "FA", 18); - lender = new MockLoanManager(); + globals = new MockGlobals(governor); implementation = new MapleLoan(); initializer = new MapleLoanInitializer(); + lender = new MockLoanManager(); poolManager = new MockPoolManager(address(poolDelegate)); - globals = new MapleGlobalsMock(governor); - feeManager = new MapleLoanFeeManager(address(globals)); lender.__setPoolManager(address(poolManager)); diff --git a/tests/Refinancer.t.sol b/tests/Refinancer.t.sol index 2c7fb8d..e84266c 100644 --- a/tests/Refinancer.t.sol +++ b/tests/Refinancer.t.sol @@ -10,9 +10,9 @@ import { Refinancer } from "../contracts/Refinancer.sol"; import { ConstructableMapleLoan } from "./harnesses/MapleLoanHarnesses.sol"; import { - MapleGlobalsMock, MockFactory, MockFeeManager, + MockGlobals, MockLoanManager, MockPoolManager } from "./mocks/Mocks.sol"; @@ -28,10 +28,10 @@ contract RefinancerTestBase is TestUtils { uint256 internal constant MIN_TOKEN_AMOUNT = 10 ** 6; // Needed so payments don't round down to zero ConstructableMapleLoan internal loan; - MapleGlobalsMock internal globals; MockERC20 internal token; MockFactory internal factory; MockFeeManager internal feeManager; + MockGlobals internal globals; MockLoanManager internal lender; Refinancer internal refinancer; @@ -40,11 +40,11 @@ contract RefinancerTestBase is TestUtils { function setUp() public virtual { feeManager = new MockFeeManager(); + globals = new MockGlobals(governor); lender = new MockLoanManager(); refinancer = new Refinancer(); token = new MockERC20("Test", "TST", 0); - globals = new MapleGlobalsMock(governor); factory = new MockFactory(address(globals)); globals.setValidBorrower(borrower, true); @@ -678,10 +678,10 @@ contract RefinancerInterestTests is TestUtils { uint256 internal constant WAD = 1e18; ConstructableMapleLoan internal loan; - MapleGlobalsMock internal globals; MockERC20 internal token; MockFactory internal factory; MockFeeManager internal feeManager; + MockGlobals internal globals; MockLoanManager internal lender; Refinancer internal refinancer; @@ -690,11 +690,11 @@ contract RefinancerInterestTests is TestUtils { function setUp() external { feeManager = new MockFeeManager(); + globals = new MockGlobals(address(governor)); lender = new MockLoanManager(); refinancer = new Refinancer(); token = new MockERC20("Test", "TST", 0); - globals = new MapleGlobalsMock(address(governor)); factory = new MockFactory(address(globals)); globals.setValidBorrower(borrower, true); @@ -1225,10 +1225,10 @@ contract RefinancingFeesTerms is TestUtils { uint256 internal constant MIN_TOKEN_AMOUNT = 10 ** 6; // Needed so payments don't round down to zero ConstructableMapleLoan internal loan; - MapleGlobalsMock internal globals; MapleLoanFeeManager internal feeManager; MockERC20 internal token; MockFactory internal factory; + MockGlobals internal globals; MockLoanManager internal lender; MockPoolManager internal poolManager; Refinancer internal refinancer; @@ -1238,7 +1238,7 @@ contract RefinancingFeesTerms is TestUtils { function setUp() public virtual { lender = new MockLoanManager(); - globals = new MapleGlobalsMock(governor); + globals = new MockGlobals(governor); poolManager = new MockPoolManager(address(POOL_DELEGATE)); refinancer = new Refinancer(); diff --git a/tests/mocks/Mocks.sol b/tests/mocks/Mocks.sol index 6c7a52e..09caeff 100644 --- a/tests/mocks/Mocks.sol +++ b/tests/mocks/Mocks.sol @@ -3,7 +3,9 @@ pragma solidity 0.8.7; import { IERC20 } from "../../modules/erc20/contracts/interfaces/IERC20.sol"; -contract MapleGlobalsMock { +contract MockGlobals { + + bool internal _isFunctionPaused; address public governor; address public mapleTreasury; @@ -18,16 +20,14 @@ contract MapleGlobalsMock { mapping(address => bool) public isCollateralAsset; mapping(address => bool) public isPoolAsset; - bool internal _isFactory; bool internal _isInstanceOf; constructor (address governor_) { governor = governor_; - _isFactory = true; } - function isFactory(bytes32, address) external view returns (bool) { - return _isFactory; + function isFunctionPaused(bytes4) external view returns (bool isFunctionPaused_) { + isFunctionPaused_ = _isFunctionPaused; } function isInstanceOf(bytes32, address) external view returns (bool) { @@ -70,10 +70,10 @@ contract MapleGlobalsMock { isPoolAsset[poolAsset_] = isValid_; } - function __setIsFactory(bool isFactory_) external { - _isFactory = isFactory_; + function __setFunctionPaused(bool paused_) external { + _isFunctionPaused = paused_; } - + function __setIsInstanceOf(bool isInstanceOf_) external { _isInstanceOf = isInstanceOf_; } From 670e9fe6dea857c8a0b203893fb32b66018f87d8 Mon Sep 17 00:00:00 2001 From: Michael De Luca <35537333+deluca-mike@users.noreply.github.com> Date: Wed, 5 Apr 2023 21:17:26 -0400 Subject: [PATCH 16/47] feat: `securityAdmin` can upgrade (#281) --- contracts/MapleLoan.sol | 2 +- contracts/interfaces/Interfaces.sol | 2 ++ tests/MapleLoan.t.sol | 27 +++++++++++++++++++++------ tests/mocks/Mocks.sol | 8 ++++---- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index 87a1445..cc97c19 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -65,7 +65,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } function upgrade(uint256 toVersion_, bytes calldata arguments_) external override whenNotPaused { - require(msg.sender == _borrower, "ML:U:NOT_BORROWER"); + require(msg.sender == _borrower || msg.sender == IMapleGlobalsLike(globals()).securityAdmin(), "ML:U:NO_AUTH"); emit Upgraded(toVersion_, arguments_); diff --git a/contracts/interfaces/Interfaces.sol b/contracts/interfaces/Interfaces.sol index 89506b7..cc2b1ff 100644 --- a/contracts/interfaces/Interfaces.sol +++ b/contracts/interfaces/Interfaces.sol @@ -21,6 +21,8 @@ interface IMapleGlobalsLike { function platformServiceFeeRate(address pool_) external view returns (uint256 platformFee_); + function securityAdmin() external view returns (address securityAdmin_); + } interface ILenderLike { diff --git a/tests/MapleLoan.t.sol b/tests/MapleLoan.t.sol index 8eb064d..cd0853d 100644 --- a/tests/MapleLoan.t.sol +++ b/tests/MapleLoan.t.sol @@ -15,9 +15,10 @@ contract MapleLoanTests is TestUtils { MockFeeManager internal feeManager; MockGlobals internal globals; - address internal borrower = address(new Address()); - address internal governor = address(new Address()); - address internal user = address(new Address()); + address internal borrower = address(new Address()); + address internal governor = address(new Address()); + address internal securityAdmin = address(new Address()); + address internal user = address(new Address()); address internal lender; @@ -37,6 +38,7 @@ contract MapleLoanTests is TestUtils { loan.__setLender(lender); globals.__setIsInstanceOf(true); + globals.__setSecurityAdmin(securityAdmin); } /***********************************/ @@ -261,7 +263,7 @@ contract MapleLoanTests is TestUtils { vm.prank(borrower); bytes32 refinanceCommitment = loan.proposeNewTerms(mockRefinancer, deadline, calls); - assertEq(refinanceCommitment, bytes32(0x26478c38be89f84468d7528127656a3342aabe17eb79f47f93c2848beb573afb)); + assertEq(refinanceCommitment, bytes32(0x1e5d5a3131b2767db93add6039629037a11bd673fe4726b7e3afc4527f96aeaf)); } function test_proposeNewTerms_acl() external { @@ -435,11 +437,15 @@ contract MapleLoanTests is TestUtils { loan.acceptLender(); } - function test_upgrade_acl() external { + function test_upgrade_acl_noAuth() external { address newImplementation = address(new MapleLoanHarness()); - vm.expectRevert("ML:U:NOT_BORROWER"); + vm.expectRevert("ML:U:NO_AUTH"); loan.upgrade(1, abi.encode(newImplementation)); + } + + function test_upgrade_acl_borrower() external { + address newImplementation = address(new MapleLoanHarness()); vm.prank(borrower); loan.upgrade(1, abi.encode(newImplementation)); @@ -447,6 +453,15 @@ contract MapleLoanTests is TestUtils { assertEq(loan.implementation(), newImplementation); } + function test_upgrade_acl_securityAdmin() external { + address newImplementation = address(new MapleLoanHarness()); + + vm.prank(securityAdmin); + loan.upgrade(1, abi.encode(newImplementation)); + + assertEq(loan.implementation(), newImplementation); + } + /**************************************************************************************************************************************/ /*** Loan Transfer-Related Tests ***/ /**************************************************************************************************************************************/ diff --git a/tests/mocks/Mocks.sol b/tests/mocks/Mocks.sol index 09caeff..fb9862d 100644 --- a/tests/mocks/Mocks.sol +++ b/tests/mocks/Mocks.sol @@ -42,10 +42,6 @@ contract MockGlobals { mapleTreasury = mapleTreasury_; } - function setSecurityAdmin(address securityAdmin_) external { - securityAdmin = securityAdmin_; - } - function setPlatformOriginationFeeRate(address poolManager_, uint256 feeRate_) external { platformOriginationFeeRate[poolManager_] = feeRate_; } @@ -78,6 +74,10 @@ contract MockGlobals { _isInstanceOf = isInstanceOf_; } + function __setSecurityAdmin(address securityAdmin_) external { + securityAdmin = securityAdmin_; + } + } contract MockFactory { From ab7b337b73cf63f0f3ce72f2481a12abc5403901 Mon Sep 17 00:00:00 2001 From: Michael De Luca <35537333+deluca-mike@users.noreply.github.com> Date: Fri, 21 Apr 2023 07:54:06 -0400 Subject: [PATCH 17/47] feat: Add isInstanceOf check for FeeManager (SC-11990) (#282) --- contracts/MapleLoanInitializer.sol | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/contracts/MapleLoanInitializer.sol b/contracts/MapleLoanInitializer.sol index c29b02f..6af1110 100644 --- a/contracts/MapleLoanInitializer.sol +++ b/contracts/MapleLoanInitializer.sol @@ -110,21 +110,22 @@ contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { require(termDetails_[1] > 0, "MLI:I:INVALID_PAYMENT_INTERVAL"); require(termDetails_[2] > 0, "MLI:I:INVALID_PAYMENTS_REMAINING"); - address globals_ = IMapleProxyFactoryLike(msg.sender).mapleGlobals(); + IMapleGlobalsLike globals_ = IMapleGlobalsLike(IMapleProxyFactoryLike(msg.sender).mapleGlobals()); - require((_borrower = borrower_) != address(0), "MLI:I:ZERO_BORROWER"); - require(IMapleGlobalsLike(globals_).isBorrower(borrower_), "MLI:I:INVALID_BORROWER"); - require(IMapleGlobalsLike(globals_).isPoolAsset(assets_[1]), "MLI:I:INVALID_FUNDS_ASSET"); - require(IMapleGlobalsLike(globals_).isCollateralAsset(assets_[0]), "MLI:I:INVALID_COLLATERAL_ASSET"); + require((_borrower = borrower_) != address(0), "MLI:I:ZERO_BORROWER"); + require(globals_.isBorrower(borrower_), "MLI:I:INVALID_BORROWER"); + require(globals_.isPoolAsset(assets_[1]), "MLI:I:INVALID_FUNDS_ASSET"); + require(globals_.isCollateralAsset(assets_[0]), "MLI:I:INVALID_COLLATERAL_ASSET"); require((_lender = lender_) != address(0), "MLI:I:ZERO_LENDER"); address loanManagerFactory_ = ILenderLike(lender_).factory(); - require(IMapleGlobalsLike(globals_).isInstanceOf("FT_LOAN_MANAGER_FACTORY", loanManagerFactory_), "MLI:I:INVALID_FACTORY"); - require(IMapleProxyFactoryLike(loanManagerFactory_).isInstance(lender_), "MLI:I:INVALID_INSTANCE"); + require(globals_.isInstanceOf("FT_LOAN_MANAGER_FACTORY", loanManagerFactory_), "MLI:I:INVALID_FACTORY"); + require(IMapleProxyFactoryLike(loanManagerFactory_).isInstance(lender_), "MLI:I:INVALID_INSTANCE"); - require((_feeManager = feeManager_) != address(0), "MLI:I:INVALID_MANAGER"); + require((_feeManager = feeManager_) != address(0), "MLI:I:INVALID_MANAGER"); + require(globals_.isInstanceOf("FEE_MANAGER", feeManager_), "MLI:I:INVALID_FEE_MANAGER"); _collateralAsset = assets_[0]; _fundsAsset = assets_[1]; From d5068b66a4e434ee770cdd8320e37a88dce56ba4 Mon Sep 17 00:00:00 2001 From: Michael De Luca <35537333+deluca-mike@users.noreply.github.com> Date: Fri, 21 Apr 2023 08:06:23 -0400 Subject: [PATCH 18/47] fix: Underscore syntax [3S #9] (SC-12033) (#284) * fix: underscore syntax [3S #9] (SC-12033) * refactor: cleanup --------- Co-authored-by: Lucas Manuel --- contracts/MapleLoan.sol | 78 ++++++++++--------- contracts/MapleLoanFeeManager.sol | 8 +- contracts/MapleLoanInitializer.sol | 24 +++--- contracts/interfaces/IMapleLoanEvents.sol | 16 ++-- contracts/interfaces/IMapleLoanFeeManager.sol | 26 ++++--- .../interfaces/IMapleLoanInitializer.sol | 12 +-- 6 files changed, 85 insertions(+), 79 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index cc97c19..19cae2a 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -104,11 +104,11 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { _refinanceInterest = uint256(0); - uint256 principalAndInterest = principal_ + interest_; + uint256 principalAndInterest_ = principal_ + interest_; // The drawable funds are increased by the extra funds in the contract, minus the total needed for payment. // NOTE: This line will revert if not enough funds were added for the full payment amount. - _drawableFunds = (_drawableFunds + getUnaccountedAmount(_fundsAsset)) - principalAndInterest; + _drawableFunds = (_drawableFunds + getUnaccountedAmount(_fundsAsset)) - principalAndInterest_; fees_ = _handleServiceFeePayment(_paymentsRemaining); @@ -116,11 +116,11 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { emit LoanClosed(principal_, interest_, fees_); - require(ERC20Helper.transfer(_fundsAsset, _lender, principalAndInterest), "ML:MP:TRANSFER_FAILED"); + require(ERC20Helper.transfer(_fundsAsset, _lender, principalAndInterest_), "ML:MP:TRANSFER_FAILED"); ILenderLike(_lender).claim(principal_, interest_, paymentDueDate_, 0); - emit FundsClaimed(principalAndInterest, _lender); + emit FundsClaimed(principalAndInterest_, _lender); } function drawdownFunds(uint256 amount_, address destination_) external override whenNotPaused returns (uint256 collateralPosted_) { @@ -129,15 +129,15 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { emit FundsDrawnDown(amount_, destination_); // Post additional collateral required to facilitate this drawdown, if needed. - uint256 additionalCollateralRequired = getAdditionalCollateralRequiredFor(amount_); + uint256 additionalCollateralRequired_ = getAdditionalCollateralRequiredFor(amount_); - if (additionalCollateralRequired > uint256(0)) { + if (additionalCollateralRequired_ > uint256(0)) { // Determine collateral currently unaccounted for. - uint256 unaccountedCollateral = getUnaccountedAmount(_collateralAsset); + uint256 unaccountedCollateral_ = getUnaccountedAmount(_collateralAsset); // Post required collateral, specifying then amount lacking as the optional amount to be transferred from. collateralPosted_ = postCollateral( - additionalCollateralRequired > unaccountedCollateral ? additionalCollateralRequired - unaccountedCollateral : uint256(0) + additionalCollateralRequired_ > unaccountedCollateral_ ? additionalCollateralRequired_ - unaccountedCollateral_ : uint256(0) ); } @@ -163,11 +163,11 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { _refinanceInterest = uint256(0); - uint256 principalAndInterest = principal_ + interest_; + uint256 principalAndInterest_ = principal_ + interest_; // The drawable funds are increased by the extra funds in the contract, minus the total needed for payment. // NOTE: This line will revert if not enough funds were added for the full payment amount. - _drawableFunds = (_drawableFunds + getUnaccountedAmount(_fundsAsset)) - principalAndInterest; + _drawableFunds = (_drawableFunds + getUnaccountedAmount(_fundsAsset)) - principalAndInterest_; fees_ = _handleServiceFeePayment(1); @@ -185,11 +185,11 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { emit PaymentMade(principal_, interest_, fees_); - require(ERC20Helper.transfer(_fundsAsset, _lender, principalAndInterest), "ML:MP:TRANSFER_FAILED"); + require(ERC20Helper.transfer(_fundsAsset, _lender, principalAndInterest_), "ML:MP:TRANSFER_FAILED"); ILenderLike(_lender).claim(principal_, interest_, previousPaymentDueDate_, nextPaymentDueDate_); - emit FundsClaimed(principalAndInterest, _lender); + emit FundsClaimed(principalAndInterest_, _lender); require(_isCollateralMaintained(), "ML:MP:INSUFFICIENT_COLLATERAL"); } @@ -295,25 +295,26 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { feeManager_.updateRefinanceServiceFees(previousPrincipalRequested, timeSinceLastDueDate_); // Get the amount of interest owed since the last payment due date, as well as the time since the last due date - uint256 proRataInterest = getRefinanceInterest(block.timestamp); + uint256 proRataInterest_ = getRefinanceInterest(block.timestamp); // In case there is still a refinance interest, just increment it instead of setting it. - _refinanceInterest += proRataInterest; + _refinanceInterest += proRataInterest_; // Clear refinance commitment to prevent implications of re-acceptance of another call to `_acceptNewTerms`. _refinanceCommitment = bytes32(0); - for (uint256 i; i < calls_.length;) { - ( bool success, ) = refinancer_.delegatecall(calls_[i]); - require(success, "ML:ANT:FAILED"); - unchecked { ++i; } + for (uint256 i_; i_ < calls_.length;) { + ( bool success_, ) = refinancer_.delegatecall(calls_[i_]); + require(success_, "ML:ANT:FAILED"); + unchecked { ++i_; } } emit NewTermsAccepted(refinanceCommitment_, refinancer_, deadline_, calls_); address fundsAsset_ = _fundsAsset; uint256 principalRequested_ = _principalRequested; - paymentInterval_ = _paymentInterval; + + paymentInterval_ = _paymentInterval; // Increment the due date to be one full payment interval from now, to restart the payment schedule with new terms. // NOTE: `_paymentInterval` here is possibly newly set via the above delegate calls, so cache it. @@ -462,18 +463,18 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { function getAdditionalCollateralRequiredFor(uint256 drawdown_) public view override returns (uint256 collateral_) { // Determine the collateral needed in the contract for a reduced drawable funds amount. - uint256 collateralNeeded = _getCollateralRequiredFor(_principal, _drawableFunds - drawdown_, _principalRequested, _collateralRequired); - uint256 currentCollateral = _collateral; + uint256 collateralNeeded_ = _getCollateralRequiredFor(_principal, _drawableFunds - drawdown_, _principalRequested, _collateralRequired); + uint256 currentCollateral_ = _collateral; - return collateralNeeded > currentCollateral ? collateralNeeded - currentCollateral : uint256(0); + return collateralNeeded_ > currentCollateral_ ? collateralNeeded_ - currentCollateral_ : uint256(0); } function getClosingPaymentBreakdown() public view override returns (uint256 principal_, uint256 interest_, uint256 fees_) { ( - uint256 delegateServiceFee_, - uint256 delegateRefinanceFee_, - uint256 platformServiceFee_, - uint256 platformRefinanceFee_ + uint256 delegateServiceFee_, + uint256 delegateRefinanceFee_, + uint256 platformServiceFee_, + uint256 platformRefinanceFee_ ) = IMapleLoanFeeManager(_feeManager).getServiceFeeBreakdown(address(this), _paymentsRemaining); fees_ = delegateServiceFee_ + platformServiceFee_ + delegateRefinanceFee_ + platformRefinanceFee_; @@ -572,10 +573,10 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } function excessCollateral() external view override returns (uint256 excessCollateral_) { - uint256 collateralNeeded = _getCollateralRequiredFor(_principal, _drawableFunds, _principalRequested, _collateralRequired); - uint256 currentCollateral = _collateral; + uint256 collateralNeeded_ = _getCollateralRequiredFor(_principal, _drawableFunds, _principalRequested, _collateralRequired); + uint256 currentCollateral_ = _collateral; - return currentCollateral > collateralNeeded ? currentCollateral - collateralNeeded : uint256(0); + return currentCollateral_ > collateralNeeded_ ? currentCollateral_ - collateralNeeded_ : uint256(0); } function factory() external view override returns (address factory_) { @@ -743,17 +744,17 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { * - Both of these rates are scaled by 1e18 (e.g., 12% => 0.12 * 10 ** 18) * \*************************************************************************************************/ - uint256 periodicRate = _getPeriodicInterestRate(interestRate_, paymentInterval_); // 1e18 decimal precision - uint256 raisedRate = _scaledExponent(SCALED_ONE + periodicRate, totalPayments_, SCALED_ONE); // 1e18 decimal precision + uint256 periodicRate_ = _getPeriodicInterestRate(interestRate_, paymentInterval_); // 1e18 decimal precision + uint256 raisedRate_ = _scaledExponent(SCALED_ONE + periodicRate_, totalPayments_, SCALED_ONE); // 1e18 decimal precision - // NOTE: If a lack of precision in `_scaledExponent` results in a `raisedRate` smaller than one, + // NOTE: If a lack of precision in `_scaledExponent` results in a `raisedRate_` smaller than one, // assume it to be one and simplify the equation. - if (raisedRate <= SCALED_ONE) return ((principal_ - endingPrincipal_) / totalPayments_, uint256(0)); + if (raisedRate_ <= SCALED_ONE) return ((principal_ - endingPrincipal_) / totalPayments_, uint256(0)); - uint256 total = ((((principal_ * raisedRate) / SCALED_ONE) - endingPrincipal_) * periodicRate) / (raisedRate - SCALED_ONE); + uint256 total_ = ((((principal_ * raisedRate_) / SCALED_ONE) - endingPrincipal_) * periodicRate_) / (raisedRate_ - SCALED_ONE); interestAmount_ = _getInterest(principal_, interestRate_, paymentInterval_); - principalAmount_ = total >= interestAmount_ ? total - interestAmount_ : uint256(0); + principalAmount_ = total_ >= interestAmount_ ? total_ - interestAmount_ : uint256(0); } /// @dev Returns an amount by applying an annualized and scaled interest rate, to a principal, over an interval of time. @@ -775,7 +776,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { ) internal view returns ( - uint256 principalAmount_, + uint256 principalAmount_, uint256[3] memory interest_, uint256[2] memory fees_ ) @@ -865,9 +866,9 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { // 24h0m1s late would be two full days late. // ((86400n - 0n + (86400n - 1n)) / 86400n) * 86400n = 86400n // ((86401n - 0n + (86400n - 1n)) / 86400n) * 86400n = 172800n - uint256 fullDaysLate = ((currentTime_ - nextPaymentDueDate_ + (1 days - 1)) / 1 days) * 1 days; + uint256 fullDaysLate_ = ((currentTime_ - nextPaymentDueDate_ + (1 days - 1)) / 1 days) * 1 days; - lateInterest_ += _getInterest(principal_, interestRate_ + lateInterestPremium_, fullDaysLate); + lateInterest_ += _getInterest(principal_, interestRate_ + lateInterestPremium_, fullDaysLate_); lateInterest_ += (lateFeeRate_ * principal_) / HUNDRED_PERCENT; } @@ -899,6 +900,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { function _handleImpairment() internal { if (!isImpaired()) return; + _originalNextPaymentDueDate = uint256(0); } diff --git a/contracts/MapleLoanFeeManager.sol b/contracts/MapleLoanFeeManager.sol index 0ca3eb9..1e49c35 100644 --- a/contracts/MapleLoanFeeManager.sol +++ b/contracts/MapleLoanFeeManager.sol @@ -123,9 +123,9 @@ contract MapleLoanFeeManager is IMapleLoanFeeManager { /**************************************************************************************************************************************/ function getDelegateServiceFeesForPeriod(address loan_, uint256 interval_) public view override returns (uint256 delegateServiceFee_) { - uint256 paymentInterval = ILoanLike(loan_).paymentInterval(); + uint256 paymentInterval_ = ILoanLike(loan_).paymentInterval(); - delegateServiceFee_ = delegateServiceFee[loan_] * interval_ / paymentInterval; + delegateServiceFee_ = delegateServiceFee[loan_] * interval_ / paymentInterval_; } function getPlatformOriginationFee(address loan_, uint256 principalRequested_) @@ -167,11 +167,11 @@ contract MapleLoanFeeManager is IMapleLoanFeeManager { } function getServiceFeesForPeriod(address loan_, uint256 interval_) external view override returns (uint256 serviceFee_) { - uint256 principalRequested = ILoanLike(loan_).principalRequested(); + uint256 principalRequested_ = ILoanLike(loan_).principalRequested(); serviceFee_ = getDelegateServiceFeesForPeriod(loan_, interval_) + - getPlatformServiceFeeForPeriod(loan_, principalRequested, interval_); + getPlatformServiceFeeForPeriod(loan_, principalRequested_, interval_); } function getOriginationFees(address loan_, uint256 principalRequested_) external view override returns (uint256 originationFees_) { diff --git a/contracts/MapleLoanInitializer.sol b/contracts/MapleLoanInitializer.sol index 6af1110..bead3e7 100644 --- a/contracts/MapleLoanInitializer.sol +++ b/contracts/MapleLoanInitializer.sol @@ -11,9 +11,9 @@ import { MapleLoanStorage } from "./MapleLoanStorage.sol"; contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { function encodeArguments( - address borrower_, - address lender_, - address feeManager_, + address borrower_, + address lender_, + address feeManager_, address[2] memory assets_, uint256[3] memory termDetails_, uint256[3] memory amounts_, @@ -25,9 +25,9 @@ contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { function decodeArguments(bytes calldata encodedArguments_) public pure override returns ( - address borrower_, - address lender_, - address feeManager_, + address borrower_, + address lender_, + address feeManager_, address[2] memory assets_, uint256[3] memory termDetails_, uint256[3] memory amounts_, @@ -49,9 +49,9 @@ contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { fallback() external { ( - address borrower_, - address lender_, - address feeManager_, + address borrower_, + address lender_, + address feeManager_, address[2] memory assets_, uint256[3] memory termDetails_, uint256[3] memory amounts_, @@ -89,9 +89,9 @@ contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { * [1]: delegateServiceFee */ function _initialize( - address borrower_, - address lender_, - address feeManager_, + address borrower_, + address lender_, + address feeManager_, address[2] memory assets_, uint256[3] memory termDetails_, uint256[3] memory amounts_, diff --git a/contracts/interfaces/IMapleLoanEvents.sol b/contracts/interfaces/IMapleLoanEvents.sol index 7cf15bf..d8cc49c 100644 --- a/contracts/interfaces/IMapleLoanEvents.sol +++ b/contracts/interfaces/IMapleLoanEvents.sol @@ -77,14 +77,14 @@ interface IMapleLoanEvents { * [1]: delegateServiceFee */ event Initialized( - address indexed borrower_, - address indexed lender_, - address indexed feeManager_, - address[2] assets_, - uint256[3] termDetails_, - uint256[3] amounts_, - uint256[4] rates_, - uint256[2] fees_ + address indexed borrower_, + address indexed lender_, + address indexed feeManager_, + address[2] assets_, + uint256[3] termDetails_, + uint256[3] amounts_, + uint256[4] rates_, + uint256[2] fees_ ); /** diff --git a/contracts/interfaces/IMapleLoanFeeManager.sol b/contracts/interfaces/IMapleLoanFeeManager.sol index f41e68c..b5068f2 100644 --- a/contracts/interfaces/IMapleLoanFeeManager.sol +++ b/contracts/interfaces/IMapleLoanFeeManager.sol @@ -13,7 +13,7 @@ interface IMapleLoanFeeManager { * @param delegateOriginationFee_ The new value for delegate origination fee. * @param delegateServiceFee_ The new value for delegate service fee. */ - event FeeTermsUpdated(address loan_, uint256 delegateOriginationFee_, uint256 delegateServiceFee_); + event FeeTermsUpdated(address indexed loan_, uint256 delegateOriginationFee_, uint256 delegateServiceFee_); /** * @dev A fee payment was made. @@ -21,14 +21,14 @@ interface IMapleLoanFeeManager { * @param delegateOriginationFee_ The amount of delegate origination fee paid. * @param platformOriginationFee_ The amount of platform origination fee paid. */ - event OriginationFeesPaid(address loan_, uint256 delegateOriginationFee_, uint256 platformOriginationFee_); + event OriginationFeesPaid(address indexed loan_, uint256 delegateOriginationFee_, uint256 platformOriginationFee_); /** * @dev New fee terms have been set. * @param loan_ The address of the loan contract. * @param platformServiceFee_ The new value for the platform service fee. */ - event PlatformServiceFeeUpdated(address loan_, uint256 platformServiceFee_); + event PlatformServiceFeeUpdated(address indexed loan_, uint256 platformServiceFee_); /** * @dev New fee terms have been set. @@ -36,7 +36,7 @@ interface IMapleLoanFeeManager { * @param partialPlatformServiceFee_ The value for the platform service fee. * @param partialDelegateServiceFee_ The value for the delegate service fee. */ - event PartialRefinanceServiceFeesUpdated(address loan_, uint256 partialPlatformServiceFee_, uint256 partialDelegateServiceFee_); + event PartialRefinanceServiceFeesUpdated(address indexed loan_, uint256 partialPlatformServiceFee_, uint256 partialDelegateServiceFee_); /** * @dev A fee payment was made. @@ -47,11 +47,11 @@ interface IMapleLoanFeeManager { * @param partialRefinancePlatformServiceFee_ The amount of partial platform service fee from refinance paid. */ event ServiceFeesPaid( - address loan_, - uint256 delegateServiceFee_, - uint256 partialRefinanceDelegateServiceFee_, - uint256 platformServiceFee_, - uint256 partialRefinancePlatformServiceFee_ + address indexed loan_, + uint256 delegateServiceFee_, + uint256 partialRefinanceDelegateServiceFee_, + uint256 platformServiceFee_, + uint256 partialRefinancePlatformServiceFee_ ); /**************************************************************************************************************************************/ @@ -135,7 +135,7 @@ interface IMapleLoanFeeManager { * @param principalRequested_ The amount of principal requested in the loan. * @return originationFees_ The amount of origination fees to be paid. */ - function getOriginationFees(address loan_, uint256 principalRequested_) external view returns (uint256 originationFees_); + function getOriginationFees(address loan_, uint256 principalRequested_) external view returns (uint256 originationFees_); /** * @dev Gets the platform origination fee value for the given loan. @@ -152,7 +152,11 @@ interface IMapleLoanFeeManager { * @param interval_ The time, in seconds, to get the proportional fee for * @return platformServiceFee_ The amount of platform service fee to be paid. */ - function getPlatformServiceFeeForPeriod(address loan_, uint256 principalRequested_, uint256 interval_) external view returns (uint256 platformServiceFee_); + function getPlatformServiceFeeForPeriod( + address loan_, + uint256 principalRequested_, + uint256 interval_ + ) external view returns (uint256 platformServiceFee_); /** * @dev Gets the service fees for the given interval. diff --git a/contracts/interfaces/IMapleLoanInitializer.sol b/contracts/interfaces/IMapleLoanInitializer.sol index 20ef76c..15577d4 100644 --- a/contracts/interfaces/IMapleLoanInitializer.sol +++ b/contracts/interfaces/IMapleLoanInitializer.sol @@ -31,9 +31,9 @@ interface IMapleLoanInitializer is IMapleLoanEvents { * [1]: delegateServiceFee */ function encodeArguments( - address borrower_, - address lender_, - address feeManager_, + address borrower_, + address lender_, + address feeManager_, address[2] memory assets_, uint256[3] memory termDetails_, uint256[3] memory amounts_, @@ -68,9 +68,9 @@ interface IMapleLoanInitializer is IMapleLoanEvents { */ function decodeArguments(bytes calldata encodedArguments_) external pure returns ( - address borrower_, - address lender_, - address feeManager_, + address borrower_, + address lender_, + address feeManager_, address[2] memory assets_, uint256[3] memory termDetails_, uint256[3] memory amounts_, From 60268e387bf0e946415659fc0b6296562e1ce7b8 Mon Sep 17 00:00:00 2001 From: Michael De Luca <35537333+deluca-mike@users.noreply.github.com> Date: Fri, 21 Apr 2023 10:52:23 -0400 Subject: [PATCH 19/47] fix: DoS on funding due to existing drawableFunds [3S #29] (SC-12049) (#285) --- contracts/MapleLoan.sol | 2 +- tests/MapleLoanLogic.t.sol | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index 19cae2a..021466c 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -351,7 +351,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { uint256 originationFees_ = IMapleLoanFeeManager(_feeManager).payOriginationFees(fundsAsset_, principalRequested_); - _drawableFunds = principalRequested_ - originationFees_; + _drawableFunds += (principalRequested_ - originationFees_); require(getUnaccountedAmount(fundsAsset_) == uint256(0), "ML:FL:UNEXPECTED_FUNDS"); diff --git a/tests/MapleLoanLogic.t.sol b/tests/MapleLoanLogic.t.sol index 7af2859..db306b4 100644 --- a/tests/MapleLoanLogic.t.sol +++ b/tests/MapleLoanLogic.t.sol @@ -901,6 +901,28 @@ contract MapleLoanLogic_FundLoanTests is TestUtils { assertEq(loan.getUnaccountedAmount(address(fundsAsset)), 0); } + function test_fundLoan_fullFundingWithExistingDrawableFunds(uint256 principalRequested_) external { + principalRequested_ = constrictToRange(principalRequested_, MIN_PRINCIPAL, MAX_PRINCIPAL); + + loan.__setDrawableFunds(1); + loan.__setFundsAsset(address(fundsAsset)); + loan.__setPaymentInterval(30 days); + loan.__setPaymentsRemaining(1); + loan.__setPrincipalRequested(principalRequested_); + + fundsAsset.mint(address(loan), principalRequested_ + 1); + + vm.prank(address(lender)); + uint256 fundsLent_ = loan.fundLoan(); + + assertEq(fundsLent_, principalRequested_); + assertEq(loan.lender(), address(lender)); + assertEq(loan.nextPaymentDueDate(), block.timestamp + loan.paymentInterval()); + assertEq(loan.principal(), principalRequested_); + assertEq(loan.drawableFunds(), principalRequested_ + 1); + assertEq(loan.getUnaccountedAmount(address(fundsAsset)), 0); + } + function test_fundLoan_partialFunding(uint256 principalRequested_) external { principalRequested_ = constrictToRange(principalRequested_, MIN_PRINCIPAL, MAX_PRINCIPAL); From 1e7148eaeffeacb73d2164d43d2ab61e04a0f780 Mon Sep 17 00:00:00 2001 From: Vedran Bidin Date: Fri, 21 Apr 2023 18:28:02 +0300 Subject: [PATCH 20/47] feat: Add limits for maximum delegate origination fees (SC-12050) (#286) * fix: make change and fix tests * fix: account origination fees into assertions --------- Co-authored-by: lucas-manuel --- contracts/MapleLoanInitializer.sol | 9 +++- tests/InitializerAndMigrator.t.sol | 12 ++--- tests/MapleLoanFactory.t.sol | 14 +++--- tests/MapleLoanFeeManager.t.sol | 22 ++++----- tests/MapleLoanLogic.t.sol | 78 ++++++++++++++++++++++++++++-- tests/MapleLoanScenarios.t.sol | 2 +- tests/Refinancer.t.sol | 64 ++++++++++++------------ 7 files changed, 139 insertions(+), 62 deletions(-) diff --git a/contracts/MapleLoanInitializer.sol b/contracts/MapleLoanInitializer.sol index bead3e7..8337886 100644 --- a/contracts/MapleLoanInitializer.sol +++ b/contracts/MapleLoanInitializer.sol @@ -107,8 +107,13 @@ contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { require(amounts_[2] <= amounts_[1], "MLI:I:INVALID_ENDING_PRINCIPAL"); // Payment interval and payments remaining need to be non-zero. - require(termDetails_[1] > 0, "MLI:I:INVALID_PAYMENT_INTERVAL"); - require(termDetails_[2] > 0, "MLI:I:INVALID_PAYMENTS_REMAINING"); + require(termDetails_[0] >= 12 hours, "MLI:I:INVALID_GRACE_PERIOD"); + require(termDetails_[1] > 0, "MLI:I:INVALID_PAYMENT_INTERVAL"); + require(termDetails_[2] > 0, "MLI:I:INVALID_PAYMENTS_REMAINING"); + + uint256 maxOriginationFee_ = amounts_[1] * 0.025e6 / 1e6; // 2.5% of principal + + require(fees_[0] <= maxOriginationFee_, "MLI:I:INVALID_ORIGINATION_FEE"); IMapleGlobalsLike globals_ = IMapleGlobalsLike(IMapleProxyFactoryLike(msg.sender).mapleGlobals()); diff --git a/tests/InitializerAndMigrator.t.sol b/tests/InitializerAndMigrator.t.sol index abb8a8d..56516b3 100644 --- a/tests/InitializerAndMigrator.t.sol +++ b/tests/InitializerAndMigrator.t.sol @@ -52,11 +52,11 @@ contract MapleLoanInitializerAndMigratorTests is TestUtils { factory.enableUpgradePath(1, 2, address(migrator)); vm.stopPrank(); - address[2] memory assets = [address(asset), address(asset)]; - uint256[3] memory termDetails = [uint256(1), uint256(365 days), uint256(1)]; - uint256[3] memory amounts = [uint256(0), uint256(1_000_000e6), uint256(1_000_000e6)]; - uint256[4] memory rates = [uint256(0.1e18), uint256(0.02e18), uint256(0.03e18), uint256(0.04e18)]; - uint256[2] memory fees = [uint256(0), uint256(0)]; + address[2] memory assets = [address(asset), address(asset)]; + uint256[3] memory termDetails = [uint256(12 hours), uint256(365 days), uint256(1)]; + uint256[3] memory amounts = [uint256(0), uint256(1_000_000e6), uint256(1_000_000e6)]; + uint256[4] memory rates = [uint256(0.1e18), uint256(0.02e18), uint256(0.03e18), uint256(0.04e18)]; + uint256[2] memory fees = [uint256(0), uint256(0)]; bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( address(1), @@ -98,7 +98,7 @@ contract MapleLoanInitializerAndMigratorTests is TestUtils { assertEq(loan.principalRequested(), 1_000_000e6); // Check term details - assertEq(loan.gracePeriod(), 1); + assertEq(loan.gracePeriod(), 12 hours); assertEq(loan.paymentInterval(), 365 days); assertEq(loan.paymentsRemaining(), 1); diff --git a/tests/MapleLoanFactory.t.sol b/tests/MapleLoanFactory.t.sol index a65af0a..2706291 100644 --- a/tests/MapleLoanFactory.t.sol +++ b/tests/MapleLoanFactory.t.sol @@ -48,7 +48,7 @@ contract MapleLoanFactoryTest is TestUtils { function test_createInstance_invalidPoolAsset() external { address[2] memory assets = [address(1), address(1)]; - uint256[3] memory termDetails = [uint256(1), uint256(1), uint256(1)]; + uint256[3] memory termDetails = [uint256(12 hours), uint256(1), uint256(1)]; uint256[3] memory amounts = [uint256(1), uint256(1), uint256(0)]; uint256[4] memory rates = [uint256(0), uint256(0), uint256(0), uint256(0)]; uint256[2] memory fees = [uint256(0), uint256(0)]; @@ -76,7 +76,7 @@ contract MapleLoanFactoryTest is TestUtils { function test_createInstance_invalidCollateralAsset() external { address[2] memory assets = [address(1), address(1)]; - uint256[3] memory termDetails = [uint256(1), uint256(1), uint256(1)]; + uint256[3] memory termDetails = [uint256(12 hours), uint256(1), uint256(1)]; uint256[3] memory amounts = [uint256(1), uint256(1), uint256(0)]; uint256[4] memory rates = [uint256(0), uint256(0), uint256(0), uint256(0)]; uint256[2] memory fees = [uint256(0), uint256(0)]; @@ -104,7 +104,7 @@ contract MapleLoanFactoryTest is TestUtils { function test_createInstance_zeroLender() external { address[2] memory assets = [address(1), address(1)]; - uint256[3] memory termDetails = [uint256(1), uint256(1), uint256(1)]; + uint256[3] memory termDetails = [uint256(12 hours), uint256(1), uint256(1)]; uint256[3] memory amounts = [uint256(1), uint256(1), uint256(0)]; uint256[4] memory rates = [uint256(0), uint256(0), uint256(0), uint256(0)]; uint256[2] memory fees = [uint256(0), uint256(0)]; @@ -139,7 +139,7 @@ contract MapleLoanFactoryTest is TestUtils { function test_createInstance_invalidFactory() external { address[2] memory assets = [address(1), address(1)]; - uint256[3] memory termDetails = [uint256(1), uint256(1), uint256(1)]; + uint256[3] memory termDetails = [uint256(12 hours), uint256(1), uint256(1)]; uint256[3] memory amounts = [uint256(1), uint256(1), uint256(0)]; uint256[4] memory rates = [uint256(0), uint256(0), uint256(0), uint256(0)]; uint256[2] memory fees = [uint256(0), uint256(0)]; @@ -167,7 +167,7 @@ contract MapleLoanFactoryTest is TestUtils { function test_createInstance_invalidInstance() external { address[2] memory assets = [address(1), address(1)]; - uint256[3] memory termDetails = [uint256(1), uint256(1), uint256(1)]; + uint256[3] memory termDetails = [uint256(12 hours), uint256(1), uint256(1)]; uint256[3] memory amounts = [uint256(1), uint256(1), uint256(0)]; uint256[4] memory rates = [uint256(0), uint256(0), uint256(0), uint256(0)]; uint256[2] memory fees = [uint256(0), uint256(0)]; @@ -195,7 +195,7 @@ contract MapleLoanFactoryTest is TestUtils { function testFail_createInstance_saltAndArgumentsCollision() external { address[2] memory assets = [address(1), address(1)]; - uint256[3] memory termDetails = [uint256(1), uint256(1), uint256(1)]; + uint256[3] memory termDetails = [uint256(12 hours), uint256(1), uint256(1)]; uint256[3] memory amounts = [uint256(1), uint256(1), uint256(0)]; uint256[4] memory rates = [uint256(0), uint256(0), uint256(0), uint256(0)]; uint256[2] memory fees = [uint256(0), uint256(0)]; @@ -221,7 +221,7 @@ contract MapleLoanFactoryTest is TestUtils { function test_createInstance(bytes32 salt_) external { address[2] memory assets = [address(1), address(1)]; - uint256[3] memory termDetails = [uint256(1), uint256(1), uint256(1)]; + uint256[3] memory termDetails = [uint256(12 hours), uint256(1), uint256(1)]; uint256[3] memory amounts = [uint256(1), uint256(1), uint256(0)]; uint256[4] memory rates = [uint256(0), uint256(0), uint256(0), uint256(0)]; uint256[2] memory fees = [uint256(0), uint256(0)]; diff --git a/tests/MapleLoanFeeManager.t.sol b/tests/MapleLoanFeeManager.t.sol index b95b86b..49609a8 100644 --- a/tests/MapleLoanFeeManager.t.sol +++ b/tests/MapleLoanFeeManager.t.sol @@ -66,7 +66,7 @@ contract FeeManagerBase is TestUtils { defaultTermDetails = [uint256(10 days), uint256(365 days / 12), uint256(3)]; defaultAmounts = [uint256(0), uint256(1_000_000e18), uint256(1_000_000e18)]; defaultRates = [uint256(0.12e6), uint256(0.02e6), uint256(0), uint256(0.02e6)]; - defaultFees = [uint256(50_000e18), uint256(500e18)]; + defaultFees = [uint256(25_000e18), uint256(500e18)]; } function _createLoan( @@ -141,7 +141,7 @@ contract PayClosingFeesTests is FeeManagerBase { assertEq(interest, 20_000e18); assertEq(fees, 2_250e18); // 1m * (0.3% + 0.6%) / 12 * 3 = 1000 + 750 - fundsAsset.mint(BORROWER, 72_250e18); // 1m + 20k + 2.25k = 1_022_250 = 950k + 72.25k + fundsAsset.mint(BORROWER, 47_250e18); // 25k + 20k + 2.25k = 47.25k vm.startPrank(BORROWER); @@ -149,14 +149,14 @@ contract PayClosingFeesTests is FeeManagerBase { assertEq(fundsAsset.balanceOf(BORROWER), 1_022_250e18); // 1m + 20k + 2.25k + = 1_022_250 assertEq(fundsAsset.balanceOf(address(lender)), 0); - assertEq(fundsAsset.balanceOf(PD), 50_000e18); // Origination fees + assertEq(fundsAsset.balanceOf(PD), 25_000e18); // Origination fees assertEq(fundsAsset.balanceOf(TREASURY), 0); loan.closeLoan(1_022_250e18); assertEq(fundsAsset.balanceOf(BORROWER), 0); assertEq(fundsAsset.balanceOf(address(lender)), 1_020_000e18); // Principal + interest - assertEq(fundsAsset.balanceOf(PD), 50_000e18 + 1_500e18); + assertEq(fundsAsset.balanceOf(PD), 25_000e18 + 1_500e18); assertEq(fundsAsset.balanceOf(TREASURY), 750e18); } @@ -187,7 +187,7 @@ contract PayOriginationFeesTests is FeeManagerBase { } function test_payOriginationFees_insufficientFunds_treasury() external { - fundsAsset.mint(address(loan), 50_750e18 - 1); // 50k + (1m * 0.3% / 12 * 3) = 50_750 + fundsAsset.mint(address(loan), 25_750e18 - 1); // 50k + (1m * 0.3% / 12 * 3) = 50_750 vm.prank(address(lender)); vm.expectRevert("MLFM:POF:TREASURY_TRANSFER"); @@ -215,8 +215,8 @@ contract PayOriginationFeesTests is FeeManagerBase { vm.prank(address(lender)); loan.fundLoan(); - assertEq(fundsAsset.balanceOf(address(loan)), 949_250e18); // Principal - both origination fees - assertEq(fundsAsset.balanceOf(PD), 50_000e18); // 50k origination fee to PD + assertEq(fundsAsset.balanceOf(address(loan)), 974_250e18); // Principal - both origination fees + assertEq(fundsAsset.balanceOf(PD), 25_000e18); // 25k origination fee to PD assertEq(fundsAsset.balanceOf(TREASURY), 750e18); // (1m * 0.3% / 12 * 3) = 750 to treasury } @@ -274,16 +274,16 @@ contract PayServiceFeesTests is FeeManagerBase { fundsAsset.approve(address(loan), 10_750e18); - assertEq(fundsAsset.balanceOf(BORROWER), 950_000e18); + assertEq(fundsAsset.balanceOf(BORROWER), 975_000e18); assertEq(fundsAsset.balanceOf(address(lender)), 0); - assertEq(fundsAsset.balanceOf(PD), 50_000e18); // Origination fees + assertEq(fundsAsset.balanceOf(PD), 25_000e18); // Origination fees assertEq(fundsAsset.balanceOf(TREASURY), 0); loan.makePayment(10_750e18); - assertEq(fundsAsset.balanceOf(BORROWER), 939_250e18); // 950k - 10.75k + assertEq(fundsAsset.balanceOf(BORROWER), 964_250e18); // 950k - 10.75k assertEq(fundsAsset.balanceOf(address(lender)), 10_000e18); // Interest - assertEq(fundsAsset.balanceOf(PD), 50_000e18 + 500e18); + assertEq(fundsAsset.balanceOf(PD), 25_000e18 + 500e18); assertEq(fundsAsset.balanceOf(TREASURY), 250e18); } diff --git a/tests/MapleLoanLogic.t.sol b/tests/MapleLoanLogic.t.sol index db306b4..8ea0e97 100644 --- a/tests/MapleLoanLogic.t.sol +++ b/tests/MapleLoanLogic.t.sol @@ -46,7 +46,7 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { // Set _initialize() parameters. defaultBorrower = address(1); defaultAssets = [address(collateralAsset), address(fundsAsset)]; - defaultTermDetails = [uint256(1), uint256(30 days), uint256(12)]; + defaultTermDetails = [uint256(12 hours), uint256(30 days), uint256(12)]; defaultAmounts = [uint256(0), uint256(1000), uint256(0)]; defaultRates = [uint256(0.10e6), uint256(7), uint256(8), uint256(9)]; defaultFees = [uint256(0), uint256(0)]; @@ -1077,7 +1077,7 @@ contract MapleLoanLogic_GetClosingPaymentBreakdownTests is TestUtils { // Set _initialize() parameters. defaultBorrower = address(1); defaultAssets = [address(token1), address(token2)]; - defaultTermDetails = [uint256(1), uint256(20 days), uint256(3)]; + defaultTermDetails = [uint256(12 hours), uint256(20 days), uint256(3)]; globals.setValidBorrower(defaultBorrower, true); globals.setValidCollateralAsset(address(token1), true); @@ -1630,7 +1630,7 @@ contract MapleLoanLogic_InitializeTests is TestUtils { // Happy path dummy arguments to pass to initialize(). defaultBorrower = address(new Address()); defaultAssets = [address(token1), address(token2)]; - defaultTermDetails = [uint256(1), uint256(20 days), uint256(3)]; + defaultTermDetails = [uint256(12 hours), uint256(20 days), uint256(3)]; defaultAmounts = [uint256(5), uint256(4_000_000), uint256(0)]; defaultRates = [uint256(6), uint256(7), uint256(8), uint256(9)]; defaultFees = [uint256(0), uint256(0)]; @@ -1720,6 +1720,42 @@ contract MapleLoanLogic_InitializeTests is TestUtils { ); } + function test_initialize_invalidGracePeriodBoundary() external { + uint256[3] memory termDetails = defaultTermDetails; + + termDetails[0] = 12 hours - 1; + + // Call initialize(), expecting to revert with correct error message. + vm.expectRevert("MLI:I:INVALID_GRACE_PERIOD"); + vm.prank(address(factory)); + new ConstructableMapleLoan( + address(factory), + defaultBorrower, + address(lender), + address(feeManager), + defaultAssets, + termDetails, + defaultAmounts, + defaultRates, + defaultFees + ); + + termDetails[0] = 12 hours; + + vm.prank(address(factory)); + new ConstructableMapleLoan( + address(factory), + defaultBorrower, + address(lender), + address(feeManager), + defaultAssets, + termDetails, + defaultAmounts, + defaultRates, + defaultFees + ); + } + function test_initialize_invalidPaymentInterval() external { uint256[3] memory termDetails = defaultTermDetails; @@ -1762,6 +1798,42 @@ contract MapleLoanLogic_InitializeTests is TestUtils { ); } + function test_initialize_invalidOriginationFeeBoundary() external { + uint256[2] memory fees = defaultFees; + + fees[0] = 4_000_000 * 0.025e6 / 1e6 + 1; + + // Call initialize(), expecting to revert with correct error message. + vm.expectRevert("MLI:I:INVALID_ORIGINATION_FEE"); + vm.prank(address(factory)); + new ConstructableMapleLoan( + address(factory), + defaultBorrower, + address(lender), + address(feeManager), + defaultAssets, + defaultTermDetails, + defaultAmounts, + defaultRates, + fees + ); + + fees[0] = 4_000_000 * 0.025e6 / 1e6; // 2.5% + + vm.prank(address(factory)); + new ConstructableMapleLoan( + address(factory), + defaultBorrower, + address(lender), + address(feeManager), + defaultAssets, + defaultTermDetails, + defaultAmounts, + defaultRates, + fees + ); + } + function test_initialize_zeroBorrower() external { // Call initialize, expecting to revert with correct error message. vm.expectRevert("MLI:I:ZERO_BORROWER"); diff --git a/tests/MapleLoanScenarios.t.sol b/tests/MapleLoanScenarios.t.sol index 3b635e4..4eb186f 100644 --- a/tests/MapleLoanScenarios.t.sol +++ b/tests/MapleLoanScenarios.t.sol @@ -347,7 +347,7 @@ contract MapleLoanScenariosTests is TestUtils { token.mint(address(lender), 1_000_000); address[2] memory assets = [address(token), address(token)]; - uint256[3] memory termDetails = [uint256(0), uint256(30 days), uint256(3)]; + uint256[3] memory termDetails = [uint256(12 hours), uint256(30 days), uint256(3)]; uint256[3] memory amounts = [uint256(0), uint256(1_000_000), uint256(1_000_000)]; uint256[4] memory rates = [uint256(0.1e6), uint256(0), uint256(0), uint256(0.1e6)]; uint256[2] memory fees = [uint256(0), uint256(0)]; diff --git a/tests/Refinancer.t.sol b/tests/Refinancer.t.sol index e84266c..4a79c04 100644 --- a/tests/Refinancer.t.sol +++ b/tests/Refinancer.t.sol @@ -123,7 +123,7 @@ contract RefinancerCollateralRequiredTests is RefinancerTestBase { principalRequested_ = constrictToRange(principalRequested_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT); collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); - gracePeriod_ = constrictToRange(gracePeriod_, 100, MAX_TIME); + gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); interestRate_ = constrictToRange(interestRate_, 0, MAX_RATE); // Giving enough room to increase the interest Rate lateFeeRate_ = constrictToRange(lateFeeRate_, 0, MAX_RATE); paymentInterval_ = constrictToRange(paymentInterval_, 1, MAX_TIME / 2); @@ -194,7 +194,7 @@ contract RefinancerDeadlineTests is RefinancerTestBase { paymentInterval_ = constrictToRange(paymentInterval_, 1 days, MAX_TIME / 2); newPaymentInterval_ = constrictToRange(newPaymentInterval_, 1 days, MAX_TIME); - setUpOngoingLoan(1e18, 0, 0, 1, 1, paymentInterval_, 2); + setUpOngoingLoan(1e18, 0, 0, 12 hours, 1, paymentInterval_, 2); assertEq(loan.paymentInterval(), paymentInterval_); @@ -230,7 +230,7 @@ contract RefinancerDeadlineTests is RefinancerTestBase { paymentInterval_ = constrictToRange(paymentInterval_, 1 days, MAX_TIME / 2); newPaymentInterval_ = constrictToRange(newPaymentInterval_, 1 days, MAX_TIME); - setUpOngoingLoan(1e18, 0, 0, 1, 1, paymentInterval_, 2); + setUpOngoingLoan(1e18, 0, 0, 12 hours, 1, paymentInterval_, 2); deadline_ = constrictToRange(deadline_, block.timestamp, block.timestamp + MAX_TIME); @@ -270,7 +270,7 @@ contract RefinancerEndingPrincipalTests is RefinancerTestBase { { principalRequested_ = constrictToRange(principalRequested_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT); // Boundary increase so principal portion on 'paymentBreakdown' is always greater than 0 collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); - gracePeriod_ = constrictToRange(gracePeriod_, 0, MAX_TIME); + gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); interestRate_ = constrictToRange(interestRate_, 0, MAX_RATE); paymentInterval_ = constrictToRange(paymentInterval_, 1, MAX_TIME); paymentsRemaining_ = constrictToRange(paymentsRemaining_, 3, MAX_PAYMENTS); @@ -325,7 +325,7 @@ contract RefinancerEndingPrincipalTests is RefinancerTestBase { principalRequested_ = constrictToRange(principalRequested_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT); // Boundary increase so principal portion on 'paymentBreakdown' is always greater than 0 collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_ - 1); - gracePeriod_ = constrictToRange(gracePeriod_, 0, MAX_TIME); + gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); interestRate_ = constrictToRange(interestRate_, 0, MAX_RATE); paymentInterval_ = constrictToRange(paymentInterval_, 1, MAX_TIME); paymentsRemaining_ = constrictToRange(paymentsRemaining_, 3, MAX_PAYMENTS); @@ -377,13 +377,13 @@ contract RefinancerEndingPrincipalTests is RefinancerTestBase { ) external { - principalRequested_ = constrictToRange(principalRequested_, 1, MAX_TOKEN_AMOUNT); - collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); - endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); - gracePeriod_ = constrictToRange(gracePeriod_, 0, MAX_TIME); - interestRate_ = constrictToRange(interestRate_, 0, MAX_RATE); - paymentInterval_ = constrictToRange(paymentInterval_, 1, MAX_TIME); - paymentsRemaining_ = constrictToRange(paymentsRemaining_, 3, MAX_PAYMENTS); + principalRequested_ = constrictToRange(principalRequested_, 1, MAX_TOKEN_AMOUNT); + collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); + endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); + gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); + interestRate_ = constrictToRange(interestRate_, 0, MAX_RATE); + paymentInterval_ = constrictToRange(paymentInterval_, 1, MAX_TIME); + paymentsRemaining_ = constrictToRange(paymentsRemaining_, 3, MAX_PAYMENTS); setUpOngoingLoan( principalRequested_, @@ -430,7 +430,7 @@ contract RefinancerFeeTests is RefinancerTestBase { principalRequested_ = constrictToRange(principalRequested_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT); collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); - gracePeriod_ = constrictToRange(gracePeriod_, 100, MAX_TIME); + gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); interestRate_ = constrictToRange(interestRate_, 0, MAX_RATE); lateFeeRate_ = constrictToRange(lateFeeRate_, 0, MAX_RATE); paymentInterval_ = constrictToRange(paymentInterval_, 1, MAX_TIME / 2); @@ -479,7 +479,7 @@ contract RefinancerFeeTests is RefinancerTestBase { principalRequested_ = constrictToRange(principalRequested_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT); collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); - gracePeriod_ = constrictToRange(gracePeriod_, 100, MAX_TIME); + gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); interestRate_ = constrictToRange(interestRate_, 0, MAX_RATE); // Giving enough room to increase the interest Rate lateFeeRate_ = constrictToRange(lateFeeRate_, 0, MAX_RATE); paymentInterval_ = constrictToRange(paymentInterval_, 1, MAX_TIME / 2); @@ -528,7 +528,7 @@ contract RefinancerFeeTests is RefinancerTestBase { principalRequested_ = constrictToRange(principalRequested_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT); collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); - gracePeriod_ = constrictToRange(gracePeriod_, 100, MAX_TIME); + gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); interestRate_ = constrictToRange(interestRate_, 0, MAX_RATE); // Giving enough room to increase the interest Rate lateFeeRate_ = constrictToRange(lateFeeRate_, 0, MAX_RATE); paymentInterval_ = constrictToRange(paymentInterval_, 1, MAX_TIME / 2); @@ -577,13 +577,13 @@ contract RefinancerGracePeriodTests is RefinancerTestBase { ) external { - principalRequested_ = constrictToRange(principalRequested_, 1, MAX_TOKEN_AMOUNT); - collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); - endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); - gracePeriod_ = constrictToRange(gracePeriod_, 100, MAX_TIME); - interestRate_ = constrictToRange(interestRate_, 0, MAX_RATE); - paymentInterval_ = constrictToRange(paymentInterval_, 1, MAX_TIME); - paymentsRemaining_ = constrictToRange(paymentsRemaining_, 3, MAX_PAYMENTS); + principalRequested_ = constrictToRange(principalRequested_, 1, MAX_TOKEN_AMOUNT); + collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); + endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); + gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); + interestRate_ = constrictToRange(interestRate_, 0, MAX_RATE); + paymentInterval_ = constrictToRange(paymentInterval_, 1, MAX_TIME); + paymentsRemaining_ = constrictToRange(paymentsRemaining_, 3, MAX_PAYMENTS); setUpOngoingLoan( principalRequested_, @@ -632,7 +632,7 @@ contract RefinancerInterestRateTests is RefinancerTestBase { principalRequested_ = constrictToRange(principalRequested_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT); collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); - gracePeriod_ = constrictToRange(gracePeriod_, 100, MAX_TIME); + gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); interestRate_ = constrictToRange(interestRate_, 0, MAX_RATE / 2); // Giving enough room to increase the interest Rate paymentInterval_ = constrictToRange(paymentInterval_, 1, MAX_TIME); paymentsRemaining_ = constrictToRange(paymentsRemaining_, 3, MAX_PAYMENTS); @@ -816,7 +816,7 @@ contract RefinancerInterestTests is TestUtils { contract RefinancerMiscellaneousTests is RefinancerTestBase { function test_refinance_invalidRefinancer() external { - setUpOngoingLoan(1, 1, 1, 1, 1, 1, 1); + setUpOngoingLoan(1, 1, 1, 12 hours, 1, 1, 1); bytes[] memory data = new bytes[](1); data[0] = abi.encodeWithSignature("setEndingPrincipal(uint256)", 0); @@ -849,7 +849,7 @@ contract RefinancerMultipleParameterTests is RefinancerTestBase { principalRequested_ = constrictToRange(principalRequested_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT - MIN_TOKEN_AMOUNT); collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, principalRequested_ / 2, principalRequested_); - gracePeriod_ = constrictToRange(gracePeriod_, 100, MAX_TIME); + gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); interestRate_ = constrictToRange(interestRate_, 10_000, MAX_RATE / 2); // Giving enough room to increase the interest Rate paymentInterval_ = constrictToRange(paymentInterval_, 30 days, MAX_TIME / 2); paymentsRemaining_ = constrictToRange(paymentsRemaining_, 3, MAX_PAYMENTS); @@ -924,7 +924,7 @@ contract RefinancerMultipleParameterTests is RefinancerTestBase { contract RefinancerPaymentIntervalTests is RefinancerTestBase { function test_refinance_paymentInterval_zeroAmount() external { - setUpOngoingLoan(MIN_TOKEN_AMOUNT, 0, MIN_TOKEN_AMOUNT, 0, 0.1e6, 30 days, 6); + setUpOngoingLoan(MIN_TOKEN_AMOUNT, 0, MIN_TOKEN_AMOUNT, 12 hours, 0.1e6, 30 days, 6); uint256 deadline = block.timestamp + 10 days; bytes[] memory data = _encodeWithSignatureAndUint("setPaymentInterval(uint256)", 0); @@ -961,7 +961,7 @@ contract RefinancerPaymentIntervalTests is RefinancerTestBase { principalRequested_ = constrictToRange(principalRequested_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT); collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); - gracePeriod_ = constrictToRange(gracePeriod_, 100, MAX_TIME); + gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); interestRate_ = constrictToRange(interestRate_, 0, MAX_RATE); paymentInterval_ = constrictToRange(paymentInterval_, 1 days, MAX_TIME / 2); paymentsRemaining_ = constrictToRange(paymentsRemaining_, 3, MAX_PAYMENTS); @@ -998,7 +998,7 @@ contract RefinancerPaymentIntervalTests is RefinancerTestBase { contract RefinancerPaymentsRemainingTests is RefinancerTestBase { function test_refinance_paymentRemaining_zeroAmount() external { - setUpOngoingLoan(MIN_TOKEN_AMOUNT, 0, MIN_TOKEN_AMOUNT, 0, 0.1e6, 30 days, 6); + setUpOngoingLoan(MIN_TOKEN_AMOUNT, 0, MIN_TOKEN_AMOUNT, 12 hours, 0.1e6, 30 days, 6); uint256 deadline = block.timestamp + 10 days; bytes[] memory data = _encodeWithSignatureAndUint("setPaymentsRemaining(uint256)", 0); @@ -1036,7 +1036,7 @@ contract RefinancerPaymentsRemainingTests is RefinancerTestBase { principalRequested_ = constrictToRange(principalRequested_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT); collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); - gracePeriod_ = constrictToRange(gracePeriod_, 100, MAX_TIME); + gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); interestRate_ = constrictToRange(interestRate_, 0, MAX_RATE); lateFeeRate_ = constrictToRange(lateFeeRate_, 0, MAX_RATE); paymentInterval_ = constrictToRange(paymentInterval_, 1 days, MAX_TIME / 2); @@ -1094,7 +1094,7 @@ contract RefinancerPrincipalRequestedTests is RefinancerTestBase { principalRequested_ = constrictToRange(principalRequested_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT - MIN_TOKEN_AMOUNT); collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); - gracePeriod_ = constrictToRange(gracePeriod_, 100, MAX_TIME); + gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); interestRate_ = constrictToRange(interestRate_, 0, MAX_RATE); // Giving enough room to increase the interest Rate lateFeeRate_ = constrictToRange(lateFeeRate_, 0, MAX_RATE); paymentInterval_ = constrictToRange(paymentInterval_, 1, MAX_TIME / 2); @@ -1163,7 +1163,7 @@ contract RefinancerPrincipalRequestedTests is RefinancerTestBase { principalRequested_ = constrictToRange(principalRequested_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT - MIN_TOKEN_AMOUNT); collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); - gracePeriod_ = constrictToRange(gracePeriod_, 100, MAX_TIME); + gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); interestRate_ = constrictToRange(interestRate_, 0, MAX_RATE); // Giving enough room to increase the interest Rate lateFeeRate_ = constrictToRange(lateFeeRate_, 0, MAX_RATE); paymentInterval_ = constrictToRange(paymentInterval_, 1, MAX_TIME / 2); @@ -1521,7 +1521,7 @@ contract RefinancingFeesTerms is TestUtils { principalRequested_ = constrictToRange(principalRequested_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT); collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principalRequested_); - gracePeriod_ = constrictToRange(gracePeriod_, 100, MAX_TIME); + gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); interestRate_ = constrictToRange(interestRate_, 0, MAX_RATE); lateFeeRate_ = constrictToRange(lateFeeRate_, 0, MAX_RATE); paymentInterval_ = constrictToRange(paymentInterval_, 1 days, MAX_TIME / 2); From 0071d39d4a6d4842a7aa45f0711bca5490f809e0 Mon Sep 17 00:00:00 2001 From: Joao Gabriel Carvalho Date: Sun, 23 Apr 2023 18:21:53 -0300 Subject: [PATCH 21/47] feat: Add funds asset check to initializer [3S #20] (SC-12059) (#287) --- contracts/MapleLoanInitializer.sol | 1 + contracts/interfaces/Interfaces.sol | 2 ++ tests/InitializerAndMigrator.t.sol | 2 ++ tests/MapleLoan.t.sol | 2 ++ tests/MapleLoanFactory.t.sol | 30 +++++++++++++++++++++++++++++ tests/MapleLoanFeeManager.t.sol | 2 ++ tests/MapleLoanLogic.t.sol | 8 ++++++++ tests/MapleLoanScenarios.t.sol | 2 ++ tests/Payments.t.sol | 1 + tests/Refinancer.t.sol | 5 +++++ tests/mocks/Mocks.sol | 5 +++++ 11 files changed, 60 insertions(+) diff --git a/contracts/MapleLoanInitializer.sol b/contracts/MapleLoanInitializer.sol index 8337886..c775f67 100644 --- a/contracts/MapleLoanInitializer.sol +++ b/contracts/MapleLoanInitializer.sol @@ -126,6 +126,7 @@ contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { address loanManagerFactory_ = ILenderLike(lender_).factory(); + require(ILenderLike(lender_).fundsAsset() == assets_[1], "MLI:I:DIFFERENT_FUNDS_ASSET"); require(globals_.isInstanceOf("FT_LOAN_MANAGER_FACTORY", loanManagerFactory_), "MLI:I:INVALID_FACTORY"); require(IMapleProxyFactoryLike(loanManagerFactory_).isInstance(lender_), "MLI:I:INVALID_INSTANCE"); diff --git a/contracts/interfaces/Interfaces.sol b/contracts/interfaces/Interfaces.sol index cc2b1ff..fa1c334 100644 --- a/contracts/interfaces/Interfaces.sol +++ b/contracts/interfaces/Interfaces.sol @@ -31,6 +31,8 @@ interface ILenderLike { function factory() external view returns (address factory_); + function fundsAsset() external view returns (address fundsAsset_); + } interface ILoanLike { diff --git a/tests/InitializerAndMigrator.t.sol b/tests/InitializerAndMigrator.t.sol index 56516b3..44d6257 100644 --- a/tests/InitializerAndMigrator.t.sol +++ b/tests/InitializerAndMigrator.t.sol @@ -40,6 +40,8 @@ contract MapleLoanInitializerAndMigratorTests is TestUtils { factory = new MapleLoanFactory(address(globals)); loanManagerFactory = MockLoanManagerFactory(lender.factory()); + lender.__setFundsAsset(address(asset)); + globals.setValidBorrower(address(1), true); globals.setValidCollateralAsset(address(asset), true); globals.setValidPoolAsset(address(asset), true); diff --git a/tests/MapleLoan.t.sol b/tests/MapleLoan.t.sol index cd0853d..0045706 100644 --- a/tests/MapleLoan.t.sol +++ b/tests/MapleLoan.t.sol @@ -1360,6 +1360,8 @@ contract MapleLoanRoleTests is TestUtils { factory = new MockFactory(address(globals)); + MockLoanManager(lender).__setFundsAsset(address(token)); + address[2] memory assets = [address(token), address(token)]; uint256[3] memory termDetails = [uint256(10 days), uint256(365 days / 6), uint256(6)]; uint256[3] memory amounts = [uint256(300_000), uint256(1_000_000), uint256(0)]; diff --git a/tests/MapleLoanFactory.t.sol b/tests/MapleLoanFactory.t.sol index 2706291..67e1dee 100644 --- a/tests/MapleLoanFactory.t.sol +++ b/tests/MapleLoanFactory.t.sol @@ -34,6 +34,8 @@ contract MapleLoanFactoryTest is TestUtils { factory = new MapleLoanFactory(address(globals)); + lender.__setFundsAsset(address(1)); + globals.setValidBorrower(address(1), true); globals.setValidCollateralAsset(address(1), true); globals.setValidPoolAsset(address(1), true); @@ -137,6 +139,34 @@ contract MapleLoanFactoryTest is TestUtils { factory.createInstance(arguments, "SALT"); } + function test_createInstance_differentFundsAsset() external { + address[2] memory assets = [address(1), address(1)]; + uint256[3] memory termDetails = [uint256(12 hours), uint256(1), uint256(1)]; + uint256[3] memory amounts = [uint256(1), uint256(1), uint256(0)]; + uint256[4] memory rates = [uint256(0), uint256(0), uint256(0), uint256(0)]; + uint256[2] memory fees = [uint256(0), uint256(0)]; + + bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( + address(1), + address(lender), + address(feeManager), + assets, + termDetails, + amounts, + rates, + fees + ); + + lender.__setFundsAsset(address(2)); + + vm.expectRevert("MPF:CI:FAILED"); + factory.createInstance(arguments, "SALT"); + + lender.__setFundsAsset(address(1)); + + factory.createInstance(arguments, "SALT"); + } + function test_createInstance_invalidFactory() external { address[2] memory assets = [address(1), address(1)]; uint256[3] memory termDetails = [uint256(12 hours), uint256(1), uint256(1)]; diff --git a/tests/MapleLoanFeeManager.t.sol b/tests/MapleLoanFeeManager.t.sol index 49609a8..562d82e 100644 --- a/tests/MapleLoanFeeManager.t.sol +++ b/tests/MapleLoanFeeManager.t.sol @@ -46,6 +46,8 @@ contract FeeManagerBase is TestUtils { globals = new MockGlobals(GOVERNOR); lender = new MockLoanManager(); + lender.__setFundsAsset(address(fundsAsset)); + factory = new MapleLoanFactory(address(globals)); feeManager = new MapleLoanFeeManager(address(globals)); diff --git a/tests/MapleLoanLogic.t.sol b/tests/MapleLoanLogic.t.sol index 8ea0e97..f88c652 100644 --- a/tests/MapleLoanLogic.t.sol +++ b/tests/MapleLoanLogic.t.sol @@ -43,6 +43,8 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { factory = new MockFactory(address(globals)); + lender.__setFundsAsset(address(fundsAsset)); + // Set _initialize() parameters. defaultBorrower = address(1); defaultAssets = [address(collateralAsset), address(fundsAsset)]; @@ -260,6 +262,8 @@ contract MapleLoanLogic_CloseLoanTests is TestUtils { factory = new MockFactory(address(globals)); + lender.__setFundsAsset(address(fundsAsset)); + loan.__setBorrower(borrower); loan.__setFactory(address(factory)); loan.__setFeeManager(address(feeManager)); @@ -1074,6 +1078,8 @@ contract MapleLoanLogic_GetClosingPaymentBreakdownTests is TestUtils { factory = new MockFactory(address(globals)); + lender.__setFundsAsset(address(token2)); + // Set _initialize() parameters. defaultBorrower = address(1); defaultAssets = [address(token1), address(token2)]; @@ -1627,6 +1633,8 @@ contract MapleLoanLogic_InitializeTests is TestUtils { factory = new MockFactory(address(globals)); + lender.__setFundsAsset(address(token2)); + // Happy path dummy arguments to pass to initialize(). defaultBorrower = address(new Address()); defaultAssets = [address(token1), address(token2)]; diff --git a/tests/MapleLoanScenarios.t.sol b/tests/MapleLoanScenarios.t.sol index 4eb186f..827983f 100644 --- a/tests/MapleLoanScenarios.t.sol +++ b/tests/MapleLoanScenarios.t.sol @@ -28,6 +28,8 @@ contract MapleLoanScenariosTests is TestUtils { factory = new MockFactory(address(globals)); + lender.__setFundsAsset(address(token)); + globals.setValidBorrower(borrower, true); globals.setValidCollateralAsset(address(token), true); globals.setValidPoolAsset(address(token), true); diff --git a/tests/Payments.t.sol b/tests/Payments.t.sol index b7d41a4..de221c9 100644 --- a/tests/Payments.t.sol +++ b/tests/Payments.t.sol @@ -44,6 +44,7 @@ contract MapleLoanPaymentsTestBase is TestUtils { feeManager = new MapleLoanFeeManager(address(globals)); lender.__setPoolManager(address(poolManager)); + lender.__setFundsAsset(address(fundsAsset)); factory = new MapleProxyFactory(address(globals)); diff --git a/tests/Refinancer.t.sol b/tests/Refinancer.t.sol index 4a79c04..32b9726 100644 --- a/tests/Refinancer.t.sol +++ b/tests/Refinancer.t.sol @@ -47,6 +47,8 @@ contract RefinancerTestBase is TestUtils { factory = new MockFactory(address(globals)); + lender.__setFundsAsset(address(token)); + globals.setValidBorrower(borrower, true); globals.setValidPoolAsset(address(token), true); @@ -697,6 +699,8 @@ contract RefinancerInterestTests is TestUtils { factory = new MockFactory(address(globals)); + lender.__setFundsAsset(address(token)); + globals.setValidBorrower(borrower, true); globals.setValidPoolAsset(address(token), true); @@ -1247,6 +1251,7 @@ contract RefinancingFeesTerms is TestUtils { token = new MockERC20("Test", "TST", 0); lender.__setPoolManager(address(poolManager)); // Set so correct PD address is used. + lender.__setFundsAsset(address(token)); globals.setValidBorrower(borrower, true); globals.setValidPoolAsset(address(token), true); diff --git a/tests/mocks/Mocks.sol b/tests/mocks/Mocks.sol index fb9862d..ab81128 100644 --- a/tests/mocks/Mocks.sol +++ b/tests/mocks/Mocks.sol @@ -209,6 +209,7 @@ contract MockFeeManager { contract MockLoanManager { address public factory; + address public fundsAsset; address public poolManager; constructor() { @@ -222,6 +223,10 @@ contract MockLoanManager { poolManager = poolManager_; } + function __setFundsAsset(address asset_) external { + fundsAsset = asset_; + } + } contract MockPoolManager { From 7c764fff3e7cb013aea1fac5af16ecdffd47a04c Mon Sep 17 00:00:00 2001 From: Michael De Luca <35537333+deluca-mike@users.noreply.github.com> Date: Sun, 23 Apr 2023 20:50:23 -0400 Subject: [PATCH 22/47] refactor: Homogenize ACL [3S #8] (SC-12035) (#283) --- contracts/MapleLoan.sol | 62 +++++++++++++++++++++----------------- tests/MapleLoan.t.sol | 26 ++++++++-------- tests/MapleLoanLogic.t.sol | 2 +- 3 files changed, 48 insertions(+), 42 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index 021466c..ed96539 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -45,8 +45,18 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { require(_drawableFunds >= drawableFundsBeforePayment, "ML:CANNOT_USE_DRAWABLE"); } + modifier onlyBorrower() { + _revertIfNotBorrower(); + _; + } + + modifier onlyLender() { + _revertIfNotLender(); + _; + } + modifier whenNotPaused() { - require(!IMapleGlobalsLike(globals()).isFunctionPaused(msg.sig), "L:PAUSED"); + _revertIfPaused(); _; } @@ -123,9 +133,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { emit FundsClaimed(principalAndInterest_, _lender); } - function drawdownFunds(uint256 amount_, address destination_) external override whenNotPaused returns (uint256 collateralPosted_) { - require(msg.sender == _borrower, "ML:DF:NOT_BORROWER"); - + function drawdownFunds(uint256 amount_, address destination_) external override whenNotPaused onlyBorrower returns (uint256 collateralPosted_) { emit FundsDrawnDown(amount_, destination_); // Post additional collateral required to facilitate this drawdown, if needed. @@ -208,9 +216,8 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } function proposeNewTerms(address refinancer_, uint256 deadline_, bytes[] calldata calls_) - external override whenNotPaused returns (bytes32 refinanceCommitment_) + external override whenNotPaused onlyBorrower returns (bytes32 refinanceCommitment_) { - require(msg.sender == _borrower, "ML:PNT:NOT_BORROWER"); require(deadline_ >= block.timestamp, "ML:PNT:INVALID_DEADLINE"); require(IMapleGlobalsLike(globals()).isInstanceOf("FT_REFINANCER", refinancer_), "ML:PNT:INVALID_REFINANCER"); require(calls_.length > uint256(0), "ML:PNT:EMPTY_CALLS"); @@ -223,9 +230,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { ); } - function removeCollateral(uint256 amount_, address destination_) external override whenNotPaused { - require(msg.sender == _borrower, "ML:RC:NOT_BORROWER"); - + function removeCollateral(uint256 amount_, address destination_) external override whenNotPaused onlyBorrower { emit CollateralRemoved(amount_, destination_); _collateral -= amount_; @@ -247,8 +252,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { emit FundsReturned(fundsReturned_); } - function setPendingBorrower(address pendingBorrower_) external override whenNotPaused { - require(msg.sender == _borrower, "ML:SPB:NOT_BORROWER"); + function setPendingBorrower(address pendingBorrower_) external override whenNotPaused onlyBorrower { require(IMapleGlobalsLike(globals()).isBorrower(pendingBorrower_), "ML:SPB:INVALID_BORROWER"); emit PendingBorrowerSet(_pendingBorrower = pendingBorrower_); @@ -267,10 +271,8 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } function acceptNewTerms(address refinancer_, uint256 deadline_, bytes[] calldata calls_) - external override whenNotPaused returns (bytes32 refinanceCommitment_) + external override whenNotPaused onlyLender returns (bytes32 refinanceCommitment_) { - require(msg.sender == _lender, "ML:ANT:NOT_LENDER"); - // NOTE: A zero refinancer address and/or empty calls array will never (probabilistically) match a refinance commitment in storage. require( _refinanceCommitment == (refinanceCommitment_ = _getRefinanceCommitment(refinancer_, deadline_, calls_)), @@ -332,11 +334,9 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { require(getUnaccountedAmount(fundsAsset_) == uint256(0), "ML:ANT:UNEXPECTED_FUNDS"); } - function fundLoan() external override whenNotPaused returns (uint256 fundsLent_) { + function fundLoan() external override whenNotPaused onlyLender returns (uint256 fundsLent_) { address lender_ = _lender; - require(msg.sender == lender_, "ML:FL:NOT_LENDER"); - // Can only fund loan if there are payments remaining (defined in the initialization) and no payment is due (as set by a funding). require((_nextPaymentDueDate == uint256(0)) && (_paymentsRemaining != uint256(0)), "ML:FL:LOAN_ACTIVE"); @@ -362,10 +362,9 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { ); } - function removeLoanImpairment() external override whenNotPaused { + function removeLoanImpairment() external override whenNotPaused onlyLender { uint256 originalNextPaymentDueDate_ = _originalNextPaymentDueDate; - require(msg.sender == _lender, "ML:RLI:NOT_LENDER"); require(originalNextPaymentDueDate_ != 0, "ML:RLI:NOT_IMPAIRED"); require(block.timestamp <= originalNextPaymentDueDate_, "ML:RLI:PAST_DATE"); @@ -376,9 +375,8 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } function repossess(address destination_) - external override whenNotPaused returns (uint256 collateralRepossessed_, uint256 fundsRepossessed_) { - require(msg.sender == _lender, "ML:R:NOT_LENDER"); - + external override whenNotPaused onlyLender returns (uint256 collateralRepossessed_, uint256 fundsRepossessed_) + { uint256 nextPaymentDueDate_ = _nextPaymentDueDate; require( @@ -413,17 +411,13 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { emit Repossessed(collateralRepossessed_, fundsRepossessed_, destination_); } - function setPendingLender(address pendingLender_) external override whenNotPaused { - require(msg.sender == _lender, "ML:SPL:NOT_LENDER"); - + function setPendingLender(address pendingLender_) external override whenNotPaused onlyLender { emit PendingLenderSet(_pendingLender = pendingLender_); } - function impairLoan() external override whenNotPaused { + function impairLoan() external override whenNotPaused onlyLender { uint256 originalNextPaymentDueDate_ = _nextPaymentDueDate; - require(msg.sender == _lender, "ML:IL:NOT_LENDER"); - // If the loan is late, do not change the payment due date. uint256 newPaymentDueDate_ = block.timestamp > originalNextPaymentDueDate_ ? originalNextPaymentDueDate_ : block.timestamp; @@ -908,6 +902,18 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { minimum_ = a_ < b_ ? a_ : b_; } + function _revertIfNotBorrower() internal view { + require(msg.sender == _borrower, "ML:NOT_BORROWER"); + } + + function _revertIfNotLender() internal view { + require(msg.sender == _lender, "ML:NOT_LENDER"); + } + + function _revertIfPaused() internal view { + require(!IMapleGlobalsLike(globals()).isFunctionPaused(msg.sig), "L:PAUSED"); + } + /** * @dev Returns exponentiation of a scaled base value. * diff --git a/tests/MapleLoan.t.sol b/tests/MapleLoan.t.sol index 0045706..b5f4306 100644 --- a/tests/MapleLoan.t.sol +++ b/tests/MapleLoan.t.sol @@ -247,7 +247,7 @@ contract MapleLoanTests is TestUtils { loan.__setFundsAsset(address(fundsAsset)); loan.__setPrincipalRequested(1_000_000); // Needed for the getAdditionalCollateralRequiredFor - vm.expectRevert("ML:DF:NOT_BORROWER"); + vm.expectRevert("ML:NOT_BORROWER"); loan.drawdownFunds(1, borrower); vm.prank(borrower); @@ -272,7 +272,7 @@ contract MapleLoanTests is TestUtils { bytes[] memory calls = new bytes[](1); calls[0] = new bytes(0); - vm.expectRevert("ML:PNT:NOT_BORROWER"); + vm.expectRevert("ML:NOT_BORROWER"); loan.proposeNewTerms(mockRefinancer, deadline, calls); vm.prank(borrower); @@ -324,7 +324,7 @@ contract MapleLoanTests is TestUtils { collateralAsset.mint(address(loan), 1); - vm.expectRevert("ML:RC:NOT_BORROWER"); + vm.expectRevert("ML:NOT_BORROWER"); loan.removeCollateral(1, borrower); vm.prank(borrower); @@ -334,7 +334,7 @@ contract MapleLoanTests is TestUtils { function test_setPendingBorrower_acl() external { globals.setValidBorrower(address(1), true); - vm.expectRevert("ML:SPB:NOT_BORROWER"); + vm.expectRevert("ML:NOT_BORROWER"); loan.setPendingBorrower(address(1)); vm.prank(borrower); @@ -368,7 +368,7 @@ contract MapleLoanTests is TestUtils { loan.__setRefinanceCommitment(keccak256(abi.encode(mockRefinancer, deadline, calls))); - vm.expectRevert("ML:ANT:NOT_LENDER"); + vm.expectRevert("ML:NOT_LENDER"); loan.acceptNewTerms(mockRefinancer, deadline, calls); vm.prank(lender); @@ -378,7 +378,7 @@ contract MapleLoanTests is TestUtils { function test_removeLoanImpairment_acl() external { loan.__setOriginalNextPaymentDueDate(block.timestamp + 300); - vm.expectRevert("ML:RLI:NOT_LENDER"); + vm.expectRevert("ML:NOT_LENDER"); loan.removeLoanImpairment(); vm.prank(lender); @@ -396,7 +396,7 @@ contract MapleLoanTests is TestUtils { vm.warp(loan.nextPaymentDueDate() + loan.gracePeriod() + 1); - vm.expectRevert("ML:R:NOT_LENDER"); + vm.expectRevert("ML:NOT_LENDER"); loan.repossess(lender); vm.prank(lender); @@ -412,7 +412,7 @@ contract MapleLoanTests is TestUtils { loan.__setNextPaymentDueDate(originalNextPaymentDate); - vm.expectRevert("ML:IL:NOT_LENDER"); + vm.expectRevert("ML:NOT_LENDER"); loan.impairLoan(); vm.prank(lender); @@ -420,7 +420,7 @@ contract MapleLoanTests is TestUtils { } function test_setPendingLender_acl() external { - vm.expectRevert("ML:SPL:NOT_LENDER"); + vm.expectRevert("ML:NOT_LENDER"); loan.setPendingLender(governor); vm.prank(lender); @@ -1398,7 +1398,7 @@ contract MapleLoanRoleTests is TestUtils { // Only borrower can call setPendingBorrower vm.prank(newBorrower); - vm.expectRevert("ML:SPB:NOT_BORROWER"); + vm.expectRevert("ML:NOT_BORROWER"); loan.setPendingBorrower(newBorrower); vm.prank(borrower); @@ -1408,7 +1408,7 @@ contract MapleLoanRoleTests is TestUtils { // Pending borrower can't call setPendingBorrower vm.prank(newBorrower); - vm.expectRevert("ML:SPB:NOT_BORROWER"); + vm.expectRevert("ML:NOT_BORROWER"); loan.setPendingBorrower(address(1)); vm.prank(borrower); @@ -1450,7 +1450,7 @@ contract MapleLoanRoleTests is TestUtils { // Only lender can call setPendingLender vm.prank(newLender); - vm.expectRevert("ML:SPL:NOT_LENDER"); + vm.expectRevert("ML:NOT_LENDER"); loan.setPendingLender(newLender); vm.prank(lender); @@ -1460,7 +1460,7 @@ contract MapleLoanRoleTests is TestUtils { // Pending lender can't call setPendingLender vm.prank(newLender); - vm.expectRevert("ML:SPL:NOT_LENDER"); + vm.expectRevert("ML:NOT_LENDER"); loan.setPendingLender(address(1)); vm.prank(lender); diff --git a/tests/MapleLoanLogic.t.sol b/tests/MapleLoanLogic.t.sol index f88c652..f489bfc 100644 --- a/tests/MapleLoanLogic.t.sol +++ b/tests/MapleLoanLogic.t.sol @@ -870,7 +870,7 @@ contract MapleLoanLogic_FundLoanTests is TestUtils { } function test_fundLoan_notLender() external { - vm.expectRevert("ML:FL:NOT_LENDER"); + vm.expectRevert("ML:NOT_LENDER"); loan.fundLoan(); } From ddec844d765f7bd8f9c66088c257c48f2d784b55 Mon Sep 17 00:00:00 2001 From: Michael De Luca <35537333+deluca-mike@users.noreply.github.com> Date: Sun, 23 Apr 2023 22:12:28 -0400 Subject: [PATCH 23/47] refactor: Reorder and interface cleanup (#288) * refactor: reorder * refactor: interfaces cleanup * fix: update formatting --------- Co-authored-by: lucas-manuel --- contracts/MapleLoan.sol | 199 +++++++++--------- contracts/MapleLoanFeeManager.sol | 32 +-- contracts/MapleLoanInitializer.sol | 6 +- contracts/interfaces/IMapleLoan.sol | 30 +-- contracts/interfaces/IMapleLoanFeeManager.sol | 40 ++-- contracts/interfaces/Interfaces.sol | 10 +- 6 files changed, 152 insertions(+), 165 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index ed96539..8cc410a 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -9,7 +9,7 @@ import { MapleProxiedInternals } from "../modules/maple-proxy-factory/contracts/ import { IMapleLoan } from "./interfaces/IMapleLoan.sol"; import { IMapleLoanFeeManager } from "./interfaces/IMapleLoanFeeManager.sol"; -import { IMapleGlobalsLike, ILenderLike, IMapleProxyFactoryLike } from "./interfaces/Interfaces.sol"; +import { IGlobalsLike, ILenderLike, IMapleProxyFactoryLike } from "./interfaces/Interfaces.sol"; import { MapleLoanStorage } from "./MapleLoanStorage.sol"; @@ -22,7 +22,6 @@ import { MapleLoanStorage } from "./MapleLoanStorage.sol"; ██║ ╚═╝ ██║██║ ██║██║ ███████╗███████╗ ███████╗╚██████╔╝██║ ██║██║ ╚████║ ╚████╔╝ ███████║ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═══╝ ╚══════╝ - */ /// @title MapleLoan implements a primitive loan with additional functionality, and is intended to be proxied. @@ -75,7 +74,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } function upgrade(uint256 toVersion_, bytes calldata arguments_) external override whenNotPaused { - require(msg.sender == _borrower || msg.sender == IMapleGlobalsLike(globals()).securityAdmin(), "ML:U:NO_AUTH"); + require(msg.sender == _borrower || msg.sender == IGlobalsLike(globals()).securityAdmin(), "ML:U:NO_AUTH"); emit Upgraded(toVersion_, arguments_); @@ -218,9 +217,9 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { function proposeNewTerms(address refinancer_, uint256 deadline_, bytes[] calldata calls_) external override whenNotPaused onlyBorrower returns (bytes32 refinanceCommitment_) { - require(deadline_ >= block.timestamp, "ML:PNT:INVALID_DEADLINE"); - require(IMapleGlobalsLike(globals()).isInstanceOf("FT_REFINANCER", refinancer_), "ML:PNT:INVALID_REFINANCER"); - require(calls_.length > uint256(0), "ML:PNT:EMPTY_CALLS"); + require(deadline_ >= block.timestamp, "ML:PNT:INVALID_DEADLINE"); + require(IGlobalsLike(globals()).isInstanceOf("FT_REFINANCER", refinancer_), "ML:PNT:INVALID_REFINANCER"); + require(calls_.length > uint256(0), "ML:PNT:EMPTY_CALLS"); emit NewTermsProposed( _refinanceCommitment = refinanceCommitment_ = _getRefinanceCommitment(refinancer_, deadline_, calls_), @@ -253,7 +252,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } function setPendingBorrower(address pendingBorrower_) external override whenNotPaused onlyBorrower { - require(IMapleGlobalsLike(globals()).isBorrower(pendingBorrower_), "ML:SPB:INVALID_BORROWER"); + require(IGlobalsLike(globals()).isBorrower(pendingBorrower_), "ML:SPB:INVALID_BORROWER"); emit PendingBorrowerSet(_pendingBorrower = pendingBorrower_); } @@ -362,6 +361,18 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { ); } + function impairLoan() external override whenNotPaused onlyLender { + uint256 originalNextPaymentDueDate_ = _nextPaymentDueDate; + + // If the loan is late, do not change the payment due date. + uint256 newPaymentDueDate_ = block.timestamp > originalNextPaymentDueDate_ ? originalNextPaymentDueDate_ : block.timestamp; + + emit LoanImpaired(newPaymentDueDate_); + + _nextPaymentDueDate = newPaymentDueDate_; + _originalNextPaymentDueDate = originalNextPaymentDueDate_; // Store the existing payment due date to enable reversion. + } + function removeLoanImpairment() external override whenNotPaused onlyLender { uint256 originalNextPaymentDueDate_ = _originalNextPaymentDueDate; @@ -415,18 +426,6 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { emit PendingLenderSet(_pendingLender = pendingLender_); } - function impairLoan() external override whenNotPaused onlyLender { - uint256 originalNextPaymentDueDate_ = _nextPaymentDueDate; - - // If the loan is late, do not change the payment due date. - uint256 newPaymentDueDate_ = block.timestamp > originalNextPaymentDueDate_ ? originalNextPaymentDueDate_ : block.timestamp; - - emit LoanImpaired(newPaymentDueDate_); - - _nextPaymentDueDate = newPaymentDueDate_; - _originalNextPaymentDueDate = originalNextPaymentDueDate_; // Store the existing payment due date to enable reversion. - } - /**************************************************************************************************************************************/ /*** Miscellaneous Functions ***/ /**************************************************************************************************************************************/ @@ -460,7 +459,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { uint256 collateralNeeded_ = _getCollateralRequiredFor(_principal, _drawableFunds - drawdown_, _principalRequested, _collateralRequired); uint256 currentCollateral_ = _collateral; - return collateralNeeded_ > currentCollateral_ ? collateralNeeded_ - currentCollateral_ : uint256(0); + collateral_ = collateralNeeded_ > currentCollateral_ ? collateralNeeded_ - currentCollateral_ : uint256(0); } function getClosingPaymentBreakdown() public view override returns (uint256 principal_, uint256 interest_, uint256 fees_) { @@ -529,7 +528,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } function getUnaccountedAmount(address asset_) public view override returns (uint256 unaccountedAmount_) { - return IERC20(asset_).balanceOf(address(this)) + unaccountedAmount_ = IERC20(asset_).balanceOf(address(this)) - (asset_ == _collateralAsset ? _collateral : uint256(0)) // `_collateral` is `_collateralAsset` accounted for. - (asset_ == _fundsAsset ? _drawableFunds : uint256(0)); // `_drawableFunds` is `_fundsAsset` accounted for. } @@ -539,50 +538,50 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { /**************************************************************************************************************************************/ function borrower() external view override returns (address borrower_) { - return _borrower; + borrower_ = _borrower; } function closingRate() external view override returns (uint256 closingRate_) { - return _closingRate; + closingRate_ = _closingRate; } function collateral() external view override returns (uint256 collateral_) { - return _collateral; + collateral_ = _collateral; } function collateralAsset() external view override returns (address collateralAsset_) { - return _collateralAsset; + collateralAsset_ = _collateralAsset; } function collateralRequired() external view override returns (uint256 collateralRequired_) { - return _collateralRequired; + collateralRequired_ = _collateralRequired; } function drawableFunds() external view override returns (uint256 drawableFunds_) { - return _drawableFunds; + drawableFunds_ = _drawableFunds; } function endingPrincipal() external view override returns (uint256 endingPrincipal_) { - return _endingPrincipal; + endingPrincipal_ = _endingPrincipal; } function excessCollateral() external view override returns (uint256 excessCollateral_) { uint256 collateralNeeded_ = _getCollateralRequiredFor(_principal, _drawableFunds, _principalRequested, _collateralRequired); uint256 currentCollateral_ = _collateral; - return currentCollateral_ > collateralNeeded_ ? currentCollateral_ - collateralNeeded_ : uint256(0); + excessCollateral_ = currentCollateral_ > collateralNeeded_ ? currentCollateral_ - collateralNeeded_ : uint256(0); } function factory() external view override returns (address factory_) { - return _factory(); + factory_ = _factory(); } function feeManager() external view override returns (address feeManager_) { - return _feeManager; + feeManager_ = _feeManager; } function fundsAsset() external view override returns (address fundsAsset_) { - return _fundsAsset; + fundsAsset_ = _fundsAsset; } function globals() public view override returns (address globals_) { @@ -590,75 +589,75 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } function governor() public view override returns (address governor_) { - governor_ = IMapleGlobalsLike(globals()).governor(); + governor_ = IGlobalsLike(globals()).governor(); } function gracePeriod() external view override returns (uint256 gracePeriod_) { - return _gracePeriod; + gracePeriod_ = _gracePeriod; } function implementation() external view override returns (address implementation_) { - return _implementation(); + implementation_ = _implementation(); } function interestRate() external view override returns (uint256 interestRate_) { - return _interestRate; + interestRate_ = _interestRate; + } + + function isImpaired() public view override returns (bool isImpaired_) { + isImpaired_ = _originalNextPaymentDueDate != uint256(0); } function lateFeeRate() external view override returns (uint256 lateFeeRate_) { - return _lateFeeRate; + lateFeeRate_ = _lateFeeRate; } function lateInterestPremiumRate() external view override returns (uint256 lateInterestPremiumRate_) { - return _lateInterestPremiumRate; + lateInterestPremiumRate_ = _lateInterestPremiumRate; } function lender() external view override returns (address lender_) { - return _lender; + lender_ = _lender; } function nextPaymentDueDate() external view override returns (uint256 nextPaymentDueDate_) { - return _nextPaymentDueDate; + nextPaymentDueDate_ = _nextPaymentDueDate; } function originalNextPaymentDueDate() external view override returns (uint256 originalNextPaymentDueDate_) { - return _originalNextPaymentDueDate; + originalNextPaymentDueDate_ = _originalNextPaymentDueDate; } function paymentInterval() external view override returns (uint256 paymentInterval_) { - return _paymentInterval; + paymentInterval_ = _paymentInterval; } function paymentsRemaining() external view override returns (uint256 paymentsRemaining_) { - return _paymentsRemaining; + paymentsRemaining_ = _paymentsRemaining; } function pendingBorrower() external view override returns (address pendingBorrower_) { - return _pendingBorrower; + pendingBorrower_ = _pendingBorrower; } function pendingLender() external view override returns (address pendingLender_) { - return _pendingLender; + pendingLender_ = _pendingLender; } function principal() external view override returns (uint256 principal_) { - return _principal; + principal_ = _principal; } function principalRequested() external view override returns (uint256 principalRequested_) { - return _principalRequested; + principalRequested_ = _principalRequested; } function refinanceCommitment() external view override returns (bytes32 refinanceCommitment_) { - return _refinanceCommitment; + refinanceCommitment_ = _refinanceCommitment; } function refinanceInterest() external view override returns (uint256 refinanceInterest_) { - return _refinanceInterest; - } - - function isImpaired() public view override returns (bool isImpaired_) { - return _originalNextPaymentDueDate != uint256(0); + refinanceInterest_ = _refinanceInterest; } /**************************************************************************************************************************************/ @@ -685,16 +684,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } /**************************************************************************************************************************************/ - /*** Internal View Functions ***/ - /**************************************************************************************************************************************/ - - /// @dev Returns whether the amount of collateral posted is commensurate with the amount of drawn down (outstanding) principal. - function _isCollateralMaintained() internal view returns (bool isMaintained_) { - return _collateral >= _getCollateralRequiredFor(_principal, _drawableFunds, _principalRequested, _collateralRequired); - } - - /**************************************************************************************************************************************/ - /*** Internal Pure Functions ***/ + /*** Internal Pure/View Functions ***/ /**************************************************************************************************************************************/ /// @dev Returns the total collateral to be posted for some drawn down (outstanding) principal and overall collateral ratio requirement. @@ -708,7 +698,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { { // Where (collateral / outstandingPrincipal) should be greater or equal to (collateralRequired / principalRequested). // NOTE: principalRequested_ cannot be 0, which is reasonable, since it means this was never a loan. - return principal_ <= drawableFunds_ + collateral_ = principal_ <= drawableFunds_ ? uint256(0) : (collateralRequired_ * (principal_ - drawableFunds_) + principalRequested_ - 1) / principalRequested_; } @@ -753,7 +743,30 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { /// @dev Returns an amount by applying an annualized and scaled interest rate, to a principal, over an interval of time. function _getInterest(uint256 principal_, uint256 interestRate_, uint256 interval_) internal pure returns (uint256 interest_) { - return (principal_ * _getPeriodicInterestRate(interestRate_, interval_)) / SCALED_ONE; + interest_ = (principal_ * _getPeriodicInterestRate(interestRate_, interval_)) / SCALED_ONE; + } + + function _getLateInterest( + uint256 currentTime_, + uint256 principal_, + uint256 interestRate_, + uint256 nextPaymentDueDate_, + uint256 lateFeeRate_, + uint256 lateInterestPremium_ + ) + internal pure returns (uint256 lateInterest_) + { + if (currentTime_ <= nextPaymentDueDate_) return 0; + + // Calculates the number of full days late in seconds (will always be multiples of 86,400). + // Rounds up and is inclusive so that if a payment is 1s late or 24h0m0s late it is 1 full day late. + // 24h0m1s late would be two full days late. + // ((86400n - 0n + (86400n - 1n)) / 86400n) * 86400n = 86400n + // ((86401n - 0n + (86400n - 1n)) / 86400n) * 86400n = 172800n + uint256 fullDaysLate_ = ((currentTime_ - nextPaymentDueDate_ + (1 days - 1)) / 1 days) * 1 days; + + lateInterest_ += _getInterest(principal_, interestRate_ + lateInterestPremium_, fullDaysLate_); + lateInterest_ += (lateFeeRate_ * principal_) / HUNDRED_PERCENT; } /// @dev Returns total principal and interest portion of a number of payments, given generic, stateless loan parameters and loan state. @@ -807,6 +820,18 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { fees_[1] = platformServiceFee_ + platformRefinanceFee_; } + /// @dev Returns the interest rate over an interval, given an annualized interest rate, scaled to 1e18. + function _getPeriodicInterestRate(uint256 interestRate_, uint256 interval_) internal pure returns (uint256 periodicInterestRate_) { + periodicInterestRate_ = (interestRate_ * (SCALED_ONE / HUNDRED_PERCENT) * interval_) / uint256(365 days); + } + + /// @dev Returns refinance commitment given refinance parameters. + function _getRefinanceCommitment(address refinancer_, uint256 deadline_, bytes[] calldata calls_) + internal pure returns (bytes32 refinanceCommitment_) + { + refinanceCommitment_ = keccak256(abi.encode(refinancer_, deadline_, calls_)); + } + function _getRefinanceInterest( uint256 currentTime_, uint256 paymentInterval_, @@ -843,39 +868,10 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { ); } - function _getLateInterest( - uint256 currentTime_, - uint256 principal_, - uint256 interestRate_, - uint256 nextPaymentDueDate_, - uint256 lateFeeRate_, - uint256 lateInterestPremium_ - ) - internal pure returns (uint256 lateInterest_) - { - if (currentTime_ <= nextPaymentDueDate_) return 0; - - // Calculates the number of full days late in seconds (will always be multiples of 86,400). - // Rounds up and is inclusive so that if a payment is 1s late or 24h0m0s late it is 1 full day late. - // 24h0m1s late would be two full days late. - // ((86400n - 0n + (86400n - 1n)) / 86400n) * 86400n = 86400n - // ((86401n - 0n + (86400n - 1n)) / 86400n) * 86400n = 172800n - uint256 fullDaysLate_ = ((currentTime_ - nextPaymentDueDate_ + (1 days - 1)) / 1 days) * 1 days; - - lateInterest_ += _getInterest(principal_, interestRate_ + lateInterestPremium_, fullDaysLate_); - lateInterest_ += (lateFeeRate_ * principal_) / HUNDRED_PERCENT; - } - - /// @dev Returns the interest rate over an interval, given an annualized interest rate, scaled to 1e18. - function _getPeriodicInterestRate(uint256 interestRate_, uint256 interval_) internal pure returns (uint256 periodicInterestRate_) { - return (interestRate_ * (SCALED_ONE / HUNDRED_PERCENT) * interval_) / uint256(365 days); - } + function _handleImpairment() internal { + if (!isImpaired()) return; - /// @dev Returns refinance commitment given refinance parameters. - function _getRefinanceCommitment(address refinancer_, uint256 deadline_, bytes[] calldata calls_) - internal pure returns (bytes32 refinanceCommitment_) - { - return keccak256(abi.encode(refinancer_, deadline_, calls_)); + _originalNextPaymentDueDate = uint256(0); } function _handleServiceFeePayment(uint256 numberOfPayments_) internal returns (uint256 fees_) { @@ -892,10 +888,9 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } } - function _handleImpairment() internal { - if (!isImpaired()) return; - - _originalNextPaymentDueDate = uint256(0); + /// @dev Returns whether the amount of collateral posted is commensurate with the amount of drawn down (outstanding) principal. + function _isCollateralMaintained() internal view returns (bool isMaintained_) { + isMaintained_ = _collateral >= _getCollateralRequiredFor(_principal, _drawableFunds, _principalRequested, _collateralRequired); } function _min(uint256 a_, uint256 b_) internal pure returns (uint256 minimum_) { @@ -911,7 +906,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } function _revertIfPaused() internal view { - require(!IMapleGlobalsLike(globals()).isFunctionPaused(msg.sig), "L:PAUSED"); + require(!IGlobalsLike(globals()).isFunctionPaused(msg.sig), "L:PAUSED"); } /** diff --git a/contracts/MapleLoanFeeManager.sol b/contracts/MapleLoanFeeManager.sol index 1e49c35..2d6cd98 100644 --- a/contracts/MapleLoanFeeManager.sol +++ b/contracts/MapleLoanFeeManager.sol @@ -4,9 +4,9 @@ pragma solidity 0.8.7; import { ERC20Helper } from "../modules/erc20-helper/src/ERC20Helper.sol"; import { + IGlobalsLike, ILoanLike, ILoanManagerLike, - IMapleGlobalsLike, IPoolManagerLike } from "./interfaces/Interfaces.sol"; @@ -100,6 +100,14 @@ contract MapleLoanFeeManager is IMapleLoanFeeManager { emit FeeTermsUpdated(msg.sender, delegateOriginationFee_, delegateServiceFee_); } + function updatePlatformServiceFee(uint256 principalRequested_, uint256 paymentInterval_) external override { + uint256 platformServiceFee_ = getPlatformServiceFeeForPeriod(msg.sender, principalRequested_, paymentInterval_); + + platformServiceFee[msg.sender] = platformServiceFee_; + + emit PlatformServiceFeeUpdated(msg.sender, platformServiceFee_); + } + function updateRefinanceServiceFees(uint256 principalRequested_, uint256 timeSinceLastDueDate_) external override { uint256 platformRefinanceServiceFee_ = getPlatformServiceFeeForPeriod(msg.sender, principalRequested_, timeSinceLastDueDate_); uint256 delegateRefinanceServiceFee_ = getDelegateServiceFeesForPeriod(msg.sender, timeSinceLastDueDate_); @@ -110,14 +118,6 @@ contract MapleLoanFeeManager is IMapleLoanFeeManager { emit PartialRefinanceServiceFeesUpdated(msg.sender, platformRefinanceServiceFee_, delegateRefinanceServiceFee_); } - function updatePlatformServiceFee(uint256 principalRequested_, uint256 paymentInterval_) external override { - uint256 platformServiceFee_ = getPlatformServiceFeeForPeriod(msg.sender, principalRequested_, paymentInterval_); - - platformServiceFee[msg.sender] = platformServiceFee_; - - emit PlatformServiceFeeUpdated(msg.sender, platformServiceFee_); - } - /**************************************************************************************************************************************/ /*** View Functions ***/ /**************************************************************************************************************************************/ @@ -128,6 +128,10 @@ contract MapleLoanFeeManager is IMapleLoanFeeManager { delegateServiceFee_ = delegateServiceFee[loan_] * interval_ / paymentInterval_; } + function getOriginationFees(address loan_, uint256 principalRequested_) external view override returns (uint256 originationFees_) { + originationFees_ = _getPlatformOriginationFee(loan_, principalRequested_) + delegateOriginationFee[loan_]; + } + function getPlatformOriginationFee(address loan_, uint256 principalRequested_) external view override returns (uint256 platformOriginationFee_) { @@ -137,7 +141,7 @@ contract MapleLoanFeeManager is IMapleLoanFeeManager { function getPlatformServiceFeeForPeriod(address loan_, uint256 principalRequested_, uint256 interval_) public view override returns (uint256 platformServiceFee_) { - uint256 platformServiceFeeRate_ = IMapleGlobalsLike(globals).platformServiceFeeRate(_getPoolManager(loan_)); + uint256 platformServiceFeeRate_ = IGlobalsLike(globals).platformServiceFeeRate(_getPoolManager(loan_)); platformServiceFee_ = principalRequested_ * platformServiceFeeRate_ * interval_ / 365 days / HUNDRED_PERCENT; } @@ -174,10 +178,6 @@ contract MapleLoanFeeManager is IMapleLoanFeeManager { getPlatformServiceFeeForPeriod(loan_, principalRequested_, interval_); } - function getOriginationFees(address loan_, uint256 principalRequested_) external view override returns (uint256 originationFees_) { - originationFees_ = _getPlatformOriginationFee(loan_, principalRequested_) + delegateOriginationFee[loan_]; - } - /**************************************************************************************************************************************/ /*** Internal View Functions ***/ /**************************************************************************************************************************************/ @@ -189,7 +189,7 @@ contract MapleLoanFeeManager is IMapleLoanFeeManager { function _getPlatformOriginationFee(address loan_, uint256 principalRequested_) internal view returns (uint256 platformOriginationFee_) { - uint256 platformOriginationFeeRate_ = IMapleGlobalsLike(globals).platformOriginationFeeRate(_getPoolManager(loan_)); + uint256 platformOriginationFeeRate_ = IGlobalsLike(globals).platformOriginationFeeRate(_getPoolManager(loan_)); uint256 loanTermLength_ = ILoanLike(loan_).paymentInterval() * ILoanLike(loan_).paymentsRemaining(); platformOriginationFee_ = platformOriginationFeeRate_ * principalRequested_ * loanTermLength_ / 365 days / HUNDRED_PERCENT; @@ -204,7 +204,7 @@ contract MapleLoanFeeManager is IMapleLoanFeeManager { } function _getTreasury() internal view returns (address mapleTreasury_) { - return IMapleGlobalsLike(globals).mapleTreasury(); + return IGlobalsLike(globals).mapleTreasury(); } /**************************************************************************************************************************************/ diff --git a/contracts/MapleLoanInitializer.sol b/contracts/MapleLoanInitializer.sol index c775f67..a86e29e 100644 --- a/contracts/MapleLoanInitializer.sol +++ b/contracts/MapleLoanInitializer.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.7; import { IMapleLoanInitializer } from "./interfaces/IMapleLoanInitializer.sol"; import { IMapleLoanFeeManager } from "./interfaces/IMapleLoanFeeManager.sol"; -import { ILenderLike, IMapleGlobalsLike, IMapleProxyFactoryLike } from "./interfaces/Interfaces.sol"; +import { IGlobalsLike, ILenderLike, IMapleProxyFactoryLike } from "./interfaces/Interfaces.sol"; import { MapleLoanStorage } from "./MapleLoanStorage.sol"; @@ -101,7 +101,7 @@ contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { internal { // Principal requested needs to be non-zero (see `_getCollateralRequiredFor` math). - require(amounts_[1] > uint256(0), "MLI:I:INVALID_PRINCIPAL"); + require(amounts_[1] > uint256(0), "MLI:I:INVALID_PRINCIPAL"); // Ending principal needs to be less than or equal to principal requested. require(amounts_[2] <= amounts_[1], "MLI:I:INVALID_ENDING_PRINCIPAL"); @@ -115,7 +115,7 @@ contract MapleLoanInitializer is IMapleLoanInitializer, MapleLoanStorage { require(fees_[0] <= maxOriginationFee_, "MLI:I:INVALID_ORIGINATION_FEE"); - IMapleGlobalsLike globals_ = IMapleGlobalsLike(IMapleProxyFactoryLike(msg.sender).mapleGlobals()); + IGlobalsLike globals_ = IGlobalsLike(IMapleProxyFactoryLike(msg.sender).mapleGlobals()); require((_borrower = borrower_) != address(0), "MLI:I:ZERO_BORROWER"); require(globals_.isBorrower(borrower_), "MLI:I:INVALID_BORROWER"); diff --git a/contracts/interfaces/IMapleLoan.sol b/contracts/interfaces/IMapleLoan.sol index 8905b0f..cca2393 100644 --- a/contracts/interfaces/IMapleLoan.sol +++ b/contracts/interfaces/IMapleLoan.sol @@ -194,6 +194,12 @@ interface IMapleLoan is IMapleProxied, IMapleLoanEvents { */ function fundLoan() external returns (uint256 fundsLent_); + /** + * @dev Fast forward the next payment due date to the current time. + * This enables the pool delegate to force a payment (or default). + */ + function impairLoan() external; + /** * @dev Make a payment to the loan. * FUNDS SHOULD NOT BE TRANSFERRED TO THIS CONTRACT NON-ATOMICALLY. IF THEY ARE, THE BALANCE MAY BE STOLEN USING `skim`. @@ -244,14 +250,6 @@ interface IMapleLoan is IMapleProxied, IMapleLoanEvents { */ function removeLoanImpairment() external; - /** - * @dev Return funds to the loan (opposite of drawing down). - * FUNDS SHOULD NOT BE TRANSFERRED TO THIS CONTRACT NON-ATOMICALLY. IF THEY ARE, THE BALANCE MAY BE STOLEN USING `skim`. - * @param amount_ An amount to pull from the caller, if any. - * @return fundsReturned_ The amount returned. - */ - function returnFunds(uint256 amount_) external returns (uint256 fundsReturned_); - /** * @dev Repossess collateral, and any funds, for a loan in default. * @param destination_ The address where the collateral and funds asset is to be sent, if any. @@ -260,6 +258,14 @@ interface IMapleLoan is IMapleProxied, IMapleLoanEvents { */ function repossess(address destination_) external returns (uint256 collateralRepossessed_, uint256 fundsRepossessed_); + /** + * @dev Return funds to the loan (opposite of drawing down). + * FUNDS SHOULD NOT BE TRANSFERRED TO THIS CONTRACT NON-ATOMICALLY. IF THEY ARE, THE BALANCE MAY BE STOLEN USING `skim`. + * @param amount_ An amount to pull from the caller, if any. + * @return fundsReturned_ The amount returned. + */ + function returnFunds(uint256 amount_) external returns (uint256 fundsReturned_); + /** * @dev Set the pendingBorrower to a new account. * @param pendingBorrower_ The address of the new pendingBorrower. @@ -280,12 +286,6 @@ interface IMapleLoan is IMapleProxied, IMapleLoanEvents { */ function skim(address token_, address destination_) external returns (uint256 skimmed_); - /** - * @dev Fast forward the next payment due date to the current time. - * This enables the pool delegate to force a payment (or default). - */ - function impairLoan() external; - /**************************************************************************************************************************************/ /*** View Functions ***/ /**************************************************************************************************************************************/ @@ -351,6 +351,6 @@ interface IMapleLoan is IMapleProxied, IMapleLoanEvents { * @dev Return if the loan has been impaired. * @return isImpaired_ Is the loan impaired or not. */ - function isImpaired() external view returns (bool isImpaired_); + function isImpaired() external view returns (bool isImpaired_); } diff --git a/contracts/interfaces/IMapleLoanFeeManager.sol b/contracts/interfaces/IMapleLoanFeeManager.sol index b5068f2..54cb358 100644 --- a/contracts/interfaces/IMapleLoanFeeManager.sol +++ b/contracts/interfaces/IMapleLoanFeeManager.sol @@ -23,13 +23,6 @@ interface IMapleLoanFeeManager { */ event OriginationFeesPaid(address indexed loan_, uint256 delegateOriginationFee_, uint256 platformOriginationFee_); - /** - * @dev New fee terms have been set. - * @param loan_ The address of the loan contract. - * @param platformServiceFee_ The new value for the platform service fee. - */ - event PlatformServiceFeeUpdated(address indexed loan_, uint256 platformServiceFee_); - /** * @dev New fee terms have been set. * @param loan_ The address of the loan contract. @@ -38,6 +31,13 @@ interface IMapleLoanFeeManager { */ event PartialRefinanceServiceFeesUpdated(address indexed loan_, uint256 partialPlatformServiceFee_, uint256 partialDelegateServiceFee_); + /** + * @dev New fee terms have been set. + * @param loan_ The address of the loan contract. + * @param platformServiceFee_ The new value for the platform service fee. + */ + event PlatformServiceFeeUpdated(address indexed loan_, uint256 platformServiceFee_); + /** * @dev A fee payment was made. * @param loan_ The address of the loan contract. @@ -45,7 +45,7 @@ interface IMapleLoanFeeManager { * @param partialRefinanceDelegateServiceFee_ The amount of partial delegate service fee from refinance paid. * @param platformServiceFee_ The amount of platform service fee paid. * @param partialRefinancePlatformServiceFee_ The amount of partial platform service fee from refinance paid. - */ + */ event ServiceFeesPaid( address indexed loan_, uint256 delegateServiceFee_, @@ -84,6 +84,11 @@ interface IMapleLoanFeeManager { */ function updateDelegateFeeTerms(uint256 delegateOriginationFee_, uint256 delegateServiceFee_) external; + /** + * @dev Function called by loans to update the saved platform service fee rate. + */ + function updatePlatformServiceFee(uint256 principalRequested_, uint256 paymentInterval_) external; + /** * @dev Called during loan refinance to save the partial service fees accrued. * @param principalRequested_ The amount of principal pre-refinance requested. @@ -91,11 +96,6 @@ interface IMapleLoanFeeManager { */ function updateRefinanceServiceFees(uint256 principalRequested_, uint256 timeSinceLastDueDate_) external; - /** - * @dev Function called by loans to update the saved platform service fee rate. - */ - function updatePlatformServiceFee(uint256 principalRequested_, uint256 paymentInterval_) external; - /**************************************************************************************************************************************/ /*** View Functions ***/ /**************************************************************************************************************************************/ @@ -109,17 +109,17 @@ interface IMapleLoanFeeManager { /** * @dev Gets the delegate service fee rate for the given loan. - * @param loan_ The address of the loan contract. - * @return delegateServiceFee_ The amount of delegate service fee to be paid. + * @param loan_ The address of the loan contract. + * @return delegateRefinanceServiceFee_ The amount of delegate service fee to be paid. */ - function delegateServiceFee(address loan_) external view returns (uint256 delegateServiceFee_); + function delegateRefinanceServiceFee(address loan_) external view returns (uint256 delegateRefinanceServiceFee_); /** * @dev Gets the delegate service fee rate for the given loan. - * @param loan_ The address of the loan contract. - * @return delegateRefinanceServiceFee_ The amount of delegate service fee to be paid. + * @param loan_ The address of the loan contract. + * @return delegateServiceFee_ The amount of delegate service fee to be paid. */ - function delegateRefinanceServiceFee(address loan_) external view returns (uint256 delegateRefinanceServiceFee_); + function delegateServiceFee(address loan_) external view returns (uint256 delegateServiceFee_); /** * @dev Gets the delegate service fee for the given loan. @@ -129,7 +129,7 @@ interface IMapleLoanFeeManager { */ function getDelegateServiceFeesForPeriod(address loan_, uint256 interval_) external view returns (uint256 delegateServiceFee_); - /** + /** * @dev Gets the sum of all origination fees for the given loan. * @param loan_ The address of the loan contract. * @param principalRequested_ The amount of principal requested in the loan. diff --git a/contracts/interfaces/Interfaces.sol b/contracts/interfaces/Interfaces.sol index fa1c334..3c3d829 100644 --- a/contracts/interfaces/Interfaces.sol +++ b/contracts/interfaces/Interfaces.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.7; -interface IMapleGlobalsLike { +interface IGlobalsLike { function governor() external view returns (address governor_); @@ -61,14 +61,6 @@ interface ILoanManagerLike { } -interface IMapleFeeManagerLike { - - function updateDelegateFeeTerms(uint256 delegateOriginationFee_, uint256 delegateServiceFee_) external; - - function updatePlatformServiceFee(uint256 principalRequested_, uint256 paymentInterval_) external; - -} - interface IMapleProxyFactoryLike { function isInstance(address instance_) external view returns (bool isInstance_); From 18480bf7d6119fa8f0590742cc6b4b1758817150 Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Thu, 27 Apr 2023 17:33:02 +0100 Subject: [PATCH 24/47] feat: revert event sig changes to match mainnet bytecode (#290) --- contracts/interfaces/IMapleLoanFeeManager.sol | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/contracts/interfaces/IMapleLoanFeeManager.sol b/contracts/interfaces/IMapleLoanFeeManager.sol index 54cb358..4b19723 100644 --- a/contracts/interfaces/IMapleLoanFeeManager.sol +++ b/contracts/interfaces/IMapleLoanFeeManager.sol @@ -13,7 +13,7 @@ interface IMapleLoanFeeManager { * @param delegateOriginationFee_ The new value for delegate origination fee. * @param delegateServiceFee_ The new value for delegate service fee. */ - event FeeTermsUpdated(address indexed loan_, uint256 delegateOriginationFee_, uint256 delegateServiceFee_); + event FeeTermsUpdated(address loan_, uint256 delegateOriginationFee_, uint256 delegateServiceFee_); /** * @dev A fee payment was made. @@ -21,7 +21,7 @@ interface IMapleLoanFeeManager { * @param delegateOriginationFee_ The amount of delegate origination fee paid. * @param platformOriginationFee_ The amount of platform origination fee paid. */ - event OriginationFeesPaid(address indexed loan_, uint256 delegateOriginationFee_, uint256 platformOriginationFee_); + event OriginationFeesPaid(address loan_, uint256 delegateOriginationFee_, uint256 platformOriginationFee_); /** * @dev New fee terms have been set. @@ -29,14 +29,14 @@ interface IMapleLoanFeeManager { * @param partialPlatformServiceFee_ The value for the platform service fee. * @param partialDelegateServiceFee_ The value for the delegate service fee. */ - event PartialRefinanceServiceFeesUpdated(address indexed loan_, uint256 partialPlatformServiceFee_, uint256 partialDelegateServiceFee_); + event PartialRefinanceServiceFeesUpdated(address loan_, uint256 partialPlatformServiceFee_, uint256 partialDelegateServiceFee_); /** * @dev New fee terms have been set. * @param loan_ The address of the loan contract. * @param platformServiceFee_ The new value for the platform service fee. */ - event PlatformServiceFeeUpdated(address indexed loan_, uint256 platformServiceFee_); + event PlatformServiceFeeUpdated(address loan_, uint256 platformServiceFee_); /** * @dev A fee payment was made. @@ -47,11 +47,11 @@ interface IMapleLoanFeeManager { * @param partialRefinancePlatformServiceFee_ The amount of partial platform service fee from refinance paid. */ event ServiceFeesPaid( - address indexed loan_, - uint256 delegateServiceFee_, - uint256 partialRefinanceDelegateServiceFee_, - uint256 platformServiceFee_, - uint256 partialRefinancePlatformServiceFee_ + address loan_, + uint256 delegateServiceFee_, + uint256 partialRefinanceDelegateServiceFee_, + uint256 platformServiceFee_, + uint256 partialRefinancePlatformServiceFee_ ); /**************************************************************************************************************************************/ From 5253ed03bda53a1e0f0c97c6c4d60a7514bb32b1 Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Tue, 9 May 2023 14:25:41 +0100 Subject: [PATCH 25/47] feat: Only securityAdmin can upgrade loan (SC-12161) (#291) * feat: only securityAdmin can upgrade loan * style: remove extra _ * fix: update test name Co-authored-by: Michael De Luca <35537333+deluca-mike@users.noreply.github.com> --------- Co-authored-by: Lucas Manuel Co-authored-by: Michael De Luca <35537333+deluca-mike@users.noreply.github.com> --- contracts/MapleLoan.sol | 2 +- tests/InitializerAndMigrator.t.sol | 6 ++++-- tests/MapleLoan.t.sol | 5 ++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index 8cc410a..a753956 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -74,7 +74,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { } function upgrade(uint256 toVersion_, bytes calldata arguments_) external override whenNotPaused { - require(msg.sender == _borrower || msg.sender == IGlobalsLike(globals()).securityAdmin(), "ML:U:NO_AUTH"); + require(msg.sender == IGlobalsLike(globals()).securityAdmin(), "ML:U:NO_AUTH"); emit Upgraded(toVersion_, arguments_); diff --git a/tests/InitializerAndMigrator.t.sol b/tests/InitializerAndMigrator.t.sol index 44d6257..3879196 100644 --- a/tests/InitializerAndMigrator.t.sol +++ b/tests/InitializerAndMigrator.t.sol @@ -13,7 +13,8 @@ import { MockGlobals, MockFeeManager, MockLoanManager, MockLoanManagerFactory } contract MapleLoanInitializerAndMigratorTests is TestUtils { - address internal governor = address(new Address()); + address internal governor = address(new Address()); + address internal securityAdmin = address(new Address()); address internal implementation4; address internal implementation5; address internal initializer; @@ -46,6 +47,7 @@ contract MapleLoanInitializerAndMigratorTests is TestUtils { globals.setValidCollateralAsset(address(asset), true); globals.setValidPoolAsset(address(asset), true); globals.__setIsInstanceOf(true); + globals.__setSecurityAdmin(securityAdmin); vm.startPrank(governor); factory.registerImplementation(1, implementation4, initializer); @@ -118,7 +120,7 @@ contract MapleLoanInitializerAndMigratorTests is TestUtils { assertEq(loan.lateInterestPremiumRate(), 0.04e18); // Upgrade - vm.prank(loan.borrower()); + vm.prank(securityAdmin); loan.upgrade(2, new bytes(0)); assertEq(loan.interestRate(), 0.1e6); diff --git a/tests/MapleLoan.t.sol b/tests/MapleLoan.t.sol index b5f4306..7ab2a6e 100644 --- a/tests/MapleLoan.t.sol +++ b/tests/MapleLoan.t.sol @@ -444,13 +444,12 @@ contract MapleLoanTests is TestUtils { loan.upgrade(1, abi.encode(newImplementation)); } - function test_upgrade_acl_borrower() external { + function test_upgrade_acl_noAuth_asBorrower() external { address newImplementation = address(new MapleLoanHarness()); vm.prank(borrower); + vm.expectRevert("ML:U:NO_AUTH"); loan.upgrade(1, abi.encode(newImplementation)); - - assertEq(loan.implementation(), newImplementation); } function test_upgrade_acl_securityAdmin() external { From 98300c7fe5fbbd2d25436779c97200620e1ced90 Mon Sep 17 00:00:00 2001 From: Vedran Bidin Date: Wed, 10 May 2023 01:23:34 +0300 Subject: [PATCH 26/47] Open feat: Remove for loop optimization (SC-12165) (#292) --- contracts/MapleLoan.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index a753956..77b6418 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -304,10 +304,9 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { // Clear refinance commitment to prevent implications of re-acceptance of another call to `_acceptNewTerms`. _refinanceCommitment = bytes32(0); - for (uint256 i_; i_ < calls_.length;) { + for (uint256 i_; i_ < calls_.length; ++i_) { ( bool success_, ) = refinancer_.delegatecall(calls_[i_]); require(success_, "ML:ANT:FAILED"); - unchecked { ++i_; } } emit NewTermsAccepted(refinanceCommitment_, refinancer_, deadline_, calls_); From 95b4efc108eabbd8be4585cc0796a05be5d7be4b Mon Sep 17 00:00:00 2001 From: Joao Gabriel Carvalho Date: Wed, 10 May 2023 14:37:33 -0300 Subject: [PATCH 27/47] fix: Make `HUNDRED_PERCENT` public (SC-12199) (#295) * fix: make hundred percent public * test: add test for getter --- contracts/MapleLoan.sol | 5 +++-- contracts/interfaces/IMapleLoan.sol | 5 +++++ tests/InitializerAndMigrator.t.sol | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index 77b6418..c166894 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -27,8 +27,9 @@ import { MapleLoanStorage } from "./MapleLoanStorage.sol"; /// @title MapleLoan implements a primitive loan with additional functionality, and is intended to be proxied. contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { - uint256 private constant HUNDRED_PERCENT = 1e6; - uint256 private constant SCALED_ONE = 1e18; + uint256 public constant override HUNDRED_PERCENT = 1e6; + + uint256 private constant SCALED_ONE = 1e18; modifier limitDrawableUse() { if (msg.sender == _borrower) { diff --git a/contracts/interfaces/IMapleLoan.sol b/contracts/interfaces/IMapleLoan.sol index cca2393..1e7528d 100644 --- a/contracts/interfaces/IMapleLoan.sol +++ b/contracts/interfaces/IMapleLoan.sol @@ -347,6 +347,11 @@ interface IMapleLoan is IMapleProxied, IMapleLoanEvents { */ function getUnaccountedAmount(address asset_) external view returns (uint256 unaccountedAmount_); + /** + * @dev The value that represents 100%, to be easily comparable with the loan rates. + */ + function HUNDRED_PERCENT() external pure returns (uint256 hundredPercent_); + /** * @dev Return if the loan has been impaired. * @return isImpaired_ Is the loan impaired or not. diff --git a/tests/InitializerAndMigrator.t.sol b/tests/InitializerAndMigrator.t.sol index 3879196..b4d933c 100644 --- a/tests/InitializerAndMigrator.t.sol +++ b/tests/InitializerAndMigrator.t.sol @@ -111,6 +111,8 @@ contract MapleLoanInitializerAndMigratorTests is TestUtils { assertEq(loan.closingRate(), 0.02e18); assertEq(loan.lateFeeRate(), 0.03e18); assertEq(loan.lateInterestPremiumRate(), 0.04e18); + + assertEq(loan.HUNDRED_PERCENT(), 1e6); } function test_migration_ratesChange() external { From dd4df7f5311f0ff50dd765188b62ad3d94f1e734 Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Wed, 10 May 2023 22:13:40 +0100 Subject: [PATCH 28/47] refactor: Rename to lateInterestPremiumRate (SC-12162) (#293) * refactor: rename to lateInterestPremiumRate * refactor: update var name in tests * fix: pr review --------- Co-authored-by: Michael De Luca Co-authored-by: Lucas Manuel --- contracts/MapleLoan.sol | 12 ++++++------ tests/MapleLoanLogic.t.sol | 24 ++++++++++++------------ tests/Refinancer.t.sol | 4 ++-- tests/harnesses/MapleLoanHarnesses.sol | 4 ++-- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index c166894..f776e2b 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -752,7 +752,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { uint256 interestRate_, uint256 nextPaymentDueDate_, uint256 lateFeeRate_, - uint256 lateInterestPremium_ + uint256 lateInterestPremiumRate_ ) internal pure returns (uint256 lateInterest_) { @@ -765,7 +765,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { // ((86401n - 0n + (86400n - 1n)) / 86400n) * 86400n = 172800n uint256 fullDaysLate_ = ((currentTime_ - nextPaymentDueDate_ + (1 days - 1)) / 1 days) * 1 days; - lateInterest_ += _getInterest(principal_, interestRate_ + lateInterestPremium_, fullDaysLate_); + lateInterest_ += _getInterest(principal_, interestRate_ + lateInterestPremiumRate_, fullDaysLate_); lateInterest_ += (lateFeeRate_ * principal_) / HUNDRED_PERCENT; } @@ -779,7 +779,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { uint256 paymentsRemaining_, uint256 interestRate_, uint256 lateFeeRate_, - uint256 lateInterestPremium_ + uint256 lateInterestPremiumRate_ ) internal view returns ( @@ -804,7 +804,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { interestRate_, nextPaymentDueDate_, lateFeeRate_, - lateInterestPremium_ + lateInterestPremiumRate_ ); interest_[2] = _refinanceInterest; @@ -841,7 +841,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { uint256 paymentsRemaining_, uint256 nextPaymentDueDate_, uint256 lateFeeRate_, - uint256 lateInterestPremium_ + uint256 lateInterestPremiumRate_ ) internal pure returns (uint256 refinanceInterest_) { @@ -864,7 +864,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { interestRate_, nextPaymentDueDate_, lateFeeRate_, - lateInterestPremium_ + lateInterestPremiumRate_ ); } diff --git a/tests/MapleLoanLogic.t.sol b/tests/MapleLoanLogic.t.sol index f489bfc..6a33257 100644 --- a/tests/MapleLoanLogic.t.sol +++ b/tests/MapleLoanLogic.t.sol @@ -1217,20 +1217,20 @@ contract MapleLoanLogic_GetNextPaymentBreakdownTests is TestUtils { uint256 paymentsRemaining_, uint256 interestRate_, uint256 lateFeeRate_, - uint256 lateInterestPremium_, + uint256 lateInterestPremiumRate_, uint256 refinanceInterest_ ) external { - nextPaymentDueDate_ = constrictToRange(nextPaymentDueDate_, block.timestamp - 365 days, block.timestamp + 365 days); - termLength_ = constrictToRange(termLength_, 1 days, 15 * 365 days); - principal_ = constrictToRange(principal_, 1, 1e12 * 1e18); - endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principal_); - paymentsRemaining_ = constrictToRange(paymentsRemaining_, 1, 100); - interestRate_ = constrictToRange(interestRate_, 0, 1.00e6); - lateFeeRate_ = constrictToRange(lateFeeRate_, interestRate_, 1.00e6); - lateInterestPremium_ = constrictToRange(lateInterestPremium_, interestRate_, 1.00e6); - refinanceInterest_ = constrictToRange(refinanceInterest_, 0, 1e12 * 1e18); + nextPaymentDueDate_ = constrictToRange(nextPaymentDueDate_, block.timestamp - 365 days, block.timestamp + 365 days); + termLength_ = constrictToRange(termLength_, 1 days, 15 * 365 days); + principal_ = constrictToRange(principal_, 1, 1e12 * 1e18); + endingPrincipal_ = constrictToRange(endingPrincipal_, 0, principal_); + paymentsRemaining_ = constrictToRange(paymentsRemaining_, 1, 100); + interestRate_ = constrictToRange(interestRate_, 0, 1.00e6); + lateFeeRate_ = constrictToRange(lateFeeRate_, interestRate_, 1.00e6); + lateInterestPremiumRate_ = constrictToRange(lateInterestPremiumRate_, interestRate_, 1.00e6); + refinanceInterest_ = constrictToRange(refinanceInterest_, 0, 1e12 * 1e18); uint256 paymentInterval = termLength_ / paymentsRemaining_; @@ -1241,7 +1241,7 @@ contract MapleLoanLogic_GetNextPaymentBreakdownTests is TestUtils { loan.__setPaymentsRemaining(paymentsRemaining_); loan.__setInterestRate(interestRate_); loan.__setLateFeeRate(lateFeeRate_); - loan.__setLateInterestPremiumRate(lateInterestPremium_); + loan.__setLateInterestPremiumRate(lateInterestPremiumRate_); loan.__setRefinanceInterest(refinanceInterest_); loan.__setFeeManager(address(new MockFeeManager())); @@ -1254,7 +1254,7 @@ contract MapleLoanLogic_GetNextPaymentBreakdownTests is TestUtils { paymentsRemaining_, interestRate_, lateFeeRate_, - lateInterestPremium_ + lateInterestPremiumRate_ ); ( uint256 actualPrincipal, uint256 actualInterest, ) = loan.getNextPaymentBreakdown(); diff --git a/tests/Refinancer.t.sol b/tests/Refinancer.t.sol index 32b9726..3fda891 100644 --- a/tests/Refinancer.t.sol +++ b/tests/Refinancer.t.sol @@ -513,7 +513,7 @@ contract RefinancerFeeTests is RefinancerTestBase { assertEq(loan.lateFeeRate(), newLateFeeRate_); } - function test_refinance_lateInterestPremium( + function test_refinance_lateInterestPremiumRate( uint256 principalRequested_, uint256 collateralRequired_, uint256 endingPrincipal_, @@ -700,7 +700,7 @@ contract RefinancerInterestTests is TestUtils { factory = new MockFactory(address(globals)); lender.__setFundsAsset(address(token)); - + globals.setValidBorrower(borrower, true); globals.setValidPoolAsset(address(token), true); diff --git a/tests/harnesses/MapleLoanHarnesses.sol b/tests/harnesses/MapleLoanHarnesses.sol index 6f9bb33..3f95011 100644 --- a/tests/harnesses/MapleLoanHarnesses.sol +++ b/tests/harnesses/MapleLoanHarnesses.sol @@ -169,7 +169,7 @@ contract MapleLoanHarness is MapleLoan { uint256 paymentsRemaining_, uint256 interestRate_, uint256 lateFeeRate_, - uint256 lateInterestPremium_ + uint256 lateInterestPremiumRate_ ) external view returns ( @@ -189,7 +189,7 @@ contract MapleLoanHarness is MapleLoan { paymentsRemaining_, interestRate_, lateFeeRate_, - lateInterestPremium_ + lateInterestPremiumRate_ ); interestAmount_ = interestArray_[0] + interestArray_[1] + interestArray_[2]; From 6df6a131f194952935eb7975999dcccd6fc3a426 Mon Sep 17 00:00:00 2001 From: Michael De Luca <35537333+deluca-mike@users.noreply.github.com> Date: Thu, 11 May 2023 09:11:33 -0400 Subject: [PATCH 29/47] refactor: Clean up events for clearing impairment (SC-12191) (SB #22) (#296) * feat: clean up impairment removal (SC-12191) (SB #22) * fix: update events --------- Co-authored-by: JG Carvalho Co-authored-by: Lucas Manuel Co-authored-by: lucas-manuel --- contracts/MapleLoan.sol | 26 ++++++++++------------- contracts/interfaces/IMapleLoanEvents.sol | 12 +++++------ 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index f776e2b..f2e4834 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -108,7 +108,6 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { require(block.timestamp <= paymentDueDate_, "ML:CL:PAYMENT_IS_LATE"); - _handleImpairment(); ( principal_, interest_, ) = getClosingPaymentBreakdown(); @@ -122,6 +121,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { fees_ = _handleServiceFeePayment(_paymentsRemaining); + // NOTE: Closing a loan always results in the an impairment being removed. _clearLoanAccounting(); emit LoanClosed(principal_, interest_, fees_); @@ -165,8 +165,6 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { "ML:MP:TRANSFER_FROM_FAILED" ); - _handleImpairment(); - ( principal_, interest_, ) = getNextPaymentBreakdown(); _refinanceInterest = uint256(0); @@ -183,12 +181,15 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { uint256 previousPaymentDueDate_ = _nextPaymentDueDate; uint256 nextPaymentDueDate_; + // NOTE: Making a payment always results in the impairment being removed. if (paymentsRemaining_ == uint256(1)) { _clearLoanAccounting(); // Assumes `getNextPaymentBreakdown` returns a `principal_` that is `_principal`. } else { _nextPaymentDueDate = nextPaymentDueDate_ = previousPaymentDueDate_ + _paymentInterval; _principal -= principal_; _paymentsRemaining = paymentsRemaining_ - uint256(1); + + delete _originalNextPaymentDueDate; } emit PaymentMade(principal_, interest_, fees_); @@ -303,15 +304,18 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { _refinanceInterest += proRataInterest_; // Clear refinance commitment to prevent implications of re-acceptance of another call to `_acceptNewTerms`. - _refinanceCommitment = bytes32(0); + delete _refinanceCommitment; + + // NOTE: Accepting new terms always results in the an impairment being removed. + delete _originalNextPaymentDueDate; + + emit NewTermsAccepted(refinanceCommitment_, refinancer_, deadline_, calls_); for (uint256 i_; i_ < calls_.length; ++i_) { ( bool success_, ) = refinancer_.delegatecall(calls_[i_]); require(success_, "ML:ANT:FAILED"); } - emit NewTermsAccepted(refinanceCommitment_, refinancer_, deadline_, calls_); - address fundsAsset_ = _fundsAsset; uint256 principalRequested_ = _principalRequested; @@ -321,8 +325,6 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { // NOTE: `_paymentInterval` here is possibly newly set via the above delegate calls, so cache it. _nextPaymentDueDate = block.timestamp + paymentInterval_; - _handleImpairment(); - // Update Platform Fees and pay originations. feeManager_.updatePlatformServiceFee(principalRequested_, paymentInterval_); @@ -382,7 +384,7 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { _nextPaymentDueDate = originalNextPaymentDueDate_; delete _originalNextPaymentDueDate; - emit NextPaymentDueDateRestored(originalNextPaymentDueDate_); + emit ImpairmentRemoved(originalNextPaymentDueDate_); } function repossess(address destination_) @@ -868,12 +870,6 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { ); } - function _handleImpairment() internal { - if (!isImpaired()) return; - - _originalNextPaymentDueDate = uint256(0); - } - function _handleServiceFeePayment(uint256 numberOfPayments_) internal returns (uint256 fees_) { uint256 balanceBeforeServiceFees_ = IERC20(_fundsAsset).balanceOf(address(this)); diff --git a/contracts/interfaces/IMapleLoanEvents.sol b/contracts/interfaces/IMapleLoanEvents.sol index d8cc49c..650e1f1 100644 --- a/contracts/interfaces/IMapleLoanEvents.sol +++ b/contracts/interfaces/IMapleLoanEvents.sol @@ -51,6 +51,12 @@ interface IMapleLoanEvents { */ event FundsReturned(uint256 amount_); + /** + * @dev The loan impairment was explicitly removed (i.e. not the result of a payment or new terms acceptance). + * @param nextPaymentDueDate_ The new next payment due date. + */ + event ImpairmentRemoved(uint256 nextPaymentDueDate_); + /** * @dev Loan was initialized. * @param borrower_ The address of the borrower. @@ -135,12 +141,6 @@ interface IMapleLoanEvents { */ event NewTermsRejected(bytes32 refinanceCommitment_, address refinancer_, uint256 deadline_, bytes[] calls_); - /** - * @dev The next payment due date was restored to it's original value, reverting the action of loan impairment. - * @param nextPaymentDueDate_ The new next payment due date. - */ - event NextPaymentDueDateRestored(uint256 nextPaymentDueDate_); - /** * @dev Payments were made. * @param principalPaid_ The portion of the total amount that went towards principal. From 188a0d68c5254d1407180ac7b5fca00e874b0b13 Mon Sep 17 00:00:00 2001 From: Joao Gabriel Carvalho Date: Mon, 15 May 2023 13:48:13 -0300 Subject: [PATCH 30/47] fix: Delete refinance commitment on clear accounting (#297) * fix: delete refinance commitment on clear accounting * fix: also clear refinance interest * test: add assertion on clear loan --- contracts/MapleLoan.sol | 4 ++++ tests/MapleLoanLogic.t.sol | 3 +++ 2 files changed, 7 insertions(+) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index f2e4834..ee5a693 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -668,6 +668,8 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { /// @dev Clears all state variables to end a loan, but keep borrower and lender withdrawal functionality intact. function _clearLoanAccounting() internal { + _refinanceCommitment = bytes32(0); + _gracePeriod = uint256(0); _paymentInterval = uint256(0); @@ -682,6 +684,8 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { _paymentsRemaining = uint256(0); _principal = uint256(0); + _refinanceInterest = uint256(0); + _originalNextPaymentDueDate = uint256(0); } diff --git a/tests/MapleLoanLogic.t.sol b/tests/MapleLoanLogic.t.sol index 6a33257..d20da44 100644 --- a/tests/MapleLoanLogic.t.sol +++ b/tests/MapleLoanLogic.t.sol @@ -2106,6 +2106,9 @@ contract MapleLoanLogic_MakePaymentTests is TestUtils { assertEq(loan.nextPaymentDueDate(), 0); assertEq(loan.paymentsRemaining(), 0); assertEq(loan.principal(), 0); + assertEq(loan.refinanceInterest(), 0); + + assertEq(loan.refinanceCommitment(), bytes32(0)); } function test_makePayment_withRefinanceInterest( From c2ba112345ae0ed9dab0608783337e618b457a4f Mon Sep 17 00:00:00 2001 From: Vedran Bidin Date: Tue, 16 May 2023 20:54:39 +0300 Subject: [PATCH 31/47] fix: Reorder refinance event (#299) --- contracts/MapleLoan.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index ee5a693..e8518a5 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -316,6 +316,9 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { require(success_, "ML:ANT:FAILED"); } + // TODO: Emit this before the refinance calls in order to adhere to the CEI pattern. + emit NewTermsAccepted(refinanceCommitment_, refinancer_, deadline_, calls_); + address fundsAsset_ = _fundsAsset; uint256 principalRequested_ = _principalRequested; From d0c7a12276570aadec7969e5dec81e61d578cf57 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Wed, 17 May 2023 12:24:44 -0400 Subject: [PATCH 32/47] fix: Remove duplicate event (#300) --- contracts/MapleLoan.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index e8518a5..52328c4 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -309,8 +309,6 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { // NOTE: Accepting new terms always results in the an impairment being removed. delete _originalNextPaymentDueDate; - emit NewTermsAccepted(refinanceCommitment_, refinancer_, deadline_, calls_); - for (uint256 i_; i_ < calls_.length; ++i_) { ( bool success_, ) = refinancer_.delegatecall(calls_[i_]); require(success_, "ML:ANT:FAILED"); From ef8640b032c77e9fcad556063de905e4540cf29b Mon Sep 17 00:00:00 2001 From: Vedran Bidin Date: Tue, 23 May 2023 23:11:00 +0300 Subject: [PATCH 33/47] feat: add codeowners (#302) --- .github/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..c5affea --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# These owners will be the default owners for everything in the repo. +* @maple-labs/smart-contracts-admin From d82465e48134fa927170ddd8dd7ede5046370bd6 Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Tue, 30 May 2023 07:52:18 +0100 Subject: [PATCH 34/47] chore: Update release procedure (#301) --- configs/package.yaml | 10 +++++----- foundry.toml | 4 ++++ scripts/release.sh | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/configs/package.yaml b/configs/package.yaml index 8c1fc48..4696581 100644 --- a/configs/package.yaml +++ b/configs/package.yaml @@ -1,5 +1,5 @@ -name: loan -version: 4.0.0-beta.0 +name: fixed-term-loan +version: 5.0.1 source: contracts packages: - path: contracts/MapleLoan.sol @@ -12,8 +12,8 @@ packages: contractName: MapleLoanInitializer - path: contracts/MapleLoanStorage.sol contractName: MapleLoanStorage - - path: contracts/MapleLoanV4Migrator.sol - contractName: MapleLoanV4Migrator + - path: contracts/MapleLoanV5Migrator.sol + contractName: MapleLoanV5Migrator - path: contracts/Refinancer.sol contractName: Refinancer -customDescription: Maple Loan Artifacts and ABIs +customDescription: Maple Fixed Term Loan Artifacts and ABIs diff --git a/foundry.toml b/foundry.toml index 094b4e2..0e4272d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -13,3 +13,7 @@ fuzz_runs = 1000 [profile.super_deep] fuzz_runs = 50000 + +[profile.release] +optimizer = true # Enable or disable the solc optimizer +optimizer_runs = 200 # The number of optimizer runs diff --git a/scripts/release.sh b/scripts/release.sh index dd0ee23..8529992 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -5,7 +5,7 @@ version=$(cat ./configs/package.yaml | grep "version: " | sed -r 's/.{9}//') name=$(cat ./configs/package.yaml | grep "name: " | sed -r 's/.{6}//') customDescription=$(cat ./configs/package.yaml | grep "customDescription: " | sed -r 's/.{19}//') -./scripts/build.sh +./scripts/build.sh -p release rm -rf ./package mkdir -p package From 24a9382132ebaca83167dff2bed04148802c27c4 Mon Sep 17 00:00:00 2001 From: Joao Gabriel Carvalho Date: Tue, 6 Jun 2023 17:36:44 -0300 Subject: [PATCH 35/47] chore: add audit report (#303) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 99efa16..ae726d4 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,8 @@ forge install | v3.0.1 - v4.0.0 | Trail of Bits | [`Trail of Bits Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/10246688/Maple.Finance.v2.-.Final.Report.-.Fixed.-.2022.pdf) | 2022-08-24 | | v3.0.1 - v4.0.0 | Spearbit | [`Spearbit Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/10223545/Maple.Finance.v2.-.Spearbit.pdf) | 2022-10-17 | | v3.0.1 - v4.0.0 | Three Sigma | [`Three Sigma Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/10223541/three-sigma_maple-finance_code-audit_v1.1.1.pdf) | 2022-10-24 | +| v5.0.0 - v5.0.1 | Spearbit Auditors via Cantina | [`2023-06-05 - Cantina Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/11667848/cantina-maple.pdf) | 2023-06-05 | +| v5.0.0 - v5.0.1 | Three Sigma | [`2023-04-10 - Three Sigma Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/11663546/maple-v2-audit_three-sigma_2023.pdf) | 2023-04-21 | ## Bug Bounty From aad0ebac4b1b2d1d1a83bac5f537eefa62ade2c4 Mon Sep 17 00:00:00 2001 From: Michael De Luca <35537333+deluca-mike@users.noreply.github.com> Date: Tue, 20 Jun 2023 12:08:33 -0400 Subject: [PATCH 36/47] chore: Cleanup and bytecode size check (SC-12382) (#304) * chore: cleanup and bytecode size check (SC-12382) * fix: coverage report and size check script --- .github/workflows/forge-pr.yaml | 26 ++++++++++++++++++++++++-- .github/workflows/forge.yml | 26 ++++++++++++++++++++++++-- Makefile | 14 ++++---------- configs/package.yaml | 2 -- foundry.toml | 10 +++++----- scripts/check-sizes.sh | 33 +++++++++++++++++++++++++++++++++ scripts/release.sh | 2 +- 7 files changed, 91 insertions(+), 22 deletions(-) create mode 100755 scripts/check-sizes.sh diff --git a/.github/workflows/forge-pr.yaml b/.github/workflows/forge-pr.yaml index 478c6fc..6a7027f 100644 --- a/.github/workflows/forge-pr.yaml +++ b/.github/workflows/forge-pr.yaml @@ -3,7 +3,8 @@ name: Forge Tests (PR) on: [pull_request] jobs: - run-ci: + test: + name: Test with "deep" profile runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -39,11 +40,32 @@ jobs: - name: Generate coverage report run: | forge coverage --report lcov + sudo apt-get install lcov + lcov --remove lcov.info -o lcov.info 'tests/*' - name: Report code coverage uses: zgosalvez/github-actions-report-lcov@v1 with: coverage-files: lcov.info - minimum-coverage: 65 + minimum-coverage: 90 artifact-name: code-coverage-report github-token: ${{ secrets.GITHUB_TOKEN }} working-directory: ./ + + size_check: + name: Check contracts sizes + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install Foundry + uses: onbjerg/foundry-toolchain@v1 + with: + version: nightly + + - name: Install submodules + run: | + git config --global url."https://github.com/".insteadOf "git@github.com:" + git submodule update --init --recursive + + - name: Check contract sizes + run: ./scripts/check-sizes.sh diff --git a/.github/workflows/forge.yml b/.github/workflows/forge.yml index 2d729df..0a370cd 100644 --- a/.github/workflows/forge.yml +++ b/.github/workflows/forge.yml @@ -6,7 +6,8 @@ on: - main jobs: - run-ci: + test: + name: Test with "deep" profile runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -42,11 +43,32 @@ jobs: - name: Generate coverage report run: | forge coverage --report lcov + sudo apt-get install lcov + lcov --remove lcov.info -o lcov.info 'tests/*' - name: Report code coverage uses: zgosalvez/github-actions-report-lcov@v1 with: coverage-files: lcov.info - minimum-coverage: 65 + minimum-coverage: 90 artifact-name: code-coverage-report github-token: ${{ secrets.GITHUB_TOKEN }} working-directory: ./ + + size_check: + name: Check contracts sizes + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install Foundry + uses: onbjerg/foundry-toolchain@v1 + with: + version: nightly + + - name: Install submodules + run: | + git config --global url."https://github.com/".insteadOf "git@github.com:" + git submodule update --init --recursive + + - name: Check contract sizes + run: ./scripts/check-sizes.sh diff --git a/Makefile b/Makefile index 71146e4..bf5f435 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,11 @@ -install: - @git submodule update --init --recursive - -update: - @forge update - build: - @scripts/build.sh -p default + @scripts/build.sh -p production release: @scripts/release.sh +size: + @scripts/check-sizes.sh + test: @scripts/test.sh -p default - -clean: - @forge clean diff --git a/configs/package.yaml b/configs/package.yaml index 4696581..9cc5120 100644 --- a/configs/package.yaml +++ b/configs/package.yaml @@ -10,8 +10,6 @@ packages: contractName: MapleLoanFeeManager - path: contracts/MapleLoanInitializer.sol contractName: MapleLoanInitializer - - path: contracts/MapleLoanStorage.sol - contractName: MapleLoanStorage - path: contracts/MapleLoanV5Migrator.sol contractName: MapleLoanV5Migrator - path: contracts/Refinancer.sol diff --git a/foundry.toml b/foundry.toml index 0e4272d..05293fe 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,10 +3,10 @@ contracts = 'contracts' # The contract directory test = 'tests' # The test directory libs = ['modules'] # A list of library directories solc_version = '0.8.7' # Override for the solc version (setting this ignores `auto_detect_solc`) -optimizer = true # Enable or disable the solc optimizer -optimizer_runs = 200 # The number of optimizer runs +optimizer = false # Enable or disable the solc optimizer verbosity = 3 # The verbosity of tests block_timestamp = 1_622_400_000 # Timestamp for tests (non-zero) +fuzz_runs = 100 # Number of fuzz runs [profile.deep] fuzz_runs = 1000 @@ -14,6 +14,6 @@ fuzz_runs = 1000 [profile.super_deep] fuzz_runs = 50000 -[profile.release] -optimizer = true # Enable or disable the solc optimizer -optimizer_runs = 200 # The number of optimizer runs +[profile.production] +optimizer = true # Enable or disable the solc optimizer +optimizer_runs = 200 # The number of optimizer runs diff --git a/scripts/check-sizes.sh b/scripts/check-sizes.sh new file mode 100755 index 0000000..0ba6bc1 --- /dev/null +++ b/scripts/check-sizes.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +while getopts p: flag +do + case "${flag}" in + p) profile=${OPTARG};; + esac +done + +export FOUNDRY_PROFILE=production + +sizes=$(forge build --sizes) + +names=($(cat ./configs/package.yaml | grep " contractName:" | sed -r 's/.{18}//')) + +fail=false + +for i in "${!names[@]}"; do + line=$(echo "$sizes" | grep -w "${names[i]}") + + if [[ $line == *"-"* ]]; then + echo "${names[i]} is too large" + fail=true + fi +done + +if $fail + then + echo "Contract size check failed" + exit 1 + else + echo "Contract size check passed" +fi diff --git a/scripts/release.sh b/scripts/release.sh index 8529992..09b7475 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -5,7 +5,7 @@ version=$(cat ./configs/package.yaml | grep "version: " | sed -r 's/.{9}//') name=$(cat ./configs/package.yaml | grep "name: " | sed -r 's/.{6}//') customDescription=$(cat ./configs/package.yaml | grep "customDescription: " | sed -r 's/.{19}//') -./scripts/build.sh -p release +./scripts/build.sh -p production rm -rf ./package mkdir -p package From d18910f1f43ef8aa57cca35f76b751b3fc80bb3a Mon Sep 17 00:00:00 2001 From: Michael De Luca <35537333+deluca-mike@users.noreply.github.com> Date: Mon, 26 Jun 2023 06:08:30 -0400 Subject: [PATCH 37/47] chore: Maple prefix naming (SC-12781) (#305) --- contracts/MapleLoanFeeManager.sol | 1 + .../{Refinancer.sol => MapleRefinancer.sol} | 18 +- .../{IRefinancer.sol => IMapleRefinancer.sol} | 4 +- tests/MapleLoanFeeManager.t.sol | 14 +- tests/MapleLoanLogic.t.sol | 12 +- ...nancer.t.sol => MapleLoanRefinancer.t.sol} | 254 +++++++++--------- tests/Payments.t.sol | 12 +- 7 files changed, 158 insertions(+), 157 deletions(-) rename contracts/{Refinancer.sol => MapleRefinancer.sol} (58%) rename contracts/interfaces/{IRefinancer.sol => IMapleRefinancer.sol} (98%) rename tests/{Refinancer.t.sol => MapleLoanRefinancer.t.sol} (98%) diff --git a/contracts/MapleLoanFeeManager.sol b/contracts/MapleLoanFeeManager.sol index 2d6cd98..04ec698 100644 --- a/contracts/MapleLoanFeeManager.sol +++ b/contracts/MapleLoanFeeManager.sol @@ -21,6 +21,7 @@ import { IMapleLoanFeeManager } from "./interfaces/IMapleLoanFeeManager.sol"; ██║ ╚═╝ ██║██║ ██║██║ ███████╗███████╗ ███████╗╚██████╔╝██║ ██║██║ ╚████║ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ + ███████╗███████╗███████╗ ███╗ ███╗ █████╗ ███╗ ██╗ █████╗ ██████╗ ███████╗██████╗ ██╔════╝██╔════╝██╔════╝ ████╗ ████║██╔══██╗████╗ ██║██╔══██╗██╔════╝ ██╔════╝██╔══██╗ █████╗ █████╗ █████╗ ██╔████╔██║███████║██╔██╗ ██║███████║██║ ███╗█████╗ ██████╔╝ diff --git a/contracts/Refinancer.sol b/contracts/MapleRefinancer.sol similarity index 58% rename from contracts/Refinancer.sol rename to contracts/MapleRefinancer.sol index 49f70f9..e24221d 100644 --- a/contracts/Refinancer.sol +++ b/contracts/MapleRefinancer.sol @@ -4,23 +4,23 @@ pragma solidity 0.8.7; import { IERC20 } from "../modules/erc20/contracts/interfaces/IERC20.sol"; import { IMapleLoanFeeManager } from "./interfaces/IMapleLoanFeeManager.sol"; -import { IRefinancer } from "./interfaces/IRefinancer.sol"; +import { IMapleRefinancer } from "./interfaces/IMapleRefinancer.sol"; import { MapleLoanStorage } from "./MapleLoanStorage.sol"; /* - ██████╗ ███████╗███████╗██╗███╗ ██╗ █████╗ ███╗ ██╗ ██████╗███████╗██████╗ - ██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔══██╗████╗ ██║██╔════╝██╔════╝██╔══██╗ - ██████╔╝█████╗ █████╗ ██║██╔██╗ ██║███████║██╔██╗ ██║██║ █████╗ ██████╔╝ - ██╔══██╗██╔══╝ ██╔══╝ ██║██║╚██╗██║██╔══██║██║╚██╗██║██║ ██╔══╝ ██╔══██╗ - ██║ ██║███████╗██║ ██║██║ ╚████║██║ ██║██║ ╚████║╚██████╗███████╗██║ ██║ - ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝╚══════╝╚═╝ ╚═╝ + ███╗ ███╗ █████╗ ██████╗ ██╗ ███████╗ ██████╗ ███████╗███████╗██╗███╗ ██╗ █████╗ ███╗ ██╗ ██████╗███████╗██████╗ + ████╗ ████║██╔══██╗██╔══██╗██║ ██╔════╝ ██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔══██╗████╗ ██║██╔════╝██╔════╝██╔══██╗ + ██╔████╔██║███████║██████╔╝██║ █████╗ ██████╔╝█████╗ █████╗ ██║██╔██╗ ██║███████║██╔██╗ ██║██║ █████╗ ██████╔╝ + ██║╚██╔╝██║██╔══██║██╔═══╝ ██║ ██╔══╝ ██╔══██╗██╔══╝ ██╔══╝ ██║██║╚██╗██║██╔══██║██║╚██╗██║██║ ██╔══╝ ██╔══██╗ + ██║ ╚═╝ ██║██║ ██║██║ ███████╗███████╗ ██║ ██║███████╗██║ ██║██║ ╚████║██║ ██║██║ ╚████║╚██████╗███████╗██║ ██║ + ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝╚══════╝╚═╝ ╚═╝ */ -/// @title Refinancer uses storage from a MapleLoan defined by MapleLoanStorage. -contract Refinancer is IRefinancer, MapleLoanStorage { +/// @title MapleRefinancer uses storage from a MapleLoan defined by MapleLoanStorage. +contract MapleRefinancer is IMapleRefinancer, MapleLoanStorage { function increasePrincipal(uint256 amount_) external override { // Cannot under-fund the principal increase, but over-funding results in additional funds left unaccounted for. diff --git a/contracts/interfaces/IRefinancer.sol b/contracts/interfaces/IMapleRefinancer.sol similarity index 98% rename from contracts/interfaces/IRefinancer.sol rename to contracts/interfaces/IMapleRefinancer.sol index 09b3739..b625361 100644 --- a/contracts/interfaces/IRefinancer.sol +++ b/contracts/interfaces/IMapleRefinancer.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.7; -/// @title Refinancer uses storage from Maple Loan. -interface IRefinancer { +/// @title MapleRefinancer uses storage from Maple Loan. +interface IMapleRefinancer { /**************************************************************************************************************************************/ /*** Events ***/ diff --git a/tests/MapleLoanFeeManager.t.sol b/tests/MapleLoanFeeManager.t.sol index 562d82e..74d0225 100644 --- a/tests/MapleLoanFeeManager.t.sol +++ b/tests/MapleLoanFeeManager.t.sol @@ -11,7 +11,7 @@ import { MapleLoanFeeManager } from "../contracts/MapleLoanFeeManager.sol"; import { MockGlobals, MockLoanManager, MockPoolManager } from "./mocks/Mocks.sol"; -contract FeeManagerBase is TestUtils { +contract TestBase is TestUtils { address internal BORROWER = address(new Address()); address internal GOVERNOR = address(new Address()); @@ -114,7 +114,7 @@ contract FeeManagerBase is TestUtils { } -contract PayClosingFeesTests is FeeManagerBase { +contract PayClosingFeesTests is TestBase { MapleLoan loan; @@ -164,7 +164,7 @@ contract PayClosingFeesTests is FeeManagerBase { } -contract PayOriginationFeesTests is FeeManagerBase { +contract PayOriginationFeesTests is TestBase { MapleLoan loan; @@ -224,7 +224,7 @@ contract PayOriginationFeesTests is FeeManagerBase { } -contract PayServiceFeesTests is FeeManagerBase { +contract PayServiceFeesTests is TestBase { MapleLoan loan; @@ -291,7 +291,7 @@ contract PayServiceFeesTests is FeeManagerBase { } -contract UpdatePlatformServiceFeeTests is FeeManagerBase { +contract UpdatePlatformServiceFeeTests is TestBase { function test_updatePlatformServiceFee() external { address loan1 = _createLoan( @@ -343,7 +343,7 @@ contract UpdatePlatformServiceFeeTests is FeeManagerBase { } -contract UpdateFeeTerms_SetterTests is FeeManagerBase { +contract UpdateDelegateFeeTermsTests is TestBase { function test_updateDelegateFeeTerms() external { address someContract = address(new Address()); @@ -360,7 +360,7 @@ contract UpdateFeeTerms_SetterTests is FeeManagerBase { } -contract FeeManager_Getters is FeeManagerBase { +contract GetterTests is TestBase { function setUp() public override { super.setUp(); diff --git a/tests/MapleLoanLogic.t.sol b/tests/MapleLoanLogic.t.sol index d20da44..0a65ea1 100644 --- a/tests/MapleLoanLogic.t.sol +++ b/tests/MapleLoanLogic.t.sol @@ -8,7 +8,7 @@ import { ConstructableMapleLoan, MapleLoanHarness } from "./harnesses/MapleLoanH import { MockGlobals, MockFactory, MockFeeManager, MockLoanManager, RevertingERC20 } from "./mocks/Mocks.sol"; -import { Refinancer } from "../contracts/Refinancer.sol"; +import { MapleRefinancer } from "../contracts/MapleRefinancer.sol"; contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { @@ -31,7 +31,7 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { MockFeeManager internal feeManager; MockGlobals internal globals; MockLoanManager internal lender; - Refinancer internal refinancer; + MapleRefinancer internal refinancer; function setUp() external { collateralAsset = new MockERC20("Token0", "T0", 0); @@ -39,7 +39,7 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { fundsAsset = new MockERC20("Token1", "T1", 0); globals = new MockGlobals(governor); lender = new MockLoanManager(); - refinancer = new Refinancer(); + refinancer = new MapleRefinancer(); factory = new MockFactory(address(globals)); @@ -2387,14 +2387,14 @@ contract MapleLoanLogic_RejectNewTermsTests is TestUtils { MapleLoanHarness loan; MockFactory factory; MockGlobals globals; - Refinancer refinancer; + MapleRefinancer refinancer; address borrower = address(new Address()); function setUp() external { globals = new MockGlobals(address(0)); loan = new MapleLoanHarness(); - refinancer = new Refinancer(); + refinancer = new MapleRefinancer(); factory = new MockFactory(address(globals)); @@ -2424,7 +2424,7 @@ contract MapleLoanLogic_RejectNewTermsTests is TestUtils { vm.startPrank(borrower); loan.proposeNewTerms(address(refinancer), deadline, calls); - address anotherRefinancer = address(new Refinancer()); + address anotherRefinancer = address(new MapleRefinancer()); vm.expectRevert("ML:RNT:COMMITMENT_MISMATCH"); loan.rejectNewTerms(anotherRefinancer, deadline, calls); } diff --git a/tests/Refinancer.t.sol b/tests/MapleLoanRefinancer.t.sol similarity index 98% rename from tests/Refinancer.t.sol rename to tests/MapleLoanRefinancer.t.sol index 3fda891..9603f64 100644 --- a/tests/Refinancer.t.sol +++ b/tests/MapleLoanRefinancer.t.sol @@ -5,7 +5,7 @@ import { Address, TestUtils } from "../modules/contract-test-utils/contracts/tes import { MockERC20 } from "../modules/erc20/contracts/test/mocks/MockERC20.sol"; import { MapleLoanFeeManager } from "../contracts/MapleLoanFeeManager.sol"; -import { Refinancer } from "../contracts/Refinancer.sol"; +import { MapleRefinancer } from "../contracts/MapleRefinancer.sol"; import { ConstructableMapleLoan } from "./harnesses/MapleLoanHarnesses.sol"; @@ -18,7 +18,7 @@ import { } from "./mocks/Mocks.sol"; // Helper contract with common functionality -contract RefinancerTestBase is TestUtils { +contract TestBase is TestUtils { // Loan Boundaries uint256 internal constant MAX_PAYMENTS = 20; @@ -33,7 +33,7 @@ contract RefinancerTestBase is TestUtils { MockFeeManager internal feeManager; MockGlobals internal globals; MockLoanManager internal lender; - Refinancer internal refinancer; + MapleRefinancer internal refinancer; address internal borrower = address(new Address()); address internal governor = address(new Address()); @@ -42,7 +42,7 @@ contract RefinancerTestBase is TestUtils { feeManager = new MockFeeManager(); globals = new MockGlobals(governor); lender = new MockLoanManager(); - refinancer = new Refinancer(); + refinancer = new MapleRefinancer(); token = new MockERC20("Test", "TST", 0); factory = new MockFactory(address(globals)); @@ -106,7 +106,115 @@ contract RefinancerTestBase is TestUtils { } -contract RefinancerCollateralRequiredTests is RefinancerTestBase { +contract MapleLoanRefinancerMiscellaneousTests is TestBase { + + function test_refinance_invalidRefinancer() external { + setUpOngoingLoan(1, 1, 1, 12 hours, 1, 1, 1); + + bytes[] memory data = new bytes[](1); + data[0] = abi.encodeWithSignature("setEndingPrincipal(uint256)", 0); + + // Executing refinance + vm.prank(borrower); + loan.proposeNewTerms(address(1), block.timestamp, data); + + vm.prank(address(lender)); + vm.expectRevert("ML:ANT:INVALID_REFINANCER"); + loan.acceptNewTerms(address(1), block.timestamp, data); + } + +} + +contract MapleLoanRefinancerMultipleParameterTests is TestBase { + + function test_refinance_multipleParameters( + uint256 principalRequested_, + uint256 collateralRequired_, + uint256 endingPrincipal_, + uint256 gracePeriod_, + uint256 interestRate_, + uint256 paymentInterval_, + uint256 paymentsRemaining_, + uint256 deadline_ + ) + external + { + principalRequested_ = constrictToRange(principalRequested_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT - MIN_TOKEN_AMOUNT); + collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); + endingPrincipal_ = constrictToRange(endingPrincipal_, principalRequested_ / 2, principalRequested_); + gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); + interestRate_ = constrictToRange(interestRate_, 10_000, MAX_RATE / 2); // Giving enough room to increase the interest Rate + paymentInterval_ = constrictToRange(paymentInterval_, 30 days, MAX_TIME / 2); + paymentsRemaining_ = constrictToRange(paymentsRemaining_, 3, MAX_PAYMENTS); + + setUpOngoingLoan( + principalRequested_, + collateralRequired_, + endingPrincipal_, + gracePeriod_, + interestRate_, + paymentInterval_, + paymentsRemaining_ + ); + + deadline_ = constrictToRange(deadline_, block.timestamp, type(uint256).max); + + // Asserting state + assertEq(loan.collateralRequired(), collateralRequired_); + assertEq(loan.endingPrincipal(), endingPrincipal_); + assertEq(loan.gracePeriod(), gracePeriod_); + assertEq(loan.interestRate(), interestRate_); + assertEq(loan.paymentInterval(), paymentInterval_); + assertEq(loan.principalRequested(), principalRequested_); + + uint256 currentPrincipal = loan.principal(); + + // Defining refinance terms + uint256 newCollateralRequired_ = MIN_TOKEN_AMOUNT; + uint256 newEndingPrincipal_ = 0; + uint256 newGracePeriod_ = 95; + uint256 newInterestRate_ = 0; + uint256 newPaymentInterval_ = 15 days; + uint256 principalIncrease_ = MIN_TOKEN_AMOUNT; + + bytes[] memory data = new bytes[](6); + data[0] = abi.encodeWithSignature("setCollateralRequired(uint256)", newCollateralRequired_); + data[1] = abi.encodeWithSignature("setEndingPrincipal(uint256)", newEndingPrincipal_); + data[2] = abi.encodeWithSignature("setGracePeriod(uint256)", newGracePeriod_); + data[3] = abi.encodeWithSignature("setInterestRate(uint256)", newInterestRate_); + data[4] = abi.encodeWithSignature("setPaymentInterval(uint256)", newPaymentInterval_); + data[5] = abi.encodeWithSignature("increasePrincipal(uint256)", principalIncrease_); + + // Executing refinance + vm.prank(borrower); + loan.proposeNewTerms(address(refinancer), deadline_, data); + + uint256 currentCollateral = loan.collateral(); + + if (newCollateralRequired_ > currentCollateral) { + token.mint(address(loan), newCollateralRequired_ - currentCollateral); + loan.postCollateral(0); + } + + token.mint(address(loan), principalIncrease_); + + uint256 expectedRefinanceInterest = loan.getRefinanceInterest(block.timestamp); + + vm.prank(address(lender)); + loan.acceptNewTerms(address(refinancer), deadline_, data); + + assertEq(loan.collateralRequired(), newCollateralRequired_); + assertEq(loan.endingPrincipal(), newEndingPrincipal_); + assertEq(loan.gracePeriod(), newGracePeriod_); + assertEq(loan.interestRate(), newInterestRate_); + assertEq(loan.paymentInterval(), newPaymentInterval_); + assertEq(loan.principal(), currentPrincipal + principalIncrease_); + assertEq(loan.refinanceInterest(), expectedRefinanceInterest); + } + +} + +contract RefinanceCollateralRequiredTests is TestBase { function test_refinance_collateralRequired( uint256 principalRequested_, @@ -183,7 +291,7 @@ contract RefinancerCollateralRequiredTests is RefinancerTestBase { } -contract RefinancerDeadlineTests is RefinancerTestBase { +contract RefinanceDeadlineTests is TestBase { // Using payments interval since it's a rather easy refinance with no need to handle principal/collateral assets. function test_refinance_afterDeadline( @@ -256,7 +364,7 @@ contract RefinancerDeadlineTests is RefinancerTestBase { } -contract RefinancerEndingPrincipalTests is RefinancerTestBase { +contract RefinanceEndingPrincipalTests is TestBase { function test_refinance_endingPrincipal_interestOnlyToAmortized( uint256 principalRequested_, @@ -413,7 +521,7 @@ contract RefinancerEndingPrincipalTests is RefinancerTestBase { } -contract RefinancerFeeTests is RefinancerTestBase { +contract RefinanceFeeTests is TestBase { function test_refinance_closingRate( uint256 principalRequested_, @@ -564,7 +672,7 @@ contract RefinancerFeeTests is RefinancerTestBase { } -contract RefinancerGracePeriodTests is RefinancerTestBase { +contract RefinanceGracePeriodTests is TestBase { function test_refinance_gracePeriod( uint256 principalRequested_, @@ -616,7 +724,7 @@ contract RefinancerGracePeriodTests is RefinancerTestBase { } -contract RefinancerInterestRateTests is RefinancerTestBase { +contract RefinanceInterestRateTests is TestBase { function test_refinance_interestRate( uint256 principalRequested_, @@ -668,7 +776,7 @@ contract RefinancerInterestRateTests is RefinancerTestBase { } -contract RefinancerInterestTests is TestUtils { +contract RefinanceInterestTests is TestUtils { // Loan Boundaries uint256 internal constant MAX_PAYMENTS = 20; @@ -685,7 +793,7 @@ contract RefinancerInterestTests is TestUtils { MockFeeManager internal feeManager; MockGlobals internal globals; MockLoanManager internal lender; - Refinancer internal refinancer; + MapleRefinancer internal refinancer; address internal borrower = address(new Address()); address internal governor = address(new Address()); @@ -694,7 +802,7 @@ contract RefinancerInterestTests is TestUtils { feeManager = new MockFeeManager(); globals = new MockGlobals(address(governor)); lender = new MockLoanManager(); - refinancer = new Refinancer(); + refinancer = new MapleRefinancer(); token = new MockERC20("Test", "TST", 0); factory = new MockFactory(address(globals)); @@ -817,115 +925,7 @@ contract RefinancerInterestTests is TestUtils { } -contract RefinancerMiscellaneousTests is RefinancerTestBase { - - function test_refinance_invalidRefinancer() external { - setUpOngoingLoan(1, 1, 1, 12 hours, 1, 1, 1); - - bytes[] memory data = new bytes[](1); - data[0] = abi.encodeWithSignature("setEndingPrincipal(uint256)", 0); - - // Executing refinance - vm.prank(borrower); - loan.proposeNewTerms(address(1), block.timestamp, data); - - vm.prank(address(lender)); - vm.expectRevert("ML:ANT:INVALID_REFINANCER"); - loan.acceptNewTerms(address(1), block.timestamp, data); - } - -} - -contract RefinancerMultipleParameterTests is RefinancerTestBase { - - function test_refinance_multipleParameters( - uint256 principalRequested_, - uint256 collateralRequired_, - uint256 endingPrincipal_, - uint256 gracePeriod_, - uint256 interestRate_, - uint256 paymentInterval_, - uint256 paymentsRemaining_, - uint256 deadline_ - ) - external - { - principalRequested_ = constrictToRange(principalRequested_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT - MIN_TOKEN_AMOUNT); - collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); - endingPrincipal_ = constrictToRange(endingPrincipal_, principalRequested_ / 2, principalRequested_); - gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); - interestRate_ = constrictToRange(interestRate_, 10_000, MAX_RATE / 2); // Giving enough room to increase the interest Rate - paymentInterval_ = constrictToRange(paymentInterval_, 30 days, MAX_TIME / 2); - paymentsRemaining_ = constrictToRange(paymentsRemaining_, 3, MAX_PAYMENTS); - - setUpOngoingLoan( - principalRequested_, - collateralRequired_, - endingPrincipal_, - gracePeriod_, - interestRate_, - paymentInterval_, - paymentsRemaining_ - ); - - deadline_ = constrictToRange(deadline_, block.timestamp, type(uint256).max); - - // Asserting state - assertEq(loan.collateralRequired(), collateralRequired_); - assertEq(loan.endingPrincipal(), endingPrincipal_); - assertEq(loan.gracePeriod(), gracePeriod_); - assertEq(loan.interestRate(), interestRate_); - assertEq(loan.paymentInterval(), paymentInterval_); - assertEq(loan.principalRequested(), principalRequested_); - - uint256 currentPrincipal = loan.principal(); - - // Defining refinance terms - uint256 newCollateralRequired_ = MIN_TOKEN_AMOUNT; - uint256 newEndingPrincipal_ = 0; - uint256 newGracePeriod_ = 95; - uint256 newInterestRate_ = 0; - uint256 newPaymentInterval_ = 15 days; - uint256 principalIncrease_ = MIN_TOKEN_AMOUNT; - - bytes[] memory data = new bytes[](6); - data[0] = abi.encodeWithSignature("setCollateralRequired(uint256)", newCollateralRequired_); - data[1] = abi.encodeWithSignature("setEndingPrincipal(uint256)", newEndingPrincipal_); - data[2] = abi.encodeWithSignature("setGracePeriod(uint256)", newGracePeriod_); - data[3] = abi.encodeWithSignature("setInterestRate(uint256)", newInterestRate_); - data[4] = abi.encodeWithSignature("setPaymentInterval(uint256)", newPaymentInterval_); - data[5] = abi.encodeWithSignature("increasePrincipal(uint256)", principalIncrease_); - - // Executing refinance - vm.prank(borrower); - loan.proposeNewTerms(address(refinancer), deadline_, data); - - uint256 currentCollateral = loan.collateral(); - - if (newCollateralRequired_ > currentCollateral) { - token.mint(address(loan), newCollateralRequired_ - currentCollateral); - loan.postCollateral(0); - } - - token.mint(address(loan), principalIncrease_); - - uint256 expectedRefinanceInterest = loan.getRefinanceInterest(block.timestamp); - - vm.prank(address(lender)); - loan.acceptNewTerms(address(refinancer), deadline_, data); - - assertEq(loan.collateralRequired(), newCollateralRequired_); - assertEq(loan.endingPrincipal(), newEndingPrincipal_); - assertEq(loan.gracePeriod(), newGracePeriod_); - assertEq(loan.interestRate(), newInterestRate_); - assertEq(loan.paymentInterval(), newPaymentInterval_); - assertEq(loan.principal(), currentPrincipal + principalIncrease_); - assertEq(loan.refinanceInterest(), expectedRefinanceInterest); - } - -} - -contract RefinancerPaymentIntervalTests is RefinancerTestBase { +contract RefinancePaymentIntervalTests is TestBase { function test_refinance_paymentInterval_zeroAmount() external { setUpOngoingLoan(MIN_TOKEN_AMOUNT, 0, MIN_TOKEN_AMOUNT, 12 hours, 0.1e6, 30 days, 6); @@ -999,7 +999,7 @@ contract RefinancerPaymentIntervalTests is RefinancerTestBase { } -contract RefinancerPaymentsRemainingTests is RefinancerTestBase { +contract RefinancePaymentsRemainingTests is TestBase { function test_refinance_paymentRemaining_zeroAmount() external { setUpOngoingLoan(MIN_TOKEN_AMOUNT, 0, MIN_TOKEN_AMOUNT, 12 hours, 0.1e6, 30 days, 6); @@ -1075,7 +1075,7 @@ contract RefinancerPaymentsRemainingTests is RefinancerTestBase { } -contract RefinancerPrincipalRequestedTests is RefinancerTestBase { +contract RefinancePrincipalRequestedTests is TestBase { // Saving as storage variables to avoid stack too deep uint256 initialDrawableFunds; @@ -1214,7 +1214,7 @@ contract RefinancerPrincipalRequestedTests is RefinancerTestBase { } -// Not Using RefinancerTestBase due to the need to use Mocks for the +// Not Using TestBase due to the need to use Mocks for the contract RefinancingFeesTerms is TestUtils { address internal POOL_DELEGATE = address(new Address()); @@ -1235,7 +1235,7 @@ contract RefinancingFeesTerms is TestUtils { MockGlobals internal globals; MockLoanManager internal lender; MockPoolManager internal poolManager; - Refinancer internal refinancer; + MapleRefinancer internal refinancer; address internal borrower = address(new Address()); address internal governor = address(new Address()); @@ -1244,7 +1244,7 @@ contract RefinancingFeesTerms is TestUtils { lender = new MockLoanManager(); globals = new MockGlobals(governor); poolManager = new MockPoolManager(address(POOL_DELEGATE)); - refinancer = new Refinancer(); + refinancer = new MapleRefinancer(); factory = new MockFactory(address(globals)); feeManager = new MapleLoanFeeManager(address(globals)); diff --git a/tests/Payments.t.sol b/tests/Payments.t.sol index de221c9..696fe31 100644 --- a/tests/Payments.t.sol +++ b/tests/Payments.t.sol @@ -11,7 +11,7 @@ import { MapleLoanInitializer } from "../contracts/MapleLoanInitializer.sol"; import { MockGlobals, MockLoanManager, MockPoolManager } from "./mocks/Mocks.sol"; -contract MapleLoanPaymentsTestBase is TestUtils { +contract TestBase is TestUtils { uint256 internal start; @@ -210,7 +210,7 @@ contract MapleLoanPaymentsTestBase is TestUtils { } -contract ClosingTests is MapleLoanPaymentsTestBase { +contract ClosingTests is TestBase { function test_payments_closing_flatRate_case1() external { /****************************************/ @@ -473,7 +473,7 @@ contract ClosingTests is MapleLoanPaymentsTestBase { } -contract FullyAmortizedPaymentsTests is MapleLoanPaymentsTestBase { +contract FullyAmortizedPaymentsTests is TestBase { function test_payments_fullyAmortized_case1() external { /****************************************/ @@ -677,7 +677,7 @@ contract FullyAmortizedPaymentsTests is MapleLoanPaymentsTestBase { } -contract InterestOnlyPaymentsTests is MapleLoanPaymentsTestBase { +contract InterestOnlyPaymentsTests is TestBase { function test_payments_interestOnly_case1() external { /****************************************/ @@ -877,7 +877,7 @@ contract InterestOnlyPaymentsTests is MapleLoanPaymentsTestBase { } -contract LateRepaymentsTests is MapleLoanPaymentsTestBase { +contract LateRepaymentsTests is TestBase { function test_payments_lateRepayment_flatRate_case1() external { /****************************************/ @@ -1524,7 +1524,7 @@ contract LateRepaymentsTests is MapleLoanPaymentsTestBase { } -contract PartiallyAmortizedPaymentsTests is MapleLoanPaymentsTestBase { +contract PartiallyAmortizedPaymentsTests is TestBase { function test_payments_partiallyAmortized_case1() external { /****************************************/ From 5b26eac66505c61b0f515f475945bed7d472c1ec Mon Sep 17 00:00:00 2001 From: Preethi Senthil Date: Wed, 13 Sep 2023 03:44:50 -0700 Subject: [PATCH 38/47] feat: Update factory to include canDeploy (SC-13704) (#306) * feat: update factory to include canDeploy (sc-13704) * fix: add err msg --------- Co-authored-by: 0xfarhaan <59924029+0xfarhaan@users.noreply.github.com> --- contracts/MapleLoanFactory.sol | 3 +++ contracts/interfaces/Interfaces.sol | 2 ++ tests/InitializerAndMigrator.t.sol | 1 + tests/MapleLoanFactory.t.sol | 31 ++++++++++++++++++++++++++++- tests/MapleLoanFeeManager.t.sol | 1 + tests/mocks/Mocks.sol | 9 +++++++++ 6 files changed, 46 insertions(+), 1 deletion(-) diff --git a/contracts/MapleLoanFactory.sol b/contracts/MapleLoanFactory.sol index da8189a..3f888b8 100644 --- a/contracts/MapleLoanFactory.sol +++ b/contracts/MapleLoanFactory.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.7; import { IMapleProxyFactory, MapleProxyFactory } from "../modules/maple-proxy-factory/contracts/MapleProxyFactory.sol"; import { IMapleLoanFactory } from "./interfaces/IMapleLoanFactory.sol"; +import { IGlobalsLike } from "./interfaces/Interfaces.sol"; /// @title MapleLoanFactory deploys Loan instances. contract MapleLoanFactory is IMapleLoanFactory, MapleProxyFactory { @@ -18,6 +19,8 @@ contract MapleLoanFactory is IMapleLoanFactory, MapleProxyFactory { address instance_ ) { + require(IGlobalsLike(mapleGlobals).canDeploy(msg.sender), "MLF:CI:CANNOT_DEPLOY"); + isLoan[instance_ = super.createInstance(arguments_, salt_)] = true; } diff --git a/contracts/interfaces/Interfaces.sol b/contracts/interfaces/Interfaces.sol index 3c3d829..bfc0545 100644 --- a/contracts/interfaces/Interfaces.sol +++ b/contracts/interfaces/Interfaces.sol @@ -23,6 +23,8 @@ interface IGlobalsLike { function securityAdmin() external view returns (address securityAdmin_); + function canDeploy(address caller_) external view returns (bool canDeploy_); + } interface ILenderLike { diff --git a/tests/InitializerAndMigrator.t.sol b/tests/InitializerAndMigrator.t.sol index b4d933c..5c598c8 100644 --- a/tests/InitializerAndMigrator.t.sol +++ b/tests/InitializerAndMigrator.t.sol @@ -47,6 +47,7 @@ contract MapleLoanInitializerAndMigratorTests is TestUtils { globals.setValidCollateralAsset(address(asset), true); globals.setValidPoolAsset(address(asset), true); globals.__setIsInstanceOf(true); + globals.__setCanDeploy(true); globals.__setSecurityAdmin(securityAdmin); vm.startPrank(governor); diff --git a/tests/MapleLoanFactory.t.sol b/tests/MapleLoanFactory.t.sol index 67e1dee..c4faba6 100644 --- a/tests/MapleLoanFactory.t.sol +++ b/tests/MapleLoanFactory.t.sol @@ -41,6 +41,7 @@ contract MapleLoanFactoryTest is TestUtils { globals.setValidPoolAsset(address(1), true); globals.__setIsInstanceOf(true); + globals.__setCanDeploy(true); vm.startPrank(governor); factory.registerImplementation(1, implementation, initializer); @@ -145,7 +146,7 @@ contract MapleLoanFactoryTest is TestUtils { uint256[3] memory amounts = [uint256(1), uint256(1), uint256(0)]; uint256[4] memory rates = [uint256(0), uint256(0), uint256(0), uint256(0)]; uint256[2] memory fees = [uint256(0), uint256(0)]; - + bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( address(1), address(lender), @@ -249,6 +250,34 @@ contract MapleLoanFactoryTest is TestUtils { factory.createInstance(arguments, salt); } + function test_createInstance_invalidCaller() external { + address[2] memory assets = [address(1), address(1)]; + uint256[3] memory termDetails = [uint256(12 hours), uint256(1), uint256(1)]; + uint256[3] memory amounts = [uint256(1), uint256(1), uint256(0)]; + uint256[4] memory rates = [uint256(0), uint256(0), uint256(0), uint256(0)]; + uint256[2] memory fees = [uint256(0), uint256(0)]; + + bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( + address(1), + address(lender), + address(feeManager), + assets, + termDetails, + amounts, + rates, + fees + ); + + globals.__setCanDeploy(false); + + vm.expectRevert("MLF:CI:CANNOT_DEPLOY"); + factory.createInstance(arguments, "SALT"); + + globals.__setCanDeploy(true); + + factory.createInstance(arguments, "SALT"); + } + function test_createInstance(bytes32 salt_) external { address[2] memory assets = [address(1), address(1)]; uint256[3] memory termDetails = [uint256(12 hours), uint256(1), uint256(1)]; diff --git a/tests/MapleLoanFeeManager.t.sol b/tests/MapleLoanFeeManager.t.sol index 74d0225..c60988a 100644 --- a/tests/MapleLoanFeeManager.t.sol +++ b/tests/MapleLoanFeeManager.t.sol @@ -59,6 +59,7 @@ contract TestBase is TestUtils { globals.setMapleTreasury(TREASURY); globals.__setIsInstanceOf(true); + globals.__setCanDeploy(true); globals.setValidBorrower(BORROWER, true); globals.setValidCollateralAsset(address(collateralAsset), true); globals.setValidPoolAsset(address(fundsAsset), true); diff --git a/tests/mocks/Mocks.sol b/tests/mocks/Mocks.sol index ab81128..01cc8e3 100644 --- a/tests/mocks/Mocks.sol +++ b/tests/mocks/Mocks.sol @@ -21,6 +21,7 @@ contract MockGlobals { mapping(address => bool) public isPoolAsset; bool internal _isInstanceOf; + bool internal _canDeploy; constructor (address governor_) { governor = governor_; @@ -34,6 +35,10 @@ contract MockGlobals { return _isInstanceOf; } + function canDeploy(address) external view returns (bool) { + return _canDeploy; + } + function setGovernor(address governor_) external { governor = governor_; } @@ -74,6 +79,10 @@ contract MockGlobals { _isInstanceOf = isInstanceOf_; } + function __setCanDeploy(bool canDeploy_) external { + _canDeploy = canDeploy_; + } + function __setSecurityAdmin(address securityAdmin_) external { securityAdmin = securityAdmin_; } From 9e55f4708957ab40caf9c43834bec683176bbf34 Mon Sep 17 00:00:00 2001 From: Preethi Senthil Date: Wed, 20 Sep 2023 07:38:22 -0700 Subject: [PATCH 39/47] feat: Create loan migrator to update factory on loan (SC-13705) (#307) * feat: create loan migrator to update factory on loan (SC-13705) * feat: add isLoan update function (SC-13705) * feat: unit tests (SC-13705) * fix: overload isLoan (SC-13705) * fix: test file name (SC-13705) * fix: comments (SC-13705) * refactor: update tests and formatting on contracts * fix: comment * chore: address PR comments --------- Co-authored-by: 0xfarhaan <59924029+0xfarhaan@users.noreply.github.com> --- contracts/MapleLoanFactory.sol | 15 ++- contracts/MapleLoanV502Migrator.sol | 16 +++ contracts/interfaces/IMapleLoanFactory.sol | 8 +- tests/InitializerAndMigrator.t.sol | 2 +- tests/MapleLoanFactory.t.sol | 48 ++++++++- tests/MapleLoanFeeManager.t.sol | 2 +- tests/MapleLoanV502Migrator.t.sol | 111 +++++++++++++++++++++ tests/mocks/Mocks.sol | 9 ++ 8 files changed, 200 insertions(+), 11 deletions(-) create mode 100644 contracts/MapleLoanV502Migrator.sol create mode 100644 tests/MapleLoanV502Migrator.t.sol diff --git a/contracts/MapleLoanFactory.sol b/contracts/MapleLoanFactory.sol index 3f888b8..4c1f0a0 100644 --- a/contracts/MapleLoanFactory.sol +++ b/contracts/MapleLoanFactory.sol @@ -9,10 +9,13 @@ import { IGlobalsLike } from "./interfaces/Interfaces.sol"; /// @title MapleLoanFactory deploys Loan instances. contract MapleLoanFactory is IMapleLoanFactory, MapleProxyFactory { - mapping(address => bool) public override isLoan; + address public immutable override oldFactory; - /// @param mapleGlobals_ The address of a Maple Globals contract. - constructor(address mapleGlobals_) MapleProxyFactory(mapleGlobals_) {} + mapping(address => bool) internal _isLoan; + + constructor(address mapleGlobals_, address oldFactory_) MapleProxyFactory(mapleGlobals_) { + oldFactory = oldFactory_; + } function createInstance(bytes calldata arguments_, bytes32 salt_) override(IMapleProxyFactory, MapleProxyFactory) public returns ( @@ -21,7 +24,11 @@ contract MapleLoanFactory is IMapleLoanFactory, MapleProxyFactory { { require(IGlobalsLike(mapleGlobals).canDeploy(msg.sender), "MLF:CI:CANNOT_DEPLOY"); - isLoan[instance_ = super.createInstance(arguments_, salt_)] = true; + _isLoan[instance_ = super.createInstance(arguments_, salt_)] = true; + } + + function isLoan(address instance_) external view override returns (bool) { + return (_isLoan[instance_] || IMapleLoanFactory(oldFactory).isLoan(instance_)); } } diff --git a/contracts/MapleLoanV502Migrator.sol b/contracts/MapleLoanV502Migrator.sol new file mode 100644 index 0000000..d3bbbee --- /dev/null +++ b/contracts/MapleLoanV502Migrator.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.7; + +import { ProxiedInternals } from "../modules/maple-proxy-factory/modules/proxy-factory/contracts/ProxiedInternals.sol"; + +import { MapleLoanStorage } from "./MapleLoanStorage.sol"; + +/// @title MapleLoanV502Migrator is to update the factory address for each deployed loan. +contract MapleLoanV502Migrator is ProxiedInternals, MapleLoanStorage { + fallback() external { + ( address factory_ ) = abi.decode(msg.data, (address)); + + _setFactory(factory_); + } + +} diff --git a/contracts/interfaces/IMapleLoanFactory.sol b/contracts/interfaces/IMapleLoanFactory.sol index 2c5c8d3..5e617d8 100644 --- a/contracts/interfaces/IMapleLoanFactory.sol +++ b/contracts/interfaces/IMapleLoanFactory.sol @@ -7,10 +7,16 @@ import { IMapleProxyFactory } from "../../modules/maple-proxy-factory/contracts/ interface IMapleLoanFactory is IMapleProxyFactory { /** - * @dev Whether the proxy is a MapleLoan deployed by this factory. + * @dev Whether the proxy is a MapleLoan deployed by this factory or the old factory. * @param proxy_ The address of the proxy contract. * @return isLoan_ Whether the proxy is a MapleLoan deployed by this factory. */ function isLoan(address proxy_) external view returns (bool isLoan_); + /** + * @dev Returns the address of the old factory. + * @return oldFactory_ The address of the old factory. + */ + function oldFactory() external view returns (address oldFactory_); + } diff --git a/tests/InitializerAndMigrator.t.sol b/tests/InitializerAndMigrator.t.sol index 5c598c8..ceedb61 100644 --- a/tests/InitializerAndMigrator.t.sol +++ b/tests/InitializerAndMigrator.t.sol @@ -38,7 +38,7 @@ contract MapleLoanInitializerAndMigratorTests is TestUtils { lender = new MockLoanManager(); migrator = new MapleLoanV5Migrator(); - factory = new MapleLoanFactory(address(globals)); + factory = new MapleLoanFactory(address(globals), address(0)); loanManagerFactory = MockLoanManagerFactory(lender.factory()); lender.__setFundsAsset(address(asset)); diff --git a/tests/MapleLoanFactory.t.sol b/tests/MapleLoanFactory.t.sol index c4faba6..75e9395 100644 --- a/tests/MapleLoanFactory.t.sol +++ b/tests/MapleLoanFactory.t.sol @@ -7,19 +7,21 @@ import { MapleLoan } from "../contracts/MapleLoan.sol"; import { MapleLoanFactory } from "../contracts/MapleLoanFactory.sol"; import { MapleLoanInitializer } from "../contracts/MapleLoanInitializer.sol"; -import { MockFeeManager, MockGlobals, MockLoanManager, MockLoanManagerFactory } from "./mocks/Mocks.sol"; +import { MockFeeManager, MockGlobals, MockLoanManager, MockLoanManagerFactory, MockLoanFactory } from "./mocks/Mocks.sol"; import { Proxy } from "../modules/maple-proxy-factory/modules/proxy-factory/contracts/Proxy.sol"; contract MapleLoanFactoryTest is TestUtils { + MockLoanFactory internal oldFactory; MapleLoanFactory internal factory; MockFeeManager internal feeManager; MockGlobals internal globals; MockLoanManager internal lender; MockLoanManagerFactory internal loanManagerFactory; - address internal governor = address(new Address()); + address internal governor = address(new Address()); + address internal securityAdmin = address(new Address()); address internal implementation; address internal initializer; @@ -32,7 +34,8 @@ contract MapleLoanFactoryTest is TestUtils { implementation = address(new MapleLoan()); initializer = address(new MapleLoanInitializer()); - factory = new MapleLoanFactory(address(globals)); + oldFactory = new MockLoanFactory(); + factory = new MapleLoanFactory(address(globals), address(oldFactory)); lender.__setFundsAsset(address(1)); @@ -310,8 +313,45 @@ contract MapleLoanFactoryTest is TestUtils { // TODO: Change back to hardcoded address once IPFS hashes can be removed on compilation in Foundry. assertEq(loan, expectedAddress); - assertTrue(!factory.isLoan(address(1))); + assertTrue(!oldFactory.isLoan(address(1))); assertTrue( factory.isLoan(loan)); } + function test_isLoan_withOldFactory() external { + address[2] memory assets = [address(1), address(1)]; + uint256[3] memory termDetails = [uint256(12 hours), uint256(1), uint256(1)]; + uint256[3] memory amounts = [uint256(1), uint256(1), uint256(0)]; + uint256[4] memory rates = [uint256(0), uint256(0), uint256(0), uint256(0)]; + uint256[2] memory fees = [uint256(0), uint256(0)]; + + bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( + address(1), + address(lender), + address(feeManager), + assets, + termDetails, + amounts, + rates, + fees + ); + + // Assert current factory points to the correct old loan factory + assertEq(factory.oldFactory(), address(oldFactory)); + + address oldLoan = address(new Address()); + + oldFactory.__setIsLoan(oldLoan, true); + + assertTrue(oldFactory.isLoan(oldLoan)); + assertTrue(factory.isLoan(oldLoan)); + + address loan1 = factory.createInstance(arguments, keccak256(abi.encodePacked("salt1"))); + address loan2 = factory.createInstance(arguments, keccak256(abi.encodePacked("salt2"))); + + assertTrue(!oldFactory.isLoan(loan1)); + assertTrue( factory.isLoan(loan1)); + assertTrue( factory.isLoan(loan2)); + assertTrue( factory.isLoan(oldLoan)); + } + } diff --git a/tests/MapleLoanFeeManager.t.sol b/tests/MapleLoanFeeManager.t.sol index c60988a..4f0e8eb 100644 --- a/tests/MapleLoanFeeManager.t.sol +++ b/tests/MapleLoanFeeManager.t.sol @@ -48,7 +48,7 @@ contract TestBase is TestUtils { lender.__setFundsAsset(address(fundsAsset)); - factory = new MapleLoanFactory(address(globals)); + factory = new MapleLoanFactory(address(globals), address(0)); feeManager = new MapleLoanFeeManager(address(globals)); lender.__setPoolManager(address(poolManager)); diff --git a/tests/MapleLoanV502Migrator.t.sol b/tests/MapleLoanV502Migrator.t.sol new file mode 100644 index 0000000..6d698d1 --- /dev/null +++ b/tests/MapleLoanV502Migrator.t.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.7; + +import { Address, TestUtils } from "../modules/contract-test-utils/contracts/test.sol"; +import { MockERC20 } from "../modules/erc20/contracts/test/mocks/MockERC20.sol"; + +import { MapleLoan } from "../contracts/MapleLoan.sol"; +import { MapleLoanFactory} from "../contracts/MapleLoanFactory.sol"; +import { MapleLoanInitializer } from "../contracts/MapleLoanInitializer.sol"; +import { MapleLoanV502Migrator } from "../contracts/MapleLoanV502Migrator.sol"; + +import { MockGlobals, MockFeeManager, MockLoanManager, MockLoanManagerFactory } from "./mocks/Mocks.sol"; + +contract MapleLoanV502MigratorTests is TestUtils { + + address internal governor = address(new Address()); + address internal securityAdmin = address(new Address()); + address internal implementation501; + address internal implementation502; + address internal initializer; + + MapleLoan loan501; + MapleLoan loan502; + MapleLoanFactory oldFactory; + MapleLoanFactory newFactory; + MapleLoanV502Migrator migrator; + MockERC20 asset; + MockFeeManager feeManager; + MockGlobals globals; + MockLoanManager lender; + MockLoanManagerFactory loanManagerFactory; + + function setUp() external { + asset = new MockERC20("Asset", "ASSET", 18); + globals = new MockGlobals(governor); + feeManager = new MockFeeManager(); + implementation501 = address(new MapleLoan()); + implementation502 = address(new MapleLoan()); + initializer = address(new MapleLoanInitializer()); + lender = new MockLoanManager(); + migrator = new MapleLoanV502Migrator(); + oldFactory = new MapleLoanFactory(address(globals), address(0)); + + loanManagerFactory = MockLoanManagerFactory(lender.factory()); + + lender.__setFundsAsset(address(asset)); + + globals.setValidBorrower(address(1), true); + globals.setValidCollateralAsset(address(asset), true); + globals.setValidPoolAsset(address(asset), true); + globals.__setIsInstanceOf(true); + globals.__setCanDeploy(true); + globals.__setSecurityAdmin(securityAdmin); + + vm.startPrank(governor); + oldFactory.registerImplementation(501, implementation501, initializer); + oldFactory.setDefaultVersion(501); + oldFactory.registerImplementation(502, implementation502, initializer); + oldFactory.enableUpgradePath(501, 502, address(migrator)); + vm.stopPrank(); + + address[2] memory assets = [address(asset), address(asset)]; + uint256[3] memory termDetails = [uint256(12 hours), uint256(365 days), uint256(1)]; + uint256[3] memory amounts = [uint256(0), uint256(1_000_000e6), uint256(1_000_000e6)]; + uint256[4] memory rates = [uint256(0.1e18), uint256(0.02e18), uint256(0.03e18), uint256(0.04e18)]; + uint256[2] memory fees = [uint256(0), uint256(0)]; + + bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( + address(1), + address(lender), + address(feeManager), + assets, + termDetails, + amounts, + rates, + fees + ); + + loan501 = MapleLoan(oldFactory.createInstance(arguments, "SALT1")); + + asset.mint(address(loan501), 1_000_000e6); + + vm.prank(address(lender)); + loan501.fundLoan(); + + loan502 = MapleLoan(oldFactory.createInstance(arguments, "SALT2")); + } + + function test_migration_factoryChange() external { + assertEq(loan501.factory(), address(oldFactory)); + assertEq(loan501.implementation(), address(implementation501)); + + newFactory = new MapleLoanFactory(address(globals), address(oldFactory)); + + bytes memory arguments = abi.encode(address(newFactory)); + + // Upgrade + vm.prank(securityAdmin); + loan501.upgrade(502, arguments); + + // Set loan502 using loan501 as it is now upgraded. + loan502 = loan501; + + assertEq(loan502.factory(), address(newFactory)); + assertEq(loan502.implementation(), address(implementation502)); + assertEq(loan502.fundsAsset(), address(asset)); + + assertEq(loan502.principal(), 1_000_000e6); + } + +} diff --git a/tests/mocks/Mocks.sol b/tests/mocks/Mocks.sol index 01cc8e3..f98b868 100644 --- a/tests/mocks/Mocks.sol +++ b/tests/mocks/Mocks.sol @@ -271,3 +271,12 @@ contract RevertingERC20 { } } + +contract MockLoanFactory { + mapping(address => bool) public isLoan; + + function __setIsLoan(address loan_, bool isLoan_) external { + isLoan[loan_] = isLoan_; + } + +} From 6e86c75f6d3b3cc611f38c2ff8c915071342c984 Mon Sep 17 00:00:00 2001 From: Vedran Bidin Date: Thu, 19 Oct 2023 13:28:15 +0300 Subject: [PATCH 40/47] fix: Remove migrator and tests (#308) --- contracts/MapleLoanV502Migrator.sol | 16 ---- tests/MapleLoanV502Migrator.t.sol | 111 ---------------------------- 2 files changed, 127 deletions(-) delete mode 100644 contracts/MapleLoanV502Migrator.sol delete mode 100644 tests/MapleLoanV502Migrator.t.sol diff --git a/contracts/MapleLoanV502Migrator.sol b/contracts/MapleLoanV502Migrator.sol deleted file mode 100644 index d3bbbee..0000000 --- a/contracts/MapleLoanV502Migrator.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.7; - -import { ProxiedInternals } from "../modules/maple-proxy-factory/modules/proxy-factory/contracts/ProxiedInternals.sol"; - -import { MapleLoanStorage } from "./MapleLoanStorage.sol"; - -/// @title MapleLoanV502Migrator is to update the factory address for each deployed loan. -contract MapleLoanV502Migrator is ProxiedInternals, MapleLoanStorage { - fallback() external { - ( address factory_ ) = abi.decode(msg.data, (address)); - - _setFactory(factory_); - } - -} diff --git a/tests/MapleLoanV502Migrator.t.sol b/tests/MapleLoanV502Migrator.t.sol deleted file mode 100644 index 6d698d1..0000000 --- a/tests/MapleLoanV502Migrator.t.sol +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.7; - -import { Address, TestUtils } from "../modules/contract-test-utils/contracts/test.sol"; -import { MockERC20 } from "../modules/erc20/contracts/test/mocks/MockERC20.sol"; - -import { MapleLoan } from "../contracts/MapleLoan.sol"; -import { MapleLoanFactory} from "../contracts/MapleLoanFactory.sol"; -import { MapleLoanInitializer } from "../contracts/MapleLoanInitializer.sol"; -import { MapleLoanV502Migrator } from "../contracts/MapleLoanV502Migrator.sol"; - -import { MockGlobals, MockFeeManager, MockLoanManager, MockLoanManagerFactory } from "./mocks/Mocks.sol"; - -contract MapleLoanV502MigratorTests is TestUtils { - - address internal governor = address(new Address()); - address internal securityAdmin = address(new Address()); - address internal implementation501; - address internal implementation502; - address internal initializer; - - MapleLoan loan501; - MapleLoan loan502; - MapleLoanFactory oldFactory; - MapleLoanFactory newFactory; - MapleLoanV502Migrator migrator; - MockERC20 asset; - MockFeeManager feeManager; - MockGlobals globals; - MockLoanManager lender; - MockLoanManagerFactory loanManagerFactory; - - function setUp() external { - asset = new MockERC20("Asset", "ASSET", 18); - globals = new MockGlobals(governor); - feeManager = new MockFeeManager(); - implementation501 = address(new MapleLoan()); - implementation502 = address(new MapleLoan()); - initializer = address(new MapleLoanInitializer()); - lender = new MockLoanManager(); - migrator = new MapleLoanV502Migrator(); - oldFactory = new MapleLoanFactory(address(globals), address(0)); - - loanManagerFactory = MockLoanManagerFactory(lender.factory()); - - lender.__setFundsAsset(address(asset)); - - globals.setValidBorrower(address(1), true); - globals.setValidCollateralAsset(address(asset), true); - globals.setValidPoolAsset(address(asset), true); - globals.__setIsInstanceOf(true); - globals.__setCanDeploy(true); - globals.__setSecurityAdmin(securityAdmin); - - vm.startPrank(governor); - oldFactory.registerImplementation(501, implementation501, initializer); - oldFactory.setDefaultVersion(501); - oldFactory.registerImplementation(502, implementation502, initializer); - oldFactory.enableUpgradePath(501, 502, address(migrator)); - vm.stopPrank(); - - address[2] memory assets = [address(asset), address(asset)]; - uint256[3] memory termDetails = [uint256(12 hours), uint256(365 days), uint256(1)]; - uint256[3] memory amounts = [uint256(0), uint256(1_000_000e6), uint256(1_000_000e6)]; - uint256[4] memory rates = [uint256(0.1e18), uint256(0.02e18), uint256(0.03e18), uint256(0.04e18)]; - uint256[2] memory fees = [uint256(0), uint256(0)]; - - bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( - address(1), - address(lender), - address(feeManager), - assets, - termDetails, - amounts, - rates, - fees - ); - - loan501 = MapleLoan(oldFactory.createInstance(arguments, "SALT1")); - - asset.mint(address(loan501), 1_000_000e6); - - vm.prank(address(lender)); - loan501.fundLoan(); - - loan502 = MapleLoan(oldFactory.createInstance(arguments, "SALT2")); - } - - function test_migration_factoryChange() external { - assertEq(loan501.factory(), address(oldFactory)); - assertEq(loan501.implementation(), address(implementation501)); - - newFactory = new MapleLoanFactory(address(globals), address(oldFactory)); - - bytes memory arguments = abi.encode(address(newFactory)); - - // Upgrade - vm.prank(securityAdmin); - loan501.upgrade(502, arguments); - - // Set loan502 using loan501 as it is now upgraded. - loan502 = loan501; - - assertEq(loan502.factory(), address(newFactory)); - assertEq(loan502.implementation(), address(implementation502)); - assertEq(loan502.fundsAsset(), address(asset)); - - assertEq(loan502.principal(), 1_000_000e6); - } - -} From 34681a86872683382a8291408d98830cd68f1f23 Mon Sep 17 00:00:00 2001 From: 0xfarhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Thu, 26 Oct 2023 21:07:36 +0400 Subject: [PATCH 41/47] Revert "fix: Remove migrator and tests (#308)" This reverts commit 6e86c75f6d3b3cc611f38c2ff8c915071342c984. --- contracts/MapleLoanV502Migrator.sol | 16 ++++ tests/MapleLoanV502Migrator.t.sol | 111 ++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 contracts/MapleLoanV502Migrator.sol create mode 100644 tests/MapleLoanV502Migrator.t.sol diff --git a/contracts/MapleLoanV502Migrator.sol b/contracts/MapleLoanV502Migrator.sol new file mode 100644 index 0000000..d3bbbee --- /dev/null +++ b/contracts/MapleLoanV502Migrator.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.7; + +import { ProxiedInternals } from "../modules/maple-proxy-factory/modules/proxy-factory/contracts/ProxiedInternals.sol"; + +import { MapleLoanStorage } from "./MapleLoanStorage.sol"; + +/// @title MapleLoanV502Migrator is to update the factory address for each deployed loan. +contract MapleLoanV502Migrator is ProxiedInternals, MapleLoanStorage { + fallback() external { + ( address factory_ ) = abi.decode(msg.data, (address)); + + _setFactory(factory_); + } + +} diff --git a/tests/MapleLoanV502Migrator.t.sol b/tests/MapleLoanV502Migrator.t.sol new file mode 100644 index 0000000..6d698d1 --- /dev/null +++ b/tests/MapleLoanV502Migrator.t.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.7; + +import { Address, TestUtils } from "../modules/contract-test-utils/contracts/test.sol"; +import { MockERC20 } from "../modules/erc20/contracts/test/mocks/MockERC20.sol"; + +import { MapleLoan } from "../contracts/MapleLoan.sol"; +import { MapleLoanFactory} from "../contracts/MapleLoanFactory.sol"; +import { MapleLoanInitializer } from "../contracts/MapleLoanInitializer.sol"; +import { MapleLoanV502Migrator } from "../contracts/MapleLoanV502Migrator.sol"; + +import { MockGlobals, MockFeeManager, MockLoanManager, MockLoanManagerFactory } from "./mocks/Mocks.sol"; + +contract MapleLoanV502MigratorTests is TestUtils { + + address internal governor = address(new Address()); + address internal securityAdmin = address(new Address()); + address internal implementation501; + address internal implementation502; + address internal initializer; + + MapleLoan loan501; + MapleLoan loan502; + MapleLoanFactory oldFactory; + MapleLoanFactory newFactory; + MapleLoanV502Migrator migrator; + MockERC20 asset; + MockFeeManager feeManager; + MockGlobals globals; + MockLoanManager lender; + MockLoanManagerFactory loanManagerFactory; + + function setUp() external { + asset = new MockERC20("Asset", "ASSET", 18); + globals = new MockGlobals(governor); + feeManager = new MockFeeManager(); + implementation501 = address(new MapleLoan()); + implementation502 = address(new MapleLoan()); + initializer = address(new MapleLoanInitializer()); + lender = new MockLoanManager(); + migrator = new MapleLoanV502Migrator(); + oldFactory = new MapleLoanFactory(address(globals), address(0)); + + loanManagerFactory = MockLoanManagerFactory(lender.factory()); + + lender.__setFundsAsset(address(asset)); + + globals.setValidBorrower(address(1), true); + globals.setValidCollateralAsset(address(asset), true); + globals.setValidPoolAsset(address(asset), true); + globals.__setIsInstanceOf(true); + globals.__setCanDeploy(true); + globals.__setSecurityAdmin(securityAdmin); + + vm.startPrank(governor); + oldFactory.registerImplementation(501, implementation501, initializer); + oldFactory.setDefaultVersion(501); + oldFactory.registerImplementation(502, implementation502, initializer); + oldFactory.enableUpgradePath(501, 502, address(migrator)); + vm.stopPrank(); + + address[2] memory assets = [address(asset), address(asset)]; + uint256[3] memory termDetails = [uint256(12 hours), uint256(365 days), uint256(1)]; + uint256[3] memory amounts = [uint256(0), uint256(1_000_000e6), uint256(1_000_000e6)]; + uint256[4] memory rates = [uint256(0.1e18), uint256(0.02e18), uint256(0.03e18), uint256(0.04e18)]; + uint256[2] memory fees = [uint256(0), uint256(0)]; + + bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( + address(1), + address(lender), + address(feeManager), + assets, + termDetails, + amounts, + rates, + fees + ); + + loan501 = MapleLoan(oldFactory.createInstance(arguments, "SALT1")); + + asset.mint(address(loan501), 1_000_000e6); + + vm.prank(address(lender)); + loan501.fundLoan(); + + loan502 = MapleLoan(oldFactory.createInstance(arguments, "SALT2")); + } + + function test_migration_factoryChange() external { + assertEq(loan501.factory(), address(oldFactory)); + assertEq(loan501.implementation(), address(implementation501)); + + newFactory = new MapleLoanFactory(address(globals), address(oldFactory)); + + bytes memory arguments = abi.encode(address(newFactory)); + + // Upgrade + vm.prank(securityAdmin); + loan501.upgrade(502, arguments); + + // Set loan502 using loan501 as it is now upgraded. + loan502 = loan501; + + assertEq(loan502.factory(), address(newFactory)); + assertEq(loan502.implementation(), address(implementation502)); + assertEq(loan502.fundsAsset(), address(asset)); + + assertEq(loan502.principal(), 1_000_000e6); + } + +} From b0abce24ed7ef4e986bc42c1d45777eda0ad7fff Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Thu, 26 Oct 2023 21:10:06 +0400 Subject: [PATCH 42/47] chore: Formatting (#309) --- contracts/MapleLoanV502Migrator.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/MapleLoanV502Migrator.sol b/contracts/MapleLoanV502Migrator.sol index d3bbbee..317ba7f 100644 --- a/contracts/MapleLoanV502Migrator.sol +++ b/contracts/MapleLoanV502Migrator.sol @@ -7,6 +7,7 @@ import { MapleLoanStorage } from "./MapleLoanStorage.sol"; /// @title MapleLoanV502Migrator is to update the factory address for each deployed loan. contract MapleLoanV502Migrator is ProxiedInternals, MapleLoanStorage { + fallback() external { ( address factory_ ) = abi.decode(msg.data, (address)); From bff5c1d5bbbca6e9c135fd811bfccc6063c1c99d Mon Sep 17 00:00:00 2001 From: Joao Gabriel Carvalho Date: Tue, 31 Oct 2023 05:12:13 -0300 Subject: [PATCH 43/47] feat: Add validation to migrator (#310) --- contracts/MapleLoanV502Migrator.sol | 10 ++++++++-- tests/MapleLoanV502Migrator.t.sol | 12 ++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/contracts/MapleLoanV502Migrator.sol b/contracts/MapleLoanV502Migrator.sol index 317ba7f..2b29fda 100644 --- a/contracts/MapleLoanV502Migrator.sol +++ b/contracts/MapleLoanV502Migrator.sol @@ -3,15 +3,21 @@ pragma solidity 0.8.7; import { ProxiedInternals } from "../modules/maple-proxy-factory/modules/proxy-factory/contracts/ProxiedInternals.sol"; +import { IGlobalsLike, IMapleProxyFactoryLike } from "./interfaces/Interfaces.sol"; + import { MapleLoanStorage } from "./MapleLoanStorage.sol"; /// @title MapleLoanV502Migrator is to update the factory address for each deployed loan. contract MapleLoanV502Migrator is ProxiedInternals, MapleLoanStorage { fallback() external { - ( address factory_ ) = abi.decode(msg.data, (address)); + ( address newFactory_ ) = abi.decode(msg.data, (address)); + + address globals = IMapleProxyFactoryLike(_factory()).mapleGlobals(); + + require(IGlobalsLike(globals).isInstanceOf("FT_LOAN_FACTORY", newFactory_), "MLV502M:INVALID_FACTORY"); - _setFactory(factory_); + _setFactory(newFactory_); } } diff --git a/tests/MapleLoanV502Migrator.t.sol b/tests/MapleLoanV502Migrator.t.sol index 6d698d1..f6a39a5 100644 --- a/tests/MapleLoanV502Migrator.t.sol +++ b/tests/MapleLoanV502Migrator.t.sol @@ -86,6 +86,18 @@ contract MapleLoanV502MigratorTests is TestUtils { loan502 = MapleLoan(oldFactory.createInstance(arguments, "SALT2")); } + function test_migration_invalidFactory() external { + newFactory = new MapleLoanFactory(address(globals), address(oldFactory)); + + bytes memory arguments = abi.encode(address(newFactory)); + + globals.__setIsInstanceOf(false); + + vm.expectRevert("MPF:UI:FAILED"); + vm.prank(securityAdmin); + loan501.upgrade(502, arguments); + } + function test_migration_factoryChange() external { assertEq(loan501.factory(), address(oldFactory)); assertEq(loan501.implementation(), address(implementation501)); From 9effdcdc728b2ebcb659bc358fef76f86891b48a Mon Sep 17 00:00:00 2001 From: Joao Gabriel Carvalho Date: Thu, 2 Nov 2023 13:13:08 -0300 Subject: [PATCH 44/47] chore: Remove todo (#311) --- contracts/MapleLoan.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/MapleLoan.sol b/contracts/MapleLoan.sol index 52328c4..abd4348 100644 --- a/contracts/MapleLoan.sol +++ b/contracts/MapleLoan.sol @@ -314,7 +314,6 @@ contract MapleLoan is IMapleLoan, MapleProxiedInternals, MapleLoanStorage { require(success_, "ML:ANT:FAILED"); } - // TODO: Emit this before the refinance calls in order to adhere to the CEI pattern. emit NewTermsAccepted(refinanceCommitment_, refinancer_, deadline_, calls_); address fundsAsset_ = _fundsAsset; From 20e4528e07d66afa0966b444331c8b46c5fa2ad3 Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Mon, 20 Nov 2023 16:09:17 +0400 Subject: [PATCH 45/47] fix: Add additional factory validation for loan migration (#312) --- contracts/MapleLoanV502Migrator.sol | 4 +++- tests/MapleLoanV502Migrator.t.sol | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/contracts/MapleLoanV502Migrator.sol b/contracts/MapleLoanV502Migrator.sol index 2b29fda..af8600f 100644 --- a/contracts/MapleLoanV502Migrator.sol +++ b/contracts/MapleLoanV502Migrator.sol @@ -9,10 +9,12 @@ import { MapleLoanStorage } from "./MapleLoanStorage.sol"; /// @title MapleLoanV502Migrator is to update the factory address for each deployed loan. contract MapleLoanV502Migrator is ProxiedInternals, MapleLoanStorage { - + fallback() external { ( address newFactory_ ) = abi.decode(msg.data, (address)); + require(_factory() != newFactory_, "MLV502M:INVALID_NO_OP"); + address globals = IMapleProxyFactoryLike(_factory()).mapleGlobals(); require(IGlobalsLike(globals).isInstanceOf("FT_LOAN_FACTORY", newFactory_), "MLV502M:INVALID_FACTORY"); diff --git a/tests/MapleLoanV502Migrator.t.sol b/tests/MapleLoanV502Migrator.t.sol index f6a39a5..102bb00 100644 --- a/tests/MapleLoanV502Migrator.t.sol +++ b/tests/MapleLoanV502Migrator.t.sol @@ -86,6 +86,16 @@ contract MapleLoanV502MigratorTests is TestUtils { loan502 = MapleLoan(oldFactory.createInstance(arguments, "SALT2")); } + function test_migration_sameFactory_noOp() external { + newFactory = new MapleLoanFactory(address(globals), address(oldFactory)); + + bytes memory arguments = abi.encode(address(oldFactory)); + + vm.expectRevert("MPF:UI:FAILED"); + vm.prank(securityAdmin); + loan501.upgrade(502, arguments); + } + function test_migration_invalidFactory() external { newFactory = new MapleLoanFactory(address(globals), address(oldFactory)); From 0aef0cb9888464be557bc166e4bbfbe3acb40f2f Mon Sep 17 00:00:00 2001 From: JG Carvalho Date: Tue, 19 Dec 2023 18:45:50 -0300 Subject: [PATCH 46/47] feat: update package and revert private readme --- README.md | 12 +++++++----- configs/package.yaml | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1ae91d3..581e1f5 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Fixed Term Maple Loan -![Foundry CI](https://github.com/maple-labs/loan-private/actions/workflows/forge.yml/badge.svg) -[![GitBook - Documentation](https://img.shields.io/badge/GitBook-Documentation-orange?logo=gitbook&logoColor=white)](https://maplefinance.gitbook.io/maple/maple-for-developers/protocol-overview) +![Foundry CI](https://github.com/maple-labs/fixed-term-loan/actions/workflows/forge.yml/badge.svg) +[![GitBook - Documentation](https://img.shields.io/badge/GitBook-Documentation-orange?logo=gitbook&logoColor=white)](https://maplefinance.gitbook.io/maple/technical-resources/protocol-overview) [![Foundry][foundry-badge]][foundry] -[![License: BUSL 1.1](https://img.shields.io/badge/License-BUSL%201.1-blue.svg)](https://github.com/maple-labs/loan-private/blob/main/LICENSE) +[![License: BUSL 1.1](https://img.shields.io/badge/License-BUSL%201.1-blue.svg)](https://github.com/maple-labs/fixed-term-loan/blob/main/LICENSE) [foundry]: https://getfoundry.sh/ [foundry-badge]: https://img.shields.io/badge/Built%20with-Foundry-FFDB1C.svg @@ -40,8 +40,8 @@ Versions of dependencies can be checked with `git submodule status`. This project was built using [Foundry](https://book.getfoundry.sh/). Refer to installation instructions [here](https://github.com/foundry-rs/foundry#installation). ```sh -git clone git@github.com:maple-labs/loan-private.git -cd loan-private +git clone git@github.com:maple-labs/fixed-term-loan.git +cd fixed-term-loan forge install ``` @@ -64,6 +64,8 @@ forge install | v3.0.1 - v4.0.0 | Three Sigma | [`Three Sigma Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/10223541/three-sigma_maple-finance_code-audit_v1.1.1.pdf) | 2022-10-24 | | v5.0.0 - v5.0.1 | Spearbit Auditors via Cantina | [`2023-06-05 - Cantina Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/11667848/cantina-maple.pdf) | 2023-06-05 | | v5.0.0 - v5.0.1 | Three Sigma | [`2023-04-10 - Three Sigma Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/11663546/maple-v2-audit_three-sigma_2023.pdf) | 2023-04-21 | +| Three Sigma | [`2023-11-06 - Three Sigma Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/13707288/Maple-Q4-Three-Sigma-Audit.pdf) | +| 0xMacro | [`2023-11-27 - 0xMacro Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/13707291/Maple-Q4-0xMacro-Audit.pdf) | ## Bug Bounty diff --git a/configs/package.yaml b/configs/package.yaml index 9cc5120..70a7179 100644 --- a/configs/package.yaml +++ b/configs/package.yaml @@ -1,5 +1,5 @@ name: fixed-term-loan -version: 5.0.1 +version: 5.0.2 source: contracts packages: - path: contracts/MapleLoan.sol From 544131bccf2851ac88b08f4c39898ef0e1eeff05 Mon Sep 17 00:00:00 2001 From: JG Carvalho Date: Wed, 20 Dec 2023 10:16:14 -0300 Subject: [PATCH 47/47] fix: attempt yaml --- .github/workflows/slither.yml | 54 ----------------------------------- 1 file changed, 54 deletions(-) delete mode 100644 .github/workflows/slither.yml diff --git a/.github/workflows/slither.yml b/.github/workflows/slither.yml deleted file mode 100644 index 97e4089..0000000 --- a/.github/workflows/slither.yml +++ /dev/null @@ -1,54 +0,0 @@ -# name: Static Analysis -# on: -# push: -# branches: "*" - -# jobs: -# build: -# runs-on: ubuntu-latest - -# steps: -# - uses: actions/checkout@v2 - -# - name: Set up Python ${{ matrix.python-version }} -# uses: actions/setup-python@v2 -# with: -# python-version: 3.8 - -# - name: Install dependencies -# run: | -# sudo snap install solc -# python -m pip install --upgrade pip -# pip install slither-analyzer==0.8.2 solc-select==0.2.1 -# solc-select install 0.8.7 -# solc-select use 0.8.7 - -# - name: Install submodules -# run: | -# git config --global url."https://github.com/".insteadOf "git@github.com:" -# git submodule update --init --recursive - -# - name: Summary of static analysis -# run: | -# slither contracts --print human-summary - -# - name: Contract summary of static analysis -# run: | -# slither contracts --print contract-summary - -# - name: Function summary -# run: | -# slither contracts --print function-summary - -# - name: Inheritance -# run: | -# slither contracts --print inheritance - -# - name: Data dependency -# run: | -# slither contracts --print data-dependency - -# - name: Static Analysis -# run: | -# slither contracts -# continue-on-error: true