Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allow lenders to enforce a min debt amount for borrowers #31

Merged
merged 1 commit into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/enforcers/AstariaV1LenderEnforcer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
Expand All @@ -42,6 +42,7 @@ contract AstariaV1LenderEnforcer is LenderEnforcer {

struct V1LenderDetails {
bool matchIdentifier;
uint256 minDebtAmount;
LenderEnforcer.Details details;
}

Expand Down Expand Up @@ -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;
Expand Down
7 changes: 5 additions & 2 deletions test/AstariaV1Test.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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)});
Expand Down
50 changes: 43 additions & 7 deletions test/TestV1LenderEnforcer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand All @@ -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))
});

Expand All @@ -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;
Expand All @@ -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));
Expand All @@ -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})));
Expand Down
7 changes: 5 additions & 2 deletions test/fuzz-testing/TestFuzzV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down