diff --git a/.gas-snapshot b/.gas-snapshot index e873aaac..d29b4bbc 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,82 +1,114 @@ -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallBase() (gas: 1039520) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLender() (gas: 691168) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLiquidation() (gas: 770737) -TestCustodian:testCannotLazyMintTwice() (gas: 76597) +DiffFuzzTestStarPortLib:testSpentToReceived((uint8,address,uint256,uint256)[]) (runs: 256, μ: 880501, ~: 882340) +DiffFuzzTestStarPortLib:testUnboundSpentToReceived((uint8,address,uint256,uint256)[]) (runs: 256, μ: 232899, ~: 237832) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallBase() (gas: 1041013) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLender() (gas: 693016) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLiquidation() (gas: 772585) +TestBorrowerEnforcer:testBERevertAdditionalTransfers() (gas: 73101) +TestBorrowerEnforcer:testBERevertInvalidLoanTerms() (gas: 78338) +TestBorrowerEnforcer:testBEValidLoanTerms() (gas: 69429) +TestBorrowerEnforcer:testBEValidLoanTermsAnyIssuer() (gas: 69581) +TestCustodian:testCannotLazyMintTwice() (gas: 76686) TestCustodian:testCannotMintInvalidLoanInvalidCustodian() (gas: 66883) -TestCustodian:testCannotMintInvalidLoanValidCustodian() (gas: 72422) +TestCustodian:testCannotMintInvalidLoanValidCustodian() (gas: 72511) 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:testGenerateOrderRepay() (gas: 164353) +TestCustodian:testGenerateOrderRepayAsRepayApprovedBorrower() (gas: 190000) +TestCustodian:testGenerateOrderRepayERC1155AndERC20AndNative() (gas: 847956) +TestCustodian:testGenerateOrderRepayERC1155AndERC20AndNativeHandlerAuthorized() (gas: 789588) +TestCustodian:testGenerateOrderRepayERC1155WithRevert() (gas: 520259) 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:testGenerateOrderSettlement() (gas: 151359) +TestCustodian:testGenerateOrderSettlementHandlerAuthorized() (gas: 160399) +TestCustodian:testGenerateOrderSettlementNoActiveLoan() (gas: 155598) TestCustodian:testGenerateOrderSettlementUnauthorized() (gas: 94219) TestCustodian:testGetBorrower() (gas: 76234) -TestCustodian:testInvalidAction() (gas: 114400) -TestCustodian:testInvalidActionRepayInActiveLoan() (gas: 117327) -TestCustodian:testInvalidActionSettleActiveLoan() (gas: 117287) +TestCustodian:testInvalidAction() (gas: 114489) +TestCustodian:testInvalidActionRepayInActiveLoan() (gas: 117416) +TestCustodian:testInvalidActionSettleActiveLoan() (gas: 117376) 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:testPreviewOrderNoActiveLoan() (gas: 98776) +TestCustodian:testPreviewOrderRepay() (gas: 208195) +TestCustodian:testPreviewOrderSettlement() (gas: 182743) +TestCustodian:testPreviewOrderSettlementInvalidFufliller() (gas: 100577) +TestCustodian:testPreviewOrderSettlementInvalidRepayer() (gas: 106735) +TestCustodian:testRatifyOrder() (gas: 170196) TestCustodian:testSeaportMetadata() (gas: 8567) TestCustodian:testSetRepayApproval() (gas: 37883) TestCustodian:testSupportsInterface() (gas: 9428) TestCustodian:testSymbol() (gas: 7149) TestCustodian:testTokenURI() (gas: 64839) 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) +TestLenderEnforcer:testLERevertAdditionalTransfersFromLender() (gas: 73760) +TestLenderEnforcer:testLERevertInvalidLoanTerms() (gas: 78385) +TestLenderEnforcer:testLEValidLoanTerms() (gas: 69429) +TestLenderEnforcer:testLEValidLoanTermsAnyBorrower() (gas: 69516) +TestLenderEnforcer:testLEValidLoanTermsWithAdditionalTransfers() (gas: 70757) +TestLoanCombinations:testLoan20For721SimpleInterestDutchFixedRepay() (gas: 532861) +TestLoanCombinations:testLoan20for20SimpleInterestDutchFixedRepay() (gas: 501718) +TestLoanCombinations:testLoan721for20SimpleInterestDutchFixedRepay() (gas: 551584) +TestLoanCombinations:testLoanAstariaSettlementRepay() (gas: 541442) +TestLoanCombinations:testLoanSimpleInterestEnglishFixed() (gas: 546102) +TestLoanManager:testAdditionalTransfers() (gas: 293082) +TestLoanManager:testCannotIssueSameLoanTwice() (gas: 331285) +TestLoanManager:testCannotOriginateWhilePaused() (gas: 87923) 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:testCannotSettleUnlessValidCustodian() (gas: 68750) +TestLoanManager:testCaveatEnforcerRevert() (gas: 119155) +TestLoanManager:testDefaultFeeRake() (gas: 352463) +TestLoanManager:testExoticDebtWithNoCaveatsNotAsBorrower() (gas: 342734) +TestLoanManager:testInitializedFlagSetProperly() (gas: 65262) +TestLoanManager:testInvalidAmountCollateral() (gas: 152833) +TestLoanManager:testInvalidAmountCollateral721() (gas: 152976) +TestLoanManager:testInvalidAmountDebt() (gas: 177144) +TestLoanManager:testInvalidIdentifierDebt() (gas: 197138) +TestLoanManager:testInvalidItemType() (gas: 138881) +TestLoanManager:testInvalidTransferLengthCollateral() (gas: 161050) +TestLoanManager:testInvalidTransferLengthDebt() (gas: 165593) +TestLoanManager:testIssued() (gas: 67144) 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) +TestLoanManager:testNonDefaultCustodianCustodyCallFails() (gas: 190220) +TestLoanManager:testNonDefaultCustodianCustodyCallSuccess() (gas: 258469) +TestLoanManager:testNonPayableFunctions() (gas: 175555) +TestLoanManager:testOverrideFeeRake() (gas: 346202) +TestLoanManager:testPause() (gas: 34222) +TestLoanManager:testSupportsInterface() (gas: 9181) +TestLoanManager:testSymbol() (gas: 7235) +TestLoanManager:testTokenNoCodeCollateral() (gas: 137653) +TestLoanManager:testTokenNoCodeDebt() (gas: 170689) +TestLoanManager:testTokenURI() (gas: 64892) +TestLoanManager:testTokenURIInvalidLoan() (gas: 13244) +TestLoanManager:testTransferFromFail() (gas: 80176) +TestLoanManager:testUnPause() (gas: 14291) +TestNewLoan:testBuyNowPayLater() (gas: 2831719) +TestNewLoan:testNewLoanERC721CollateralDefaultTerms2() (gas: 391890) +TestNewLoan:testNewLoanERC721CollateralLessDebtThanOffered() (gas: 2259) TestNewLoan:testNewLoanRefinanceNew() (gas: 207) +TestNewLoan:testNewLoanViaOriginatorBorrowerApprovalAndLenderApproval() (gas: 299336) +TestNewLoan:testNewLoanViaOriginatorLenderApproval() (gas: 353952) TestNewLoan:testSettleLoan() (gas: 163) +TestRefStarPortLib:testSpentToReceived() (gas: 13315) +TestRefStarPortLib:testValidateSalt(address,bytes32) (runs: 256, μ: 33865, ~: 33865) TestRepayLoan:testRepayLoan() (gas: 207) -TestStarLiteUtils:testEncodeReceivedWithRecipient() (gas: 17955) -TestStarLiteUtils:testSpentToReceived() (gas: 17708) -TestStarLiteUtils:testValidateSaltOpt(address,bytes32) (runs: 256, μ: 26479, ~: 26479) -TestStarLiteUtils:testValidateSaltRef(address,bytes32) (runs: 256, μ: 26849, ~: 26849) \ No newline at end of file +TestStarPortLib:testSpentToReceived() (gas: 13315) +TestStarPortLib:testValidateSalt(address,bytes32) (runs: 256, μ: 33865, ~: 33865) +TestStrategistOriginator:testEncodeWithAccountCounter() (gas: 12307) +TestStrategistOriginator:testGetStrategistData() (gas: 1473217) +TestStrategistOriginator:testIncrementCounterAsStrategist() (gas: 18676) +TestStrategistOriginator:testIncrementCounterNotAuthorized() (gas: 13467) +TestStrategistOriginator:testInvalidCollateral() (gas: 204416) +TestStrategistOriginator:testInvalidDeadline() (gas: 210145) +TestStrategistOriginator:testInvalidDebt() (gas: 206100) +TestStrategistOriginator:testInvalidDebtAmountAskingMoreThanOffered() (gas: 206548) +TestStrategistOriginator:testInvalidDebtAmountOfferingZero() (gas: 186903) +TestStrategistOriginator:testInvalidDebtAmountRequestingZero() (gas: 206801) +TestStrategistOriginator:testInvalidDebtLength() (gas: 205428) +TestStrategistOriginator:testInvalidOffer() (gas: 396325) +TestStrategistOriginator:testInvalidSigner() (gas: 208639) +TestStrategistOriginator:testSetStrategist() (gas: 17818) \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 4ff40c48..49315808 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,6 +1,7 @@ [profile.default] +#cancun = true src = "src" out = "out" libs = ["lib"] - +#solc = "./bin/solc" # See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/src/BNPLHelper.sol b/src/BNPLHelper.sol new file mode 100644 index 00000000..3cce91d5 --- /dev/null +++ b/src/BNPLHelper.sol @@ -0,0 +1,138 @@ +pragma solidity ^0.8.17; + +import { + OfferItem, + SpentItem, + ConsiderationItem, + AdvancedOrder, + OrderParameters, + CriteriaResolver, + ItemType, + Fulfillment +} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; + +import {Seaport} from "seaport/contracts/Seaport.sol"; +import {LoanManager} from "./LoanManager.sol"; +import {CaveatEnforcer} from "./enforcers/CaveatEnforcer.sol"; +import "forge-std/console.sol"; + +interface IVault { + function flashLoan( + IFlashLoanRecipient recipient, + address[] calldata tokens, + uint256[] calldata amounts, + bytes memory userData + ) external; +} + +interface IFlashLoanRecipient { + function receiveFlashLoan( + address[] calldata tokens, + uint256[] calldata amounts, + uint256[] calldata feeAmounts, + bytes memory userData + ) external; +} + +interface IWETH9 { + function withdraw(uint256) external; +} + +interface ERC20 { + function transfer(address, uint256) external returns (bool); +} + +// fulfiller + +contract BNPLHelper is IFlashLoanRecipient { + address private constant vault = address(0xBA12222222228d8Ba445958a75a0704d566BF2C8); + IWETH9 private immutable WETH; + bytes32 private activeUserDataHash; + + constructor(address WETH_) { + WETH = IWETH9(WETH_); + } + + struct Execution { + address lm; + address seaport; + address borrower; + CaveatEnforcer.CaveatWithApproval borrowerCaveat; + CaveatEnforcer.CaveatWithApproval lenderCaveat; + LoanManager.Loan loan; + AdvancedOrder[] orders; + CriteriaResolver[] resolvers; + Fulfillment[] fulfillments; + } + + error SenderNotSelf(); + error DoNotSendETH(); + error InvalidUserDataProvided(); + + function makeFlashLoan( + address[] calldata tokens, + uint256[] calldata amounts, + // uint256[] calldata userProvidedAmounts, + bytes calldata userData + ) external { + // assembly { + // // Compute the hash of userData + // let dataHash := keccak256(userData.offset, calldatasize()) + // + // // Store the hash in the activeUserDataHash state variable + // tstore(activeUserDataHash.slot, dataHash) + // } + activeUserDataHash = keccak256(userData); + + IVault(vault).flashLoan(this, tokens, amounts, userData); + } + + function receiveFlashLoan( + address[] calldata tokens, // are all erc20s + uint256[] calldata amounts, + uint256[] calldata feeAmounts, + bytes calldata userData + ) external override { + require(msg.sender == vault); + + if (activeUserDataHash != keccak256(userData)) { + revert InvalidUserDataProvided(); + } + delete activeUserDataHash; + + Execution memory execution = abi.decode(userData, (Execution)); + //approve seaport + for (uint256 i = 0; i < tokens.length;) { + ERC20(tokens[i]).transfer(execution.borrower, amounts[i]); + unchecked { + ++i; + } + } + Seaport(payable(execution.seaport)).matchAdvancedOrders( + execution.orders, execution.resolvers, execution.fulfillments, execution.borrower + ); + + ConduitTransfer[] memory transfers = new ConduitTransfer[](tokens.length); + for (uint256 i = 0; i < tokens.length;) { + transfers[i] = ConduitTransfer({ + itemType: ConduitItemType.ERC20, + identifier: 0, + token: tokens[0], + from: execution.borrower, + to: vault, + amount: amounts[0] + feeAmounts[0] + }); + unchecked { + ++i; + } + } + LoanManager(execution.lm).originate(transfers, execution.borrowerCaveat, execution.lenderCaveat, execution.loan); + } + + receive() external payable { + if (msg.sender != address(WETH)) { + revert DoNotSendETH(); + } + } +} diff --git a/src/ConduitHelper.sol b/src/ConduitHelper.sol index 312d2b45..e6f40901 100644 --- a/src/ConduitHelper.sol +++ b/src/ConduitHelper.sol @@ -18,7 +18,7 @@ * * Chainworks Labs */ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {ItemType, OfferItem, Schema, SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; diff --git a/src/Custodian.sol b/src/Custodian.sol index 5f2da6e0..08edd17c 100644 --- a/src/Custodian.sol +++ b/src/Custodian.sol @@ -18,7 +18,7 @@ * * Chainworks Labs */ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {ERC721} from "solady/src/tokens/ERC721.sol"; import {ERC20} from "solady/src/tokens/ERC20.sol"; diff --git a/src/LoanManager.sol b/src/LoanManager.sol index ab800512..89f5c913 100644 --- a/src/LoanManager.sol +++ b/src/LoanManager.sol @@ -18,7 +18,7 @@ * * Chainworks Labs */ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {ERC721} from "solady/src/tokens/ERC721.sol"; import {ERC20} from "solady/src/tokens/ERC20.sol"; @@ -57,7 +57,6 @@ contract LoanManager is Ownable, ERC721 { bytes32 internal immutable _DOMAIN_SEPARATOR; ConsiderationInterface public immutable seaport; - // bool public paused; //TODO: address payable public immutable defaultCustodian; bytes32 public immutable DEFAULT_CUSTODIAN_CODE_HASH; @@ -70,20 +69,28 @@ contract LoanManager is Ownable, ERC721 { bytes32 public constant INTENT_ORIGINATION_TYPEHASH = keccak256("Origination(bytes32 hash,bytes32 salt,bytes32 caveatHash"); bytes32 public constant VERSION = keccak256("0"); + bool public paused; address public feeTo; - uint96 public defaultFeeRake; + uint88 public defaultFeeRake; 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; + // mapping(address => mapping(address => bool)) public approvals; + enum ApprovalType { + NOTHING, + BORROWER, + LENDER + } enum FieldFlags { INITIALIZED, ACTIVE, INACTIVE } + mapping(address => mapping(address => ApprovalType)) public approvals; + mapping(address => uint256) public caveatNonces; + //contract to token //fee rake + mapping(address => Fee) public feeOverride; + struct Terms { address hook; //the address of the hookmodule bytes hookData; //bytes encoded hook data @@ -104,54 +111,34 @@ contract LoanManager is Ownable, ERC721 { Terms terms; //the actionable terms of the loan } - struct Caveat { - address enforcer; - bytes terms; - } - - struct Obligation { - address custodian; - SpentItem[] debt; - address originator; - address borrower; - bytes32 salt; - Caveat[] caveats; - bytes details; - bytes approval; - } - struct Fee { bool enabled; - uint96 amount; + uint88 amount; } event Close(uint256 loanId); event Open(uint256 loanId, LoanManager.Loan loan); - event SeaportCompatibleContractDeployed(); + event Paused(); + event UnPaused(); - error CannotTransferLoans(); - error ConduitTransferError(); - error InvalidAction(); - error InvalidConduit(); error InvalidRefinance(); error InvalidCustodian(); error InvalidLoan(); error InvalidItemAmount(); + error InvalidItemIdentifier(); //must be zero for ERC20's error InvalidItemTokenNoCode(); + error InvalidItemType(); error InvalidTransferLength(); - // error InvalidNoRefinanceConsideration(); + error CannotTransferLoans(); + error ConduitTransferError(); error LoanExists(); error NotLoanCustodian(); - error NotPayingFees(); error NotSeaport(); - error NotEnteredViaSeaport(); - error NativeAssetsNotSupported(); - error HashAlreadyInvalidated(); - error InvalidItemType(); error UnauthorizedAdditionalTransferIncluded(); error InvalidCaveatSigner(); error MalformedRefinance(); + error IsPaused(); constructor(ConsiderationInterface seaport_) { address custodian = address(new Custodian(this, seaport_)); @@ -165,32 +152,75 @@ contract LoanManager is Ownable, ERC721 { DEFAULT_CUSTODIAN_CODE_HASH = defaultCustodianCodeHash; _DOMAIN_SEPARATOR = keccak256(abi.encode(EIP_DOMAIN, VERSION, block.chainid, address(this))); _initializeOwner(msg.sender); - emit SeaportCompatibleContractDeployed(); + } + // + // function setApproval(address who, bool approved) external { + // assembly { + // // Compute the storage slot of approvals[msg.sender] + // let slot := keccak256(add(mul(caller(), 0x1000000000000000000000000), mload(approvals.slot)), 0x20) + // + // // Compute the storage slot of approvals[msg.sender][who] + // slot := keccak256(add(who, slot), 0x20) + // + // // Update the value at the computed storage slot + // sstore(slot, approved) + // } + // } + + // function setApprovalTransient(address who) external { + // assembly { + // // Compute the storage slot of approvals[msg.sender] + // let slot := keccak256(add(mul(caller(), 0x1000000000000000000000000), mload(approvals.slot)), 0x20) + // + // // Compute the storage slot of approvals[msg.sender][who] + // slot := keccak256(add(who, slot), 0x20) + // + // // Update the value at the computed storage slot + // tstore(slot, 1) + // } + // } + + function setOriginateApproval(address who, ApprovalType approvalType) external { + approvals[msg.sender][who] = approvalType; } function transferFrom(address from, address to, uint256 tokenId) public payable override { revert CannotTransferLoans(); } + function pause() external onlyOwner { + paused = true; + emit Paused(); + } + + function unPause() external onlyOwner { + paused = false; + emit UnPaused(); + } + function originate( ConduitTransfer[] calldata additionalTransfers, CaveatEnforcer.CaveatWithApproval calldata borrowerCaveat, CaveatEnforcer.CaveatWithApproval calldata lenderCaveat, LoanManager.Loan memory loan ) external payable { + if (paused) { + revert IsPaused(); + } //cache the addresses address borrower = loan.borrower; address issuer = loan.issuer; address feeRecipient = feeTo; - if (msg.sender != loan.borrower) { + if (msg.sender != loan.borrower && !(approvals[borrower][msg.sender] == ApprovalType.BORROWER)) { _validateAndEnforceCaveats(borrowerCaveat, borrower, additionalTransfers, loan); } - if (msg.sender != issuer && !approvals[issuer][msg.sender]) { + if (msg.sender != issuer && !(approvals[issuer][msg.sender] == ApprovalType.LENDER)) { _validateAndEnforceCaveats(lenderCaveat, issuer, additionalTransfers, loan); } _transferSpentItems(loan.collateral, borrower, loan.custodian); + _callCustody(loan); if (feeRecipient == address(0)) { _transferSpentItems(loan.debt, issuer, borrower); @@ -217,6 +247,9 @@ contract LoanManager is Ownable, ERC721 { LoanManager.Loan memory loan, bytes calldata pricingData ) external { + if (paused) { + revert IsPaused(); + } ( SpentItem[] memory considerationPayment, SpentItem[] memory carryPayment, @@ -233,7 +266,7 @@ contract LoanManager is Ownable, ERC721 { loan.originator = address(0); loan.start = 0; - if (msg.sender != loan.issuer && !approvals[loan.issuer][msg.sender]) { + if (msg.sender != loan.issuer && !(approvals[loan.issuer][msg.sender] == ApprovalType.LENDER)) { _validateAndEnforceCaveats(lenderCaveat, loan.issuer, additionalTransfers, loan); } @@ -410,7 +443,9 @@ contract LoanManager is Ownable, ERC721 { } if (amount > 0) { if (itemType == ItemType.ERC20) { - // erc20 transfer + if (identifier > 0) { + revert InvalidItemIdentifier(); + } SafeTransferLib.safeTransferFrom(token, from, to, amount); } else if (itemType == ItemType.ERC721) { // erc721 transfer @@ -567,7 +602,7 @@ contract LoanManager is Ownable, ERC721 { * @param feeTo_ The feeToAddress * @param defaultFeeRake_ the default fee rake in WAD denomination(1e17 = 10%) */ - function setFeeData(address feeTo_, uint96 defaultFeeRake_) external onlyOwner { + function setFeeData(address feeTo_, uint88 defaultFeeRake_) external onlyOwner { feeTo = feeTo_; defaultFeeRake = defaultFeeRake_; } @@ -578,7 +613,7 @@ contract LoanManager is Ownable, ERC721 { * @param token The token to override * @param overrideValue the new value in WAD denomination to override(1e17 = 10%) */ - function setFeeOverride(address token, uint96 overrideValue) external onlyOwner { + function setFeeOverride(address token, uint88 overrideValue) external onlyOwner { feeOverride[token].enabled = true; feeOverride[token].amount = overrideValue; } diff --git a/src/custodians/AAVEPoolCustodian.sol b/src/custodians/AAVEPoolCustodian.sol index d75285f3..177a81c3 100644 --- a/src/custodians/AAVEPoolCustodian.sol +++ b/src/custodians/AAVEPoolCustodian.sol @@ -1,4 +1,4 @@ -//pragma solidity =0.8.17; +//pragma solidity ^0.8.17;; // //import {ERC20} from "solady/src/tokens/ERC20.sol"; //import "../Custodian.sol"; diff --git a/src/enforcers/BorrowerEnforcer.sol b/src/enforcers/BorrowerEnforcer.sol index eab66f95..d6186f23 100644 --- a/src/enforcers/BorrowerEnforcer.sol +++ b/src/enforcers/BorrowerEnforcer.sol @@ -1,8 +1,9 @@ -pragma solidity =0.8.17; +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"; +import {ConsiderationInterface} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; contract BorrowerEnforcer is CaveatEnforcer { error BorrowerOnlyEnforcer(); @@ -13,19 +14,23 @@ contract BorrowerEnforcer is CaveatEnforcer { LoanManager.Loan loan; } + /// @notice Enforces that the loan terms are identical except for the issuer + /// @notice The issuer is allowed to be any address + /// @notice No additional transfers are permitted + /// @param additionalTransfers The additional transfers to be made + /// @param loan The loan terms + /// @param caveatData The borrowers encoded details 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 (keccak256(abi.encode(loan)) != keccak256(abi.encode(details.loan))) revert InvalidLoanTerms(); + //Should additional transfers from the accounts other than the borrower be allowed? if (additionalTransfers.length > 0) revert InvalidAdditionalTransfer(); } } diff --git a/src/enforcers/BorrowerEnforcerBNPL.sol b/src/enforcers/BorrowerEnforcerBNPL.sol new file mode 100644 index 00000000..b79ecdb2 --- /dev/null +++ b/src/enforcers/BorrowerEnforcerBNPL.sol @@ -0,0 +1,64 @@ +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"; +import {ConsiderationInterface} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; + +contract BorrowerEnforcerBNPL is CaveatEnforcer { + error BorrowerOnlyEnforcer(); + error InvalidLoanTerms(); + error InvalidAdditionalTransfer(); + + error OrderInvalid(); + + struct Details { + LoanManager.Loan loan; + address seaport; + bytes32 offerHash; + ConduitTransfer additionalTransfer; + } + + 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) { + if (details.offerHash != bytes32(0)) { + (bool isValidated, bool isCancelled, uint256 numerator, uint256 denominator) = + ConsiderationInterface(details.seaport).getOrderStatus(details.offerHash); + + if (isCancelled || !isValidated) { + revert OrderInvalid(); + } + + if (additionalTransfers.length > 1) { + revert InvalidAdditionalTransfer(); + } + if ( + additionalTransfers[0].itemType != details.additionalTransfer.itemType + || additionalTransfers[0].identifier != details.additionalTransfer.identifier + || additionalTransfers[0].amount > details.additionalTransfer.amount + || additionalTransfers[0].token != details.additionalTransfer.token + ) { + revert InvalidAdditionalTransfer(); + } + } else { + revert InvalidAdditionalTransfer(); + } + } + } +} diff --git a/src/enforcers/CaveatEnforcer.sol b/src/enforcers/CaveatEnforcer.sol index ffce25fc..1766ffa1 100644 --- a/src/enforcers/CaveatEnforcer.sol +++ b/src/enforcers/CaveatEnforcer.sol @@ -1,5 +1,4 @@ -pragma solidity =0.8.17; -// import {ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; +pragma solidity ^0.8.17; import {ConduitTransfer} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; import {LoanManager} from "starport-core/LoanManager.sol"; diff --git a/src/enforcers/LenderEnforcer.sol b/src/enforcers/LenderEnforcer.sol index c1b79710..53e35314 100644 --- a/src/enforcers/LenderEnforcer.sol +++ b/src/enforcers/LenderEnforcer.sol @@ -1,4 +1,4 @@ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; import {ConduitTransfer} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; @@ -13,15 +13,18 @@ contract LenderEnforcer is CaveatEnforcer { LoanManager.Loan loan; } + /// @notice Enforces that the loan terms are identical except for the borrower + /// @notice The borrower is allowed to be any address + /// @notice No additional transfers from the issuer are permitted + /// @param additionalTransfers The additional transfers to be made + /// @param loan The loan terms + /// @param caveatData The borrowers encoded details 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))) { diff --git a/src/handlers/AstariaV1SettlementHandler.sol b/src/handlers/AstariaV1SettlementHandler.sol index 196227e5..7a9d2500 100644 --- a/src/handlers/AstariaV1SettlementHandler.sol +++ b/src/handlers/AstariaV1SettlementHandler.sol @@ -1,4 +1,4 @@ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {LoanManager, SpentItem, ReceivedItem, SettlementHandler} from "starport-core/handlers/SettlementHandler.sol"; import {BaseHook} from "starport-core/hooks/BaseHook.sol"; diff --git a/src/handlers/DutchAuctionHandler.sol b/src/handlers/DutchAuctionHandler.sol index 2504980c..bbc106f1 100644 --- a/src/handlers/DutchAuctionHandler.sol +++ b/src/handlers/DutchAuctionHandler.sol @@ -1,4 +1,4 @@ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import { ItemType, diff --git a/src/handlers/EnglishAuctionHandler.sol b/src/handlers/EnglishAuctionHandler.sol index f76b165c..27bfad48 100644 --- a/src/handlers/EnglishAuctionHandler.sol +++ b/src/handlers/EnglishAuctionHandler.sol @@ -1,4 +1,4 @@ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; diff --git a/src/handlers/FixedTermDutchAuctionHandler.sol b/src/handlers/FixedTermDutchAuctionHandler.sol index aae335d8..707fc5a9 100644 --- a/src/handlers/FixedTermDutchAuctionHandler.sol +++ b/src/handlers/FixedTermDutchAuctionHandler.sol @@ -1,4 +1,4 @@ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {LoanManager, SpentItem, ReceivedItem, SettlementHandler} from "starport-core/handlers/SettlementHandler.sol"; import {BaseHook} from "starport-core/hooks/BaseHook.sol"; diff --git a/src/handlers/SettlementHandler.sol b/src/handlers/SettlementHandler.sol index 870f88b7..19000518 100644 --- a/src/handlers/SettlementHandler.sol +++ b/src/handlers/SettlementHandler.sol @@ -18,7 +18,7 @@ * * Chainworks Labs */ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; diff --git a/src/hh_helpers/MockERC20.sol b/src/hh_helpers/MockERC20.sol index 8f311695..2d34bdcf 100644 --- a/src/hh_helpers/MockERC20.sol +++ b/src/hh_helpers/MockERC20.sol @@ -1,4 +1,4 @@ -pragma solidity 0.8.17; +pragma solidity ^0.8.17; import {TestERC20} from "seaport/contracts/test/TestERC20.sol"; diff --git a/src/hh_helpers/MockERC721.sol b/src/hh_helpers/MockERC721.sol index 523e52fa..2e6e99e7 100644 --- a/src/hh_helpers/MockERC721.sol +++ b/src/hh_helpers/MockERC721.sol @@ -1,4 +1,4 @@ -pragma solidity 0.8.17; +pragma solidity ^0.8.17; import {TestERC721} from "seaport/contracts/test/TestERC721.sol"; diff --git a/src/hooks/AstariaV1SettlementHook.sol b/src/hooks/AstariaV1SettlementHook.sol index 8a61be97..3e592581 100644 --- a/src/hooks/AstariaV1SettlementHook.sol +++ b/src/hooks/AstariaV1SettlementHook.sol @@ -1,4 +1,4 @@ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; import {BaseRecall} from "starport-core/hooks/BaseRecall.sol"; diff --git a/src/hooks/BaseHook.sol b/src/hooks/BaseHook.sol index b6865cb5..48bd7d2c 100644 --- a/src/hooks/BaseHook.sol +++ b/src/hooks/BaseHook.sol @@ -18,7 +18,7 @@ * * Chainworks Labs */ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {SettlementHook} from "starport-core/hooks/SettlementHook.sol"; import {LoanManager} from "starport-core/LoanManager.sol"; diff --git a/src/hooks/BaseRecall.sol b/src/hooks/BaseRecall.sol index a28f8e76..0a778af2 100644 --- a/src/hooks/BaseRecall.sol +++ b/src/hooks/BaseRecall.sol @@ -18,7 +18,7 @@ * * Chainworks Labs */ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import "forge-std/console2.sol"; diff --git a/src/hooks/FixedTermHook.sol b/src/hooks/FixedTermHook.sol index d2e0887b..c85039a4 100644 --- a/src/hooks/FixedTermHook.sol +++ b/src/hooks/FixedTermHook.sol @@ -1,4 +1,4 @@ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; import {SettlementHook} from "starport-core/hooks/SettlementHook.sol"; diff --git a/src/hooks/SettlementHook.sol b/src/hooks/SettlementHook.sol index 34657779..8747bbaa 100644 --- a/src/hooks/SettlementHook.sol +++ b/src/hooks/SettlementHook.sol @@ -18,7 +18,7 @@ * * Chainworks Labs */ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; diff --git a/src/interfaces/Lender.sol b/src/interfaces/Lender.sol index 2dd8c68f..15220422 100644 --- a/src/interfaces/Lender.sol +++ b/src/interfaces/Lender.sol @@ -1,4 +1,4 @@ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import "starport-core/LoanManager.sol"; diff --git a/src/interfaces/TokenReceiverInterface.sol b/src/interfaces/TokenReceiverInterface.sol index 7b18c01f..1b7cc9fd 100644 --- a/src/interfaces/TokenReceiverInterface.sol +++ b/src/interfaces/TokenReceiverInterface.sol @@ -1,4 +1,4 @@ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; interface TokenReceiverInterface { function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) diff --git a/src/lib/RefStarPortLib.sol b/src/lib/RefStarPortLib.sol new file mode 100644 index 00000000..dd1335ff --- /dev/null +++ b/src/lib/RefStarPortLib.sol @@ -0,0 +1,64 @@ +pragma solidity ^0.8.17; + +import {ItemType, ReceivedItem, SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; + +import {LoanManager} from "starport-core/LoanManager.sol"; +import "forge-std/console.sol"; + +enum Actions { + Nothing, + Origination, + Refinance, + Repayment, + Settlement +} + +library RefStarPortLib { + error InvalidSalt(); + + uint256 internal constant ONE_WORD = 0x20; + uint256 internal constant CUSTODIAN_WORD_OFFSET = 0x40; + + function getAction(bytes calldata data) internal pure returns (Actions action) { + assembly { + action := calldataload(data.offset) + } + } + + function getCustodian(bytes calldata data) internal pure returns (address custodian) { + assembly { + custodian := calldataload(add(data.offset, CUSTODIAN_WORD_OFFSET)) + } + } + + function toReceivedItems(SpentItem[] calldata spentItems, address recipient) + internal + pure + returns (ReceivedItem[] memory consideration) + { + consideration = new ReceivedItem[](spentItems.length); + for (uint256 i = 0; i < spentItems.length;) { + consideration[i] = ReceivedItem({ + itemType: spentItems[i].itemType, + token: spentItems[i].token, + identifier: spentItems[i].identifier, + amount: spentItems[i].amount, + recipient: payable(recipient) + }); + unchecked { + ++i; + } + } + } + + function validateSalt( + mapping(address => mapping(bytes32 => bool)) storage usedSalts, + address borrower, + bytes32 salt + ) internal { + if (usedSalts[borrower][salt]) { + revert InvalidSalt(); + } + usedSalts[borrower][salt] = true; + } +} diff --git a/src/lib/StarPortLib.sol b/src/lib/StarPortLib.sol index b3af8e2e..33a7cf35 100644 --- a/src/lib/StarPortLib.sol +++ b/src/lib/StarPortLib.sol @@ -117,17 +117,6 @@ library StarPortLib { } } - function validateSaltRef( - mapping(address => mapping(bytes32 => bool)) storage usedSalts, - address borrower, - bytes32 salt - ) internal { - if (usedSalts[borrower][salt]) { - revert InvalidSalt(); - } - usedSalts[borrower][salt] = true; - } - function validateSalt( mapping(address => mapping(bytes32 => bool)) storage usedSalts, address borrower, @@ -138,13 +127,12 @@ library StarPortLib { mstore(0x20, usedSalts.slot) //usedSalts[borrower] - let loc := keccak256(0x0, 0x40) + mstore(0x20, keccak256(0x0, 0x40)) mstore(0x0, salt) - mstore(0x20, loc) //usedSalts[borrower][salt] - loc := keccak256(0x0, 0x40) + let loc := keccak256(0x0, 0x40) //if (usedSalts[borrower][salt] == true) if iszero(iszero(sload(loc))) { diff --git a/src/originators/Originator.sol b/src/originators/Originator.sol index 77ec41c6..efb1e955 100644 --- a/src/originators/Originator.sol +++ b/src/originators/Originator.sol @@ -18,63 +18,24 @@ * * Chainworks Labs */ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; import {SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; import {Ownable} from "solady/src/auth/Ownable.sol"; +import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; abstract contract Originator is Ownable { - struct Response { - LoanManager.Terms terms; - address issuer; - } - struct Request { - address custodian; - address receiver; + address borrower; + CaveatEnforcer.CaveatWithApproval borrowerCaveat; SpentItem[] collateral; SpentItem[] debt; bytes details; bytes approval; } - event Origination(uint256 indexed loanId, address indexed issuer, bytes nlrDetails); - - function execute(Request calldata params) external virtual returns (Response memory response); - - function _packageTransfers(SpentItem[] memory loan, address borrower, address issuer) - internal - pure - returns (ConduitTransfer[] memory transfers) - { - uint256 i = 0; - transfers = new ConduitTransfer[](loan.length); - for (; i < loan.length;) { - ConduitItemType itemType; - SpentItem memory debt = loan[i]; - - assembly { - itemType := mload(debt) - switch itemType - case 1 {} - case 2 {} - case 3 {} - default { revert(0, 0) } //TODO: Update with error selector - InvalidContext(ContextErrors.INVALID_LOAN) - } - transfers[i] = ConduitTransfer({ - itemType: itemType, - from: issuer, - token: loan[i].token, - identifier: loan[i].identifier, - amount: loan[i].amount, - to: borrower - }); - unchecked { - ++i; - } - } - } + function originate(Request calldata params) external virtual; } diff --git a/src/originators/StrategistOriginator.sol b/src/originators/StrategistOriginator.sol index f484e929..77fbd483 100644 --- a/src/originators/StrategistOriginator.sol +++ b/src/originators/StrategistOriginator.sol @@ -18,7 +18,7 @@ * * Chainworks Labs */ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; @@ -31,6 +31,7 @@ import {ECDSA} from "solady/src/utils/ECDSA.sol"; import {SignatureCheckerLib} from "solady/src/utils/SignatureCheckerLib.sol"; import {Ownable} from "solady/src/auth/Ownable.sol"; import {Originator} from "starport-core/originators/Originator.sol"; +import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; // Validator abstract contract that lays out the necessary structure and functions for the validator contract StrategistOriginator is Ownable, Originator { @@ -40,7 +41,6 @@ contract StrategistOriginator is Ownable, Originator { struct Details { address custodian; - address conduit; address issuer; uint256 deadline; Offer offer; @@ -53,17 +53,10 @@ contract StrategistOriginator is Ownable, Originator { SpentItem[] debt; } - event CounterUpdated(); + event CounterUpdated(uint256); event HashInvalidated(bytes32 hash); - modifier onlyLoanManager() { - if (msg.sender != address(LM)) { - revert NotLoanManager(); - } - _; - } - error NotLoanManager(); error NotAuthorized(); error InvalidDebt(); @@ -110,14 +103,6 @@ contract StrategistOriginator is Ownable, Originator { emit StrategistTransferred(newStrategist); } - function _buildResponse(Request calldata params, Details memory details) - internal - virtual - returns (Response memory response) - { - response = Response({terms: details.offer.terms, issuer: details.issuer}); - } - // Encode the data with the account's nonce for generating a signature function encodeWithAccountCounter(bytes32 contextHash) public view virtual returns (bytes memory) { bytes32 hash = keccak256(abi.encode(ORIGINATOR_DETAILS_TYPEHASH, _counter, contextHash)); @@ -135,11 +120,11 @@ contract StrategistOriginator is Ownable, Originator { } function incrementCounter() external { - if (msg.sender != strategist || msg.sender != owner()) { + if (msg.sender != strategist && msg.sender != owner()) { revert NotAuthorized(); } _counter += uint256(blockhash(block.number - 1) << 0x80); - emit CounterUpdated(); + emit CounterUpdated(_counter); } // Function to generate the domain separator for signatures @@ -147,17 +132,23 @@ contract StrategistOriginator is Ownable, Originator { return _DOMAIN_SEPARATOR; } - function execute(Request calldata params) - external - virtual - override - onlyLoanManager - returns (Response memory response) - { + function originate(Request calldata params) external virtual override { Details memory details = abi.decode(params.details, (Details)); _validateOffer(params, details); - _execute(params, details); - response = _buildResponse(params, details); + + LoanManager.Loan memory loan = LoanManager.Loan({ + start: uint256(0), // are set in the loan manager + originator: address(0), // are set in the loan manager + custodian: details.custodian, + issuer: details.issuer, + borrower: params.borrower, + collateral: params.collateral, + debt: params.debt, + terms: details.offer.terms + }); + + CaveatEnforcer.CaveatWithApproval memory le; + LM.originate(new ConduitTransfer[](0), params.borrowerCaveat, le, loan); } function _validateAsk(Request calldata request, Details memory details) internal virtual { @@ -167,7 +158,7 @@ contract StrategistOriginator is Ownable, Originator { //loop through collateral and check if the collateral is the same - for (uint256 i = 0; i < request.collateral.length;) { + for (uint256 i = 0; i < request.debt.length;) { if ( request.debt[i].itemType != details.offer.debt[i].itemType || request.debt[i].token != details.offer.debt[i].token @@ -191,9 +182,6 @@ contract StrategistOriginator is Ownable, Originator { function _validateOffer(Request calldata request, Details memory details) internal virtual { bytes32 contextHash = keccak256(request.details); _validateSignature(keccak256(encodeWithAccountCounter(keccak256(request.details))), request.approval); - if (request.custodian != details.custodian) { - revert InvalidCustodian(); - } if (request.debt.length != details.offer.debt.length) { revert InvalidDebtLength(); } @@ -212,15 +200,6 @@ contract StrategistOriginator is Ownable, Originator { } } - function _execute(Request calldata request, Details memory details) internal virtual { - if ( - ConduitInterface(details.conduit).execute(_packageTransfers(request.debt, request.receiver, details.issuer)) - != ConduitInterface.execute.selector - ) { - revert ConduitTransferError(); - } - } - function _validateSignature(bytes32 hash, bytes memory signature) internal view virtual { if (!SignatureCheckerLib.isValidSignatureNow(strategist, hash, signature)) { revert InvalidSigner(); diff --git a/src/pricing/AstariaV1Pricing.sol b/src/pricing/AstariaV1Pricing.sol index ea220a53..879f6f55 100644 --- a/src/pricing/AstariaV1Pricing.sol +++ b/src/pricing/AstariaV1Pricing.sol @@ -1,4 +1,4 @@ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; import {CompoundInterestPricing} from "starport-core/pricing/CompoundInterestPricing.sol"; diff --git a/src/pricing/BasePricing.sol b/src/pricing/BasePricing.sol index 7b37409f..17b79767 100644 --- a/src/pricing/BasePricing.sol +++ b/src/pricing/BasePricing.sol @@ -18,7 +18,7 @@ * * Chainworks Labs */ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; import {Pricing} from "starport-core/pricing/Pricing.sol"; diff --git a/src/pricing/BaseRecallPricing.sol b/src/pricing/BaseRecallPricing.sol index 67bed392..f4cd3366 100644 --- a/src/pricing/BaseRecallPricing.sol +++ b/src/pricing/BaseRecallPricing.sol @@ -18,7 +18,7 @@ * * Chainworks Labs */ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; import {BasePricing} from "starport-core/pricing/BasePricing.sol"; diff --git a/src/pricing/CompoundInterestPricing.sol b/src/pricing/CompoundInterestPricing.sol index bc4ebef8..ac88ec4f 100644 --- a/src/pricing/CompoundInterestPricing.sol +++ b/src/pricing/CompoundInterestPricing.sol @@ -1,4 +1,4 @@ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; import {BasePricing} from "starport-core/pricing/BasePricing.sol"; diff --git a/src/pricing/Pricing.sol b/src/pricing/Pricing.sol index 4e986313..c4d911fb 100644 --- a/src/pricing/Pricing.sol +++ b/src/pricing/Pricing.sol @@ -18,7 +18,7 @@ * * Chainworks Labs */ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {LoanManager} from "starport-core/LoanManager.sol"; import {SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; diff --git a/src/pricing/SimpleInterestPricing.sol b/src/pricing/SimpleInterestPricing.sol index 3b4f59eb..7b2202ab 100644 --- a/src/pricing/SimpleInterestPricing.sol +++ b/src/pricing/SimpleInterestPricing.sol @@ -18,7 +18,7 @@ * * Chainworks Labs */ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {ReceivedItem, BasePricing} from "starport-core/pricing/BasePricing.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; diff --git a/src/scripts/Deploy.sol b/src/scripts/Deploy.sol index b9b5afdd..18beff8c 100644 --- a/src/scripts/Deploy.sol +++ b/src/scripts/Deploy.sol @@ -1,4 +1,4 @@ -//pragma solidity =0.8.17; +//pragma solidity ^0.8.17;; // //import "forge-std/Script.sol"; //import "../LoanManager.sol"; diff --git a/test/AstariaV1Test.sol b/test/AstariaV1Test.sol index 5bebc7e5..396921e4 100644 --- a/test/AstariaV1Test.sol +++ b/test/AstariaV1Test.sol @@ -1,4 +1,4 @@ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import "forge-std/console2.sol"; diff --git a/test/StarPortTest.sol b/test/StarPortTest.sol index b969415f..4192a471 100644 --- a/test/StarPortTest.sol +++ b/test/StarPortTest.sol @@ -1,4 +1,4 @@ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; @@ -66,7 +66,7 @@ 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 {BorrowerEnforcerBNPL} from "starport-core/enforcers/BorrowerEnforcerBNPL.sol"; import {LenderEnforcer} from "starport-core/enforcers/LenderEnforcer.sol"; @@ -155,6 +155,8 @@ contract StarPortTest is BaseOrderTest { StrategistOriginator SO; BorrowerEnforcer borrowerEnforcer; + BorrowerEnforcerBNPL borrowerEnforcerBNPL; + LenderEnforcer lenderEnforcer; bytes32 conduitKeyRefinancer; @@ -221,8 +223,10 @@ contract StarPortTest is BaseOrderTest { erc721s[2].mint(lender.addr, 1); } borrowerEnforcer = new BorrowerEnforcer(); + borrowerEnforcerBNPL = new BorrowerEnforcerBNPL(); lenderEnforcer = new LenderEnforcer(); vm.label(address(borrowerEnforcer), "BorrowerEnforcer"); + vm.label(address(borrowerEnforcerBNPL), "BorrowerEnforcerBNPL"); vm.label(address(lenderEnforcer), "LenderEnforcer"); conduitKeyOne = bytes32(uint256(uint160(address(lender.addr))) << 96); @@ -275,12 +279,6 @@ contract StarPortTest is BaseOrderTest { // ConsiderationItem[] collateral20; SpentItem[] activeDebt; - struct NewLoanData { - address custodian; - LoanManager.Caveat[] caveats; - bytes details; - } - function _setApprovalsForSpentItems(address approver, SpentItem[] memory items) internal { vm.startPrank(approver); uint256 i = 0; @@ -300,6 +298,16 @@ contract StarPortTest is BaseOrderTest { vm.stopPrank(); } + function _emptyCaveat() internal returns (CaveatEnforcer.CaveatWithApproval memory) { + return CaveatEnforcer.CaveatWithApproval({ + v: 0, + r: bytes32(0), + s: bytes32(0), + salt: bytes32(0), + caveat: new CaveatEnforcer.Caveat[](0) + }); + } + // loan.borrower and signer.addr could be mismatched function _generateSignedCaveatBorrower(LoanManager.Loan memory loan, Account memory signer, bytes32 salt) public @@ -526,42 +534,6 @@ contract StarPortTest is BaseOrderTest { } } - function buyNowPayLater( - AdvancedOrder memory thingToBuy, - NewLoanData memory loanData, - 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 - // ); - } - function _buildContractOrder(address offerer, OfferItem[] memory offer, ConsiderationItem[] memory consider) internal view @@ -726,246 +698,6 @@ contract StarPortTest is BaseOrderTest { vm.stopPrank(); } - function _buyNowPLNLR( - AdvancedOrder memory x, - // LoanManager.Loan memory loanAsk, - 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 = 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) - internal - returns (LoanManager.Loan memory loan) - { - return _executeNLR(nlr, collateral, ""); - } - - function _executeNLR( - LoanManager.Obligation memory nlr, - ConsiderationItem[] memory collateral, - bytes memory revertReason - ) internal returns (LoanManager.Loan memory loan) { - // 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 { vm.startPrank(borrower); erc20s[0].approve(address(consideration), amount); @@ -1047,40 +779,6 @@ contract StarPortTest is BaseOrderTest { loan.custodian = incomingCustodian; } - function _createLoanWithCaveat( - address lender, - LoanManager.Terms memory terms, - ConsiderationItem memory collateralItem, - 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 - // ); - } - function _getERC20SpentItem(TestERC20 token, uint256 amount) internal pure returns (SpentItem memory) { return SpentItem({ itemType: ItemType.ERC20, diff --git a/test/fuzz-testing/differential-fuzzing/TestStarPortLib.sol b/test/fuzz-testing/differential-fuzzing/TestStarPortLib.sol new file mode 100644 index 00000000..d4835cfb --- /dev/null +++ b/test/fuzz-testing/differential-fuzzing/TestStarPortLib.sol @@ -0,0 +1,139 @@ +pragma solidity =0.8.17; + +import "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; + +import {ItemType, ReceivedItem, SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import { + ConsiderationItem, + AdvancedOrder, + CriteriaResolver, + OrderType +} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {Conduit} from "seaport-core/src/conduit/Conduit.sol"; +import {ConduitController} from "seaport-core/src/conduit/ConduitController.sol"; +import {StarPortLib} from "starport-core/lib/StarPortLib.sol"; +import {RefStarPortLib} from "starport-core/lib/RefStarPortLib.sol"; +import "starport-test/utils/FuzzStructs.sol" as Fuzz; +import {Bound} from "starport-test/utils/Bound.sol"; +import {DeepEq} from "starport-test/utils/DeepEq.sol"; + +contract DiffFuzzTestStarPortLib is Test, Bound, DeepEq { + StarPortLibImpl testContract; + RefStarPortLibImpl refContract; + + function setUp() public { + testContract = new StarPortLibImpl(); + refContract = new RefStarPortLibImpl(); + } + + function testSpentToReceived(Fuzz.SpentItem[] memory unbSpentItems) public view { + SpentItem[] memory spentItems = _boundSpentItems(unbSpentItems); + + ReceivedItem[] memory consideration0 = testContract.toReceivedItems(spentItems, address(1)); + ReceivedItem[] memory consideration1 = refContract.toReceivedItems(spentItems, address(1)); + + _deepEq(consideration0, consideration1); + } + + function testUnboundSpentToReceived(Fuzz.SpentItem[] memory unbSpentItems) public { + console.log("testUnboundSpentToReceived"); + (bool success,) = address(refContract).call( + abi.encodeWithSelector(RefStarPortLibImpl.toReceivedItems.selector, unbSpentItems, address(1)) + ); + bool expectRevert = !success; + + (success,) = address(testContract).call( + abi.encodeWithSelector(StarPortLibImpl.toReceivedItems.selector, unbSpentItems, address(1)) + ); + if (expectRevert) { + assertTrue(!success, "expected revert"); + } else { + assertTrue(success, "expected success"); + } + } +} + +abstract contract BaseTestStarPortLib is Test { + StarPortLibImpl testContract; + + function _setUp(address testImpl) internal { + testContract = StarPortLibImpl(testImpl); + } + + function testValidateSalt(address user, bytes32 salt) public { + testContract.validateSalt(user, salt); + + assert(testContract.usedSalts(user, salt)); + + vm.expectRevert(abi.encodeWithSelector(StarPortLib.InvalidSalt.selector)); + testContract.validateSalt(user, salt); + } + + function testSpentToReceived() public { + SpentItem[] memory spentItems = new SpentItem[](2); + spentItems[0] = SpentItem({itemType: ItemType.ERC20, token: address(2), identifier: 3, amount: 4}); + + spentItems[1] = SpentItem({itemType: ItemType.ERC20, token: address(2), identifier: 3, amount: 4}); + + address recipient = address(1); + ReceivedItem[] memory consideration0 = testContract.toReceivedItems(spentItems, recipient); + + assertEq(consideration0.length, spentItems.length); + for (uint256 i = 0; i < consideration0.length; i++) { + assert(consideration0[i].itemType == spentItems[i].itemType); + assertEq(consideration0[i].token, spentItems[i].token); + assertEq(consideration0[i].identifier, spentItems[i].identifier); + assertEq(consideration0[i].amount, spentItems[i].amount); + assertEq(consideration0[i].recipient, recipient); + } + } +} + +contract TestStarPortLib is BaseTestStarPortLib { + function setUp() public { + _setUp(address(new StarPortLibImpl())); + } +} + +contract TestRefStarPortLib is BaseTestStarPortLib { + function setUp() public { + _setUp(address(new RefStarPortLibImpl())); + } +} + +contract StarPortLibImpl { + using RefStarPortLib for *; + + mapping(address => mapping(bytes32 => bool)) public usedSalts; + + function toReceivedItems(SpentItem[] calldata spentItems, address recipient) + external + pure + returns (ReceivedItem[] memory consideration) + { + return spentItems.toReceivedItems(recipient); + } + + function validateSalt(address user, bytes32 salt) external { + usedSalts.validateSalt(user, salt); + } +} + +contract RefStarPortLibImpl { + using RefStarPortLib for *; + + mapping(address => mapping(bytes32 => bool)) public usedSalts; + + function toReceivedItems(SpentItem[] calldata spentItems, address recipient) + external + pure + returns (ReceivedItem[] memory consideration) + { + return spentItems.toReceivedItems(recipient); + } + + function validateSalt(address user, bytes32 salt) external { + usedSalts.validateSalt(user, salt); + } +} diff --git a/test/fuzz-testing/differential-fuzzing/TestUtils.sol b/test/fuzz-testing/differential-fuzzing/TestUtils.sol deleted file mode 100644 index 01326f44..00000000 --- a/test/fuzz-testing/differential-fuzzing/TestUtils.sol +++ /dev/null @@ -1,157 +0,0 @@ -pragma solidity =0.8.17; - -import "forge-std/Test.sol"; -import {Vm} from "forge-std/Vm.sol"; - -import {ItemType, ReceivedItem, SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; -import { - ConsiderationItem, - AdvancedOrder, - CriteriaResolver, - OrderType -} from "seaport-types/src/lib/ConsiderationStructs.sol"; -import {Conduit} from "seaport-core/src/conduit/Conduit.sol"; -import {ConduitController} from "seaport-core/src/conduit/ConduitController.sol"; -import {StarPortLib} from "starport-core/lib/StarPortLib.sol"; - -contract TestStarLiteUtils is Test { - TestContract testContract; - mapping(address => mapping(bytes32 => bool)) usedSalts; - - function setUp() public { - testContract = new TestContract(); - } - - function testValidateSaltRef(address user, bytes32 salt) public { - StarPortLib.validateSaltRef(usedSalts, user, salt); - - assert(usedSalts[user][salt]); - vm.expectRevert(abi.encodeWithSelector(StarPortLib.InvalidSalt.selector)); - - StarPortLib.validateSaltRef(usedSalts, user, salt); - } - - function testValidateSaltOpt(address user, bytes32 salt) public { - StarPortLib.validateSalt(usedSalts, user, salt); - - assert(usedSalts[user][salt]); - - vm.expectRevert(abi.encodeWithSelector(StarPortLib.InvalidSalt.selector)); - StarPortLib.validateSalt(usedSalts, user, salt); - } - - function testSpentToReceived() public { - SpentItem[] memory spentItems = new SpentItem[](2); - spentItems[0] = SpentItem({itemType: ItemType.ERC20, token: address(2), identifier: 3, amount: 4}); - - spentItems[1] = SpentItem({itemType: ItemType.ERC20, token: address(2), identifier: 3, amount: 4}); - - ReceivedItem[] memory consideration0 = testContract.spentToReceivedBoring(spentItems, address(1)); - - ReceivedItem[] memory consideration1 = testContract.spentToReceivedSexc(spentItems, address(1)); - - assertEq(consideration0.length, consideration1.length); - for (uint256 i = 0; i < consideration0.length; i++) { - assert(consideration0[i].itemType == consideration1[i].itemType); - assertEq(consideration0[i].token, consideration1[i].token); - assertEq(consideration0[i].identifier, consideration1[i].identifier); - assertEq(consideration0[i].amount, consideration1[i].amount); - assertEq(consideration0[i].recipient, consideration1[i].recipient); - } - } - - function testEncodeReceivedWithRecipient() public { - ReceivedItem[] memory receivedItems = new ReceivedItem[](2); - receivedItems[0] = ReceivedItem({ - itemType: ItemType.ERC20, - token: address(2), - identifier: 3, - amount: 4, - recipient: payable(address(5)) - }); - - receivedItems[1] = ReceivedItem({ - itemType: ItemType.ERC20, - token: address(2), - identifier: 3, - amount: 4, - recipient: payable(address(6)) - }); - - ReceivedItem[] memory consideration0 = - testContract.encodeReceivedItemsWithRecipientBoring(receivedItems, address(1)); - - ReceivedItem[] memory consideration1 = - testContract.encodeReceivedItemsWithRecipientSexc(receivedItems, address(1)); - - assertEq(consideration0.length, consideration1.length); - for (uint256 i = 0; i < consideration0.length; i++) { - assert(consideration0[i].itemType == consideration1[i].itemType); - assertEq(consideration0[i].token, consideration1[i].token); - assertEq(consideration0[i].identifier, consideration1[i].identifier); - assertEq(consideration0[i].amount, consideration1[i].amount); - assertEq(consideration0[i].recipient, consideration1[i].recipient); - } - } -} - -contract TestContract { - using {StarPortLib.toReceivedItems} for SpentItem[]; - using {StarPortLib.encodeWithRecipient} for ReceivedItem[]; - - function encodeReceivedItemsWithRecipientSexc(ReceivedItem[] calldata receivedItems, address recipient) - external - pure - returns (ReceivedItem[] memory consideration) - { - return receivedItems.encodeWithRecipient(recipient); - } - - function encodeReceivedItemsWithRecipientBoring(ReceivedItem[] calldata receivedItems, address recipient) - external - pure - returns (ReceivedItem[] memory consideration) - { - consideration = new ReceivedItem[](receivedItems.length); - for (uint256 i = 0; i < receivedItems.length;) { - consideration[i] = ReceivedItem({ - itemType: receivedItems[i].itemType, - token: receivedItems[i].token, - identifier: receivedItems[i].identifier, - amount: receivedItems[i].amount, - recipient: payable(recipient) - }); - unchecked { - ++i; - } - } - } - - function spentToReceivedSexc(SpentItem[] calldata spentItems, address recipient) - external - pure - returns (ReceivedItem[] memory consideration) - { - return spentItems.toReceivedItems(recipient); - } - - function spentToReceivedBoring(SpentItem[] calldata spentItems, address recipient) - external - pure - returns (ReceivedItem[] memory consideration) - { - consideration = new ReceivedItem[](spentItems.length); - for (uint256 i = 0; i < spentItems.length;) { - consideration[i] = ReceivedItem({ - itemType: spentItems[i].itemType, - token: spentItems[i].token, - identifier: spentItems[i].identifier, - amount: spentItems[i].amount, - recipient: payable(recipient) - }); - unchecked { - ++i; - } - } - } -} diff --git a/test/integration-testing/TestAstariaV1Loan.sol b/test/integration-testing/TestAstariaV1Loan.sol index e90d2164..4d571f9a 100644 --- a/test/integration-testing/TestAstariaV1Loan.sol +++ b/test/integration-testing/TestAstariaV1Loan.sol @@ -1,3 +1,5 @@ +pragma solidity ^0.8.17; + import "starport-test/AstariaV1Test.sol"; import {BaseRecall} from "starport-core/hooks/BaseRecall.sol"; diff --git a/test/integration-testing/TestLoanCombinations.t.sol b/test/integration-testing/TestLoanCombinations.t.sol index d955d558..d416b4c8 100644 --- a/test/integration-testing/TestLoanCombinations.t.sol +++ b/test/integration-testing/TestLoanCombinations.t.sol @@ -1,3 +1,5 @@ +pragma solidity ^0.8.17; + import "starport-test/StarPortTest.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; import {LibString} from "solady/src/utils/LibString.sol"; diff --git a/test/integration-testing/TestNewLoan.sol b/test/integration-testing/TestNewLoan.sol index 7cd81cfe..dfe8649d 100644 --- a/test/integration-testing/TestNewLoan.sol +++ b/test/integration-testing/TestNewLoan.sol @@ -1,6 +1,56 @@ +pragma solidity ^0.8.17; + import "starport-test/StarPortTest.sol"; import {AstariaV1Pricing} from "starport-core/pricing/AstariaV1Pricing.sol"; import {Actions} from "starport-core/lib/StarPortLib.sol"; +import {BNPLHelper, IFlashLoanRecipient} from "starport-core/BNPLHelper.sol"; +import {Originator} from "starport-core/originators/Originator.sol"; + +contract FlashLoan { + function flashLoan( + IFlashLoanRecipient recipient, + address[] memory tokens, + uint256[] memory amounts, + bytes memory userData + ) external { + uint256[] memory feeAmounts = new uint256[](tokens.length); + uint256[] memory preLoanBalances = new uint256[](tokens.length); + + // Used to ensure `tokens` is sorted in ascending order, which ensures token uniqueness. + ERC20 previousToken = ERC20(address(0)); + + for (uint256 i = 0; i < tokens.length; ++i) { + ERC20 token = ERC20(tokens[i]); + uint256 amount = amounts[i]; + + // previousToken = token; + + preLoanBalances[i] = token.balanceOf(address(this)); + feeAmounts[i] = uint256(0); + + token.transfer(address(recipient), amount); + } + + recipient.receiveFlashLoan(tokens, amounts, feeAmounts, userData); + + for (uint256 i = 0; i < tokens.length; ++i) { + ERC20 token = ERC20(tokens[i]); + uint256 preLoanBalance = preLoanBalances[i]; + + // Checking for loan repayment first (without accounting for fees) makes for simpler debugging, and results + // in more accurate revert reasons if the flash loan protocol fee percentage is zero. + uint256 postLoanBalance = token.balanceOf(address(this)); + require(postLoanBalance >= preLoanBalance, "loan was not paid back"); + + // No need for checked arithmetic since we know the loan was fully repaid. + uint256 receivedFeeAmount = postLoanBalance - preLoanBalance; + require(receivedFeeAmount >= feeAmounts[i], "no fees paid"); + + // _payFeeAmount(token, receivedFeeAmount); + // emit FlashLoan(recipient, token, amounts[i], receivedFeeAmount); + } + } +} contract TestNewLoan is StarPortTest { function testNewLoanERC721CollateralDefaultTerms2() public returns (LoanManager.Loan memory) { @@ -114,101 +164,234 @@ contract TestNewLoan is StarPortTest { } 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) - // }); - + ConsiderationItem[] memory want = new ConsiderationItem[](1); + want[0] = ConsiderationItem({ + token: address(erc20s[0]), + startAmount: 100, + endAmount: 100, + 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 - // }) - // }); + 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 r; + bytes32 s; + uint8 v; + (r, s, v) = getSignatureComponents( + consideration, seller.key, consideration.getOrderHash(OrderParametersLib.toOrderComponents(thingToSell, 0)) + ); + + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + + loan.collateral[0].token = sellingNFT[0].token; + loan.collateral[0].identifier = sellingNFT[0].identifierOrCriteria; + loan.collateral[0].amount = 1; + + loan.debt[0].identifier = 0; + loan.debt[0].amount = 100; + + BNPLHelper helper = new BNPLHelper(address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)); + + AdvancedOrder[] memory orders = new AdvancedOrder[](2); + orders[0] = AdvancedOrder({ + parameters: thingToSell, + numerator: 1, + denominator: 1, + signature: abi.encodePacked(r, s, v), + extraData: "" + }); + OrderParameters memory buyerOrderParams = + createMirrorOrderParameters(thingToSell, payable(borrower.addr), address(0), bytes32(0)); + bytes32 buyingHash = consideration.getOrderHash(OrderParametersLib.toOrderComponents(buyerOrderParams, 0)); //0 is for the current nonce + (r, s, v) = getSignatureComponents(consideration, borrower.key, buyingHash); + orders[1] = AdvancedOrder({ + parameters: buyerOrderParams, + numerator: 1, + denominator: 1, + signature: abi.encodePacked(r, s, v), + extraData: "" + }); + { + _setApprovalsForSpentItems(loan.issuer, loan.debt); + _setApprovalsForSpentItems(loan.borrower, loan.debt); + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + } + + address balancerVault = address(0xBA12222222228d8Ba445958a75a0704d566BF2C8); + vm.etch(balancerVault, address(new FlashLoan()).code); + deal(address(erc20s[0]), balancerVault, type(uint128).max); + { + LoanManager.Loan memory loan2 = loan; + bytes32 buyingHash2 = buyingHash; + Fulfillment[] memory fill = new Fulfillment[](2); + fill[0] = Fulfillment({ + offerComponents: new FulfillmentComponent[](1), + considerationComponents: new FulfillmentComponent[](1) + }); + + fill[0].offerComponents[0] = FulfillmentComponent({orderIndex: 0, itemIndex: 0}); + fill[0].considerationComponents[0] = FulfillmentComponent({orderIndex: 1, itemIndex: 0}); + fill[1] = Fulfillment({ + offerComponents: new FulfillmentComponent[](1), + considerationComponents: new FulfillmentComponent[](1) + }); + + fill[1].offerComponents[0] = FulfillmentComponent({orderIndex: 1, itemIndex: 0}); + + fill[1].considerationComponents[0] = FulfillmentComponent({orderIndex: 0, itemIndex: 0}); + + address[] memory tokens = new address[](1); + tokens[0] = address(erc20s[0]); + uint256[] memory amounts = new uint256[](1); + amounts[0] = 100; + + helper.makeFlashLoan( + tokens, + amounts, + abi.encode( + BNPLHelper.Execution({ + lm: address(LM), + seaport: address(seaport), + borrower: borrower.addr, + borrowerCaveat: signCaveatForAccount( + CaveatEnforcer.Caveat({ + enforcer: address(borrowerEnforcerBNPL), + deadline: block.timestamp + 1 days, + data: abi.encode( + BorrowerEnforcerBNPL.Details({ + loan: loan2, + offerHash: buyingHash2, + additionalTransfer: ConduitTransfer({ + itemType: ConduitItemType.ERC20, + identifier: 0, + token: address(erc20s[0]), + amount: 100, + from: borrower.addr, + to: address(0) + }), + seaport: address(consideration) + }) + ) + }), + bytes32(uint256(1)), + borrower + ), + lenderCaveat: _generateSignedCaveatLender(loan2, lender, bytes32(uint256(1))), + loan: loan2, + orders: orders, + resolvers: new CriteriaResolver[](0), + fulfillments: fill + }) + ) + ); + } + } - // TermEnforcer TE = new TermEnforcer(); + function testNewLoanViaOriginatorLenderApproval() public { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + + StrategistOriginator.Details memory newLoanDetails = StrategistOriginator.Details({ + custodian: LM.defaultCustodian(), + issuer: lender.addr, + deadline: block.timestamp + 100, + offer: StrategistOriginator.Offer({ + terms: loan.terms, + salt: bytes32(0), + collateral: loan.collateral, + debt: loan.debt + }) + }); - // TermEnforcer.Details memory TEDetails = - // TermEnforcer.Details({pricing: address(pricing), hook: address(hook), handler: address(handler)}); + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + + (uint8 v, bytes32 r, bytes32 s) = + vm.sign(strategist.key, keccak256(SO.encodeWithAccountCounter(keccak256(abi.encode(newLoanDetails))))); + + uint256 borrowerBalanceBefore = erc20s[0].balanceOf(borrower.addr); + uint256 lenderBalanceBefore = erc20s[0].balanceOf(lender.addr); + vm.prank(lender.addr); + LM.setOriginateApproval(address(SO), LoanManager.ApprovalType.LENDER); + vm.prank(borrower.addr); + SO.originate( + Originator.Request({ + borrower: borrower.addr, + borrowerCaveat: _generateSignedCaveatBorrower(loan, borrower, bytes32(uint256(5))), + collateral: loan.collateral, + debt: loan.debt, + details: abi.encode(newLoanDetails), + approval: abi.encodePacked(r, s, v) + }) + ); + assert(erc20s[0].balanceOf(borrower.addr) == borrowerBalanceBefore + loan.debt[0].amount); + assert(erc20s[0].balanceOf(lender.addr) == lenderBalanceBefore - loan.debt[0].amount); + assert(erc721s[0].ownerOf(loan.collateral[0].identifier) == address(LM.defaultCustodian())); + } - // LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); - // caveats[0] = LoanManager.Caveat({enforcer: address(TE), terms: abi.encode(TEDetails)}); + function testNewLoanViaOriginatorBorrowerApprovalAndLenderApproval() public { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + + StrategistOriginator.Details memory newLoanDetails = StrategistOriginator.Details({ + custodian: LM.defaultCustodian(), + issuer: lender.addr, + deadline: block.timestamp + 100, + offer: StrategistOriginator.Offer({ + terms: loan.terms, + salt: bytes32(0), + collateral: loan.collateral, + debt: loan.debt + }) + }); - // buyNowPayLater( - // advThingToSell, - // NewLoanData(address(custody), caveats, abi.encode(loanDetails)), - // StrategistOriginator(SO), - // selectedCollateral - // ); + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + + (uint8 v, bytes32 r, bytes32 s) = + vm.sign(strategist.key, keccak256(SO.encodeWithAccountCounter(keccak256(abi.encode(newLoanDetails))))); + + uint256 borrowerBalanceBefore = erc20s[0].balanceOf(borrower.addr); + uint256 lenderBalanceBefore = erc20s[0].balanceOf(lender.addr); + vm.prank(borrower.addr); + LM.setOriginateApproval(address(SO), LoanManager.ApprovalType.BORROWER); + vm.prank(lender.addr); + LM.setOriginateApproval(address(SO), LoanManager.ApprovalType.LENDER); + vm.prank(borrower.addr); + SO.originate( + Originator.Request({ + borrower: borrower.addr, + borrowerCaveat: _emptyCaveat(), + collateral: loan.collateral, + debt: loan.debt, + details: abi.encode(newLoanDetails), + approval: abi.encodePacked(r, s, v) + }) + ); + assert(erc20s[0].balanceOf(borrower.addr) == borrowerBalanceBefore + loan.debt[0].amount); + assert(erc20s[0].balanceOf(lender.addr) == lenderBalanceBefore - loan.debt[0].amount); + assert(erc721s[0].ownerOf(loan.collateral[0].identifier) == address(LM.defaultCustodian())); } function testSettleLoan() public { diff --git a/test/integration-testing/TestRepayLoan.sol b/test/integration-testing/TestRepayLoan.sol index a4438468..74f809bb 100644 --- a/test/integration-testing/TestRepayLoan.sol +++ b/test/integration-testing/TestRepayLoan.sol @@ -1,3 +1,5 @@ +pragma solidity ^0.8.17; + import "starport-test/StarPortTest.sol"; contract TestRepayLoan is StarPortTest { diff --git a/test/unit-testing/EnforcerTest.t.sol b/test/unit-testing/EnforcerTest.t.sol index 747bb039..c62f97e2 100644 --- a/test/unit-testing/EnforcerTest.t.sol +++ b/test/unit-testing/EnforcerTest.t.sol @@ -1,3 +1,5 @@ +pragma solidity ^0.8.17; + import "starport-test/StarPortTest.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; import {LibString} from "solady/src/utils/LibString.sol"; diff --git a/test/unit-testing/TestBorrowerEnforcer.sol b/test/unit-testing/TestBorrowerEnforcer.sol new file mode 100644 index 00000000..ba1b836c --- /dev/null +++ b/test/unit-testing/TestBorrowerEnforcer.sol @@ -0,0 +1,46 @@ +import "starport-test/StarPortTest.sol"; +import {BorrowerEnforcer} from "starport-core/enforcers/BorrowerEnforcer.sol"; +import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; + +import "forge-std/console.sol"; + +contract TestBorrowerEnforcer is StarPortTest { + function testBERevertAdditionalTransfers() external { + ConduitTransfer[] memory additionalTransfers = new ConduitTransfer[](1); + additionalTransfers[0] = ConduitTransfer({ + token: address(0), + amount: 0, + to: address(0), + from: address(0), + identifier: 0, + itemType: ConduitItemType.ERC20 + }); + + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + BorrowerEnforcer.Details memory details = BorrowerEnforcer.Details({loan: loan}); + vm.expectRevert(BorrowerEnforcer.InvalidAdditionalTransfer.selector); + borrowerEnforcer.validate(additionalTransfers, loan, abi.encode(details)); + } + + function testBERevertInvalidLoanTerms() external { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + + BorrowerEnforcer.Details memory details = BorrowerEnforcer.Details({loan: loan}); + details.loan.borrower = lender.addr; + vm.expectRevert(BorrowerEnforcer.InvalidLoanTerms.selector); + borrowerEnforcer.validate(new ConduitTransfer[](0), generateDefaultLoanTerms(), abi.encode(details)); + } + + function testBEValidLoanTerms() external view { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + borrowerEnforcer.validate(new ConduitTransfer[](0), loan, abi.encode(BorrowerEnforcer.Details({loan: loan}))); + } + + function testBEValidLoanTermsAnyIssuer() external view { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + BorrowerEnforcer.Details memory details = BorrowerEnforcer.Details({loan: loan}); + details.loan.issuer = address(0); + + borrowerEnforcer.validate(new ConduitTransfer[](0), loan, abi.encode(BorrowerEnforcer.Details({loan: loan}))); + } +} diff --git a/test/unit-testing/TestCustodian.sol b/test/unit-testing/TestCustodian.sol index 9bff8c5d..1e87a46f 100644 --- a/test/unit-testing/TestCustodian.sol +++ b/test/unit-testing/TestCustodian.sol @@ -1,3 +1,5 @@ +pragma solidity ^0.8.17; + import "starport-test/StarPortTest.sol"; import {DeepEq} from "starport-test/utils/DeepEq.sol"; import {MockCall} from "starport-test/utils/MockCall.sol"; diff --git a/test/unit-testing/TestLenderEnforcer.sol b/test/unit-testing/TestLenderEnforcer.sol new file mode 100644 index 00000000..abf3acb6 --- /dev/null +++ b/test/unit-testing/TestLenderEnforcer.sol @@ -0,0 +1,61 @@ +import "starport-test/StarPortTest.sol"; +import {LenderEnforcer} from "starport-core/enforcers/LenderEnforcer.sol"; +import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; + +import "forge-std/console.sol"; + +contract TestLenderEnforcer is StarPortTest { + function testLERevertAdditionalTransfersFromLender() external { + ConduitTransfer[] memory additionalTransfers = new ConduitTransfer[](1); + additionalTransfers[0] = ConduitTransfer({ + token: address(0), + amount: 0, + to: address(0), + from: lender.addr, + identifier: 0, + itemType: ConduitItemType.ERC20 + }); + + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + + vm.expectRevert(LenderEnforcer.InvalidAdditionalTransfer.selector); + lenderEnforcer.validate(additionalTransfers, loan, abi.encode(LenderEnforcer.Details({loan: loan}))); + } + + function testLERevertInvalidLoanTerms() external { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + + LenderEnforcer.Details memory details = LenderEnforcer.Details({loan: loan}); + details.loan.custodian = borrower.addr; + vm.expectRevert(LenderEnforcer.InvalidLoanTerms.selector); + + lenderEnforcer.validate(new ConduitTransfer[](0), generateDefaultLoanTerms(), abi.encode(details)); + } + + function testLEValidLoanTermsWithAdditionalTransfers() external view { + ConduitTransfer[] memory additionalTransfers = new ConduitTransfer[](1); + additionalTransfers[0] = ConduitTransfer({ + token: address(0), + amount: 0, + to: address(0), + from: address(0), + identifier: 0, + itemType: ConduitItemType.ERC20 + }); + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + lenderEnforcer.validate(additionalTransfers, loan, abi.encode(LenderEnforcer.Details({loan: loan}))); + } + + function testLEValidLoanTerms() external view { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + lenderEnforcer.validate(new ConduitTransfer[](0), loan, abi.encode(LenderEnforcer.Details({loan: loan}))); + } + + function testLEValidLoanTermsAnyBorrower() external view { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + LenderEnforcer.Details memory details = LenderEnforcer.Details({loan: loan}); + details.loan.borrower = address(0); + + lenderEnforcer.validate(new ConduitTransfer[](0), loan, abi.encode(LenderEnforcer.Details({loan: loan}))); + } +} diff --git a/test/unit-testing/TestLoanManager.sol b/test/unit-testing/TestLoanManager.sol index a838bd1d..0ee80df1 100644 --- a/test/unit-testing/TestLoanManager.sol +++ b/test/unit-testing/TestLoanManager.sol @@ -1,3 +1,5 @@ +pragma solidity ^0.8.17; + import "starport-test/StarPortTest.sol"; import {StarPortLib} from "starport-core/lib/StarPortLib.sol"; import {DeepEq} from "starport-test/utils/DeepEq.sol"; @@ -50,19 +52,23 @@ contract MockOriginator is StrategistOriginator, TokenReceiverInterface { }); } - function execute(Request calldata request) external override returns (Response memory response) { - address issuer = address(this); - if (request.details.length > 0) { - if (request.debt[0].itemType != ItemType.NATIVE) { - StrategistOriginator.Details memory details = - abi.decode(request.details, (StrategistOriginator.Details)); - issuer = details.issuer == address(0) ? issuer : details.issuer; - _execute(request, details); - } else { - payable(request.receiver).call{value: request.debt[0].amount}(""); - } - } - return Response({terms: terms(request.details), issuer: address(this)}); + function originate(Request calldata params) external virtual override { + StrategistOriginator.Details memory details = abi.decode(params.details, (StrategistOriginator.Details)); + _validateOffer(params, details); + + LoanManager.Loan memory loan = LoanManager.Loan({ + start: uint256(0), // are set in the loan manager + originator: address(0), // are set in the loan manager + custodian: details.custodian, + issuer: details.issuer, + borrower: params.borrower, + collateral: params.collateral, + debt: params.debt, + terms: details.offer.terms + }); + + CaveatEnforcer.CaveatWithApproval memory le; + LM.originate(new ConduitTransfer[](0), params.borrowerCaveat, le, loan); } receive() external payable {} @@ -128,6 +134,21 @@ contract TestLoanManager is StarPortTest, DeepEq { LM.settle(activeLoan); } + event Paused(); + event UnPaused(); + + function testPause() public { + vm.expectEmit(); + emit Paused(); + LM.pause(); + } + + function testUnPause() public { + vm.expectEmit(); + emit UnPaused(); + LM.unPause(); + } + function testIssued() public { assert(LM.issued(activeLoan.getId())); } @@ -173,6 +194,13 @@ contract TestLoanManager is StarPortTest, DeepEq { vm.stopPrank(); } + function testCannotOriginateWhilePaused() public { + LM.pause(); + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + vm.expectRevert(abi.encodeWithSelector(LoanManager.IsPaused.selector)); + LM.originate(new ConduitTransfer[](0), _emptyCaveat(), _emptyCaveat(), loan); + } + function testNonDefaultCustodianCustodyCallSuccess() public { CaveatEnforcer.CaveatWithApproval memory borrowerCaveat; @@ -237,6 +265,27 @@ contract TestLoanManager is StarPortTest, DeepEq { vm.stopPrank(); } + function testInvalidIdentifierDebt() public { + CaveatEnforcer.CaveatWithApproval memory borrowerCaveat; + + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + loan.collateral[0].identifier = uint256(2); + loan.debt[0].identifier = uint256(2); + + CaveatEnforcer.CaveatWithApproval memory lenderCaveat = getLenderSignedCaveat({ + details: LenderEnforcer.Details({loan: loan}), + signer: lender, + salt: bytes32(0), + enforcer: address(lenderEnforcer) + }); + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + vm.startPrank(loan.borrower); + vm.expectRevert(abi.encodeWithSelector(LoanManager.InvalidItemIdentifier.selector)); + LM.originate(new ConduitTransfer[](0), borrowerCaveat, lenderCaveat, loan); + vm.stopPrank(); + } + function testInvalidAmountCollateral() public { CaveatEnforcer.CaveatWithApproval memory borrowerCaveat; diff --git a/test/unit-testing/TestStrategistOriginator.sol b/test/unit-testing/TestStrategistOriginator.sol new file mode 100644 index 00000000..94868bb0 --- /dev/null +++ b/test/unit-testing/TestStrategistOriginator.sol @@ -0,0 +1,464 @@ +pragma solidity ^0.8.17; + +import "starport-test/StarPortTest.sol"; +import {StarPortLib} from "starport-core/lib/StarPortLib.sol"; +import {DeepEq} from "starport-test/utils/DeepEq.sol"; +import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; +import "forge-std/console2.sol"; +import {SpentItemLib} from "seaport-sol/src/lib/SpentItemLib.sol"; +import {Originator} from "starport-core/originators/Originator.sol"; +import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; + +contract TestStrategistOriginator is StarPortTest, DeepEq { + using Cast for *; + using FixedPointMathLib for uint256; + + event StrategistTransferred(address newStrategist); + event CounterUpdated(uint256); + event HashInvalidated(bytes32 hash); + + using {StarPortLib.getId} for LoanManager.Loan; + + uint256 public borrowAmount = 100; + + function setUp() public virtual override { + super.setUp(); + } + + function testSetStrategist() public { + vm.expectEmit(); + emit StrategistTransferred(address(this)); + SO.setStrategist(address(this)); + } + + function testEncodeWithAccountCounter() public { + bytes32 contextHash = keccak256(abi.encodePacked(string("test"))); + bytes32 hash = keccak256(abi.encode(SO.ORIGINATOR_DETAILS_TYPEHASH(), SO.getCounter(), contextHash)); + + assert( + keccak256(SO.encodeWithAccountCounter(contextHash)) + == keccak256(abi.encodePacked(bytes1(0x19), bytes1(0x01), SO.domainSeparator(), hash)) + ); + } + + function testGetStrategistData() public { + StrategistOriginator SOO = new StrategistOriginator(LM, strategist.addr, 1e17, address(this)); + (address activeStrategist, uint256 strategistFee) = SOO.getStrategistData(); + assert(activeStrategist == strategist.addr); + assert(strategistFee == 1e17); + } + + function testIncrementCounterAsStrategist() public { + uint256 newCounter = SO.getCounter() + uint256(blockhash(block.number - 1) << 0x80); + vm.expectEmit(); + emit CounterUpdated(newCounter); + vm.prank(strategist.addr); + SO.incrementCounter(); + } + + function testIncrementCounterNotAuthorized() public { + vm.expectRevert(abi.encodeWithSelector(StrategistOriginator.NotAuthorized.selector)); + vm.prank(address(1)); + SO.incrementCounter(); + } + + function testInvalidDeadline() public { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + + StrategistOriginator.Details memory newLoanDetails = StrategistOriginator.Details({ + custodian: LM.defaultCustodian(), + issuer: lender.addr, + deadline: block.timestamp + 100, + offer: StrategistOriginator.Offer({ + terms: loan.terms, + salt: bytes32(0), + collateral: loan.collateral, + debt: loan.debt + }) + }); + + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + + (uint8 v, bytes32 r, bytes32 s) = + vm.sign(strategist.key, keccak256(SO.encodeWithAccountCounter(keccak256(abi.encode(newLoanDetails))))); + + uint256 borrowerBalanceBefore = erc20s[0].balanceOf(borrower.addr); + uint256 lenderBalanceBefore = erc20s[0].balanceOf(lender.addr); + + CaveatEnforcer.CaveatWithApproval memory be = _generateSignedCaveatBorrower(loan, borrower, bytes32(uint256(5))); + vm.prank(lender.addr); + LM.setOriginateApproval(address(SO), LoanManager.ApprovalType.LENDER); + skip(200); + vm.expectRevert(abi.encodeWithSelector(StrategistOriginator.InvalidDeadline.selector)); + vm.prank(borrower.addr); + SO.originate( + Originator.Request({ + borrower: borrower.addr, + borrowerCaveat: be, + collateral: loan.collateral, + debt: loan.debt, + details: abi.encode(newLoanDetails), + approval: abi.encodePacked(r, s, v) + }) + ); + } + + function testInvalidCollateral() public { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + + StrategistOriginator.Details memory newLoanDetails = StrategistOriginator.Details({ + custodian: LM.defaultCustodian(), + issuer: lender.addr, + deadline: block.timestamp + 100, + offer: StrategistOriginator.Offer({ + terms: loan.terms, + salt: bytes32(0), + collateral: loan.collateral, + debt: loan.debt + }) + }); + + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + + bytes memory encodedLoanDetails = abi.encode(newLoanDetails); + (uint8 v, bytes32 r, bytes32 s) = + vm.sign(strategist.key, keccak256(SO.encodeWithAccountCounter(keccak256(encodedLoanDetails)))); + + loan.collateral[0].identifier = uint256(7); + uint256 borrowerBalanceBefore = erc20s[0].balanceOf(borrower.addr); + uint256 lenderBalanceBefore = erc20s[0].balanceOf(lender.addr); + + CaveatEnforcer.CaveatWithApproval memory be = _generateSignedCaveatBorrower(loan, borrower, bytes32(uint256(5))); + vm.prank(lender.addr); + LM.setOriginateApproval(address(SO), LoanManager.ApprovalType.LENDER); + vm.expectRevert(abi.encodeWithSelector(StrategistOriginator.InvalidCollateral.selector)); + vm.prank(borrower.addr); + SO.originate( + Originator.Request({ + borrower: borrower.addr, + borrowerCaveat: be, + collateral: loan.collateral, + debt: loan.debt, + details: encodedLoanDetails, + approval: abi.encodePacked(r, s, v) + }) + ); + } + + function testInvalidDebt() public { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + + StrategistOriginator.Details memory newLoanDetails = StrategistOriginator.Details({ + custodian: LM.defaultCustodian(), + issuer: lender.addr, + deadline: block.timestamp + 100, + offer: StrategistOriginator.Offer({ + terms: loan.terms, + salt: bytes32(0), + collateral: loan.collateral, + debt: loan.debt + }) + }); + + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + + bytes memory encodedLoanDetails = abi.encode(newLoanDetails); + (uint8 v, bytes32 r, bytes32 s) = + vm.sign(strategist.key, keccak256(SO.encodeWithAccountCounter(keccak256(encodedLoanDetails)))); + + loan.debt[0].identifier = uint256(7); + uint256 borrowerBalanceBefore = erc20s[0].balanceOf(borrower.addr); + uint256 lenderBalanceBefore = erc20s[0].balanceOf(lender.addr); + + CaveatEnforcer.CaveatWithApproval memory be = _generateSignedCaveatBorrower(loan, borrower, bytes32(uint256(5))); + vm.prank(lender.addr); + LM.setOriginateApproval(address(SO), LoanManager.ApprovalType.LENDER); + vm.expectRevert(abi.encodeWithSelector(StrategistOriginator.InvalidDebt.selector)); + vm.prank(borrower.addr); + SO.originate( + Originator.Request({ + borrower: borrower.addr, + borrowerCaveat: be, + collateral: loan.collateral, + debt: loan.debt, + details: encodedLoanDetails, + approval: abi.encodePacked(r, s, v) + }) + ); + } + + function testInvalidDebtAmountRequestingZero() public { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + + StrategistOriginator.Details memory newLoanDetails = StrategistOriginator.Details({ + custodian: LM.defaultCustodian(), + issuer: lender.addr, + deadline: block.timestamp + 100, + offer: StrategistOriginator.Offer({ + terms: loan.terms, + salt: bytes32(0), + collateral: loan.collateral, + debt: loan.debt + }) + }); + + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + + bytes memory encodedLoanDetails = abi.encode(newLoanDetails); + (uint8 v, bytes32 r, bytes32 s) = + vm.sign(strategist.key, keccak256(SO.encodeWithAccountCounter(keccak256(encodedLoanDetails)))); + + loan.debt[0].amount = 0; + uint256 borrowerBalanceBefore = erc20s[0].balanceOf(borrower.addr); + uint256 lenderBalanceBefore = erc20s[0].balanceOf(lender.addr); + + CaveatEnforcer.CaveatWithApproval memory be = _generateSignedCaveatBorrower(loan, borrower, bytes32(uint256(5))); + vm.prank(lender.addr); + LM.setOriginateApproval(address(SO), LoanManager.ApprovalType.LENDER); + vm.expectRevert(abi.encodeWithSelector(StrategistOriginator.InvalidDebtAmount.selector)); + vm.prank(borrower.addr); + SO.originate( + Originator.Request({ + borrower: borrower.addr, + borrowerCaveat: be, + collateral: loan.collateral, + debt: loan.debt, + details: encodedLoanDetails, + approval: abi.encodePacked(r, s, v) + }) + ); + } + + function testInvalidDebtAmountOfferingZero() public { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + loan.debt[0].amount = 0; + + StrategistOriginator.Details memory newLoanDetails = StrategistOriginator.Details({ + custodian: LM.defaultCustodian(), + issuer: lender.addr, + deadline: block.timestamp + 100, + offer: StrategistOriginator.Offer({ + terms: loan.terms, + salt: bytes32(0), + collateral: loan.collateral, + debt: loan.debt + }) + }); + + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + + bytes memory encodedLoanDetails = abi.encode(newLoanDetails); + (uint8 v, bytes32 r, bytes32 s) = + vm.sign(strategist.key, keccak256(SO.encodeWithAccountCounter(keccak256(encodedLoanDetails)))); + + uint256 borrowerBalanceBefore = erc20s[0].balanceOf(borrower.addr); + uint256 lenderBalanceBefore = erc20s[0].balanceOf(lender.addr); + + CaveatEnforcer.CaveatWithApproval memory be = _generateSignedCaveatBorrower(loan, borrower, bytes32(uint256(5))); + vm.prank(lender.addr); + LM.setOriginateApproval(address(SO), LoanManager.ApprovalType.LENDER); + vm.expectRevert(abi.encodeWithSelector(StrategistOriginator.InvalidDebtAmount.selector)); + vm.prank(borrower.addr); + SO.originate( + Originator.Request({ + borrower: borrower.addr, + borrowerCaveat: be, + collateral: loan.collateral, + debt: loan.debt, + details: encodedLoanDetails, + approval: abi.encodePacked(r, s, v) + }) + ); + } + + function testInvalidDebtAmountAskingMoreThanOffered() public { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + + StrategistOriginator.Details memory newLoanDetails = StrategistOriginator.Details({ + custodian: LM.defaultCustodian(), + issuer: lender.addr, + deadline: block.timestamp + 100, + offer: StrategistOriginator.Offer({ + terms: loan.terms, + salt: bytes32(0), + collateral: loan.collateral, + debt: loan.debt + }) + }); + + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + + bytes memory encodedLoanDetails = abi.encode(newLoanDetails); + (uint8 v, bytes32 r, bytes32 s) = + vm.sign(strategist.key, keccak256(SO.encodeWithAccountCounter(keccak256(encodedLoanDetails)))); + + uint256 borrowerBalanceBefore = erc20s[0].balanceOf(borrower.addr); + uint256 lenderBalanceBefore = erc20s[0].balanceOf(lender.addr); + loan.debt[0].amount = 2e18; + CaveatEnforcer.CaveatWithApproval memory be = _generateSignedCaveatBorrower(loan, borrower, bytes32(uint256(5))); + vm.prank(lender.addr); + LM.setOriginateApproval(address(SO), LoanManager.ApprovalType.LENDER); + vm.expectRevert(abi.encodeWithSelector(StrategistOriginator.InvalidDebtAmount.selector)); + vm.prank(borrower.addr); + SO.originate( + Originator.Request({ + borrower: borrower.addr, + borrowerCaveat: be, + collateral: loan.collateral, + debt: loan.debt, + details: encodedLoanDetails, + approval: abi.encodePacked(r, s, v) + }) + ); + } + + function testInvalidDebtLength() public { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + + StrategistOriginator.Details memory newLoanDetails = StrategistOriginator.Details({ + custodian: LM.defaultCustodian(), + issuer: lender.addr, + deadline: block.timestamp + 100, + offer: StrategistOriginator.Offer({ + terms: loan.terms, + salt: bytes32(0), + collateral: loan.collateral, + debt: loan.debt + }) + }); + + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + + bytes memory encodedLoanDetails = abi.encode(newLoanDetails); + (uint8 v, bytes32 r, bytes32 s) = + vm.sign(strategist.key, keccak256(SO.encodeWithAccountCounter(keccak256(encodedLoanDetails)))); + + uint256 borrowerBalanceBefore = erc20s[0].balanceOf(borrower.addr); + uint256 lenderBalanceBefore = erc20s[0].balanceOf(lender.addr); + SpentItem[] memory newDebt = new SpentItem[](2); + newDebt[0] = loan.debt[0]; + loan.debt = newDebt; + CaveatEnforcer.CaveatWithApproval memory be = _generateSignedCaveatBorrower(loan, borrower, bytes32(uint256(5))); + vm.prank(lender.addr); + LM.setOriginateApproval(address(SO), LoanManager.ApprovalType.LENDER); + vm.expectRevert(abi.encodeWithSelector(StrategistOriginator.InvalidDebtLength.selector)); + vm.prank(borrower.addr); + SO.originate( + Originator.Request({ + borrower: borrower.addr, + borrowerCaveat: be, + collateral: loan.collateral, + debt: loan.debt, + details: encodedLoanDetails, + approval: abi.encodePacked(r, s, v) + }) + ); + } + + function testInvalidSigner() public { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + + StrategistOriginator.Details memory newLoanDetails = StrategistOriginator.Details({ + custodian: LM.defaultCustodian(), + issuer: lender.addr, + deadline: block.timestamp + 100, + offer: StrategistOriginator.Offer({ + terms: loan.terms, + salt: bytes32(0), + collateral: loan.collateral, + debt: loan.debt + }) + }); + + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + + bytes memory encodedLoanDetails = abi.encode(newLoanDetails); + (uint8 v, bytes32 r, bytes32 s) = + vm.sign(strategist.key, keccak256(SO.encodeWithAccountCounter(keccak256(encodedLoanDetails)))); + + loan.debt[0].amount = 2e18; + + uint256 borrowerBalanceBefore = erc20s[0].balanceOf(borrower.addr); + uint256 lenderBalanceBefore = erc20s[0].balanceOf(lender.addr); + SpentItem[] memory newDebt = new SpentItem[](2); + CaveatEnforcer.CaveatWithApproval memory be = _generateSignedCaveatBorrower(loan, borrower, bytes32(uint256(5))); + vm.prank(lender.addr); + LM.setOriginateApproval(address(SO), LoanManager.ApprovalType.LENDER); + vm.expectRevert(abi.encodeWithSelector(StrategistOriginator.InvalidSigner.selector)); + vm.prank(borrower.addr); + SO.originate( + Originator.Request({ + borrower: borrower.addr, + borrowerCaveat: be, + collateral: loan.collateral, + debt: loan.debt, + details: abi.encode(newLoanDetails), + approval: abi.encodePacked(r, s, v) + }) + ); + } + + function testInvalidOffer() public { + LoanManager.Loan memory loan = generateDefaultLoanTerms(); + + StrategistOriginator.Details memory newLoanDetails = StrategistOriginator.Details({ + custodian: LM.defaultCustodian(), + issuer: lender.addr, + deadline: block.timestamp + 100, + offer: StrategistOriginator.Offer({ + terms: loan.terms, + salt: bytes32(uint256(1)), + collateral: loan.collateral, + debt: loan.debt + }) + }); + + _setApprovalsForSpentItems(loan.borrower, loan.collateral); + _setApprovalsForSpentItems(loan.issuer, loan.debt); + + bytes memory encodedLoanDetails = abi.encode(newLoanDetails); + (uint8 v, bytes32 r, bytes32 s) = + vm.sign(strategist.key, keccak256(SO.encodeWithAccountCounter(keccak256(encodedLoanDetails)))); + + uint256 borrowerBalanceBefore = erc20s[0].balanceOf(borrower.addr); + uint256 lenderBalanceBefore = erc20s[0].balanceOf(lender.addr); + SpentItem[] memory newDebt = new SpentItem[](2); + CaveatEnforcer.CaveatWithApproval memory be = _generateSignedCaveatBorrower(loan, borrower, bytes32(uint256(5))); + vm.prank(lender.addr); + LM.setOriginateApproval(address(SO), LoanManager.ApprovalType.LENDER); + + vm.expectEmit(); + emit HashInvalidated(keccak256(encodedLoanDetails)); + vm.prank(borrower.addr); + SO.originate( + Originator.Request({ + borrower: borrower.addr, + borrowerCaveat: be, + collateral: loan.collateral, + debt: loan.debt, + details: encodedLoanDetails, + approval: abi.encodePacked(r, s, v) + }) + ); + vm.expectRevert(abi.encodeWithSelector(StrategistOriginator.InvalidOffer.selector)); + vm.prank(borrower.addr); + SO.originate( + Originator.Request({ + borrower: borrower.addr, + borrowerCaveat: be, + collateral: loan.collateral, + debt: loan.debt, + details: encodedLoanDetails, + approval: abi.encodePacked(r, s, v) + }) + ); + } +} diff --git a/test/utils/Bound.sol b/test/utils/Bound.sol index 3f5149b7..ce3c888b 100644 --- a/test/utils/Bound.sol +++ b/test/utils/Bound.sol @@ -1,4 +1,4 @@ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {ItemType, SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {Cast} from "starport-test/utils/Cast.sol"; diff --git a/test/utils/Cast.sol b/test/utils/Cast.sol index e1b24f12..2bca518d 100644 --- a/test/utils/Cast.sol +++ b/test/utils/Cast.sol @@ -1,4 +1,4 @@ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; import {ItemType, SpentItem, ReceivedItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import "starport-test/utils/FuzzStructs.sol" as Fuzz; diff --git a/test/utils/FuzzStructs.sol b/test/utils/FuzzStructs.sol index 3b98758d..25c1c8b0 100644 --- a/test/utils/FuzzStructs.sol +++ b/test/utils/FuzzStructs.sol @@ -1,4 +1,4 @@ -pragma solidity =0.8.17; +pragma solidity ^0.8.17; struct ReceivedItem { uint8 itemType; diff --git a/test/utils/MockCall.sol b/test/utils/MockCall.sol index 06ebe266..11db546b 100644 --- a/test/utils/MockCall.sol +++ b/test/utils/MockCall.sol @@ -1,3 +1,5 @@ +pragma solidity ^0.8.17; + import {ItemType, SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {TestBase} from "forge-std/Test.sol"; import {SettlementHook} from "src/hooks/SettlementHook.sol";