From b2157b34989357d98c94e9ca81b31b6078ea58a9 Mon Sep 17 00:00:00 2001 From: androolloyd Date: Thu, 2 Nov 2023 10:29:07 -0300 Subject: [PATCH] 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)); + } +}