From a035c7ba1c89c013a98fbc1d15ef4afd446ea60a Mon Sep 17 00:00:00 2001 From: Joseph Delong Date: Tue, 16 Jan 2024 13:44:03 -0600 Subject: [PATCH] fix: allow lenders to enforce a min debt amount for borrowers --- src/enforcers/AstariaV1LenderEnforcer.sol | 12 ++++-- test/AstariaV1Test.sol | 7 +++- test/TestV1LenderEnforcer.sol | 50 +++++++++++++++++++---- test/fuzz-testing/TestFuzzV1.sol | 7 +++- 4 files changed, 61 insertions(+), 15 deletions(-) diff --git a/src/enforcers/AstariaV1LenderEnforcer.sol b/src/enforcers/AstariaV1LenderEnforcer.sol index 2b022a0..d489852 100644 --- a/src/enforcers/AstariaV1LenderEnforcer.sol +++ b/src/enforcers/AstariaV1LenderEnforcer.sol @@ -26,10 +26,10 @@ contract AstariaV1LenderEnforcer is LenderEnforcer { /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ - error LoanAmountExceedsCaveatAmount(); error LoanRateLessThanCaveatRate(); error DebtBundlesNotSupported(); - + error DebtAmountOOB(uint256 min, uint256 max, uint256 actual); + error MinDebtAmountExceedsMax(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONSTANTS AND IMMUTABLES */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -42,6 +42,7 @@ contract AstariaV1LenderEnforcer is LenderEnforcer { struct V1LenderDetails { bool matchIdentifier; + uint256 minDebtAmount; LenderEnforcer.Details details; } @@ -76,9 +77,12 @@ contract AstariaV1LenderEnforcer is LenderEnforcer { Starport.Loan memory caveatLoan = v1Details.details.loan; SpentItem memory caveatDebt = caveatLoan.debt[0]; - if (loanAmount > caveatDebt.amount) { + if (v1Details.minDebtAmount > caveatDebt.amount) { + revert MinDebtAmountExceedsMax(); + } + if (loanAmount > caveatDebt.amount || loanAmount < v1Details.minDebtAmount) { // Debt amount is greater than the max amount or the caveatDebt amount - revert LoanAmountExceedsCaveatAmount(); + revert DebtAmountOOB(v1Details.minDebtAmount, caveatDebt.amount, loanAmount); } bytes memory caveatPricingData = caveatLoan.terms.pricingData; diff --git a/test/AstariaV1Test.sol b/test/AstariaV1Test.sol index c8049ea..eb23288 100644 --- a/test/AstariaV1Test.sol +++ b/test/AstariaV1Test.sol @@ -132,8 +132,11 @@ contract AstariaV1Test is StarportTest { loan = loanCopy(loan); loan.borrower = address(0); - AstariaV1LenderEnforcer.V1LenderDetails memory v1LenderDetails = - AstariaV1LenderEnforcer.V1LenderDetails({matchIdentifier: true, details: LenderEnforcer.Details(loan)}); + AstariaV1LenderEnforcer.V1LenderDetails memory v1LenderDetails = AstariaV1LenderEnforcer.V1LenderDetails({ + matchIdentifier: true, + minDebtAmount: loan.debt[0].amount, + details: LenderEnforcer.Details(loan) + }); CaveatEnforcer.Caveat memory caveat = CaveatEnforcer.Caveat({enforcer: address(lenderEnforcer), data: abi.encode(v1LenderDetails)}); diff --git a/test/TestV1LenderEnforcer.sol b/test/TestV1LenderEnforcer.sol index cd2cb36..992bd9f 100644 --- a/test/TestV1LenderEnforcer.sol +++ b/test/TestV1LenderEnforcer.sol @@ -25,19 +25,46 @@ import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; contract TestV1LenderEnforcer is AstariaV1Test, AstariaV1LenderEnforcer { function testV1LenderEnforcerAmount() public { Starport.Loan memory loan = generateDefaultLoanTerms(); - + uint256 max = loan.debt[0].amount; AstariaV1LenderEnforcer.V1LenderDetails memory details = AstariaV1LenderEnforcer.V1LenderDetails({ matchIdentifier: true, + minDebtAmount: loan.debt[0].amount / 2, details: LenderEnforcer.Details(loanCopy(loan)) }); // Test excessive amount loan.debt[0].amount = details.details.loan.debt[0].amount + 1; - vm.expectRevert(LoanAmountExceedsCaveatAmount.selector); + vm.expectRevert(abi.encodeWithSelector(DebtAmountOOB.selector, details.minDebtAmount, max, loan.debt[0].amount)); + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + + // Test insufficient amount + loan.debt[0].amount = details.minDebtAmount - 1; + vm.expectRevert(abi.encodeWithSelector(DebtAmountOOB.selector, details.minDebtAmount, max, loan.debt[0].amount)); lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); - // Test malleable amount - loan.debt[0].amount = details.details.loan.debt[0].amount - 1; + // on upper bound match + loan.debt[0].amount = details.details.loan.debt[0].amount; + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + + // on lower bound match + loan.debt[0].amount = details.minDebtAmount; + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + + // in between upper and lower bound + loan.debt[0].amount = details.minDebtAmount + 5; + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + } + + function testV1LenderEnforcerMinDebtExceedsMax() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + AstariaV1LenderEnforcer.V1LenderDetails memory details = AstariaV1LenderEnforcer.V1LenderDetails({ + matchIdentifier: true, + minDebtAmount: loan.debt[0].amount + 1, + details: LenderEnforcer.Details(loanCopy(loan)) + }); + + // Test excessive amount + vm.expectRevert(MinDebtAmountExceedsMax.selector); lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); } @@ -46,6 +73,7 @@ contract TestV1LenderEnforcer is AstariaV1Test, AstariaV1LenderEnforcer { AstariaV1LenderEnforcer.V1LenderDetails memory details = AstariaV1LenderEnforcer.V1LenderDetails({ matchIdentifier: true, + minDebtAmount: loan.debt[0].amount, details: LenderEnforcer.Details(loanCopy(loan)) }); @@ -69,6 +97,7 @@ contract TestV1LenderEnforcer is AstariaV1Test, AstariaV1LenderEnforcer { AstariaV1LenderEnforcer.V1LenderDetails memory details = AstariaV1LenderEnforcer.V1LenderDetails({ matchIdentifier: false, + minDebtAmount: loan.debt[0].amount, details: LenderEnforcer.Details(loanCopy(loan)) }); loan.debt[0].identifier += 1; @@ -93,8 +122,11 @@ contract TestV1LenderEnforcer is AstariaV1Test, AstariaV1LenderEnforcer { }); Starport.Loan memory loan = generateDefaultLoanTerms(); - AstariaV1LenderEnforcer.V1LenderDetails memory details = - AstariaV1LenderEnforcer.V1LenderDetails({matchIdentifier: false, details: LenderEnforcer.Details(loan)}); + AstariaV1LenderEnforcer.V1LenderDetails memory details = AstariaV1LenderEnforcer.V1LenderDetails({ + matchIdentifier: false, + minDebtAmount: loan.debt[0].amount, + details: LenderEnforcer.Details(loan) + }); vm.expectRevert(LenderEnforcer.InvalidAdditionalTransfer.selector); lenderEnforcer.validate(additionalTransfers, loan, abi.encode(details)); @@ -111,7 +143,11 @@ contract TestV1LenderEnforcer is AstariaV1Test, AstariaV1LenderEnforcer { debt[1] = _getERC721SpentItem(TestERC721(loan.debt[0].token), loan.debt[0].identifier + 1); loan.debt = debt; - AstariaV1LenderEnforcer.V1LenderDetails({matchIdentifier: false, details: LenderEnforcer.Details(loan)}); + AstariaV1LenderEnforcer.V1LenderDetails({ + matchIdentifier: false, + minDebtAmount: loan.debt[0].amount, + details: LenderEnforcer.Details(loan) + }); vm.expectRevert(DebtBundlesNotSupported.selector); lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(LenderEnforcer.Details({loan: loan}))); diff --git a/test/fuzz-testing/TestFuzzV1.sol b/test/fuzz-testing/TestFuzzV1.sol index 0a84af1..7017278 100644 --- a/test/fuzz-testing/TestFuzzV1.sol +++ b/test/fuzz-testing/TestFuzzV1.sol @@ -285,8 +285,11 @@ contract TestFuzzV1 is AstariaV1Test, TestFuzzStarport { refiLoan.debt = SP.applyRefinanceConsiderationToLoan(considerationPayment, carryPayment); LenderEnforcer.Details memory details = LenderEnforcer.Details({loan: refiLoan}); - AstariaV1LenderEnforcer.V1LenderDetails memory lenderDetails = - AstariaV1LenderEnforcer.V1LenderDetails({matchIdentifier: true, details: details}); + AstariaV1LenderEnforcer.V1LenderDetails memory lenderDetails = AstariaV1LenderEnforcer.V1LenderDetails({ + matchIdentifier: true, + minDebtAmount: refiLoan.debt[0].amount, + details: details + }); bytes32 salt = bytes32(msg.sig); details.loan.issuer = account.addr;