From 2364ed7a2721b81ee1b718f5d0c3b7cfa33201c4 Mon Sep 17 00:00:00 2001 From: androolloyd Date: Thu, 2 Nov 2023 09:46:57 -0300 Subject: [PATCH 1/3] Feat/ast 1952 v1 handler testing (#41) * update getAuctionStart to be a public helper, make an internal version that is more optimized(less calls/decoding), add tests * coverage testing for AstariaV1Settlement handler complete * coverage testing for AstariaV1Settlement handler complete * fix test name --- .gas-snapshot | 28 +- src/handlers/AstariaV1SettlementHandler.sol | 52 +++- src/handlers/DutchAuctionHandler.sol | 8 +- src/handlers/FixedTermDutchAuctionHandler.sol | 2 +- .../integration-testing/TestAstariaV1Loan.sol | 4 +- test/unit-testing/TestV1Handler.sol | 265 ++++++++++++++++++ 6 files changed, 335 insertions(+), 24 deletions(-) create mode 100644 test/unit-testing/TestV1Handler.sol diff --git a/.gas-snapshot b/.gas-snapshot index 9a6a745c..7fa31faa 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,8 +1,8 @@ -DiffFuzzTestStarPortLib:testSpentToReceived((uint8,address,uint256,uint256)[]) (runs: 256, μ: 880483, ~: 882762) -DiffFuzzTestStarPortLib:testUnboundSpentToReceived((uint8,address,uint256,uint256)[]) (runs: 256, μ: 232905, ~: 237832) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallBase() (gas: 1028726) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLender() (gas: 693092) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLiquidation() (gas: 763444) +DiffFuzzTestStarPortLib:testSpentToReceived((uint8,address,uint256,uint256)[]) (runs: 256, μ: 880529, ~: 882914) +DiffFuzzTestStarPortLib:testUnboundSpentToReceived((uint8,address,uint256,uint256)[]) (runs: 256, μ: 232899, ~: 237832) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallBase() (gas: 1011545) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLender() (gas: 693342) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLiquidation() (gas: 746648) TestBorrowerEnforcer:testBERevertAdditionalTransfers() (gas: 73101) TestBorrowerEnforcer:testBERevertInvalidLoanTerms() (gas: 78338) TestBorrowerEnforcer:testBEValidLoanTerms() (gas: 69429) @@ -16,13 +16,13 @@ TestCustodian:testGenerateOrderInvalidHandlerExecution() (gas: 132842) TestCustodian:testGenerateOrderRepay() (gas: 173845) TestCustodian:testGenerateOrderRepayAsRepayApprovedBorrower() (gas: 199470) TestCustodian:testGenerateOrderRepayERC1155AndERC20AndNative() (gas: 864690) -TestCustodian:testGenerateOrderRepayERC1155AndERC20AndNativeHandlerAuthorized() (gas: 790047) +TestCustodian:testGenerateOrderRepayERC1155AndERC20AndNativeHandlerAuthorized() (gas: 789937) TestCustodian:testGenerateOrderRepayERC1155WithRevert() (gas: 529819) TestCustodian:testGenerateOrderRepayInvalidHookAddress() (gas: 90336) TestCustodian:testGenerateOrderRepayInvalidHookReturnType() (gas: 84653) TestCustodian:testGenerateOrderRepayNotBorrower() (gas: 96427) TestCustodian:testGenerateOrderSettlement() (gas: 151465) -TestCustodian:testGenerateOrderSettlementHandlerAuthorized() (gas: 160505) +TestCustodian:testGenerateOrderSettlementHandlerAuthorized() (gas: 160461) TestCustodian:testGenerateOrderSettlementNoActiveLoan() (gas: 155682) TestCustodian:testGenerateOrderSettlementUnauthorized() (gas: 94206) TestCustodian:testGetBorrower() (gas: 76322) @@ -99,12 +99,19 @@ TestRefStarPortLib:testValidateSalt(address,bytes32) (runs: 256, μ: 33865, ~: 3 TestRepayLoan:testRepayLoanApprovedRepayer() (gas: 643615) TestRepayLoan:testRepayLoanBase() (gas: 580019) TestRepayLoan:testRepayLoanGenerateOrderNotSeaport() (gas: 399854) -TestRepayLoan:testRepayLoanInSettlement() (gas: 544869) +TestRepayLoan:testRepayLoanInSettlement() (gas: 540930) TestRepayLoan:testRepayLoanInvalidRepayer() (gas: 560285) TestRepayLoan:testRepayLoanThatDoesNotExist() (gas: 848820) TestStarPortLib:testSpentToReceived() (gas: 13315) TestStarPortLib:testValidateSalt(address,bytes32) (runs: 256, μ: 33865, ~: 33865) TestStrategistOriginator:testEncodeWithAccountCounter() (gas: 12307) +TestStrategistOriginator:testGetAuctionStart() (gas: 424899) +TestStrategistOriginator:testGetAuctionStartNotStarted() (gas: 424491) +TestStrategistOriginator:testGetCurrentAuctionPrice() (gas: 439776) +TestStrategistOriginator:testGetCurrentAuctionPriceNoAuction() (gas: 428100) +TestStrategistOriginator:testGetSettlementDutchAuctionSettlementAbove() (gas: 482308) +TestStrategistOriginator:testGetSettlementFailedDutchAuction() (gas: 440099) +TestStrategistOriginator:testGetSettlementLoanNotRecalled() (gas: 430598) TestStrategistOriginator:testGetStrategistData() (gas: 1473217) TestStrategistOriginator:testIncrementCounterAsStrategist() (gas: 18654) TestStrategistOriginator:testIncrementCounterNotAuthorized() (gas: 13445) @@ -117,4 +124,7 @@ TestStrategistOriginator:testInvalidDebtAmountRequestingZero() (gas: 206823) TestStrategistOriginator:testInvalidDebtLength() (gas: 205450) TestStrategistOriginator:testInvalidOffer() (gas: 396393) TestStrategistOriginator:testInvalidSigner() (gas: 208683) -TestStrategistOriginator:testSetStrategist() (gas: 17796) \ No newline at end of file +TestStrategistOriginator:testSetStrategist() (gas: 17796) +TestStrategistOriginator:testV1SettlementHandlerExecute() (gas: 411108) +TestStrategistOriginator:testV1SettlementHandlerValidate() (gas: 411220) +TestStrategistOriginator:testV1SettlementHandlerValidateInvalidHandler() (gas: 411344) \ No newline at end of file diff --git a/src/handlers/AstariaV1SettlementHandler.sol b/src/handlers/AstariaV1SettlementHandler.sol index 7a9d2500..edeed16a 100644 --- a/src/handlers/AstariaV1SettlementHandler.sol +++ b/src/handlers/AstariaV1SettlementHandler.sol @@ -6,7 +6,7 @@ import {BaseRecall} from "starport-core/hooks/BaseRecall.sol"; import {DutchAuctionHandler} from "starport-core/handlers/DutchAuctionHandler.sol"; import {StarPortLib} from "starport-core/lib/StarPortLib.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; -import "forge-std/console2.sol"; + import {Pricing} from "starport-core/pricing/Pricing.sol"; import {BasePricing} from "starport-core/pricing/BasePricing.sol"; @@ -16,12 +16,45 @@ contract AstariaV1SettlementHandler is DutchAuctionHandler { constructor(LoanManager LM_) DutchAuctionHandler(LM_) {} - function _getAuctionStart(LoanManager.Loan memory loan) internal view virtual override returns (uint256) { + error NoAuction(); + error LoanNotRecalled(); + error ExecuteHandlerNotImplemented(); + error InvalidHandler(); + + function getCurrentAuctionPrice(LoanManager.Loan calldata loan) public view virtual returns (uint256) { + (address recaller, uint64 recallStart) = BaseRecall(loan.terms.hook).recalls(loan.getId()); + if (recaller == loan.issuer || recallStart == uint256(0) || recaller == address(0)) { + revert NoAuction(); + } + + uint256 start = _getAuctionStart(loan, recallStart); + + Details memory details = abi.decode(loan.terms.handlerData, (Details)); + + return _locateCurrentAmount({ + startAmount: details.startingPrice, + endAmount: details.endingPrice, + startTime: start, + endTime: start + details.window, + roundUp: true + }); + } + + function getAuctionStart(LoanManager.Loan calldata loan) public view virtual override returns (uint256) { (, uint64 start) = BaseRecall(loan.terms.hook).recalls(loan.getId()); + if (start == 0) { + revert LoanNotRecalled(); + } BaseRecall.Details memory details = abi.decode(loan.terms.hookData, (BaseRecall.Details)); return start + details.recallWindow + 1; } + function _getAuctionStart(LoanManager.Loan calldata loan, uint64 start) internal view virtual returns (uint256) { + BaseRecall.Details memory details = abi.decode(loan.terms.hookData, (BaseRecall.Details)); + + return start + details.recallWindow + 1; + } + function getSettlement(LoanManager.Loan calldata loan) public view @@ -29,13 +62,16 @@ contract AstariaV1SettlementHandler is DutchAuctionHandler { override returns (ReceivedItem[] memory consideration, address restricted) { - (address recaller,) = BaseRecall(loan.terms.hook).recalls(loan.getId()); + (address recaller, uint64 recallStart) = BaseRecall(loan.terms.hook).recalls(loan.getId()); + if (recaller == address(0) || recallStart == uint256(0)) { + revert LoanNotRecalled(); + } if (recaller == loan.issuer) { return (new ReceivedItem[](0), recaller); } - uint256 start = _getAuctionStart(loan); + uint256 start = _getAuctionStart(loan, recallStart); Details memory details = abi.decode(loan.terms.handlerData, (Details)); // DutchAuction has failed, give the NFT back to the lender (if they want it 😐) @@ -121,10 +157,14 @@ contract AstariaV1SettlementHandler is DutchAuctionHandler { } function execute(LoanManager.Loan calldata loan, address fulfiller) external virtual override returns (bytes4) { - return SettlementHandler.execute.selector; + revert ExecuteHandlerNotImplemented(); } function validate(LoanManager.Loan calldata loan) external view virtual override returns (bool) { - return true; + if (loan.terms.handler != address(this)) { + revert InvalidHandler(); + } + Details memory details = abi.decode(loan.terms.handlerData, (Details)); //will revert if this fails + return (details.startingPrice > details.endingPrice); } } diff --git a/src/handlers/DutchAuctionHandler.sol b/src/handlers/DutchAuctionHandler.sol index bbc106f1..37467cb1 100644 --- a/src/handlers/DutchAuctionHandler.sol +++ b/src/handlers/DutchAuctionHandler.sol @@ -13,11 +13,9 @@ import {AmountDeriver} from "seaport-core/src/lib/AmountDeriver.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; import {LoanManager, SettlementHandler} from "starport-core/handlers/SettlementHandler.sol"; -import {ConduitHelper} from "starport-core/ConduitHelper.sol"; -import "forge-std/console2.sol"; import {BasePricing} from "starport-core/pricing/BasePricing.sol"; -abstract contract DutchAuctionHandler is SettlementHandler, AmountDeriver, ConduitHelper { +abstract contract DutchAuctionHandler is SettlementHandler, AmountDeriver { constructor(LoanManager LM_) SettlementHandler(LM_) { LM = LM_; } @@ -36,7 +34,7 @@ abstract contract DutchAuctionHandler is SettlementHandler, AmountDeriver, Condu return SettlementHandler.execute.selector; } - function _getAuctionStart(LoanManager.Loan memory loan) internal view virtual returns (uint256); + function getAuctionStart(LoanManager.Loan calldata loan) public view virtual returns (uint256); function getSettlement(LoanManager.Loan calldata loan) public @@ -47,7 +45,7 @@ abstract contract DutchAuctionHandler is SettlementHandler, AmountDeriver, Condu { Details memory details = abi.decode(loan.terms.handlerData, (Details)); - uint256 start = _getAuctionStart(loan); + uint256 start = getAuctionStart(loan); // DutchAuction has failed, allow lender to redeem if (start + details.window < block.timestamp) { diff --git a/src/handlers/FixedTermDutchAuctionHandler.sol b/src/handlers/FixedTermDutchAuctionHandler.sol index 707fc5a9..a2b068f0 100644 --- a/src/handlers/FixedTermDutchAuctionHandler.sol +++ b/src/handlers/FixedTermDutchAuctionHandler.sol @@ -13,7 +13,7 @@ contract FixedTermDutchAuctionHandler is DutchAuctionHandler { constructor(LoanManager LM_) DutchAuctionHandler(LM_) {} - function _getAuctionStart(LoanManager.Loan memory loan) internal view virtual override returns (uint256) { + function getAuctionStart(LoanManager.Loan calldata loan) public view virtual override returns (uint256) { FixedTermHook.Details memory details = abi.decode(loan.terms.hookData, (FixedTermHook.Details)); return loan.start + details.loanDuration; } diff --git a/test/integration-testing/TestAstariaV1Loan.sol b/test/integration-testing/TestAstariaV1Loan.sol index 4d571f9a..b74c98fb 100644 --- a/test/integration-testing/TestAstariaV1Loan.sol +++ b/test/integration-testing/TestAstariaV1Loan.sol @@ -141,7 +141,7 @@ contract TestAstariaV1Loan is AstariaV1Test { console.logBytes32( LM.hashCaveatWithSaltAndNonce(refinancer.addr, bytes32(uint256(1)), refinancerCaveat.caveat) ); - emit log_caveatapproval(refinancerCaveat); + vm.startPrank(refinancer.addr); erc20s[0].approve(address(LM), refinanceDetails.loan.debt[0].amount); vm.stopPrank(); @@ -221,8 +221,6 @@ contract TestAstariaV1Loan is AstariaV1Test { } } - event log_caveatapproval(CaveatEnforcer.CaveatWithApproval caveatApproval); - // lender is recaller, liquidation amount is 0 function testNewLoanERC721CollateralDefaultTermsRecallLender() public { LoanManager.Terms memory terms = LoanManager.Terms({ diff --git a/test/unit-testing/TestV1Handler.sol b/test/unit-testing/TestV1Handler.sol new file mode 100644 index 00000000..3468c634 --- /dev/null +++ b/test/unit-testing/TestV1Handler.sol @@ -0,0 +1,265 @@ +pragma solidity ^0.8.17; + +import "starport-test/AstariaV1Test.sol"; +import {StarPortLib, Actions} from "starport-core/lib/StarPortLib.sol"; +import {DeepEq} from "starport-test/utils/DeepEq.sol"; +import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; +import {SpentItemLib} from "seaport-sol/src/lib/SpentItemLib.sol"; +import {Originator} from "starport-core/originators/Originator.sol"; +import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; +import "forge-std/console2.sol"; + +contract TestAstariaV1Handler is AstariaV1Test, DeepEq { + using Cast for *; + using FixedPointMathLib for uint256; + + using {StarPortLib.getId} for LoanManager.Loan; + // recaller is not the lender, liquidation amount is a dutch auction + + function testGetSettlementFailedDutchAuction() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + uint256 loanId = loan.getId(); + + bytes4 recallsSelector = bytes4(keccak256("recalls(uint256)")); + vm.mockCall( + address(loan.terms.hook), + abi.encodeWithSelector(recallsSelector, loanId), + abi.encode(address(this), uint64(2)) + ); + uint256 auctionStart = AstariaV1SettlementHandler(loan.terms.handler).getAuctionStart(loan); + DutchAuctionHandler.Details memory details = abi.decode(loan.terms.handlerData, (DutchAuctionHandler.Details)); + + vm.warp(auctionStart + details.window + 5); + (ReceivedItem[] memory settlementConsideration, address restricted) = + SettlementHandler(loan.terms.handler).getSettlement(loan); + assertEq(settlementConsideration.length, 0, "Settlement consideration should be empty"); + assertEq(restricted, address(loan.issuer), "Restricted address should be loan.issuer"); + } + + function testGetSettlementLoanNotRecalled() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + uint256 loanId = loan.getId(); + + vm.expectRevert(abi.encodeWithSelector(AstariaV1SettlementHandler.LoanNotRecalled.selector)); + SettlementHandler(loan.terms.handler).getSettlement(loan); + } + + function testGetSettlementDutchAuctionSettlementAbove() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + uint256 loanId = loan.getId(); + + bytes4 recallsSelector = bytes4(keccak256("recalls(uint256)")); + vm.mockCall( + address(loan.terms.hook), + abi.encodeWithSelector(recallsSelector, loanId), + abi.encode(address(this), uint64(2)) + ); + + BasePricing.Details memory pricingDetails = abi.decode(loan.terms.pricingData, (BasePricing.Details)); + + vm.warp(AstariaV1SettlementHandler(loan.terms.handler).getAuctionStart(loan)); + skip(7 days - 1300); + uint256 currentAuctionPrice = AstariaV1SettlementHandler(loan.terms.handler).getCurrentAuctionPrice(loan); + + vm.mockCall( + loan.terms.pricing, + abi.encodeWithSelector( + BasePricing.getInterest.selector, loan, pricingDetails.rate, loan.start, block.timestamp, 0 + ), + abi.encode(currentAuctionPrice - loan.debt[0].amount + 1) + ); + + uint256 interest = + BasePricing(loan.terms.pricing).getInterest(loan, pricingDetails.rate, loan.start, block.timestamp, 0); + uint256 carry = interest.mulWad(pricingDetails.carryRate); + (ReceivedItem[] memory settlementConsideration, address restricted) = + SettlementHandler(loan.terms.handler).getSettlement(loan); + BaseRecall.Details memory hookDetails = abi.decode(loan.terms.hookData, (BaseRecall.Details)); + + assertEq( + settlementConsideration[0].amount, + currentAuctionPrice - loan.debt[0].amount + interest - carry, + "Settlement 0 (originator payment) incorrect" + ); + assertEq( + settlementConsideration[1].amount, + (currentAuctionPrice - settlementConsideration[0].amount).mulWad(hookDetails.recallerRewardRatio), + "Settlement 1 (recaller reward) incorrect" + ); + assertEq( + settlementConsideration[2].amount, + currentAuctionPrice - settlementConsideration[0].amount - settlementConsideration[1].amount, + "Settlement 2 (issuer payment) incorrect" + ); + assertEq(settlementConsideration.length, 3, "Settlement consideration should have 3 elements"); + assertEq(restricted, address(0), "Restricted address should be address(0)"); + } + + function testGetAuctionStartNotStarted() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + + vm.expectRevert(abi.encodeWithSelector(AstariaV1SettlementHandler.LoanNotRecalled.selector)); + AstariaV1SettlementHandler(loan.terms.handler).getAuctionStart(loan); + } + + function testGetAuctionStart() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + uint256 loanId = loan.getId(); + + bytes4 recallsSelector = bytes4(keccak256("recalls(uint256)")); + uint256 recallStart = uint256(2); + vm.mockCall( + address(loan.terms.hook), + abi.encodeWithSelector(recallsSelector, loanId), + abi.encode(address(this), recallStart) + ); + + BaseRecall.Details memory details = abi.decode(loan.terms.hookData, (BaseRecall.Details)); + uint256 auctionStart = recallStart + details.recallWindow + 1; + + assertEq( + auctionStart, AstariaV1SettlementHandler(loan.terms.handler).getAuctionStart(loan), "start times dont match" + ); + } + + function testGetCurrentAuctionPrice() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + uint256 loanId = loan.getId(); + + bytes4 recallsSelector = bytes4(keccak256("recalls(uint256)")); + vm.mockCall( + address(loan.terms.hook), + abi.encodeWithSelector(recallsSelector, loanId), + abi.encode(address(this), uint64(2)) + ); + + DutchAuctionHandler.Details memory handlerDetails = + abi.decode(loan.terms.handlerData, (DutchAuctionHandler.Details)); + + vm.warp(AstariaV1SettlementHandler(loan.terms.handler).getAuctionStart(loan)); + skip(7 days); + uint256 currentAuctionPrice = AstariaV1SettlementHandler(loan.terms.handler).getCurrentAuctionPrice(loan); + + assertEq(currentAuctionPrice, handlerDetails.endingPrice); + } + + function testGetCurrentAuctionPriceNoAuction() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + uint256 loanId = loan.getId(); + + vm.expectRevert(abi.encodeWithSelector(AstariaV1SettlementHandler.NoAuction.selector)); + uint256 currentAuctionPrice = AstariaV1SettlementHandler(loan.terms.handler).getCurrentAuctionPrice(loan); + } + + function testV1SettlementHandlerExecute() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + + vm.expectRevert(abi.encodeWithSelector(AstariaV1SettlementHandler.ExecuteHandlerNotImplemented.selector)); + AstariaV1SettlementHandler(loan.terms.handler).execute(loan, address(this)); + } + + function testV1SettlementHandlerValidate() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + + assert(AstariaV1SettlementHandler(loan.terms.handler).validate(loan)); + } + + function testV1SettlementHandlerValidateInvalidHandler() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + + address handler = loan.terms.handler; + loan.terms.handler = address(0); + vm.expectRevert(abi.encodeWithSelector(AstariaV1SettlementHandler.InvalidHandler.selector)); + AstariaV1SettlementHandler(handler).validate(loan); + } +} From b2157b34989357d98c94e9ca81b31b6078ea58a9 Mon Sep 17 00:00:00 2001 From: androolloyd Date: Thu, 2 Nov 2023 10:29:07 -0300 Subject: [PATCH 2/3] feat/AST 1952 v1 handler testing (#43) * update getAuctionStart to be a public helper, make an internal version that is more optimized(less calls/decoding), add tests * coverage testing for AstariaV1Settlement handler complete * coverage testing for AstariaV1Settlement handler complete * fix test name * Astaria V1 Hook testing complete(-withdraw), Conduit Helper Removed, ConduitTransfer => AdditionalTransfer * remove commented code * final comment removal --- src/BNPLHelper.sol | 8 +- src/ConduitHelper.sol | 164 --------------- src/Custodian.sol | 4 +- src/LoanManager.sol | 49 +---- src/enforcers/BorrowerEnforcer.sol | 4 +- src/enforcers/BorrowerEnforcerBNPL.sol | 6 +- src/enforcers/CaveatEnforcer.sol | 4 +- src/enforcers/LenderEnforcer.sol | 4 +- src/handlers/EnglishAuctionHandler.sol | 2 +- src/hooks/BaseRecall.sol | 53 ++--- src/lib/StarPortLib.sol | 49 ++++- src/originators/Originator.sol | 2 +- src/originators/StrategistOriginator.sol | 6 +- src/pricing/AstariaV1Pricing.sol | 4 +- src/pricing/BaseRecallPricing.sol | 6 +- src/pricing/Pricing.sol | 4 +- src/pricing/SimpleInterestPricing.sol | 6 +- test/AstariaV1Test.sol | 8 +- test/StarPortTest.sol | 4 +- .../integration-testing/TestAstariaV1Loan.sol | 9 +- test/integration-testing/TestNewLoan.sol | 4 +- test/unit-testing/TestBorrowerEnforcer.sol | 14 +- test/unit-testing/TestCustodian.sol | 4 +- test/unit-testing/TestLenderEnforcer.sol | 20 +- test/unit-testing/TestLoanManager.sol | 42 ++-- test/unit-testing/TestV1Hook.sol | 189 ++++++++++++++++++ 26 files changed, 342 insertions(+), 327 deletions(-) delete mode 100644 src/ConduitHelper.sol create mode 100644 test/unit-testing/TestV1Hook.sol diff --git a/src/BNPLHelper.sol b/src/BNPLHelper.sol index 3cce91d5..747e7def 100644 --- a/src/BNPLHelper.sol +++ b/src/BNPLHelper.sol @@ -10,7 +10,7 @@ import { ItemType, Fulfillment } from "seaport-types/src/lib/ConsiderationStructs.sol"; -import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {AdditionalTransfer} from "starport-core/lib/StarPortLib.sol"; import {Seaport} from "seaport/contracts/Seaport.sol"; import {LoanManager} from "./LoanManager.sol"; @@ -113,10 +113,10 @@ contract BNPLHelper is IFlashLoanRecipient { execution.orders, execution.resolvers, execution.fulfillments, execution.borrower ); - ConduitTransfer[] memory transfers = new ConduitTransfer[](tokens.length); + AdditionalTransfer[] memory transfers = new AdditionalTransfer[](tokens.length); for (uint256 i = 0; i < tokens.length;) { - transfers[i] = ConduitTransfer({ - itemType: ConduitItemType.ERC20, + transfers[i] = AdditionalTransfer({ + itemType: ItemType.ERC20, identifier: 0, token: tokens[0], from: execution.borrower, diff --git a/src/ConduitHelper.sol b/src/ConduitHelper.sol deleted file mode 100644 index e6f40901..00000000 --- a/src/ConduitHelper.sol +++ /dev/null @@ -1,164 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -/** - * ,--, - * ,---.'| - * ,----.. ,---, ,-. | | : - * / / \ ,--.' | ,--, ,--/ /| : : | ,---, - * | : :| | : ,--.'| ,---, .---. ,---. __ ,-.,--. :/ | | ' : ,---.'| - * . | ;. /: : : | |, ,-+-. / | /. ./| ' ,'\ ,' ,'/ /|: : ' / .--.--. ; ; ' | | : .--.--. - * . ; /--` : | |,--. ,--.--. `--'_ ,--.'|' | .-'-. ' | / / |' | |' || ' / / / ' ' | |__ ,--.--. : : : / / ' - * ; | ; | : ' | / \ ,' ,'| | | ,"' | /___/ \: |. ; ,. :| | ,'' | : | : /`./ | | :.'| / \ : |,-.| : /`./ - * | : | | | /' :.--. .-. | ' | | | | / | | .-'.. ' ' .' | |: :' : / | | \| : ;_ ' : ;.--. .-. | | : ' || : ;_ - * . | '___ ' : | | | \__\/: . . | | : | | | | |/___/ \: '' | .; :| | ' ' : |. \\ \ `. | | ./ \__\/: . . | | / : \ \ `. - * ' ; : .'|| | ' | : ," .--.; | ' : |__ | | | |/ . \ ' .\ | : |; : | | | ' \ \`----. \ ; : ; ," .--.; | ' : |: | `----. \ - * ' | '/ :| : :_:,'/ / ,. | | | '.'|| | |--' \ \ ' \ | \ \ / | , ; ' : |--'/ /`--' / | ,/ / / ,. | | | '/ : / /`--' / - * | : / | | ,' ; : .' \; : ;| |/ \ \ |--" `----' ---' ; |,' '--'. / '---' ; : .' \| : |'--'. / - * \ \ .' `--'' | , .-./| , / '---' \ \ | '--' `--'---' | , .-.// \ / `--'---' - * `---` `--`---' ---`-' '---" `--`---' `-'----' - * - * Chainworks Labs - */ -pragma solidity ^0.8.17; - -import {ItemType, OfferItem, Schema, SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; - -import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; - -abstract contract ConduitHelper { - error RepayCarryLengthMismatch(); - - function _mergeConsiderations( - ReceivedItem[] memory repayConsideration, - ReceivedItem[] memory carryConsideration, - ReceivedItem[] memory additionalConsiderations - ) internal pure returns (ReceivedItem[] memory consideration) { - if (carryConsideration.length == 0 && additionalConsiderations.length == 0) { - return repayConsideration; - } - consideration = new ReceivedItem[](repayConsideration.length + - carryConsideration.length + - additionalConsiderations.length); - - uint256 j = 0; - // if there is a carry to handle, subtract it from the amount owed - if (carryConsideration.length > 0) { - if (repayConsideration.length != carryConsideration.length) { - revert RepayCarryLengthMismatch(); - } - uint256 i = 0; - for (; i < repayConsideration.length;) { - repayConsideration[i].amount -= carryConsideration[i].amount; - consideration[j] = repayConsideration[i]; - unchecked { - ++i; - ++j; - } - } - i = 0; - for (; i < carryConsideration.length;) { - consideration[j] = carryConsideration[i]; - unchecked { - ++i; - ++j; - } - } - } - // else just use the consideration payment only - else { - for (; j < repayConsideration.length;) { - consideration[j] = repayConsideration[j]; - unchecked { - ++j; - } - } - } - - if (additionalConsiderations.length > 0) { - uint256 i = 0; - for (; i < additionalConsiderations.length;) { - consideration[j] = additionalConsiderations[i]; - unchecked { - ++i; - ++j; - } - } - } - } - - function _removeZeroAmounts(ReceivedItem[] memory consideration) - internal - view - returns (ReceivedItem[] memory newConsideration) - { - uint256 i = 0; - uint256 validConsiderations = 0; - for (; i < consideration.length;) { - if (consideration[i].amount > 0) ++validConsiderations; - unchecked { - ++i; - } - } - i = 0; - uint256 j = 0; - newConsideration = new ReceivedItem[](validConsiderations); - for (; i < consideration.length;) { - if (consideration[i].amount > 0) { - newConsideration[j] = consideration[i]; - unchecked { - ++j; - } - } - unchecked { - ++i; - } - } - } - - function _packageTransfers(ReceivedItem[] memory refinanceConsideration, address refinancer) - internal - pure - returns (ConduitTransfer[] memory transfers) - { - uint256 i = 0; - uint256 validConsiderations = 0; - for (; i < refinanceConsideration.length;) { - if (refinanceConsideration[i].amount > 0) ++validConsiderations; - unchecked { - ++i; - } - } - transfers = new ConduitTransfer[](validConsiderations); - i = 0; - uint256 j = 0; - for (; i < refinanceConsideration.length;) { - ConduitItemType itemType; - ReceivedItem memory debt = refinanceConsideration[i]; - - assembly { - itemType := mload(debt) - switch itemType - case 1 {} - case 2 {} - case 3 {} - default { revert(0, 0) } //TODO: Update with error selector - InvalidContext(ContextErrors.INVALID_LOAN) - } - if (refinanceConsideration[i].amount > 0) { - transfers[j] = ConduitTransfer({ - itemType: itemType, - from: refinancer, - token: refinanceConsideration[i].token, - identifier: refinanceConsideration[i].identifier, - amount: refinanceConsideration[i].amount, - to: refinanceConsideration[i].recipient - }); - unchecked { - ++j; - } - } - - unchecked { - ++i; - } - } - } -} diff --git a/src/Custodian.sol b/src/Custodian.sol index 2157115a..d98146b3 100644 --- a/src/Custodian.sol +++ b/src/Custodian.sol @@ -27,7 +27,7 @@ import {ERC1155} from "solady/src/tokens/ERC1155.sol"; import {ItemType, Schema, SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {ConsiderationInterface} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; import {ContractOffererInterface} from "seaport-types/src/interfaces/ContractOffererInterface.sol"; -import {ConduitHelper} from "starport-core/ConduitHelper.sol"; + import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; import {SettlementHook} from "starport-core/hooks/SettlementHook.sol"; import {SettlementHandler} from "starport-core/handlers/SettlementHandler.sol"; @@ -36,7 +36,7 @@ import {LoanManager} from "starport-core/LoanManager.sol"; import {StarPortLib, Actions} from "starport-core/lib/StarPortLib.sol"; import "forge-std/console2.sol"; -contract Custodian is ERC721, ContractOffererInterface, ConduitHelper { +contract Custodian is ERC721, ContractOffererInterface { using {StarPortLib.getId} for LoanManager.Loan; LoanManager public immutable LM; diff --git a/src/LoanManager.sol b/src/LoanManager.sol index fa3c5fd0..10ed881b 100644 --- a/src/LoanManager.sol +++ b/src/LoanManager.sol @@ -33,7 +33,7 @@ import {SettlementHandler} from "starport-core/handlers/SettlementHandler.sol"; import {Pricing} from "starport-core/pricing/Pricing.sol"; import {StarPortLib, Actions} from "starport-core/lib/StarPortLib.sol"; -import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {AdditionalTransfer} from "starport-core/lib/StarPortLib.sol"; import {ConduitControllerInterface} from "seaport-types/src/interfaces/ConduitControllerInterface.sol"; import {ConduitInterface} from "seaport-types/src/interfaces/ConduitInterface.sol"; import {Custodian} from "starport-core/Custodian.sol"; @@ -130,7 +130,7 @@ contract LoanManager is Ownable, ERC721 { error InvalidItemType(); error InvalidTransferLength(); error CannotTransferLoans(); - error ConduitTransferError(); + error AdditionalTransferError(); error LoanExists(); error NotLoanCustodian(); error NotSeaport(); @@ -199,7 +199,7 @@ contract LoanManager is Ownable, ERC721 { } function originate( - ConduitTransfer[] calldata additionalTransfers, + AdditionalTransfer[] calldata additionalTransfers, CaveatEnforcer.CaveatWithApproval calldata borrowerCaveat, CaveatEnforcer.CaveatWithApproval calldata lenderCaveat, LoanManager.Loan memory loan @@ -234,7 +234,7 @@ contract LoanManager is Ownable, ERC721 { if (additionalTransfers.length > 0) { _validateAdditionalTransfersCalldata(borrower, issuer, msg.sender, additionalTransfers); - _transferConduitTransfers(additionalTransfers); + StarPortLib.transferAdditionalTransfers(additionalTransfers); } //sets originator and start time @@ -253,7 +253,7 @@ contract LoanManager is Ownable, ERC721 { ( SpentItem[] memory considerationPayment, SpentItem[] memory carryPayment, - ConduitTransfer[] memory additionalTransfers + AdditionalTransfer[] memory additionalTransfers ) = Pricing(loan.terms.pricing).isValidRefinance(loan, pricingData, msg.sender); _settle(loan); @@ -272,7 +272,7 @@ contract LoanManager is Ownable, ERC721 { if (additionalTransfers.length > 0) { _validateAdditionalTransfers(loan.borrower, loan.issuer, msg.sender, additionalTransfers); - _transferConduitTransfers(additionalTransfers); + StarPortLib.transferAdditionalTransfers(additionalTransfers); } //sets originator and start time @@ -338,7 +338,7 @@ contract LoanManager is Ownable, ERC721 { address borrower, address lender, address fulfiller, - ConduitTransfer[] memory additionalTransfers + AdditionalTransfer[] memory additionalTransfers ) internal pure { uint256 i = 0; for (i; i < additionalTransfers.length;) { @@ -358,7 +358,7 @@ contract LoanManager is Ownable, ERC721 { address borrower, address lender, address fulfiller, - ConduitTransfer[] calldata additionalTransfers + AdditionalTransfer[] calldata additionalTransfers ) internal pure { uint256 i = 0; for (i; i < additionalTransfers.length;) { @@ -375,7 +375,7 @@ contract LoanManager is Ownable, ERC721 { function _validateAndEnforceCaveats( CaveatEnforcer.CaveatWithApproval calldata caveatApproval, address validator, - ConduitTransfer[] memory additionalTransfers, + AdditionalTransfer[] memory additionalTransfers, LoanManager.Loan memory loan ) internal { bytes32 hash = hashCaveatWithSaltAndNonce(validator, caveatApproval.salt, caveatApproval.caveat); @@ -399,37 +399,6 @@ contract LoanManager is Ownable, ERC721 { } } - function _transferConduitTransfers(ConduitTransfer[] memory transfers) internal { - uint256 i = 0; - uint256 amount = 0; - for (i; i < transfers.length;) { - amount = transfers[i].amount; - if (amount > 0) { - if (transfers[i].itemType == ConduitItemType.ERC20) { - // erc20 transfer - - SafeTransferLib.safeTransferFrom(transfers[i].token, transfers[i].from, transfers[i].to, amount); - } else if (transfers[i].itemType == ConduitItemType.ERC721) { - // erc721 transfer - if (amount > 1) { - revert InvalidItemAmount(); - } - ERC721(transfers[i].token).transferFrom(transfers[i].from, transfers[i].to, transfers[i].identifier); - } else if (transfers[i].itemType == ConduitItemType.ERC1155) { - // erc1155 transfer - ERC1155(transfers[i].token).safeTransferFrom( - transfers[i].from, transfers[i].to, transfers[i].identifier, amount, new bytes(0) - ); - } else { - revert NativeAssetsNotSupported(); - } - } - unchecked { - ++i; - } - } - } - function _transferItem( ItemType itemType, address token, diff --git a/src/enforcers/BorrowerEnforcer.sol b/src/enforcers/BorrowerEnforcer.sol index d6186f23..a5705c16 100644 --- a/src/enforcers/BorrowerEnforcer.sol +++ b/src/enforcers/BorrowerEnforcer.sol @@ -1,7 +1,7 @@ pragma solidity ^0.8.17; import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; -import {ConduitTransfer} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {AdditionalTransfer} from "starport-core/lib/StarPortLib.sol"; import {LoanManager} from "starport-core/LoanManager.sol"; import {ConsiderationInterface} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; @@ -21,7 +21,7 @@ contract BorrowerEnforcer is CaveatEnforcer { /// @param loan The loan terms /// @param caveatData The borrowers encoded details function validate( - ConduitTransfer[] calldata additionalTransfers, + AdditionalTransfer[] calldata additionalTransfers, LoanManager.Loan calldata loan, bytes calldata caveatData ) public view virtual override { diff --git a/src/enforcers/BorrowerEnforcerBNPL.sol b/src/enforcers/BorrowerEnforcerBNPL.sol index b79ecdb2..ef46645d 100644 --- a/src/enforcers/BorrowerEnforcerBNPL.sol +++ b/src/enforcers/BorrowerEnforcerBNPL.sol @@ -1,7 +1,7 @@ pragma solidity ^0.8.17; import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; -import {ConduitTransfer} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {AdditionalTransfer} from "starport-core/lib/StarPortLib.sol"; import {LoanManager} from "starport-core/LoanManager.sol"; import {ConsiderationInterface} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; @@ -16,11 +16,11 @@ contract BorrowerEnforcerBNPL is CaveatEnforcer { LoanManager.Loan loan; address seaport; bytes32 offerHash; - ConduitTransfer additionalTransfer; + AdditionalTransfer additionalTransfer; } function validate( - ConduitTransfer[] calldata additionalTransfers, + AdditionalTransfer[] calldata additionalTransfers, LoanManager.Loan calldata loan, bytes calldata caveatData ) public view virtual override { diff --git a/src/enforcers/CaveatEnforcer.sol b/src/enforcers/CaveatEnforcer.sol index 1766ffa1..5166d124 100644 --- a/src/enforcers/CaveatEnforcer.sol +++ b/src/enforcers/CaveatEnforcer.sol @@ -1,6 +1,6 @@ pragma solidity ^0.8.17; -import {ConduitTransfer} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {AdditionalTransfer} from "starport-core/lib/StarPortLib.sol"; import {LoanManager} from "starport-core/LoanManager.sol"; abstract contract CaveatEnforcer { @@ -18,7 +18,7 @@ abstract contract CaveatEnforcer { Caveat[] caveat; } - function validate(ConduitTransfer[] calldata solution, LoanManager.Loan calldata loan, bytes calldata caveatData) + function validate(AdditionalTransfer[] calldata solution, LoanManager.Loan calldata loan, bytes calldata caveatData) public view virtual; diff --git a/src/enforcers/LenderEnforcer.sol b/src/enforcers/LenderEnforcer.sol index 53e35314..4e8e7f1b 100644 --- a/src/enforcers/LenderEnforcer.sol +++ b/src/enforcers/LenderEnforcer.sol @@ -1,7 +1,7 @@ pragma solidity ^0.8.17; import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; -import {ConduitTransfer} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {AdditionalTransfer} from "starport-core/lib/StarPortLib.sol"; import {LoanManager} from "starport-core/LoanManager.sol"; contract LenderEnforcer is CaveatEnforcer { @@ -20,7 +20,7 @@ contract LenderEnforcer is CaveatEnforcer { /// @param loan The loan terms /// @param caveatData The borrowers encoded details function validate( - ConduitTransfer[] calldata additionalTransfers, + AdditionalTransfer[] calldata additionalTransfers, LoanManager.Loan calldata loan, bytes calldata caveatData ) public view virtual override { diff --git a/src/handlers/EnglishAuctionHandler.sol b/src/handlers/EnglishAuctionHandler.sol index 27bfad48..cca2eb77 100644 --- a/src/handlers/EnglishAuctionHandler.sol +++ b/src/handlers/EnglishAuctionHandler.sol @@ -1,7 +1,7 @@ pragma solidity ^0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; -import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {AdditionalTransfer} from "starport-core/lib/StarPortLib.sol"; import {ConsiderationInterface} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; import {ConduitInterface} from "seaport-types/src/interfaces/ConduitInterface.sol"; import {ConduitControllerInterface} from "seaport-types/src/interfaces/ConduitControllerInterface.sol"; diff --git a/src/hooks/BaseRecall.sol b/src/hooks/BaseRecall.sol index bcbff4ba..f280994d 100644 --- a/src/hooks/BaseRecall.sol +++ b/src/hooks/BaseRecall.sol @@ -28,33 +28,31 @@ import {ERC20} from "solady/src/tokens/ERC20.sol"; import {BasePricing} from "starport-core/pricing/BasePricing.sol"; -import {ConduitHelper} from "starport-core/ConduitHelper.sol"; - import {ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {ItemType} from "seaport-types/src/lib/ConsiderationEnums.sol"; import {ConduitControllerInterface} from "seaport-sol/src/ConduitControllerInterface.sol"; import {ConsiderationInterface} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; -import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {AdditionalTransfer} from "starport-core/lib/StarPortLib.sol"; import {ConduitInterface} from "seaport-types/src/interfaces/ConduitInterface.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; import {StarPortLib} from "starport-core/lib/StarPortLib.sol"; -abstract contract BaseRecall is ConduitHelper { +abstract contract BaseRecall { using FixedPointMathLib for uint256; using {StarPortLib.getId} for LoanManager.Loan; event Recalled(uint256 loandId, address recaller, uint256 end); event Withdraw(uint256 loanId, address withdrawer); - LoanManager LM; + LoanManager public immutable LM; error InvalidWithdraw(); error InvalidConduit(); - error ConduitTransferError(); + error AdditionalTransferError(); error InvalidStakeType(); error LoanDoesNotExist(); error RecallBeforeHoneymoonExpiry(); @@ -62,7 +60,6 @@ abstract contract BaseRecall is ConduitHelper { error WithdrawDoesNotExist(); error InvalidItemType(); - ConsiderationInterface public constant seaport = ConsiderationInterface(0x2e234DAe75C793f67A35089C9d99245E1C58470b); mapping(uint256 => Recall) public recalls; struct Details { @@ -94,7 +91,7 @@ abstract contract BaseRecall is ConduitHelper { return details.recallMax.mulWad((block.timestamp - recalls[loanId].start).divWad(details.recallWindow)); } - function recall(LoanManager.Loan memory loan, address conduit) external { + function recall(LoanManager.Loan calldata loan) external { Details memory details = abi.decode(loan.terms.hookData, (Details)); if ((loan.start + details.honeymoon) > block.timestamp) { @@ -102,17 +99,15 @@ abstract contract BaseRecall is ConduitHelper { } if (loan.issuer != msg.sender && loan.borrower != msg.sender) { - // (,, address conduitController) = seaport.information(); + // (,, address conduitController) = LM.seaport().information(); // validate that the provided conduit is owned by the msg.sender // if (ConduitControllerInterface(conduitController).ownerOf(conduit) != msg.sender) { // revert InvalidConduit(); // } - ConduitTransfer[] memory recallConsideration = _generateRecallConsideration( + AdditionalTransfer[] memory recallConsideration = _generateRecallConsideration( loan, 0, details.recallStakeDuration, 1e18, msg.sender, payable(address(this)) ); - if (ConduitInterface(conduit).execute(recallConsideration) != ConduitInterface.execute.selector) { - revert ConduitTransferError(); - } + StarPortLib.transferAdditionalTransfers(recallConsideration); } // get conduitController @@ -127,7 +122,7 @@ abstract contract BaseRecall is ConduitHelper { } // transfers all stake to anyone who asks after the LM token is burned - function withdraw(LoanManager.Loan memory loan, address payable receiver) external { + function withdraw(LoanManager.Loan calldata loan, address payable receiver) external { Details memory details = abi.decode(loan.terms.hookData, (Details)); bytes memory encodedLoan = abi.encode(loan); uint256 loanId = uint256(keccak256(encodedLoan)); @@ -142,7 +137,7 @@ abstract contract BaseRecall is ConduitHelper { } if (loan.issuer != recall.recaller && loan.borrower != recall.recaller) { - ConduitTransfer[] memory recallConsideration = + AdditionalTransfer[] memory recallConsideration = _generateRecallConsideration(loan, 0, details.recallStakeDuration, 1e18, address(this), receiver); recall.recaller = payable(address(0)); recall.start = 0; @@ -178,29 +173,29 @@ abstract contract BaseRecall is ConduitHelper { } function generateRecallConsideration( - LoanManager.Loan memory loan, + LoanManager.Loan calldata loan, uint256 proportion, address from, address payable to - ) external view returns (ConduitTransfer[] memory consideration) { + ) external view returns (AdditionalTransfer[] memory consideration) { Details memory details = abi.decode(loan.terms.hookData, (Details)); - return _generateRecallConsideration(loan, 0, details.recallStakeDuration, 1e18, from, to); + return _generateRecallConsideration(loan, 0, details.recallStakeDuration, proportion, from, to); } function _generateRecallConsideration( - LoanManager.Loan memory loan, + LoanManager.Loan calldata loan, uint256 start, uint256 end, uint256 proportion, address from, address payable to - ) internal view returns (ConduitTransfer[] memory additionalTransfers) { + ) internal view returns (AdditionalTransfer[] memory additionalTransfers) { uint256[] memory stake = _getRecallStake(loan, start, end); - additionalTransfers = new ConduitTransfer[](stake.length); + additionalTransfers = new AdditionalTransfer[](stake.length); for (uint256 i; i < additionalTransfers.length;) { - additionalTransfers[i] = ConduitTransfer({ - itemType: _convertItemTypeToConduitItemType(loan.debt[i].itemType), + additionalTransfers[i] = AdditionalTransfer({ + itemType: loan.debt[i].itemType, identifier: loan.debt[i].identifier, amount: stake[i].mulWad(proportion), token: loan.debt[i].token, @@ -212,16 +207,4 @@ abstract contract BaseRecall is ConduitHelper { } } } - - function _convertItemTypeToConduitItemType(ItemType itemType) internal pure returns (ConduitItemType) { - if (itemType == ItemType.ERC20) { - return ConduitItemType.ERC20; - } else if (itemType == ItemType.ERC721) { - return ConduitItemType.ERC721; - } else if (itemType == ItemType.ERC1155) { - return ConduitItemType.ERC1155; - } else { - revert InvalidItemType(); - } - } } diff --git a/src/lib/StarPortLib.sol b/src/lib/StarPortLib.sol index e25cac8f..3aba2180 100644 --- a/src/lib/StarPortLib.sol +++ b/src/lib/StarPortLib.sol @@ -1,20 +1,32 @@ pragma solidity ^0.8.17; import {ItemType, ReceivedItem, SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; - import {LoanManager} from "starport-core/LoanManager.sol"; import "forge-std/console.sol"; +import {ERC721} from "solady/src/tokens/ERC721.sol"; +import {ERC20} from "solady/src/tokens/ERC20.sol"; +import {ERC1155} from "solady/src/tokens/ERC1155.sol"; +import {SafeTransferLib} from "solady/src/utils/SafeTransferLib.sol"; enum Actions { Nothing, - Origination, - Refinance, Repayment, Settlement } +struct AdditionalTransfer { + ItemType itemType; + address token; + address from; + address to; + uint256 identifier; + uint256 amount; +} + library StarPortLib { error InvalidSalt(); + error InvalidItemAmount(); + error NativeAssetsNotSupported(); uint256 internal constant _INVALID_SALT = 0x81e69d9b00000000000000000000000000000000000000000000000000000000; @@ -200,4 +212,35 @@ library StarPortLib { mstore(consideration, j) } } + + function transferAdditionalTransfers(AdditionalTransfer[] memory transfers) internal { + uint256 i = 0; + uint256 amount = 0; + for (i; i < transfers.length;) { + amount = transfers[i].amount; + if (amount > 0) { + if (transfers[i].itemType == ItemType.ERC20) { + // erc20 transfer + + SafeTransferLib.safeTransferFrom(transfers[i].token, transfers[i].from, transfers[i].to, amount); + } else if (transfers[i].itemType == ItemType.ERC721) { + // erc721 transfer + if (amount > 1) { + revert InvalidItemAmount(); + } + ERC721(transfers[i].token).transferFrom(transfers[i].from, transfers[i].to, transfers[i].identifier); + } else if (transfers[i].itemType == ItemType.ERC1155) { + // erc1155 transfer + ERC1155(transfers[i].token).safeTransferFrom( + transfers[i].from, transfers[i].to, transfers[i].identifier, amount, new bytes(0) + ); + } else { + revert NativeAssetsNotSupported(); + } + } + unchecked { + ++i; + } + } + } } diff --git a/src/originators/Originator.sol b/src/originators/Originator.sol index efb1e955..0b8e6df9 100644 --- a/src/originators/Originator.sol +++ b/src/originators/Originator.sol @@ -23,7 +23,7 @@ pragma solidity ^0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; import {SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; -import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {AdditionalTransfer} from "starport-core/lib/StarPortLib.sol"; import {Ownable} from "solady/src/auth/Ownable.sol"; import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; diff --git a/src/originators/StrategistOriginator.sol b/src/originators/StrategistOriginator.sol index 77fbd483..1f149ea6 100644 --- a/src/originators/StrategistOriginator.sol +++ b/src/originators/StrategistOriginator.sol @@ -23,7 +23,7 @@ pragma solidity ^0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; import {ItemType, ReceivedItem, SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; -import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {AdditionalTransfer} from "starport-core/lib/StarPortLib.sol"; import {ConduitControllerInterface} from "seaport-types/src/interfaces/ConduitControllerInterface.sol"; import {ConduitInterface} from "seaport-types/src/interfaces/ConduitInterface.sol"; @@ -67,7 +67,7 @@ contract StrategistOriginator is Ownable, Originator { error InvalidDeadline(); error InvalidOffer(); error InvalidSigner(); - error ConduitTransferError(); + error AdditionalTransferError(); LoanManager public immutable LM; @@ -148,7 +148,7 @@ contract StrategistOriginator is Ownable, Originator { }); CaveatEnforcer.CaveatWithApproval memory le; - LM.originate(new ConduitTransfer[](0), params.borrowerCaveat, le, loan); + LM.originate(new AdditionalTransfer[](0), params.borrowerCaveat, le, loan); } function _validateAsk(Request calldata request, Details memory details) internal virtual { diff --git a/src/pricing/AstariaV1Pricing.sol b/src/pricing/AstariaV1Pricing.sol index 879f6f55..d47272f5 100644 --- a/src/pricing/AstariaV1Pricing.sol +++ b/src/pricing/AstariaV1Pricing.sol @@ -11,7 +11,7 @@ import {AstariaV1SettlementHook} from "starport-core/hooks/AstariaV1SettlementHo import {BaseRecall} from "starport-core/hooks/BaseRecall.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; import {StarPortLib} from "starport-core/lib/StarPortLib.sol"; -import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {AdditionalTransfer} from "starport-core/lib/StarPortLib.sol"; contract AstariaV1Pricing is CompoundInterestPricing { using FixedPointMathLib for uint256; @@ -29,7 +29,7 @@ contract AstariaV1Pricing is CompoundInterestPricing { returns ( SpentItem[] memory repayConsideration, SpentItem[] memory carryConsideration, - ConduitTransfer[] memory recallConsideration + AdditionalTransfer[] memory recallConsideration ) { // borrowers can refinance a loan at any time diff --git a/src/pricing/BaseRecallPricing.sol b/src/pricing/BaseRecallPricing.sol index f4cd3366..523e482d 100644 --- a/src/pricing/BaseRecallPricing.sol +++ b/src/pricing/BaseRecallPricing.sol @@ -30,7 +30,7 @@ import "forge-std/console2.sol"; import {BaseHook} from "starport-core/hooks/BaseHook.sol"; import {StarPortLib} from "starport-core/lib/StarPortLib.sol"; -import {ConduitTransfer} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {AdditionalTransfer} from "starport-core/lib/StarPortLib.sol"; abstract contract BaseRecallPricing is BasePricing { function isValidRefinance(LoanManager.Loan memory loan, bytes memory newPricingData, address caller) @@ -41,7 +41,7 @@ abstract contract BaseRecallPricing is BasePricing { returns ( SpentItem[] memory repayConsideration, SpentItem[] memory carryConsideration, - ConduitTransfer[] memory recallConsideration + AdditionalTransfer[] memory recallConsideration ) { Details memory oldDetails = abi.decode(loan.terms.pricingData, (Details)); @@ -51,7 +51,7 @@ abstract contract BaseRecallPricing is BasePricing { //todo: figure out the proper flow for here if ((isRecalled && newDetails.rate >= oldDetails.rate) || (newDetails.rate < oldDetails.rate)) { (repayConsideration, carryConsideration) = getPaymentConsideration(loan); - recallConsideration = new ConduitTransfer[](0); + recallConsideration = new AdditionalTransfer[](0); } } } diff --git a/src/pricing/Pricing.sol b/src/pricing/Pricing.sol index c4d911fb..0e958fd0 100644 --- a/src/pricing/Pricing.sol +++ b/src/pricing/Pricing.sol @@ -22,7 +22,7 @@ pragma solidity ^0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; import {SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; -import {ConduitTransfer} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {AdditionalTransfer} from "starport-core/lib/StarPortLib.sol"; abstract contract Pricing { LoanManager LM; @@ -43,5 +43,5 @@ abstract contract Pricing { external view virtual - returns (SpentItem[] memory, SpentItem[] memory, ConduitTransfer[] memory); + returns (SpentItem[] memory, SpentItem[] memory, AdditionalTransfer[] memory); } diff --git a/src/pricing/SimpleInterestPricing.sol b/src/pricing/SimpleInterestPricing.sol index 7b2202ab..071a7d59 100644 --- a/src/pricing/SimpleInterestPricing.sol +++ b/src/pricing/SimpleInterestPricing.sol @@ -24,7 +24,7 @@ import {ReceivedItem, BasePricing} from "starport-core/pricing/BasePricing.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; import {LoanManager} from "starport-core/LoanManager.sol"; import {Pricing} from "starport-core/pricing/Pricing.sol"; -import {ConduitTransfer} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {AdditionalTransfer} from "starport-core/lib/StarPortLib.sol"; import {SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; contract SimpleInterestPricing is BasePricing { @@ -48,7 +48,7 @@ contract SimpleInterestPricing is BasePricing { returns ( SpentItem[] memory repayConsideration, SpentItem[] memory carryConsideration, - ConduitTransfer[] memory additionalConsideration + AdditionalTransfer[] memory additionalConsideration ) { Details memory oldDetails = abi.decode(loan.terms.pricingData, (Details)); @@ -57,7 +57,7 @@ contract SimpleInterestPricing is BasePricing { //todo: figure out the proper flow for here if ((newDetails.rate < oldDetails.rate)) { (repayConsideration, carryConsideration) = getPaymentConsideration(loan); - additionalConsideration = new ConduitTransfer[](0); + additionalConsideration = new AdditionalTransfer[](0); } else { revert InvalidRefinance(); } diff --git a/test/AstariaV1Test.sol b/test/AstariaV1Test.sol index 396921e4..d194ca2c 100644 --- a/test/AstariaV1Test.sol +++ b/test/AstariaV1Test.sol @@ -19,8 +19,6 @@ import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; contract AstariaV1Test is StarPortTest { Account recaller; - address recallerConduit; - bytes32 conduitKeyRecaller; function setUp() public override { super.setUp(); @@ -33,12 +31,8 @@ contract AstariaV1Test is StarPortTest { handler = new AstariaV1SettlementHandler(LM); hook = new AstariaV1SettlementHook(LM); - conduitKeyRecaller = bytes32(uint256(uint160(address(recaller.addr))) << 96); - vm.startPrank(recaller.addr); - recallerConduit = conduitController.createConduit(conduitKeyRecaller, recaller.addr); - conduitController.updateChannel(recallerConduit, address(hook), true); - erc20s[0].approve(address(recallerConduit), 1e18); + erc20s[0].approve(address(hook), 1e18); vm.stopPrank(); // // 1% interest rate per second diff --git a/test/StarPortTest.sol b/test/StarPortTest.sol index b658c179..e1fbd5d4 100644 --- a/test/StarPortTest.sol +++ b/test/StarPortTest.sol @@ -13,7 +13,7 @@ import { OrderParameters } from "seaport-types/src/lib/ConsiderationStructs.sol"; import {OrderParametersLib} from "seaport/lib/seaport-sol/src/lib/OrderParametersLib.sol"; -import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {AdditionalTransfer} from "starport-core/lib/StarPortLib.sol"; import {ConsiderationInterface} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; import { ConsiderationItem, @@ -421,7 +421,7 @@ contract StarPortTest is BaseOrderTest { ) internal returns (LoanManager.Loan memory originatedLoan) { vm.recordLogs(); vm.startPrank(fulfiller); - LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + LM.originate(new AdditionalTransfer[](0), borrowerCaveat, lenderCaveat, loan); vm.stopPrank(); Vm.Log[] memory logs = vm.getRecordedLogs(); diff --git a/test/integration-testing/TestAstariaV1Loan.sol b/test/integration-testing/TestAstariaV1Loan.sol index b74c98fb..5bde741f 100644 --- a/test/integration-testing/TestAstariaV1Loan.sol +++ b/test/integration-testing/TestAstariaV1Loan.sol @@ -28,7 +28,7 @@ contract TestAstariaV1Loan is AstariaV1Test { vm.startPrank(recaller.addr); vm.expectRevert(BaseRecall.RecallBeforeHoneymoonExpiry.selector); // attempt recall before honeymoon period has ended - BaseRecall(address(hook)).recall(loan, recallerConduit); + BaseRecall(address(hook)).recall(loan); vm.stopPrank(); } { @@ -64,7 +64,7 @@ contract TestAstariaV1Loan is AstariaV1Test { vm.startPrank(recaller.addr); BaseRecall recallContract = BaseRecall(address(hook)); - recallContract.recall(loan, recallerConduit); + recallContract.recall(loan); vm.stopPrank(); uint256 balanceAfter = erc20s[0].balanceOf(recaller.addr); @@ -244,7 +244,8 @@ contract TestAstariaV1Loan is AstariaV1Test { vm.startPrank(lender.addr); conduitController.updateChannel(lenderConduit, address(hook), true); BaseRecall recallContract = BaseRecall(address(hook)); - recallContract.recall(loan, lenderConduit); + erc20s[0].approve(loan.terms.hook, 10e18); + recallContract.recall(loan); vm.stopPrank(); uint256 balanceAfter = erc20s[0].balanceOf(lender.addr); @@ -355,7 +356,7 @@ contract TestAstariaV1Loan is AstariaV1Test { vm.startPrank(recaller.addr); BaseRecall recallContract = BaseRecall(address(hook)); - recallContract.recall(loan, recallerConduit); + recallContract.recall(loan); vm.stopPrank(); uint256 balanceAfter = erc20s[0].balanceOf(recaller.addr); diff --git a/test/integration-testing/TestNewLoan.sol b/test/integration-testing/TestNewLoan.sol index dfe8649d..84d5dcaa 100644 --- a/test/integration-testing/TestNewLoan.sol +++ b/test/integration-testing/TestNewLoan.sol @@ -284,8 +284,8 @@ contract TestNewLoan is StarPortTest { BorrowerEnforcerBNPL.Details({ loan: loan2, offerHash: buyingHash2, - additionalTransfer: ConduitTransfer({ - itemType: ConduitItemType.ERC20, + additionalTransfer: AdditionalTransfer({ + itemType: ItemType.ERC20, identifier: 0, token: address(erc20s[0]), amount: 100, diff --git a/test/unit-testing/TestBorrowerEnforcer.sol b/test/unit-testing/TestBorrowerEnforcer.sol index ba1b836c..1ffd2def 100644 --- a/test/unit-testing/TestBorrowerEnforcer.sol +++ b/test/unit-testing/TestBorrowerEnforcer.sol @@ -1,19 +1,19 @@ import "starport-test/StarPortTest.sol"; import {BorrowerEnforcer} from "starport-core/enforcers/BorrowerEnforcer.sol"; -import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {AdditionalTransfer, ItemType} from "starport-core/lib/StarPortLib.sol"; import "forge-std/console.sol"; contract TestBorrowerEnforcer is StarPortTest { function testBERevertAdditionalTransfers() external { - ConduitTransfer[] memory additionalTransfers = new ConduitTransfer[](1); - additionalTransfers[0] = ConduitTransfer({ + AdditionalTransfer[] memory additionalTransfers = new AdditionalTransfer[](1); + additionalTransfers[0] = AdditionalTransfer({ token: address(0), amount: 0, to: address(0), from: address(0), identifier: 0, - itemType: ConduitItemType.ERC20 + itemType: ItemType.ERC20 }); LoanManager.Loan memory loan = generateDefaultLoanTerms(); @@ -28,12 +28,12 @@ contract TestBorrowerEnforcer is StarPortTest { BorrowerEnforcer.Details memory details = BorrowerEnforcer.Details({loan: loan}); details.loan.borrower = lender.addr; vm.expectRevert(BorrowerEnforcer.InvalidLoanTerms.selector); - borrowerEnforcer.validate(new ConduitTransfer[](0), generateDefaultLoanTerms(), abi.encode(details)); + borrowerEnforcer.validate(new AdditionalTransfer[](0), generateDefaultLoanTerms(), abi.encode(details)); } function testBEValidLoanTerms() external view { LoanManager.Loan memory loan = generateDefaultLoanTerms(); - borrowerEnforcer.validate(new ConduitTransfer[](0), loan, abi.encode(BorrowerEnforcer.Details({loan: loan}))); + borrowerEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(BorrowerEnforcer.Details({loan: loan}))); } function testBEValidLoanTermsAnyIssuer() external view { @@ -41,6 +41,6 @@ contract TestBorrowerEnforcer is StarPortTest { BorrowerEnforcer.Details memory details = BorrowerEnforcer.Details({loan: loan}); details.loan.issuer = address(0); - borrowerEnforcer.validate(new ConduitTransfer[](0), loan, abi.encode(BorrowerEnforcer.Details({loan: loan}))); + borrowerEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(BorrowerEnforcer.Details({loan: loan}))); } } diff --git a/test/unit-testing/TestCustodian.sol b/test/unit-testing/TestCustodian.sol index 68dd609c..7b72c0dc 100644 --- a/test/unit-testing/TestCustodian.sol +++ b/test/unit-testing/TestCustodian.sol @@ -561,11 +561,11 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { mockHookCall(activeLoan.terms.hook, true); vm.expectRevert(abi.encodeWithSelector(Custodian.InvalidAction.selector)); - custodian.generateOrder(alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Origination, activeLoan)); + custodian.generateOrder(alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Nothing, activeLoan)); vm.expectRevert(abi.encodeWithSelector(Custodian.InvalidAction.selector)); custodian.previewOrder( - seaportAddr, alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Origination, activeLoan) + seaportAddr, alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Nothing, activeLoan) ); } } diff --git a/test/unit-testing/TestLenderEnforcer.sol b/test/unit-testing/TestLenderEnforcer.sol index abf3acb6..e992ab6a 100644 --- a/test/unit-testing/TestLenderEnforcer.sol +++ b/test/unit-testing/TestLenderEnforcer.sol @@ -1,19 +1,19 @@ import "starport-test/StarPortTest.sol"; import {LenderEnforcer} from "starport-core/enforcers/LenderEnforcer.sol"; -import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {AdditionalTransfer, ItemType} from "starport-core/lib/StarPortLib.sol"; import "forge-std/console.sol"; contract TestLenderEnforcer is StarPortTest { function testLERevertAdditionalTransfersFromLender() external { - ConduitTransfer[] memory additionalTransfers = new ConduitTransfer[](1); - additionalTransfers[0] = ConduitTransfer({ + AdditionalTransfer[] memory additionalTransfers = new AdditionalTransfer[](1); + additionalTransfers[0] = AdditionalTransfer({ token: address(0), amount: 0, to: address(0), from: lender.addr, identifier: 0, - itemType: ConduitItemType.ERC20 + itemType: ItemType.ERC20 }); LoanManager.Loan memory loan = generateDefaultLoanTerms(); @@ -29,18 +29,18 @@ contract TestLenderEnforcer is StarPortTest { details.loan.custodian = borrower.addr; vm.expectRevert(LenderEnforcer.InvalidLoanTerms.selector); - lenderEnforcer.validate(new ConduitTransfer[](0), generateDefaultLoanTerms(), abi.encode(details)); + lenderEnforcer.validate(new AdditionalTransfer[](0), generateDefaultLoanTerms(), abi.encode(details)); } function testLEValidLoanTermsWithAdditionalTransfers() external view { - ConduitTransfer[] memory additionalTransfers = new ConduitTransfer[](1); - additionalTransfers[0] = ConduitTransfer({ + AdditionalTransfer[] memory additionalTransfers = new AdditionalTransfer[](1); + additionalTransfers[0] = AdditionalTransfer({ token: address(0), amount: 0, to: address(0), from: address(0), identifier: 0, - itemType: ConduitItemType.ERC20 + itemType: ItemType.ERC20 }); LoanManager.Loan memory loan = generateDefaultLoanTerms(); lenderEnforcer.validate(additionalTransfers, loan, abi.encode(LenderEnforcer.Details({loan: loan}))); @@ -48,7 +48,7 @@ contract TestLenderEnforcer is StarPortTest { function testLEValidLoanTerms() external view { LoanManager.Loan memory loan = generateDefaultLoanTerms(); - lenderEnforcer.validate(new ConduitTransfer[](0), loan, abi.encode(LenderEnforcer.Details({loan: loan}))); + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(LenderEnforcer.Details({loan: loan}))); } function testLEValidLoanTermsAnyBorrower() external view { @@ -56,6 +56,6 @@ contract TestLenderEnforcer is StarPortTest { LenderEnforcer.Details memory details = LenderEnforcer.Details({loan: loan}); details.loan.borrower = address(0); - lenderEnforcer.validate(new ConduitTransfer[](0), loan, abi.encode(LenderEnforcer.Details({loan: loan}))); + lenderEnforcer.validate(new AdditionalTransfer[](0), loan, abi.encode(LenderEnforcer.Details({loan: loan}))); } } diff --git a/test/unit-testing/TestLoanManager.sol b/test/unit-testing/TestLoanManager.sol index 96346e80..7f5f8d6f 100644 --- a/test/unit-testing/TestLoanManager.sol +++ b/test/unit-testing/TestLoanManager.sol @@ -68,7 +68,7 @@ contract MockOriginator is StrategistOriginator, TokenReceiverInterface { }); CaveatEnforcer.CaveatWithApproval memory le; - LM.originate(new ConduitTransfer[](0), params.borrowerCaveat, le, loan); + LM.originate(new AdditionalTransfer[](0), params.borrowerCaveat, le, loan); } receive() external payable {} @@ -194,7 +194,7 @@ contract TestLoanManager is StarPortTest, DeepEq { }); vm.startPrank(loan.borrower); vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidCustodian.selector)); - LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + LM.originate(new AdditionalTransfer[](0), borrowerCaveat, lenderCaveat, loan); vm.stopPrank(); } @@ -202,7 +202,7 @@ contract TestLoanManager is StarPortTest, DeepEq { LM.pause(); LoanManager.Loan memory loan = generateDefaultLoanTerms(); vm.expectRevert(abi.encodeWithSelector(LoanManager.IsPaused.selector)); - LM.originate(new ConduitTransfer[](0), _emptyCaveat(), _emptyCaveat(), loan); + LM.originate(new AdditionalTransfer[](0), _emptyCaveat(), _emptyCaveat(), loan); } function testNonDefaultCustodianCustodyCallSuccess() public { @@ -225,7 +225,7 @@ contract TestLoanManager is StarPortTest, DeepEq { abi.encode(bytes4(Custodian.custody.selector)) ); vm.startPrank(loan.borrower); - LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + LM.originate(new AdditionalTransfer[](0), borrowerCaveat, lenderCaveat, loan); vm.stopPrank(); } @@ -245,7 +245,7 @@ contract TestLoanManager is StarPortTest, DeepEq { _setApprovalsForSpentItems(loan.issuer, loan.debt); vm.startPrank(loan.borrower); vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidTransferLength.selector)); - LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + LM.originate(new AdditionalTransfer[](0), borrowerCaveat, lenderCaveat, loan); vm.stopPrank(); } @@ -265,7 +265,7 @@ contract TestLoanManager is StarPortTest, DeepEq { _setApprovalsForSpentItems(loan.issuer, loan.debt); vm.startPrank(loan.borrower); vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidItemAmount.selector)); - LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + LM.originate(new AdditionalTransfer[](0), borrowerCaveat, lenderCaveat, loan); vm.stopPrank(); } @@ -286,7 +286,7 @@ contract TestLoanManager is StarPortTest, DeepEq { _setApprovalsForSpentItems(loan.issuer, loan.debt); vm.startPrank(loan.borrower); vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidItemIdentifier.selector)); - LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + LM.originate(new AdditionalTransfer[](0), borrowerCaveat, lenderCaveat, loan); vm.stopPrank(); } @@ -307,7 +307,7 @@ contract TestLoanManager is StarPortTest, DeepEq { _setApprovalsForSpentItems(loan.issuer, loan.debt); vm.startPrank(loan.borrower); vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidItemAmount.selector)); - LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + LM.originate(new AdditionalTransfer[](0), borrowerCaveat, lenderCaveat, loan); vm.stopPrank(); } @@ -327,7 +327,7 @@ contract TestLoanManager is StarPortTest, DeepEq { vm.startPrank(loan.borrower); vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidItemType.selector)); - LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + LM.originate(new AdditionalTransfer[](0), borrowerCaveat, lenderCaveat, loan); vm.stopPrank(); } @@ -347,7 +347,7 @@ contract TestLoanManager is StarPortTest, DeepEq { vm.startPrank(loan.borrower); vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidItemTokenNoCode.selector)); - LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + LM.originate(new AdditionalTransfer[](0), borrowerCaveat, lenderCaveat, loan); vm.stopPrank(); } @@ -369,7 +369,7 @@ contract TestLoanManager is StarPortTest, DeepEq { // _setApprovalsForSpentItems(loan.issuer, loan.debt); vm.startPrank(loan.borrower); vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidItemTokenNoCode.selector)); - LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + LM.originate(new AdditionalTransfer[](0), borrowerCaveat, lenderCaveat, loan); vm.stopPrank(); } @@ -390,7 +390,7 @@ contract TestLoanManager is StarPortTest, DeepEq { _setApprovalsForSpentItems(loan.issuer, loan.debt); vm.startPrank(loan.borrower); vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidItemAmount.selector)); - LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + LM.originate(new AdditionalTransfer[](0), borrowerCaveat, lenderCaveat, loan); vm.stopPrank(); } @@ -409,7 +409,7 @@ contract TestLoanManager is StarPortTest, DeepEq { _setApprovalsForSpentItems(loan.issuer, loan.debt); vm.startPrank(loan.borrower); vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidTransferLength.selector)); - LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + LM.originate(new AdditionalTransfer[](0), borrowerCaveat, lenderCaveat, loan); vm.stopPrank(); } @@ -453,7 +453,7 @@ contract TestLoanManager is StarPortTest, DeepEq { }); loan.custodian = address(mockCustodian); vm.expectRevert(); - LM.originate(new ConduitTransfer[](0), borrowerEnforcer, lenderEnforcer, loan); + LM.originate(new AdditionalTransfer[](0), borrowerEnforcer, lenderEnforcer, loan); } // needs modification to work with the new origination flow (unsure if it needs to be elimianted all together) @@ -477,7 +477,7 @@ contract TestLoanManager is StarPortTest, DeepEq { _setApprovalsForSpentItems(loan.borrower, loan.collateral); _setApprovalsForSpentItems(loan.issuer, loan.debt); vm.prank(loan.borrower); - LM.originate(new ConduitTransfer[](0), borrowerEnforcer, lenderEnforcer, loan); + LM.originate(new AdditionalTransfer[](0), borrowerEnforcer, lenderEnforcer, loan); } function testNonPayableFunctions() public { @@ -493,7 +493,7 @@ contract TestLoanManager is StarPortTest, DeepEq { vm.expectRevert(); payable(address(LM)).call{value: 1 ether}( abi.encodeWithSelector( - LoanManager.originate.selector, new ConduitTransfer[](0), be, be, generateDefaultLoanTerms() + LoanManager.originate.selector, new AdditionalTransfer[](0), be, be, generateDefaultLoanTerms() ) ); vm.expectRevert(); @@ -534,12 +534,12 @@ contract TestLoanManager is StarPortTest, DeepEq { _setApprovalsForSpentItems(loan.issuer, loan.debt); vm.prank(loan.borrower); - LM.originate(new ConduitTransfer[](0), be, le1, loan); + LM.originate(new AdditionalTransfer[](0), be, le1, loan); _setApprovalsForSpentItems(loan.borrower, loan.collateral); _setApprovalsForSpentItems(loan.issuer, loan.debt); vm.expectRevert(abi.encodeWithSelector(LoanManager.LoanExists.selector)); vm.prank(loan.borrower); - LM.originate(new ConduitTransfer[](0), be, le2, loan); + LM.originate(new AdditionalTransfer[](0), be, le2, loan); } function testAdditionalTransfers() public { @@ -561,9 +561,9 @@ contract TestLoanManager is StarPortTest, DeepEq { }); _setApprovalsForSpentItems(loan.borrower, loan.collateral); _setApprovalsForSpentItems(loan.issuer, loan.debt); - ConduitTransfer[] memory additionalTransfers = new ConduitTransfer[](1); - additionalTransfers[0] = ConduitTransfer({ - itemType: ConduitItemType.ERC20, + AdditionalTransfer[] memory additionalTransfers = new AdditionalTransfer[](1); + additionalTransfers[0] = AdditionalTransfer({ + itemType: ItemType.ERC20, token: address(erc20s[0]), from: address(loan.borrower), to: address(address(20)), diff --git a/test/unit-testing/TestV1Hook.sol b/test/unit-testing/TestV1Hook.sol new file mode 100644 index 00000000..6d72390d --- /dev/null +++ b/test/unit-testing/TestV1Hook.sol @@ -0,0 +1,189 @@ +pragma solidity ^0.8.17; + +import "starport-test/AstariaV1Test.sol"; +import {StarPortLib, Actions} from "starport-core/lib/StarPortLib.sol"; +import {DeepEq} from "starport-test/utils/DeepEq.sol"; +import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; +import {SpentItemLib} from "seaport-sol/src/lib/SpentItemLib.sol"; +import {Originator} from "starport-core/originators/Originator.sol"; +import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; +import "forge-std/console2.sol"; + +contract TestAstariaV1Hook is AstariaV1Test, DeepEq { + using Cast for *; + using FixedPointMathLib for uint256; + using stdStorage for StdStorage; + using {StarPortLib.getId} for LoanManager.Loan; + + function testIsActive() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + uint256 loanId = loan.getId(); + assert(AstariaV1SettlementHook(loan.terms.hook).isActive(loan)); + } + + function testIsRecalledInsideWindow() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + uint256 loanId = loan.getId(); + + BaseRecall.Details memory details = abi.decode(loan.terms.hookData, (BaseRecall.Details)); + + erc20s[0].mint(address(this), 10e18); + erc20s[0].approve(loan.terms.hook, 10e18); + + skip(details.honeymoon); + AstariaV1SettlementHook(loan.terms.hook).recall(loan); + (address recaller, uint64 recallStart) = AstariaV1SettlementHook(loan.terms.hook).recalls(loanId); + skip(details.recallWindow - 1); + assert(AstariaV1SettlementHook(loan.terms.hook).isActive(loan)); + assert(AstariaV1SettlementHook(loan.terms.hook).isRecalled(loan)); + } + + function testIsRecalledOutsideWindow() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + uint256 loanId = loan.getId(); + BaseRecall.Details memory details = abi.decode(loan.terms.hookData, (BaseRecall.Details)); + + erc20s[0].mint(address(this), 10e18); + erc20s[0].approve(loan.terms.hook, 10e18); + + skip(details.honeymoon); + AstariaV1SettlementHook(loan.terms.hook).recall(loan); + (address recaller, uint64 recallStart) = AstariaV1SettlementHook(loan.terms.hook).recalls(loanId); + skip(details.recallWindow + 1); + assert(!AstariaV1SettlementHook(loan.terms.hook).isActive(loan)); + assert(!AstariaV1SettlementHook(loan.terms.hook).isRecalled(loan)); + } + + function testGenerateRecallConsideration() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + uint256 loanId = loan.getId(); + + BaseRecall.Details memory recallDetails = abi.decode(loan.terms.hookData, (BaseRecall.Details)); + BasePricing.Details memory pricingDetails = abi.decode(loan.terms.pricingData, (BasePricing.Details)); + //compute interest across a band from 0 to recallStakeDuration instead of from loan.start to end + uint256 recallStake = BasePricing(loan.terms.pricing).getInterest( + loan, + pricingDetails.rate, + 0, + recallDetails.recallStakeDuration, + 0 //index of the loan + ); + uint256 proportion = 1e18; + AdditionalTransfer[] memory recallConsideration = AstariaV1SettlementHook(loan.terms.hook) + .generateRecallConsideration(loan, proportion, payable(address(this)), payable(loan.issuer)); + assertEq(recallConsideration[0].token, address(erc20s[0])); + assertEq(recallConsideration[0].amount, recallStake); + assert(recallConsideration.length == 1); + proportion = 5e17; + recallConsideration = AstariaV1SettlementHook(loan.terms.hook).generateRecallConsideration( + loan, proportion, payable(address(this)), payable(loan.issuer) + ); + assertEq(recallConsideration[0].token, address(erc20s[0])); + assertEq(recallConsideration[0].amount, recallStake / 2); + assert(recallConsideration.length == 1); + } + + function testRecallRateEmptyRecall() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + BaseRecall.Details memory hookDetails = abi.decode(loan.terms.hookData, (BaseRecall.Details)); + uint256 recallRate = AstariaV1SettlementHook(loan.terms.hook).getRecallRate(loan); + uint256 computedRecallRate = + hookDetails.recallMax.mulWad((block.timestamp - 0).divWad(hookDetails.recallWindow)); + assertEq(recallRate, computedRecallRate); + } + + function testRecallRateActiveRecall() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + uint256 loanId = loan.getId(); + BaseRecall.Details memory hookDetails = abi.decode(loan.terms.hookData, (BaseRecall.Details)); + + erc20s[0].mint(address(this), 10e18); + erc20s[0].approve(loan.terms.hook, 10e18); + + skip(hookDetails.honeymoon); + AstariaV1SettlementHook(loan.terms.hook).recall(loan); + (address recaller, uint64 recallStart) = AstariaV1SettlementHook(loan.terms.hook).recalls(loanId); + uint256 recallRate = AstariaV1SettlementHook(loan.terms.hook).getRecallRate(loan); + uint256 computedRecallRate = + hookDetails.recallMax.mulWad((block.timestamp - recallStart).divWad(hookDetails.recallWindow)); + assertEq(recallRate, computedRecallRate); + } + + //TODO: this needs to be done because withdraw is being looked at + function testRecallWithdraw() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + uint256 loanId = loan.getId(); + BaseRecall.Details memory hookDetails = abi.decode(loan.terms.hookData, (BaseRecall.Details)); + + erc20s[0].mint(address(this), 10e18); + erc20s[0].approve(loan.terms.hook, 10e18); + + skip(hookDetails.honeymoon); + AstariaV1SettlementHook(loan.terms.hook).recall(loan); + + vm.mockCall(address(LM), abi.encodeWithSelector(LM.inactive.selector, loanId), abi.encode(true)); + } +} From 90411a2f9cab37ae9ec0f5a65c44b0fece20f632 Mon Sep 17 00:00:00 2001 From: androolloyd Date: Thu, 2 Nov 2023 15:39:18 -0300 Subject: [PATCH 3/3] feat/V1 hook updates (#44) * update getAuctionStart to be a public helper, make an internal version that is more optimized(less calls/decoding), add tests * coverage testing for AstariaV1Settlement handler complete * coverage testing for AstariaV1Settlement handler complete * fix test name * Astaria V1 Hook testing complete(-withdraw), Conduit Helper Removed, ConduitTransfer => AdditionalTransfer * remove commented code * final comment removal * additional tests for v1 hook as well as some cleanup/fixes * update snaphot --- .gas-snapshot | 159 +++++++++++++++++-------------- mermaid/origination.mmd | 57 +++++++++++ mermaid/origination.svg | 1 + package.json | 2 + src/hooks/BaseRecall.sol | 29 +++--- src/pricing/AstariaV1Pricing.sol | 4 +- test/unit-testing/TestV1Hook.sol | 131 ++++++++++++++++++++++--- 7 files changed, 283 insertions(+), 100 deletions(-) create mode 100644 mermaid/origination.mmd create mode 100644 mermaid/origination.svg diff --git a/.gas-snapshot b/.gas-snapshot index 7fa31faa..6e4de034 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,9 +1,30 @@ -DiffFuzzTestStarPortLib:testSpentToReceived((uint8,address,uint256,uint256)[]) (runs: 256, μ: 880529, ~: 882914) +DiffFuzzTestStarPortLib:testSpentToReceived((uint8,address,uint256,uint256)[]) (runs: 256, μ: 880433, ~: 883031) DiffFuzzTestStarPortLib:testUnboundSpentToReceived((uint8,address,uint256,uint256)[]) (runs: 256, μ: 232899, ~: 237832) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallBase() (gas: 1011545) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLender() (gas: 693342) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLiquidation() (gas: 746648) -TestBorrowerEnforcer:testBERevertAdditionalTransfers() (gas: 73101) +TestAstariaV1Handler:testGetAuctionStart() (gas: 425169) +TestAstariaV1Handler:testGetAuctionStartNotStarted() (gas: 424764) +TestAstariaV1Handler:testGetCurrentAuctionPrice() (gas: 440046) +TestAstariaV1Handler:testGetCurrentAuctionPriceNoAuction() (gas: 428373) +TestAstariaV1Handler:testGetSettlementDutchAuctionSettlementAbove() (gas: 482578) +TestAstariaV1Handler:testGetSettlementFailedDutchAuction() (gas: 440369) +TestAstariaV1Handler:testGetSettlementLoanNotRecalled() (gas: 430871) +TestAstariaV1Handler:testV1SettlementHandlerExecute() (gas: 411378) +TestAstariaV1Handler:testV1SettlementHandlerValidate() (gas: 411490) +TestAstariaV1Handler:testV1SettlementHandlerValidateInvalidHandler() (gas: 411614) +TestAstariaV1Hook:testCannotRecallTwice() (gas: 567550) +TestAstariaV1Hook:testCannotWithdrawLoanHasNotBeenRefinanced() (gas: 417239) +TestAstariaV1Hook:testCannotWithdrawWithdrawDoesNotExist() (gas: 423656) +TestAstariaV1Hook:testGenerateRecallConsideration() (gas: 466708) +TestAstariaV1Hook:testInvalidRecallInvalidStakeType() (gas: 483833) +TestAstariaV1Hook:testInvalidRecallLoanDoesNotExist() (gas: 513060) +TestAstariaV1Hook:testIsActive() (gas: 424376) +TestAstariaV1Hook:testIsRecalledInsideWindow() (gas: 560673) +TestAstariaV1Hook:testIsRecalledOutsideWindow() (gas: 558574) +TestAstariaV1Hook:testRecallRateActiveRecall() (gas: 546234) +TestAstariaV1Hook:testRecallRateEmptyRecall() (gas: 421919) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallBase() (gas: 1007784) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLender() (gas: 717605) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLiquidation() (gas: 742743) +TestBorrowerEnforcer:testBERevertAdditionalTransfers() (gas: 73128) TestBorrowerEnforcer:testBERevertInvalidLoanTerms() (gas: 78338) TestBorrowerEnforcer:testBEValidLoanTerms() (gas: 69429) TestBorrowerEnforcer:testBEValidLoanTermsAnyIssuer() (gas: 69581) @@ -12,107 +33,100 @@ TestCustodian:testCannotMintInvalidLoanInvalidCustodian() (gas: 66861) TestCustodian:testCannotMintInvalidLoanValidCustodian() (gas: 72501) TestCustodian:testCustodySelector() (gas: 2625664) TestCustodian:testDefaultCustodySelectorRevert() (gas: 70083) -TestCustodian:testGenerateOrderInvalidHandlerExecution() (gas: 132842) -TestCustodian:testGenerateOrderRepay() (gas: 173845) -TestCustodian:testGenerateOrderRepayAsRepayApprovedBorrower() (gas: 199470) -TestCustodian:testGenerateOrderRepayERC1155AndERC20AndNative() (gas: 864690) -TestCustodian:testGenerateOrderRepayERC1155AndERC20AndNativeHandlerAuthorized() (gas: 789937) -TestCustodian:testGenerateOrderRepayERC1155WithRevert() (gas: 529819) -TestCustodian:testGenerateOrderRepayInvalidHookAddress() (gas: 90336) -TestCustodian:testGenerateOrderRepayInvalidHookReturnType() (gas: 84653) -TestCustodian:testGenerateOrderRepayNotBorrower() (gas: 96427) -TestCustodian:testGenerateOrderSettlement() (gas: 151465) -TestCustodian:testGenerateOrderSettlementHandlerAuthorized() (gas: 160461) -TestCustodian:testGenerateOrderSettlementNoActiveLoan() (gas: 155682) -TestCustodian:testGenerateOrderSettlementUnauthorized() (gas: 94206) +TestCustodian:testGenerateOrderInvalidHandlerExecution() (gas: 132813) +TestCustodian:testGenerateOrderRepay() (gas: 173924) +TestCustodian:testGenerateOrderRepayAsRepayApprovedBorrower() (gas: 199549) +TestCustodian:testGenerateOrderRepayERC1155AndERC20AndNative() (gas: 865388) +TestCustodian:testGenerateOrderRepayERC1155AndERC20AndNativeHandlerAuthorized() (gas: 790527) +TestCustodian:testGenerateOrderRepayERC1155WithRevert() (gas: 530139) +TestCustodian:testGenerateOrderRepayInvalidHookAddress() (gas: 90307) +TestCustodian:testGenerateOrderRepayInvalidHookReturnType() (gas: 84624) +TestCustodian:testGenerateOrderRepayNotBorrower() (gas: 96398) +TestCustodian:testGenerateOrderSettlement() (gas: 151490) +TestCustodian:testGenerateOrderSettlementHandlerAuthorized() (gas: 160486) +TestCustodian:testGenerateOrderSettlementNoActiveLoan() (gas: 155707) +TestCustodian:testGenerateOrderSettlementUnauthorized() (gas: 94177) TestCustodian:testGetBorrower() (gas: 76322) -TestCustodian:testInvalidAction() (gas: 114479) -TestCustodian:testInvalidActionRepayInActiveLoan() (gas: 117428) -TestCustodian:testInvalidActionSettleActiveLoan() (gas: 117388) +TestCustodian:testInvalidAction() (gas: 114421) +TestCustodian:testInvalidActionRepayInActiveLoan() (gas: 117370) +TestCustodian:testInvalidActionSettleActiveLoan() (gas: 117330) TestCustodian:testName() (gas: 7098) TestCustodian:testNonPayableFunctions() (gas: 225770) TestCustodian:testOnlySeaport() (gas: 17895) TestCustodian:testPayableFunctions() (gas: 41667) -TestCustodian:testPreviewOrderNoActiveLoan() (gas: 98766) -TestCustodian:testPreviewOrderRepay() (gas: 225774) -TestCustodian:testPreviewOrderSettlement() (gas: 182835) -TestCustodian:testPreviewOrderSettlementInvalidFufliller() (gas: 100684) -TestCustodian:testPreviewOrderSettlementInvalidRepayer() (gas: 106814) -TestCustodian:testRatifyOrder() (gas: 180014) +TestCustodian:testPreviewOrderNoActiveLoan() (gas: 98737) +TestCustodian:testPreviewOrderRepay() (gas: 225878) +TestCustodian:testPreviewOrderSettlement() (gas: 182831) +TestCustodian:testPreviewOrderSettlementInvalidFufliller() (gas: 100655) +TestCustodian:testPreviewOrderSettlementInvalidRepayer() (gas: 106785) +TestCustodian:testRatifyOrder() (gas: 180093) TestCustodian:testSeaportMetadata() (gas: 8545) TestCustodian:testSetRepayApproval() (gas: 37861) TestCustodian:testSupportsInterface() (gas: 9428) TestCustodian:testSymbol() (gas: 7127) TestCustodian:testTokenURI() (gas: 64817) TestCustodian:testTokenURIInvalidLoan() (gas: 13196) -TestLenderEnforcer:testLERevertAdditionalTransfersFromLender() (gas: 73738) +TestLenderEnforcer:testLERevertAdditionalTransfersFromLender() (gas: 73765) TestLenderEnforcer:testLERevertInvalidLoanTerms() (gas: 78363) TestLenderEnforcer:testLEValidLoanTerms() (gas: 69496) TestLenderEnforcer:testLEValidLoanTermsAnyBorrower() (gas: 69494) -TestLenderEnforcer:testLEValidLoanTermsWithAdditionalTransfers() (gas: 70735) -TestLoanCombinations:testLoan20For721SimpleInterestDutchFixedRepay() (gas: 552683) -TestLoanCombinations:testLoan20for20SimpleInterestDutchFixedRepay() (gas: 540288) -TestLoanCombinations:testLoan721for20SimpleInterestDutchFixedRepay() (gas: 570519) -TestLoanCombinations:testLoanAstariaSettlementRepay() (gas: 560249) -TestLoanCombinations:testLoanSimpleInterestEnglishFixed() (gas: 565723) +TestLenderEnforcer:testLEValidLoanTermsWithAdditionalTransfers() (gas: 70762) +TestLoanCombinations:testLoan20For721SimpleInterestDutchFixedRepay() (gas: 553060) +TestLoanCombinations:testLoan20for20SimpleInterestDutchFixedRepay() (gas: 540665) +TestLoanCombinations:testLoan721for20SimpleInterestDutchFixedRepay() (gas: 570896) +TestLoanCombinations:testLoanAstariaSettlementRepay() (gas: 560626) +TestLoanCombinations:testLoanSimpleInterestEnglishFixed() (gas: 566100) TestLoanManager:testActive() (gas: 67135) -TestLoanManager:testAdditionalTransfers() (gas: 293184) -TestLoanManager:testCannotIssueSameLoanTwice() (gas: 331467) +TestLoanManager:testAdditionalTransfers() (gas: 293454) +TestLoanManager:testCannotIssueSameLoanTwice() (gas: 331845) TestLoanManager:testCannotOriginateWhilePaused() (gas: 87814) -TestLoanManager:testCannotSettleInvalidLoan() (gas: 72691) +TestLoanManager:testCannotSettleInvalidLoan() (gas: 72745) TestLoanManager:testCannotSettleUnlessValidCustodian() (gas: 68772) TestLoanManager:testCaveatEnforcerRevert() (gas: 119189) -TestLoanManager:testDefaultFeeRake() (gas: 352643) -TestLoanManager:testExoticDebtWithNoCaveatsNotAsBorrower() (gas: 342836) +TestLoanManager:testDefaultFeeRake() (gas: 352859) +TestLoanManager:testExoticDebtWithNoCaveatsNotAsBorrower() (gas: 343160) TestLoanManager:testInitializedFlagSetProperly() (gas: 65234) -TestLoanManager:testInvalidAmountCollateral() (gas: 152867) -TestLoanManager:testInvalidAmountCollateral721() (gas: 153010) -TestLoanManager:testInvalidAmountDebt() (gas: 177200) -TestLoanManager:testInvalidIdentifierDebt() (gas: 197194) -TestLoanManager:testInvalidItemType() (gas: 138915) -TestLoanManager:testInvalidTransferLengthCollateral() (gas: 161106) -TestLoanManager:testInvalidTransferLengthDebt() (gas: 165627) +TestLoanManager:testInvalidAmountCollateral() (gas: 152921) +TestLoanManager:testInvalidAmountCollateral721() (gas: 153064) +TestLoanManager:testInvalidAmountDebt() (gas: 177254) +TestLoanManager:testInvalidIdentifierDebt() (gas: 197248) +TestLoanManager:testInvalidItemType() (gas: 138969) +TestLoanManager:testInvalidTransferLengthCollateral() (gas: 161133) +TestLoanManager:testInvalidTransferLengthDebt() (gas: 165654) TestLoanManager:testIssued() (gas: 67084) TestLoanManager:testName() (gas: 7251) -TestLoanManager:testNonDefaultCustodianCustodyCallFails() (gas: 190254) -TestLoanManager:testNonDefaultCustodianCustodyCallSuccess() (gas: 258571) +TestLoanManager:testNonDefaultCustodianCustodyCallFails() (gas: 190362) +TestLoanManager:testNonDefaultCustodianCustodyCallSuccess() (gas: 258841) TestLoanManager:testNonPayableFunctions() (gas: 175599) -TestLoanManager:testOverrideFeeRake() (gas: 346404) +TestLoanManager:testOverrideFeeRake() (gas: 346620) TestLoanManager:testPause() (gas: 34091) TestLoanManager:testSupportsInterface() (gas: 9181) TestLoanManager:testSymbol() (gas: 7235) -TestLoanManager:testTokenNoCodeCollateral() (gas: 137687) -TestLoanManager:testTokenNoCodeDebt() (gas: 170831) +TestLoanManager:testTokenNoCodeCollateral() (gas: 137741) +TestLoanManager:testTokenNoCodeDebt() (gas: 170885) TestLoanManager:testTokenURI() (gas: 64945) TestLoanManager:testTokenURIInvalidLoan() (gas: 13384) TestLoanManager:testTransferFromFail() (gas: 80088) TestLoanManager:testUnPause() (gas: 14291) -TestNewLoan:testBuyNowPayLater() (gas: 2831787) -TestNewLoan:testNewLoanERC721CollateralDefaultTerms2() (gas: 391958) +TestNewLoan:testBuyNowPayLater() (gas: 2830344) +TestNewLoan:testNewLoanERC721CollateralDefaultTerms2() (gas: 392228) TestNewLoan:testNewLoanERC721CollateralLessDebtThanOffered() (gas: 2348) TestNewLoan:testNewLoanRefinanceNew() (gas: 185) -TestNewLoan:testNewLoanViaOriginatorBorrowerApprovalAndLenderApproval() (gas: 299404) -TestNewLoan:testNewLoanViaOriginatorLenderApproval() (gas: 354020) +TestNewLoan:testNewLoanViaOriginatorBorrowerApprovalAndLenderApproval() (gas: 299558) +TestNewLoan:testNewLoanViaOriginatorLenderApproval() (gas: 354228) TestNewLoan:testSettleLoan() (gas: 252) TestRefStarPortLib:testSpentToReceived() (gas: 13315) TestRefStarPortLib:testValidateSalt(address,bytes32) (runs: 256, μ: 33865, ~: 33865) -TestRepayLoan:testRepayLoanApprovedRepayer() (gas: 643615) -TestRepayLoan:testRepayLoanBase() (gas: 580019) -TestRepayLoan:testRepayLoanGenerateOrderNotSeaport() (gas: 399854) -TestRepayLoan:testRepayLoanInSettlement() (gas: 540930) -TestRepayLoan:testRepayLoanInvalidRepayer() (gas: 560285) -TestRepayLoan:testRepayLoanThatDoesNotExist() (gas: 848820) +TestRepayLoan:testRepayLoanApprovedRepayer() (gas: 643968) +TestRepayLoan:testRepayLoanBase() (gas: 580372) +TestRepayLoan:testRepayLoanGenerateOrderNotSeaport() (gas: 400095) +TestRepayLoan:testRepayLoanInSettlement() (gas: 541092) +TestRepayLoan:testRepayLoanInvalidRepayer() (gas: 560501) +TestRepayLoan:testRepayLoanThatDoesNotExist() (gas: 849335) TestStarPortLib:testSpentToReceived() (gas: 13315) TestStarPortLib:testValidateSalt(address,bytes32) (runs: 256, μ: 33865, ~: 33865) TestStrategistOriginator:testEncodeWithAccountCounter() (gas: 12307) -TestStrategistOriginator:testGetAuctionStart() (gas: 424899) -TestStrategistOriginator:testGetAuctionStartNotStarted() (gas: 424491) -TestStrategistOriginator:testGetCurrentAuctionPrice() (gas: 439776) -TestStrategistOriginator:testGetCurrentAuctionPriceNoAuction() (gas: 428100) -TestStrategistOriginator:testGetSettlementDutchAuctionSettlementAbove() (gas: 482308) -TestStrategistOriginator:testGetSettlementFailedDutchAuction() (gas: 440099) -TestStrategistOriginator:testGetSettlementLoanNotRecalled() (gas: 430598) -TestStrategistOriginator:testGetStrategistData() (gas: 1473217) +TestStrategistOriginator:testGetStrategistData() (gas: 1471010) TestStrategistOriginator:testIncrementCounterAsStrategist() (gas: 18654) TestStrategistOriginator:testIncrementCounterNotAuthorized() (gas: 13445) TestStrategistOriginator:testInvalidCollateral() (gas: 204438) @@ -122,9 +136,6 @@ TestStrategistOriginator:testInvalidDebtAmountAskingMoreThanOffered() (gas: 2065 TestStrategistOriginator:testInvalidDebtAmountOfferingZero() (gas: 186947) TestStrategistOriginator:testInvalidDebtAmountRequestingZero() (gas: 206823) TestStrategistOriginator:testInvalidDebtLength() (gas: 205450) -TestStrategistOriginator:testInvalidOffer() (gas: 396393) +TestStrategistOriginator:testInvalidOffer() (gas: 396601) TestStrategistOriginator:testInvalidSigner() (gas: 208683) -TestStrategistOriginator:testSetStrategist() (gas: 17796) -TestStrategistOriginator:testV1SettlementHandlerExecute() (gas: 411108) -TestStrategistOriginator:testV1SettlementHandlerValidate() (gas: 411220) -TestStrategistOriginator:testV1SettlementHandlerValidateInvalidHandler() (gas: 411344) \ No newline at end of file +TestStrategistOriginator:testSetStrategist() (gas: 17796) \ No newline at end of file diff --git a/mermaid/origination.mmd b/mermaid/origination.mmd new file mode 100644 index 00000000..03d1a2d5 --- /dev/null +++ b/mermaid/origination.mmd @@ -0,0 +1,57 @@ +sequenceDiagram + title Starport Origination Sequence Diagram + participant F as Fulfiller + participant L as Lender + participant B as Borrower + + + F->>LoanManager: originate + + opt F is not Borrower + loop 1->n + LoanManager->>CaveatEnforcer: validate + end + end + opt F is not Lender + loop 1->n + LoanManager->>CaveatEnforcer: validate + end + end + loop Transfer 1->n collateral items + B->>Custodian: Move Collateral to Custodian + end + opt Custodian is not default + LoanManager->>Custodian: custody (optional) + end + + alt Fees Disabled + loop Transfer 1->n debt items + L->>B: Move debt to borrower + end + else Fees Enabled + LoanManager->>LoanManager: compute feeRake + opt FeeItems length > 0 + loop Transfer 1->n fee items + L->>FeeRecipient: Move Fee to FeeRecipient + end + end + loop Transfer 1->n debt items + L->>B: Move debt to Borrower + end + end + + opt AdditionalTransferItems length > 0 + loop 1->n + alt From is Borrower + B->>AdditionalTransferRecipient: AdditionalTransferItem from Borrower => AdditionalTransferRecipient + else From is Lender + L->>AdditionalTransferRecipient: AdditionalTransferItem from Lender => AdditionalTransferRecipient + else From is F + F->>AdditionalTransferRecipient: AdditionalTransferItem from Fulfiller => AdditionalTransferRecipient + end + end + end + + opt Lender is contract + LoanManager->>L: onERC721Received + end \ No newline at end of file diff --git a/mermaid/origination.svg b/mermaid/origination.svg new file mode 100644 index 00000000..eb4ec3ca --- /dev/null +++ b/mermaid/origination.svg @@ -0,0 +1 @@ +AdditionalTransferRecipientFeeRecipientCustodianCaveatEnforcerLoanManagerBorrowerLenderFulfillerAdditionalTransferRecipientFeeRecipientCustodianCaveatEnforcerLoanManagerBorrowerLenderFulfillerloop[1->n]opt[F is not Borrower]loop[1->n]opt[F is not Lender]loop[Transfer 1->n collateral items]opt[Custodian is not default]loop[Transfer 1->n debt items]loop[Transfer 1->n fee items]opt[FeeItems length > 0]loop[Transfer 1->n debt items]alt[Fees Disabled][Fees Enabled]alt[From is Borrower][From is Lender][From is F]loop[1->n]opt[AdditionalTransferItems length > 0]opt[Lender is contract]originatevalidatevalidateMove Collateral to Custodiancustody (optional)Move debt to borrowercompute feeRakeMove Fee to FeeRecipientMove debt to BorrowerAdditionalTransferItem from Borrower => AdditionalTransferRecipientAdditionalTransferItem from Lender => AdditionalTransferRecipientAdditionalTransferItem from Fulfiller => AdditionalTransferRecipientonERC721ReceivedStarport Origination Sequence Diagram \ No newline at end of file diff --git a/package.json b/package.json index e008f340..617100ee 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,9 @@ "name": "starlite", "dependencies": { "@chainlink/contracts": "^0.6.1", + "@mermaid-js/mermaid-cli": "^10.6.0", "chai": "^4.3.7", + "global": "^4.4.0", "husky": "^8.0.3", "mocha": "^10.2.0", "prettier": "^2.8.8", diff --git a/src/hooks/BaseRecall.sol b/src/hooks/BaseRecall.sol index f280994d..769aa698 100644 --- a/src/hooks/BaseRecall.sol +++ b/src/hooks/BaseRecall.sol @@ -59,6 +59,7 @@ abstract contract BaseRecall { error LoanHasNotBeenRefinanced(); error WithdrawDoesNotExist(); error InvalidItemType(); + error RecallAlreadyExists(); mapping(uint256 => Recall) public recalls; @@ -99,26 +100,22 @@ abstract contract BaseRecall { } if (loan.issuer != msg.sender && loan.borrower != msg.sender) { - // (,, address conduitController) = LM.seaport().information(); - // validate that the provided conduit is owned by the msg.sender - // if (ConduitControllerInterface(conduitController).ownerOf(conduit) != msg.sender) { - // revert InvalidConduit(); - // } AdditionalTransfer[] memory recallConsideration = _generateRecallConsideration( loan, 0, details.recallStakeDuration, 1e18, msg.sender, payable(address(this)) ); StarPortLib.transferAdditionalTransfers(recallConsideration); } - // get conduitController - - bytes memory encodedLoan = abi.encode(loan); - - uint256 loanId = uint256(keccak256(encodedLoan)); + uint256 loanId = loan.getId(); - if (!LM.active(loanId)) revert LoanDoesNotExist(); + if (!LM.active(loanId)) { + revert LoanDoesNotExist(); + } + if (recalls[loanId].start > 0) { + revert RecallAlreadyExists(); + } recalls[loanId] = Recall(payable(msg.sender), uint64(block.timestamp)); - emit Recalled(loanId, msg.sender, loan.start + details.recallWindow); + emit Recalled(loanId, msg.sender, block.timestamp + details.recallWindow); } // transfers all stake to anyone who asks after the LM token is burned @@ -128,7 +125,9 @@ abstract contract BaseRecall { uint256 loanId = uint256(keccak256(encodedLoan)); // loan has not been refinanced, loan is still active. LM.tokenId changes on refinance - if (!LM.inactive(loanId)) revert LoanHasNotBeenRefinanced(); + if (!LM.inactive(loanId)) { + revert LoanHasNotBeenRefinanced(); + } Recall storage recall = recalls[loanId]; // ensure that a recall exists for the provided tokenId, ensure that the recall @@ -143,7 +142,9 @@ abstract contract BaseRecall { recall.start = 0; for (uint256 i; i < recallConsideration.length;) { - if (loan.debt[i].itemType != ItemType.ERC20) revert InvalidStakeType(); + if (loan.debt[i].itemType != ItemType.ERC20) { + revert InvalidItemType(); + } ERC20(loan.debt[i].token).transfer(receiver, recallConsideration[i].amount); diff --git a/src/pricing/AstariaV1Pricing.sol b/src/pricing/AstariaV1Pricing.sol index d47272f5..6b1fa004 100644 --- a/src/pricing/AstariaV1Pricing.sol +++ b/src/pricing/AstariaV1Pricing.sol @@ -40,7 +40,9 @@ contract AstariaV1Pricing is CompoundInterestPricing { if (hook.isRecalled(loan)) { uint256 rate = hook.getRecallRate(loan); // offered loan did not meet the terms of the recall auction - if (newDetails.rate > rate) revert InsufficientRefinance(); + if (newDetails.rate > rate) { + revert InsufficientRefinance(); + } } // recall is not occuring else { diff --git a/test/unit-testing/TestV1Hook.sol b/test/unit-testing/TestV1Hook.sol index 6d72390d..9309deb1 100644 --- a/test/unit-testing/TestV1Hook.sol +++ b/test/unit-testing/TestV1Hook.sol @@ -15,6 +15,9 @@ contract TestAstariaV1Hook is AstariaV1Test, DeepEq { using stdStorage for StdStorage; using {StarPortLib.getId} for LoanManager.Loan; + event Recalled(uint256 loandId, address recaller, uint256 end); + event Withdraw(uint256 loanId, address withdrawer); + function testIsActive() public { LoanManager.Terms memory terms = LoanManager.Terms({ hook: address(hook), @@ -49,6 +52,8 @@ contract TestAstariaV1Hook is AstariaV1Test, DeepEq { erc20s[0].approve(loan.terms.hook, 10e18); skip(details.honeymoon); + vm.expectEmit(); + emit Recalled(loanId, address(this), block.timestamp + details.recallWindow); AstariaV1SettlementHook(loan.terms.hook).recall(loan); (address recaller, uint64 recallStart) = AstariaV1SettlementHook(loan.terms.hook).recalls(loanId); skip(details.recallWindow - 1); @@ -56,6 +61,80 @@ contract TestAstariaV1Hook is AstariaV1Test, DeepEq { assert(AstariaV1SettlementHook(loan.terms.hook).isRecalled(loan)); } + function testInvalidRecallLoanDoesNotExist() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + uint256 loanId = loan.getId(); + + BaseRecall.Details memory details = abi.decode(loan.terms.hookData, (BaseRecall.Details)); + + erc20s[0].mint(address(this), 10e18); + erc20s[0].approve(loan.terms.hook, 10e18); + + skip(details.honeymoon); + vm.mockCall(address(LM), abi.encodeWithSelector(LM.active.selector, loan.getId()), abi.encode(false)); + vm.expectRevert(abi.encodeWithSelector(BaseRecall.LoanDoesNotExist.selector)); + AstariaV1SettlementHook(loan.terms.hook).recall(loan); + } + + function testInvalidRecallInvalidStakeType() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + uint256 loanId = loan.getId(); + + loan.debt[0].itemType = ItemType.ERC721; + loan.debt[0].amount = 1; + BaseRecall.Details memory details = abi.decode(loan.terms.hookData, (BaseRecall.Details)); + + skip(details.honeymoon); + vm.mockCall(address(LM), abi.encodeWithSelector(LM.active.selector, loan.getId()), abi.encode(true)); + AstariaV1SettlementHook(loan.terms.hook).recall(loan); + skip(details.recallWindow); + vm.mockCall(address(LM), abi.encodeWithSelector(LM.inactive.selector, loan.getId()), abi.encode(true)); + vm.expectRevert(abi.encodeWithSelector(BaseRecall.InvalidItemType.selector)); + AstariaV1SettlementHook(loan.terms.hook).withdraw(loan, payable(address(this))); + } + + function testCannotRecallTwice() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + uint256 loanId = loan.getId(); + + BaseRecall.Details memory details = abi.decode(loan.terms.hookData, (BaseRecall.Details)); + + erc20s[0].mint(address(this), 10e18); + erc20s[0].approve(loan.terms.hook, 10e18); + + skip(details.honeymoon); + AstariaV1SettlementHook(loan.terms.hook).recall(loan); + vm.expectRevert(abi.encodeWithSelector(BaseRecall.RecallAlreadyExists.selector)); + AstariaV1SettlementHook(loan.terms.hook).recall(loan); + } + function testIsRecalledOutsideWindow() public { LoanManager.Terms memory terms = LoanManager.Terms({ hook: address(hook), @@ -163,8 +242,7 @@ contract TestAstariaV1Hook is AstariaV1Test, DeepEq { assertEq(recallRate, computedRecallRate); } - //TODO: this needs to be done because withdraw is being looked at - function testRecallWithdraw() public { + function testCannotWithdrawWithdrawDoesNotExist() public { LoanManager.Terms memory terms = LoanManager.Terms({ hook: address(hook), handler: address(handler), @@ -175,15 +253,46 @@ contract TestAstariaV1Hook is AstariaV1Test, DeepEq { }); LoanManager.Loan memory loan = _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); - uint256 loanId = loan.getId(); - BaseRecall.Details memory hookDetails = abi.decode(loan.terms.hookData, (BaseRecall.Details)); - - erc20s[0].mint(address(this), 10e18); - erc20s[0].approve(loan.terms.hook, 10e18); - - skip(hookDetails.honeymoon); - AstariaV1SettlementHook(loan.terms.hook).recall(loan); + vm.mockCall(address(LM), abi.encodeWithSelector(LM.inactive.selector, loan.getId()), abi.encode(true)); + vm.expectRevert(abi.encodeWithSelector(BaseRecall.WithdrawDoesNotExist.selector)); + AstariaV1SettlementHook(loan.terms.hook).withdraw(loan, payable(address(this))); + } - vm.mockCall(address(LM), abi.encodeWithSelector(LM.inactive.selector, loanId), abi.encode(true)); + function testCannotWithdrawLoanHasNotBeenRefinanced() public { + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + vm.expectRevert(abi.encodeWithSelector(BaseRecall.LoanHasNotBeenRefinanced.selector)); + AstariaV1SettlementHook(loan.terms.hook).withdraw(loan, payable(address(this))); } + // //TODO: this needs to be done because withdraw is being looked at + // function testRecallWithdraw() public { + // LoanManager.Terms memory terms = LoanManager.Terms({ + // hook: address(hook), + // handler: address(handler), + // pricing: address(pricing), + // pricingData: defaultPricingData, + // handlerData: defaultHandlerData, + // hookData: defaultHookData + // }); + // LoanManager.Loan memory loan = + // _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + // uint256 loanId = loan.getId(); + // BaseRecall.Details memory hookDetails = abi.decode(loan.terms.hookData, (BaseRecall.Details)); + // + // erc20s[0].mint(address(this), 10e18); + // erc20s[0].approve(loan.terms.hook, 10e18); + // + // skip(hookDetails.honeymoon); + // AstariaV1SettlementHook(loan.terms.hook).recall(loan); + // + // vm.mockCall(address(LM), abi.encodeWithSelector(LM.inactive.selector, loanId), abi.encode(true)); + // } }