From b1711c80badecbd007196ee28d3b56bc87738975 Mon Sep 17 00:00:00 2001 From: GregTheDev <40359730+0xgregthedev@users.noreply.github.com> Date: Sat, 7 Oct 2023 06:11:01 -0500 Subject: [PATCH] Test custodian (#23) --- .gas-snapshot | 70 ++++++++++++----------- src/ConduitHelper.sol | 1 - src/Custodian.sol | 18 +++--- src/originators/Originator.sol | 21 ++++--- test/TestCustodian.sol | 100 ++++++++++++++++++++++++++++++--- test/TestExoticLoans.t.sol | 24 ++++---- test/TestLoanManager.sol | 26 ++++----- test/utils/MockCall.sol | 18 ++++++ 8 files changed, 194 insertions(+), 84 deletions(-) create mode 100644 test/utils/MockCall.sol diff --git a/.gas-snapshot b/.gas-snapshot index 6cc8844f..8b931c4e 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,36 +1,40 @@ -EnforcerTest:testCollateralEnforcer() (gas: 959475) -EnforcerTest:testFailCollateralEnforcerDifferentCollateral() (gas: 842116) -EnforcerTest:testFailRateEnforcerMaxCarryRate() (gas: 797272) -EnforcerTest:testFailRateEnforcerMaxRate() (gas: 797222) -EnforcerTest:testFailRateEnforcerMaxRateAndMaxCarryRate() (gas: 797087) -EnforcerTest:testRateEnforcerBasic() (gas: 916734) -EnforcerTest:testTermEnforcerBasic() (gas: 988996) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecall() (gas: 978416) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLender() (gas: 889991) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLiquidation() (gas: 923967) -TestCustodian:testCustodySelector() (gas: 7084) -TestCustodian:testGenerateOrder() (gas: 165936) -TestCustodian:testGetBorrower() (gas: 70626) -TestCustodian:testOnlySeaport() (gas: 17842) -TestCustodian:testPreviewOrder() (gas: 207) -TestCustodian:testRatifyOrder() (gas: 164) -TestCustodian:testSafeTransferReceive() (gas: 158751) -TestCustodian:testSeaportMetadata() (gas: 8518) -TestCustodian:testSupportsInterface() (gas: 5664) -TestExoticLoans:testSwap() (gas: 1356417) +EnforcerTest:testCollateralEnforcer() (gas: 973177) +EnforcerTest:testFailCollateralEnforcerDifferentCollateral() (gas: 855101) +EnforcerTest:testFailRateEnforcerMaxCarryRate() (gas: 810244) +EnforcerTest:testFailRateEnforcerMaxRate() (gas: 810194) +EnforcerTest:testFailRateEnforcerMaxRateAndMaxCarryRate() (gas: 810059) +EnforcerTest:testRateEnforcerBasic() (gas: 930425) +EnforcerTest:testTermEnforcerBasic() (gas: 1002689) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecall() (gas: 988282) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLender() (gas: 906210) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLiquidation() (gas: 952988) +TestCustodian:testCustodySelector() (gas: 7152) +TestCustodian:testGenerateOrderRepay() (gas: 174157) +TestCustodian:testGenerateOrderRepayNotBorrower() (gas: 102146) +TestCustodian:testGenerateOrderSettlement() (gas: 125008) +TestCustodian:testGenerateOrderSettlementUnauthorized() (gas: 100145) +TestCustodian:testGetBorrower() (gas: 75840) +TestCustodian:testOnlySeaport() (gas: 17931) +TestCustodian:testPreviewOrderNoActiveLoan() (gas: 229) +TestCustodian:testPreviewOrderRepay() (gas: 224706) +TestCustodian:testPreviewOrderSettlement() (gas: 152470) +TestCustodian:testRatifyOrder() (gas: 188716) +TestCustodian:testSafeTransferReceive() (gas: 159061) +TestCustodian:testSeaportMetadata() (gas: 8519) +TestCustodian:testSupportsInterface() (gas: 5791) +TestExoticLoans:testSwap() (gas: 1167541) TestLoanCombinations:testLoan20For721SimpleInterestDutchFixedRepay() (gas: 252) -TestLoanCombinations:testLoan20for20SimpleInterestDutchFixedRepay() (gas: 512554) -TestLoanCombinations:testLoan721for20SimpleInterestDutchFixedRepay() (gas: 736003) -TestLoanCombinations:testLoanAstariaSettlementRepay() (gas: 510525) -TestLoanCombinations:testLoanSimpleInterestEnglishFixed() (gas: 721961) -TestLoanManager:testGenerateOrder() (gas: 773998) -TestLoanManager:testGenerateOrderInvalidSender() (gas: 13012) -TestLoanManager:testSupportsInterface() (gas: 6959) -TestNewLoan:testBuyNowPayLater() (gas: 1142014) -TestNewLoan:testNewLoanERC721CollateralDefaultTerms2():((uint256,address,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256)[],(address,bytes,address,bytes,address,bytes))) (gas: 987827) -TestNewLoan:testNewLoanERC721CollateralDefaultTermsRefinance() (gas: 648642) -TestNewLoan:testNewLoanERC721CollateralDefaultTermsWithMerkleProof():((uint256,address,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256)[],(address,bytes,address,bytes,address,bytes))) (gas: 581561) -TestNewLoan:testSettleLoan() (gas: 1260082) -TestRepayLoan:testRepayLoan() (gas: 719171) +TestLoanCombinations:testLoan20for20SimpleInterestDutchFixedRepay() (gas: 521951) +TestLoanCombinations:testLoan721for20SimpleInterestDutchFixedRepay() (gas: 753642) +TestLoanCombinations:testLoanAstariaSettlementRepay() (gas: 519386) +TestLoanCombinations:testLoanSimpleInterestEnglishFixed() (gas: 740159) +TestLoanManager:testGenerateOrder() (gas: 1009691) +TestLoanManager:testGenerateOrderInvalidSender() (gas: 13075) +TestLoanManager:testSupportsInterface() (gas: 7003) +TestNewLoan:testBuyNowPayLater() (gas: 1154993) +TestNewLoan:testNewLoanERC721CollateralDefaultTerms2():((uint256,address,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256)[],(address,bytes,address,bytes,address,bytes))) (gas: 1001549) +TestNewLoan:testNewLoanERC721CollateralDefaultTermsRefinance() (gas: 658024) +TestNewLoan:testSettleLoan() (gas: 1282926) +TestRepayLoan:testRepayLoan() (gas: 736801) TestStarLiteUtils:testEncodeReceivedWithRecipient() (gas: 17955) TestStarLiteUtils:testSpentToReceived() (gas: 17796) \ No newline at end of file diff --git a/src/ConduitHelper.sol b/src/ConduitHelper.sol index 9ebeafad..7155f49b 100644 --- a/src/ConduitHelper.sol +++ b/src/ConduitHelper.sol @@ -1,6 +1,5 @@ pragma solidity =0.8.17; - import {ItemType, OfferItem, Schema, SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; diff --git a/src/Custodian.sol b/src/Custodian.sol index efe9e84b..a2eaaf23 100644 --- a/src/Custodian.sol +++ b/src/Custodian.sol @@ -33,7 +33,9 @@ contract Custodian is ContractOffererInterface, TokenReceiverInterface, ConduitH event RepayApproval(address borrower, address repayer, bool approved); event SeaportCompatibleContractDeployed(); - error InvalidSender(); + error NotSeaport(); + error InvalidRepayer(); + error InvalidFulfiller(); error InvalidHandler(); constructor(LoanManager LM_, address seaport_) { @@ -86,7 +88,7 @@ contract Custodian is ContractOffererInterface, TokenReceiverInterface, ConduitH modifier onlySeaport() { if (msg.sender != address(seaport)) { - revert InvalidSender(); + revert NotSeaport(); } _; } @@ -253,7 +255,7 @@ contract Custodian is ContractOffererInterface, TokenReceiverInterface, ConduitH if (SettlementHook(loan.terms.hook).isActive(loan)) { address borrower = getBorrower(loan); if (fulfiller != borrower && !repayApproval[borrower][fulfiller]) { - revert InvalidSender(); + revert InvalidRepayer(); } (ReceivedItem[] memory paymentConsiderations, ReceivedItem[] memory carryFeeConsideration) = @@ -267,19 +269,19 @@ contract Custodian is ContractOffererInterface, TokenReceiverInterface, ConduitH _settleLoan(loan); } } else { - address restricted; + address authorized; //add in originator fee if (withEffects) { _beforeSettlementHandlerHook(loan); - (consideration, restricted) = SettlementHandler(loan.terms.handler).getSettlement(loan); + (consideration, authorized) = SettlementHandler(loan.terms.handler).getSettlement(loan); _afterSettlementHandlerHook(loan); } else { - (consideration, restricted) = SettlementHandler(loan.terms.handler).getSettlement(loan); + (consideration, authorized) = SettlementHandler(loan.terms.handler).getSettlement(loan); } //TODO: remove and revert in get settlement if needed - if (restricted != address(0) && fulfiller != restricted) { - revert InvalidSender(); + if (authorized != address(0) && fulfiller != authorized) { + revert InvalidFulfiller(); } } diff --git a/src/originators/Originator.sol b/src/originators/Originator.sol index d5bbdab4..7cd80250 100644 --- a/src/originators/Originator.sol +++ b/src/originators/Originator.sol @@ -37,13 +37,17 @@ abstract contract Originator is Ownable { INITIALIZED, CLOSED } + error InvalidDebt(); error InvalidOffer(); + struct Response { LoanManager.Terms terms; address issuer; } + event StrategistTransferred(address newStrategist); + mapping(bytes32 => bool) public usedHashes; struct Request { @@ -56,7 +60,7 @@ abstract contract Originator is Ownable { } struct Details { -// uint16 offerType; + // uint16 offerType; address custodian; address conduit; address issuer; @@ -170,19 +174,18 @@ abstract contract Originator is Ownable { return abi.decode(details, (Details)).offer.terms; } - function execute(Request calldata params) - external - virtual - onlyLoanManager - returns (Response memory response) - { + function execute(Request calldata params) external virtual onlyLoanManager returns (Response memory response) { Details memory details = abi.decode(params.details, (Details)); _validateOffer(params, details); _execute(params, details); response = _buildResponse(params, details); } - function _buildResponse(Request calldata params, Details memory details) internal virtual returns (Response memory response) { + function _buildResponse(Request calldata params, Details memory details) + internal + virtual + returns (Response memory response) + { response = Response({terms: details.offer.terms, issuer: details.issuer}); } @@ -242,7 +245,7 @@ abstract contract Originator is Ownable { if ( ConduitInterface(details.conduit).execute(_packageTransfers(request.debt, request.receiver, details.issuer)) - != ConduitInterface.execute.selector + != ConduitInterface.execute.selector ) { revert ConduitTransferError(); } diff --git a/test/TestCustodian.sol b/test/TestCustodian.sol index 64c256eb..78907930 100644 --- a/test/TestCustodian.sol +++ b/test/TestCustodian.sol @@ -1,6 +1,9 @@ import "./StarPortTest.sol"; +import {DeepEq} from "test/utils/DeepEq.sol"; +import {MockCall} from "test/utils/MockCall.sol"; +import "forge-std/Test.sol"; -contract TestCustodian is StarPortTest { +contract TestCustodian is StarPortTest, DeepEq, MockCall { using Cast for *; LoanManager.Loan public activeLoan; @@ -49,10 +52,10 @@ contract TestCustodian is StarPortTest { } function testOnlySeaport() public { - vm.expectRevert(abi.encodeWithSelector(Custodian.InvalidSender.selector)); + vm.expectRevert(abi.encodeWithSelector(Custodian.NotSeaport.selector)); custodian.ratifyOrder(new SpentItem[](0), new ReceivedItem[](0), new bytes(0), new bytes32[](0), uint256(0)); - vm.expectRevert(abi.encodeWithSelector(Custodian.InvalidSender.selector)); + vm.expectRevert(abi.encodeWithSelector(Custodian.NotSeaport.selector)); custodian.generateOrder(address(this), new SpentItem[](0), new SpentItem[](0), new bytes(0)); } @@ -80,8 +83,8 @@ contract TestCustodian is StarPortTest { custodian.getSeaportMetadata(); } - function testGetBorrower() public view { - custodian.getBorrower(activeLoan.toMemory()); + function testGetBorrower() public { + assertEq(custodian.getBorrower(activeLoan.toMemory()), activeLoan.borrower); } function testCustodySelector() public { @@ -91,12 +94,93 @@ contract TestCustodian is StarPortTest { ); } - function testGenerateOrder() public { + //TODO: add assertions + function testGenerateOrderRepay() public { vm.prank(seaportAddr); custodian.generateOrder(activeLoan.borrower, new SpentItem[](0), debt, abi.encode(activeLoan)); } - function testRatifyOrder() public {} + function testGenerateOrderRepayNotBorrower() public { + vm.prank(seaportAddr); + vm.expectRevert(abi.encodeWithSelector(Custodian.InvalidRepayer.selector)); + custodian.generateOrder(alice, new SpentItem[](0), debt, abi.encode(activeLoan)); + } + + function testGenerateOrderSettlement() public { + vm.startPrank(seaportAddr); + mockHookCall(activeLoan.terms.hook, false); + + mockHandlerCall(activeLoan.terms.handler, new ReceivedItem[](0), address(0)); + + (SpentItem[] memory offer, ReceivedItem[] memory consideration) = + custodian.generateOrder(alice, new SpentItem[](0), debt, abi.encode(activeLoan)); + + vm.stopPrank(); + + assertEq(consideration.length, 0); + } + + function testGenerateOrderSettlementUnauthorized() public { + vm.prank(seaportAddr); + mockHookCall(activeLoan.terms.hook, false); + mockHandlerCall(activeLoan.terms.handler, new ReceivedItem[](0), lender.addr); + + vm.expectRevert(abi.encodeWithSelector(Custodian.InvalidFulfiller.selector)); + custodian.generateOrder(borrower.addr, new SpentItem[](0), debt, abi.encode(activeLoan)); + } + + //TODO: add assertions + function testRatifyOrder() public { + vm.startPrank(seaportAddr); + bytes memory context = abi.encode(activeLoan); + + (SpentItem[] memory offer, ReceivedItem[] memory consideration) = + custodian.generateOrder(activeLoan.borrower, new SpentItem[](0), debt, context); + + custodian.ratifyOrder(offer, consideration, context, new bytes32[](0), 0); + + vm.stopPrank(); + } + + function testPreviewOrderRepay() public { + vm.prank(seaportAddr); + + mockHookCall(activeLoan.terms.hook, true); + mockHandlerCall(activeLoan.terms.handler, new ReceivedItem[](0), address(0)); + + (SpentItem[] memory expectedOffer, ReceivedItem[] memory expectedConsideration) = + custodian.generateOrder(activeLoan.borrower, new SpentItem[](0), debt, abi.encode(activeLoan)); + + mockHookCall(activeLoan.terms.hook, true); + mockHandlerCall(activeLoan.terms.handler, new ReceivedItem[](0), address(0)); + + (SpentItem[] memory receivedOffer, ReceivedItem[] memory receivedCosideration) = custodian.previewOrder( + activeLoan.borrower, activeLoan.borrower, new SpentItem[](0), debt, abi.encode(activeLoan) + ); + + _deepEq(receivedOffer, expectedOffer); + _deepEq(receivedCosideration, expectedConsideration); + } + + function testPreviewOrderSettlement() public { + vm.prank(seaportAddr); + + mockHookCall(activeLoan.terms.hook, false); + mockHandlerCall(activeLoan.terms.handler, new ReceivedItem[](0), address(0)); + + (SpentItem[] memory expectedOffer, ReceivedItem[] memory expectedConsideration) = + custodian.generateOrder(alice, new SpentItem[](0), debt, abi.encode(activeLoan)); + + mockHookCall(activeLoan.terms.hook, false); + mockHandlerCall(activeLoan.terms.handler, new ReceivedItem[](0), address(0)); + + (SpentItem[] memory receivedOffer, ReceivedItem[] memory receivedCosideration) = + custodian.previewOrder(alice, alice, new SpentItem[](0), debt, abi.encode(activeLoan)); + + _deepEq(receivedOffer, expectedOffer); + _deepEq(receivedCosideration, expectedConsideration); + } - function testPreviewOrder() public {} + //TODO: should revert + function testPreviewOrderNoActiveLoan() public {} } diff --git a/test/TestExoticLoans.t.sol b/test/TestExoticLoans.t.sol index 289ca3c6..60231669 100644 --- a/test/TestExoticLoans.t.sol +++ b/test/TestExoticLoans.t.sol @@ -63,10 +63,10 @@ contract SwapHandler is SettlementHandler { } function getSettlement(LoanManager.Loan calldata loan) - public - view - override - returns (ReceivedItem[] memory consideration, address restricted) + public + view + override + returns (ReceivedItem[] memory consideration, address restricted) { return (new ReceivedItem[](0), address(0)); } @@ -76,19 +76,19 @@ contract SwapPricing is Pricing { constructor(LoanManager LM_) Pricing(LM_) {} function getPaymentConsideration(LoanManager.Loan memory loan) - public - view - override - returns (ReceivedItem[] memory, ReceivedItem[] memory) + public + view + override + returns (ReceivedItem[] memory, ReceivedItem[] memory) { return (new ReceivedItem[](0), new ReceivedItem[](0)); } function isValidRefinance(LoanManager.Loan memory loan, bytes memory newPricingData, address caller) - external - view - override - returns (ReceivedItem[] memory, ReceivedItem[] memory, ReceivedItem[] memory) + external + view + override + returns (ReceivedItem[] memory, ReceivedItem[] memory, ReceivedItem[] memory) { return (new ReceivedItem[](0), new ReceivedItem[](0), new ReceivedItem[](0)); } diff --git a/test/TestLoanManager.sol b/test/TestLoanManager.sol index 4f8860dc..038e0dd3 100644 --- a/test/TestLoanManager.sol +++ b/test/TestLoanManager.sol @@ -5,28 +5,28 @@ contract MockOriginator is Originator, TokenReceiverInterface { // PUBLIC FUNCTIONS function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) - public - pure - virtual - returns (bytes4) + public + pure + virtual + returns (bytes4) { return TokenReceiverInterface.onERC721Received.selector; } function onERC1155Received(address, address, uint256, uint256, bytes calldata) - external - pure - virtual - returns (bytes4) + external + pure + virtual + returns (bytes4) { return TokenReceiverInterface.onERC1155Received.selector; } function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata) - external - pure - virtual - returns (bytes4) + external + pure + virtual + returns (bytes4) { return TokenReceiverInterface.onERC1155BatchReceived.selector; } @@ -81,7 +81,7 @@ contract TestLoanManager is StarPortTest { // OrderParameters memory op = _buildContractOrder(address(LM), new OfferItem[](0), selectedCollateral); vm.startPrank(seaport); (SpentItem[] memory offer, ReceivedItem[] memory consideration) = - LM.generateOrder(address(this), new SpentItem[](0), maxSpent, abi.encode(O)); + LM.generateOrder(address(this), new SpentItem[](0), maxSpent, abi.encode(O)); //TODO:: validate return data matches request // assertEq(keccak256(abi.encode(consideration)), keccak256(abi.encode(maxSpent))); } diff --git a/test/utils/MockCall.sol b/test/utils/MockCall.sol new file mode 100644 index 00000000..21edf145 --- /dev/null +++ b/test/utils/MockCall.sol @@ -0,0 +1,18 @@ +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"; +import {SettlementHandler} from "src/handlers/SettlementHandler.sol"; + +abstract contract MockCall is TestBase { + function mockHookCall(address hook, bool status) public { + vm.mockCall(hook, abi.encodeWithSelector(SettlementHook.isActive.selector), abi.encode(status)); + } + + function mockHandlerCall(address handler, ReceivedItem[] memory receivedItems, address authorized) public { + vm.mockCall( + handler, + abi.encodeWithSelector(SettlementHandler.getSettlement.selector), + abi.encode(receivedItems, authorized) + ); + } +}