From dbc1d0cfb10511b0bc16bb2f583c629115865ec3 Mon Sep 17 00:00:00 2001 From: Joseph Delong Date: Sat, 4 May 2024 19:16:57 -0500 Subject: [PATCH] feat: tests for ratio lender --- .gas-snapshot | 120 +++++---- .../AstariaV1RatioLenderEnforcer.sol | 46 ++-- test/AstariaV1Test.sol | 24 ++ test/TestV1RatioLenderEnforcer.sol | 235 ++++++++++++++++++ 4 files changed, 343 insertions(+), 82 deletions(-) create mode 100644 test/TestV1RatioLenderEnforcer.sol diff --git a/.gas-snapshot b/.gas-snapshot index 837ece1..dea9b3b 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,54 +1,68 @@ -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallAuctionFailLenderClaim() (gas: 569882) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallBase() (gas: 910054) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLenderClaimRandomFulfiller() (gas: 567197) -TestAstariaV1Loan:testNewLoanERC721CollateralRecallNotBlockedBorrower() (gas: 473813) -TestAstariaV1Loan:testNewLoanERC721CollateralRecallNotBlockedLender() (gas: 473538) -TestAstariaV1Loan:testNewLoanERC721CollateralRecallerNotBorrowerOrLender() (gas: 446127) -TestAstariaV1Pricing:testGetPaymentConsiderationIncrementation() (gas: 57195) -TestAstariaV1Pricing:testGetRefinanceConsiderationAsBorrowerZeroRate() (gas: 60433) -TestAstariaV1Pricing:testGetRefinanceConsiderationInsufficientRefinance() (gas: 95727) -TestAstariaV1Pricing:testGetRefinanceConsiderationInvalidRefinance() (gas: 59300) -TestAstariaV1Pricing:testGetRefinanceConsiderationValidEqualRate() (gas: 97989) -TestAstariaV1Pricing:testGetRefinanceConsiderationValidHigherRate() (gas: 108970) -TestAstariaV1Pricing:testGetRefinanceConsiderationValidLowerRate() (gas: 104617) -TestAstariaV1Pricing:testGetRefinanceConsiderationZeroRate() (gas: 64686) -TestAstariaV1Pricing:testGetRefinanceNewDecimalMismatch() (gas: 64618) -TestAstariaV1Pricing:testV1PricingValidateInvalid() (gas: 56870) -TestAstariaV1Pricing:testV1PricingValidateValid() (gas: 57388) -TestAstariaV1Settlement:testGetSettlementConsideration() (gas: 440480) -TestAstariaV1Settlement:testV1SettlementHandlerValidate() (gas: 436565) -TestAstariaV1Settlement:testV1SettlementValidateInvalid() (gas: 51055) -TestAstariaV1Settlement:testV1SettlementValidateValid() (gas: 50398) -TestAstariaV1Status:testCannotRecallTwice() (gas: 530871) -TestAstariaV1Status:testInvalidRecallLoanDoesNotExist() (gas: 497581) -TestAstariaV1Status:testIsActive() (gas: 446151) -TestAstariaV1Status:testIsRecalledInsideWindow() (gas: 541272) -TestAstariaV1Status:testIsRecalledOutsideWindow() (gas: 538810) -TestAstariaV1Status:testRecallAndRefinanceInsideWindow() (gas: 683957) -TestAstariaV1Status:testRecallAndRefinanceWithLenderCaveat() (gas: 742794) -TestAstariaV1Status:testRecallPauseable() (gas: 19669) -TestAstariaV1Status:testRecallRateActiveRecall() (gas: 527199) -TestAstariaV1Status:testRecallRateEmptyRecall() (gas: 446889) -TestAstariaV1Status:testV1StatusValidateInValid() (gas: 58479) -TestAstariaV1Status:testV1StatusValidateValid() (gas: 51916) -TestCompoundInterest:testDecimalsTooHigh() (gas: 3318) -TestCompoundInterest:testInterestAccrual() (gas: 52624) -TestCompoundInterest:testMaxAmountDecimals() (gas: 7891) -TestCompoundInterest:testRateExceedsMaxRecallRate() (gas: 3297) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallAuctionFailLenderClaim() (gas: 569913) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallBase() (gas: 910085) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLenderClaimRandomFulfiller() (gas: 567097) +TestAstariaV1Loan:testNewLoanERC721CollateralRecallNotBlockedBorrower() (gas: 473733) +TestAstariaV1Loan:testNewLoanERC721CollateralRecallNotBlockedLender() (gas: 473569) +TestAstariaV1Loan:testNewLoanERC721CollateralRecallerNotBorrowerOrLender() (gas: 446158) +TestAstariaV1Pricing:testGetPaymentConsiderationIncrementation() (gas: 57204) +TestAstariaV1Pricing:testGetRefinanceConsiderationAsBorrowerZeroRate() (gas: 60464) +TestAstariaV1Pricing:testGetRefinanceConsiderationInsufficientRefinance() (gas: 95736) +TestAstariaV1Pricing:testGetRefinanceConsiderationInvalidRefinance() (gas: 59309) +TestAstariaV1Pricing:testGetRefinanceConsiderationValidEqualRate() (gas: 97998) +TestAstariaV1Pricing:testGetRefinanceConsiderationValidHigherRate() (gas: 109001) +TestAstariaV1Pricing:testGetRefinanceConsiderationValidLowerRate() (gas: 104626) +TestAstariaV1Pricing:testGetRefinanceConsiderationZeroRate() (gas: 64695) +TestAstariaV1Pricing:testGetRefinanceNewDecimalMismatch() (gas: 64649) +TestAstariaV1Pricing:testV1PricingValidateInvalid() (gas: 56901) +TestAstariaV1Pricing:testV1PricingValidateValid() (gas: 57419) +TestAstariaV1Settlement:testGetSettlementConsideration() (gas: 440400) +TestAstariaV1Settlement:testV1SettlementHandlerValidate() (gas: 436596) +TestAstariaV1Settlement:testV1SettlementValidateInvalid() (gas: 51064) +TestAstariaV1Settlement:testV1SettlementValidateValid() (gas: 50429) +TestAstariaV1Status:testCannotRecallTwice() (gas: 530858) +TestAstariaV1Status:testInvalidRecallLoanDoesNotExist() (gas: 497612) +TestAstariaV1Status:testIsActive() (gas: 446160) +TestAstariaV1Status:testIsRecalledInsideWindow() (gas: 541303) +TestAstariaV1Status:testIsRecalledOutsideWindow() (gas: 538841) +TestAstariaV1Status:testRecallAndRefinanceInsideWindow() (gas: 683988) +TestAstariaV1Status:testRecallAndRefinanceWithLenderCaveat() (gas: 742715) +TestAstariaV1Status:testRecallPauseable() (gas: 19691) +TestAstariaV1Status:testRecallRateActiveRecall() (gas: 527208) +TestAstariaV1Status:testRecallRateEmptyRecall() (gas: 446920) +TestAstariaV1Status:testV1StatusValidateInValid() (gas: 58510) +TestAstariaV1Status:testV1StatusValidateValid() (gas: 51925) +TestCompoundInterest:testDecimalsTooHigh() (gas: 3340) +TestCompoundInterest:testInterestAccrual() (gas: 52646) +TestCompoundInterest:testMaxAmountDecimals() (gas: 7913) +TestCompoundInterest:testRateExceedsMaxRecallRate() (gas: 3319) TestCompoundInterest:testRateTooLowZero() (gas: 3413) -TestV1BorrowerEnforcer:testFuzzRateMethods((uint256,uint256,uint256),uint256) (runs: 10000, μ: 1223, ~: 1223) -TestV1BorrowerEnforcer:testRevertLocateCurrentRateAndAmount() (gas: 50278) -TestV1BorrowerEnforcer:testV1BorrowerEnforcerCollateralAmountOOB() (gas: 112121) -TestV1BorrowerEnforcer:testV1BorrowerEnforcerDebtAmountOOB() (gas: 99409) -TestV1BorrowerEnforcer:testV1BorrowerEnforcerDebtBundlesNotSupported() (gas: 57321) -TestV1BorrowerEnforcer:testV1BorrowerEnforcerEnd() (gas: 106805) -TestV1BorrowerEnforcer:testV1BorrowerEnforcerHalfway() (gas: 101968) -TestV1BorrowerEnforcer:testV1BorrowerEnforcerRateGTCurrent() (gas: 81834) -TestV1BorrowerEnforcer:testV1BorrowerEnforcerRateLTCurrent() (gas: 89323) -TestV1BorrowerEnforcer:testV1BorrowerEnforcerStart() (gas: 86476) -TestV1LenderEnforcer:testV1LenderEnforcerAdditionalTransfers() (gas: 105167) -TestV1LenderEnforcer:testV1LenderEnforcerAmount() (gas: 173982) -TestV1LenderEnforcer:testV1LenderEnforcerDebtBundlesNotSupported() (gas: 63352) -TestV1LenderEnforcer:testV1LenderEnforcerMatchIdentifier() (gas: 109746) -TestV1LenderEnforcer:testV1LenderEnforcerMinDebtExceedsMax() (gas: 77387) -TestV1LenderEnforcer:testV1LenderEnforcerRate() (gas: 102337) \ No newline at end of file +TestV1BorrowerEnforcer:testFuzzRateMethods((uint256,uint256,uint256),uint256) (runs: 10000, μ: 1245, ~: 1245) +TestV1BorrowerEnforcer:testRevertLocateCurrentRateAndAmount() (gas: 50309) +TestV1BorrowerEnforcer:testV1BorrowerEnforcerCollateralAmountOOB() (gas: 112152) +TestV1BorrowerEnforcer:testV1BorrowerEnforcerDebtAmountOOB() (gas: 99418) +TestV1BorrowerEnforcer:testV1BorrowerEnforcerDebtBundlesNotSupported() (gas: 57330) +TestV1BorrowerEnforcer:testV1BorrowerEnforcerEnd() (gas: 106836) +TestV1BorrowerEnforcer:testV1BorrowerEnforcerHalfway() (gas: 101999) +TestV1BorrowerEnforcer:testV1BorrowerEnforcerRateGTCurrent() (gas: 81865) +TestV1BorrowerEnforcer:testV1BorrowerEnforcerRateLTCurrent() (gas: 89310) +TestV1BorrowerEnforcer:testV1BorrowerEnforcerStart() (gas: 86485) +TestV1LenderEnforcer:testV1LenderEnforcerAdditionalTransfers() (gas: 105176) +TestV1LenderEnforcer:testV1LenderEnforcerAmount() (gas: 173991) +TestV1LenderEnforcer:testV1LenderEnforcerDebtBundlesNotSupported() (gas: 63361) +TestV1LenderEnforcer:testV1LenderEnforcerMatchIdentifier() (gas: 109755) +TestV1LenderEnforcer:testV1LenderEnforcerMinDebtExceedsMax() (gas: 77418) +TestV1LenderEnforcer:testV1LenderEnforcerRate() (gas: 102346) +TestV1RatioLenderEnforcer:testV1LenderEnforcerCopyBorrower() (gas: 82012) +TestV1RatioLenderEnforcer:testV1LenderEnforcerCopyCollateralAmount() (gas: 81517) +TestV1RatioLenderEnforcer:testV1LenderEnforcerCopyDebtAmount() (gas: 81325) +TestV1RatioLenderEnforcer:testV1LenderEnforcerCopyOriginator() (gas: 84119) +TestV1RatioLenderEnforcer:testV1RatioLenderCollateralBundle() (gas: 72008) +TestV1RatioLenderEnforcer:testV1RatioLenderDebtAmountExceedsDebtMax() (gas: 79937) +TestV1RatioLenderEnforcer:testV1RatioLenderDebtBundle() (gas: 71732) +TestV1RatioLenderEnforcer:testV1RatioLenderDefault() (gas: 83916) +TestV1RatioLenderEnforcer:testV1RatioLenderEnforcerAdditionalTransfers() (gas: 116076) +TestV1RatioLenderEnforcer:testV1RatioLenderEnforcerMatchIdentifier() (gas: 112205) +TestV1RatioLenderEnforcer:testV1RatioLenderEnforcerRate() (gas: 104962) +TestV1RatioLenderEnforcer:testV1RatioLenderLoanRateLessThanCaveatRate() (gas: 79672) +TestV1RatioLenderEnforcer:testV1RatioLenderMaxDebtOrCollateralToDebtRatioZero() (gas: 79502) +TestV1RatioLenderEnforcer:testV1RatioLenderMinCollateralAmount() (gas: 105956) \ No newline at end of file diff --git a/src/enforcers/AstariaV1RatioLenderEnforcer.sol b/src/enforcers/AstariaV1RatioLenderEnforcer.sol index 96f74fe..b581038 100644 --- a/src/enforcers/AstariaV1RatioLenderEnforcer.sol +++ b/src/enforcers/AstariaV1RatioLenderEnforcer.sol @@ -16,15 +16,12 @@ import {Starport} from "starport-core/Starport.sol"; import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; import {BasePricing} from "v1-core/pricing/BasePricing.sol"; import {AdditionalTransfer} from "starport-core/lib/StarportLib.sol"; - import {AstariaV1Lib} from "v1-core/lib/AstariaV1Lib.sol"; - -import {ItemType} from "seaport-types/src/lib/ConsiderationEnums.sol"; -import {SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; contract AstariaV1RatioLenderEnforcer is CaveatEnforcer { using FixedPointMathLib for uint256; + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -34,16 +31,10 @@ contract AstariaV1RatioLenderEnforcer is CaveatEnforcer { error LoanRateLessThanCaveatRate(); error DebtBundlesNotSupported(); error CollateralBundlesNotSupported(); - error DebtAmountExceedsDebtMax(uint256 maxDebt, uint256 loanAmount); + error DebtAmountExceedsDebtMax(uint256 maxDebt, uint256 debtAmount); error BelowMinCollateralAmount(); error MaxDebtOrCollateralToDebtRatioZero(); - /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ - /* CONSTANTS AND IMMUTABLES */ - /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ - - uint256 constant MAX_DURATION = uint256(3 * 365 days); // 3 years - /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* STRUCTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -59,8 +50,10 @@ contract AstariaV1RatioLenderEnforcer is CaveatEnforcer { /* PUBLIC FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ - /// @notice Validates a loan against a caveat, w/ a minimum rate and a maximum amount - /// @dev Bundle support is not implemented, and will revert + /// @notice Validates a loan against a caveat, w/ a minCollateralAmount, collateralToDebtRatio, and a matchIdentifier + /// @dev collateralToDebtRatio is a 1e18 value allowing a ratio conversion from collateral to debt, 1e18 was used to allow flexibility on the collateral units lower bound + /// @dev Collateral bundle support is not implemented, and will revert + /// @dev Debt bundle support is not implemented, and will revert /// @dev matchIdentifier = false will allow the loan to have a different identifier than the caveat /// @dev Only viable for use w/ AstariaV1Pricing and AstariaV1Status modules function validate( @@ -78,9 +71,9 @@ contract AstariaV1RatioLenderEnforcer is CaveatEnforcer { Starport.Terms calldata loanTerms = loan.terms; uint256 loanRate = abi.decode(loanTerms.pricingData, (BasePricing.Details)).rate; - uint256 loanAmount = loan.debt[0].amount; + uint256 debtAmount = loan.debt[0].amount; AstariaV1Lib.validateCompoundInterest( - loanAmount, + debtAmount, loanRate, AstariaV1Lib.getBaseRecallMax(loanTerms.statusData), AstariaV1Lib.getBasePricingDecimals(loanTerms.pricingData) @@ -93,9 +86,9 @@ contract AstariaV1RatioLenderEnforcer is CaveatEnforcer { revert BelowMinCollateralAmount(); } - uint256 maxDebt = (collateralAmount * details.collateralToDebtRatio) / AstariaV1Lib.WAD; - if (loanAmount > maxDebt) { - revert DebtAmountExceedsDebtMax(maxDebt, loanAmount); + uint256 maxDebt = collateralAmount.mulWad(details.collateralToDebtRatio); + if (debtAmount > maxDebt) { + revert DebtAmountExceedsDebtMax(maxDebt, debtAmount); } if (maxDebt == 0) { @@ -112,21 +105,16 @@ contract AstariaV1RatioLenderEnforcer is CaveatEnforcer { AstariaV1Lib.setBasePricingRate(caveatPricingData, loanRate); Starport.Loan memory caveatLoan = details.loan; - // Update the caveat loan amount - caveatLoan.debt[0].amount = loanAmount; - if (!details.matchIdentifier) { // Update the caveat loan identifier - uint256 i = 0; - for (; i < caveatLoan.collateral.length;) { - caveatLoan.collateral[i].identifier = loan.collateral[i].identifier; - unchecked { - ++i; - } - } + caveatLoan.collateral[0].identifier = loan.collateral[0].identifier; } - // Hash and match w/ expected borrower + // Update the caveat debt and collateral amounts + caveatLoan.debt[0].amount = debtAmount; + caveatLoan.collateral[0].amount = collateralAmount; + + // Hash and match w/ expected borrower and originator _validate(additionalTransfers, loan, caveatLoan); selector = CaveatEnforcer.validate.selector; } diff --git a/test/AstariaV1Test.sol b/test/AstariaV1Test.sol index e05d39b..64b7b2e 100644 --- a/test/AstariaV1Test.sol +++ b/test/AstariaV1Test.sol @@ -89,6 +89,30 @@ contract AstariaV1Test is StarportTest { return LenderEnforcer.Details({loan: refiLoan}); } + function generateDefaultERC20LoanTerms() public view virtual returns (Starport.Loan memory) { + SpentItem[] memory newCollateral = new SpentItem[](1); + newCollateral[0] = SpentItem({itemType: ItemType.ERC20, token: address(erc20s[1]), identifier: 0, amount: 1e6}); + SpentItem[] memory newDebt = new SpentItem[](1); + newDebt[0] = SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), identifier: 0, amount: 1e18}); + return Starport.Loan({ + start: 0, + custodian: address(custodian), + borrower: borrower.addr, + issuer: lender.addr, + originator: address(0), + collateral: newCollateral, + debt: newDebt, + terms: Starport.Terms({ + status: address(status), + settlement: address(settlement), + pricing: address(pricing), + pricingData: defaultPricingData, + settlementData: defaultSettlementData, + statusData: defaultStatusData + }) + }); + } + // loan.borrower and signer.addr could be mismatched function _generateSignedCaveatBorrower(Starport.Loan memory loan, Account memory signer, bytes32 salt) public diff --git a/test/TestV1RatioLenderEnforcer.sol b/test/TestV1RatioLenderEnforcer.sol new file mode 100644 index 0000000..f41e45b --- /dev/null +++ b/test/TestV1RatioLenderEnforcer.sol @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: Apache-2.0 +// █████╗ ███████╗████████╗ █████╗ ██████╗ ██╗ █████╗ ██╗ ██╗ ██╗ +// ██╔══██╗██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██║██╔══██╗ ██║ ██║███║ +// ███████║███████╗ ██║ ███████║██████╔╝██║███████║ ██║ ██║╚██║ +// ██╔══██║╚════██║ ██║ ██╔══██║██╔══██╗██║██╔══██║ ╚██╗ ██╔╝ ██║ +// ██║ ██║███████║ ██║ ██║ ██║██║ ██║██║██║ ██║ ╚████╔╝ ██║ +// ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═══╝ ╚═╝ +// +// Astaria v1 Lending +// Built on Starport https://github.com/astariaXYZ/starport +// Designed with love by Astaria Labs, Inc + +pragma solidity ^0.8.17; + +import "test/AstariaV1Test.sol"; + +import {Starport} from "starport-core/Starport.sol"; +import {StarportLib, AdditionalTransfer} from "starport-core/lib/StarportLib.sol"; + +import {AstariaV1RatioLenderEnforcer} from "v1-core/enforcers/AstariaV1RatioLenderEnforcer.sol"; +import {AstariaV1Lib} from "v1-core/lib/AstariaV1Lib.sol"; + +import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; +import {BasePricing} from "v1-core/pricing/BasePricing.sol"; + +import {console2} from "forge-std/console2.sol"; + +contract TestV1RatioLenderEnforcer is AstariaV1Test, AstariaV1RatioLenderEnforcer { + using FixedPointMathLib for uint256; + + function setUp() public virtual override { + super.setUp(); + + lenderEnforcer = new AstariaV1RatioLenderEnforcer(); + } + + function getDefaultV1RatioLenderDetails(Starport.Loan memory loan) + public + pure + returns (AstariaV1RatioLenderEnforcer.Details memory details) + { + details = AstariaV1RatioLenderEnforcer.Details({ + matchIdentifier: false, + minCollateralAmount: loan.collateral[0].amount, + collateralToDebtRatio: loan.debt[0].amount.divWadUp(loan.collateral[0].amount), + loan: loanCopy(loan) + }); + } + + function testV1RatioLenderDefault() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + AstariaV1RatioLenderEnforcer.Details memory details = getDefaultV1RatioLenderDetails(loan); + + // Test general passing case + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + } + + function testV1RatioLenderDebtBundle() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + AstariaV1RatioLenderEnforcer.Details memory details = getDefaultV1RatioLenderDetails(loan); + + // Test Debt Bundle + loan.debt = new SpentItem[](2); + vm.expectRevert(DebtBundlesNotSupported.selector); + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + } + + function testV1RatioLenderCollateralBundle() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + AstariaV1RatioLenderEnforcer.Details memory details = getDefaultV1RatioLenderDetails(loan); + + // Test Collateral Bundle + loan.collateral = new SpentItem[](2); + vm.expectRevert(CollateralBundlesNotSupported.selector); + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + } + + function testV1RatioLenderMinCollateralAmount() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + AstariaV1RatioLenderEnforcer.Details memory details = getDefaultV1RatioLenderDetails(loan); + + // Test below min collateral amount + loan.collateral[0].amount--; + vm.expectRevert(BelowMinCollateralAmount.selector); + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + + // Test below min above collateral amount + loan.collateral[0].amount += 2; + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + } + + function testV1RatioLenderDebtAmountExceedsDebtMax() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + AstariaV1RatioLenderEnforcer.Details memory details = getDefaultV1RatioLenderDetails(loan); + + // + loan.debt[0].amount++; + vm.expectRevert( + abi.encodeWithSelector( + DebtAmountExceedsDebtMax.selector, + loan.collateral[0].amount.mulWad(details.collateralToDebtRatio), + loan.debt[0].amount + ) + ); + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + } + + function testV1RatioLenderMaxDebtOrCollateralToDebtRatioZero() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + AstariaV1RatioLenderEnforcer.Details memory details = getDefaultV1RatioLenderDetails(loan); + + loan.debt[0].amount = 0; + loan.collateral[0].amount = 0; + details.collateralToDebtRatio = 0; + details.minCollateralAmount = 0; + vm.expectRevert(abi.encodeWithSelector(MaxDebtOrCollateralToDebtRatioZero.selector)); + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + } + + function testV1RatioLenderLoanRateLessThanCaveatRate() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + AstariaV1RatioLenderEnforcer.Details memory details = getDefaultV1RatioLenderDetails(loan); + + BasePricing.Details memory pricingDetails = abi.decode(loan.terms.pricingData, (BasePricing.Details)); + pricingDetails.rate--; + loan.terms.pricingData = abi.encode(pricingDetails); + vm.expectRevert(LoanRateLessThanCaveatRate.selector); + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + } + + function testV1RatioLenderEnforcerRate() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + AstariaV1RatioLenderEnforcer.Details memory details = getDefaultV1RatioLenderDetails(loan); + + // Test malleable rate + AstariaV1Lib.setBasePricingRate( + loan.terms.pricingData, AstariaV1Lib.getBasePricingRate(details.loan.terms.pricingData) + 1 + ); + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + + // Test insufficient rate + AstariaV1Lib.setBasePricingRate( + loan.terms.pricingData, AstariaV1Lib.getBasePricingRate(details.loan.terms.pricingData) - 1 + ); + vm.expectRevert(LoanRateLessThanCaveatRate.selector); + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + } + + // Test matchIdentifier + function testV1RatioLenderEnforcerMatchIdentifier() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + AstariaV1RatioLenderEnforcer.Details memory details = getDefaultV1RatioLenderDetails(loan); + + loan.collateral[0].identifier += 1; + + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + details.matchIdentifier = true; + + vm.expectRevert(LenderEnforcer.InvalidLoanTerms.selector); + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + } + + function testV1RatioLenderEnforcerAdditionalTransfers() external { + Starport.Loan memory loan = generateDefaultLoanTerms(); + AstariaV1RatioLenderEnforcer.Details memory details = getDefaultV1RatioLenderDetails(loan); + // Test invalid additional transfer from lender + AdditionalTransfer[] memory additionalTransfers = new AdditionalTransfer[](1); + additionalTransfers[0] = AdditionalTransfer({ + token: address(0), + amount: 0, + to: address(0), + from: lender.addr, + identifier: 0, + itemType: ItemType.ERC20 + }); + + vm.expectRevert(LenderEnforcer.InvalidAdditionalTransfer.selector); + lenderEnforcer.validate(additionalTransfers, loan, abi.encode(details)); + + // Test valid additional transfer from other party + additionalTransfers[0].from = borrower.addr; + lenderEnforcer.validate(additionalTransfers, loan, abi.encode(details)); + } + + // ensures that a debt copy occurs at the end of validate + function testV1LenderEnforcerCopyDebtAmount() external { + Starport.Loan memory loan = generateDefaultERC20LoanTerms(); + AstariaV1RatioLenderEnforcer.Details memory details = AstariaV1RatioLenderEnforcer.Details({ + matchIdentifier: false, + minCollateralAmount: 1, + collateralToDebtRatio: loan.debt[0].amount.divWadUp(loan.collateral[0].amount), + loan: loanCopy(loan) + }); + + loan.debt[0].amount /= 2; + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + } + + // ensures that a collateral copy occurs at the end of validate + function testV1LenderEnforcerCopyCollateralAmount() external { + Starport.Loan memory loan = generateDefaultERC20LoanTerms(); + AstariaV1RatioLenderEnforcer.Details memory details = AstariaV1RatioLenderEnforcer.Details({ + matchIdentifier: false, + minCollateralAmount: 1, + collateralToDebtRatio: loan.debt[0].amount.divWadUp(loan.collateral[0].amount), + loan: loanCopy(loan) + }); + + loan.collateral[0].amount = 10; + loan.debt[0].amount = loan.collateral[0].amount.mulWad(details.collateralToDebtRatio); + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + } + + // ensures that a borrower copy occurs at the beginning of _validate + function testV1LenderEnforcerCopyBorrower() external { + Starport.Loan memory loan = generateDefaultERC20LoanTerms(); + AstariaV1RatioLenderEnforcer.Details memory details = getDefaultV1RatioLenderDetails(loan); + + // ensuring that the details.loan.borrower does not match the loan.borrower + details.loan.borrower = address(uint160(details.loan.borrower) << 1); + assert(details.loan.borrower != loan.borrower); + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + } + + // ensures that a originator copy occurs at the beginning of _validate + function testV1LenderEnforcerCopyOriginator() external { + Starport.Loan memory loan = generateDefaultERC20LoanTerms(); + AstariaV1RatioLenderEnforcer.Details memory details = getDefaultV1RatioLenderDetails(loan); + + // ensuring that the details.loan.originator does not match the loan.originator + details.loan.originator = address(uint160(fulfiller.addr) << 1); + assert(details.loan.originator != loan.originator); + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(details)); + } +}