From 4e0b967edde9469e4722f0d97b51690df1256684 Mon Sep 17 00:00:00 2001 From: androolloyd Date: Mon, 20 Nov 2023 14:00:54 -0400 Subject: [PATCH] feat/exotic repayment settlement tests (#76) * update for simple interest pricing to ensure old and new decimals are the same, update _settleLoan helper, add exotic repayment/settlement testing with custom pricing and settlement modules * update fuzzers with random erc20 and decimals, update applyRefinanceConsideration to just override the debt based on incoming payment data from getRefinanceConsideration * simply return from applyRefinanceConsideration * chore: snapshot * update order of operations to account for changes to the applyRefinanceConsiderationToLoan method accessing memory directly --- .gas-snapshot | 222 ++++++++--------- src/Starport.sol | 34 +-- src/pricing/SimpleInterestPricing.sol | 2 +- test/StarportTest.sol | 16 +- test/fuzz-testing/TestFuzzStarport.sol | 144 ++++++++--- test/integration-testing/TestCaveats.sol | 14 +- test/integration-testing/TestNewLoan.sol | 7 +- test/unit-testing/TestStarport.sol | 289 ++++++++++++++++++++++- 8 files changed, 551 insertions(+), 177 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 4091213e..c3263658 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,144 +1,146 @@ -IntegrationTestCaveats:testOriginateUnapprovedFulfiller() (gas: 327195) -IntegrationTestCaveats:testOriginateWBorrowerApproval() (gas: 280767) -IntegrationTestCaveats:testOriginateWCaveatsAsBorrower() (gas: 302928) -IntegrationTestCaveats:testOriginateWCaveatsExpired() (gas: 157332) -IntegrationTestCaveats:testOriginateWCaveatsIncrementedNonce() (gas: 166063) -IntegrationTestCaveats:testOriginateWCaveatsInvalidSalt() (gas: 281012) -IntegrationTestCaveats:testOriginateWCaveatsInvalidSaltManual() (gas: 139972) -IntegrationTestCaveats:testOriginateWLenderApproval() (gas: 280843) -IntegrationTestCaveats:testRefinanceAsLender() (gas: 1043237) -IntegrationTestCaveats:testRefinanceCaveatFailure() (gas: 395324) -IntegrationTestCaveats:testRefinanceLoanStartAtBlockTimestampInvalidLoan() (gas: 342658) -IntegrationTestCaveats:testRefinanceUnapprovedFulfiller() (gas: 450558) -IntegrationTestCaveats:testRefinanceWCaveatsInvalidSalt() (gas: 371779) -IntegrationTestCaveats:testRefinanceWLenderApproval() (gas: 392364) -ModuleTesting:testFixedTermDutchAuctionSettlement() (gas: 433591) -ModuleTesting:testFixedTermDutchAuctionSettlementGetSettlementAuctionExpired() (gas: 436458) -ModuleTesting:testFixedTermDutchAuctionSettlementNotValid() (gas: 432581) -ModuleTesting:testFixedTermDutchAuctionSettlementValid() (gas: 433402) -ModuleTesting:testModuleValidation() (gas: 1270232) +IntegrationTestCaveats:testOriginateUnapprovedFulfiller() (gas: 327589) +IntegrationTestCaveats:testOriginateWBorrowerApproval() (gas: 281025) +IntegrationTestCaveats:testOriginateWCaveatsAsBorrower() (gas: 303246) +IntegrationTestCaveats:testOriginateWCaveatsExpired() (gas: 157439) +IntegrationTestCaveats:testOriginateWCaveatsIncrementedNonce() (gas: 166170) +IntegrationTestCaveats:testOriginateWCaveatsInvalidSalt() (gas: 281363) +IntegrationTestCaveats:testOriginateWCaveatsInvalidSaltManual() (gas: 140101) +IntegrationTestCaveats:testOriginateWLenderApproval() (gas: 281057) +IntegrationTestCaveats:testRefinanceAsLender() (gas: 1043289) +IntegrationTestCaveats:testRefinanceCaveatFailure() (gas: 395348) +IntegrationTestCaveats:testRefinanceLoanStartAtBlockTimestampInvalidLoan() (gas: 342963) +IntegrationTestCaveats:testRefinanceUnapprovedFulfiller() (gas: 443624) +IntegrationTestCaveats:testRefinanceWCaveatsInvalidSalt() (gas: 364730) +IntegrationTestCaveats:testRefinanceWLenderApproval() (gas: 392349) +ModuleTesting:testFixedTermDutchAuctionSettlement() (gas: 433475) +ModuleTesting:testFixedTermDutchAuctionSettlementGetSettlementAuctionExpired() (gas: 436253) +ModuleTesting:testFixedTermDutchAuctionSettlementNotValid() (gas: 432487) +ModuleTesting:testFixedTermDutchAuctionSettlementValid() (gas: 433286) +ModuleTesting:testModuleValidation() (gas: 1269674) PausableNonReentrantImpl:test() (gas: 2442) PausableNonReentrantImpl:testReentrancy() (gas: 2735) -TestBorrowerEnforcer:testBERevertAdditionalTransfersFromBorrower() (gas: 76434) -TestBorrowerEnforcer:testBERevertInvalidLoanTerms() (gas: 81242) -TestBorrowerEnforcer:testBEValidLoanTerms() (gas: 72237) -TestBorrowerEnforcer:testBEValidLoanTermsAnyIssuer() (gas: 72301) -TestCustodian:testCannotLazyMintTwice() (gas: 78788) -TestCustodian:testCannotMintInvalidLoanInvalidCustodian() (gas: 69031) -TestCustodian:testCannotMintInvalidLoanValidCustodian() (gas: 74547) -TestCustodian:testCustodySelector() (gas: 2861655) +TestBorrowerEnforcer:testBERevertAdditionalTransfersFromBorrower() (gas: 76318) +TestBorrowerEnforcer:testBERevertInvalidLoanTerms() (gas: 81104) +TestBorrowerEnforcer:testBEValidLoanTerms() (gas: 72121) +TestBorrowerEnforcer:testBEValidLoanTermsAnyIssuer() (gas: 72185) +TestCustodian:testCannotLazyMintTwice() (gas: 78701) +TestCustodian:testCannotMintInvalidLoanInvalidCustodian() (gas: 69119) +TestCustodian:testCannotMintInvalidLoanValidCustodian() (gas: 74460) +TestCustodian:testCustodySelector() (gas: 2861633) TestCustodian:testDefaultCustodySelectorRevert() (gas: 72312) -TestCustodian:testGenerateOrderInvalidPostRepayment() (gas: 172910) -TestCustodian:testGenerateOrderInvalidPostSettlement() (gas: 163033) -TestCustodian:testGenerateOrderRepay() (gas: 177065) -TestCustodian:testGenerateOrderRepayAsRepayApprovedBorrower() (gas: 193534) -TestCustodian:testGenerateOrderRepayERC1155AndERC20() (gas: 868007) -TestCustodian:testGenerateOrderRepayERC1155AndERC20HandlerAuthorized() (gas: 797668) +TestCustodian:testGenerateOrderInvalidPostRepayment() (gas: 172968) +TestCustodian:testGenerateOrderInvalidPostSettlement() (gas: 163091) +TestCustodian:testGenerateOrderRepay() (gas: 177123) +TestCustodian:testGenerateOrderRepayAsRepayApprovedBorrower() (gas: 193592) +TestCustodian:testGenerateOrderRepayERC1155AndERC20() (gas: 868471) +TestCustodian:testGenerateOrderRepayERC1155AndERC20HandlerAuthorized() (gas: 798132) TestCustodian:testGenerateOrderRepayInvalidHookAddress() (gas: 97601) TestCustodian:testGenerateOrderRepayInvalidHookReturnType() (gas: 91984) TestCustodian:testGenerateOrderRepayNotBorrower() (gas: 106839) -TestCustodian:testGenerateOrderSettlement() (gas: 154788) -TestCustodian:testGenerateOrderSettlementHandlerAuthorized() (gas: 160185) -TestCustodian:testGenerateOrderSettlementNoActiveLoan() (gas: 163250) -TestCustodian:testGenerateOrderSettlementUnauthorized() (gas: 101813) -TestCustodian:testGenerateOrdersWithLoanStartAtBlockTimestampInvalidLoan() (gas: 458245) -TestCustodian:testGetBorrower() (gas: 78641) +TestCustodian:testGenerateOrderSettlement() (gas: 154846) +TestCustodian:testGenerateOrderSettlementHandlerAuthorized() (gas: 160243) +TestCustodian:testGenerateOrderSettlementNoActiveLoan() (gas: 163308) +TestCustodian:testGenerateOrderSettlementUnauthorized() (gas: 101791) +TestCustodian:testGenerateOrdersWithLoanStartAtBlockTimestampInvalidLoan() (gas: 458397) +TestCustodian:testGetBorrower() (gas: 78619) TestCustodian:testInvalidAction() (gas: 173196) TestCustodian:testInvalidActionRepayInActiveLoan() (gas: 130060) TestCustodian:testInvalidActionSettleActiveLoan() (gas: 130042) TestCustodian:testInvalidEncodedData() (gas: 26192) -TestCustodian:testMintWithApprovalSetAsBorrower() (gas: 359984) +TestCustodian:testMintWithApprovalSetAsBorrower() (gas: 360049) TestCustodian:testMintWithApprovalSetAsBorrowerInvalidLoan() (gas: 60792) -TestCustodian:testMintWithApprovalSetNotAuthorized() (gas: 76751) +TestCustodian:testMintWithApprovalSetNotAuthorized() (gas: 76664) TestCustodian:testName() (gas: 7121) TestCustodian:testNonPayableFunctions() (gas: 215173) TestCustodian:testOnlySeaport() (gas: 17829) TestCustodian:testPreviewOrderNoActiveLoan() (gas: 105761) -TestCustodian:testPreviewOrderRepay() (gas: 229966) -TestCustodian:testPreviewOrderSettlement() (gas: 191658) +TestCustodian:testPreviewOrderRepay() (gas: 230024) +TestCustodian:testPreviewOrderSettlement() (gas: 191716) TestCustodian:testPreviewOrderSettlementInvalidFufliller() (gas: 108190) TestCustodian:testPreviewOrderSettlementInvalidRepayer() (gas: 116917) -TestCustodian:testRatifyOrder() (gas: 183886) -TestCustodian:testSeaportMetadata() (gas: 8632) +TestCustodian:testRatifyOrder() (gas: 183944) +TestCustodian:testSeaportMetadata() (gas: 8610) TestCustodian:testSupportsInterface() (gas: 9428) TestCustodian:testSymbol() (gas: 7105) -TestCustodian:testTokenURI() (gas: 67024) +TestCustodian:testTokenURI() (gas: 67002) TestCustodian:testTokenURIInvalidLoan() (gas: 13151) -TestLenderEnforcer:testLERevertAdditionalTransfersFromLender() (gas: 76426) -TestLenderEnforcer:testLERevertInvalidLoanTerms() (gas: 81178) -TestLenderEnforcer:testLEValidLoanTerms() (gas: 72171) -TestLenderEnforcer:testLEValidLoanTermsAnyBorrower() (gas: 72303) -TestLenderEnforcer:testLEValidLoanTermsWithAdditionalTransfers() (gas: 73526) -TestLoanCombinations:testLoan20For721SimpleInterestDutchFixedRepay() (gas: 586872) -TestLoanCombinations:testLoan20for20SimpleInterestDutchFixedRepay() (gas: 594085) -TestLoanCombinations:testLoan721for20SimpleInterestDutchFixedRepay() (gas: 585158) -TestLoanCombinations:testLoanAstariaSettlementRepay() (gas: 574976) -TestNewLoan:testBuyNowPayLater() (gas: 2869677) +TestLenderEnforcer:testLERevertAdditionalTransfersFromLender() (gas: 76310) +TestLenderEnforcer:testLERevertInvalidLoanTerms() (gas: 81062) +TestLenderEnforcer:testLEValidLoanTerms() (gas: 72055) +TestLenderEnforcer:testLEValidLoanTermsAnyBorrower() (gas: 72098) +TestLenderEnforcer:testLEValidLoanTermsWithAdditionalTransfers() (gas: 73410) +TestLoanCombinations:testLoan20For721SimpleInterestDutchFixedRepay() (gas: 587270) +TestLoanCombinations:testLoan20for20SimpleInterestDutchFixedRepay() (gas: 594461) +TestLoanCombinations:testLoan721for20SimpleInterestDutchFixedRepay() (gas: 585469) +TestLoanCombinations:testLoanAstariaSettlementRepay() (gas: 575374) +TestNewLoan:testBuyNowPayLater() (gas: 2869909) TestNewLoan:testInvalidSenderBNPL() (gas: 1613720) TestNewLoan:testInvalidUserDataHashBNPL() (gas: 1616299) -TestNewLoan:testNewLoanAs1271ProxyAccountSender() (gas: 861299) -TestNewLoan:testNewLoanAs1271ProxyAccountThirdPartyFiller() (gas: 870929) -TestNewLoan:testNewLoanERC721CollateralDefaultTerms2() (gas: 424755) -TestNewLoan:testNewLoanRefinance() (gas: 583149) -TestNewLoan:testNewLoanViaOriginatorBorrowerApprovalAndLenderApproval() (gas: 325459) -TestNewLoan:testNewLoanViaOriginatorLenderApproval() (gas: 381950) -TestNewLoan:testSettleLoan() (gas: 636302) +TestNewLoan:testNewLoanAs1271ProxyAccountSender() (gas: 861457) +TestNewLoan:testNewLoanAs1271ProxyAccountThirdPartyFiller() (gas: 871145) +TestNewLoan:testNewLoanERC721CollateralDefaultTerms2() (gas: 424965) +TestNewLoan:testNewLoanRefinance() (gas: 577284) +TestNewLoan:testNewLoanViaOriginatorBorrowerApprovalAndLenderApproval() (gas: 325441) +TestNewLoan:testNewLoanViaOriginatorLenderApproval() (gas: 382057) +TestNewLoan:testSettleLoan() (gas: 636584) TestPausableNonReentrant:testNotOwner() (gas: 21254) TestPausableNonReentrant:testPauseAndUnpause() (gas: 22555) TestPausableNonReentrant:testReentrancy() (gas: 15360) TestPausableNonReentrant:testUnpauseWhenNotPaused() (gas: 12582) -TestRepayLoan:testRepayLoanApprovedRepayer() (gas: 658586) -TestRepayLoan:testRepayLoanBase() (gas: 594797) -TestRepayLoan:testRepayLoanGenerateOrderNotSeaport() (gas: 433786) -TestRepayLoan:testRepayLoanInSettlement() (gas: 580440) -TestRepayLoan:testRepayLoanInvalidRepayer() (gas: 598869) -TestRepayLoan:testRepayLoanThatDoesNotExist() (gas: 853072) -TestSimpleInterestPricing:test_calculateInterest() (gas: 895930) -TestSimpleInterestPricing:test_getPaymentConsideration() (gas: 943140) -TestSimpleInterestPricing:test_getRefinanceConsideration() (gas: 933907) -TestStarport:testActive() (gas: 69312) -TestStarport:testAdditionalTransfers() (gas: 298209) -TestStarport:testAdditionalTransfersOriginate() (gas: 272927) -TestStarport:testAdditionalTransfersRefinance() (gas: 210821) -TestStarport:testApplyRefinanceConsiderationToLoanMalformed() (gas: 121962) -TestStarport:testCannotIssueSameLoanTwice() (gas: 359137) -TestStarport:testCannotOriginateWhilePaused() (gas: 73479) -TestStarport:testCannotSettleInvalidLoan() (gas: 74823) +TestRepayLoan:testRepayLoanApprovedRepayer() (gas: 658789) +TestRepayLoan:testRepayLoanBase() (gas: 595065) +TestRepayLoan:testRepayLoanGenerateOrderNotSeaport() (gas: 434107) +TestRepayLoan:testRepayLoanInSettlement() (gas: 580650) +TestRepayLoan:testRepayLoanInvalidRepayer() (gas: 599079) +TestRepayLoan:testRepayLoanThatDoesNotExist() (gas: 853478) +TestSimpleInterestPricing:test_calculateInterest() (gas: 899536) +TestSimpleInterestPricing:test_getPaymentConsideration() (gas: 946746) +TestSimpleInterestPricing:test_getRefinanceConsideration() (gas: 937602) +TestStarport:testActive() (gas: 69225) +TestStarport:testAdditionalTransfers() (gas: 298383) +TestStarport:testAdditionalTransfersOriginate() (gas: 273101) +TestStarport:testAdditionalTransfersRefinance() (gas: 211137) +TestStarport:testApplyRefinanceConsiderationToLoanMalformed() (gas: 129578) +TestStarport:testCannotIssueSameLoanTwice() (gas: 359427) +TestStarport:testCannotOriginateWhilePaused() (gas: 73501) +TestStarport:testCannotSettleInvalidLoan() (gas: 74881) TestStarport:testCannotSettleUnlessValidCustodian() (gas: 70985) TestStarport:testCaveatEnforcerRevert() (gas: 99214) -TestStarport:testDefaultFeeRake() (gas: 357863) -TestStarport:testDefaultFeeRakeExoticDebt() (gas: 367996) -TestStarport:testExoticDebtWithNoCaveatsNotAsBorrower() (gas: 374210) -TestStarport:testIncrementCaveatNonce() (gas: 35230) +TestStarport:testDefaultFeeRake() (gas: 358092) +TestStarport:testDefaultFeeRakeExoticDebt() (gas: 368247) +TestStarport:testExoticDebtWithCustomPricingAndRepayment() (gas: 1235383) +TestStarport:testExoticDebtWithCustomPricingAndSettlement() (gas: 1690431) +TestStarport:testExoticDebtWithNoCaveatsNotAsBorrower() (gas: 374504) +TestStarport:testIncrementCaveatNonce() (gas: 35252) TestStarport:testInitializedFlagSetProperly() (gas: 67349) -TestStarport:testInvalidAdditionalTransfersOriginate() (gas: 227996) -TestStarport:testInvalidAdditionalTransfersRefinance() (gas: 163582) -TestStarport:testInvalidAmountCollateral() (gas: 163561) -TestStarport:testInvalidAmountCollateral721() (gas: 163561) -TestStarport:testInvalidItemType() (gas: 149509) -TestStarport:testInvalidTransferLengthCollateral() (gas: 151851) -TestStarport:testInvalidTransferLengthDebt() (gas: 173518) -TestStarport:testInvalidateCaveatSalt() (gas: 33516) -TestStarport:testNonDefaultCustodianCustodyCallFails() (gas: 261642) -TestStarport:testNonDefaultCustodianCustodyCallSuccess() (gas: 287855) -TestStarport:testNonPayableFunctions() (gas: 112065) -TestStarport:testOverrideFeeRake() (gas: 354203) +TestStarport:testInvalidAdditionalTransfersOriginate() (gas: 228054) +TestStarport:testInvalidAdditionalTransfersRefinance() (gas: 163790) +TestStarport:testInvalidAmountCollateral() (gas: 163619) +TestStarport:testInvalidAmountCollateral721() (gas: 163552) +TestStarport:testInvalidItemType() (gas: 149567) +TestStarport:testInvalidTransferLengthCollateral() (gas: 151880) +TestStarport:testInvalidTransferLengthDebt() (gas: 173547) +TestStarport:testInvalidateCaveatSalt() (gas: 33538) +TestStarport:testNonDefaultCustodianCustodyCallFails() (gas: 261852) +TestStarport:testNonDefaultCustodianCustodyCallSuccess() (gas: 288087) +TestStarport:testNonPayableFunctions() (gas: 112087) +TestStarport:testOverrideFeeRake() (gas: 354454) TestStarport:testPause() (gas: 18115) -TestStarport:testRefinancePostRepaymentFails() (gas: 120679) -TestStarport:testTokenNoCodeCollateral() (gas: 148322) -TestStarport:testTokenNoCodeDebt() (gas: 178595) -TestStarport:testUnpause() (gas: 17220) +TestStarport:testRefinancePostRepaymentFails() (gas: 120853) +TestStarport:testTokenNoCodeCollateral() (gas: 148380) +TestStarport:testTokenNoCodeDebt() (gas: 178567) +TestStarport:testUnpause() (gas: 17264) TestStrategistOriginator:testEncodeWithAccountCounter() (gas: 12307) TestStrategistOriginator:testGetStrategistData() (gas: 1489933) TestStrategistOriginator:testIncrementCounterAsStrategist() (gas: 18676) -TestStrategistOriginator:testIncrementCounterNotAuthorized() (gas: 13401) -TestStrategistOriginator:testInvalidCollateral() (gas: 209819) -TestStrategistOriginator:testInvalidDeadline() (gas: 215534) -TestStrategistOriginator:testInvalidDebt() (gas: 211457) -TestStrategistOriginator:testInvalidDebtAmountAskingMoreThanOffered() (gas: 211818) -TestStrategistOriginator:testInvalidDebtAmountOfferingZero() (gas: 212117) -TestStrategistOriginator:testInvalidDebtAmountRequestingZero() (gas: 212071) -TestStrategistOriginator:testInvalidDebtLength() (gas: 210700) -TestStrategistOriginator:testInvalidOffer() (gas: 424519) -TestStrategistOriginator:testInvalidSigner() (gas: 214028) +TestStrategistOriginator:testIncrementCounterNotAuthorized() (gas: 13445) +TestStrategistOriginator:testInvalidCollateral() (gas: 209574) +TestStrategistOriginator:testInvalidDeadline() (gas: 215208) +TestStrategistOriginator:testInvalidDebt() (gas: 211256) +TestStrategistOriginator:testInvalidDebtAmountAskingMoreThanOffered() (gas: 211573) +TestStrategistOriginator:testInvalidDebtAmountOfferingZero() (gas: 211872) +TestStrategistOriginator:testInvalidDebtAmountRequestingZero() (gas: 211826) +TestStrategistOriginator:testInvalidDebtLength() (gas: 210368) +TestStrategistOriginator:testInvalidOffer() (gas: 424415) +TestStrategistOriginator:testInvalidSigner() (gas: 213725) TestStrategistOriginator:testSetStrategist() (gas: 17796) \ No newline at end of file diff --git a/src/Starport.sol b/src/Starport.sol index 8c08a40a..dd3e07ef 100644 --- a/src/Starport.sol +++ b/src/Starport.sol @@ -209,12 +209,13 @@ contract Starport is PausableNonReentrant { _settle(loan); _postRepaymentExecute(loan, msg.sender); - loan = applyRefinanceConsiderationToLoan(loan, considerationPayment, carryPayment, pricingData); StarportLib.transferSpentItems(considerationPayment, lender, loan.issuer, false); if (carryPayment.length > 0) { StarportLib.transferSpentItems(carryPayment, lender, loan.originator, false); } + loan.debt = applyRefinanceConsiderationToLoan(considerationPayment, carryPayment); + loan.terms.pricingData = pricingData; loan.issuer = lender; loan.originator = address(0); @@ -245,39 +246,44 @@ contract Starport is PausableNonReentrant { } } - function applyRefinanceConsiderationToLoan( - Starport.Loan memory loan, - SpentItem[] memory considerationPayment, - SpentItem[] memory carryPayment, - bytes calldata pricingData - ) public pure returns (Starport.Loan memory) { + function applyRefinanceConsiderationToLoan(SpentItem[] memory considerationPayment, SpentItem[] memory carryPayment) + public + pure + returns (SpentItem[] memory newDebt) + { if ( considerationPayment.length == 0 || (carryPayment.length != 0 && considerationPayment.length != carryPayment.length) - || considerationPayment.length != loan.debt.length ) { revert MalformedRefinance(); } - uint256 i = 0; if (carryPayment.length > 0) { + SpentItem[] memory newDebt = new SpentItem[](considerationPayment.length); + uint256 i = 0; for (; i < considerationPayment.length;) { - loan.debt[i].amount = considerationPayment[i].amount + carryPayment[i].amount; - + newDebt[i] = considerationPayment[i]; + newDebt[i].amount += carryPayment[i].amount; + if (newDebt[i].itemType == ItemType.ERC721 && newDebt[i].amount > 1) { + revert MalformedRefinance(); + } unchecked { ++i; } } + return newDebt; } else { + uint256 i = 0; for (; i < considerationPayment.length;) { - loan.debt[i].amount = considerationPayment[i].amount; + if (considerationPayment[i].itemType == ItemType.ERC721 && considerationPayment[i].amount > 1) { + revert MalformedRefinance(); + } unchecked { ++i; } } + return considerationPayment; } - loan.terms.pricingData = pricingData; - return loan; } /** diff --git a/src/pricing/SimpleInterestPricing.sol b/src/pricing/SimpleInterestPricing.sol index 507a1c8d..97906cde 100644 --- a/src/pricing/SimpleInterestPricing.sol +++ b/src/pricing/SimpleInterestPricing.sol @@ -65,7 +65,7 @@ contract SimpleInterestPricing is BasePricing { Details memory oldDetails = abi.decode(loan.terms.pricingData, (Details)); Details memory newDetails = abi.decode(newPricingData, (Details)); - if ((newDetails.rate < oldDetails.rate)) { + if (oldDetails.decimals == newDetails.decimals && (newDetails.rate < oldDetails.rate)) { (repayConsideration, carryConsideration) = getPaymentConsideration(loan); additionalConsideration = new AdditionalTransfer[](0); } else { diff --git a/test/StarportTest.sol b/test/StarportTest.sol index 1b998b91..346a4148 100644 --- a/test/StarportTest.sol +++ b/test/StarportTest.sol @@ -483,16 +483,6 @@ contract StarportTest is BaseOrderTest { return refinanceLoan(loan, newPricingData, asWho, lenderCaveat, lender, ""); } - function getRefinanceCaveat(Starport.Loan memory loan, bytes memory pricingData, address fulfiller) - public - view - returns (Starport.Loan memory) - { - (SpentItem[] memory considerationPayment, SpentItem[] memory carryPayment,) = - Pricing(loan.terms.pricing).getRefinanceConsideration(loan, pricingData, fulfiller); - return SP.applyRefinanceConsiderationToLoan(loan, considerationPayment, carryPayment, pricingData); - } - function refinanceLoan( Starport.Loan memory loan, bytes memory pricingData, @@ -584,7 +574,11 @@ contract StarportTest is BaseOrderTest { (SpentItem[] memory offer, ReceivedItem[] memory paymentConsideration) = Custodian( payable(activeLoan.custodian) ).previewOrder( - address(consideration), fulfiller, new SpentItem[](0), new SpentItem[](0), abi.encode(activeLoan) + address(consideration), + fulfiller, + new SpentItem[](0), + new SpentItem[](0), + abi.encode(Custodian.Command(Actions.Settlement, activeLoan, "")) ); OrderParameters memory op = _buildContractOrder( diff --git a/test/fuzz-testing/TestFuzzStarport.sol b/test/fuzz-testing/TestFuzzStarport.sol index bc7b4f12..bd25afac 100644 --- a/test/fuzz-testing/TestFuzzStarport.sol +++ b/test/fuzz-testing/TestFuzzStarport.sol @@ -4,6 +4,63 @@ import "starport-test/utils/Bound.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; import {DeepEq} from "../utils/DeepEq.sol"; import {StarportLib} from "starport-core/lib/StarportLib.sol"; +import {ERC20 as RariERC20} from "@rari-capital/solmate/src/tokens/ERC20.sol"; + +contract TestDebt is RariERC20 { + bool public blocked; + + bool public noReturnData; + + constructor(uint8 decimals) RariERC20("Test20", "TST20", decimals) { + blocked = false; + noReturnData = false; + } + + function blockTransfer(bool blocking) external { + blocked = blocking; + } + + function setNoReturnData(bool noReturn) external { + noReturnData = noReturn; + } + + function mint(address to, uint256 amount) external returns (bool) { + _mint(to, amount); + return true; + } + + function transferFrom(address from, address to, uint256 amount) public override returns (bool ok) { + if (blocked) { + return false; + } + + uint256 allowed = allowance[from][msg.sender]; + + if (amount > allowed) { + revert("NOT_AUTHORIZED"); + } + + super.transferFrom(from, to, amount); + + if (noReturnData) { + assembly { + return(0, 0) + } + } + + ok = true; + } + + function increaseAllowance(address spender, uint256 amount) external returns (bool) { + uint256 current = allowance[msg.sender][spender]; + uint256 remaining = type(uint256).max - current; + if (amount > remaining) { + amount = remaining; + } + approve(spender, current + amount); + return true; + } +} contract TestFuzzStarport is StarportTest, Bound, DeepEq { using FixedPointMathLib for uint256; @@ -55,7 +112,7 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq { BasePricing.Details memory details = BasePricing.Details({ rate: _boundMax(min, (uint256(1e16) * 150)), carryRate: _boundMax(0, uint256((1e16 * 100))), - decimals: 18 + decimals: _boundMax(0, 18) }); pricingData = abi.encode(details); } @@ -147,12 +204,24 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq { } loan.collateral = ret; SpentItem[] memory debt = new SpentItem[](1); - debt[0] = SpentItem({ - itemType: ItemType.ERC20, - identifier: 0, - amount: _boundMax(params.debtAmount, type(uint112).max), - token: address(erc20s[1]) - }); + BasePricing.Details memory pricingDetails = abi.decode(loan.terms.pricingData, (BasePricing.Details)); + if (pricingDetails.decimals == 18) { + debt[0] = SpentItem({ + itemType: ItemType.ERC20, + identifier: 0, + amount: _boundMax(params.debtAmount, type(uint128).max), + token: address(erc20s[1]) + }); + } else { + TestDebt newDebt = new TestDebt(uint8(pricingDetails.decimals)); + debt[0] = SpentItem({ + itemType: ItemType.ERC20, + identifier: 0, + amount: _boundMax(params.debtAmount, type(uint128).max), + token: address(newDebt) + }); + } + loan.debt = debt; loan.borrower = borrower.addr; loan.custodian = SP.defaultCustodian(); @@ -216,17 +285,19 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq { } else { fulfiller = _toAddress(_boundMin(_toUint(params.fulfiller), 100)); } - uint256 borrowerDebtBalanceBefore = erc20s[1].balanceOf(loan.borrower); + uint256 borrowerDebtBalanceBefore = ERC20(loan.debt[0].token).balanceOf(loan.borrower); goodLoan = newLoan(loan, borrowerSalt, lenderSalt, fulfiller); if (params.feesOn) { assert( - erc20s[1].balanceOf(loan.borrower) + ERC20(loan.debt[0].token).balanceOf(loan.borrower) == (borrowerDebtBalanceBefore + (loan.debt[0].amount - loan.debt[0].amount.mulWad(feeRake))) ); } else { - assert(erc20s[1].balanceOf(loan.borrower) == (borrowerDebtBalanceBefore + loan.debt[0].amount)); + assert( + ERC20(loan.debt[0].token).balanceOf(loan.borrower) == (borrowerDebtBalanceBefore + loan.debt[0].amount) + ); } } @@ -349,16 +420,22 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq { extraData: abi.encode(Custodian.Command(Actions.Repayment, badLoan, "")) }); + vm.startPrank(badLoan.borrower); + for (uint256 i = 0; i < paymentConsideration.length; i++) { + TestDebt token = TestDebt(paymentConsideration[i].token); + token.mint(goodLoan.borrower, paymentConsideration[i].amount); + token.approve(address(consideration), type(uint256).max); + } if (keccak256(abi.encode(goodLoan)) != keccak256(abi.encode(badLoan))) { vm.expectRevert(); } - vm.prank(badLoan.borrower); consideration.fulfillAdvancedOrder({ advancedOrder: x, criteriaResolvers: new CriteriaResolver[](0), fulfillerConduitKey: bytes32(0), recipient: address(badLoan.borrower) }); + vm.stopPrank(); } function testFuzzRepaymentSuccess(FuzzRepaymentLoan memory params) public { @@ -373,9 +450,6 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq { new SpentItem[](0), abi.encode(Custodian.Command(Actions.Repayment, goodLoan, "")) ); - for (uint256 i = 0; i < paymentConsideration.length; i++) { - erc20s[0].mint(goodLoan.borrower, paymentConsideration[i].amount); - } OrderParameters memory op = _buildContractOrder( address(goodLoan.custodian), _SpentItemsToOfferItems(offer), _toConsiderationItems(paymentConsideration) @@ -389,7 +463,11 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq { }); vm.startPrank(goodLoan.borrower); - erc20s[0].approve(address(consideration), type(uint256).max); + for (uint256 i = 0; i < paymentConsideration.length; i++) { + TestDebt token = TestDebt(paymentConsideration[i].token); + token.mint(goodLoan.borrower, paymentConsideration[i].amount); + token.approve(address(consideration), type(uint256).max); + } consideration.fulfillAdvancedOrder({ advancedOrder: x, criteriaResolvers: new CriteriaResolver[](0), @@ -429,16 +507,22 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq { extraData: abi.encode(Actions.Settlement, badLoan) }); + vm.startPrank(badLoan.borrower); + for (uint256 i = 0; i < paymentConsideration.length; i++) { + TestDebt token = TestDebt(paymentConsideration[i].token); + token.mint(goodLoan.borrower, paymentConsideration[i].amount); + token.approve(address(consideration), type(uint256).max); + } if (keccak256(abi.encode(goodLoan)) != keccak256(abi.encode(badLoan))) { vm.expectRevert(); } - vm.prank(badLoan.borrower); consideration.fulfillAdvancedOrder({ advancedOrder: x, criteriaResolvers: new CriteriaResolver[](0), fulfillerConduitKey: bytes32(0), recipient: address(badLoan.borrower) }); + vm.stopPrank(); } function _generateGoodLoan(FuzzLoan memory params) internal virtual returns (Starport.Loan memory) { @@ -487,7 +571,11 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq { }); vm.startPrank(filler); - erc20s[1].approve(address(consideration), type(uint256).max); + for (uint256 i = 0; i < paymentConsideration.length; i++) { + TestDebt token = TestDebt(paymentConsideration[i].token); + token.mint(filler, paymentConsideration[i].amount); + token.approve(address(consideration), type(uint256).max); + } consideration.fulfillAdvancedOrder({ advancedOrder: x, criteriaResolvers: new CriteriaResolver[](0), @@ -500,11 +588,14 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq { function testFuzzRefinance(FuzzRefinanceLoan memory params) public virtual { Starport.Loan memory goodLoan = fuzzNewLoanOrigination(params.origination, abi.encode(LoanBounds(1))); - uint256 oldRate = abi.decode(goodLoan.terms.pricingData, (BasePricing.Details)).rate; + BasePricing.Details memory oldDetails = abi.decode(goodLoan.terms.pricingData, (BasePricing.Details)); - uint256 newRate = _boundMax(oldRate - 1, (uint256(1e16) * 1000) / (365 * 1 days)); - BasePricing.Details memory newPricingDetails = - BasePricing.Details({rate: newRate, carryRate: _boundMax(0, uint256((1e16 * 100))), decimals: 18}); + uint256 newRate = _boundMax(oldDetails.rate - 1, (uint256(1e16) * 1000) / (365 * 1 days)); + BasePricing.Details memory newPricingDetails = BasePricing.Details({ + rate: newRate, + carryRate: _boundMax(0, uint256((1e16 * 100))), + decimals: oldDetails.decimals + }); Account memory account = makeAndAllocateAccount(params.refiKey); address refiFulfiller; @@ -525,11 +616,10 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq { refiFulfiller = _toAddress(_boundMin(params.skipTime, 100)); } Starport.Loan memory goodLoan2 = goodLoan; - LenderEnforcer.Details memory details = LenderEnforcer.Details({ - loan: SP.applyRefinanceConsiderationToLoan( - goodLoan2, considerationPayment, carryPayment, abi.encode(newPricingDetails) - ) - }); + Starport.Loan memory refiLoan = loanCopy(goodLoan); + refiLoan.terms.pricingData = abi.encode(newPricingDetails); + refiLoan.debt = SP.applyRefinanceConsiderationToLoan(considerationPayment, carryPayment); + LenderEnforcer.Details memory details = LenderEnforcer.Details({loan: refiLoan}); _issueAndApproveTarget(details.loan.debt, account.addr, address(SP)); details.loan.issuer = account.addr; @@ -543,7 +633,7 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq { enforcer: address(lenderEnforcer) }); { - if (newRate > oldRate) { + if (newRate > oldDetails.rate) { vm.expectRevert(); } vm.prank(refiFulfiller); diff --git a/test/integration-testing/TestCaveats.sol b/test/integration-testing/TestCaveats.sol index 4509d283..4479896a 100644 --- a/test/integration-testing/TestCaveats.sol +++ b/test/integration-testing/TestCaveats.sol @@ -185,10 +185,9 @@ contract IntegrationTestCaveats is StarportTest, DeepEq, MockCall { function testRefinanceWCaveatsInvalidSalt() public { Starport.Loan memory loan = newLoanWithDefaultTerms(); - - LenderEnforcer.Details memory details = LenderEnforcer.Details({ - loan: SP.applyRefinanceConsiderationToLoan(loan, loan.debt, new SpentItem[](0), defaultPricingData) - }); + Starport.Loan memory refiLoan = loanCopy(loan); + refiLoan.debt = SP.applyRefinanceConsiderationToLoan(loan.debt, new SpentItem[](0)); + LenderEnforcer.Details memory details = LenderEnforcer.Details({loan: refiLoan}); details.loan.issuer = lender.addr; details.loan.originator = address(0); @@ -238,9 +237,10 @@ contract IntegrationTestCaveats is StarportTest, DeepEq, MockCall { function testRefinanceUnapprovedFulfiller() public { Starport.Loan memory loan = newLoanWithDefaultTerms(); - LenderEnforcer.Details memory details = LenderEnforcer.Details({ - loan: SP.applyRefinanceConsiderationToLoan(loan, loan.debt, new SpentItem[](0), defaultPricingData) - }); + Starport.Loan memory refiLoan = loanCopy(loan); + + refiLoan.debt = SP.applyRefinanceConsiderationToLoan(loan.debt, new SpentItem[](0)); + LenderEnforcer.Details memory details = LenderEnforcer.Details({loan: refiLoan}); details.loan.issuer = lender.addr; details.loan.originator = address(0); diff --git a/test/integration-testing/TestNewLoan.sol b/test/integration-testing/TestNewLoan.sol index 805a231e..5099ee4d 100644 --- a/test/integration-testing/TestNewLoan.sol +++ b/test/integration-testing/TestNewLoan.sol @@ -104,11 +104,12 @@ contract TestNewLoan is StarportTest { function testNewLoanRefinance() public { Starport.Loan memory loan = testNewLoanERC721CollateralDefaultTerms2(); + Starport.Loan memory refiLoan = loanCopy(loan); bytes memory newPricingData = abi.encode(BasePricing.Details({carryRate: (uint256(1e16) * 10), rate: uint256(1e16) * 100, decimals: 18})); - LenderEnforcer.Details memory details = LenderEnforcer.Details({ - loan: SP.applyRefinanceConsiderationToLoan(loan, loan.debt, new SpentItem[](0), newPricingData) - }); + refiLoan.terms.pricingData = newPricingData; + refiLoan.debt = SP.applyRefinanceConsiderationToLoan(loan.debt, new SpentItem[](0)); + LenderEnforcer.Details memory details = LenderEnforcer.Details({loan: refiLoan}); details.loan.issuer = refinancer.addr; details.loan.originator = address(0); diff --git a/test/unit-testing/TestStarport.sol b/test/unit-testing/TestStarport.sol index b28ba122..60dd0892 100644 --- a/test/unit-testing/TestStarport.sol +++ b/test/unit-testing/TestStarport.sol @@ -9,6 +9,172 @@ import {SpentItemLib} from "seaport-sol/src/lib/SpentItemLib.sol"; import {PausableNonReentrant} from "starport-core/lib/PausableNonReentrant.sol"; import {Originator} from "starport-core/originators/Originator.sol"; +import {Validation} from "starport-core/lib/Validation.sol"; + +contract MockFixedTermDutchAuctionSettlement is DutchAuctionSettlement { + using {StarportLib.getId} for Starport.Loan; + using FixedPointMathLib for uint256; + + address public debtToken; + + constructor(Starport SP_, address debtToken_) DutchAuctionSettlement(SP_) { + debtToken = debtToken_; + } + + // @inheritdoc DutchAuctionSettlement + function getAuctionStart(Starport.Loan calldata loan) public view virtual override returns (uint256) { + FixedTermStatus.Details memory details = abi.decode(loan.terms.statusData, (FixedTermStatus.Details)); + return loan.start + details.loanDuration; + } + + function getSettlementConsideration(Starport.Loan calldata loan) + public + view + virtual + override + returns (ReceivedItem[] memory consideration, address authorized) + { + Details memory details = abi.decode(loan.terms.settlementData, (Details)); + + uint256 start = getAuctionStart(loan); + + // DutchAuction has failed, allow lender to redeem + if (start + details.window < block.timestamp) { + return (new ReceivedItem[](0), loan.issuer); + } + + uint256 settlementPrice = _locateCurrentAmount({ + startAmount: details.startingPrice, + endAmount: details.endingPrice, + startTime: start, + endTime: start + details.window, + roundUp: true + }); + + consideration = new ReceivedItem[](1); + + consideration[0] = ReceivedItem({ + itemType: ItemType.ERC20, + identifier: 0, + amount: settlementPrice, + token: debtToken, + recipient: payable(loan.issuer) + }); + } +} + +contract MockExoticPricing is Pricing { + error NoRefinance(); + + using FixedPointMathLib for uint256; + using {StarportLib.getId} for Starport.Loan; + + struct Details { + uint256 rate; + uint256 carryRate; + uint256 decimals; + address token; + uint256 debtAmount; + } + + constructor(Starport SP_) Pricing(SP_) {} + + function getPaymentConsideration(Starport.Loan calldata loan) + public + view + virtual + override + returns (SpentItem[] memory repayConsideration, SpentItem[] memory carryConsideration) + { + Details memory details = abi.decode(loan.terms.pricingData, (Details)); + if (details.carryRate > 0 && loan.issuer != loan.originator) { + carryConsideration = new SpentItem[](loan.debt.length + 1); + } else { + carryConsideration = new SpentItem[](0); + } + repayConsideration = new SpentItem[](loan.debt.length + 1); + + repayConsideration[0] = loan.debt[0]; + + if (carryConsideration.length > 1) { + SpentItem memory empty; + carryConsideration[0] = empty; + } + uint256 i = 0; + for (; i < loan.debt.length;) { + uint256 interest = + _getInterest(details.debtAmount, details.rate, loan.start, block.timestamp, details.decimals); + + if (carryConsideration.length > 0) { + carryConsideration[i + 1] = SpentItem({ + itemType: ItemType.ERC20, + identifier: 0, + amount: (interest * details.carryRate) / 10 ** details.decimals, + token: details.token + }); + repayConsideration[i + 1] = SpentItem({ + itemType: ItemType.ERC20, + identifier: 0, + amount: details.debtAmount + interest - carryConsideration[i].amount, + token: details.token + }); + } else { + repayConsideration[i + 1] = SpentItem({ + itemType: ItemType.ERC20, + identifier: 0, + amount: details.debtAmount + interest, + token: details.token + }); + } + unchecked { + ++i; + } + } + } + + function validate(Starport.Loan calldata loan) external view virtual override returns (bytes4) { + Details memory details = abi.decode(loan.terms.pricingData, (Details)); + return (details.decimals > 0) ? Validation.validate.selector : bytes4(0xFFFFFFFF); + } + + function getInterest(Starport.Loan calldata loan, uint256 end) public pure returns (uint256) { + Details memory details = abi.decode(loan.terms.pricingData, (Details)); + uint256 delta_t = end - loan.start; + return calculateInterest(delta_t, details.debtAmount, details.rate, details.decimals); + } + + function _getInterest(uint256 amount, uint256 rate, uint256 start, uint256 end, uint256 decimals) + public + pure + returns (uint256) + { + uint256 delta_t = end - start; + return calculateInterest(delta_t, amount, rate, decimals); + } + + function calculateInterest(uint256 delta_t, uint256 amount, uint256 rate, uint256 decimals) + public + pure + returns (uint256) + { + return StarportLib.calculateSimpleInterest(delta_t, amount, rate, decimals); + } + + function getRefinanceConsideration(Starport.Loan calldata loan, bytes memory newPricingData, address fulfiller) + external + view + virtual + override + returns ( + SpentItem[] memory repayConsideration, + SpentItem[] memory carryConsideration, + AdditionalTransfer[] memory additionalConsideration + ) + { + revert NoRefinance(); + } +} + contract MockOriginator is StrategistOriginator, TokenReceiverInterface { constructor(Starport SP_, address strategist_, uint256 fee_) StrategistOriginator(SP_, strategist_, fee_, msg.sender) @@ -162,8 +328,27 @@ contract TestStarport is StarportTest, DeepEq { } function testApplyRefinanceConsiderationToLoanMalformed() public { + //test for 721 witt more than 1 amount + SpentItem[] memory dummy721 = new SpentItem[](1); + dummy721[0] = SpentItem({token: address(0), amount: 2, identifier: 0, itemType: ItemType.ERC721}); + vm.expectRevert(Starport.MalformedRefinance.selector); + SP.applyRefinanceConsiderationToLoan(dummy721, new SpentItem[](0)); + + dummy721 = new SpentItem[](1); + dummy721[0] = SpentItem({token: address(0), amount: 1, identifier: 0, itemType: ItemType.ERC721}); + SP.applyRefinanceConsiderationToLoan(dummy721, new SpentItem[](0)); + + SpentItem[] memory dummyCarry721 = new SpentItem[](1); + dummyCarry721[0] = SpentItem({token: address(0), amount: 1, identifier: 0, itemType: ItemType.ERC721}); + + vm.expectRevert(Starport.MalformedRefinance.selector); + SP.applyRefinanceConsiderationToLoan(dummy721, dummyCarry721); + vm.expectRevert(Starport.MalformedRefinance.selector); - SP.applyRefinanceConsiderationToLoan(activeLoan, new SpentItem[](0), new SpentItem[](0), ""); + SP.applyRefinanceConsiderationToLoan(dummy721, dummyCarry721); + + vm.expectRevert(Starport.MalformedRefinance.selector); + SP.applyRefinanceConsiderationToLoan(new SpentItem[](0), new SpentItem[](0)); SpentItem[] memory dummy = new SpentItem[](1); dummy[0] = SpentItem({token: address(0), amount: 0, identifier: 0, itemType: ItemType.ERC20}); @@ -171,9 +356,12 @@ contract TestStarport is StarportTest, DeepEq { dummyCarry[0] = SpentItem({token: address(0), amount: 0, identifier: 0, itemType: ItemType.ERC20}); dummyCarry[1] = SpentItem({token: address(0), amount: 0, identifier: 0, itemType: ItemType.ERC20}); vm.expectRevert(Starport.MalformedRefinance.selector); - SP.applyRefinanceConsiderationToLoan(activeLoan, dummy, dummyCarry, ""); - vm.expectRevert(Starport.MalformedRefinance.selector); - SP.applyRefinanceConsiderationToLoan(activeLoan, dummyCarry, new SpentItem[](0), ""); + SP.applyRefinanceConsiderationToLoan(dummy, dummyCarry); + Starport.Loan memory goodLoan = activeLoan; + Starport.Loan memory testLoan = loanCopy(goodLoan); + testLoan.debt = dummyCarry; + goodLoan.debt = SP.applyRefinanceConsiderationToLoan(dummyCarry, new SpentItem[](0)); + assert(keccak256(abi.encode(testLoan)) == keccak256(abi.encode(goodLoan))); } function testInitializedFlagSetProperly() public { @@ -468,6 +656,99 @@ contract TestStarport is StarportTest, DeepEq { SP.originate(new AdditionalTransfer[](0), borrowerEnforcer, lenderEnforcer, loan); } + function testExoticDebtWithCustomPricingAndRepayment() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + + MockExoticPricing mockPricing = new MockExoticPricing(SP); + + loan.terms.pricing = address(mockPricing); + loan.terms.pricingData = abi.encode( + MockExoticPricing.Details({ + token: address(erc20s[0]), + debtAmount: 100, + rate: 100, + carryRate: 100, + decimals: 18 + }) + ); + + SpentItem[] memory exoticDebt = new SpentItem[](1); + exoticDebt[0] = SpentItem({token: address(erc721s[2]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); + + loan.debt = exoticDebt; + loan.collateral[0] = + SpentItem({token: address(erc721s[0]), amount: 1, identifier: 2, itemType: ItemType.ERC721}); + CaveatEnforcer.SignedCaveats memory borrowerEnforcer; + CaveatEnforcer.SignedCaveats memory lenderEnforcer = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, + salt: bytes32(0), + enforcer: address(lenderEnforcer) + }); + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + Starport.Loan memory loanCopy = abi.decode(abi.encode(loan), (Starport.Loan)); + loanCopy.start = block.timestamp; + loanCopy.originator = address(loan.borrower); + vm.expectEmit(); + emit Open(loanCopy.getId(), loanCopy); + vm.prank(loan.borrower); + SP.originate(new AdditionalTransfer[](0), borrowerEnforcer, lenderEnforcer, loan); + loan.start = block.timestamp; + loan.originator = address(loan.borrower); + + skip(100); + + _executeRepayLoan(loan, loan.borrower); + } + + function testExoticDebtWithCustomPricingAndSettlement() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + + MockExoticPricing mockPricing = new MockExoticPricing(SP); + MockFixedTermDutchAuctionSettlement mockSettlement = + new MockFixedTermDutchAuctionSettlement(SP, address(erc20s[0])); + loan.terms.settlement = address(mockSettlement); + loan.terms.pricing = address(mockPricing); + loan.terms.pricingData = abi.encode( + MockExoticPricing.Details({ + token: address(erc20s[0]), + debtAmount: 100, + rate: 100, + carryRate: 100, + decimals: 18 + }) + ); + SpentItem[] memory exoticDebt = new SpentItem[](1); + exoticDebt[0] = SpentItem({token: address(erc721s[2]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); + + loan.debt = exoticDebt; + loan.collateral[0] = + SpentItem({token: address(erc721s[0]), amount: 1, identifier: 2, itemType: ItemType.ERC721}); + CaveatEnforcer.SignedCaveats memory borrowerEnforcer; + CaveatEnforcer.SignedCaveats memory lenderEnforcer = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, + salt: bytes32(0), + enforcer: address(lenderEnforcer) + }); + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + Starport.Loan memory loanCopy = abi.decode(abi.encode(loan), (Starport.Loan)); + loanCopy.start = block.timestamp; + loanCopy.originator = address(loan.borrower); + vm.expectEmit(); + emit Open(loanCopy.getId(), loanCopy); + vm.prank(loan.borrower); + SP.originate(new AdditionalTransfer[](0), borrowerEnforcer, lenderEnforcer, loan); + loan.start = block.timestamp; + loan.originator = address(loan.borrower); + + skip(abi.decode(loan.terms.statusData, (FixedTermStatus.Details)).loanDuration + 1); + + _settleLoan(loan, loan.borrower); + } + function testNonPayableFunctions() public { CaveatEnforcer.SignedCaveats memory be; vm.expectRevert();