From cc94101decba1e4410763bf33dbb8b759d55152f Mon Sep 17 00:00:00 2001 From: Joseph Delong Date: Mon, 30 Oct 2023 08:58:11 -0500 Subject: [PATCH] Implement all new origination flow (#36) * feat: implement new origination flow * fix: integrate with LoanManager, get tests compiling * fix: fix first integration test * fix: TestAstariaV1Loan fully working * fix: custodian tests working * fix: all test running or commented out * updates to multiple caveats, tweaks to enforcement flows, gas savings and signature lib updates * update LM tests for the new orgination flow * add snapshot * test for additional transfers --------- Co-authored-by: Andrew Redden --- .gas-snapshot | 174 ++- src/Custodian.sol | 43 +- src/LoanManager.sol | 832 +++++------- src/enforcers/BorrowerEnforcer.sol | 31 + src/enforcers/CaveatEnforcer.sol | 21 +- src/enforcers/CollateralEnforcer.sol | 25 - src/enforcers/LenderEnforcer.sol | 41 + src/enforcers/RateEnforcer.sol | 23 - src/enforcers/TermEnforcer.sol | 34 - src/handlers/AstariaV1SettlementHandler.sol | 87 +- src/handlers/DutchAuctionHandler.sol | 51 +- src/handlers/EnglishAuctionHandler.sol | 3 + src/hooks/BaseRecall.sol | 91 +- src/pricing/AstariaV1Pricing.sol | 10 +- src/pricing/BasePricing.sol | 118 +- src/pricing/BaseRecallPricing.sol | 10 +- src/pricing/Pricing.sol | 9 +- src/pricing/SimpleInterestPricing.sol | 10 +- test/AstariaV1Test.sol | 21 +- test/StarPortTest.sol | 1002 ++++++++------- .../integration-testing/TestAstariaV1Loan.sol | 237 ++-- .../integration-testing/TestExoticLoans.t.sol | 95 -- .../TestLoanCombinations.t.sol | 71 +- test/integration-testing/TestNewLoan.sol | 516 ++++---- test/integration-testing/TestRepayLoan.sol | 98 +- test/unit-testing/EnforcerTest.t.sol | 600 ++++----- test/unit-testing/TestCustodian.sol | 233 ++-- test/unit-testing/TestLoanManager.sol | 1138 ++++------------- 28 files changed, 2438 insertions(+), 3186 deletions(-) create mode 100644 src/enforcers/BorrowerEnforcer.sol delete mode 100644 src/enforcers/CollateralEnforcer.sol create mode 100644 src/enforcers/LenderEnforcer.sol delete mode 100644 src/enforcers/RateEnforcer.sol delete mode 100644 src/enforcers/TermEnforcer.sol delete mode 100644 test/integration-testing/TestExoticLoans.t.sol diff --git a/.gas-snapshot b/.gas-snapshot index edbc069a..e873aaac 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,103 +1,81 @@ -EnforcerTest:testCollateralEnforcer() (gas: 954073) -EnforcerTest:testFailCollateralEnforcerDifferentCollateral() (gas: 834092) -EnforcerTest:testFailRateEnforcerMaxCarryRate() (gas: 789228) -EnforcerTest:testFailRateEnforcerMaxRate() (gas: 789200) -EnforcerTest:testFailRateEnforcerMaxRateAndMaxCarryRate() (gas: 789065) -EnforcerTest:testRateEnforcerBasic() (gas: 911246) -EnforcerTest:testTermEnforcerBasic() (gas: 983574) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallBase() (gas: 1218188) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLender() (gas: 858353) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLiquidation() (gas: 894602) -TestCustodian:testCannotLazyMintTwice() (gas: 76663) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallBase() (gas: 1039520) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLender() (gas: 691168) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLiquidation() (gas: 770737) +TestCustodian:testCannotLazyMintTwice() (gas: 76597) TestCustodian:testCannotMintInvalidLoanInvalidCustodian() (gas: 66883) -TestCustodian:testCannotMintInvalidLoanValidCustodian() (gas: 72400) -TestCustodian:testCustodySelector() (gas: 2818119) -TestCustodian:testDefaultCustodySelectorRevert() (gas: 11672) -TestCustodian:testGenerateOrderInvalidHandlerExecution() (gas: 139368) -TestCustodian:testGenerateOrderRepay() (gas: 176877) -TestCustodian:testGenerateOrderRepayAsRepayApprovedBorrower() (gas: 202524) -TestCustodian:testGenerateOrderRepayERC1155AndERC20AndNative() (gas: 1115040) -TestCustodian:testGenerateOrderRepayERC1155AndERC20AndNativeHandlerAuthorized() (gas: 1028737) -TestCustodian:testGenerateOrderRepayERC1155WithRevert() (gas: 524739) -TestCustodian:testGenerateOrderRepayInvalidHookAddress() (gas: 96790) -TestCustodian:testGenerateOrderRepayInvalidHookReturnType() (gas: 91194) -TestCustodian:testGenerateOrderRepayNotBorrower() (gas: 102990) -TestCustodian:testGenerateOrderSettlement() (gas: 158129) -TestCustodian:testGenerateOrderSettlementHandlerAuthorized() (gas: 167257) -TestCustodian:testGenerateOrderSettlementNoActiveLoan() (gas: 162422) -TestCustodian:testGenerateOrderSettlementUnauthorized() (gas: 100769) -TestCustodian:testGetBorrower() (gas: 76157) -TestCustodian:testInvalidAction() (gas: 121504) -TestCustodian:testInvalidActionRepayInActiveLoan() (gas: 124409) -TestCustodian:testInvalidActionSettleActiveLoan() (gas: 124413) -TestCustodian:testName() (gas: 7098) -TestCustodian:testNonPayableFunctions() (gas: 219009) -TestCustodian:testOnlySeaport() (gas: 17975) -TestCustodian:testPayableFunctions() (gas: 43468) -TestCustodian:testPreviewOrderNoActiveLoan() (gas: 105228) -TestCustodian:testPreviewOrderRepay() (gas: 227912) -TestCustodian:testPreviewOrderSettlement() (gas: 190065) -TestCustodian:testPreviewOrderSettlementInvalidFufliller() (gas: 107146) -TestCustodian:testPreviewOrderSettlementInvalidRepayer() (gas: 113276) -TestCustodian:testRatifyOrder() (gas: 183062) -TestCustodian:testSafeTransfer1155Receive() (gas: 115587) -TestCustodian:testSeaportMetadata() (gas: 8589) -TestCustodian:testSetRepayApproval() (gas: 37839) -TestCustodian:testSupportsInterface() (gas: 9406) -TestCustodian:testSymbol() (gas: 7171) +TestCustodian:testCannotMintInvalidLoanValidCustodian() (gas: 72422) +TestCustodian:testCustodySelector() (gas: 2543980) +TestCustodian:testDefaultCustodySelectorRevert() (gas: 70105) +TestCustodian:testGenerateOrderInvalidHandlerExecution() (gas: 132855) +TestCustodian:testGenerateOrderRepay() (gas: 164375) +TestCustodian:testGenerateOrderRepayAsRepayApprovedBorrower() (gas: 190022) +TestCustodian:testGenerateOrderRepayERC1155AndERC20AndNative() (gas: 843726) +TestCustodian:testGenerateOrderRepayERC1155AndERC20AndNativeHandlerAuthorized() (gas: 785358) +TestCustodian:testGenerateOrderRepayERC1155WithRevert() (gas: 518154) +TestCustodian:testGenerateOrderRepayInvalidHookAddress() (gas: 90249) +TestCustodian:testGenerateOrderRepayInvalidHookReturnType() (gas: 84653) +TestCustodian:testGenerateOrderRepayNotBorrower() (gas: 96449) +TestCustodian:testGenerateOrderSettlement() (gas: 151381) +TestCustodian:testGenerateOrderSettlementHandlerAuthorized() (gas: 160421) +TestCustodian:testGenerateOrderSettlementNoActiveLoan() (gas: 155620) +TestCustodian:testGenerateOrderSettlementUnauthorized() (gas: 94219) +TestCustodian:testGetBorrower() (gas: 76234) +TestCustodian:testInvalidAction() (gas: 114400) +TestCustodian:testInvalidActionRepayInActiveLoan() (gas: 117327) +TestCustodian:testInvalidActionSettleActiveLoan() (gas: 117287) +TestCustodian:testName() (gas: 7120) +TestCustodian:testNonPayableFunctions() (gas: 225770) +TestCustodian:testOnlySeaport() (gas: 17917) +TestCustodian:testPayableFunctions() (gas: 41689) +TestCustodian:testPreviewOrderNoActiveLoan() (gas: 98687) +TestCustodian:testPreviewOrderRepay() (gas: 208128) +TestCustodian:testPreviewOrderSettlement() (gas: 182676) +TestCustodian:testPreviewOrderSettlementInvalidFufliller() (gas: 100488) +TestCustodian:testPreviewOrderSettlementInvalidRepayer() (gas: 106646) +TestCustodian:testRatifyOrder() (gas: 170218) +TestCustodian:testSeaportMetadata() (gas: 8567) +TestCustodian:testSetRepayApproval() (gas: 37883) +TestCustodian:testSupportsInterface() (gas: 9428) +TestCustodian:testSymbol() (gas: 7149) TestCustodian:testTokenURI() (gas: 64839) -TestCustodian:testTokenURIInvalidLoan() (gas: 13196) -TestExoticLoans:testSwap() (gas: 1353169) -TestLoanCombinations:testLoan20For721SimpleInterestDutchFixedRepay() (gas: 164) -TestLoanCombinations:testLoan20for20SimpleInterestDutchFixedRepay() (gas: 517422) -TestLoanCombinations:testLoan721for20SimpleInterestDutchFixedRepay() (gas: 711981) -TestLoanCombinations:testLoanAstariaSettlementRepay() (gas: 541506) -TestLoanCombinations:testLoanSimpleInterestEnglishFixed() (gas: 701482) -TestLoanManager:testCannotIssueSameLoanTwice() (gas: 1529536) -TestLoanManager:testCannotSettleInvalidLoan() (gas: 72844) -TestLoanManager:testCannotSettleUnlessValidCustodian() (gas: 68990) -TestLoanManager:testCaveatEnforcerInvalidOrigination() (gas: 1797025) -TestLoanManager:testDefaultFeeRake() (gas: 403187) -TestLoanManager:testExoticDebtWithNoCaveatsAsBorrower() (gas: 1587178) -TestLoanManager:testExoticDebtWithNoCaveatsNotAsBorrower() (gas: 1677088) -TestLoanManager:testGenerateOrder() (gas: 1495062) -TestLoanManager:testGenerateOrderInvalidAction() (gas: 1354698) -TestLoanManager:testGenerateOrderNotSeaport() (gas: 13023) -TestLoanManager:testInitializedFlagSetProperly() (gas: 65194) -TestLoanManager:testInvalidDebtLength() (gas: 39181) -TestLoanManager:testInvalidDebtType() (gas: 1364311) -TestLoanManager:testInvalidMaximumSpentEmpty() (gas: 50192) -TestLoanManager:testIssued() (gas: 67041) -TestLoanManager:testName() (gas: 7209) -TestLoanManager:testNativeDebtWithNoCaveatsAsBorrower() (gas: 1500521) -TestLoanManager:testNativeDebtWithNoCaveatsNotAsBorrower() (gas: 1541426) -TestLoanManager:testNativeDebtWithNoCaveatsNotAsBorrowerFeesOn() (gas: 1604818) -TestLoanManager:testNonDefaultCustodianCustodyCallFails() (gas: 23953) -TestLoanManager:testNonDefaultCustodianCustodyCallSuccess() (gas: 25327) -TestLoanManager:testNonPayableFunctions() (gas: 109562) -TestLoanManager:testOverrideFeeRake() (gas: 404503) -TestLoanManager:testPayableFunctions() (gas: 55281) -TestLoanManager:testPreviewOrderInvalidAction() (gas: 1299017) -TestLoanManager:testPreviewOrderOriginationWithNoCaveatsSetAsBorrowerFeeOn() (gas: 1345487) -TestLoanManager:testPreviewOrderOriginationWithNoCaveatsSetAsBorrowerNoFee() (gas: 1313690) -TestLoanManager:testPreviewOrderOriginationWithNoCaveatsSetNotBorrowerFeeOn() (gas: 1354173) -TestLoanManager:testPreviewOrderOriginationWithNoCaveatsSetNotBorrowerNoFee() (gas: 1322064) -TestLoanManager:testPreviewOrderRefinanceAsRefinancerFeeOff() (gas: 1373981) -TestLoanManager:testPreviewOrderRefinanceAsRefinancerFeeOn() (gas: 1399308) -TestLoanManager:testRefinanceNoRefinanceConsideration() (gas: 1386463) -TestLoanManager:testSafeTransfer1155Receive() (gas: 54338) -TestLoanManager:testSeaportMetadata() (gas: 8667) -TestLoanManager:testSupportsInterface() (gas: 9580) -TestLoanManager:testSymbol() (gas: 7238) -TestLoanManager:testTokenURI() (gas: 64967) -TestLoanManager:testTokenURIInvalidLoan() (gas: 13290) -TestLoanManager:testTransferFromFailFromSeaport() (gas: 82187) -TestNewLoan:testBuyNowPayLater() (gas: 1143307) -TestNewLoan:testNewLoanERC721CollateralDefaultTerms2():((uint256,address,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256)[],(address,bytes,address,bytes,address,bytes))) (gas: 982374) -TestNewLoan:testNewLoanERC721CollateralLessDebtThanOffered():((uint256,address,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256)[],(address,bytes,address,bytes,address,bytes))) (gas: 982708) -TestNewLoan:testNewLoanRefinanceNew() (gas: 702267) -TestNewLoan:testSettleLoan() (gas: 1223453) -TestRepayLoan:testRepayLoan() (gas: 683942) +TestCustodian:testTokenURIInvalidLoan() (gas: 13218) +TestLoanCombinations:testLoan20For721SimpleInterestDutchFixedRepay() (gas: 530977) +TestLoanCombinations:testLoan20for20SimpleInterestDutchFixedRepay() (gas: 499814) +TestLoanCombinations:testLoan721for20SimpleInterestDutchFixedRepay() (gas: 549734) +TestLoanCombinations:testLoanAstariaSettlementRepay() (gas: 539558) +TestLoanCombinations:testLoanSimpleInterestEnglishFixed() (gas: 544218) +TestLoanManager:testCannotIssueSameLoanTwice() (gas: 331333) +TestLoanManager:testCannotSettleInvalidLoan() (gas: 72594) +TestLoanManager:testCannotSettleUnlessValidCustodian() (gas: 68772) +TestLoanManager:testCaveatEnforcerRevert() (gas: 116699) +TestLoanManager:testDefaultFeeRake() (gas: 350119) +TestLoanManager:testExoticDebtWithNoCaveatsNotAsBorrower() (gas: 342776) +TestLoanManager:testInitializedFlagSetProperly() (gas: 65230) +TestLoanManager:testInvalidAmountCollateral() (gas: 152875) +TestLoanManager:testInvalidAmountCollateral721() (gas: 152995) +TestLoanManager:testInvalidAmountDebt() (gas: 177208) +TestLoanManager:testInvalidItemType() (gas: 138923) +TestLoanManager:testInvalidTransferLengthCollateral() (gas: 161136) +TestLoanManager:testInvalidTransferLengthDebt() (gas: 165669) +TestLoanManager:testIssued() (gas: 67055) +TestLoanManager:testName() (gas: 7184) +TestLoanManager:testNonDefaultCustodianCustodyCallFails() (gas: 190240) +TestLoanManager:testNonDefaultCustodianCustodyCallSuccess() (gas: 258491) +TestLoanManager:testNonPayableFunctions() (gas: 173071) +TestLoanManager:testOverrideFeeRake() (gas: 343912) +TestLoanManager:testSupportsInterface() (gas: 9159) +TestLoanManager:testSymbol() (gas: 7127) +TestLoanManager:testTokenNoCodeCollateral() (gas: 137717) +TestLoanManager:testTokenNoCodeDebt() (gas: 170775) +TestLoanManager:testTokenURI() (gas: 64914) +TestLoanManager:testTokenURIInvalidLoan() (gas: 13309) +TestLoanManager:testTransferFromFail() (gas: 80088) +TestNewLoan:testBuyNowPayLater() (gas: 208) +TestNewLoan:testNewLoanERC721CollateralDefaultTerms2():((uint256,address,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256)[],(address,bytes,address,bytes,address,bytes))) (gas: 389501) +TestNewLoan:testNewLoanERC721CollateralLessDebtThanOffered():((uint256,address,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256)[],(address,bytes,address,bytes,address,bytes))) (gas: 2259) +TestNewLoan:testNewLoanRefinanceNew() (gas: 207) +TestNewLoan:testSettleLoan() (gas: 163) +TestRepayLoan:testRepayLoan() (gas: 207) TestStarLiteUtils:testEncodeReceivedWithRecipient() (gas: 17955) TestStarLiteUtils:testSpentToReceived() (gas: 17708) TestStarLiteUtils:testValidateSaltOpt(address,bytes32) (runs: 256, μ: 26479, ~: 26479) diff --git a/src/Custodian.sol b/src/Custodian.sol index 38989ebd..5f2da6e0 100644 --- a/src/Custodian.sol +++ b/src/Custodian.sol @@ -213,11 +213,11 @@ contract Custodian is ERC721, ContractOffererInterface, ConduitHelper { _beforeApprovalsSetHook(fulfiller, maximumSpent, context); _setOfferApprovalsWithSeaport(offer); - (ReceivedItem[] memory paymentConsiderations, ReceivedItem[] memory carryFeeConsideration) = + (SpentItem[] memory paymentConsiderations, SpentItem[] memory carryFeeConsideration) = Pricing(loan.terms.pricing).getPaymentConsideration(loan); - consideration = _mergeConsiderations(paymentConsiderations, carryFeeConsideration, new ReceivedItem[](0)); - consideration = _removeZeroAmounts(consideration); + // consideration = _mergeConsiderations(paymentConsiderations, carryFeeConsideration, new ReceivedItem[](0)); + // consideration = _removeZeroAmounts(consideration); _settleLoan(loan); } else if (action == Actions.Settlement && !SettlementHook(loan.terms.hook).isActive(loan)) { @@ -252,20 +252,12 @@ contract Custodian is ERC721, ContractOffererInterface, ConduitHelper { } /** - * @dev previews the order for this contract offerer. + * @dev If any additional state updates are needed when taking custody of a loan * - * @param consideration The items received from the order completing - * @param orderHashes The hashes of the orders completed - * @param contractNonce The nonce of the contract in seaport - * @param context The abi encoded bytes passed with the order + * @param loan The loan that was just placed into custody * @return selector The function selector of the custody method */ - function custody( - ReceivedItem[] calldata consideration, - bytes32[] calldata orderHashes, - uint256 contractNonce, - bytes calldata context - ) external virtual onlyLoanManager returns (bytes4 selector) { + function custody(LoanManager.Loan memory loan) external virtual onlyLoanManager returns (bytes4 selector) { revert ImplementInChild(); } @@ -317,11 +309,11 @@ contract Custodian is ERC721, ContractOffererInterface, ConduitHelper { } offer = loan.collateral; - (ReceivedItem[] memory paymentConsiderations, ReceivedItem[] memory carryFeeConsideration) = + (SpentItem[] memory paymentConsiderations, SpentItem[] memory carryFeeConsideration) = Pricing(loan.terms.pricing).getPaymentConsideration(loan); - consideration = _mergeConsiderations(paymentConsiderations, carryFeeConsideration, new ReceivedItem[](0)); - consideration = _removeZeroAmounts(consideration); + // consideration = _mergeConsiderations(paymentConsiderations, carryFeeConsideration, new ReceivedItem[](0)); + // consideration = _removeZeroAmounts(consideration); } else if (action == Actions.Settlement && !loanActive) { address authorized; (consideration, authorized) = SettlementHandler(loan.terms.handler).getSettlement(loan); @@ -343,9 +335,10 @@ contract Custodian is ERC721, ContractOffererInterface, ConduitHelper { * revert with NotEnteredViaSeaport() */ function onERC1155Received(address, address, uint256, uint256, bytes calldata) public virtual returns (bytes4) { - try seaport.incrementCounter() { - revert NotEnteredViaSeaport(); - } catch {} + // commenting out because, we are not entering this flow via Seaport after teh new origiantion changes + // try seaport.incrementCounter() { + // revert NotEnteredViaSeaport(); + // } catch {} return this.onERC1155Received.selector; } @@ -358,9 +351,7 @@ contract Custodian is ERC721, ContractOffererInterface, ConduitHelper { */ function _enableAssetWithSeaport(SpentItem memory offer) internal { //approve consideration based on item type - if (offer.itemType == ItemType.NATIVE) { - payable(address(seaport)).call{value: offer.amount}(""); - } else if (offer.itemType == ItemType.ERC721) { + if (offer.itemType == ItemType.ERC721) { ERC721(offer.token).approve(address(seaport), offer.identifier); } else if (offer.itemType == ItemType.ERC1155) { ERC1155(offer.token).setApprovalForAll(address(seaport), true); @@ -388,9 +379,7 @@ contract Custodian is ERC721, ContractOffererInterface, ConduitHelper { function _transferCollateralToHandler(SpentItem memory offer, address handler) internal { //approve consideration based on item type - if (offer.itemType == ItemType.NATIVE) { - payable(address(handler)).call{value: offer.amount}(""); - } else if (offer.itemType == ItemType.ERC721) { + if (offer.itemType == ItemType.ERC721) { ERC721(offer.token).transferFrom(address(this), handler, offer.identifier); } else if (offer.itemType == ItemType.ERC1155) { ERC1155(offer.token).safeTransferFrom(address(this), handler, offer.identifier, offer.amount, ""); @@ -480,6 +469,4 @@ contract Custodian is ERC721, ContractOffererInterface, ConduitHelper { * @param loan The loan being settled */ function _afterSettleLoanHook(LoanManager.Loan memory loan) internal virtual {} - - receive() external payable onlySeaport {} } diff --git a/src/LoanManager.sol b/src/LoanManager.sol index e34d06bc..ab800512 100644 --- a/src/LoanManager.sol +++ b/src/LoanManager.sol @@ -25,7 +25,6 @@ import {ERC20} from "solady/src/tokens/ERC20.sol"; import {ERC1155} from "solady/src/tokens/ERC1155.sol"; import {ItemType, OfferItem, Schema, SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; -import {ContractOffererInterface} from "seaport-types/src/interfaces/ContractOffererInterface.sol"; import {ConsiderationInterface} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; import {Originator} from "starport-core/originators/Originator.sol"; @@ -42,14 +41,13 @@ import {ECDSA} from "solady/src/utils/ECDSA.sol"; import {SignatureCheckerLib} from "solady/src/utils/SignatureCheckerLib.sol"; import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; import {Ownable} from "solady/src/auth/Ownable.sol"; -import {ConduitHelper} from "starport-core/ConduitHelper.sol"; -import "forge-std/console.sol"; +import {SafeTransferLib} from "solady/src/utils/SafeTransferLib.sol"; interface LoanSettledCallback { function onLoanSettled(LoanManager.Loan calldata loan) external; } -contract LoanManager is ConduitHelper, Ownable, ERC721 { +contract LoanManager is Ownable, ERC721 { using FixedPointMathLib for uint256; using {StarPortLib.toReceivedItems} for SpentItem[]; @@ -67,14 +65,16 @@ contract LoanManager is ConduitHelper, Ownable, ERC721 { // Define the EIP712 domain and typehash constants for generating signatures bytes32 public constant EIP_DOMAIN = keccak256("EIP712Domain(string version,uint256 chainId,address verifyingContract)"); + // bytes32 public constant INTENT_ORIGINATION_TYPEHASH = + // keccak256("Origination(bytes32 hash,address enforcer,bytes32 salt,uint256 nonce,uint256 deadline,bytes data)"); bytes32 public constant INTENT_ORIGINATION_TYPEHASH = - keccak256("IntentOrigination(bytes32 hash,bytes32 salt,uint256 nonce)"); + keccak256("Origination(bytes32 hash,bytes32 salt,bytes32 caveatHash"); bytes32 public constant VERSION = keccak256("0"); address public feeTo; uint96 public defaultFeeRake; - mapping(address => mapping(bytes32 => bool)) public usedSalts; - mapping(address => uint256) public borrowerNonce; //needs to be invalidated - + mapping(address => mapping(bytes32 => bool)) public invalidHashes; + mapping(address => mapping(address => bool)) public approvals; + mapping(address => uint256) public caveatNonces; //contract to token //fee rake mapping(address => Fee) public feeOverride; @@ -136,17 +136,23 @@ contract LoanManager is ConduitHelper, Ownable, ERC721 { error InvalidRefinance(); error InvalidCustodian(); error InvalidLoan(); - error InvalidMaximumSpentEmpty(); - error InvalidDebtLength(); - error InvalidDebtType(); - error InvalidOrigination(); - error InvalidNoRefinanceConsideration(); + error InvalidItemAmount(); + error InvalidItemTokenNoCode(); + error InvalidTransferLength(); + // error InvalidNoRefinanceConsideration(); error LoanExists(); error NotLoanCustodian(); error NotPayingFees(); error NotSeaport(); error NotEnteredViaSeaport(); + error NativeAssetsNotSupported(); + error HashAlreadyInvalidated(); + error InvalidItemType(); + error UnauthorizedAdditionalTransferIncluded(); + error InvalidCaveatSigner(); + error MalformedRefinance(); + constructor(ConsiderationInterface seaport_) { address custodian = address(new Custodian(this, seaport_)); seaport = seaport_; @@ -162,25 +168,300 @@ contract LoanManager is ConduitHelper, Ownable, ERC721 { emit SeaportCompatibleContractDeployed(); } + function transferFrom(address from, address to, uint256 tokenId) public payable override { + revert CannotTransferLoans(); + } + + function originate( + ConduitTransfer[] calldata additionalTransfers, + CaveatEnforcer.CaveatWithApproval calldata borrowerCaveat, + CaveatEnforcer.CaveatWithApproval calldata lenderCaveat, + LoanManager.Loan memory loan + ) external payable { + //cache the addresses + address borrower = loan.borrower; + address issuer = loan.issuer; + address feeRecipient = feeTo; + if (msg.sender != loan.borrower) { + _validateAndEnforceCaveats(borrowerCaveat, borrower, additionalTransfers, loan); + } + + if (msg.sender != issuer && !approvals[issuer][msg.sender]) { + _validateAndEnforceCaveats(lenderCaveat, issuer, additionalTransfers, loan); + } + + _transferSpentItems(loan.collateral, borrower, loan.custodian); + _callCustody(loan); + if (feeRecipient == address(0)) { + _transferSpentItems(loan.debt, issuer, borrower); + } else { + (SpentItem[] memory feeItems, SpentItem[] memory sentToBorrower) = _feeRake(loan.debt); + if (feeItems.length > 0) { + _transferSpentItems(feeItems, issuer, feeRecipient); + } + _transferSpentItems(sentToBorrower, issuer, borrower); + } + + if (additionalTransfers.length > 0) { + _validateAdditionalTransfersCalldata(borrower, issuer, msg.sender, additionalTransfers); + _transferConduitTransfers(additionalTransfers); + } + + //sets originator and start time + _issueLoanManager(loan); + } + + function refinance( + address lender, + CaveatEnforcer.CaveatWithApproval calldata lenderCaveat, + LoanManager.Loan memory loan, + bytes calldata pricingData + ) external { + ( + SpentItem[] memory considerationPayment, + SpentItem[] memory carryPayment, + ConduitTransfer[] memory additionalTransfers + ) = Pricing(loan.terms.pricing).isValidRefinance(loan, pricingData, msg.sender); + + _settle(loan); + loan = applyRefinanceConsiderationToLoan(loan, considerationPayment, carryPayment, pricingData); + + _transferSpentItems(considerationPayment, lender, loan.issuer); + _transferSpentItems(carryPayment, lender, loan.originator); + + loan.issuer = lender; + loan.originator = address(0); + loan.start = 0; + + if (msg.sender != loan.issuer && !approvals[loan.issuer][msg.sender]) { + _validateAndEnforceCaveats(lenderCaveat, loan.issuer, additionalTransfers, loan); + } + + if (additionalTransfers.length > 0) { + _validateAdditionalTransfers(loan.borrower, loan.issuer, msg.sender, additionalTransfers); + _transferConduitTransfers(additionalTransfers); + } + + //sets originator and start time + _issueLoanManager(loan); + } + + function applyRefinanceConsiderationToLoan( + LoanManager.Loan memory loan, + SpentItem[] memory considerationPayment, + SpentItem[] memory carryPayment, + bytes memory pricingData + ) public pure returns (LoanManager.Loan memory) { + if ( + considerationPayment.length == 0 + || (carryPayment.length != 0 && considerationPayment.length != carryPayment.length) + || considerationPayment.length != loan.debt.length + ) { + revert MalformedRefinance(); + } + + uint256 i = 0; + if (carryPayment.length > 0) { + for (; i < considerationPayment.length;) { + loan.debt[i].amount = considerationPayment[i].amount + carryPayment[i].amount; + + unchecked { + ++i; + } + } + } else { + for (; i < considerationPayment.length;) { + loan.debt[i].amount = considerationPayment[i].amount; + unchecked { + ++i; + } + } + } + loan.terms.pricingData = pricingData; + return loan; + } + /** - * @dev previews the order for this contract offerer. - * - * @param borrower The address of the borrower - * @param salt The salt of the borrower's obligation - * @param caveatHash The hash of the abi.encoded obligation caveats - * @return The abi encode packed bytes that include the intent typehash with the salt and nonce and caveatHash + * @dev internal method to call the custody selector of the custodian if it does not share + * the same codehash as the default custodian + * @param loan The loan being placed into custody */ - function encodeWithSaltAndBorrowerCounter(address borrower, bytes32 salt, bytes32 caveatHash) + function _callCustody(LoanManager.Loan memory loan) internal { + address custodian = loan.custodian; + // Comparing the retrieved code hash with a known hash + bytes32 codeHash; + assembly { + codeHash := extcodehash(custodian) + } + if ( + codeHash != DEFAULT_CUSTODIAN_CODE_HASH + && Custodian(payable(custodian)).custody(loan) != Custodian.custody.selector + ) { + revert InvalidCustodian(); + } + } + + function _validateAdditionalTransfers( + address borrower, + address lender, + address fulfiller, + ConduitTransfer[] memory additionalTransfers + ) internal pure { + uint256 i = 0; + for (i; i < additionalTransfers.length;) { + if ( + additionalTransfers[i].from != borrower && additionalTransfers[i].from != lender + && additionalTransfers[i].from != fulfiller + ) { + revert UnauthorizedAdditionalTransferIncluded(); + } + unchecked { + ++i; + } + } + } + + function _validateAdditionalTransfersCalldata( + address borrower, + address lender, + address fulfiller, + ConduitTransfer[] calldata additionalTransfers + ) internal pure { + uint256 i = 0; + for (i; i < additionalTransfers.length;) { + if ( + additionalTransfers[i].from != borrower && additionalTransfers[i].from != lender + && additionalTransfers[i].from != fulfiller + ) revert UnauthorizedAdditionalTransferIncluded(); + unchecked { + ++i; + } + } + } + + function _validateAndEnforceCaveats( + CaveatEnforcer.CaveatWithApproval calldata caveatApproval, + address validator, + ConduitTransfer[] memory additionalTransfers, + LoanManager.Loan memory loan + ) internal { + bytes32 hash = hashCaveatWithSaltAndNonce(validator, caveatApproval.salt, caveatApproval.caveat); + invalidHashes.validateSalt(validator, caveatApproval.salt); + + if ( + !SignatureCheckerLib.isValidSignatureNow( + validator, hash, caveatApproval.v, caveatApproval.r, caveatApproval.s + ) + ) { + revert InvalidCaveatSigner(); + } + + for (uint256 i = 0; i < caveatApproval.caveat.length;) { + CaveatEnforcer(caveatApproval.caveat[i].enforcer).validate( + additionalTransfers, loan, caveatApproval.caveat[i].data + ); + unchecked { + ++i; + } + } + } + + 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, + uint256 identifier, + uint256 amount, + address from, + address to + ) internal { + if (token.code.length == 0) { + revert InvalidItemTokenNoCode(); + } + if (amount > 0) { + if (itemType == ItemType.ERC20) { + // erc20 transfer + SafeTransferLib.safeTransferFrom(token, from, to, amount); + } else if (itemType == ItemType.ERC721) { + // erc721 transfer + if (amount > 1) { + revert InvalidItemAmount(); + } + ERC721(token).transferFrom(from, to, identifier); + } else if (itemType == ItemType.ERC1155) { + // erc1155 transfer + ERC1155(token).safeTransferFrom(from, to, identifier, amount, new bytes(0)); + } else { + revert InvalidItemType(); + } + } else { + revert InvalidItemAmount(); + } + } + + function _transferSpentItems(SpentItem[] memory transfers, address from, address to) internal { + if (transfers.length > 0) { + uint256 i = 0; + for (i; i < transfers.length;) { + _transferItem( + transfers[i].itemType, transfers[i].token, transfers[i].identifier, transfers[i].amount, from, to + ); + unchecked { + ++i; + } + } + } else { + revert InvalidTransferLength(); + } + } + + function hashCaveatWithSaltAndNonce(address validator, bytes32 salt, CaveatEnforcer.Caveat[] calldata caveat) public view virtual - returns (bytes memory) + returns (bytes32) { - return abi.encodePacked( - bytes1(0x19), - bytes1(0x01), - _DOMAIN_SEPARATOR, - keccak256(abi.encode(INTENT_ORIGINATION_TYPEHASH, salt, borrowerNonce[borrower], caveatHash)) + return keccak256( + abi.encodePacked( + bytes1(0x19), + bytes1(0x01), + _DOMAIN_SEPARATOR, + keccak256( + abi.encode( + INTENT_ORIGINATION_TYPEHASH, caveatNonces[validator], salt, keccak256(abi.encode(caveat)) + ) + ) + ) ); } @@ -200,16 +481,6 @@ contract LoanManager is ConduitHelper, Ownable, ERC721 { return "SLM"; } - /** - * @dev modifier to check if the caller is seaport - */ - modifier onlySeaport() { - if (msg.sender != address(seaport)) { - revert NotSeaport(); - } - _; - } - /** * @dev helper to check if a loan is active * @param loanId The id of the loan @@ -290,171 +561,6 @@ contract LoanManager is ConduitHelper, Ownable, ERC721 { emit Close(tokenId); } - /** - * @dev internal method to call the custody selector of the custodian if it does not share - * the same codehash as the default custodian - * @param consideration the receivedItems[] - * @param orderHashes the order hashes of the seaport txn - * @param contractNonce the nonce of the current contract offerer - * @param context the abi encoded bytes data of the order - */ - function _callCustody( - ReceivedItem[] calldata consideration, - bytes32[] calldata orderHashes, - uint256 contractNonce, - bytes calldata context - ) internal { - address custodian = StarPortLib.getCustodian(context); - // Comparing the retrieved code hash with a known hash - bytes32 codeHash; - assembly { - codeHash := extcodehash(custodian) - } - if ( - codeHash != DEFAULT_CUSTODIAN_CODE_HASH - && Custodian(payable(custodian)).custody(consideration, orderHashes, contractNonce, context) - != Custodian.custody.selector - ) { - revert InvalidCustodian(); - } - } - - /** - * @dev previews the order for this contract offerer. - * - * @param caller The address of the contract fulfiller. - * @param fulfiller The address of the contract fulfiller. - * @param minimumReceivedFromBorrower The minimum the fulfiller must receive. - * @param maximumSpentFromBorrower The most a fulfiller will spend - * @param context The context of the order. - * @return offer The items spent by the order. - * @return consideration The items received by the order. - */ - function previewOrder( - address caller, - address fulfiller, - SpentItem[] calldata minimumReceivedFromBorrower, - SpentItem[] calldata maximumSpentFromBorrower, - bytes calldata context // encoded based on the schemaID - ) public returns (SpentItem[] memory offer, ReceivedItem[] memory consideration) { - Actions action = StarPortLib.getAction(context); - if (action == Actions.Origination) { - (, LoanManager.Obligation memory obligation) = abi.decode(context, (Actions, LoanManager.Obligation)); - - bool feeOn; - if (obligation.debt.length == 0) { - revert InvalidDebtLength(); - } - if (maximumSpentFromBorrower.length == 0) { - revert InvalidMaximumSpentEmpty(); - } - consideration = maximumSpentFromBorrower.toReceivedItems(obligation.custodian); - if (feeTo != address(0)) { - feeOn = true; - } - address receiver = obligation.borrower; - - // we settle via seaport channels if caveats are present - if (fulfiller != receiver || obligation.caveats.length > 0) { - bytes32 caveatHash = keccak256( - encodeWithSaltAndBorrowerCounter( - obligation.borrower, obligation.salt, keccak256(abi.encode(obligation.caveats)) - ) - ); - SpentItem[] memory debt = obligation.debt; - offer = new SpentItem[](debt.length + 1); - SpentItem[] memory feeItems = !feeOn ? new SpentItem[](0) : _feeRake(debt); - - for (uint256 i; i < debt.length;) { - if ( - debt[i].itemType == ItemType.ERC721_WITH_CRITERIA - || debt[i].itemType == ItemType.ERC1155_WITH_CRITERIA - ) { - revert InvalidDebtType(); - } - offer[i] = SpentItem({ - itemType: debt[i].itemType, - token: debt[i].token, - identifier: debt[i].identifier, - amount: debt[i].amount - }); - if (feeOn && feeItems[i].amount > 0) { - offer[i].amount = debt[i].amount - feeItems[i].amount; - } - unchecked { - ++i; - } - } - - offer[debt.length] = SpentItem({ - itemType: ItemType.ERC721, - token: address(this), - identifier: uint256(caveatHash), - amount: 1 - }); - } else if (feeOn) { - SpentItem[] memory debt = obligation.debt; - offer = new SpentItem[](debt.length); - - SpentItem[] memory feeItems = !feeOn ? new SpentItem[](0) : _feeRake(debt); - - for (uint256 i; i < debt.length;) { - offer[i] = SpentItem({ - itemType: debt[i].itemType, - token: debt[i].token, - identifier: debt[i].identifier, - amount: debt[i].amount - }); - if (feeItems[i].amount > 0) { - offer[i].amount = debt[i].amount - feeItems[i].amount; - } - unchecked { - ++i; - } - } - } - } else if (action == Actions.Refinance) { - (, LoanManager.Loan memory loan, bytes memory newPricingData) = - abi.decode(context, (Actions, LoanManager.Loan, bytes)); - - consideration = _getRefinanceConsiderationsPreview(loan, newPricingData, fulfiller); - // if for malicious or non-malicious the refinanceConsideration is zero - if (consideration.length == 0) { - revert InvalidNoRefinanceConsideration(); - } - } else { - revert InvalidAction(); - } - } - - function _getRefinanceConsiderationsPreview( - LoanManager.Loan memory loan, - bytes memory newPricingData, - address fulfiller - ) internal view returns (ReceivedItem[] memory consideration) { - ( - // used to update the new loan amount - ReceivedItem[] memory considerationPayment, - ReceivedItem[] memory carryPayment, - ReceivedItem[] memory additionalPayment - ) = Pricing(loan.terms.pricing).isValidRefinance(loan, newPricingData, fulfiller); - - consideration = _mergeConsiderations(considerationPayment, carryPayment, additionalPayment); - consideration = _removeZeroAmounts(consideration); - } - - /** - * @dev Gets the metadata for this contract offerer. - * - * @return name The name of the contract offerer. - * @return schemas The schemas supported by the contract offerer. - */ - function getSeaportMetadata() external pure returns (string memory, Schema[] memory schemas) { - schemas = new Schema[](1); - schemas[0] = Schema(8, ""); - return ("Loans", schemas); - } - /** * @dev set's the default fee Data * only owner can call @@ -483,114 +589,54 @@ contract LoanManager is ConduitHelper, Ownable, ERC721 { * @param debt The debt to rake * @return feeItems SpentItem[] of fee's */ - function _feeRake(SpentItem[] memory debt) internal view returns (SpentItem[] memory feeItems) { + function _feeRake(SpentItem[] memory debt) + internal + view + returns (SpentItem[] memory feeItems, SpentItem[] memory paymentToBorrower) + { feeItems = new SpentItem[](debt.length); - uint256 totalDebtItems; + paymentToBorrower = new SpentItem[](debt.length); + uint256 totalFeeItems; for (uint256 i = 0; i < debt.length;) { Fee memory feeOverride = feeOverride[debt[i].token]; feeItems[i].identifier = 0; //fees are native or erc20 if (debt[i].itemType == ItemType.NATIVE || debt[i].itemType == ItemType.ERC20) { - feeItems[i].amount = debt[i].amount.mulDiv( + uint256 amount = debt[i].amount.mulDiv( !feeOverride.enabled ? defaultFeeRake : feeOverride.amount, (debt[i].itemType == ItemType.NATIVE) ? 1e18 : 10 ** ERC20(debt[i].token).decimals() ); - feeItems[i].token = debt[i].token; - feeItems[i].itemType = debt[i].itemType; - ++totalDebtItems; + paymentToBorrower[i] = SpentItem({ + token: debt[i].token, + itemType: debt[i].itemType, + identifier: debt[i].identifier, + amount: debt[i].amount - amount + }); + if (amount > 0) { + feeItems[i].amount = amount; + feeItems[i].token = debt[i].token; + feeItems[i].itemType = debt[i].itemType; + + ++totalFeeItems; + } } unchecked { ++i; } } assembly { - mstore(feeItems, totalDebtItems) - } - } - - /** - * @dev fills and verifies the incoming obligation - * - * @param fulfiller the new value in WAD denomination to override(1e17 = 10%) - * @param maximumSpentFromBorrower the maximum incoming items from the order - * @param context bytes encoded abi of the obligation - */ - function _fillObligationAndVerify( - address fulfiller, - SpentItem[] calldata maximumSpentFromBorrower, - bytes calldata context - ) internal returns (SpentItem[] memory offer, ReceivedItem[] memory consideration) { - bool feesOn = false; - if (feeTo != address(0)) { - feesOn = true; - } - (, LoanManager.Obligation memory obligation) = abi.decode(context, (Actions, LoanManager.Obligation)); - - if (obligation.debt.length == 0) { - revert InvalidDebtLength(); + mstore(feeItems, totalFeeItems) } - if (maximumSpentFromBorrower.length == 0) { - revert InvalidMaximumSpentEmpty(); - } - consideration = maximumSpentFromBorrower.toReceivedItems(obligation.custodian); - - address receiver = obligation.borrower; - bool enforceCaveats = fulfiller != receiver || obligation.caveats.length > 0; - if (enforceCaveats || feesOn) { - receiver = address(this); - } - Originator.Response memory response = Originator(obligation.originator).execute( - Originator.Request({ - custodian: obligation.custodian, - receiver: receiver, - collateral: maximumSpentFromBorrower, - debt: obligation.debt, - details: obligation.details, - approval: obligation.approval - }) - ); - Loan memory loan = Loan({ - start: block.timestamp, - issuer: response.issuer, - custodian: obligation.custodian, - borrower: obligation.borrower, - originator: obligation.originator, - collateral: maximumSpentFromBorrower, - debt: obligation.debt, - terms: response.terms - }); - - // we settle via seaport channels if caveats are present - - if (enforceCaveats) { - bytes32 caveatHash = keccak256( - encodeWithSaltAndBorrowerCounter( - obligation.borrower, obligation.salt, keccak256(abi.encode(obligation.caveats)) - ) - ); - usedSalts.validateSalt(obligation.borrower, obligation.salt); - uint256 i = 0; - for (; i < obligation.caveats.length;) { - if (!CaveatEnforcer(obligation.caveats[i].enforcer).enforceCaveat(obligation.caveats[i].terms, loan)) { - revert InvalidOrigination(); - } - unchecked { - ++i; - } - } - offer = _setOffer(loan.debt, caveatHash, feesOn); - } else if (feesOn) { - offer = _setOffer(loan.debt, bytes32(0), feesOn); - } - _issueLoanManager(loan, response.issuer.code.length > 0); } /** * @dev issues a LM token if needed * only owner can call * @param loan the loan to issue - * @param mint if true, mint the token */ - function _issueLoanManager(Loan memory loan, bool mint) internal { + function _issueLoanManager(Loan memory loan) internal { + loan.start = block.timestamp; + loan.originator = msg.sender; + bytes memory encodedLoan = abi.encode(loan); uint256 loanId = loan.getId(); @@ -598,219 +644,9 @@ contract LoanManager is ConduitHelper, Ownable, ERC721 { revert LoanExists(); } _setExtraData(loanId, uint8(FieldFlags.ACTIVE)); - if (mint) { + if (loan.issuer.code.length > 0) { _safeMint(loan.issuer, loanId, encodedLoan); } emit Open(loanId, loan); } - - /** - * @dev Generates the order for this contract offerer. - * - * @param fulfiller The address of the contract fulfiller. - * @param maximumSpent The maximum amount of items to be spent by the order. - * @param context The context of the order. - * @return offer The items spent by the order. - * @return consideration The items received by the order. - */ - function generateOrder( - address fulfiller, - SpentItem[] calldata minimumReceived, - SpentItem[] calldata maximumSpent, - bytes calldata context // encoded based on the schemaID - ) external onlySeaport returns (SpentItem[] memory offer, ReceivedItem[] memory consideration) { - Actions action = StarPortLib.getAction(context); - if (action == Actions.Origination) { - (offer, consideration) = _fillObligationAndVerify(fulfiller, maximumSpent, context); - } else if (action == Actions.Refinance) { - consideration = _refinance(fulfiller, context); - } else { - revert InvalidAction(); - } - } - - /** - * @dev moves the fee's collected to the feeTo address - * - * @param feeItem The feeItem to payout - */ - function _moveFeesToReceived(SpentItem memory feeItem) internal { - if (feeItem.itemType == ItemType.NATIVE) { - payable(feeTo).call{value: feeItem.amount}(""); - } else if (feeItem.itemType == ItemType.ERC20) { - ERC20(feeItem.token).transfer(feeTo, feeItem.amount); - } - } - - /** - * @dev enables the debt to be spent via seaport - * - * @param debt The item to make available to seaport - */ - function _enableDebtWithSeaport(SpentItem memory debt) internal { - //approve consideration based on item type - if (debt.itemType == ItemType.NATIVE) { - payable(address(seaport)).call{value: debt.amount}(""); - } else if (debt.itemType == ItemType.ERC721) { - ERC721(debt.token).approve(address(seaport), debt.identifier); - } else if (debt.itemType == ItemType.ERC1155) { - ERC1155(debt.token).setApprovalForAll(address(seaport), true); - } else if (debt.itemType == ItemType.ERC20) { - ERC20(debt.token).approve(address(seaport), debt.amount); - } else { - revert InvalidDebtType(); - } - } - - /** - * @dev set's the offer item to be spent via seaport - * - * @param debt The items to make available to seaport - * @param caveatHash the caveat hash if any - * @param feesOn if we're collecting fees - * @return offer an array of SpentItem on offer to seaport - */ - function _setOffer(SpentItem[] memory debt, bytes32 caveatHash, bool feesOn) - internal - returns (SpentItem[] memory offer) - { - uint256 caveatLength = (caveatHash == bytes32(0)) ? 0 : 1; - offer = new SpentItem[](debt.length + caveatLength); - SpentItem[] memory feeItems = !feesOn ? new SpentItem[](0) : _feeRake(debt); - for (uint256 i; i < debt.length;) { - offer[i] = SpentItem({ - itemType: debt[i].itemType, - token: debt[i].token, - identifier: debt[i].identifier, - amount: debt[i].amount - }); - if (feesOn) { - offer[i].amount = debt[i].amount - feeItems[i].amount; - _moveFeesToReceived(feeItems[i]); - } - _enableDebtWithSeaport(offer[i]); - unchecked { - ++i; - } - } - if (caveatHash != bytes32(0)) { - offer[debt.length] = - SpentItem({itemType: ItemType.ERC721, token: address(this), identifier: uint256(caveatHash), amount: 1}); - } - } - - /** - * @dev override the transferFrom so that onlyseaport can call it - * shim it so that it does a false success so seaport can tell us - * to move caveatHash tokens even though none are minted - * this allows caveatHash tokens to be signed into the seaport order and get guarentee's from seaport on execution - * @param from the address to send from (if not the LM, then revert CannotTransferLoans() - * @param to the receiving party - * @param tokenId the tokenId (only caveatHash tokens are supported and aren't actually issued/sent) - */ - function transferFrom(address from, address to, uint256 tokenId) public payable override onlySeaport { - if (address(this) != from) revert CannotTransferLoans(); - } - - /** - * @dev Generates the order for this contract offerer. - * - * @param offer The address of the contract fulfiller. - * @param consideration The maximum amount of items to be spent by the order. - * @param context The context of the order. - * @param orderHashes The context of the order. - * @param contractNonce The context of the order. - * @return ratifyOrderMagicValue The magic value returned by the ratify. - */ - function ratifyOrder( - SpentItem[] calldata offer, - ReceivedItem[] calldata consideration, - bytes calldata context, // encoded based on the schemaID - bytes32[] calldata orderHashes, - uint256 contractNonce - ) external onlySeaport returns (bytes4 ratifyOrderMagicValue) { - Actions action = StarPortLib.getAction(context); - if (action == Actions.Origination) { - _callCustody(consideration, orderHashes, contractNonce, context); - } - ratifyOrderMagicValue = ContractOffererInterface.ratifyOrder.selector; - } - - /** - * @dev Helper to determine if an interface is supported by this contract - * - * @param interfaceId The interface to check - * @return bool return true if the interface is supported - */ - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721) returns (bool) { - return interfaceId == type(ContractOffererInterface).interfaceId || interfaceId == type(ERC721).interfaceId - || super.supportsInterface(interfaceId); - } - - /** - * @dev internal method for conducting a refinance - * - * @param fulfiller The address who is executing the seaport txn - * @param context The abi encoded bytes data passed with the order - * @return consideration The array of ReceivedItem to be received by the fulfiller - */ - function _refinance(address fulfiller, bytes calldata context) - internal - returns (ReceivedItem[] memory consideration) - { - (, LoanManager.Loan memory loan, bytes memory newPricingData) = - abi.decode(context, (Actions, LoanManager.Loan, bytes)); - - ( - ReceivedItem[] memory considerationPayment, - ReceivedItem[] memory carryPayment, - ReceivedItem[] memory additionalPayment - ) = Pricing(loan.terms.pricing).isValidRefinance(loan, newPricingData, fulfiller); - - consideration = _mergeConsiderations(considerationPayment, carryPayment, additionalPayment); - consideration = _removeZeroAmounts(consideration); - // if for malicious or non-malicious the refinanceConsideration is zero - if (consideration.length == 0) { - revert InvalidNoRefinanceConsideration(); - } - - _settle(loan); - - for (uint256 i; i < loan.debt.length;) { - loan.debt[i].amount = considerationPayment[i].amount; - unchecked { - ++i; - } - } - - loan.terms.pricingData = newPricingData; - loan.originator = fulfiller; - loan.issuer = fulfiller; - loan.start = block.timestamp; - _issueLoanManager(loan, fulfiller.code.length > 0); - } - - /** - * @dev receive eth method - * if we are able to increment the counter in seaport that means we have not entered into seaport - * revert with NotEnteredViaSeaport() - */ - receive() external payable { - try seaport.incrementCounter() { - revert NotEnteredViaSeaport(); - } catch {} - } - - /** - * @dev onERC1155Received handler - * if we are able to increment the counter in seaport that means we have not entered into seaport - * we dont add for 721 as they are able to ignore the on handler call as apart of the spec - * revert with NotEnteredViaSeaport() - */ - function onERC1155Received(address, address, uint256, uint256, bytes calldata) external returns (bytes4) { - try seaport.incrementCounter() { - revert NotEnteredViaSeaport(); - } catch {} - return this.onERC1155Received.selector; - } } diff --git a/src/enforcers/BorrowerEnforcer.sol b/src/enforcers/BorrowerEnforcer.sol new file mode 100644 index 00000000..eab66f95 --- /dev/null +++ b/src/enforcers/BorrowerEnforcer.sol @@ -0,0 +1,31 @@ +pragma solidity =0.8.17; + +import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; +import {ConduitTransfer} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {LoanManager} from "starport-core/LoanManager.sol"; + +contract BorrowerEnforcer is CaveatEnforcer { + error BorrowerOnlyEnforcer(); + error InvalidLoanTerms(); + error InvalidAdditionalTransfer(); + + struct Details { + LoanManager.Loan loan; + } + + function validate( + ConduitTransfer[] calldata additionalTransfers, + LoanManager.Loan calldata loan, + bytes calldata caveatData + ) public view virtual override { + bytes32 loanHash = keccak256(abi.encode(loan)); + + Details memory details = abi.decode(caveatData, (Details)); + if (details.loan.borrower != loan.borrower) revert BorrowerOnlyEnforcer(); + details.loan.issuer = loan.issuer; + + if (loanHash != keccak256(abi.encode(details.loan))) revert InvalidLoanTerms(); + + if (additionalTransfers.length > 0) revert InvalidAdditionalTransfer(); + } +} diff --git a/src/enforcers/CaveatEnforcer.sol b/src/enforcers/CaveatEnforcer.sol index 2320b35b..ffce25fc 100644 --- a/src/enforcers/CaveatEnforcer.sol +++ b/src/enforcers/CaveatEnforcer.sol @@ -1,7 +1,26 @@ pragma solidity =0.8.17; +// import {ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {ConduitTransfer} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; import {LoanManager} from "starport-core/LoanManager.sol"; abstract contract CaveatEnforcer { - function enforceCaveat(bytes calldata terms, LoanManager.Loan memory loan) public virtual returns (bool); + struct Caveat { + address enforcer; + uint256 deadline; + bytes data; + } + + struct CaveatWithApproval { + uint8 v; + bytes32 r; + bytes32 s; + bytes32 salt; + Caveat[] caveat; + } + + function validate(ConduitTransfer[] calldata solution, LoanManager.Loan calldata loan, bytes calldata caveatData) + public + view + virtual; } diff --git a/src/enforcers/CollateralEnforcer.sol b/src/enforcers/CollateralEnforcer.sol deleted file mode 100644 index bf770550..00000000 --- a/src/enforcers/CollateralEnforcer.sol +++ /dev/null @@ -1,25 +0,0 @@ -pragma solidity =0.8.17; - -import {CaveatEnforcer} from "./CaveatEnforcer.sol"; -import {LoanManager} from "starport-core/LoanManager.sol"; - -import {ItemType, SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; - -contract CollateralEnforcer is CaveatEnforcer { - struct Details { - SpentItem[] collateral; - bool isAny; // TODO delete? - } - - function enforceCaveat(bytes calldata terms, LoanManager.Loan memory loan) - public - view - override - returns (bool valid) - { - Details memory details = abi.decode(terms, (Details)); - //TODO: figure out or/and comparison simple impl - - return (keccak256(abi.encode(loan.collateral)) == keccak256(abi.encode(details.collateral))); - } -} diff --git a/src/enforcers/LenderEnforcer.sol b/src/enforcers/LenderEnforcer.sol new file mode 100644 index 00000000..c1b79710 --- /dev/null +++ b/src/enforcers/LenderEnforcer.sol @@ -0,0 +1,41 @@ +pragma solidity =0.8.17; + +import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; +import {ConduitTransfer} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {LoanManager} from "starport-core/LoanManager.sol"; + +contract LenderEnforcer is CaveatEnforcer { + error LenderOnlyEnforcer(); + error InvalidLoanTerms(); + error InvalidAdditionalTransfer(); + + struct Details { + LoanManager.Loan loan; + } + + function validate( + ConduitTransfer[] calldata additionalTransfers, + LoanManager.Loan calldata loan, + bytes calldata caveatData + ) public view virtual override { + // bytes32 loanHash = keccak256(abi.encode(loan)); + + Details memory details = abi.decode(caveatData, (Details)); + if (details.loan.issuer != loan.issuer) revert LenderOnlyEnforcer(); + details.loan.borrower = loan.borrower; + + if (keccak256(abi.encode(loan)) != keccak256(abi.encode(details.loan))) { + revert InvalidLoanTerms(); + } + + if (additionalTransfers.length > 0) { + uint256 i = 0; + for (; i < additionalTransfers.length;) { + if (additionalTransfers[i].from == loan.issuer) revert InvalidAdditionalTransfer(); + unchecked { + ++i; + } + } + } + } +} diff --git a/src/enforcers/RateEnforcer.sol b/src/enforcers/RateEnforcer.sol deleted file mode 100644 index 7ddef9ed..00000000 --- a/src/enforcers/RateEnforcer.sol +++ /dev/null @@ -1,23 +0,0 @@ -pragma solidity =0.8.17; - -import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; -import {BasePricing} from "starport-core/pricing/BasePricing.sol"; -import {LoanManager} from "starport-core/LoanManager.sol"; - -contract FixedRateEnforcer is CaveatEnforcer { - struct Details { - uint256 maxRate; - uint256 maxCarryRate; - } - - function enforceCaveat( - bytes calldata caveatTerms, //enforce theis - LoanManager.Loan memory loan - ) public view override returns (bool) { - //lower and upper bounds - Details memory caveatDetails = abi.decode(caveatTerms, (Details)); - - BasePricing.Details memory details = abi.decode(loan.terms.pricingData, (BasePricing.Details)); - return (caveatDetails.maxRate > details.rate && caveatDetails.maxCarryRate > details.carryRate); - } -} diff --git a/src/enforcers/TermEnforcer.sol b/src/enforcers/TermEnforcer.sol deleted file mode 100644 index df65d96b..00000000 --- a/src/enforcers/TermEnforcer.sol +++ /dev/null @@ -1,34 +0,0 @@ -pragma solidity ^0.8.0; - -import {CaveatEnforcer} from "./CaveatEnforcer.sol"; -import {LoanManager} from "starport-core/LoanManager.sol"; -import "forge-std/console.sol"; - -contract TermEnforcer is CaveatEnforcer { - struct Details { - address pricing; - address hook; - address handler; - } - - function enforceCaveat(bytes calldata terms, LoanManager.Loan memory loan) - public - view - override - returns (bool valid) - { - Details memory details = abi.decode(terms, (Details)); - valid = true; - - console.log("enforcing term caveat"); - if (details.pricing != address(0)) { - valid = valid && loan.terms.pricing == details.pricing; - } - if (details.hook != address(0)) { - valid = valid && loan.terms.hook == details.hook; - } - if (details.handler != address(0)) { - valid = valid && loan.terms.handler == details.handler; - } - } -} diff --git a/src/handlers/AstariaV1SettlementHandler.sol b/src/handlers/AstariaV1SettlementHandler.sol index 9c6f4753..196227e5 100644 --- a/src/handlers/AstariaV1SettlementHandler.sol +++ b/src/handlers/AstariaV1SettlementHandler.sol @@ -8,6 +8,7 @@ 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"; contract AstariaV1SettlementHandler is DutchAuctionHandler { using {StarPortLib.getId} for LoanManager.Loan; @@ -51,36 +52,72 @@ contract AstariaV1SettlementHandler is DutchAuctionHandler { roundUp: true }); - (ReceivedItem[] memory paymentConsiderations, ReceivedItem[] memory carryFeeConsideration) = - Pricing(loan.terms.pricing).getPaymentConsideration(loan); - - // the settlementPrice does not cover carryFees - if (paymentConsiderations[0].amount <= settlementPrice) { - carryFeeConsideration = new ReceivedItem[](0); - } - // the settlementPrice covers at least some of the carry fees - else { - carryFeeConsideration[0].amount = - settlementPrice - paymentConsiderations[0].amount - carryFeeConsideration[0].amount; + consideration = new ReceivedItem[](3); + uint256 i = 0; + BasePricing.Details memory pricingDetails = abi.decode(loan.terms.pricingData, (BasePricing.Details)); + uint256 interest = + BasePricing(loan.terms.pricing).getInterest(loan, pricingDetails.rate, loan.start, block.timestamp, 0); + + uint256 carry = interest.mulWad(pricingDetails.carryRate); + + if (loan.debt[0].amount + interest <= settlementPrice) { + consideration[i] = ReceivedItem({ + itemType: loan.debt[0].itemType, + identifier: loan.debt[0].identifier, + amount: carry, + token: loan.debt[0].token, + recipient: payable(loan.originator) + }); + settlementPrice -= consideration[i].amount; + unchecked { + ++i; + } + } else if (loan.debt[0].amount + interest - carry <= settlementPrice) { + consideration[i] = ReceivedItem({ + itemType: loan.debt[0].itemType, + identifier: loan.debt[0].identifier, + amount: (settlementPrice - loan.debt[0].amount + interest - carry), + token: loan.debt[0].token, + recipient: payable(loan.originator) + }); + settlementPrice -= consideration[i].amount; + unchecked { + ++i; + } } - paymentConsiderations[0].amount = settlementPrice; + BaseRecall.Details memory hookDetails = abi.decode(loan.terms.hookData, (BaseRecall.Details)); - uint256 recallerReward = paymentConsiderations[0].amount.mulWad(hookDetails.recallerRewardRatio); - - // recallerReward is taken directly from the repayment, carry is not subject to the recallerReward - paymentConsiderations[0].amount -= recallerReward; - ReceivedItem[] memory recallerPayment = new ReceivedItem[](1); - recallerPayment[0] = ReceivedItem({ - itemType: paymentConsiderations[0].itemType, - identifier: paymentConsiderations[0].identifier, - amount: recallerReward, - token: paymentConsiderations[0].token, - recipient: payable(recaller) + uint256 recallerReward = (settlementPrice).mulWad(hookDetails.recallerRewardRatio); + if (recallerReward > 0) { + consideration[i] = ReceivedItem({ + itemType: loan.debt[0].itemType, + identifier: loan.debt[0].identifier, + amount: settlementPrice.mulWad(hookDetails.recallerRewardRatio), + token: loan.debt[0].token, + recipient: payable(recaller) + }); + settlementPrice -= consideration[i].amount; + unchecked { + ++i; + } + } + + consideration[i] = ReceivedItem({ + itemType: loan.debt[0].itemType, + identifier: loan.debt[0].identifier, + amount: settlementPrice, + token: loan.debt[0].token, + recipient: payable(loan.issuer) }); - consideration = _mergeConsiderations(paymentConsiderations, carryFeeConsideration, recallerPayment); - consideration = _removeZeroAmounts(consideration); + unchecked { + ++i; + } + + assembly { + mstore(consideration, i) + } } function execute(LoanManager.Loan calldata loan, address fulfiller) external virtual override returns (bytes4) { diff --git a/src/handlers/DutchAuctionHandler.sol b/src/handlers/DutchAuctionHandler.sol index 64d1f2ea..2504980c 100644 --- a/src/handlers/DutchAuctionHandler.sol +++ b/src/handlers/DutchAuctionHandler.sol @@ -15,6 +15,7 @@ import {LoanManager, SettlementHandler} from "starport-core/handlers/SettlementH 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 { constructor(LoanManager LM_) SettlementHandler(LM_) { @@ -45,16 +46,15 @@ abstract contract DutchAuctionHandler is SettlementHandler, AmountDeriver, Condu returns (ReceivedItem[] memory consideration, address restricted) { Details memory details = abi.decode(loan.terms.handlerData, (Details)); - uint256 settlementPrice; uint256 start = _getAuctionStart(loan); - // DutchAuction has failed + // DutchAuction has failed, allow lender to redeem if (start + details.window < block.timestamp) { return (new ReceivedItem[](0), loan.issuer); } - settlementPrice = _locateCurrentAmount({ + uint256 settlementPrice = _locateCurrentAmount({ startAmount: details.startingPrice, endAmount: details.endingPrice, startTime: start, @@ -62,19 +62,44 @@ abstract contract DutchAuctionHandler is SettlementHandler, AmountDeriver, Condu roundUp: true }); - (ReceivedItem[] memory paymentConsiderations, ReceivedItem[] memory carryFeeConsideration) = - Pricing(loan.terms.pricing).getPaymentConsideration(loan); - - if (paymentConsiderations[0].amount <= settlementPrice) { - carryFeeConsideration = new ReceivedItem[](0); + BasePricing.Details memory pricingDetails = abi.decode(loan.terms.pricingData, (BasePricing.Details)); + uint256 interest = + BasePricing(loan.terms.pricing).getInterest(loan, pricingDetails.rate, loan.start, block.timestamp, 0); + + uint256 carry = interest.mulWad(pricingDetails.carryRate); + + if (loan.debt[0].amount + interest <= settlementPrice) { + consideration = new ReceivedItem[](2); + consideration[0] = ReceivedItem({ + itemType: loan.debt[0].itemType, + identifier: loan.debt[0].identifier, + amount: carry, + token: loan.debt[0].token, + recipient: payable(loan.originator) + }); + + settlementPrice -= consideration[0].amount; + } else if (loan.debt[0].amount + interest - carry <= settlementPrice) { + consideration = new ReceivedItem[](2); + consideration[0] = ReceivedItem({ + itemType: loan.debt[0].itemType, + identifier: loan.debt[0].identifier, + amount: (settlementPrice - loan.debt[0].amount + interest - carry), + token: loan.debt[0].token, + recipient: payable(loan.originator) + }); + settlementPrice -= consideration[0].amount; } else { - carryFeeConsideration[0].amount = - settlementPrice - paymentConsiderations[0].amount - carryFeeConsideration[0].amount; + consideration = new ReceivedItem[](1); } - paymentConsiderations[0].amount = settlementPrice; - consideration = _mergeConsiderations(paymentConsiderations, carryFeeConsideration, new ReceivedItem[](0)); - consideration = _removeZeroAmounts(consideration); + consideration[consideration.length - 1] = ReceivedItem({ + itemType: loan.debt[0].itemType, + identifier: loan.debt[0].identifier, + amount: settlementPrice, + token: loan.debt[0].token, + recipient: payable(loan.issuer) + }); } function validate(LoanManager.Loan calldata loan) external view virtual override returns (bool) { diff --git a/src/handlers/EnglishAuctionHandler.sol b/src/handlers/EnglishAuctionHandler.sol index dcf97996..f76b165c 100644 --- a/src/handlers/EnglishAuctionHandler.sol +++ b/src/handlers/EnglishAuctionHandler.sol @@ -56,6 +56,9 @@ contract EnglishAuctionHandler is SettlementHandler { } function execute(LoanManager.Loan calldata loan, address fulfiller) external virtual override returns (bytes4) { + if (fulfiller != address(this)) { + revert("must liquidate via the handler to trigger english auction"); + } return SettlementHandler.execute.selector; } diff --git a/src/hooks/BaseRecall.sol b/src/hooks/BaseRecall.sol index 6b792843..a28f8e76 100644 --- a/src/hooks/BaseRecall.sol +++ b/src/hooks/BaseRecall.sol @@ -36,6 +36,7 @@ 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 {ConduitInterface} from "seaport-types/src/interfaces/ConduitInterface.sol"; @@ -59,6 +60,7 @@ abstract contract BaseRecall is ConduitHelper { error RecallBeforeHoneymoonExpiry(); error LoanHasNotBeenRefinanced(); error WithdrawDoesNotExist(); + error InvalidItemType(); ConsiderationInterface public constant seaport = ConsiderationInterface(0x2e234DAe75C793f67A35089C9d99245E1C58470b); mapping(uint256 => Recall) public recalls; @@ -99,20 +101,20 @@ abstract contract BaseRecall is ConduitHelper { revert RecallBeforeHoneymoonExpiry(); } - // get conduitController - (,, address conduitController) = seaport.information(); - // validate that the provided conduit is owned by the msg.sender - if (ConduitControllerInterface(conduitController).ownerOf(conduit) != msg.sender) { - revert InvalidConduit(); - } - ReceivedItem[] memory recallConsideration = - _generateRecallConsideration(loan, 0, details.recallStakeDuration, 1e18, payable(address(this))); - if ( - ConduitInterface(conduit).execute(_packageTransfers(recallConsideration, msg.sender)) - != ConduitInterface.execute.selector - ) { - revert ConduitTransferError(); + if (loan.issuer != msg.sender && loan.borrower != msg.sender) { + (,, address conduitController) = 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( + loan, 0, details.recallStakeDuration, 1e18, msg.sender, payable(address(this)) + ); + if (ConduitInterface(conduit).execute(recallConsideration) != ConduitInterface.execute.selector) { + revert ConduitTransferError(); + } } + // get conduitController bytes memory encodedLoan = abi.encode(loan); @@ -139,18 +141,20 @@ abstract contract BaseRecall is ConduitHelper { revert WithdrawDoesNotExist(); } - ReceivedItem[] memory recallConsideration = - _generateRecallConsideration(loan, 0, details.recallStakeDuration, 1e18, receiver); - recall.recaller = payable(address(0)); - recall.start = 0; + if (loan.issuer != recall.recaller && loan.borrower != recall.recaller) { + ConduitTransfer[] memory recallConsideration = + _generateRecallConsideration(loan, 0, details.recallStakeDuration, 1e18, address(this), receiver); + recall.recaller = payable(address(0)); + recall.start = 0; - for (uint256 i; i < recallConsideration.length;) { - if (loan.debt[i].itemType != ItemType.ERC20) revert InvalidStakeType(); + for (uint256 i; i < recallConsideration.length;) { + if (loan.debt[i].itemType != ItemType.ERC20) revert InvalidStakeType(); - ERC20(loan.debt[i].token).transfer(receiver, recallConsideration[i].amount); + ERC20(loan.debt[i].token).transfer(receiver, recallConsideration[i].amount); - unchecked { - ++i; + unchecked { + ++i; + } } } @@ -165,7 +169,7 @@ abstract contract BaseRecall is ConduitHelper { BasePricing.Details memory details = abi.decode(loan.terms.pricingData, (BasePricing.Details)); recallStake = new uint256[](loan.debt.length); for (uint256 i; i < loan.debt.length;) { - recallStake[i] = BasePricing(loan.terms.pricing).getInterest(loan, details, start, end, i); + recallStake[i] = BasePricing(loan.terms.pricing).getInterest(loan, details.rate, start, end, i); unchecked { ++i; @@ -173,13 +177,14 @@ abstract contract BaseRecall is ConduitHelper { } } - function generateRecallConsideration(LoanManager.Loan memory loan, uint256 proportion, address payable receiver) - external - view - returns (ReceivedItem[] memory consideration) - { + function generateRecallConsideration( + LoanManager.Loan memory loan, + uint256 proportion, + address from, + address payable to + ) external view returns (ConduitTransfer[] memory consideration) { Details memory details = abi.decode(loan.terms.hookData, (Details)); - return _generateRecallConsideration(loan, 0, details.recallStakeDuration, 1e18, receiver); + return _generateRecallConsideration(loan, 0, details.recallStakeDuration, 1e18, from, to); } function _generateRecallConsideration( @@ -187,22 +192,36 @@ abstract contract BaseRecall is ConduitHelper { uint256 start, uint256 end, uint256 proportion, - address payable receiver - ) internal view returns (ReceivedItem[] memory consideration) { + address from, + address payable to + ) internal view returns (ConduitTransfer[] memory additionalTransfers) { uint256[] memory stake = _getRecallStake(loan, start, end); - consideration = new ReceivedItem[](stake.length); + additionalTransfers = new ConduitTransfer[](stake.length); - for (uint256 i; i < consideration.length;) { - consideration[i] = ReceivedItem({ - itemType: loan.debt[i].itemType, + for (uint256 i; i < additionalTransfers.length;) { + additionalTransfers[i] = ConduitTransfer({ + itemType: _convertItemTypeToConduitItemType(loan.debt[i].itemType), identifier: loan.debt[i].identifier, amount: stake[i].mulWad(proportion), token: loan.debt[i].token, - recipient: receiver + from: from, + to: to }); unchecked { ++i; } } } + + 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/pricing/AstariaV1Pricing.sol b/src/pricing/AstariaV1Pricing.sol index b1c3fafe..ea220a53 100644 --- a/src/pricing/AstariaV1Pricing.sol +++ b/src/pricing/AstariaV1Pricing.sol @@ -5,11 +5,13 @@ import {CompoundInterestPricing} from "starport-core/pricing/CompoundInterestPri import {Pricing} from "starport-core/pricing/Pricing.sol"; import {BasePricing} from "starport-core/pricing/BasePricing.sol"; import {ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {AstariaV1SettlementHook} from "starport-core/hooks/AstariaV1SettlementHook.sol"; 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"; contract AstariaV1Pricing is CompoundInterestPricing { using FixedPointMathLib for uint256; @@ -25,9 +27,9 @@ contract AstariaV1Pricing is CompoundInterestPricing { virtual override returns ( - ReceivedItem[] memory repayConsideration, - ReceivedItem[] memory carryConsideration, - ReceivedItem[] memory recallConsideration + SpentItem[] memory repayConsideration, + SpentItem[] memory carryConsideration, + ConduitTransfer[] memory recallConsideration ) { // borrowers can refinance a loan at any time @@ -60,7 +62,7 @@ contract AstariaV1Pricing is CompoundInterestPricing { // split is proportional to the difference in rate proportion = 1e18 - (oldDetails.rate - newDetails.rate).divWad(oldDetails.rate); } - recallConsideration = hook.generateRecallConsideration(loan, proportion, receiver); + recallConsideration = hook.generateRecallConsideration(loan, proportion, caller, receiver); } (repayConsideration, carryConsideration) = getPaymentConsideration(loan); diff --git a/src/pricing/BasePricing.sol b/src/pricing/BasePricing.sol index 4d57af89..7b37409f 100644 --- a/src/pricing/BasePricing.sol +++ b/src/pricing/BasePricing.sol @@ -29,6 +29,7 @@ import "forge-std/console2.sol"; import {BaseHook} from "starport-core/hooks/BaseHook.sol"; import {StarPortLib} from "starport-core/lib/StarPortLib.sol"; +import {SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; abstract contract BasePricing is Pricing { using FixedPointMathLib for uint256; @@ -44,54 +45,51 @@ abstract contract BasePricing is Pricing { view virtual override - returns (ReceivedItem[] memory repayConsideration, ReceivedItem[] memory carryConsideration) + returns (SpentItem[] memory repayConsideration, SpentItem[] memory carryConsideration) { - repayConsideration = _generateRepayConsideration(loan); - carryConsideration = _generateRepayCarryConsideration(loan); - } - - function getOwed(LoanManager.Loan memory loan) public view returns (uint256[] memory) { Details memory details = abi.decode(loan.terms.pricingData, (Details)); - return _getOwed(loan, details, loan.start, block.timestamp); - } + if (details.carryRate > 0) carryConsideration = new SpentItem[](loan.debt.length); + else carryConsideration = new SpentItem[](0); + repayConsideration = new SpentItem[](loan.debt.length); - function _getOwedCarry(LoanManager.Loan memory loan, Details memory details, uint256 timestamp) - internal - view - returns (uint256[] memory carryOwed) - { - carryOwed = new uint256[](loan.debt.length); uint256 i = 0; - for (; i < loan.debt.length;) { - uint256 carry = getInterest(loan, details, loan.start, timestamp, i).mulWad(details.carryRate); - carryOwed[i] = carry; + uint256 interest = getInterest(loan, details.rate, loan.start, block.timestamp, i); + + if (details.carryRate > 0) { + carryConsideration[i] = SpentItem({ + itemType: loan.debt[i].itemType, + identifier: loan.debt[i].identifier, + amount: interest.mulWad(details.carryRate), + token: loan.debt[i].token + }); + repayConsideration[i] = SpentItem({ + itemType: loan.debt[i].itemType, + identifier: loan.debt[i].identifier, + amount: loan.debt[i].amount + interest - carryConsideration[i].amount, + token: loan.debt[i].token + }); + } else { + repayConsideration[i] = SpentItem({ + itemType: loan.debt[i].itemType, + identifier: loan.debt[i].identifier, + amount: loan.debt[i].amount + interest, + token: loan.debt[i].token + }); + } unchecked { ++i; } } } - function _getOwed(LoanManager.Loan memory loan, Details memory details, uint256 start, uint256 end) - internal - view - returns (uint256[] memory updatedDebt) + function getInterest(LoanManager.Loan memory loan, uint256 rate, uint256 start, uint256 end, uint256 index) + public + pure + returns (uint256) { - updatedDebt = new uint256[](loan.debt.length); - for (uint256 i = 0; i < loan.debt.length; i++) { - updatedDebt[i] = loan.debt[i].amount + getInterest(loan, details, start, end, i); - } - } - - function getInterest( - LoanManager.Loan memory loan, - Details memory details, - uint256 start, - uint256 end, - uint256 index - ) public view returns (uint256) { uint256 delta_t = end - start; - return calculateInterest(delta_t, details.rate, loan.debt[index].amount); + return calculateInterest(delta_t, rate, loan.debt[index].amount); } function calculateInterest( @@ -99,54 +97,4 @@ abstract contract BasePricing is Pricing { uint256 amount, uint256 rate // expressed as SPR seconds per rate ) public pure virtual returns (uint256); - - function _generateRepayConsideration(LoanManager.Loan memory loan) - internal - view - returns (ReceivedItem[] memory consideration) - { - Details memory details = abi.decode(loan.terms.pricingData, (Details)); - - consideration = new ReceivedItem[](loan.debt.length); - uint256[] memory owing = _getOwed(loan, details, loan.start, block.timestamp); - - uint256 i = 0; - for (; i < consideration.length;) { - consideration[i] = ReceivedItem({ - itemType: loan.debt[i].itemType, - identifier: loan.debt[i].identifier, - amount: owing[i], - token: loan.debt[i].token, - recipient: payable(loan.issuer) - }); - unchecked { - ++i; - } - } - } - - function _generateRepayCarryConsideration(LoanManager.Loan memory loan) - internal - view - returns (ReceivedItem[] memory consideration) - { - Details memory details = abi.decode(loan.terms.pricingData, (Details)); - - if (details.carryRate == 0) return new ReceivedItem[](0); - uint256[] memory owing = _getOwedCarry(loan, details, block.timestamp); - consideration = new ReceivedItem[](owing.length); - uint256 i = 0; - for (; i < consideration.length;) { - consideration[i] = ReceivedItem({ - itemType: loan.debt[i].itemType, - identifier: loan.debt[i].identifier, - amount: owing[i], - token: loan.debt[i].token, - recipient: payable(loan.originator) - }); - unchecked { - ++i; - } - } - } } diff --git a/src/pricing/BaseRecallPricing.sol b/src/pricing/BaseRecallPricing.sol index 096c9bb2..67bed392 100644 --- a/src/pricing/BaseRecallPricing.sol +++ b/src/pricing/BaseRecallPricing.sol @@ -23,12 +23,14 @@ pragma solidity =0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; import {BasePricing} from "starport-core/pricing/BasePricing.sol"; import {ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {SettlementHook} from "starport-core/hooks/SettlementHook.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; 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"; abstract contract BaseRecallPricing is BasePricing { function isValidRefinance(LoanManager.Loan memory loan, bytes memory newPricingData, address caller) @@ -37,9 +39,9 @@ abstract contract BaseRecallPricing is BasePricing { virtual override returns ( - ReceivedItem[] memory repayConsideration, - ReceivedItem[] memory carryConsideration, - ReceivedItem[] memory recallConsideration + SpentItem[] memory repayConsideration, + SpentItem[] memory carryConsideration, + ConduitTransfer[] memory recallConsideration ) { Details memory oldDetails = abi.decode(loan.terms.pricingData, (Details)); @@ -49,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 ReceivedItem[](0); + recallConsideration = new ConduitTransfer[](0); } } } diff --git a/src/pricing/Pricing.sol b/src/pricing/Pricing.sol index 89755e28..4e986313 100644 --- a/src/pricing/Pricing.sol +++ b/src/pricing/Pricing.sol @@ -21,7 +21,8 @@ pragma solidity =0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; -import {ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {ConduitTransfer} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; abstract contract Pricing { LoanManager LM; @@ -36,11 +37,11 @@ abstract contract Pricing { public view virtual - returns (ReceivedItem[] memory, ReceivedItem[] memory); + returns (SpentItem[] memory, SpentItem[] memory); - function isValidRefinance(LoanManager.Loan memory loan, bytes memory newPricingData, address caller) + function isValidRefinance(LoanManager.Loan memory loan, bytes calldata newPricingData, address caller) external view virtual - returns (ReceivedItem[] memory, ReceivedItem[] memory, ReceivedItem[] memory); + returns (SpentItem[] memory, SpentItem[] memory, ConduitTransfer[] memory); } diff --git a/src/pricing/SimpleInterestPricing.sol b/src/pricing/SimpleInterestPricing.sol index b9d03b99..3b4f59eb 100644 --- a/src/pricing/SimpleInterestPricing.sol +++ b/src/pricing/SimpleInterestPricing.sol @@ -24,6 +24,8 @@ 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 {SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; contract SimpleInterestPricing is BasePricing { using FixedPointMathLib for uint256; @@ -44,9 +46,9 @@ contract SimpleInterestPricing is BasePricing { virtual override returns ( - ReceivedItem[] memory repayConsideration, - ReceivedItem[] memory carryConsideration, - ReceivedItem[] memory additionalConsideration + SpentItem[] memory repayConsideration, + SpentItem[] memory carryConsideration, + ConduitTransfer[] memory additionalConsideration ) { Details memory oldDetails = abi.decode(loan.terms.pricingData, (Details)); @@ -55,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 ReceivedItem[](0); + additionalConsideration = new ConduitTransfer[](0); } else { revert InvalidRefinance(); } diff --git a/test/AstariaV1Test.sol b/test/AstariaV1Test.sol index 75bd751a..5bebc7e5 100644 --- a/test/AstariaV1Test.sol +++ b/test/AstariaV1Test.sol @@ -12,7 +12,10 @@ import {AstariaV1SettlementHook} from "starport-core/hooks/AstariaV1SettlementHo import {BaseRecall} from "starport-core/hooks/BaseRecall.sol"; import {AstariaV1SettlementHandler} from "starport-core/handlers/AstariaV1SettlementHandler.sol"; +import {LenderEnforcer} from "starport-core/enforcers/LenderEnforcer.sol"; +import {BorrowerEnforcer} from "starport-core/enforcers/BorrowerEnforcer.sol"; // import "forge-std/console2.sol"; +import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; contract AstariaV1Test is StarPortTest { Account recaller; @@ -35,7 +38,7 @@ contract AstariaV1Test is StarPortTest { vm.startPrank(recaller.addr); recallerConduit = conduitController.createConduit(conduitKeyRecaller, recaller.addr); conduitController.updateChannel(recallerConduit, address(hook), true); - erc20s[0].approve(address(recallerConduit), 100000); + erc20s[0].approve(address(recallerConduit), 1e18); vm.stopPrank(); // // 1% interest rate per second @@ -57,4 +60,20 @@ contract AstariaV1Test is StarPortTest { }) ); } + + function getRefinanceDetails(LoanManager.Loan memory loan, bytes memory pricingData, address transactor) + public + view + returns (LenderEnforcer.Details memory) + { + (SpentItem[] memory considerationPayment, SpentItem[] memory carryPayment,) = + Pricing(loan.terms.pricing).isValidRefinance(loan, pricingData, transactor); + + loan = LM.applyRefinanceConsiderationToLoan(loan, considerationPayment, carryPayment, pricingData); + loan.issuer = transactor; + loan.start = 0; + loan.originator = address(0); + + return LenderEnforcer.Details({loan: loan}); + } } diff --git a/test/StarPortTest.sol b/test/StarPortTest.sol index 0abc3c99..b969415f 100644 --- a/test/StarPortTest.sol +++ b/test/StarPortTest.sol @@ -55,17 +55,21 @@ import "seaport/lib/seaport-sol/src/lib/AdvancedOrderLib.sol"; import {SettlementHook} from "starport-core/hooks/SettlementHook.sol"; import {SettlementHandler} from "starport-core/handlers/SettlementHandler.sol"; import {Pricing} from "starport-core/pricing/Pricing.sol"; -import {TermEnforcer} from "starport-core/enforcers/TermEnforcer.sol"; -import {FixedRateEnforcer} from "starport-core/enforcers/RateEnforcer.sol"; -import {CollateralEnforcer} from "starport-core/enforcers/CollateralEnforcer.sol"; import {Cast} from "starport-test/utils/Cast.sol"; import {ERC20} from "solady/src/tokens/ERC20.sol"; import {ERC721} from "solady/src/tokens/ERC721.sol"; +import {ERC1155} from "solady/src/tokens/ERC1155.sol"; import {ContractOffererInterface} from "seaport-types/src/interfaces/ContractOffererInterface.sol"; import {TokenReceiverInterface} from "starport-core/interfaces/TokenReceiverInterface.sol"; import {LoanSettledCallback} from "starport-core/LoanManager.sol"; import {Actions} from "starport-core/lib/StarPortLib.sol"; +import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; +import {BorrowerEnforcer} from "starport-core/enforcers/BorrowerEnforcer.sol"; +import {LenderEnforcer} from "starport-core/enforcers/LenderEnforcer.sol"; + +import {LenderEnforcer} from "starport-core/enforcers/LenderEnforcer.sol"; + interface IWETH9 { function deposit() external payable; @@ -140,6 +144,7 @@ contract StarPortTest is BaseOrderTest { Account seller; Account strategist; Account refinancer; + Account fulfiller; bytes32 conduitKey; address lenderConduit; @@ -149,6 +154,9 @@ contract StarPortTest is BaseOrderTest { Custodian custodian; StrategistOriginator SO; + BorrowerEnforcer borrowerEnforcer; + LenderEnforcer lenderEnforcer; + bytes32 conduitKeyRefinancer; function _deployAndConfigureConsideration() public { @@ -186,6 +194,7 @@ contract StarPortTest is BaseOrderTest { strategist = makeAndAllocateAccount("strategist"); seller = makeAndAllocateAccount("seller"); refinancer = makeAndAllocateAccount("refinancer"); + fulfiller = makeAndAllocateAccount("fulfiller"); LM = new LoanManager(consideration); custodian = Custodian(payable(LM.defaultCustodian())); @@ -211,6 +220,11 @@ contract StarPortTest is BaseOrderTest { erc1155s[1].mint(lender.addr, 2, 10); erc721s[2].mint(lender.addr, 1); } + borrowerEnforcer = new BorrowerEnforcer(); + lenderEnforcer = new LenderEnforcer(); + vm.label(address(borrowerEnforcer), "BorrowerEnforcer"); + vm.label(address(lenderEnforcer), "LenderEnforcer"); + conduitKeyOne = bytes32(uint256(uint160(address(lender.addr))) << 96); conduitKeyRefinancer = bytes32(uint256(uint160(address(refinancer.addr))) << 96); @@ -257,9 +271,9 @@ contract StarPortTest is BaseOrderTest { return this.onERC721Received.selector; } - ConsiderationItem[] selectedCollateral; - ConsiderationItem[] collateral20; - SpentItem[] debt; + // ConsiderationItem[] selectedCollateral; + // ConsiderationItem[] collateral20; + SpentItem[] activeDebt; struct NewLoanData { address custodian; @@ -267,108 +281,239 @@ contract StarPortTest is BaseOrderTest { bytes details; } - function newLoan( - NewLoanData memory loanData, - StrategistOriginator originator, - ConsiderationItem[] storage collateral - ) internal returns (LoanManager.Loan memory) { - return newLoan(loanData, originator, collateral, ""); + function _setApprovalsForSpentItems(address approver, SpentItem[] memory items) internal { + vm.startPrank(approver); + uint256 i = 0; + for (; i < items.length;) { + if (items[i].itemType == ItemType.ERC20) { + ERC20(items[i].token).approve(address(LM), items[i].amount); + } else if (items[i].itemType == ItemType.ERC721) { + ERC721(items[i].token).setApprovalForAll(address(LM), true); + } else if (items[i].itemType == ItemType.ERC1155) { + ERC1155(items[i].token).setApprovalForAll(address(LM), true); + } + + unchecked { + ++i; + } + } + vm.stopPrank(); } - function newLoan( - NewLoanData memory loanData, - StrategistOriginator originator, - ConsiderationItem[] storage collateral, - bytes memory revertMessage + // loan.borrower and signer.addr could be mismatched + function _generateSignedCaveatBorrower(LoanManager.Loan memory loan, Account memory signer, bytes32 salt) + public + view + returns (CaveatEnforcer.CaveatWithApproval memory caveatWithApproval) + { + loan = loanCopy(loan); + loan.issuer = address(0); + + return _generateSignedCaveat(loan, signer, address(borrowerEnforcer), salt); + } + + // loan.issuer and signer.addr could be mismatched + function _generateSignedCaveatLender(LoanManager.Loan memory loan, Account memory signer, bytes32 salt) + public + view + returns (CaveatEnforcer.CaveatWithApproval memory caveatWithApproval) + { + loan = loanCopy(loan); + loan.borrower = address(0); + + return _generateSignedCaveat(loan, signer, address(lenderEnforcer), salt); + } + + function loanCopy(LoanManager.Loan memory loan) public pure returns (LoanManager.Loan memory) { + bytes memory copyBytes = abi.encode(loan); + + return abi.decode(copyBytes, (LoanManager.Loan)); + } + + function _generateSignedCaveat(LoanManager.Loan memory loan, Account memory signer, address enforcer, bytes32 salt) + public + view + returns (CaveatEnforcer.CaveatWithApproval memory caveatWithApproval) + { + LenderEnforcer.Details memory details = LenderEnforcer.Details({loan: loan}); + return signCaveatForAccount( + CaveatEnforcer.Caveat({enforcer: enforcer, deadline: block.timestamp + 1 days, data: abi.encode(details)}), + salt, + signer + ); + } + + function signCaveatForAccount(CaveatEnforcer.Caveat memory caveat, bytes32 salt, Account memory signer) + public + view + returns (CaveatEnforcer.CaveatWithApproval memory caveatWithApproval) + { + caveatWithApproval = CaveatEnforcer.CaveatWithApproval({ + v: 0, + r: bytes32(0), + s: bytes32(0), + salt: salt, + caveat: new CaveatEnforcer.Caveat[](1) + }); + + caveatWithApproval.caveat[0] = caveat; + bytes32 hash = LM.hashCaveatWithSaltAndNonce(signer.addr, salt, caveatWithApproval.caveat); + (caveatWithApproval.v, caveatWithApproval.r, caveatWithApproval.s) = vm.sign(signer.key, hash); + } + + function newLoanOriginationSetup( + LoanManager.Loan memory loan, + Account memory borrowerSigner, + bytes32 borrowerSalt, + Account memory lenderSigner, + bytes32 lenderSalt + ) + public + returns ( + CaveatEnforcer.CaveatWithApproval memory borrowerCaveat, + CaveatEnforcer.CaveatWithApproval memory lenderCaveat + ) + { + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + + borrowerCaveat = _generateSignedCaveatBorrower(loan, borrowerSigner, borrowerSalt); + lenderCaveat = _generateSignedCaveatLender(loan, lenderSigner, lenderSalt); + } + + function newLoanWithProvidedSigners( + LoanManager.Loan memory loan, + bytes32 borrowerSalt, + Account memory borrowerSigner, + bytes32 lenderSalt, + Account memory lenderSigner, + address fulfiller ) internal returns (LoanManager.Loan memory) { - { - bytes32 detailsHash = keccak256(originator.encodeWithAccountCounter(keccak256(loanData.details))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(strategist.key, detailsHash); - return _executeNLR( - LoanManager.Obligation({ - custodian: address(loanData.custodian), - borrower: borrower.addr, - debt: debt, - salt: bytes32(0), - details: loanData.details, - approval: abi.encodePacked(r, s, v), - caveats: loanData.caveats, - originator: address(originator) - }), - collateral // for building contract offer - ); - } + (CaveatEnforcer.CaveatWithApproval memory borrowerCaveat, CaveatEnforcer.CaveatWithApproval memory lenderCaveat) + = newLoanOriginationSetup(loan, borrowerSigner, borrowerSalt, lenderSigner, lenderSalt); + return newLoan(loan, borrowerCaveat, lenderCaveat, fulfiller); } - function refinanceLoan(LoanManager.Loan memory loan, bytes memory newPricingData, address asWho) + function newLoan(LoanManager.Loan memory loan, bytes32 borrowerSalt, bytes32 lenderSalt, address fulfiller) internal - returns (LoanManager.Loan memory newLoan) + returns (LoanManager.Loan memory) { - return refinanceLoan(loan, newPricingData, asWho, ""); + (CaveatEnforcer.CaveatWithApproval memory borrowerCaveat, CaveatEnforcer.CaveatWithApproval memory lenderCaveat) + = newLoanOriginationSetup(loan, borrower, borrowerSalt, lender, lenderSalt); + return newLoan(loan, borrowerCaveat, lenderCaveat, fulfiller); } + function newLoan( + LoanManager.Loan memory loan, + CaveatEnforcer.CaveatWithApproval memory borrowerCaveat, + CaveatEnforcer.CaveatWithApproval memory lenderCaveat, + address fulfiller + ) internal returns (LoanManager.Loan memory originatedLoan) { + vm.recordLogs(); + vm.startPrank(fulfiller); + LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + vm.stopPrank(); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + + bytes32 lienOpenTopic = bytes32(0x57cb72d73c48fadf55428537f6c9efbe080ae111339b0c5af42d9027ed20ba17); + for (uint256 i = 0; i < logs.length; i++) { + if (logs[i].topics[0] == lienOpenTopic) { + (, originatedLoan) = abi.decode(logs[i].data, (uint256, LoanManager.Loan)); + break; + } + } + } + + function getLenderSignedCaveat( + LenderEnforcer.Details memory details, + Account memory signer, + bytes32 salt, + address enforcer + ) public view returns (CaveatEnforcer.CaveatWithApproval memory caveatApproval) { + caveatApproval.caveat = new CaveatEnforcer.Caveat[](1); + caveatApproval.salt = salt; + caveatApproval.caveat[0] = + CaveatEnforcer.Caveat({enforcer: enforcer, deadline: block.timestamp + 1 days, data: abi.encode(details)}); + bytes32 hash = LM.hashCaveatWithSaltAndNonce(signer.addr, salt, caveatApproval.caveat); + + (caveatApproval.v, caveatApproval.r, caveatApproval.s) = vm.sign(signer.key, hash); + } + + function newLoanWithDefaultTerms() public returns (LoanManager.Loan memory) { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + return newLoan(loan, bytes32(msg.sig), bytes32(msg.sig), borrower.addr); + } + + function generateDefaultLoanTerms() public view returns (LoanManager.Loan memory) { + SpentItem[] memory newCollateral = new SpentItem[](1); + newCollateral[0] = SpentItem({itemType: ItemType.ERC721, token: address(erc721s[0]), identifier: 1, amount: 1}); + SpentItem[] memory newDebt = new SpentItem[](1); + newDebt[0] = SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), identifier: 0, amount: 1e18}); + return LoanManager.Loan({ + start: 0, + custodian: address(custodian), + borrower: borrower.addr, + issuer: lender.addr, + originator: address(0), + collateral: newCollateral, + debt: newDebt, + terms: LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }) + }); + } + + // function newLoan( + // LoanManager.Loan memory loan, + // bytes32 borrowerSalt, + // bytes32 lenderSalt + // ) internal returns (LoanManager.Loan memory originatedLoan) { + // newLoanSpecifySigner(loan, borrowerSalt, borrower, lenderSalt, lender); + // } + function refinanceLoan( LoanManager.Loan memory loan, bytes memory newPricingData, address asWho, - bytes memory revertMessage + CaveatEnforcer.CaveatWithApproval memory lenderCaveat, + address lender ) internal returns (LoanManager.Loan memory newLoan) { - if (revertMessage.length > 0) { - vm.expectRevert(revertMessage); - } - (SpentItem[] memory offer, ReceivedItem[] memory requiredConsideration) = LM.previewOrder( - address(seaport), - asWho, - new SpentItem[](0), - new SpentItem[](0), - abi.encode(Actions.Refinance, loan, newPricingData) - ); - //OrderParameters parameters; - // uint120 numerator; - // uint120 denominator; - // bytes signature; - // bytes extraData; - OfferItem[] memory offerItems = new OfferItem[](offer.length); - for (uint256 i = 0; i < offer.length; i++) { - offerItems[i] = OfferItem({ - itemType: offer[i].itemType, - token: offer[i].token, - identifierOrCriteria: offer[i].identifier, - startAmount: offer[i].amount, - endAmount: offer[i].amount - }); - } + return refinanceLoan(loan, newPricingData, asWho, lenderCaveat, lender, ""); + } - ConsiderationItem[] memory considerationItems = new ConsiderationItem[](requiredConsideration.length); - for (uint256 i = 0; i < requiredConsideration.length; i++) { - considerationItems[i] = ConsiderationItem({ - itemType: requiredConsideration[i].itemType, - token: requiredConsideration[i].token, - identifierOrCriteria: requiredConsideration[i].identifier, - startAmount: requiredConsideration[i].amount, - endAmount: requiredConsideration[i].amount, - recipient: requiredConsideration[i].recipient - }); - } - AdvancedOrder memory refinanceOrder = AdvancedOrder({ - signature: "", - parameters: _buildContractOrder(address(LM), offerItems, considerationItems), - numerator: 1, - denominator: 1, - extraData: abi.encode(Actions.Refinance, loan, newPricingData) - }); + function getRefinanceCaveat(LoanManager.Loan memory loan, bytes memory pricingData, address fulfiller) + external + returns (LoanManager.Loan memory) + { + (SpentItem[] memory considerationPayment, SpentItem[] memory carryPayment,) = + Pricing(loan.terms.pricing).isValidRefinance(loan, pricingData, fulfiller); + return LM.applyRefinanceConsiderationToLoan(loan, considerationPayment, carryPayment, pricingData); + } + + function refinanceLoan( + LoanManager.Loan memory loan, + bytes memory pricingData, + address asWho, + CaveatEnforcer.CaveatWithApproval memory lenderCaveat, + address lender, + bytes memory revertMessage + ) internal returns (LoanManager.Loan memory newLoan) { vm.recordLogs(); vm.startPrank(asWho); + console.logBytes32(LM.hashCaveatWithSaltAndNonce(lender, bytes32(uint256(1)), lenderCaveat.caveat)); + if (revertMessage.length > 0) { - vm.expectRevert(); //reverts InvalidContractOfferer with an address an a contract nonce so expect general revert + vm.expectRevert(revertMessage); //reverts InvalidContractOfferer with an address an a contract nonce so expect general revert } - consideration.fulfillAdvancedOrder({ - advancedOrder: refinanceOrder, - criteriaResolvers: new CriteriaResolver[](0), - fulfillerConduitKey: bytes32(0), - recipient: address(asWho) - }); + LM.refinance(lender, lenderCaveat, loan, pricingData); + vm.stopPrank(); Vm.Log[] memory logs = vm.getRecordedLogs(); @@ -387,34 +532,34 @@ contract StarPortTest is BaseOrderTest { StrategistOriginator originator, ConsiderationItem[] storage collateral ) internal { - (uint8 v, bytes32 r, bytes32 s) = - vm.sign(strategist.key, keccak256(originator.encodeWithAccountCounter(keccak256(loanData.details)))); - - LoanManager.Loan memory loan = LoanManager.Loan({ - custodian: address(loanData.custodian), - issuer: address(0), - borrower: borrower.addr, - originator: address(0), - terms: abi.decode(loanData.details, (StrategistOriginator.Details)).offer.terms, - debt: debt, - collateral: ConsiderationItemLib.toSpentItemArray(collateral), - start: uint256(0) - }); - - _buyNowPLNLR( - thingToBuy, - LoanManager.Obligation({ - custodian: address(loanData.custodian), - borrower: borrower.addr, - debt: debt, - details: loanData.details, - salt: bytes32(0), - approval: abi.encodePacked(r, s, v), - caveats: loanData.caveats, - originator: address(originator) - }), - collateral // for building contract offer - ); + // (uint8 v, bytes32 r, bytes32 s) = + // vm.sign(strategist.key, keccak256(originator.encodeWithAccountCounter(keccak256(loanData.details)))); + + // LoanManager.Loan memory loan = LoanManager.Loan({ + // custodian: address(loanData.custodian), + // issuer: address(0), + // borrower: borrower.addr, + // originator: address(0), + // terms: abi.decode(loanData.details, (StrategistOriginator.Details)).offer.terms, + // debt: debt, + // collateral: ConsiderationItemLib.toSpentItemArray(collateral), + // start: uint256(0) + // }); + + // _buyNowPLNLR( + // thingToBuy, + // LoanManager.Obligation({ + // custodian: address(loanData.custodian), + // borrower: borrower.addr, + // debt: debt, + // details: loanData.details, + // salt: bytes32(0), + // approval: abi.encodePacked(r, s, v), + // caveats: loanData.caveats, + // originator: address(originator) + // }), + // collateral // for building contract offer + // ); } function _buildContractOrder(address offerer, OfferItem[] memory offer, ConsiderationItem[] memory consider) @@ -508,7 +653,7 @@ contract StarPortTest is BaseOrderTest { } function _executeRepayLoan(LoanManager.Loan memory activeLoan) internal { - (ReceivedItem[] memory loanPayment, ReceivedItem[] memory carryPayment) = + (SpentItem[] memory loanPayment, SpentItem[] memory carryPayment) = Pricing(activeLoan.terms.pricing).getPaymentConsideration(activeLoan); uint256 i = 0; ConsiderationItem[] memory consider = new ConsiderationItem[]( @@ -521,7 +666,7 @@ contract StarPortTest is BaseOrderTest { consider[i].startAmount = loanPayment[i].amount; //TODO: update this consider[i].endAmount = loanPayment[i].amount; - consider[i].recipient = loanPayment[i].recipient; + // consider[i].recipient = loanPayment[i].recipient; unchecked { ++i; } @@ -533,7 +678,7 @@ contract StarPortTest is BaseOrderTest { consider[i].startAmount = carryPayment[i].amount; //TODO: update this consider[i].endAmount = carryPayment[i].amount; - consider[i].recipient = carryPayment[i].recipient; + // consider[i].recipient = carryPayment[i].recipient; unchecked { ++i; } @@ -587,132 +732,132 @@ contract StarPortTest is BaseOrderTest { LoanManager.Obligation memory nlr, ConsiderationItem[] memory collateral // collateral (nft) and weth (purchase price is incoming weth plus debt) ) internal returns (LoanManager.Loan memory loan) { - //use murky to create a tree that is good - - bytes32 caveatHash = - keccak256(LM.encodeWithSaltAndBorrowerCounter(nlr.borrower, nlr.salt, keccak256(abi.encode(nlr.caveats)))); - OfferItem[] memory offer = new OfferItem[](nlr.debt.length + 1); - - for (uint256 i; i < debt.length;) { - offer[i] = OfferItem({ - itemType: debt[i].itemType, - token: debt[i].token, - identifierOrCriteria: debt[i].identifier, - startAmount: debt[i].amount, - endAmount: debt[i].amount - }); - unchecked { - ++i; - } - } - - offer[nlr.debt.length] = OfferItem({ - itemType: ItemType.ERC721, - token: address(LM), - identifierOrCriteria: uint256(caveatHash), - startAmount: 1, - endAmount: 1 - }); - - OfferItem[] memory zOffer = new OfferItem[](1); - zOffer[0] = OfferItem({ - itemType: nlr.debt[0].itemType, - token: nlr.debt[0].token, - identifierOrCriteria: nlr.debt[0].identifier, - startAmount: x.parameters.consideration[0].startAmount - nlr.debt[0].amount, - endAmount: x.parameters.consideration[0].startAmount - nlr.debt[0].amount - }); - ConsiderationItem[] memory zConsider = new ConsiderationItem[](1); - zConsider[0] = ConsiderationItem({ - itemType: ItemType.ERC721, - token: address(LM), - identifierOrCriteria: uint256(caveatHash), - startAmount: 1, - endAmount: 1, - recipient: payable(address(nlr.borrower)) - }); - OrderParameters memory zOP = OrderParameters({ - offerer: address(nlr.borrower), - zone: address(0), - offer: zOffer, - consideration: zConsider, - orderType: OrderType.FULL_OPEN, - startTime: block.timestamp, - endTime: block.timestamp + 100, - zoneHash: bytes32(0), - salt: 0, - conduitKey: bytes32(0), - totalOriginalConsiderationItems: 1 - }); - AdvancedOrder memory z = - AdvancedOrder({parameters: zOP, numerator: 1, denominator: 1, signature: "", extraData: ""}); - - AdvancedOrder[] memory orders = new AdvancedOrder[](3); - orders[0] = x; - orders[1] = AdvancedOrder({ - parameters: _buildContractOrder(address(LM), offer, collateral), - numerator: 1, - denominator: 1, - signature: "", - extraData: abi.encode(Actions.Origination, nlr) - }); - orders[2] = z; - - // x is offering erc721 1 to satisfy y consideration - Fulfillment[] memory fill = new Fulfillment[](4); - fill[0] = Fulfillment({ - offerComponents: new FulfillmentComponent[](1), - considerationComponents: new FulfillmentComponent[](1) - }); - - fill[0].offerComponents[0] = FulfillmentComponent({orderIndex: 1, itemIndex: 0}); - fill[0].considerationComponents[0] = FulfillmentComponent({orderIndex: 0, itemIndex: 0}); - fill[1] = Fulfillment({ - offerComponents: new FulfillmentComponent[](1), - considerationComponents: new FulfillmentComponent[](1) - }); - - fill[1].offerComponents[0] = FulfillmentComponent({orderIndex: 2, itemIndex: 0}); - - fill[1].considerationComponents[0] = FulfillmentComponent({orderIndex: 0, itemIndex: 0}); - - fill[2] = Fulfillment({ - offerComponents: new FulfillmentComponent[](1), - considerationComponents: new FulfillmentComponent[](1) - }); - - fill[2].offerComponents[0] = FulfillmentComponent({orderIndex: 0, itemIndex: 0}); - - fill[2].considerationComponents[0] = FulfillmentComponent({orderIndex: 1, itemIndex: 0}); - - fill[3] = Fulfillment({ - offerComponents: new FulfillmentComponent[](1), - considerationComponents: new FulfillmentComponent[](1) - }); - - fill[3].offerComponents[0] = FulfillmentComponent({orderIndex: 1, itemIndex: 1}); - - fill[3].considerationComponents[0] = FulfillmentComponent({orderIndex: 2, itemIndex: 0}); - - uint256 balanceBefore = erc20s[0].balanceOf(seller.addr); - vm.recordLogs(); - vm.startPrank(borrower.addr); - - consideration.matchAdvancedOrders(orders, new CriteriaResolver[](0), fill, address(borrower.addr)); - - Vm.Log[] memory logs = vm.getRecordedLogs(); - - // console.logBytes32(logs[logs.length - 4].topics[0]); - for (uint256 i = 0; i < logs.length; i++) { - if (logs[i].topics[0] == bytes32(0x57cb72d73c48fadf55428537f6c9efbe080ae111339b0c5af42d9027ed20ba17)) { - (, loan) = abi.decode(logs[i].data, (uint256, LoanManager.Loan)); - break; - } - } - - assertEq(erc721s[1].ownerOf(1), address(nlr.custodian)); - assertEq(erc20s[0].balanceOf(seller.addr), balanceBefore + x.parameters.consideration[0].startAmount); - vm.stopPrank(); + // //use murky to create a tree that is good + + // bytes32 caveatHash = bytes32(uint256(0)); + // // keccak256(LM.encodeWithSaltAndBorrowerCounter(nlr.borrower, nlr.salt, keccak256(abi.encode(nlr.caveats)))); + // OfferItem[] memory offer = new OfferItem[](nlr.debt.length + 1); + + // for (uint256 i; i < debt.length;) { + // offer[i] = OfferItem({ + // itemType: debt[i].itemType, + // token: debt[i].token, + // identifierOrCriteria: debt[i].identifier, + // startAmount: debt[i].amount, + // endAmount: debt[i].amount + // }); + // unchecked { + // ++i; + // } + // } + + // offer[nlr.debt.length] = OfferItem({ + // itemType: ItemType.ERC721, + // token: address(LM), + // identifierOrCriteria: uint256(caveatHash), + // startAmount: 1, + // endAmount: 1 + // }); + + // OfferItem[] memory zOffer = new OfferItem[](1); + // zOffer[0] = OfferItem({ + // itemType: nlr.debt[0].itemType, + // token: nlr.debt[0].token, + // identifierOrCriteria: nlr.debt[0].identifier, + // startAmount: x.parameters.consideration[0].startAmount - nlr.debt[0].amount, + // endAmount: x.parameters.consideration[0].startAmount - nlr.debt[0].amount + // }); + // ConsiderationItem[] memory zConsider = new ConsiderationItem[](1); + // zConsider[0] = ConsiderationItem({ + // itemType: ItemType.ERC721, + // token: address(LM), + // identifierOrCriteria: uint256(caveatHash), + // startAmount: 1, + // endAmount: 1, + // recipient: payable(address(nlr.borrower)) + // }); + // OrderParameters memory zOP = OrderParameters({ + // offerer: address(nlr.borrower), + // zone: address(0), + // offer: zOffer, + // consideration: zConsider, + // orderType: OrderType.FULL_OPEN, + // startTime: block.timestamp, + // endTime: block.timestamp + 100, + // zoneHash: bytes32(0), + // salt: 0, + // conduitKey: bytes32(0), + // totalOriginalConsiderationItems: 1 + // }); + // AdvancedOrder memory z = + // AdvancedOrder({parameters: zOP, numerator: 1, denominator: 1, signature: "", extraData: ""}); + + // AdvancedOrder[] memory orders = new AdvancedOrder[](3); + // orders[0] = x; + // orders[1] = AdvancedOrder({ + // parameters: _buildContractOrder(address(LM), offer, collateral), + // numerator: 1, + // denominator: 1, + // signature: "", + // extraData: abi.encode(Actions.Origination, nlr) + // }); + // orders[2] = z; + + // // x is offering erc721 1 to satisfy y consideration + // Fulfillment[] memory fill = new Fulfillment[](4); + // fill[0] = Fulfillment({ + // offerComponents: new FulfillmentComponent[](1), + // considerationComponents: new FulfillmentComponent[](1) + // }); + + // fill[0].offerComponents[0] = FulfillmentComponent({orderIndex: 1, itemIndex: 0}); + // fill[0].considerationComponents[0] = FulfillmentComponent({orderIndex: 0, itemIndex: 0}); + // fill[1] = Fulfillment({ + // offerComponents: new FulfillmentComponent[](1), + // considerationComponents: new FulfillmentComponent[](1) + // }); + + // fill[1].offerComponents[0] = FulfillmentComponent({orderIndex: 2, itemIndex: 0}); + + // fill[1].considerationComponents[0] = FulfillmentComponent({orderIndex: 0, itemIndex: 0}); + + // fill[2] = Fulfillment({ + // offerComponents: new FulfillmentComponent[](1), + // considerationComponents: new FulfillmentComponent[](1) + // }); + + // fill[2].offerComponents[0] = FulfillmentComponent({orderIndex: 0, itemIndex: 0}); + + // fill[2].considerationComponents[0] = FulfillmentComponent({orderIndex: 1, itemIndex: 0}); + + // fill[3] = Fulfillment({ + // offerComponents: new FulfillmentComponent[](1), + // considerationComponents: new FulfillmentComponent[](1) + // }); + + // fill[3].offerComponents[0] = FulfillmentComponent({orderIndex: 1, itemIndex: 1}); + + // fill[3].considerationComponents[0] = FulfillmentComponent({orderIndex: 2, itemIndex: 0}); + + // uint256 balanceBefore = erc20s[0].balanceOf(seller.addr); + // vm.recordLogs(); + // vm.startPrank(borrower.addr); + + // consideration.matchAdvancedOrders(orders, new CriteriaResolver[](0), fill, address(borrower.addr)); + + // Vm.Log[] memory logs = vm.getRecordedLogs(); + + // // console.logBytes32(logs[logs.length - 4].topics[0]); + // for (uint256 i = 0; i < logs.length; i++) { + // if (logs[i].topics[0] == bytes32(0x57cb72d73c48fadf55428537f6c9efbe080ae111339b0c5af42d9027ed20ba17)) { + // (, loan) = abi.decode(logs[i].data, (uint256, LoanManager.Loan)); + // break; + // } + // } + + // assertEq(erc721s[1].ownerOf(1), address(nlr.custodian)); + // assertEq(erc20s[0].balanceOf(seller.addr), balanceBefore + x.parameters.consideration[0].startAmount); + // vm.stopPrank(); } function _executeNLR(LoanManager.Obligation memory nlr, ConsiderationItem[] memory collateral) @@ -727,98 +872,98 @@ contract StarPortTest is BaseOrderTest { ConsiderationItem[] memory collateral, bytes memory revertReason ) internal returns (LoanManager.Loan memory loan) { - bytes32 caveatHash = - keccak256(LM.encodeWithSaltAndBorrowerCounter(nlr.borrower, nlr.salt, keccak256(abi.encode(nlr.caveats)))); - OfferItem[] memory offer = new OfferItem[](nlr.debt.length + 1); - - for (uint256 i; i < debt.length;) { - offer[i] = OfferItem({ - itemType: debt[i].itemType, - token: debt[i].token, - identifierOrCriteria: debt[i].identifier, - startAmount: debt[i].amount, - endAmount: debt[i].amount - }); - unchecked { - ++i; - } - } - - offer[nlr.debt.length] = OfferItem({ - itemType: ItemType.ERC721, - token: address(LM), - identifierOrCriteria: uint256(caveatHash), - startAmount: 1, - endAmount: 1 - }); - - OrderParameters memory op = - _buildContractOrder(address(LM), nlr.caveats.length == 0 ? new OfferItem[](0) : offer, collateral); - - AdvancedOrder memory x = AdvancedOrder({ - parameters: op, - numerator: 1, - denominator: 1, - signature: "0x", - extraData: abi.encode(Actions.Origination, nlr) - }); - - uint256 balanceBefore; - if (debt[0].token == address(0)) { - balanceBefore = borrower.addr.balance; - } else { - balanceBefore = ERC20(debt[0].token).balanceOf(borrower.addr); - } - vm.recordLogs(); - vm.startPrank(borrower.addr); - if (revertReason.length > 0) { - vm.expectRevert(revertReason); - } - if (collateral[0].itemType == ItemType.NATIVE) { - consideration.fulfillAdvancedOrder{value: collateral[0].endAmount}({ - advancedOrder: x, - criteriaResolvers: new CriteriaResolver[](0), - fulfillerConduitKey: bytes32(0), - recipient: address(borrower.addr) - }); - } else { - consideration.fulfillAdvancedOrder({ - advancedOrder: x, - criteriaResolvers: new CriteriaResolver[](0), - fulfillerConduitKey: bytes32(0), - recipient: address(borrower.addr) - }); - } - Vm.Log[] memory logs = vm.getRecordedLogs(); - uint256 loanId; - - // console.logBytes32(logs[logs.length - 4].topics[0]); - bytes32 lienOpenTopic = bytes32(0x57cb72d73c48fadf55428537f6c9efbe080ae111339b0c5af42d9027ed20ba17); - for (uint256 i = 0; i < logs.length; i++) { - if (logs[i].topics[0] == lienOpenTopic) { - (loanId, loan) = abi.decode(logs[i].data, (uint256, LoanManager.Loan)); - break; - } - } - - uint256 balanceAfter; - if (debt[0].token == address(0)) { - balanceAfter = borrower.addr.balance; - } else { - balanceAfter = ERC20(debt[0].token).balanceOf(borrower.addr); - } - - uint256 feeReceiverBalance; - if (LM.feeTo() != address(0)) { - if (debt[0].token == address(0)) { - feeReceiverBalance = LM.feeTo().balance; - } else { - feeReceiverBalance = ERC20(debt[0].token).balanceOf(LM.feeTo()); - } - } - - assertEq(balanceAfter - balanceBefore + feeReceiverBalance, debt[0].amount); - vm.stopPrank(); + // bytes32 caveatHash = bytes32(uint256(0)); + // // keccak256(LM.encodeWithSaltAndBorrowerCounter(nlr.borrower, nlr.salt, keccak256(abi.encode(nlr.caveats)))); + // OfferItem[] memory offer = new OfferItem[](nlr.debt.length + 1); + + // for (uint256 i; i < debt.length;) { + // offer[i] = OfferItem({ + // itemType: debt[i].itemType, + // token: debt[i].token, + // identifierOrCriteria: debt[i].identifier, + // startAmount: debt[i].amount, + // endAmount: debt[i].amount + // }); + // unchecked { + // ++i; + // } + // } + + // offer[nlr.debt.length] = OfferItem({ + // itemType: ItemType.ERC721, + // token: address(LM), + // identifierOrCriteria: uint256(caveatHash), + // startAmount: 1, + // endAmount: 1 + // }); + + // OrderParameters memory op = + // _buildContractOrder(address(LM), nlr.caveats.length == 0 ? new OfferItem[](0) : offer, collateral); + + // AdvancedOrder memory x = AdvancedOrder({ + // parameters: op, + // numerator: 1, + // denominator: 1, + // signature: "0x", + // extraData: abi.encode(Actions.Origination, nlr) + // }); + + // uint256 balanceBefore; + // if (debt[0].token == address(0)) { + // balanceBefore = borrower.addr.balance; + // } else { + // balanceBefore = ERC20(debt[0].token).balanceOf(borrower.addr); + // } + // vm.recordLogs(); + // vm.startPrank(borrower.addr); + // if (revertReason.length > 0) { + // vm.expectRevert(revertReason); + // } + // if (collateral[0].itemType == ItemType.NATIVE) { + // consideration.fulfillAdvancedOrder{value: collateral[0].endAmount}({ + // advancedOrder: x, + // criteriaResolvers: new CriteriaResolver[](0), + // fulfillerConduitKey: bytes32(0), + // recipient: address(borrower.addr) + // }); + // } else { + // consideration.fulfillAdvancedOrder({ + // advancedOrder: x, + // criteriaResolvers: new CriteriaResolver[](0), + // fulfillerConduitKey: bytes32(0), + // recipient: address(borrower.addr) + // }); + // } + // Vm.Log[] memory logs = vm.getRecordedLogs(); + // uint256 loanId; + + // // console.logBytes32(logs[logs.length - 4].topics[0]); + // bytes32 lienOpenTopic = bytes32(0x57cb72d73c48fadf55428537f6c9efbe080ae111339b0c5af42d9027ed20ba17); + // for (uint256 i = 0; i < logs.length; i++) { + // if (logs[i].topics[0] == lienOpenTopic) { + // (loanId, loan) = abi.decode(logs[i].data, (uint256, LoanManager.Loan)); + // break; + // } + // } + + // uint256 balanceAfter; + // if (debt[0].token == address(0)) { + // balanceAfter = borrower.addr.balance; + // } else { + // balanceAfter = ERC20(debt[0].token).balanceOf(borrower.addr); + // } + + // uint256 feeReceiverBalance; + // if (LM.feeTo() != address(0)) { + // if (debt[0].token == address(0)) { + // feeReceiverBalance = LM.feeTo().balance; + // } else { + // feeReceiverBalance = ERC20(debt[0].token).balanceOf(LM.feeTo()); + // } + // } + + // assertEq(balanceAfter - balanceBefore + feeReceiverBalance, debt[0].amount); + // vm.stopPrank(); } function _repayLoan(address borrower, uint256 amount, LoanManager.Loan memory loan) internal { @@ -836,19 +981,11 @@ contract StarPortTest is BaseOrderTest { assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); - loan = _createLoan({ - lender: lender, - terms: terms, - collateralItem: ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custodian)) - }), - debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: borrowAmount, identifier: 0}) - }); + LoanManager.Loan memory originationDetails = _generateOriginationDetails( + _getERC721SpentItem(erc721s[0]), _getERC20SpentItem(erc20s[0], borrowAmount), lender + ); + originationDetails.terms = terms; + loan = newLoan(originationDetails, bytes32(msg.sig), bytes32(msg.sig), fulfiller.addr); assertTrue(erc721s[0].balanceOf(borrower.addr) < initial721Balance, "Borrower ERC721 was not sent out"); assertTrue(erc20s[0].balanceOf(borrower.addr) > initial20Balance, "Borrower did not receive ERC20"); @@ -866,19 +1003,11 @@ contract StarPortTest is BaseOrderTest { uint256 initial20Balance0 = erc20s[0].balanceOf(borrower.addr); - loan = _createLoan({ - lender: lender, - terms: terms, - collateralItem: ConsiderationItem({ - token: address(erc20s[1]), - startAmount: collateralAmount, - endAmount: collateralAmount, - identifierOrCriteria: 0, - itemType: ItemType.ERC20, - recipient: payable(address(custodian)) - }), - debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: borrowAmount, identifier: 0}) - }); + LoanManager.Loan memory originationDetails = _generateOriginationDetails( + _getERC20SpentItem(erc20s[1], collateralAmount), _getERC20SpentItem(erc20s[0], borrowAmount), lender + ); + originationDetails.terms = terms; + loan = newLoan(originationDetails, bytes32(msg.sig), bytes32(msg.sig), fulfiller.addr); assertEq( initial20Balance1 - collateralAmount, erc20s[1].balanceOf(borrower.addr), "Borrower ERC20 was not sent out" @@ -891,78 +1020,31 @@ contract StarPortTest is BaseOrderTest { internal returns (LoanManager.Loan memory loan) { - return _createLoan({ - lender: lender, - terms: terms, - collateralItem: ConsiderationItem({ - token: address(erc20s[0]), - startAmount: 20, - endAmount: 20, - identifierOrCriteria: 0, - itemType: ItemType.ERC20, - recipient: payable(address(custodian)) - }), - debtItem: SpentItem({itemType: ItemType.ERC721, token: address(erc721s[0]), amount: 1, identifier: 0}) - }); + LoanManager.Loan memory originationDetails = + _generateOriginationDetails(_getERC20SpentItem(erc20s[0], 20), _getERC721SpentItem(erc721s[2]), lender); + originationDetails.terms = terms; + return newLoan(originationDetails, bytes32(msg.sig), bytes32(msg.sig), fulfiller.addr); } - function _generateOriginationDetails( - ConsiderationItem memory collateral, - SpentItem memory debtRequested, - address incomingIssuer - ) internal returns (StrategistOriginator.Details memory) { - return _generateOriginationDetails(collateral, debtRequested, incomingIssuer, address(custodian)); + function _generateOriginationDetails(SpentItem memory collateral, SpentItem memory debt, address incomingIssuer) + internal + view + returns (LoanManager.Loan memory loan) + { + return _generateOriginationDetails(collateral, debt, incomingIssuer, address(custodian)); } function _generateOriginationDetails( - ConsiderationItem memory collateral, - SpentItem memory debtRequested, + SpentItem memory collateral, + SpentItem memory debt, address incomingIssuer, address incomingCustodian - ) internal returns (StrategistOriginator.Details memory details) { - delete selectedCollateral; - delete debt; - selectedCollateral.push(collateral); - debt.push(debtRequested); - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(hook), - handler: address(handler), - pricing: address(pricing), - pricingData: defaultPricingData, - handlerData: defaultHandlerData, - hookData: defaultHookData - }); - details = StrategistOriginator.Details({ - conduit: address(lenderConduit), - custodian: address(incomingCustodian), - issuer: incomingIssuer, - deadline: block.timestamp + 100, - offer: StrategistOriginator.Offer({ - salt: bytes32(0), - terms: terms, - collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), - debt: debt - }) - }); - } - - function _createLoan( - address lender, - LoanManager.Terms memory terms, - ConsiderationItem memory collateralItem, - SpentItem memory debtItem - ) internal returns (LoanManager.Loan memory loan) { - StrategistOriginator.Details memory loanDetails = _generateOriginationDetails(collateralItem, debtItem, lender); - - loan = newLoan( - NewLoanData({ - custodian: address(custodian), - caveats: new LoanManager.Caveat[](0), // TODO check - details: abi.encode(loanDetails) - }), - StrategistOriginator(SO), - selectedCollateral - ); + ) internal view returns (LoanManager.Loan memory loan) { + loan = generateDefaultLoanTerms(); + loan.issuer = incomingIssuer; + loan.debt[0] = debt; + loan.collateral[0] = collateral; + loan.custodian = incomingCustodian; } function _createLoanWithCaveat( @@ -972,34 +1054,34 @@ contract StarPortTest is BaseOrderTest { SpentItem memory debtItem, LoanManager.Caveat[] memory caveats ) internal returns (LoanManager.Loan memory loan) { - selectedCollateral.push(collateralItem); - debt.push(debtItem); - - StrategistOriginator.Details memory loanDetails = StrategistOriginator.Details({ - conduit: address(lenderConduit), - custodian: address(custodian), - issuer: lender, - deadline: block.timestamp + 100, - offer: StrategistOriginator.Offer({ - salt: bytes32(0), - terms: terms, - collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), - debt: debt - }) - }); - - loan = newLoan( - NewLoanData({ - custodian: address(custodian), - caveats: caveats, // TODO check - details: abi.encode(loanDetails) - }), - StrategistOriginator(SO), - selectedCollateral - ); + // selectedCollateral.push(collateralItem); + // debt.push(debtItem); + + // StrategistOriginator.Details memory loanDetails = StrategistOriginator.Details({ + // conduit: address(lenderConduit), + // custodian: address(custodian), + // issuer: lender, + // deadline: block.timestamp + 100, + // offer: StrategistOriginator.Offer({ + // salt: bytes32(0), + // terms: terms, + // collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), + // debt: debt + // }) + // }); + + // loan = newLoan( + // NewLoanData({ + // custodian: address(custodian), + // caveats: caveats, // TODO check + // details: abi.encode(loanDetails) + // }), + // StrategistOriginator(SO), + // selectedCollateral + // ); } - function _getERC20SpentItem(TestERC20 token, uint256 amount) internal view returns (SpentItem memory) { + function _getERC20SpentItem(TestERC20 token, uint256 amount) internal pure returns (SpentItem memory) { return SpentItem({ itemType: ItemType.ERC20, token: address(token), @@ -1008,6 +1090,18 @@ contract StarPortTest is BaseOrderTest { }); } + function _getERC721SpentItem(TestERC721 token) internal pure returns (SpentItem memory) { + return SpentItem({itemType: ItemType.ERC721, token: address(token), amount: 1, identifier: 1}); + } + + function _getERC721SpentItem(TestERC721 token, uint256 tokenId) internal pure returns (SpentItem memory) { + return SpentItem({itemType: ItemType.ERC721, token: address(token), amount: 1, identifier: tokenId}); + } + + function _getERC1155SpentItem(TestERC1155 token) internal pure returns (SpentItem memory) { + return SpentItem({itemType: ItemType.ERC1155, token: address(token), amount: 1, identifier: 1}); + } + function _getERC721Consideration(TestERC721 token) internal view returns (ConsiderationItem memory) { return ConsiderationItem({ token: address(token), diff --git a/test/integration-testing/TestAstariaV1Loan.sol b/test/integration-testing/TestAstariaV1Loan.sol index 16912f74..e90d2164 100644 --- a/test/integration-testing/TestAstariaV1Loan.sol +++ b/test/integration-testing/TestAstariaV1Loan.sol @@ -4,23 +4,23 @@ import {BaseRecall} from "starport-core/hooks/BaseRecall.sol"; import "forge-std/console2.sol"; import {StarPortLib, Actions} from "starport-core/lib/StarPortLib.sol"; +import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; + contract TestAstariaV1Loan is AstariaV1Test { + using FixedPointMathLib for uint256; using {StarPortLib.getId} for LoanManager.Loan; function testNewLoanERC721CollateralDefaultTermsRecallBase() public { - StrategistOriginator.Details memory loanDetails = _generateOriginationDetails( - _getERC721Consideration(erc721s[0]), - SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0}), - lender.addr - ); - - LoanManager.Loan memory loan = newLoan( - NewLoanData(address(custodian), new LoanManager.Caveat[](0), abi.encode(loanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); - uint256 loanId = loan.getId(); - assertTrue(LM.active(loanId), "LoanId not in active state after a new loan"); + 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.startPrank(recaller.addr); @@ -31,10 +31,25 @@ contract TestAstariaV1Loan is AstariaV1Test { } { // refinance with before recall is initiated + CaveatEnforcer.CaveatWithApproval memory lenderCaveat = CaveatEnforcer.CaveatWithApproval({ + v: 0, + r: bytes32(0), + s: bytes32(0), + salt: bytes32(uint256(1)), + caveat: new CaveatEnforcer.Caveat[](1) + }); + lenderCaveat.caveat[0] = CaveatEnforcer.Caveat({ + enforcer: address(lenderEnforcer), + deadline: block.timestamp + 1 days, + data: abi.encode(uint256(0)) + }); + refinanceLoan( loan, abi.encode(BasePricing.Details({rate: (uint256(1e16) * 100) / (365 * 1 days), carryRate: 0})), refinancer.addr, + lenderCaveat, + refinancer.addr, abi.encodeWithSelector(Pricing.InvalidRefinance.selector) ); } @@ -65,6 +80,7 @@ contract TestAstariaV1Loan is AstariaV1Test { ); } { + uint256 loanId = loan.getId(); BaseRecall recallContract = BaseRecall(address(hook)); address recallerAddr; uint64 start; @@ -81,10 +97,25 @@ contract TestAstariaV1Loan is AstariaV1Test { } { // refinance with incorrect terms + CaveatEnforcer.CaveatWithApproval memory lenderCaveat = CaveatEnforcer.CaveatWithApproval({ + v: 0, + r: bytes32(0), + s: bytes32(0), + salt: bytes32(uint256(1)), + caveat: new CaveatEnforcer.Caveat[](1) + }); + + lenderCaveat.caveat[0] = CaveatEnforcer.Caveat({ + enforcer: address(lenderEnforcer), + deadline: block.timestamp + 1 days, + data: abi.encode(uint256(0)) + }); refinanceLoan( loan, abi.encode(BasePricing.Details({rate: (uint256(1e16) * 100) / (365 * 1 days), carryRate: 0})), refinancer.addr, + lenderCaveat, + refinancer.addr, abi.encodeWithSelector(AstariaV1Pricing.InsufficientRefinance.selector) ); } @@ -92,32 +123,81 @@ contract TestAstariaV1Loan is AstariaV1Test { // refinance with correct terms uint256 newLenderBefore = erc20s[0].balanceOf(refinancer.addr); uint256 oldLenderBefore = erc20s[0].balanceOf(lender.addr); + uint256 oldOriginatorBefore = erc20s[0].balanceOf(loan.originator); uint256 recallerBefore = erc20s[0].balanceOf(recaller.addr); + uint256 newFullfillerBefore = erc20s[0].balanceOf(address(this)); BaseRecall.Details memory details = abi.decode(loan.terms.hookData, (BaseRecall.Details)); vm.warp(block.timestamp + (details.recallWindow / 2)); - refinanceLoan( - loan, abi.encode(BasePricing.Details({rate: details.recallMax / 2, carryRate: 0})), refinancer.addr - ); + + bytes memory pricingData = abi.encode(BasePricing.Details({rate: details.recallMax / 2, carryRate: 0})); + { + LenderEnforcer.Details memory refinanceDetails = getRefinanceDetails(loan, pricingData, refinancer.addr); + console.log("here"); + CaveatEnforcer.CaveatWithApproval memory refinancerCaveat = + getLenderSignedCaveat(refinanceDetails, refinancer, bytes32(uint256(1)), address(lenderEnforcer)); + // vm.startPrank(refinancer.addr); + 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(); + + erc20s[0].approve(address(LM), stake); + refinanceLoan(loan, pricingData, address(this), refinancerCaveat, refinancer.addr); + console.log("here2"); + } + uint256 delta_t = block.timestamp - loan.start; BasePricing.Details memory pricingDetails = abi.decode(loan.terms.pricingData, (BasePricing.Details)); - uint256 interest = - BasePricing(address(pricing)).calculateInterest(delta_t, loan.debt[0].amount, pricingDetails.rate); - uint256 newLenderAfter = erc20s[0].balanceOf(refinancer.addr); - uint256 oldLenderAfter = erc20s[0].balanceOf(lender.addr); - assertEq( - oldLenderAfter, - oldLenderBefore + loan.debt[0].amount + interest, - "Payment to old lender calculated incorrectly" - ); - assertEq( - newLenderAfter, - newLenderBefore - (loan.debt[0].amount + interest + stake), - "Payment from new lender calculated incorrectly" + uint256 interest = CompoundInterestPricing(address(pricing)).calculateInterest( + delta_t, loan.debt[0].amount, pricingDetails.rate ); + + { + uint256 oldLenderAfter = erc20s[0].balanceOf(lender.addr); + assertEq( + oldLenderAfter, + oldLenderBefore + loan.debt[0].amount + interest.mulWad(1e18 - pricingDetails.carryRate), + "Payment to old lender calculated incorrectly" + ); + } + + { + uint256 newLenderAfter = erc20s[0].balanceOf(refinancer.addr); + assertEq( + newLenderAfter, + newLenderBefore - (loan.debt[0].amount + interest), + "Payment from new lender calculated incorrectly" + ); + } assertEq( recallerBefore + stake, erc20s[0].balanceOf(recaller.addr), "Recaller did not recover stake as expected" ); - assertTrue(LM.inactive(loanId), "LoanId not properly flipped to inactive after refinance"); + + { + uint256 oldOriginatorAfter = erc20s[0].balanceOf(loan.originator); + assertEq( + oldOriginatorAfter, + oldOriginatorBefore + interest.mulWad(pricingDetails.carryRate), + "Carry payment to old originator calculated incorrectly" + ); + } + + { + uint256 newFullfillerAfter = erc20s[0].balanceOf(address(this)); + assertEq( + newFullfillerAfter, + newFullfillerBefore - stake, + "New fulfiller did not repay recaller stake correctly" + ); + } + + { + uint256 loanId = loan.getId(); + assertTrue(LM.inactive(loanId), "LoanId not properly flipped to inactive after refinance"); + } } { uint256 withdrawerBalanceBefore = erc20s[0].balanceOf(address(this)); @@ -139,10 +219,10 @@ contract TestAstariaV1Loan is AstariaV1Test { } } + event log_caveatapproval(CaveatEnforcer.CaveatWithApproval caveatApproval); + // lender is recaller, liquidation amount is 0 function testNewLoanERC721CollateralDefaultTermsRecallLender() public { - Custodian custody = Custodian(LM.defaultCustodian()); - LoanManager.Terms memory terms = LoanManager.Terms({ hook: address(hook), handler: address(handler), @@ -151,37 +231,8 @@ contract TestAstariaV1Loan is AstariaV1Test { handlerData: defaultHandlerData, hookData: defaultHookData }); - - selectedCollateral.push( - ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custody)) - }) - ); - - debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); - StrategistOriginator.Details memory loanDetails = StrategistOriginator.Details({ - conduit: address(lenderConduit), - custodian: address(custody), - issuer: lender.addr, - deadline: block.timestamp + 100, - offer: StrategistOriginator.Offer({ - salt: bytes32(0), - terms: terms, - collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), - debt: debt - }) - }); - - LoanManager.Loan memory loan = newLoan( - NewLoanData(address(custody), new LoanManager.Caveat[](0), abi.encode(loanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); uint256 loanId = loan.getId(); uint256 stake; @@ -203,9 +254,10 @@ contract TestAstariaV1Loan is AstariaV1Test { stake = BasePricing(address(pricing)).calculateInterest( details.recallStakeDuration, loan.debt[0].amount, pricingDetails.rate ); - assertEq(balanceBefore, balanceAfter + stake, "Recaller balance not transfered correctly"); + // lender is not required to provide a stake to recall + assertEq(balanceBefore, balanceAfter, "Recaller balance not transfered correctly"); assertEq( - recallContractBalanceBefore + stake, + recallContractBalanceBefore, recallContractBalanceAfter, "Balance not transfered to recall contract correctly" ); @@ -282,8 +334,6 @@ contract TestAstariaV1Loan is AstariaV1Test { // recaller is not the lender, liquidation amount is a dutch auction function testNewLoanERC721CollateralDefaultTermsRecallLiquidation() public { - Custodian custody = Custodian(LM.defaultCustodian()); - LoanManager.Terms memory terms = LoanManager.Terms({ hook: address(hook), handler: address(handler), @@ -292,37 +342,8 @@ contract TestAstariaV1Loan is AstariaV1Test { handlerData: defaultHandlerData, hookData: defaultHookData }); - - selectedCollateral.push( - ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custody)) - }) - ); - - debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); - StrategistOriginator.Details memory loanDetails = StrategistOriginator.Details({ - conduit: address(lenderConduit), - custodian: address(custody), - issuer: lender.addr, - deadline: block.timestamp + 100, - offer: StrategistOriginator.Offer({ - salt: bytes32(0), - terms: terms, - collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), - debt: debt - }) - }); - - LoanManager.Loan memory loan = newLoan( - NewLoanData(address(custody), new LoanManager.Caveat[](0), abi.encode(loanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); uint256 loanId = loan.getId(); uint256 stake; @@ -377,11 +398,25 @@ contract TestAstariaV1Loan is AstariaV1Test { SettlementHandler(loan.terms.handler).getSettlement(loan); assertEq( - settlementConsideration.length, 2, "Settlement consideration length for a dutch auction should be 2" + settlementConsideration.length, + 3, + "Settlement consideration length for a dutch auction should be 3 (carry, recaller, and the lender)" ); assertEq(restricted, address(0), "SettlementConsideration should be unrestricted"); - assertEq(settlementConsideration[0].amount, 450 ether, "Settlement consideration for loan incorrect"); - assertEq(settlementConsideration[1].amount, 50 ether, "Settlement consideration for loan incorrect"); + { + uint256 carry = uint256(1643840372884797); + uint256 settlementPrice = 500 ether - carry; + uint256 recallerReward = settlementPrice.mulWad(10e16); + assertEq(settlementConsideration[0].amount, carry, "Settlement consideration for carry incorrect"); + assertEq( + settlementConsideration[1].amount, recallerReward, "Settlement consideration for recaller incorrect" + ); + assertEq( + settlementConsideration[2].amount, + settlementPrice - recallerReward, + "Settlement consideration for lender incorrect" + ); + } ConsiderationItem[] memory consider = new ConsiderationItem[]( settlementConsideration.length ); diff --git a/test/integration-testing/TestExoticLoans.t.sol b/test/integration-testing/TestExoticLoans.t.sol deleted file mode 100644 index 17a7b0ce..00000000 --- a/test/integration-testing/TestExoticLoans.t.sol +++ /dev/null @@ -1,95 +0,0 @@ -import "starport-test/StarPortTest.sol"; -import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; -import {LibString} from "solady/src/utils/LibString.sol"; - -import "forge-std/console.sol"; - -contract TestExoticLoans is StarPortTest { - function testSwap() public { - SwapHook swapHook = new SwapHook(); - SwapHandler swapHandler = new SwapHandler(LM); - SwapPricing swapPricing = new SwapPricing(LM); - - bytes memory swapPricingData = ""; - bytes memory swapHandlerData = ""; - bytes memory swapHookData = ""; - - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(swapHook), - handler: address(swapHandler), - pricing: address(swapPricing), - pricingData: swapPricingData, - handlerData: swapHandlerData, - hookData: swapHookData - }); - - // uint256 initialErc201balance = erc20s[1].balanceOf(borrower.addr); - // uint256 initialErc202balance = erc20s[0].balanceOf(borrower.addr); - - LoanManager.Loan memory loan = _createLoan20Collateral20Debt({ - lender: lender.addr, - collateralAmount: 20, // erc20s[1] - borrowAmount: 100, // erc20s[0] - terms: terms - }); - - // assertEq(erc20s[1].balanceOf(borrower.addr), initialErc201balance); - // assertEq(erc20s[0].balanceOf(borrower.addr), initialErc202balance); - // skip(10 days); - // - // _repayLoan({ - // borrower: borrower.addr, - // amount: 375, - // loan: loan - // }); - } -} - -contract SwapHook is SettlementHook { - function isActive(LoanManager.Loan calldata loan) external view override returns (bool) { - return true; - } -} - -contract SwapHandler is SettlementHandler { - constructor(LoanManager LM_) SettlementHandler(LM_) {} - - function execute(LoanManager.Loan calldata loan, address fulfiller) external override returns (bytes4) { - return bytes4(0); - } - - function validate(LoanManager.Loan calldata loan) external view override returns (bool) { - return true; - } - - function getSettlement(LoanManager.Loan calldata loan) - public - view - override - returns (ReceivedItem[] memory consideration, address restricted) - { - return (new ReceivedItem[](0), address(0)); - } -} - -contract SwapPricing is Pricing { - constructor(LoanManager LM_) Pricing(LM_) {} - - function getPaymentConsideration(LoanManager.Loan memory loan) - public - view - override - returns (ReceivedItem[] memory, ReceivedItem[] memory) - { - return (new ReceivedItem[](0), new ReceivedItem[](0)); - } - - function isValidRefinance(LoanManager.Loan memory loan, bytes memory newPricingData, address caller) - external - view - override - returns (ReceivedItem[] memory, ReceivedItem[] memory, ReceivedItem[] memory) - { - return (new ReceivedItem[](0), new ReceivedItem[](0), new ReceivedItem[](0)); - } -} diff --git a/test/integration-testing/TestLoanCombinations.t.sol b/test/integration-testing/TestLoanCombinations.t.sol index 9bff5a99..d955d558 100644 --- a/test/integration-testing/TestLoanCombinations.t.sol +++ b/test/integration-testing/TestLoanCombinations.t.sol @@ -54,59 +54,44 @@ contract TestLoanCombinations is StarPortTest { terms: terms }); - // skip(10 days); - // - // _repayLoan({ - // borrower: borrower.addr, - // amount: 375, - // loan: loan - // }); + skip(10 days); + + _repayLoan({borrower: borrower.addr, amount: 375, loan: loan}); } function testLoan20For721SimpleInterestDutchFixedRepay() public { - // LoanManager.Terms memory terms = LoanManager.Terms({ - // hook: address(fixedTermHook), - // handler: address(dutchAuctionHandler), - // pricing: address(simpleInterestPricing), - // pricingData: defaultPricingData, - // handlerData: defaultHandlerData, - // hookData: defaultHookData - // }); - // LoanManager.Loan memory loan = _createLoan20Collateral721Debt({ - // lender: lender.addr, - // terms: terms - // }); - // skip(10 days); - // - // _repayLoan({ // TODO different repay - // borrower: borrower.addr, - // amount: 375, - // loan: loan - // }); + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(fixedTermHook), + handler: address(dutchAuctionHandler), + pricing: address(simpleInterestPricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + LoanManager.Loan memory loan = _createLoan20Collateral721Debt({lender: lender.addr, terms: terms}); + skip(10 days); + + _repayLoan({ // TODO different repay + borrower: borrower.addr, + amount: 375, + loan: loan + }); } function testLoanAstariaSettlementRepay() public { - bytes memory astariaPricingData = new bytes(0); - bytes memory astariaSettlementHandlerData = new bytes(0); - bytes memory astariaSettlementHookData = new bytes(0); - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(astariaSettlementHook), - handler: address(astariaSettlementHandler), - pricing: address(astariaPricing), - pricingData: astariaPricingData, - handlerData: astariaSettlementHandlerData, - hookData: astariaSettlementHookData + hook: address(fixedTermHook), + handler: address(dutchAuctionHandler), + pricing: address(simpleInterestPricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData }); LoanManager.Loan memory loan = _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 100, terms: terms}); - // skip(10 days); - // - // _repayLoan({ - // borrower: borrower.addr, - // amount: 375, - // loan: loan - // }); + skip(10 days); + + _repayLoan({borrower: borrower.addr, amount: 375, loan: loan}); } function testLoanSimpleInterestEnglishFixed() public { diff --git a/test/integration-testing/TestNewLoan.sol b/test/integration-testing/TestNewLoan.sol index 2e1be9f9..7cd81cfe 100644 --- a/test/integration-testing/TestNewLoan.sol +++ b/test/integration-testing/TestNewLoan.sol @@ -15,286 +15,264 @@ contract TestNewLoan is StarPortTest { hookData: defaultHookData }); - selectedCollateral.push( - ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custody)) - }) - ); - - debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); - StrategistOriginator.Details memory loanDetails = StrategistOriginator.Details({ - conduit: address(lenderConduit), - custodian: address(custody), - issuer: lender.addr, - deadline: block.timestamp + 100, - offer: StrategistOriginator.Offer({ - salt: bytes32(0), - terms: terms, - collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), - debt: debt - }) - }); - - TermEnforcer TE = new TermEnforcer(); - - TermEnforcer.Details memory TEDetails = - TermEnforcer.Details({pricing: address(pricing), hook: address(hook), handler: address(handler)}); - - LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); - caveats[0] = LoanManager.Caveat({enforcer: address(TE), terms: abi.encode(TEDetails)}); - - return newLoan( - NewLoanData(address(custody), caveats, abi.encode(loanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); + return _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 100, terms: terms}); } function testNewLoanERC721CollateralLessDebtThanOffered() public returns (LoanManager.Loan memory) { - Custodian custody = Custodian(LM.defaultCustodian()); - - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(hook), - handler: address(handler), - pricing: address(pricing), - pricingData: defaultPricingData, - handlerData: defaultHandlerData, - hookData: defaultHookData - }); - - selectedCollateral.push( - ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custody)) - }) - ); - - debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); - StrategistOriginator.Details memory loanDetails = StrategistOriginator.Details({ - conduit: address(lenderConduit), - custodian: address(custody), - issuer: lender.addr, - deadline: block.timestamp + 100, - offer: StrategistOriginator.Offer({ - salt: bytes32(0), - terms: terms, - collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), - debt: debt - }) - }); - debt[0].amount = 50; - - TermEnforcer TE = new TermEnforcer(); - - TermEnforcer.Details memory TEDetails = - TermEnforcer.Details({pricing: address(pricing), hook: address(hook), handler: address(handler)}); - - LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); - caveats[0] = LoanManager.Caveat({enforcer: address(TE), terms: abi.encode(TEDetails)}); - - return newLoan( - NewLoanData(address(custody), caveats, abi.encode(loanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); + // Custodian custody = Custodian(LM.defaultCustodian()); + + // LoanManager.Terms memory terms = LoanManager.Terms({ + // hook: address(hook), + // handler: address(handler), + // pricing: address(pricing), + // pricingData: defaultPricingData, + // handlerData: defaultHandlerData, + // hookData: defaultHookData + // }); + + // selectedCollateral.push( + // ConsiderationItem({ + // token: address(erc721s[0]), + // startAmount: 1, + // endAmount: 1, + // identifierOrCriteria: 1, + // itemType: ItemType.ERC721, + // recipient: payable(address(custody)) + // }) + // ); + + // debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); + // StrategistOriginator.Details memory loanDetails = StrategistOriginator.Details({ + // conduit: address(lenderConduit), + // custodian: address(custody), + // issuer: lender.addr, + // deadline: block.timestamp + 100, + // offer: StrategistOriginator.Offer({ + // salt: bytes32(0), + // terms: terms, + // collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), + // debt: debt + // }) + // }); + // debt[0].amount = 50; + + // TermEnforcer TE = new TermEnforcer(); + + // TermEnforcer.Details memory TEDetails = + // TermEnforcer.Details({pricing: address(pricing), hook: address(hook), handler: address(handler)}); + + // LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); + // caveats[0] = LoanManager.Caveat({enforcer: address(TE), terms: abi.encode(TEDetails)}); + + // return newLoan( + // NewLoanData(address(custody), caveats, abi.encode(loanDetails)), + // StrategistOriginator(SO), + // selectedCollateral + // ); } function testNewLoanRefinanceNew() public { - StrategistOriginator.Details memory loanDetails = _generateOriginationDetails( - ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custodian)) - }), - SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0}), - lender.addr - ); - - LoanManager.Loan memory loan = newLoan( - NewLoanData(address(loanDetails.custodian), new LoanManager.Caveat[](0), abi.encode(loanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); - - refinanceLoan( - loan, - abi.encode(BasePricing.Details({rate: (uint256(1e16) * 100) / (365 * 1 days), carryRate: 0})), - refinancer.addr - ); + // StrategistOriginator.Details memory loanDetails = _generateOriginationDetails( + // ConsiderationItem({ + // token: address(erc721s[0]), + // startAmount: 1, + // endAmount: 1, + // identifierOrCriteria: 1, + // itemType: ItemType.ERC721, + // recipient: payable(address(custodian)) + // }), + // SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0}), + // lender.addr + // ); + + // LoanManager.Loan memory loan = newLoan( + // NewLoanData(address(loanDetails.custodian), new LoanManager.Caveat[](0), abi.encode(loanDetails)), + // StrategistOriginator(SO), + // selectedCollateral + // ); + + // CaveatEnforcer.CaveatWithApproval memory lenderCaveat = CaveatEnforcer.CaveatWithApproval({ + // v: 0, + // r: bytes32(0), + // s: bytes32(0), + // caveat: CaveatEnforcer.Caveat({ + // enforcer: address(0), + // salt: bytes32(uint256(1)), + // deadline: block.timestamp + 1 days, + // data: abi.encode(uint256(0)) + // }) + // }); + + // // getLenderSignedCaveat(); + // refinanceLoan( + // loan, + // abi.encode(BasePricing.Details({rate: (uint256(1e16) * 100) / (365 * 1 days), carryRate: 0})), + // refinancer.addr, + // lenderCaveat, + // refinancer.addr + // ); } function testBuyNowPayLater() public { - ConsiderationItem[] memory want = new ConsiderationItem[](1); - want[0] = ConsiderationItem({ - token: address(erc20s[0]), - startAmount: 150, - endAmount: 150, - identifierOrCriteria: 0, - itemType: ItemType.ERC20, - recipient: payable(seller.addr) - }); - - //order 1, which lets is the seller, they have something that we can borrower againt (ERC721) - //order 2 which is the - - OfferItem[] memory sellingNFT = new OfferItem[](1); - sellingNFT[0] = OfferItem({ - identifierOrCriteria: 1, - token: address(erc721s[1]), - startAmount: 1, - endAmount: 1, - itemType: ItemType.ERC721 - }); - OrderParameters memory thingToSell = OrderParameters({ - offerer: seller.addr, - zone: address(0), - offer: sellingNFT, - consideration: want, - orderType: OrderType.FULL_OPEN, - startTime: block.timestamp, - endTime: block.timestamp + 150, - zoneHash: bytes32(0), - salt: 0, - conduitKey: bytes32(0), - totalOriginalConsiderationItems: 1 - }); - bytes32 sellingHash = consideration.getOrderHash(OrderParametersLib.toOrderComponents(thingToSell, 0)); - (bytes32 r, bytes32 s, uint8 v) = getSignatureComponents(consideration, seller.key, sellingHash); - - AdvancedOrder memory advThingToSell = AdvancedOrder({ - parameters: thingToSell, - numerator: 1, - denominator: 1, - signature: abi.encodePacked(r, s, v), - extraData: "" - }); - - Custodian custody = Custodian(LM.defaultCustodian()); - - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(hook), - handler: address(handler), - pricing: address(pricing), - pricingData: defaultPricingData, - handlerData: defaultHandlerData, - hookData: defaultHookData - }); - - selectedCollateral.push( - ConsiderationItem({ - token: address(erc721s[1]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custody)) - }) - ); - - debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); - StrategistOriginator.Details memory loanDetails = StrategistOriginator.Details({ - conduit: address(lenderConduit), - custodian: address(custody), - issuer: lender.addr, - deadline: block.timestamp + 100, - offer: StrategistOriginator.Offer({ - salt: bytes32(0), - terms: terms, - collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), - debt: debt - }) - }); - - TermEnforcer TE = new TermEnforcer(); - - TermEnforcer.Details memory TEDetails = - TermEnforcer.Details({pricing: address(pricing), hook: address(hook), handler: address(handler)}); - - LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); - caveats[0] = LoanManager.Caveat({enforcer: address(TE), terms: abi.encode(TEDetails)}); - - buyNowPayLater( - advThingToSell, - NewLoanData(address(custody), caveats, abi.encode(loanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); + // ConsiderationItem[] memory want = new ConsiderationItem[](1); + // want[0] = ConsiderationItem({ + // token: address(erc20s[0]), + // startAmount: 150, + // endAmount: 150, + // identifierOrCriteria: 0, + // itemType: ItemType.ERC20, + // recipient: payable(seller.addr) + // }); + + // //order 1, which lets is the seller, they have something that we can borrower againt (ERC721) + // //order 2 which is the + + // OfferItem[] memory sellingNFT = new OfferItem[](1); + // sellingNFT[0] = OfferItem({ + // identifierOrCriteria: 1, + // token: address(erc721s[1]), + // startAmount: 1, + // endAmount: 1, + // itemType: ItemType.ERC721 + // }); + // OrderParameters memory thingToSell = OrderParameters({ + // offerer: seller.addr, + // zone: address(0), + // offer: sellingNFT, + // consideration: want, + // orderType: OrderType.FULL_OPEN, + // startTime: block.timestamp, + // endTime: block.timestamp + 150, + // zoneHash: bytes32(0), + // salt: 0, + // conduitKey: bytes32(0), + // totalOriginalConsiderationItems: 1 + // }); + // bytes32 sellingHash = consideration.getOrderHash(OrderParametersLib.toOrderComponents(thingToSell, 0)); + // (bytes32 r, bytes32 s, uint8 v) = getSignatureComponents(consideration, seller.key, sellingHash); + + // AdvancedOrder memory advThingToSell = AdvancedOrder({ + // parameters: thingToSell, + // numerator: 1, + // denominator: 1, + // signature: abi.encodePacked(r, s, v), + // extraData: "" + // }); + + // Custodian custody = Custodian(LM.defaultCustodian()); + + // LoanManager.Terms memory terms = LoanManager.Terms({ + // hook: address(hook), + // handler: address(handler), + // pricing: address(pricing), + // pricingData: defaultPricingData, + // handlerData: defaultHandlerData, + // hookData: defaultHookData + // }); + + // selectedCollateral.push( + // ConsiderationItem({ + // token: address(erc721s[1]), + // startAmount: 1, + // endAmount: 1, + // identifierOrCriteria: 1, + // itemType: ItemType.ERC721, + // recipient: payable(address(custody)) + // }) + // ); + + // debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); + // StrategistOriginator.Details memory loanDetails = StrategistOriginator.Details({ + // conduit: address(lenderConduit), + // custodian: address(custody), + // issuer: lender.addr, + // deadline: block.timestamp + 100, + // offer: StrategistOriginator.Offer({ + // salt: bytes32(0), + // terms: terms, + // collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), + // debt: debt + // }) + // }); + + // TermEnforcer TE = new TermEnforcer(); + + // TermEnforcer.Details memory TEDetails = + // TermEnforcer.Details({pricing: address(pricing), hook: address(hook), handler: address(handler)}); + + // LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); + // caveats[0] = LoanManager.Caveat({enforcer: address(TE), terms: abi.encode(TEDetails)}); + + // buyNowPayLater( + // advThingToSell, + // NewLoanData(address(custody), caveats, abi.encode(loanDetails)), + // StrategistOriginator(SO), + // selectedCollateral + // ); } function testSettleLoan() public { - //default is 14 day term - LoanManager.Loan memory activeLoan = testNewLoanERC721CollateralDefaultTerms2(); - - skip(14 days); - - minimumReceived.push( - SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 600 ether, identifier: 0}) - ); - - (ReceivedItem[] memory settlementConsideration, address restricted) = - SettlementHandler(activeLoan.terms.handler).getSettlement(activeLoan); - - ConsiderationItem[] memory consider = new ConsiderationItem[]( - settlementConsideration.length - ); - uint256 i = 0; - for (; i < settlementConsideration.length;) { - consider[i].token = settlementConsideration[i].token; - consider[i].itemType = settlementConsideration[i].itemType; - consider[i].identifierOrCriteria = settlementConsideration[i].identifier; - consider[i].startAmount = settlementConsideration[i].amount; - //TODO: update this - consider[i].endAmount = settlementConsideration[i].amount; - consider[i].recipient = settlementConsideration[i].recipient; - unchecked { - ++i; - } - } - OfferItem[] memory repayOffering = new OfferItem[]( - activeLoan.collateral.length - ); - i = 0; - for (; i < activeLoan.collateral.length;) { - repayOffering[i] = OfferItem({ - itemType: activeLoan.collateral[i].itemType, - token: address(activeLoan.collateral[i].token), - identifierOrCriteria: activeLoan.collateral[i].identifier, - endAmount: activeLoan.collateral[i].itemType != ItemType.ERC721 ? activeLoan.collateral[i].amount : 1, - startAmount: activeLoan.collateral[i].itemType != ItemType.ERC721 ? activeLoan.collateral[i].amount : 1 - }); - unchecked { - ++i; - } - } - - OrderParameters memory op = _buildContractOrder(address(activeLoan.custodian), repayOffering, consider); - if (restricted == address(0)) { - AdvancedOrder memory settlementOrder = AdvancedOrder({ - numerator: 1, - denominator: 1, - parameters: op, - extraData: abi.encode(Actions.Settlement, activeLoan), - signature: "" - }); - - consideration.fulfillAdvancedOrder({ - advancedOrder: settlementOrder, - criteriaResolvers: new CriteriaResolver[](0), - fulfillerConduitKey: bytes32(0), - recipient: address(0) - }); - } + // //default is 14 day term + // LoanManager.Loan memory activeLoan = testNewLoanERC721CollateralDefaultTerms2(); + + // skip(14 days); + + // minimumReceived.push( + // SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 600 ether, identifier: 0}) + // ); + + // (ReceivedItem[] memory settlementConsideration, address restricted) = + // SettlementHandler(activeLoan.terms.handler).getSettlement(activeLoan); + + // ConsiderationItem[] memory consider = new ConsiderationItem[]( + // settlementConsideration.length + // ); + // uint256 i = 0; + // for (; i < settlementConsideration.length;) { + // consider[i].token = settlementConsideration[i].token; + // consider[i].itemType = settlementConsideration[i].itemType; + // consider[i].identifierOrCriteria = settlementConsideration[i].identifier; + // consider[i].startAmount = settlementConsideration[i].amount; + // //TODO: update this + // consider[i].endAmount = settlementConsideration[i].amount; + // consider[i].recipient = settlementConsideration[i].recipient; + // unchecked { + // ++i; + // } + // } + // OfferItem[] memory repayOffering = new OfferItem[]( + // activeLoan.collateral.length + // ); + // i = 0; + // for (; i < activeLoan.collateral.length;) { + // repayOffering[i] = OfferItem({ + // itemType: activeLoan.collateral[i].itemType, + // token: address(activeLoan.collateral[i].token), + // identifierOrCriteria: activeLoan.collateral[i].identifier, + // endAmount: activeLoan.collateral[i].itemType != ItemType.ERC721 ? activeLoan.collateral[i].amount : 1, + // startAmount: activeLoan.collateral[i].itemType != ItemType.ERC721 ? activeLoan.collateral[i].amount : 1 + // }); + // unchecked { + // ++i; + // } + // } + + // OrderParameters memory op = _buildContractOrder(address(activeLoan.custodian), repayOffering, consider); + // if (restricted == address(0)) { + // AdvancedOrder memory settlementOrder = AdvancedOrder({ + // numerator: 1, + // denominator: 1, + // parameters: op, + // extraData: abi.encode(Actions.Settlement, activeLoan), + // signature: "" + // }); + + // consideration.fulfillAdvancedOrder({ + // advancedOrder: settlementOrder, + // criteriaResolvers: new CriteriaResolver[](0), + // fulfillerConduitKey: bytes32(0), + // recipient: address(0) + // }); + // } } } diff --git a/test/integration-testing/TestRepayLoan.sol b/test/integration-testing/TestRepayLoan.sol index 069b56dd..a4438468 100644 --- a/test/integration-testing/TestRepayLoan.sol +++ b/test/integration-testing/TestRepayLoan.sol @@ -2,58 +2,58 @@ import "starport-test/StarPortTest.sol"; contract TestRepayLoan is StarPortTest { function testRepayLoan() public { - uint256 borrowAmount = 100; - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(hook), - handler: address(handler), - pricing: address(pricing), - pricingData: defaultPricingData, - handlerData: defaultHandlerData, - hookData: defaultHookData - }); + // uint256 borrowAmount = 100; + // LoanManager.Terms memory terms = LoanManager.Terms({ + // hook: address(hook), + // handler: address(handler), + // pricing: address(pricing), + // pricingData: defaultPricingData, + // handlerData: defaultHandlerData, + // hookData: defaultHookData + // }); - selectedCollateral.push( - ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custodian)) - }) - ); + // selectedCollateral.push( + // ConsiderationItem({ + // token: address(erc721s[0]), + // startAmount: 1, + // endAmount: 1, + // identifierOrCriteria: 1, + // itemType: ItemType.ERC721, + // recipient: payable(address(custodian)) + // }) + // ); - debt.push( - SpentItem({ - itemType: ItemType.ERC20, - token: address(erc20s[0]), - amount: borrowAmount, - identifier: 0 // 0 for ERC20 - }) - ); + // debt.push( + // SpentItem({ + // itemType: ItemType.ERC20, + // token: address(erc20s[0]), + // amount: borrowAmount, + // identifier: 0 // 0 for ERC20 + // }) + // ); - StrategistOriginator.Details memory loanDetails = StrategistOriginator.Details({ - conduit: address(lenderConduit), - custodian: address(custodian), - issuer: lender.addr, - deadline: block.timestamp + 100, - offer: StrategistOriginator.Offer({ - salt: bytes32(0), - terms: terms, - collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), - debt: debt - }) - }); + // StrategistOriginator.Details memory loanDetails = StrategistOriginator.Details({ + // conduit: address(lenderConduit), + // custodian: address(custodian), + // issuer: lender.addr, + // deadline: block.timestamp + 100, + // offer: StrategistOriginator.Offer({ + // salt: bytes32(0), + // terms: terms, + // collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), + // debt: debt + // }) + // }); - LoanManager.Loan memory activeLoan = newLoan( - NewLoanData(address(custodian), new LoanManager.Caveat[](0), abi.encode(loanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); - vm.startPrank(borrower.addr); - skip(10 days); - erc20s[0].approve(address(consideration), 375); - vm.stopPrank(); - _executeRepayLoan(activeLoan); + // LoanManager.Loan memory activeLoan = newLoan( + // NewLoanData(address(custodian), new LoanManager.Caveat[](0), abi.encode(loanDetails)), + // StrategistOriginator(SO), + // selectedCollateral + // ); + // vm.startPrank(borrower.addr); + // skip(10 days); + // erc20s[0].approve(address(consideration), 375); + // vm.stopPrank(); + // _executeRepayLoan(activeLoan); } } diff --git a/test/unit-testing/EnforcerTest.t.sol b/test/unit-testing/EnforcerTest.t.sol index b395dc63..747bb039 100644 --- a/test/unit-testing/EnforcerTest.t.sol +++ b/test/unit-testing/EnforcerTest.t.sol @@ -5,304 +5,304 @@ import {LibString} from "solady/src/utils/LibString.sol"; import "forge-std/console.sol"; contract EnforcerTest is StarPortTest { - function testTermEnforcerBasic() public { - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(hook), - handler: address(handler), - pricing: address(pricing), - pricingData: defaultPricingData, - handlerData: defaultHandlerData, - hookData: defaultHookData - }); - - uint256 initial721Balance = erc721s[0].balanceOf(borrower.addr); - assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); - - uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); - - TermEnforcer TE = new TermEnforcer(); - - TermEnforcer.Details memory TEDetails = - TermEnforcer.Details({pricing: address(pricing), hook: address(hook), handler: address(handler)}); - - LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); - caveats[0] = LoanManager.Caveat({enforcer: address(TE), terms: abi.encode(TEDetails)}); - - LoanManager.Loan memory loan = _createLoanWithCaveat({ - lender: lender.addr, - terms: terms, - collateralItem: ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custodian)) - }), - debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0}), - caveats: caveats - }); - - // LoanManager.Loan memory loan = - // _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 100, terms: terms}); - // - // assertTrue(erc721s[0].balanceOf(borrower.addr) < initial721Balance, "Borrower ERC721 was not sent out"); - // assertTrue(erc20s[0].balanceOf(borrower.addr) > initial20Balance, "Borrower did not receive ERC20"); - // - // uint256 loanId = loan.getId(); - // assertTrue(LM.active(loanId), "LoanId not in active state after a new loan"); - // skip(10 days); - // - // _repayLoan({borrower: borrower.addr, amount: 375, loan: loan}); - } - - function testRateEnforcerBasic() public { - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(hook), - handler: address(handler), - pricing: address(pricing), - pricingData: defaultPricingData, - handlerData: defaultHandlerData, - hookData: defaultHookData - }); - - uint256 initial721Balance = erc721s[0].balanceOf(borrower.addr); - assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); - - uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); - - FixedRateEnforcer RE = new FixedRateEnforcer(); - - FixedRateEnforcer.Details memory REDetails = FixedRateEnforcer.Details({ - maxRate: ((uint256(1e16) * 150) / (365 * 1 days)) * 2, - maxCarryRate: (uint256(1e16) * 10) * 2 - }); - - LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); - caveats[0] = LoanManager.Caveat({enforcer: address(RE), terms: abi.encode(REDetails)}); - - LoanManager.Loan memory loan = _createLoanWithCaveat({ - lender: lender.addr, - terms: terms, - collateralItem: ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custodian)) - }), - debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0}), - caveats: caveats - }); - } - - function testFailRateEnforcerMaxRate() public { - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(hook), - handler: address(handler), - pricing: address(pricing), - pricingData: defaultPricingData, - handlerData: defaultHandlerData, - hookData: defaultHookData - }); - - uint256 initial721Balance = erc721s[0].balanceOf(borrower.addr); - assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); - - uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); - - FixedRateEnforcer RE = new FixedRateEnforcer(); - - FixedRateEnforcer.Details memory REDetails = FixedRateEnforcer.Details({ - maxRate: (uint256(1e16) * 150) / (365 * 1 days), - maxCarryRate: (uint256(1e16) * 10) * 2 - }); // maxRate == defaultPricingData.carryRate - - LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); - caveats[0] = LoanManager.Caveat({enforcer: address(RE), terms: abi.encode(REDetails)}); - - LoanManager.Loan memory loan = _createLoanWithCaveat({ - lender: lender.addr, - terms: terms, - collateralItem: ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custodian)) - }), - debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0}), - caveats: caveats - }); - } - - function testFailRateEnforcerMaxCarryRate() public { - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(hook), - handler: address(handler), - pricing: address(pricing), - pricingData: defaultPricingData, - handlerData: defaultHandlerData, - hookData: defaultHookData - }); - - uint256 initial721Balance = erc721s[0].balanceOf(borrower.addr); - assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); - - uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); - - FixedRateEnforcer RE = new FixedRateEnforcer(); - - FixedRateEnforcer.Details memory REDetails = FixedRateEnforcer.Details({ - maxRate: ((uint256(1e16) * 150) / (365 * 1 days)) * 2, - maxCarryRate: (uint256(1e16) * 10) - }); // maxCarryRate == defaultPricingData.rate - - LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); - caveats[0] = LoanManager.Caveat({enforcer: address(RE), terms: abi.encode(REDetails)}); - - LoanManager.Loan memory loan = _createLoanWithCaveat({ - lender: lender.addr, - terms: terms, - collateralItem: ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custodian)) - }), - debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0}), - caveats: caveats - }); - } - - function testFailRateEnforcerMaxRateAndMaxCarryRate() public { - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(hook), - handler: address(handler), - pricing: address(pricing), - pricingData: defaultPricingData, - handlerData: defaultHandlerData, - hookData: defaultHookData - }); - - uint256 initial721Balance = erc721s[0].balanceOf(borrower.addr); - assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); - - uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); - - FixedRateEnforcer RE = new FixedRateEnforcer(); - - FixedRateEnforcer.Details memory REDetails = FixedRateEnforcer.Details({ - maxRate: (uint256(1e16) * 150) / (365 * 1 days), - maxCarryRate: (uint256(1e16) * 10) - }); // maxCarryRate == defaultPricingData.rate - - LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); - caveats[0] = LoanManager.Caveat({enforcer: address(RE), terms: abi.encode(REDetails)}); - - LoanManager.Loan memory loan = _createLoanWithCaveat({ - lender: lender.addr, - terms: terms, - collateralItem: ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custodian)) - }), - debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0}), - caveats: caveats - }); - } - - function testCollateralEnforcer() public { - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(hook), - handler: address(handler), - pricing: address(pricing), - pricingData: defaultPricingData, - handlerData: defaultHandlerData, - hookData: defaultHookData - }); - - uint256 initial721Balance = erc721s[0].balanceOf(borrower.addr); - assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); - - uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); - - CollateralEnforcer CE = new CollateralEnforcer(); - - SpentItem[] memory CECollateral = new SpentItem[](1); - - CECollateral[0] = SpentItem({itemType: ItemType.ERC721, token: address(erc721s[0]), amount: 1, identifier: 1}); - - CollateralEnforcer.Details memory CEDetails = - CollateralEnforcer.Details({collateral: CECollateral, isAny: true}); - - LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); - caveats[0] = LoanManager.Caveat({enforcer: address(CE), terms: abi.encode(CEDetails)}); - - LoanManager.Loan memory loan = _createLoanWithCaveat({ - lender: lender.addr, - terms: terms, - collateralItem: ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custodian)) - }), - debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0}), - caveats: caveats - }); - } - - function testFailCollateralEnforcerDifferentCollateral() public { - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(hook), - handler: address(handler), - pricing: address(pricing), - pricingData: defaultPricingData, - handlerData: defaultHandlerData, - hookData: defaultHookData - }); - - uint256 initial721Balance = erc721s[0].balanceOf(borrower.addr); - assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); - - uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); - - CollateralEnforcer CE = new CollateralEnforcer(); - - SpentItem[] memory CECollateral = new SpentItem[](1); - - CECollateral[0] = SpentItem({itemType: ItemType.ERC721, token: address(erc721s[1]), amount: 1, identifier: 1}); - - CollateralEnforcer.Details memory CEDetails = - CollateralEnforcer.Details({collateral: CECollateral, isAny: true}); - - LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); - caveats[0] = LoanManager.Caveat({enforcer: address(CE), terms: abi.encode(CEDetails)}); - - LoanManager.Loan memory loan = _createLoanWithCaveat({ - lender: lender.addr, - terms: terms, - collateralItem: ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custodian)) - }), - debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0}), - caveats: caveats - }); - } +// function testTermEnforcerBasic() public { +// LoanManager.Terms memory terms = LoanManager.Terms({ +// hook: address(hook), +// handler: address(handler), +// pricing: address(pricing), +// pricingData: defaultPricingData, +// handlerData: defaultHandlerData, +// hookData: defaultHookData +// }); + +// uint256 initial721Balance = erc721s[0].balanceOf(borrower.addr); +// assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); + +// uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); + +// TermEnforcer TE = new TermEnforcer(); + +// TermEnforcer.Details memory TEDetails = +// TermEnforcer.Details({pricing: address(pricing), hook: address(hook), handler: address(handler)}); + +// LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); +// caveats[0] = LoanManager.Caveat({enforcer: address(TE), terms: abi.encode(TEDetails)}); + +// LoanManager.Loan memory loan = _createLoanWithCaveat({ +// lender: lender.addr, +// terms: terms, +// collateralItem: ConsiderationItem({ +// token: address(erc721s[0]), +// startAmount: 1, +// endAmount: 1, +// identifierOrCriteria: 1, +// itemType: ItemType.ERC721, +// recipient: payable(address(custodian)) +// }), +// debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0}), +// caveats: caveats +// }); + +// // LoanManager.Loan memory loan = +// // _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 100, terms: terms}); +// // +// // assertTrue(erc721s[0].balanceOf(borrower.addr) < initial721Balance, "Borrower ERC721 was not sent out"); +// // assertTrue(erc20s[0].balanceOf(borrower.addr) > initial20Balance, "Borrower did not receive ERC20"); +// // +// // uint256 loanId = loan.getId(); +// // assertTrue(LM.active(loanId), "LoanId not in active state after a new loan"); +// // skip(10 days); +// // +// // _repayLoan({borrower: borrower.addr, amount: 375, loan: loan}); +// } + +// function testRateEnforcerBasic() public { +// LoanManager.Terms memory terms = LoanManager.Terms({ +// hook: address(hook), +// handler: address(handler), +// pricing: address(pricing), +// pricingData: defaultPricingData, +// handlerData: defaultHandlerData, +// hookData: defaultHookData +// }); + +// uint256 initial721Balance = erc721s[0].balanceOf(borrower.addr); +// assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); + +// uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); + +// FixedRateEnforcer RE = new FixedRateEnforcer(); + +// FixedRateEnforcer.Details memory REDetails = FixedRateEnforcer.Details({ +// maxRate: ((uint256(1e16) * 150) / (365 * 1 days)) * 2, +// maxCarryRate: (uint256(1e16) * 10) * 2 +// }); + +// LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); +// caveats[0] = LoanManager.Caveat({enforcer: address(RE), terms: abi.encode(REDetails)}); + +// LoanManager.Loan memory loan = _createLoanWithCaveat({ +// lender: lender.addr, +// terms: terms, +// collateralItem: ConsiderationItem({ +// token: address(erc721s[0]), +// startAmount: 1, +// endAmount: 1, +// identifierOrCriteria: 1, +// itemType: ItemType.ERC721, +// recipient: payable(address(custodian)) +// }), +// debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0}), +// caveats: caveats +// }); +// } + +// function testFailRateEnforcerMaxRate() public { +// LoanManager.Terms memory terms = LoanManager.Terms({ +// hook: address(hook), +// handler: address(handler), +// pricing: address(pricing), +// pricingData: defaultPricingData, +// handlerData: defaultHandlerData, +// hookData: defaultHookData +// }); + +// uint256 initial721Balance = erc721s[0].balanceOf(borrower.addr); +// assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); + +// uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); + +// FixedRateEnforcer RE = new FixedRateEnforcer(); + +// FixedRateEnforcer.Details memory REDetails = FixedRateEnforcer.Details({ +// maxRate: (uint256(1e16) * 150) / (365 * 1 days), +// maxCarryRate: (uint256(1e16) * 10) * 2 +// }); // maxRate == defaultPricingData.carryRate + +// LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); +// caveats[0] = LoanManager.Caveat({enforcer: address(RE), terms: abi.encode(REDetails)}); + +// LoanManager.Loan memory loan = _createLoanWithCaveat({ +// lender: lender.addr, +// terms: terms, +// collateralItem: ConsiderationItem({ +// token: address(erc721s[0]), +// startAmount: 1, +// endAmount: 1, +// identifierOrCriteria: 1, +// itemType: ItemType.ERC721, +// recipient: payable(address(custodian)) +// }), +// debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0}), +// caveats: caveats +// }); +// } + +// function testFailRateEnforcerMaxCarryRate() public { +// LoanManager.Terms memory terms = LoanManager.Terms({ +// hook: address(hook), +// handler: address(handler), +// pricing: address(pricing), +// pricingData: defaultPricingData, +// handlerData: defaultHandlerData, +// hookData: defaultHookData +// }); + +// uint256 initial721Balance = erc721s[0].balanceOf(borrower.addr); +// assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); + +// uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); + +// FixedRateEnforcer RE = new FixedRateEnforcer(); + +// FixedRateEnforcer.Details memory REDetails = FixedRateEnforcer.Details({ +// maxRate: ((uint256(1e16) * 150) / (365 * 1 days)) * 2, +// maxCarryRate: (uint256(1e16) * 10) +// }); // maxCarryRate == defaultPricingData.rate + +// LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); +// caveats[0] = LoanManager.Caveat({enforcer: address(RE), terms: abi.encode(REDetails)}); + +// LoanManager.Loan memory loan = _createLoanWithCaveat({ +// lender: lender.addr, +// terms: terms, +// collateralItem: ConsiderationItem({ +// token: address(erc721s[0]), +// startAmount: 1, +// endAmount: 1, +// identifierOrCriteria: 1, +// itemType: ItemType.ERC721, +// recipient: payable(address(custodian)) +// }), +// debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0}), +// caveats: caveats +// }); +// } + +// function testFailRateEnforcerMaxRateAndMaxCarryRate() public { +// LoanManager.Terms memory terms = LoanManager.Terms({ +// hook: address(hook), +// handler: address(handler), +// pricing: address(pricing), +// pricingData: defaultPricingData, +// handlerData: defaultHandlerData, +// hookData: defaultHookData +// }); + +// uint256 initial721Balance = erc721s[0].balanceOf(borrower.addr); +// assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); + +// uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); + +// FixedRateEnforcer RE = new FixedRateEnforcer(); + +// FixedRateEnforcer.Details memory REDetails = FixedRateEnforcer.Details({ +// maxRate: (uint256(1e16) * 150) / (365 * 1 days), +// maxCarryRate: (uint256(1e16) * 10) +// }); // maxCarryRate == defaultPricingData.rate + +// LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); +// caveats[0] = LoanManager.Caveat({enforcer: address(RE), terms: abi.encode(REDetails)}); + +// LoanManager.Loan memory loan = _createLoanWithCaveat({ +// lender: lender.addr, +// terms: terms, +// collateralItem: ConsiderationItem({ +// token: address(erc721s[0]), +// startAmount: 1, +// endAmount: 1, +// identifierOrCriteria: 1, +// itemType: ItemType.ERC721, +// recipient: payable(address(custodian)) +// }), +// debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0}), +// caveats: caveats +// }); +// } + +// function testCollateralEnforcer() public { +// LoanManager.Terms memory terms = LoanManager.Terms({ +// hook: address(hook), +// handler: address(handler), +// pricing: address(pricing), +// pricingData: defaultPricingData, +// handlerData: defaultHandlerData, +// hookData: defaultHookData +// }); + +// uint256 initial721Balance = erc721s[0].balanceOf(borrower.addr); +// assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); + +// uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); + +// CollateralEnforcer CE = new CollateralEnforcer(); + +// SpentItem[] memory CECollateral = new SpentItem[](1); + +// CECollateral[0] = SpentItem({itemType: ItemType.ERC721, token: address(erc721s[0]), amount: 1, identifier: 1}); + +// CollateralEnforcer.Details memory CEDetails = +// CollateralEnforcer.Details({collateral: CECollateral, isAny: true}); + +// LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); +// caveats[0] = LoanManager.Caveat({enforcer: address(CE), terms: abi.encode(CEDetails)}); + +// LoanManager.Loan memory loan = _createLoanWithCaveat({ +// lender: lender.addr, +// terms: terms, +// collateralItem: ConsiderationItem({ +// token: address(erc721s[0]), +// startAmount: 1, +// endAmount: 1, +// identifierOrCriteria: 1, +// itemType: ItemType.ERC721, +// recipient: payable(address(custodian)) +// }), +// debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0}), +// caveats: caveats +// }); +// } + +// function testFailCollateralEnforcerDifferentCollateral() public { +// LoanManager.Terms memory terms = LoanManager.Terms({ +// hook: address(hook), +// handler: address(handler), +// pricing: address(pricing), +// pricingData: defaultPricingData, +// handlerData: defaultHandlerData, +// hookData: defaultHookData +// }); + +// uint256 initial721Balance = erc721s[0].balanceOf(borrower.addr); +// assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); + +// uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); + +// CollateralEnforcer CE = new CollateralEnforcer(); + +// SpentItem[] memory CECollateral = new SpentItem[](1); + +// CECollateral[0] = SpentItem({itemType: ItemType.ERC721, token: address(erc721s[1]), amount: 1, identifier: 1}); + +// CollateralEnforcer.Details memory CEDetails = +// CollateralEnforcer.Details({collateral: CECollateral, isAny: true}); + +// LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); +// caveats[0] = LoanManager.Caveat({enforcer: address(CE), terms: abi.encode(CEDetails)}); + +// LoanManager.Loan memory loan = _createLoanWithCaveat({ +// lender: lender.addr, +// terms: terms, +// collateralItem: ConsiderationItem({ +// token: address(erc721s[0]), +// startAmount: 1, +// endAmount: 1, +// identifierOrCriteria: 1, +// itemType: ItemType.ERC721, +// recipient: payable(address(custodian)) +// }), +// debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0}), +// caveats: caveats +// }); +// } } diff --git a/test/unit-testing/TestCustodian.sol b/test/unit-testing/TestCustodian.sol index 584326ea..9bff8c5d 100644 --- a/test/unit-testing/TestCustodian.sol +++ b/test/unit-testing/TestCustodian.sol @@ -7,12 +7,13 @@ import {StarPortLib, Actions} from "starport-core/lib/StarPortLib.sol"; contract MockCustodian is Custodian { constructor(LoanManager LM_, ConsiderationInterface seaport_) Custodian(LM_, seaport_) {} - function custody( - ReceivedItem[] calldata consideration, - bytes32[] calldata orderHashes, - uint256 contractNonce, - bytes calldata context - ) external virtual override onlyLoanManager returns (bytes4 selector) { + function custody(LoanManager.Loan memory loan) + external + virtual + override + onlyLoanManager + returns (bytes4 selector) + { selector = Custodian.custody.selector; } } @@ -33,15 +34,7 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { erc20s[0].approve(address(lenderConduit), 100000); - StrategistOriginator.Details memory loanDetails = _generateOriginationDetails( - _getERC721Consideration(erc721s[0]), _getERC20SpentItem(erc20s[0], borrowAmount), lender.addr - ); - - LoanManager.Loan memory loan = newLoan( - NewLoanData(address(custodian), new LoanManager.Caveat[](0), abi.encode(loanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); + LoanManager.Loan memory loan = newLoanWithDefaultTerms(); Custodian(custodian).mint(loan); loan.toStorage(activeLoan); @@ -101,11 +94,7 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { ) ); vm.expectRevert(); - payable(address(custodian)).call{value: 1 ether}( - abi.encodeWithSelector( - Custodian.custody.selector, new ReceivedItem[](0), new bytes32[](0), uint256(0), new bytes(0) - ) - ); + payable(address(custodian)).call{value: 1 ether}(abi.encodeWithSelector(Custodian.custody.selector, activeLoan)); vm.expectRevert(); payable(address(custodian)).call{value: 1 ether}(abi.encodeWithSelector(Custodian.getSeaportMetadata.selector)); vm.expectRevert(); @@ -183,14 +172,15 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { custodian.generateOrder(address(this), new SpentItem[](0), new SpentItem[](0), new bytes(0)); } - function testSafeTransfer1155Receive() public { - erc1155s[0].mint(address(this), 1, 2); + // no longer applicable since we do not enter through Seaport + // function testSafeTransfer1155Receive() public { + // erc1155s[0].mint(address(this), 1, 2); - vm.expectRevert(abi.encodeWithSelector(Custodian.NotEnteredViaSeaport.selector)); - erc1155s[0].safeTransferFrom(address(this), address(custodian), 1, 1, new bytes(0)); - vm.store(address(seaport), bytes32(uint256(0)), bytes32(uint256(2))); - erc1155s[0].safeTransferFrom(address(this), address(custodian), 1, 1, new bytes(0)); - } + // vm.expectRevert(abi.encodeWithSelector(Custodian.NotEnteredViaSeaport.selector)); + // erc1155s[0].safeTransferFrom(address(this), address(custodian), 1, 1, new bytes(0)); + // vm.store(address(seaport), bytes32(uint256(0)), bytes32(uint256(2))); + // erc1155s[0].safeTransferFrom(address(this), address(custodian), 1, 1, new bytes(0)); + // } //TODO: make this test meaningful function testSeaportMetadata() public view { @@ -204,23 +194,20 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { function testCustodySelector() public { MockCustodian custodianMock = new MockCustodian(LM, seaport); vm.prank(address(custodianMock.LM())); - assert( - custodianMock.custody(new ReceivedItem[](0), new bytes32[](0), uint256(0), new bytes(0)) - == Custodian.custody.selector - ); + assert(custodianMock.custody(activeLoan) == Custodian.custody.selector); } function testDefaultCustodySelectorRevert() public { vm.prank(address(custodian.LM())); vm.expectRevert(abi.encodeWithSelector(Custodian.ImplementInChild.selector)); - custodian.custody(new ReceivedItem[](0), new bytes32[](0), uint256(0), new bytes(0)); + custodian.custody(activeLoan); } //TODO: add assertions function testGenerateOrderRepay() public { vm.prank(seaportAddr); custodian.generateOrder( - activeLoan.borrower, new SpentItem[](0), debt, abi.encode(Actions.Repayment, activeLoan) + activeLoan.borrower, new SpentItem[](0), activeDebt, abi.encode(Actions.Repayment, activeLoan) ); } //TODO: add assertions @@ -229,21 +216,20 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { vm.prank(activeLoan.borrower); custodian.setRepayApproval(address(this), true); vm.prank(seaportAddr); - custodian.generateOrder(address(this), new SpentItem[](0), debt, abi.encode(Actions.Repayment, activeLoan)); + custodian.generateOrder( + address(this), new SpentItem[](0), activeDebt, abi.encode(Actions.Repayment, activeLoan) + ); } //TODO: add assertions function testGenerateOrderRepayERC1155WithRevert() public { - //1155 - StrategistOriginator.Details memory loanDetails = _generateOriginationDetails( - _getERC1155Consideration(erc1155s[0]), _getERC20SpentItem(erc20s[0], borrowAmount), address(issuer) + // 1155 + LoanManager.Loan memory originationDetails = _generateOriginationDetails( + _getERC1155SpentItem(erc1155s[0]), _getERC20SpentItem(erc20s[0], borrowAmount), address(issuer) ); - LoanManager.Loan memory loan = newLoan( - NewLoanData(address(custodian), new LoanManager.Caveat[](0), abi.encode(loanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); + LoanManager.Loan memory loan = + newLoan(originationDetails, bytes32(uint256(2)), bytes32(uint256(2)), address(issuer)); loan.toStorage(activeLoan); vm.prank(seaportAddr); @@ -256,21 +242,18 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { new bytes(0) ); custodian.generateOrder( - activeLoan.borrower, new SpentItem[](0), debt, abi.encode(Actions.Repayment, activeLoan) + activeLoan.borrower, new SpentItem[](0), activeDebt, abi.encode(Actions.Repayment, activeLoan) ); } function testGenerateOrderRepayERC1155AndERC20AndNativeHandlerAuthorized() public { //1155 - StrategistOriginator.Details memory loanDetails = _generateOriginationDetails( - _getERC1155Consideration(erc1155s[0]), _getERC20SpentItem(erc20s[0], borrowAmount), address(issuer) + LoanManager.Loan memory originationDetails = _generateOriginationDetails( + _getERC1155SpentItem(erc1155s[0]), _getERC20SpentItem(erc20s[0], borrowAmount), address(issuer) ); - LoanManager.Loan memory loan = newLoan( - NewLoanData(address(custodian), new LoanManager.Caveat[](0), abi.encode(loanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); + LoanManager.Loan memory loan = + newLoan(originationDetails, bytes32(uint256(3)), bytes32(uint256(3)), address(issuer)); loan.toStorage(activeLoan); mockHookCall(activeLoan.terms.hook, false); @@ -278,104 +261,94 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { mockHandlerCall(activeLoan.terms.handler, new ReceivedItem[](0), address(activeLoan.terms.handler)); vm.prank(seaportAddr); custodian.generateOrder( - activeLoan.borrower, new SpentItem[](0), debt, abi.encode(Actions.Settlement, activeLoan) + activeLoan.borrower, new SpentItem[](0), activeDebt, abi.encode(Actions.Settlement, activeLoan) ); //ERC20 - loanDetails = _generateOriginationDetails( - _getERC20Consideration(erc20s[1]), _getERC20SpentItem(erc20s[0], borrowAmount), address(this) + originationDetails = _generateOriginationDetails( + _getERC20SpentItem(erc20s[1], borrowAmount + 1), _getERC20SpentItem(erc20s[0], borrowAmount), address(this) ); - loan = newLoan( - NewLoanData(address(custodian), new LoanManager.Caveat[](0), abi.encode(loanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); + loan = newLoan(originationDetails, bytes32(uint256(4)), bytes32(uint256(4)), address(this)); loan.toStorage(activeLoan); vm.prank(seaportAddr); custodian.generateOrder( - activeLoan.borrower, new SpentItem[](0), debt, abi.encode(Actions.Settlement, activeLoan) + activeLoan.borrower, new SpentItem[](0), activeDebt, abi.encode(Actions.Settlement, activeLoan) ); + // Native no longer supported unless we add conduti support //Native - loanDetails = _generateOriginationDetails( - _getNativeConsideration(), _getERC20SpentItem(erc20s[0], borrowAmount), lender.addr - ); + // loanDetails = _generateOriginationDetails( + // _getNativeConsideration(), _getERC20SpentItem(erc20s[0], borrowAmount), lender.addr + // ); - loan = newLoan( - NewLoanData(address(custodian), new LoanManager.Caveat[](0), abi.encode(loanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); + // loan = newLoan( + // NewLoanData(address(custodian), new LoanManager.Caveat[](0), abi.encode(loanDetails)), + // StrategistOriginator(SO), + // selectedCollateral + // ); - loan.toStorage(activeLoan); + // loan.toStorage(activeLoan); - vm.prank(seaportAddr); - custodian.generateOrder( - activeLoan.borrower, new SpentItem[](0), debt, abi.encode(Actions.Settlement, activeLoan) - ); + // vm.prank(seaportAddr); + // custodian.generateOrder( + // activeLoan.borrower, new SpentItem[](0), activeDebt, abi.encode(Actions.Settlement, activeLoan) + // ); } function testGenerateOrderRepayERC1155AndERC20AndNative() public { //1155 - StrategistOriginator.Details memory loanDetails = _generateOriginationDetails( - _getERC1155Consideration(erc1155s[0]), _getERC20SpentItem(erc20s[0], borrowAmount), address(issuer) + LoanManager.Loan memory originationDetails = _generateOriginationDetails( + _getERC1155SpentItem(erc1155s[0]), _getERC20SpentItem(erc20s[0], borrowAmount), address(issuer) ); - LoanManager.Loan memory loan = newLoan( - NewLoanData(address(custodian), new LoanManager.Caveat[](0), abi.encode(loanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); + LoanManager.Loan memory loan = + newLoan(originationDetails, bytes32(uint256(5)), bytes32(uint256(5)), address(issuer)); loan.toStorage(activeLoan); vm.prank(seaportAddr); custodian.generateOrder( - activeLoan.borrower, new SpentItem[](0), debt, abi.encode(Actions.Repayment, activeLoan) + activeLoan.borrower, new SpentItem[](0), activeDebt, abi.encode(Actions.Repayment, activeLoan) ); //ERC20 - loanDetails = _generateOriginationDetails( - _getERC20Consideration(erc20s[1]), _getERC20SpentItem(erc20s[0], borrowAmount), address(this) + originationDetails = _generateOriginationDetails( + _getERC20SpentItem(erc20s[1], borrowAmount + 1), _getERC20SpentItem(erc20s[0], borrowAmount), address(this) ); - loan = newLoan( - NewLoanData(address(custodian), new LoanManager.Caveat[](0), abi.encode(loanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); + loan = newLoan(originationDetails, bytes32(uint256(6)), bytes32(uint256(6)), address(this)); loan.toStorage(activeLoan); vm.prank(seaportAddr); custodian.generateOrder( - activeLoan.borrower, new SpentItem[](0), debt, abi.encode(Actions.Repayment, activeLoan) + activeLoan.borrower, new SpentItem[](0), activeDebt, abi.encode(Actions.Repayment, activeLoan) ); //Native - loanDetails = _generateOriginationDetails( - _getNativeConsideration(), _getERC20SpentItem(erc20s[0], borrowAmount), lender.addr - ); + // loanDetails = _generateOriginationDetails( + // _getNativeConsideration(), _getERC20SpentItem(erc20s[0], borrowAmount), lender.addr + // ); - loan = newLoan( - NewLoanData(address(custodian), new LoanManager.Caveat[](0), abi.encode(loanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); + // loan = newLoan( + // NewLoanData(address(custodian), new LoanManager.Caveat[](0), abi.encode(loanDetails)), + // StrategistOriginator(SO), + // selectedCollateral + // ); - loan.toStorage(activeLoan); + // loan.toStorage(activeLoan); - vm.prank(seaportAddr); - custodian.generateOrder( - activeLoan.borrower, new SpentItem[](0), debt, abi.encode(Actions.Repayment, activeLoan) - ); + // vm.prank(seaportAddr); + // custodian.generateOrder( + // activeLoan.borrower, new SpentItem[](0), activeDebt, abi.encode(Actions.Repayment, activeLoan) + // ); } function testGenerateOrderRepayNotBorrower() public { vm.prank(seaportAddr); vm.expectRevert(abi.encodeWithSelector(Custodian.InvalidRepayer.selector)); - custodian.generateOrder(alice, new SpentItem[](0), debt, abi.encode(Actions.Repayment, activeLoan)); + custodian.generateOrder(alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Repayment, activeLoan)); } function testGenerateOrderSettlement() public { @@ -385,7 +358,7 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { mockHandlerCall(activeLoan.terms.handler, new ReceivedItem[](0), address(0)); (SpentItem[] memory offer, ReceivedItem[] memory consideration) = - custodian.generateOrder(alice, new SpentItem[](0), debt, abi.encode(Actions.Settlement, activeLoan)); + custodian.generateOrder(alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Settlement, activeLoan)); vm.stopPrank(); @@ -399,7 +372,7 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { mockHandlerCall(activeLoan.terms.handler, new ReceivedItem[](0), address(activeLoan.terms.handler)); (SpentItem[] memory offer, ReceivedItem[] memory consideration) = - custodian.generateOrder(alice, new SpentItem[](0), debt, abi.encode(Actions.Settlement, activeLoan)); + custodian.generateOrder(alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Settlement, activeLoan)); vm.stopPrank(); @@ -412,7 +385,9 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { mockHandlerCall(activeLoan.terms.handler, new ReceivedItem[](0), alice); vm.expectRevert(abi.encodeWithSelector(Custodian.InvalidFulfiller.selector)); - custodian.generateOrder(borrower.addr, new SpentItem[](0), debt, abi.encode(Actions.Settlement, activeLoan)); + custodian.generateOrder( + borrower.addr, new SpentItem[](0), activeDebt, abi.encode(Actions.Settlement, activeLoan) + ); } function testGenerateOrderSettlementNoActiveLoan() public { @@ -422,7 +397,9 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { activeLoan.borrower = address(bob); vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidLoan.selector)); - custodian.generateOrder(borrower.addr, new SpentItem[](0), debt, abi.encode(Actions.Settlement, activeLoan)); + custodian.generateOrder( + borrower.addr, new SpentItem[](0), activeDebt, abi.encode(Actions.Settlement, activeLoan) + ); } //TODO: add assertions @@ -431,7 +408,7 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { bytes memory context = abi.encode(Actions.Repayment, activeLoan); (SpentItem[] memory offer, ReceivedItem[] memory consideration) = - custodian.generateOrder(activeLoan.borrower, new SpentItem[](0), debt, context); + custodian.generateOrder(activeLoan.borrower, new SpentItem[](0), activeDebt, context); custodian.ratifyOrder(offer, consideration, context, new bytes32[](0), 0); @@ -445,7 +422,7 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { mockHandlerCall(activeLoan.terms.handler, new ReceivedItem[](0), address(activeLoan.terms.handler)); mockHandlerExecuteFail(activeLoan.terms.handler); vm.expectRevert(abi.encodeWithSelector(Custodian.InvalidHandlerExecution.selector)); - custodian.generateOrder(alice, new SpentItem[](0), debt, context); + custodian.generateOrder(alice, new SpentItem[](0), activeDebt, context); vm.stopPrank(); } @@ -457,7 +434,7 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { mockHandlerCall(activeLoan.terms.handler, new ReceivedItem[](0), address(0)); (SpentItem[] memory expectedOffer, ReceivedItem[] memory expectedConsideration) = custodian.generateOrder( - activeLoan.borrower, new SpentItem[](0), debt, abi.encode(Actions.Repayment, activeLoan) + activeLoan.borrower, new SpentItem[](0), activeDebt, abi.encode(Actions.Repayment, activeLoan) ); mockHookCall(activeLoan.terms.hook, true); @@ -467,7 +444,7 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { activeLoan.borrower, activeLoan.borrower, new SpentItem[](0), - debt, + activeDebt, abi.encode(Actions.Repayment, activeLoan) ); @@ -482,7 +459,7 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { vm.expectRevert(); (SpentItem[] memory expectedOffer, ReceivedItem[] memory expectedConsideration) = custodian.generateOrder( - activeLoan.borrower, new SpentItem[](0), debt, abi.encode(Actions.Repayment, activeLoan) + activeLoan.borrower, new SpentItem[](0), activeDebt, abi.encode(Actions.Repayment, activeLoan) ); } @@ -497,7 +474,7 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { vm.expectRevert(); (SpentItem[] memory expectedOffer, ReceivedItem[] memory expectedConsideration) = custodian.generateOrder( - activeLoan.borrower, new SpentItem[](0), debt, abi.encode(Actions.Repayment, activeLoan) + activeLoan.borrower, new SpentItem[](0), activeDebt, abi.encode(Actions.Repayment, activeLoan) ); } @@ -507,8 +484,9 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { mockHookCall(activeLoan.terms.hook, false); mockHandlerCall(activeLoan.terms.handler, new ReceivedItem[](0), address(1)); vm.expectRevert(abi.encodeWithSelector(Custodian.InvalidFulfiller.selector)); - (SpentItem[] memory receivedOffer, ReceivedItem[] memory receivedConsideration) = - custodian.previewOrder(alice, alice, new SpentItem[](0), debt, abi.encode(Actions.Settlement, activeLoan)); + (SpentItem[] memory receivedOffer, ReceivedItem[] memory receivedConsideration) = custodian.previewOrder( + alice, alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Settlement, activeLoan) + ); } function testPreviewOrderSettlementInvalidRepayer() public { @@ -517,8 +495,9 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { mockHookCall(activeLoan.terms.hook, true); mockHandlerCall(activeLoan.terms.handler, new ReceivedItem[](0), address(0)); vm.expectRevert(abi.encodeWithSelector(Custodian.InvalidRepayer.selector)); - (SpentItem[] memory receivedOffer, ReceivedItem[] memory receivedCosideration) = - custodian.previewOrder(alice, bob, new SpentItem[](0), debt, abi.encode(Actions.Repayment, activeLoan)); + (SpentItem[] memory receivedOffer, ReceivedItem[] memory receivedCosideration) = custodian.previewOrder( + alice, bob, new SpentItem[](0), activeDebt, abi.encode(Actions.Repayment, activeLoan) + ); } function testPreviewOrderSettlement() public { @@ -528,13 +507,13 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { mockHandlerCall(activeLoan.terms.handler, new ReceivedItem[](0), address(0)); (SpentItem[] memory expectedOffer, ReceivedItem[] memory expectedConsideration) = - custodian.generateOrder(alice, new SpentItem[](0), debt, abi.encode(Actions.Settlement, activeLoan)); + custodian.generateOrder(alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Settlement, activeLoan)); mockHookCall(activeLoan.terms.hook, false); mockHandlerCall(activeLoan.terms.handler, new ReceivedItem[](0), address(0)); (SpentItem[] memory receivedOffer, ReceivedItem[] memory receivedCosideration) = custodian.previewOrder( - seaportAddr, alice, new SpentItem[](0), debt, abi.encode(Actions.Settlement, activeLoan) + seaportAddr, alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Settlement, activeLoan) ); _deepEq(receivedOffer, expectedOffer); @@ -547,7 +526,7 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { activeLoan.borrower = address(bob); vm.expectRevert(abi.encodeWithSelector(Custodian.InvalidLoan.selector)); (SpentItem[] memory receivedOffer, ReceivedItem[] memory receivedCosideration) = custodian.previewOrder( - seaportAddr, alice, new SpentItem[](0), debt, abi.encode(Actions.Settlement, activeLoan) + seaportAddr, alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Settlement, activeLoan) ); } @@ -556,10 +535,12 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { mockHookCall(activeLoan.terms.hook, true); vm.expectRevert(abi.encodeWithSelector(Custodian.InvalidAction.selector)); - custodian.generateOrder(alice, new SpentItem[](0), debt, abi.encode(Actions.Settlement, activeLoan)); + custodian.generateOrder(alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Settlement, activeLoan)); vm.expectRevert(abi.encodeWithSelector(Custodian.InvalidAction.selector)); - custodian.previewOrder(seaportAddr, alice, new SpentItem[](0), debt, abi.encode(Actions.Settlement, activeLoan)); + custodian.previewOrder( + seaportAddr, alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Settlement, activeLoan) + ); } function testInvalidActionRepayInActiveLoan() public { @@ -567,10 +548,12 @@ contract TestCustodian is StarPortTest, DeepEq, MockCall { mockHookCall(activeLoan.terms.hook, false); vm.expectRevert(abi.encodeWithSelector(Custodian.InvalidAction.selector)); - custodian.generateOrder(alice, new SpentItem[](0), debt, abi.encode(Actions.Repayment, activeLoan)); + custodian.generateOrder(alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Repayment, activeLoan)); vm.expectRevert(abi.encodeWithSelector(Custodian.InvalidAction.selector)); - custodian.previewOrder(seaportAddr, alice, new SpentItem[](0), debt, abi.encode(Actions.Repayment, activeLoan)); + custodian.previewOrder( + seaportAddr, alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Repayment, activeLoan) + ); } function testInvalidAction() public { @@ -578,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), debt, abi.encode(Actions.Origination, activeLoan)); + custodian.generateOrder(alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Origination, activeLoan)); vm.expectRevert(abi.encodeWithSelector(Custodian.InvalidAction.selector)); custodian.previewOrder( - seaportAddr, alice, new SpentItem[](0), debt, abi.encode(Actions.Origination, activeLoan) + seaportAddr, alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Origination, activeLoan) ); } } diff --git a/test/unit-testing/TestLoanManager.sol b/test/unit-testing/TestLoanManager.sol index 1e7c5465..a838bd1d 100644 --- a/test/unit-testing/TestLoanManager.sol +++ b/test/unit-testing/TestLoanManager.sol @@ -71,12 +71,13 @@ contract MockOriginator is StrategistOriginator, TokenReceiverInterface { contract MockCustodian is Custodian { constructor(LoanManager LM_, ConsiderationInterface seaport) Custodian(LM_, seaport) {} - function custody( - ReceivedItem[] calldata consideration, - bytes32[] calldata orderHashes, - uint256 contractNonce, - bytes calldata context - ) external virtual override onlyLoanManager returns (bytes4 selector) {} + function custody(LoanManager.Loan memory loan) + external + virtual + override + onlyLoanManager + returns (bytes4 selector) + {} } contract TestLoanManager is StarPortTest, DeepEq { @@ -92,19 +93,9 @@ contract TestLoanManager is StarPortTest, DeepEq { function setUp() public virtual override { super.setUp(); - - erc20s[0].approve(address(lenderConduit), 100000); - mockCustodian = new MockCustodian(LM, seaport); - StrategistOriginator.Details memory defaultLoanDetails = _generateOriginationDetails( - _getERC721Consideration(erc721s[0]), _getERC20SpentItem(erc20s[0], borrowAmount), lender.addr - ); - LoanManager.Loan memory loan = newLoan( - NewLoanData(address(custodian), new LoanManager.Caveat[](0), abi.encode(defaultLoanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); + LoanManager.Loan memory loan = newLoanWithDefaultTerms(); Custodian(custodian).mint(loan); loan.toStorage(activeLoan); @@ -119,45 +110,12 @@ contract TestLoanManager is StarPortTest, DeepEq { } function testSupportsInterface() public { - assertTrue(LM.supportsInterface(type(ContractOffererInterface).interfaceId)); - assertTrue(LM.supportsInterface(type(ERC721).interfaceId)); + assertTrue(LM.supportsInterface(bytes4(0x01ffc9a7))); + assertTrue(LM.supportsInterface(bytes4(0x80ac58cd))); assertTrue(LM.supportsInterface(bytes4(0x5b5e139f))); assertTrue(LM.supportsInterface(bytes4(0x01ffc9a7))); } - function testGenerateOrderNotSeaport() public { - vm.expectRevert(abi.encodeWithSelector(LoanManager.NotSeaport.selector)); - LM.generateOrder(address(this), new SpentItem[](0), new SpentItem[](0), new bytes(0)); - } - - function testGenerateOrder() public { - StrategistOriginator originator = new MockOriginator(LM, address(0), 0); - address seaport = address(LM.seaport()); - debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); - - SpentItem[] memory maxSpent = new SpentItem[](1); - maxSpent[0] = SpentItem({token: address(erc721s[0]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); - - // - LoanManager.Obligation memory O = LoanManager.Obligation({ - custodian: address(custodian), - borrower: borrower.addr, - debt: debt, - salt: bytes32(0), - details: "", - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(originator) - }); - - // OrderParameters memory op = _buildContractOrder(address(LM), new OfferItem[](0), selectedCollateral); - vm.startPrank(seaport); - (SpentItem[] memory offer, ReceivedItem[] memory consideration) = - LM.generateOrder(address(this), new SpentItem[](0), maxSpent, abi.encode(Actions.Origination, O)); - //TODO:: validate return data matches request - // assertEq(keccak256(abi.encode(consideration)), keccak256(abi.encode(maxSpent))); - } - function testCannotSettleUnlessValidCustodian() public { vm.expectRevert(abi.encodeWithSelector(LoanManager.NotLoanCustodian.selector)); LM.settle(activeLoan); @@ -188,936 +146,382 @@ contract TestLoanManager is StarPortTest, DeepEq { LM.tokenURI(uint256(0)); } - function testTransferFromFailFromSeaport() public { - vm.startPrank(address(LM.seaport())); + function testTransferFromFail() public { vm.expectRevert(abi.encodeWithSelector(LoanManager.CannotTransferLoans.selector)); LM.transferFrom(address(this), address(this), uint256(keccak256(abi.encode(activeLoan)))); vm.expectRevert(abi.encodeWithSelector(LoanManager.CannotTransferLoans.selector)); LM.safeTransferFrom(address(this), address(this), uint256(keccak256(abi.encode(activeLoan)))); vm.expectRevert(abi.encodeWithSelector(LoanManager.CannotTransferLoans.selector)); LM.safeTransferFrom(address(this), address(this), uint256(keccak256(abi.encode(activeLoan))), ""); - vm.stopPrank(); } function testNonDefaultCustodianCustodyCallFails() public { - LoanManager.Obligation memory obligation = LoanManager.Obligation({ - custodian: address(mockCustodian), - borrower: address(0), - debt: new SpentItem[](0), + CaveatEnforcer.CaveatWithApproval memory borrowerCaveat; + + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + loan.collateral[0].identifier = uint256(2); + loan.custodian = address(mockCustodian); + CaveatEnforcer.CaveatWithApproval memory lenderCaveat = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, salt: bytes32(0), - details: "", - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(SO) + enforcer: address(lenderEnforcer) }); - vm.prank(address(LM.seaport())); + vm.startPrank(loan.borrower); vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidCustodian.selector)); - LM.ratifyOrder( - new SpentItem[](0), - new ReceivedItem[](0), - abi.encode(Actions.Origination, obligation), - new bytes32[](0), - uint256(0) - ); + LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + vm.stopPrank(); } function testNonDefaultCustodianCustodyCallSuccess() public { - LoanManager.Obligation memory obligation = LoanManager.Obligation({ - custodian: address(mockCustodian), - borrower: address(0), - debt: new SpentItem[](0), + CaveatEnforcer.CaveatWithApproval memory borrowerCaveat; + + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + loan.collateral[0].identifier = uint256(2); + loan.custodian = address(mockCustodian); + CaveatEnforcer.CaveatWithApproval memory lenderCaveat = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, salt: bytes32(0), - details: "", - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(SO) + enforcer: address(lenderEnforcer) }); - + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); vm.mockCall( address(mockCustodian), - abi.encodeWithSelector( - Custodian.custody.selector, - new ReceivedItem[](0), - new bytes32[](0), - uint256(0), - abi.encode(Actions.Origination, obligation) - ), + abi.encodeWithSelector(Custodian.custody.selector, loan), abi.encode(bytes4(Custodian.custody.selector)) ); - vm.prank(address(LM.seaport())); - LM.ratifyOrder( - new SpentItem[](0), - new ReceivedItem[](0), - abi.encode(Actions.Origination, obligation), - new bytes32[](0), - uint256(0) - ); + vm.startPrank(loan.borrower); + LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + vm.stopPrank(); } - function testInvalidDebtLength() public { - LoanManager.Obligation memory obligation = LoanManager.Obligation({ - custodian: address(mockCustodian), - borrower: address(0), - debt: new SpentItem[](0), - salt: bytes32(0), - details: "", - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(SO) - }); - vm.prank(address(LM.seaport())); - vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidDebtLength.selector)); - LM.generateOrder( - address(this), new SpentItem[](0), new SpentItem[](0), abi.encode(Actions.Origination, obligation) - ); + function testInvalidTransferLengthDebt() public { + CaveatEnforcer.CaveatWithApproval memory borrowerCaveat; - vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidDebtLength.selector)); - LM.previewOrder( - address(seaport), - address(this), - new SpentItem[](0), - new SpentItem[](0), - abi.encode(Actions.Origination, obligation) - ); - } - - function testInvalidDebtType() public { - MockOriginator MO = new MockOriginator(LM, address(0), 0); - delete debt; - debt.push( - SpentItem({itemType: ItemType.ERC721_WITH_CRITERIA, token: address(erc721s[0]), amount: 100, identifier: 0}) - ); - LoanManager.Obligation memory obligation = LoanManager.Obligation({ - custodian: address(mockCustodian), - borrower: address(0), - debt: debt, + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + loan.collateral[0].identifier = uint256(2); + delete loan.debt; + CaveatEnforcer.CaveatWithApproval memory lenderCaveat = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, salt: bytes32(0), - details: "", - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(MO) + enforcer: address(lenderEnforcer) }); - vm.prank(address(LM.seaport())); - vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidDebtType.selector)); - SpentItem[] memory maxSpent = new SpentItem[](1); - maxSpent[0] = SpentItem({token: address(erc721s[0]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); - LM.generateOrder(address(this), new SpentItem[](0), maxSpent, abi.encode(Actions.Origination, obligation)); - - vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidDebtType.selector)); - LM.previewOrder( - address(seaport), address(this), new SpentItem[](0), maxSpent, abi.encode(Actions.Origination, obligation) - ); + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + vm.startPrank(loan.borrower); + vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidTransferLength.selector)); + LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + vm.stopPrank(); } - //TODO: make this test meaningful - function testSeaportMetadata() public view { - LM.getSeaportMetadata(); - } + function testInvalidAmountDebt() public { + CaveatEnforcer.CaveatWithApproval memory borrowerCaveat; - function testInvalidMaximumSpentEmpty() public { - LoanManager.Obligation memory obligation = LoanManager.Obligation({ - custodian: address(mockCustodian), - borrower: address(0), - debt: debt, + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + loan.collateral[0].identifier = uint256(2); + loan.debt[0].amount = 0; + CaveatEnforcer.CaveatWithApproval memory lenderCaveat = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, salt: bytes32(0), - details: "", - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(SO) + enforcer: address(lenderEnforcer) }); - vm.prank(address(LM.seaport())); - vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidMaximumSpentEmpty.selector)); - LM.generateOrder( - address(this), new SpentItem[](0), new SpentItem[](0), abi.encode(Actions.Origination, obligation) - ); - vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidMaximumSpentEmpty.selector)); - LM.previewOrder( - address(seaport), - address(this), - new SpentItem[](0), - new SpentItem[](0), - abi.encode(Actions.Origination, obligation) - ); - } - - function testDefaultFeeRake() public { - assertEq(LM.defaultFeeRake(), 0); - address feeReceiver = address(20); - LM.setFeeData(feeReceiver, 1e17); //10% fees - - StrategistOriginator.Details memory defaultLoanDetails = _generateOriginationDetails( - _getERC721Consideration(erc721s[0], uint256(2)), _getERC20SpentItem(erc20s[0], borrowAmount), lender.addr - ); - - LoanManager.Loan memory loan = newLoan( - NewLoanData(address(custodian), new LoanManager.Caveat[](0), abi.encode(defaultLoanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); - assertEq(erc20s[0].balanceOf(feeReceiver), debt[0].amount * 1e17 / 1e18, "fee receiver not paid properly"); - } - - function testOverrideFeeRake() public { - assertEq(LM.defaultFeeRake(), 0); - address feeReceiver = address(20); - LM.setFeeData(feeReceiver, 1e17); //10% fees - LM.setFeeOverride(debt[0].token, 0); //0% fees - - StrategistOriginator.Details memory defaultLoanDetails = _generateOriginationDetails( - _getERC721Consideration(erc721s[0], uint256(2)), _getERC20SpentItem(erc20s[0], borrowAmount), lender.addr - ); - - LoanManager.Loan memory loan = newLoan( - NewLoanData(address(custodian), new LoanManager.Caveat[](0), abi.encode(defaultLoanDetails)), - StrategistOriginator(SO), - selectedCollateral - ); - assertEq(erc20s[0].balanceOf(feeReceiver), 0, "fee receiver not paid properly"); + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + vm.startPrank(loan.borrower); + vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidItemAmount.selector)); + LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + vm.stopPrank(); } - function testCaveatEnforcerInvalidOrigination() public { - Originator originator = new MockOriginator(LM, address(0), 0); - address seaport = address(LM.seaport()); - debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); - - SpentItem[] memory maxSpent = new SpentItem[](1); - maxSpent[0] = SpentItem({token: address(erc721s[0]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); + function testInvalidAmountCollateral() public { + CaveatEnforcer.CaveatWithApproval memory borrowerCaveat; - TermEnforcer TE = new TermEnforcer(); - - LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); - caveats[0] = LoanManager.Caveat({enforcer: address(TE), terms: ""}); - LoanManager.Obligation memory O = LoanManager.Obligation({ - custodian: address(custodian), - borrower: borrower.addr, - debt: debt, + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + loan.collateral[0].identifier = uint256(2); + loan.collateral[0].amount = 0; + loan.debt[0].amount = 0; + CaveatEnforcer.CaveatWithApproval memory lenderCaveat = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, salt: bytes32(0), - details: "", - approval: "", - caveats: caveats, - originator: address(originator) - }); - - LoanManager.Loan memory mockLoan = LoanManager.Loan({ - start: block.timestamp, - borrower: O.borrower, - collateral: maxSpent, - issuer: O.originator, - custodian: O.custodian, - debt: debt, - originator: O.originator, - terms: LoanManager.Terms({ - hook: address(0), - hookData: new bytes(0), - pricing: address(0), - pricingData: new bytes(0), - handler: address(0), - handlerData: new bytes(0) - }) + enforcer: address(lenderEnforcer) }); - vm.mockCall( - address(TE), - abi.encodeWithSelector(TermEnforcer.enforceCaveat.selector, bytes(""), mockLoan), - abi.encode(false) - ); - vm.startPrank(seaport); - vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidOrigination.selector)); - LM.generateOrder(address(this), new SpentItem[](0), maxSpent, abi.encode(Actions.Origination, O)); + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + vm.startPrank(loan.borrower); + vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidItemAmount.selector)); + LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + vm.stopPrank(); } - function testGenerateOrderInvalidAction() public { - Originator originator = new MockOriginator(LM, address(0), 0); - address seaport = address(LM.seaport()); - debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); + function testInvalidItemType() public { + CaveatEnforcer.CaveatWithApproval memory borrowerCaveat; - SpentItem[] memory maxSpent = new SpentItem[](1); - maxSpent[0] = SpentItem({token: address(erc721s[0]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); - - // - LoanManager.Obligation memory O = LoanManager.Obligation({ - custodian: address(custodian), - borrower: borrower.addr, - debt: debt, + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + loan.collateral[0].itemType = ItemType.ERC721_WITH_CRITERIA; + loan.debt[0].token = address(0); + CaveatEnforcer.CaveatWithApproval memory lenderCaveat = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, salt: bytes32(0), - details: "", - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(originator) + enforcer: address(lenderEnforcer) }); + _setApprovalsForSpentItems(loan.borrower, loan.collateral); - vm.startPrank(seaport); - vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidAction.selector)); - LM.generateOrder(address(this), new SpentItem[](0), maxSpent, abi.encode(Actions.Repayment, O)); + vm.startPrank(loan.borrower); + vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidItemType.selector)); + LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + vm.stopPrank(); } - function testPreviewOrderInvalidAction() public { - Originator originator = new MockOriginator(LM, address(0), 0); - address seaport = address(LM.seaport()); + function testTokenNoCodeDebt() public { + CaveatEnforcer.CaveatWithApproval memory borrowerCaveat; - SpentItem[] memory maxSpent = new SpentItem[](1); - maxSpent[0] = SpentItem({token: address(erc721s[0]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); - - LoanManager.Obligation memory O = LoanManager.Obligation({ - custodian: address(custodian), - borrower: borrower.addr, - debt: debt, + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + loan.collateral[0].identifier = uint256(2); + loan.debt[0].token = address(0); + CaveatEnforcer.CaveatWithApproval memory lenderCaveat = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, salt: bytes32(0), - details: "", - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(originator) + enforcer: address(lenderEnforcer) }); + _setApprovalsForSpentItems(loan.borrower, loan.collateral); - vm.startPrank(seaport); - vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidAction.selector)); - LM.previewOrder(seaport, address(this), new SpentItem[](0), maxSpent, abi.encode(Actions.Repayment, O)); + vm.startPrank(loan.borrower); + vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidItemTokenNoCode.selector)); + LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + vm.stopPrank(); } - function testPreviewOrderOriginationWithNoCaveatsSetNotBorrowerNoFee() public { - Originator originator = new MockOriginator(LM, address(0), 0); - address seaport = address(LM.seaport()); - delete debt; - debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); + function testTokenNoCodeCollateral() public { + CaveatEnforcer.CaveatWithApproval memory borrowerCaveat; - SpentItem[] memory maxSpent = new SpentItem[](1); - maxSpent[0] = SpentItem({token: address(erc721s[0]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); - - LoanManager.Obligation memory O = LoanManager.Obligation({ - custodian: address(custodian), - borrower: borrower.addr, - debt: debt, + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + loan.collateral[0].token = address(0); + loan.collateral[0].identifier = uint256(2); + loan.collateral[0].amount = 0; + loan.debt[0].amount = 0; + CaveatEnforcer.CaveatWithApproval memory lenderCaveat = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, salt: bytes32(0), - details: "", - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(originator) + enforcer: address(lenderEnforcer) }); - - bytes32 caveatHash = - keccak256(LM.encodeWithSaltAndBorrowerCounter(O.borrower, O.salt, keccak256(abi.encode(O.caveats)))); - ReceivedItem[] memory expectedConsider = new ReceivedItem[](maxSpent.length); - for (uint256 i; i < maxSpent.length; i++) { - expectedConsider[i] = ReceivedItem({ - itemType: maxSpent[i].itemType, - token: maxSpent[i].token, - identifier: maxSpent[i].identifier, - amount: maxSpent[i].amount, - recipient: payable(O.custodian) - }); - } - SpentItem[] memory expectedOffer = new SpentItem[](2); - expectedOffer[0] = debt[0]; - expectedOffer[1] = - SpentItem({itemType: ItemType.ERC721, token: address(LM), identifier: uint256(caveatHash), amount: 1}); - (SpentItem[] memory offer, ReceivedItem[] memory consider) = - LM.previewOrder(seaport, address(this), new SpentItem[](0), maxSpent, abi.encode(Actions.Origination, O)); - _deepEq(offer, expectedOffer); - _deepEq(consider, expectedConsider); + // _setApprovalsForSpentItems(loan.borrower, loan.collateral); + // _setApprovalsForSpentItems(loan.issuer, loan.debt); + vm.startPrank(loan.borrower); + vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidItemTokenNoCode.selector)); + LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + vm.stopPrank(); } - function testPreviewOrderOriginationWithNoCaveatsSetNotBorrowerFeeOn() public { - Originator originator = new MockOriginator(LM, address(0), 0); - address seaport = address(LM.seaport()); - LM.setFeeData(address(20), 1e17); //10% fees - delete debt; - debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); - SpentItem[] memory maxSpent = new SpentItem[](1); - maxSpent[0] = SpentItem({token: address(erc721s[0]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); + function testInvalidAmountCollateral721() public { + CaveatEnforcer.CaveatWithApproval memory borrowerCaveat; - LoanManager.Obligation memory O = LoanManager.Obligation({ - custodian: address(custodian), - borrower: borrower.addr, - debt: debt, + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + loan.collateral[0].identifier = uint256(2); + loan.collateral[0].amount = uint256(2); + loan.debt[0].amount = 0; + CaveatEnforcer.CaveatWithApproval memory lenderCaveat = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, salt: bytes32(0), - details: "", - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(originator) + enforcer: address(lenderEnforcer) }); - - bytes32 caveatHash = - keccak256(LM.encodeWithSaltAndBorrowerCounter(O.borrower, O.salt, keccak256(abi.encode(O.caveats)))); - ReceivedItem[] memory expectedConsider = new ReceivedItem[](maxSpent.length); - for (uint256 i; i < maxSpent.length; i++) { - expectedConsider[i] = ReceivedItem({ - itemType: maxSpent[i].itemType, - token: maxSpent[i].token, - identifier: maxSpent[i].identifier, - amount: maxSpent[i].amount, - recipient: payable(O.custodian) - }); - } - SpentItem[] memory expectedOffer = new SpentItem[](2); - expectedOffer[0] = debt[0]; - expectedOffer[0].amount = debt[0].amount - debt[0].amount.mulDiv(1e17, 1e18); - expectedOffer[1] = - SpentItem({itemType: ItemType.ERC721, token: address(LM), identifier: uint256(caveatHash), amount: 1}); - (SpentItem[] memory offer, ReceivedItem[] memory consider) = - LM.previewOrder(seaport, address(this), new SpentItem[](0), maxSpent, abi.encode(Actions.Origination, O)); - _deepEq(offer, expectedOffer); - _deepEq(consider, expectedConsider); + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + vm.startPrank(loan.borrower); + vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidItemAmount.selector)); + LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + vm.stopPrank(); } - function testPreviewOrderOriginationWithNoCaveatsSetAsBorrowerNoFee() public { - Originator originator = new MockOriginator(LM, address(0), 0); - address seaport = address(LM.seaport()); - console.log(LM.feeTo()); - delete debt; - debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); - SpentItem[] memory maxSpent = new SpentItem[](1); - maxSpent[0] = SpentItem({token: address(erc721s[0]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); + function testInvalidTransferLengthCollateral() public { + CaveatEnforcer.CaveatWithApproval memory borrowerCaveat; - LoanManager.Obligation memory O = LoanManager.Obligation({ - custodian: address(custodian), - borrower: borrower.addr, - debt: debt, + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + delete loan.collateral; + CaveatEnforcer.CaveatWithApproval memory lenderCaveat = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, salt: bytes32(0), - details: "", - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(originator) + enforcer: address(lenderEnforcer) }); - - ReceivedItem[] memory expectedConsider = new ReceivedItem[](maxSpent.length); - for (uint256 i; i < maxSpent.length; i++) { - expectedConsider[i] = ReceivedItem({ - itemType: maxSpent[i].itemType, - token: maxSpent[i].token, - identifier: maxSpent[i].identifier, - amount: maxSpent[i].amount, - recipient: payable(O.custodian) - }); - } - SpentItem[] memory expectedOffer = new SpentItem[](0); - (SpentItem[] memory offer, ReceivedItem[] memory consider) = - LM.previewOrder(seaport, borrower.addr, new SpentItem[](0), maxSpent, abi.encode(Actions.Origination, O)); - _deepEq(offer, expectedOffer); - _deepEq(consider, expectedConsider); + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + vm.startPrank(loan.borrower); + vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidTransferLength.selector)); + LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + vm.stopPrank(); } - function testPreviewOrderOriginationWithNoCaveatsSetAsBorrowerFeeOn() public { - Originator originator = new MockOriginator(LM, address(0), 0); - address seaport = address(LM.seaport()); - LM.setFeeData(address(20), 1e17); //10% fees - delete debt; - debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); - - SpentItem[] memory maxSpent = new SpentItem[](1); - maxSpent[0] = SpentItem({token: address(erc721s[0]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); - - LoanManager.Obligation memory O = LoanManager.Obligation({ - custodian: address(custodian), - borrower: borrower.addr, - debt: debt, - salt: bytes32(0), - details: "", - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(originator) - }); - ReceivedItem[] memory expectedConsider = new ReceivedItem[](maxSpent.length); - for (uint256 i; i < maxSpent.length; i++) { - expectedConsider[i] = ReceivedItem({ - itemType: maxSpent[i].itemType, - token: maxSpent[i].token, - identifier: maxSpent[i].identifier, - amount: maxSpent[i].amount, - recipient: payable(O.custodian) - }); - } - SpentItem[] memory expectedOffer = new SpentItem[](1); - expectedOffer[0] = debt[0]; - expectedOffer[0].amount = debt[0].amount - debt[0].amount.mulDiv(1e17, 1e18); - (SpentItem[] memory offer, ReceivedItem[] memory consider) = - LM.previewOrder(seaport, borrower.addr, new SpentItem[](0), maxSpent, abi.encode(Actions.Origination, O)); - _deepEq(offer, expectedOffer); - _deepEq(consider, expectedConsider); - } + function testDefaultFeeRake() public { + assertEq(LM.defaultFeeRake(), 0); + address feeReceiver = address(20); + LM.setFeeData(feeReceiver, 1e17); //10% fees - function testPreviewOrderRefinanceAsRefinancerFeeOn() public { - Originator originator = new MockOriginator(LM, address(0), 0); - address seaport = address(LM.seaport()); - LM.setFeeData(address(20), 1e17); //10% fees - - ReceivedItem[] memory expectedConsideration = new ReceivedItem[](1); - for (uint256 i; i < debt.length; i++) { - expectedConsideration[i] = ReceivedItem({ - itemType: debt[i].itemType, - token: debt[i].token, - identifier: debt[i].identifier, - amount: debt[i].amount, - recipient: payable(activeLoan.issuer) - }); - } - (SpentItem[] memory offer, ReceivedItem[] memory originationConsideration) = LM.previewOrder( - seaport, refinancer.addr, new SpentItem[](0), new SpentItem[](0), abi.encode(Actions.Refinance, activeLoan) + LoanManager.Loan memory originationDetails = _generateOriginationDetails( + _getERC721SpentItem(erc721s[0], uint256(2)), _getERC20SpentItem(erc20s[0], borrowAmount), lender.addr ); - _deepEq(originationConsideration, expectedConsideration); - } - function testPreviewOrderRefinanceAsRefinancerFeeOff() public { - Originator originator = new MockOriginator(LM, address(0), 0); - address seaport = address(LM.seaport()); - - ReceivedItem[] memory expectedConsideration = new ReceivedItem[](1); - for (uint256 i; i < debt.length; i++) { - expectedConsideration[i] = ReceivedItem({ - itemType: debt[i].itemType, - token: debt[i].token, - identifier: debt[i].identifier, - amount: debt[i].amount, - recipient: payable(activeLoan.issuer) - }); - } - (SpentItem[] memory offer, ReceivedItem[] memory originationConsideration) = LM.previewOrder( - seaport, refinancer.addr, new SpentItem[](0), new SpentItem[](0), abi.encode(Actions.Refinance, activeLoan) - ); - _deepEq(originationConsideration, expectedConsideration); + LoanManager.Loan memory loan = + newLoan(originationDetails, bytes32(bytes32(msg.sig)), bytes32(bytes32(msg.sig)), lender.addr); + assertEq(erc20s[0].balanceOf(feeReceiver), loan.debt[0].amount * 1e17 / 1e18, "fee receiver not paid properly"); } - function testRefinanceNoRefinanceConsideration() public { - Originator originator = new MockOriginator(LM, address(0), 0); - address seaport = address(LM.seaport()); - bytes memory newPricingData = - abi.encode(BasePricing.Details({rate: (uint256(1e16) * 100) / (365 * 1 days), carryRate: 0})); + function testOverrideFeeRake() public { + assertEq(LM.defaultFeeRake(), 0); + address feeReceiver = address(20); + LM.setFeeData(feeReceiver, 1e17); //10% fees + LM.setFeeOverride(address(erc20s[0]), 0); //0% fees - vm.mockCall( - address(activeLoan.terms.pricing), - abi.encodeWithSelector(Pricing.isValidRefinance.selector, activeLoan, newPricingData, refinancer.addr), - abi.encode(new ReceivedItem[](0), new ReceivedItem[](0), new ReceivedItem[](0)) - ); - vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidNoRefinanceConsideration.selector)); - LM.previewOrder( - seaport, - refinancer.addr, - new SpentItem[](0), - new SpentItem[](0), - abi.encode(Actions.Refinance, activeLoan, newPricingData) + LoanManager.Loan memory originationDetails = _generateOriginationDetails( + _getERC721SpentItem(erc721s[0], uint256(2)), _getERC20SpentItem(erc20s[0], borrowAmount), lender.addr ); - vm.prank(address(LM.seaport())); - vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidNoRefinanceConsideration.selector)); - LM.generateOrder( - refinancer.addr, - new SpentItem[](0), - new SpentItem[](0), - abi.encode(Actions.Refinance, activeLoan, newPricingData) - ); - } - function testExoticDebtWithNoCaveatsNotAsBorrower() public { - Originator originator = new MockOriginator(LM, address(0), 0); - address seaport = address(LM.seaport()); - - SpentItem[] memory exoticDebt = new SpentItem[](2); - exoticDebt[0] = SpentItem({token: address(erc1155s[1]), amount: 1, identifier: 1, itemType: ItemType.ERC1155}); - exoticDebt[1] = SpentItem({token: address(erc721s[2]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); + newLoan(originationDetails, bytes32(bytes32(msg.sig)), bytes32(bytes32(msg.sig)), lender.addr); + assertEq(erc20s[0].balanceOf(feeReceiver), 0, "fee receiver not paid properly"); + } - SpentItem[] memory maxSpent = new SpentItem[](1); - maxSpent[0] = SpentItem({token: address(erc721s[0]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); - StrategistOriginator.Details memory OD; - OD.issuer = lender.addr; - OD.conduit = lenderConduit; - vm.prank(lender.addr); - conduitController.updateChannel(lenderConduit, address(originator), true); - - LoanManager.Obligation memory O = LoanManager.Obligation({ - custodian: address(custodian), - borrower: borrower.addr, - debt: exoticDebt, + // needs modification to work with the new origination flow (unsure if it needs to be elimianted all together) + function testCaveatEnforcerRevert() public { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + CaveatEnforcer.CaveatWithApproval memory borrowerEnforcer; + CaveatEnforcer.CaveatWithApproval memory lenderEnforcer = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, salt: bytes32(0), - details: abi.encode(OD), - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(originator) + enforcer: address(lenderEnforcer) }); - - bytes32 caveatHash = - keccak256(LM.encodeWithSaltAndBorrowerCounter(O.borrower, O.salt, keccak256(abi.encode(O.caveats)))); - ReceivedItem[] memory expectedConsider = new ReceivedItem[](maxSpent.length); - for (uint256 i; i < maxSpent.length; i++) { - expectedConsider[i] = ReceivedItem({ - itemType: maxSpent[i].itemType, - token: maxSpent[i].token, - identifier: maxSpent[i].identifier, - amount: maxSpent[i].amount, - recipient: payable(O.custodian) - }); - } - SpentItem[] memory expectedOffer = new SpentItem[](3); - expectedOffer[0] = exoticDebt[0]; - expectedOffer[1] = exoticDebt[1]; - expectedOffer[2] = - SpentItem({itemType: ItemType.ERC721, token: address(LM), identifier: uint256(caveatHash), amount: 1}); - (SpentItem[] memory previewOffer, ReceivedItem[] memory previewConsider) = LM.previewOrder( - address(LM.seaport()), address(this), new SpentItem[](0), maxSpent, abi.encode(Actions.Origination, O) - ); - vm.store(address(seaport), bytes32(uint256(0)), bytes32(uint256(2))); - vm.prank(address(LM.seaport())); - (SpentItem[] memory offer, ReceivedItem[] memory consider) = - LM.generateOrder(address(this), new SpentItem[](0), maxSpent, abi.encode(Actions.Origination, O)); - _deepEq(offer, expectedOffer); - _deepEq(offer, previewOffer); - _deepEq(consider, previewConsider); - _deepEq(consider, expectedConsider); + loan.custodian = address(mockCustodian); + vm.expectRevert(); + LM.originate(new ConduitTransfer[](0), borrowerEnforcer, lenderEnforcer, loan); } - function testExoticDebtWithNoCaveatsAsBorrower() public { - Originator originator = new MockOriginator(LM, address(0), 0); - address seaport = address(LM.seaport()); + // needs modification to work with the new origination flow (unsure if it needs to be elimianted all together) + function testExoticDebtWithNoCaveatsNotAsBorrower() public { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); SpentItem[] memory exoticDebt = new SpentItem[](2); exoticDebt[0] = SpentItem({token: address(erc1155s[1]), amount: 1, identifier: 1, itemType: ItemType.ERC1155}); exoticDebt[1] = SpentItem({token: address(erc721s[2]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); - SpentItem[] memory maxSpent = new SpentItem[](1); - maxSpent[0] = SpentItem({token: address(erc721s[0]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); - StrategistOriginator.Details memory OD; - OD.issuer = lender.addr; - OD.conduit = lenderConduit; - vm.prank(lender.addr); - conduitController.updateChannel(lenderConduit, address(originator), true); - - LoanManager.Obligation memory O = LoanManager.Obligation({ - custodian: address(custodian), - borrower: borrower.addr, - debt: exoticDebt, + loan.debt = exoticDebt; + loan.collateral[0] = + SpentItem({token: address(erc721s[0]), amount: 1, identifier: 2, itemType: ItemType.ERC721}); + CaveatEnforcer.CaveatWithApproval memory borrowerEnforcer; + CaveatEnforcer.CaveatWithApproval memory lenderEnforcer = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, salt: bytes32(0), - details: abi.encode(OD), - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(originator) + enforcer: address(lenderEnforcer) }); - - (SpentItem[] memory previewOffer, ReceivedItem[] memory previewConsider) = LM.previewOrder( - address(LM.seaport()), borrower.addr, new SpentItem[](0), maxSpent, abi.encode(Actions.Origination, O) - ); - ReceivedItem[] memory expectedConsider = new ReceivedItem[](maxSpent.length); - for (uint256 i; i < maxSpent.length; i++) { - expectedConsider[i] = ReceivedItem({ - itemType: maxSpent[i].itemType, - token: maxSpent[i].token, - identifier: maxSpent[i].identifier, - amount: maxSpent[i].amount, - recipient: payable(O.custodian) - }); - } - vm.prank(address(LM.seaport())); - (SpentItem[] memory offer, ReceivedItem[] memory consider) = - LM.generateOrder(borrower.addr, new SpentItem[](0), maxSpent, abi.encode(Actions.Origination, O)); - _deepEq(offer, new SpentItem[](0)); - _deepEq(previewOffer, offer); - _deepEq(consider, previewConsider); - _deepEq(consider, expectedConsider); - assert(erc721s[2].ownerOf(1) == borrower.addr); - assert(erc1155s[1].balanceOf(borrower.addr, 1) == 1); - } - - function testNativeDebtWithNoCaveatsAsBorrower() public { - Originator originator = new MockOriginator(LM, address(0), 0); - vm.deal(address(originator), 1 ether); - address seaport = address(LM.seaport()); - - SpentItem[] memory exoticDebt = new SpentItem[](1); - exoticDebt[0] = SpentItem({token: address(erc1155s[1]), amount: 100, identifier: 1, itemType: ItemType.NATIVE}); - - SpentItem[] memory maxSpent = new SpentItem[](1); - maxSpent[0] = SpentItem({token: address(erc721s[0]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); - StrategistOriginator.Details memory OD; - OD.issuer = lender.addr; - OD.conduit = lenderConduit; - vm.prank(lender.addr); - conduitController.updateChannel(lenderConduit, address(originator), true); - - LoanManager.Obligation memory O = LoanManager.Obligation({ - custodian: address(custodian), - borrower: borrower.addr, - debt: exoticDebt, - salt: bytes32(0), - details: abi.encode(OD), - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(originator) - }); - - ReceivedItem[] memory expectedConsideration = new ReceivedItem[](1); - for (uint256 i; i < maxSpent.length; i++) { - expectedConsideration[i] = ReceivedItem({ - itemType: maxSpent[i].itemType, - token: maxSpent[i].token, - identifier: maxSpent[i].identifier, - amount: maxSpent[i].amount, - recipient: payable(O.custodian) - }); - } - (SpentItem[] memory previewOffer, ReceivedItem[] memory previewConsider) = LM.previewOrder( - address(LM.seaport()), borrower.addr, new SpentItem[](0), maxSpent, abi.encode(Actions.Origination, O) - ); - - uint256 balanceBefore = borrower.addr.balance; - vm.prank(address(LM.seaport())); - (SpentItem[] memory offer, ReceivedItem[] memory consider) = - LM.generateOrder(borrower.addr, new SpentItem[](0), maxSpent, abi.encode(Actions.Origination, O)); - - _deepEq(offer, new SpentItem[](0)); - _deepEq(previewOffer, offer); - - _deepEq(consider, previewConsider); - _deepEq(consider, expectedConsideration); - - assert(borrower.addr.balance == balanceBefore + exoticDebt[0].amount); - } - - function testNativeDebtWithNoCaveatsNotAsBorrower() public { - Originator originator = new MockOriginator(LM, address(0), 0); - vm.deal(address(originator), 1 ether); - address seaport = address(LM.seaport()); - - SpentItem[] memory exoticDebt = new SpentItem[](1); - exoticDebt[0] = SpentItem({token: address(0), amount: 100, identifier: 1, itemType: ItemType.NATIVE}); - - SpentItem[] memory maxSpent = new SpentItem[](1); - maxSpent[0] = SpentItem({token: address(erc721s[0]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); - StrategistOriginator.Details memory OD; - OD.issuer = lender.addr; - OD.conduit = lenderConduit; - vm.prank(lender.addr); - conduitController.updateChannel(lenderConduit, address(originator), true); - - LoanManager.Obligation memory O = LoanManager.Obligation({ - custodian: address(custodian), - borrower: borrower.addr, - debt: exoticDebt, - salt: bytes32(0), - details: abi.encode(OD), - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(originator) - }); - - (SpentItem[] memory previewOffer, ReceivedItem[] memory previewConsider) = LM.previewOrder( - address(LM.seaport()), address(this), new SpentItem[](0), maxSpent, abi.encode(Actions.Origination, O) - ); - - uint256 balanceOfLM = address(LM).balance; - //enable re entrancy guard - vm.store(address(seaport), bytes32(uint256(0)), bytes32(uint256(2))); - - bytes32 caveatHash = - keccak256(LM.encodeWithSaltAndBorrowerCounter(O.borrower, O.salt, keccak256(abi.encode(O.caveats)))); - ReceivedItem[] memory expectedConsider = new ReceivedItem[](maxSpent.length); - for (uint256 i; i < maxSpent.length; i++) { - expectedConsider[i] = ReceivedItem({ - itemType: maxSpent[i].itemType, - token: maxSpent[i].token, - identifier: maxSpent[i].identifier, - amount: maxSpent[i].amount, - recipient: payable(O.custodian) - }); - } - SpentItem[] memory expectedOffer = new SpentItem[](2); - expectedOffer[0] = exoticDebt[0]; - expectedOffer[1] = - SpentItem({itemType: ItemType.ERC721, token: address(LM), identifier: uint256(caveatHash), amount: 1}); - vm.prank(address(LM.seaport())); - (SpentItem[] memory offer, ReceivedItem[] memory consider) = - LM.generateOrder(address(this), new SpentItem[](0), maxSpent, abi.encode(Actions.Origination, O)); - _deepEq(offer, expectedOffer); - _deepEq(previewOffer, offer); - _deepEq(consider, previewConsider); - _deepEq(consider, expectedConsider); - assert(address(LM).balance == balanceOfLM + exoticDebt[0].amount); - } - - function testNativeDebtWithNoCaveatsNotAsBorrowerFeesOn() public { - Originator originator = new MockOriginator(LM, address(0), 0); - vm.deal(address(originator), 1 ether); - address seaport = address(LM.seaport()); - - LM.setFeeData(address(20), 1e17); //10% fees - SpentItem[] memory exoticDebt = new SpentItem[](1); - exoticDebt[0] = SpentItem({token: address(0), amount: 100, identifier: 1, itemType: ItemType.NATIVE}); - - SpentItem[] memory maxSpent = new SpentItem[](1); - maxSpent[0] = SpentItem({token: address(erc721s[0]), amount: 1, identifier: 1, itemType: ItemType.ERC721}); - StrategistOriginator.Details memory OD; - OD.issuer = lender.addr; - OD.conduit = lenderConduit; - vm.prank(lender.addr); - conduitController.updateChannel(lenderConduit, address(originator), true); - - LoanManager.Obligation memory O = LoanManager.Obligation({ - custodian: address(custodian), - borrower: borrower.addr, - debt: exoticDebt, - salt: bytes32(0), - details: abi.encode(OD), - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(originator) - }); - - bytes memory encodedObligation = abi.encode(Actions.Origination, O); - - (SpentItem[] memory previewOffer, ReceivedItem[] memory previewConsider) = - LM.previewOrder(address(LM.seaport()), address(this), new SpentItem[](0), maxSpent, encodedObligation); - - uint256 balanceOfLM = address(LM).balance; - //enable re entrancy guard - vm.store(address(seaport), bytes32(uint256(0)), bytes32(uint256(2))); - - bytes32 caveatHash = - keccak256(LM.encodeWithSaltAndBorrowerCounter(O.borrower, O.salt, keccak256(abi.encode(O.caveats)))); - - ReceivedItem[] memory expectedConsider = new ReceivedItem[](maxSpent.length); - for (uint256 i; i < maxSpent.length; i++) { - expectedConsider[i] = ReceivedItem({ - itemType: maxSpent[i].itemType, - token: maxSpent[i].token, - identifier: maxSpent[i].identifier, - amount: maxSpent[i].amount, - recipient: payable(O.custodian) - }); - } - SpentItem[] memory expectedOffer = new SpentItem[](2); - expectedOffer[0] = SpentItemLib.copy(exoticDebt[0]); - expectedOffer[0].amount = expectedOffer[0].amount - expectedOffer[0].amount.mulDiv(1e17, 1e18); - expectedOffer[1] = - SpentItem({itemType: ItemType.ERC721, token: address(LM), identifier: uint256(caveatHash), amount: 1}); - vm.prank(address(LM.seaport())); - (SpentItem[] memory offer, ReceivedItem[] memory consider) = - LM.generateOrder(address(this), new SpentItem[](0), maxSpent, encodedObligation); - _deepEq(offer, expectedOffer); - _deepEq(previewOffer, offer); - _deepEq(consider, expectedConsider); - _deepEq(consider, previewConsider); - - assert(address(LM).balance == balanceOfLM + expectedOffer[0].amount); - assert(address(LM.feeTo()).balance == 10); - } - - function testPayableFunctions() public { - vm.deal(seaportAddr, 2 ether); - vm.prank(seaportAddr); - payable(address(LM)).call{value: 1 ether}(abi.encodeWithSignature("helloWorld()")); - vm.prank(seaportAddr); - payable(address(LM)).call{value: 1 ether}(""); - - vm.expectRevert(abi.encodeWithSelector(LoanManager.NotSeaport.selector)); - payable(address(LM)).call{value: 1 ether}(abi.encodeWithSignature("helloWorld()")); - vm.expectRevert(abi.encodeWithSelector(LoanManager.NotSeaport.selector)); - payable(address(LM)).call{value: 1 ether}(""); + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + vm.prank(loan.borrower); + LM.originate(new ConduitTransfer[](0), borrowerEnforcer, lenderEnforcer, loan); } function testNonPayableFunctions() public { vm.expectRevert(); payable(address(LM)).call{value: 1 ether}(abi.encodeWithSelector(LoanManager.tokenURI.selector, uint256(0))); vm.expectRevert(); - payable(address(LM)).call{value: 1 ether}( - abi.encodeWithSelector(LoanManager.supportsInterface.selector, bytes4(0)) - ); + payable(address(LM)).call{value: 1 ether}(abi.encodeWithSelector(ERC721.supportsInterface.selector, bytes4(0))); vm.expectRevert(); payable(address(LM)).call{value: 1 ether}(abi.encodeWithSelector(LoanManager.name.selector)); vm.expectRevert(); payable(address(LM)).call{value: 1 ether}(abi.encodeWithSelector(LoanManager.symbol.selector)); + CaveatEnforcer.CaveatWithApproval memory be; vm.expectRevert(); payable(address(LM)).call{value: 1 ether}( abi.encodeWithSelector( - LoanManager.ratifyOrder.selector, - new SpentItem[](0), - new ReceivedItem[](0), - new bytes(0), - new bytes32[](0), - uint256(0) + LoanManager.originate.selector, new ConduitTransfer[](0), be, be, generateDefaultLoanTerms() ) ); vm.expectRevert(); + //address lender, + // CaveatEnforcer.CaveatWithApproval calldata lenderCaveat, + // LoanManager.Loan memory loan, + // bytes calldata pricingData payable(address(LM)).call{value: 1 ether}( - abi.encodeWithSelector( - LoanManager.generateOrder.selector, address(0), new SpentItem[](0), new SpentItem[](0), new bytes(0) - ) - ); - vm.expectRevert(); - payable(address(LM)).call{value: 1 ether}(abi.encodeWithSelector(LoanManager.getSeaportMetadata.selector)); - vm.expectRevert(); - payable(address(LM)).call{value: 1 ether}( - abi.encodeWithSelector( - LoanManager.previewOrder.selector, - address(0), - address(0), - new SpentItem[](0), - new SpentItem[](0), - new bytes(0) - ) - ); - vm.expectRevert(); - payable(address(LM)).call{value: 1 ether}( - abi.encodeWithSelector( - LoanManager.onERC1155Received.selector, address(0), address(0), uint256(0), uint256(0), new bytes(0) - ) + abi.encodeWithSelector(LoanManager.refinance.selector, address(0), be, generateDefaultLoanTerms(), "") ); } - function testSafeTransfer1155Receive() public { - erc1155s[0].mint(address(this), 1, 1); - - vm.store(address(LM.seaport()), bytes32(uint256(0)), bytes32(uint256(2))); - erc1155s[0].safeTransferFrom(address(this), address(LM), 1, 1, new bytes(0)); - } - + //cavets prevent this flow i think, as borrower needs 2 lender caveats to function testCannotIssueSameLoanTwice() public { - Originator originator = new MockOriginator(LM, address(0), 0); - vm.deal(address(originator), 1 ether); - address seaport = address(LM.seaport()); + LoanManager.Loan memory loan = generateDefaultLoanTerms(); SpentItem[] memory exoticDebt = new SpentItem[](1); - exoticDebt[0] = SpentItem({token: address(0), amount: 100, identifier: 1, itemType: ItemType.NATIVE}); + exoticDebt[0] = SpentItem({token: address(erc20s[0]), amount: 100, identifier: 0, itemType: ItemType.ERC20}); + loan.debt = exoticDebt; SpentItem[] memory maxSpent = new SpentItem[](1); - maxSpent[0] = SpentItem({token: address(erc20s[0]), amount: 20, identifier: 1, itemType: ItemType.ERC20}); - StrategistOriginator.Details memory OD; - vm.prank(lender.addr); - conduitController.updateChannel(lenderConduit, address(originator), true); - - LoanManager.Obligation memory O = LoanManager.Obligation({ - custodian: address(custodian), - borrower: borrower.addr, - debt: exoticDebt, + maxSpent[0] = SpentItem({token: address(erc20s[0]), amount: 20, identifier: 0, itemType: ItemType.ERC20}); + loan.collateral = maxSpent; + CaveatEnforcer.CaveatWithApproval memory be; + CaveatEnforcer.CaveatWithApproval memory le1 = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, salt: bytes32(0), - details: abi.encode(OD), - approval: "", - caveats: new LoanManager.Caveat[](0), - originator: address(originator) + enforcer: address(lenderEnforcer) }); + CaveatEnforcer.CaveatWithApproval memory le2 = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, + salt: bytes32(uint256(1)), + enforcer: address(lenderEnforcer) + }); + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); - bytes memory encodedObligation = abi.encode(Actions.Origination, O); - - (SpentItem[] memory previewOffer, ReceivedItem[] memory previewConsider) = - LM.previewOrder(address(LM.seaport()), borrower.addr, new SpentItem[](0), maxSpent, encodedObligation); + vm.prank(loan.borrower); + LM.originate(new ConduitTransfer[](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); + } - uint256 balanceOfLM = address(LM).balance; - //enable re entrancy guard - vm.store(address(seaport), bytes32(uint256(0)), bytes32(uint256(2))); + function testAdditionalTransfers() public { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); - ReceivedItem[] memory expectedConsider = new ReceivedItem[](maxSpent.length); - for (uint256 i; i < maxSpent.length; i++) { - expectedConsider[i] = ReceivedItem({ - itemType: maxSpent[i].itemType, - token: maxSpent[i].token, - identifier: maxSpent[i].identifier, - amount: maxSpent[i].amount, - recipient: payable(O.custodian) - }); - } + SpentItem[] memory exoticDebt = new SpentItem[](1); + exoticDebt[0] = SpentItem({token: address(erc20s[0]), amount: 100, identifier: 0, itemType: ItemType.ERC20}); - vm.prank(address(LM.seaport())); - (SpentItem[] memory offer, ReceivedItem[] memory consider) = - LM.generateOrder(borrower.addr, new SpentItem[](0), maxSpent, encodedObligation); - _deepEq(offer, new SpentItem[](0)); - _deepEq(previewOffer, offer); - _deepEq(consider, expectedConsider); - _deepEq(consider, previewConsider); - vm.prank(address(LM.seaport())); - vm.expectRevert(LoanManager.LoanExists.selector); - LM.generateOrder(borrower.addr, new SpentItem[](0), maxSpent, encodedObligation); + loan.debt = exoticDebt; + SpentItem[] memory maxSpent = new SpentItem[](1); + maxSpent[0] = SpentItem({token: address(erc20s[0]), amount: 20, identifier: 0, itemType: ItemType.ERC20}); + loan.collateral = maxSpent; + CaveatEnforcer.CaveatWithApproval memory be; + CaveatEnforcer.CaveatWithApproval memory le1 = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, + salt: bytes32(0), + enforcer: address(lenderEnforcer) + }); + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + ConduitTransfer[] memory additionalTransfers = new ConduitTransfer[](1); + additionalTransfers[0] = ConduitTransfer({ + itemType: ConduitItemType.ERC20, + token: address(erc20s[0]), + from: address(loan.borrower), + to: address(address(20)), + identifier: 0, + amount: 20 + }); + vm.prank(loan.borrower); + ERC20(address(erc20s[0])).approve(address(LM), type(uint256).max); + vm.prank(loan.borrower); + LM.originate(additionalTransfers, be, le1, loan); + assert(erc20s[0].balanceOf(address(20)) == 20); + assert(erc20s[0].balanceOf(address(loan.custodian)) == 20); } }