From c662f017b00622f5dccf986ea6555f2657fc07b5 Mon Sep 17 00:00:00 2001 From: Andrew Redden Date: Wed, 15 Nov 2023 11:39:56 -0400 Subject: [PATCH] add mock erc1271 proxy to demonstrate ERC1271 sig working --- .gas-snapshot | 82 ++++++----- test/StarportTest.sol | 7 +- test/integration-testing/TestNewLoan.sol | 179 ++++++++++++++++++++++- 3 files changed, 225 insertions(+), 43 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 4b661546..afe4c6ae 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -6,16 +6,16 @@ IntegrationTestCaveats:testOriginateWCaveatsIncrementedNonce() (gas: 205798) IntegrationTestCaveats:testOriginateWCaveatsInvalidSalt() (gas: 303644) IntegrationTestCaveats:testOriginateWCaveatsInvalidSaltManual() (gas: 179719) IntegrationTestCaveats:testOriginateWLenderApproval() (gas: 323375) -IntegrationTestCaveats:testRefinanceAsLender() (gas: 1090532) -IntegrationTestCaveats:testRefinanceCaveatFailure() (gas: 439685) -IntegrationTestCaveats:testRefinanceLoanStartAtBlockTimestampInvalidLoan() (gas: 386956) -IntegrationTestCaveats:testRefinanceUnapprovedFulfiller() (gas: 497489) -IntegrationTestCaveats:testRefinanceWCaveatsInvalidSalt() (gas: 416353) -IntegrationTestCaveats:testRefinanceWLenderApproval() (gas: 439616) -ModuleTesting:testFixedTermDutchAuctionSettlement() (gas: 435806) -ModuleTesting:testFixedTermDutchAuctionSettlementGetSettlementAuctionExpired() (gas: 438584) -ModuleTesting:testFixedTermDutchAuctionSettlementNotValid() (gas: 434694) -ModuleTesting:testFixedTermDutchAuctionSettlementValid() (gas: 435558) +IntegrationTestCaveats:testRefinanceAsLender() (gas: 1088316) +IntegrationTestCaveats:testRefinanceCaveatFailure() (gas: 437472) +IntegrationTestCaveats:testRefinanceLoanStartAtBlockTimestampInvalidLoan() (gas: 384742) +IntegrationTestCaveats:testRefinanceUnapprovedFulfiller() (gas: 495279) +IntegrationTestCaveats:testRefinanceWCaveatsInvalidSalt() (gas: 414143) +IntegrationTestCaveats:testRefinanceWLenderApproval() (gas: 437399) +ModuleTesting:testFixedTermDutchAuctionSettlement() (gas: 436114) +ModuleTesting:testFixedTermDutchAuctionSettlementGetSettlementAuctionExpired() (gas: 438892) +ModuleTesting:testFixedTermDutchAuctionSettlementNotValid() (gas: 435002) +ModuleTesting:testFixedTermDutchAuctionSettlementValid() (gas: 435866) PausableNonReentrantImpl:test() (gas: 2442) PausableNonReentrantImpl:testReentrancy() (gas: 2735) TestBorrowerEnforcer:testBERevertAdditionalTransfers() (gas: 75662) @@ -31,8 +31,8 @@ TestCustodian:testGenerateOrderInvalidPostRepayment() (gas: 178774) TestCustodian:testGenerateOrderInvalidPostSettlement() (gas: 163188) TestCustodian:testGenerateOrderRepay() (gas: 182892) TestCustodian:testGenerateOrderRepayAsRepayApprovedBorrower() (gas: 199361) -TestCustodian:testGenerateOrderRepayERC1155AndERC20() (gas: 881395) -TestCustodian:testGenerateOrderRepayERC1155AndERC20HandlerAuthorized() (gas: 802829) +TestCustodian:testGenerateOrderRepayERC1155AndERC20() (gas: 882011) +TestCustodian:testGenerateOrderRepayERC1155AndERC20HandlerAuthorized() (gas: 803445) TestCustodian:testGenerateOrderRepayInvalidHookAddress() (gas: 97601) TestCustodian:testGenerateOrderRepayInvalidHookReturnType() (gas: 91984) TestCustodian:testGenerateOrderRepayNotBorrower() (gas: 106802) @@ -40,13 +40,13 @@ TestCustodian:testGenerateOrderSettlement() (gas: 154943) TestCustodian:testGenerateOrderSettlementHandlerAuthorized() (gas: 160340) TestCustodian:testGenerateOrderSettlementNoActiveLoan() (gas: 163351) TestCustodian:testGenerateOrderSettlementUnauthorized() (gas: 101822) -TestCustodian:testGenerateOrdersWithLoanStartAtBlockTimestampInvalidLoan() (gas: 460672) +TestCustodian:testGenerateOrdersWithLoanStartAtBlockTimestampInvalidLoan() (gas: 460980) TestCustodian:testGetBorrower() (gas: 78641) TestCustodian:testInvalidAction() (gas: 173284) TestCustodian:testInvalidActionRepayInActiveLoan() (gas: 130104) TestCustodian:testInvalidActionSettleActiveLoan() (gas: 130086) TestCustodian:testInvalidEncodedData() (gas: 26192) -TestCustodian:testMintWithApprovalSetAsBorrower() (gas: 364940) +TestCustodian:testMintWithApprovalSetAsBorrower() (gas: 362721) TestCustodian:testMintWithApprovalSetAsBorrowerInvalidLoan() (gas: 60792) TestCustodian:testMintWithApprovalSetNotAuthorized() (gas: 76759) TestCustodian:testName() (gas: 7121) @@ -63,40 +63,42 @@ TestCustodian:testSupportsInterface() (gas: 9428) TestCustodian:testSymbol() (gas: 7105) TestCustodian:testTokenURI() (gas: 67024) TestCustodian:testTokenURIInvalidLoan() (gas: 13151) -TestFuzzStarport:testFuzzNewOrigination((bool,address,uint256,uint256,uint256,uint256,(uint8,address,uint256,uint256)[],uint8)) (runs: 256, μ: 480560, ~: 477975) -TestFuzzStarport:testFuzzRefinance(((bool,address,uint256,uint256,uint256,uint256,(uint8,address,uint256,uint256)[],uint8),string,uint8,uint256,uint256,uint256)) (runs: 256, μ: 1319546, ~: 1321205) -TestFuzzStarport:testFuzzRepaymentFails(((bool,address,uint256,uint256,uint256,uint256,(uint8,address,uint256,uint256)[],uint8),(uint8,address,uint256,uint256)[10],(uint8,address,uint256,uint256)[10],address[3],uint256)) (runs: 256, μ: 804943, ~: 799555) -TestFuzzStarport:testFuzzRepaymentSuccess(((bool,address,uint256,uint256,uint256,uint256,(uint8,address,uint256,uint256)[],uint8),(uint8,address,uint256,uint256)[10],(uint8,address,uint256,uint256)[10],address[3],uint256)) (runs: 256, μ: 674643, ~: 673107) -TestFuzzStarport:testFuzzSettlementFails(((bool,address,uint256,uint256,uint256,uint256,(uint8,address,uint256,uint256)[],uint8),(uint8,address,uint256,uint256)[10],(uint8,address,uint256,uint256)[10],address[3],uint256)) (runs: 256, μ: 784821, ~: 783762) -TestFuzzStarport:testFuzzSettlementSuccess(((bool,address,uint256,uint256,uint256,uint256,(uint8,address,uint256,uint256)[],uint8),uint256)) (runs: 256, μ: 658947, ~: 656208) +TestFuzzStarport:testFuzzNewOrigination((bool,address,uint256,uint256,uint256,uint256,(uint8,address,uint256,uint256)[],uint8)) (runs: 256, μ: 482689, ~: 480860) +TestFuzzStarport:testFuzzRefinance(((bool,address,uint256,uint256,uint256,uint256,(uint8,address,uint256,uint256)[],uint8),string,uint8,uint256,uint256,uint256)) (runs: 256, μ: 1319456, ~: 1319887) +TestFuzzStarport:testFuzzRepaymentFails(((bool,address,uint256,uint256,uint256,uint256,(uint8,address,uint256,uint256)[],uint8),(uint8,address,uint256,uint256)[10],(uint8,address,uint256,uint256)[10],address[3],uint256)) (runs: 256, μ: 804336, ~: 801869) +TestFuzzStarport:testFuzzRepaymentSuccess(((bool,address,uint256,uint256,uint256,uint256,(uint8,address,uint256,uint256)[],uint8),(uint8,address,uint256,uint256)[10],(uint8,address,uint256,uint256)[10],address[3],uint256)) (runs: 256, μ: 673412, ~: 676288) +TestFuzzStarport:testFuzzSettlementFails(((bool,address,uint256,uint256,uint256,uint256,(uint8,address,uint256,uint256)[],uint8),(uint8,address,uint256,uint256)[10],(uint8,address,uint256,uint256)[10],address[3],uint256)) (runs: 256, μ: 784447, ~: 787224) +TestFuzzStarport:testFuzzSettlementSuccess(((bool,address,uint256,uint256,uint256,uint256,(uint8,address,uint256,uint256)[],uint8),uint256)) (runs: 256, μ: 654443, ~: 651971) TestLenderEnforcer:testLERevertAdditionalTransfersFromLender() (gas: 76210) TestLenderEnforcer:testLERevertInvalidLoanTerms() (gas: 80962) TestLenderEnforcer:testLEValidLoanTerms() (gas: 71955) TestLenderEnforcer:testLEValidLoanTermsAnyBorrower() (gas: 72087) TestLenderEnforcer:testLEValidLoanTermsWithAdditionalTransfers() (gas: 73310) -TestLoanCombinations:testLoan20For721SimpleInterestDutchFixedRepay() (gas: 598751) -TestLoanCombinations:testLoan20for20SimpleInterestDutchFixedRepay() (gas: 605964) -TestLoanCombinations:testLoan721for20SimpleInterestDutchFixedRepay() (gas: 597045) -TestLoanCombinations:testLoanAstariaSettlementRepay() (gas: 586855) -TestNewLoan:testBuyNowPayLater() (gas: 2872284) -TestNewLoan:testInvalidSenderBNPL() (gas: 1613698) +TestLoanCombinations:testLoan20For721SimpleInterestDutchFixedRepay() (gas: 599059) +TestLoanCombinations:testLoan20for20SimpleInterestDutchFixedRepay() (gas: 606272) +TestLoanCombinations:testLoan721for20SimpleInterestDutchFixedRepay() (gas: 597353) +TestLoanCombinations:testLoanAstariaSettlementRepay() (gas: 587163) +TestNewLoan:testBuyNowPayLater() (gas: 2872468) +TestNewLoan:testInvalidSenderBNPL() (gas: 1613720) TestNewLoan:testInvalidUserDataHashBNPL() (gas: 1616299) -TestNewLoan:testNewLoanERC721CollateralDefaultTerms2() (gas: 427647) -TestNewLoan:testNewLoanRefinance() (gas: 589048) -TestNewLoan:testNewLoanViaOriginatorBorrowerApprovalAndLenderApproval() (gas: 328481) -TestNewLoan:testNewLoanViaOriginatorLenderApproval() (gas: 384557) -TestNewLoan:testSettleLoan() (gas: 639688) +TestNewLoan:testNewLoanAs1271ProxyAccountSender() (gas: 864031) +TestNewLoan:testNewLoanAs1271ProxyAccountThirdPartyFiller() (gas: 873452) +TestNewLoan:testNewLoanERC721CollateralDefaultTerms2() (gas: 427278) +TestNewLoan:testNewLoanRefinance() (gas: 588737) +TestNewLoan:testNewLoanViaOriginatorBorrowerApprovalAndLenderApproval() (gas: 328366) +TestNewLoan:testNewLoanViaOriginatorLenderApproval() (gas: 384660) +TestNewLoan:testSettleLoan() (gas: 639318) TestPausableNonReentrant:testNotOwner() (gas: 21254) TestPausableNonReentrant:testPauseAndUnpause() (gas: 22555) TestPausableNonReentrant:testReentrancy() (gas: 15360) TestPausableNonReentrant:testUnpauseWhenNotPaused() (gas: 12582) TestRefStarportLib:testValidateSalt(address,bytes32) (runs: 256, μ: 33998, ~: 33998) -TestRepayLoan:testRepayLoanApprovedRepayer() (gas: 672362) -TestRepayLoan:testRepayLoanBase() (gas: 608565) -TestRepayLoan:testRepayLoanGenerateOrderNotSeaport() (gas: 436001) -TestRepayLoan:testRepayLoanInSettlement() (gas: 582969) -TestRepayLoan:testRepayLoanInvalidRepayer() (gas: 606735) -TestRepayLoan:testRepayLoanThatDoesNotExist() (gas: 884110) +TestRepayLoan:testRepayLoanApprovedRepayer() (gas: 672670) +TestRepayLoan:testRepayLoanBase() (gas: 608873) +TestRepayLoan:testRepayLoanGenerateOrderNotSeaport() (gas: 436309) +TestRepayLoan:testRepayLoanInSettlement() (gas: 583277) +TestRepayLoan:testRepayLoanInvalidRepayer() (gas: 607043) +TestRepayLoan:testRepayLoanThatDoesNotExist() (gas: 884418) TestSimpleInterestPricing:test_calculateInterest() (gas: 881308) TestSimpleInterestPricing:test_getPaymentConsideration() (gas: 928516) TestSimpleInterestPricing:test_getRefinanceConsideration() (gas: 919289) @@ -110,8 +112,8 @@ TestStarport:testCannotOriginateWhilePaused() (gas: 73457) TestStarport:testCannotSettleInvalidLoan() (gas: 74915) TestStarport:testCannotSettleUnlessValidCustodian() (gas: 70985) TestStarport:testCaveatEnforcerRevert() (gas: 99214) -TestStarport:testDefaultFeeRake() (gas: 363566) -TestStarport:testDefaultFeeRakeExoticDebt() (gas: 373079) +TestStarport:testDefaultFeeRake() (gas: 361348) +TestStarport:testDefaultFeeRakeExoticDebt() (gas: 370860) TestStarport:testExoticDebtWithNoCaveatsNotAsBorrower() (gas: 377331) TestStarport:testIncrementCaveatNonce() (gas: 35208) TestStarport:testInitializedFlagSetProperly() (gas: 67393) @@ -126,7 +128,7 @@ TestStarport:testInvalidateCaveatSalt() (gas: 33463) TestStarport:testNonDefaultCustodianCustodyCallFails() (gas: 264374) TestStarport:testNonDefaultCustodianCustodyCallSuccess() (gas: 290587) TestStarport:testNonPayableFunctions() (gas: 112043) -TestStarport:testOverrideFeeRake() (gas: 359463) +TestStarport:testOverrideFeeRake() (gas: 357244) TestStarport:testPause() (gas: 18093) TestStarport:testRefinancePostRepaymentFails() (gas: 120804) TestStarport:testTokenNoCodeCollateral() (gas: 148242) diff --git a/test/StarportTest.sol b/test/StarportTest.sol index c9249e9f..d9689d87 100644 --- a/test/StarportTest.sol +++ b/test/StarportTest.sol @@ -419,7 +419,12 @@ contract StarportTest is BaseOrderTest { ) internal returns (Starport.Loan memory originatedLoan) { vm.recordLogs(); vm.startPrank(fulfiller); - SP.originate(new AdditionalTransfer[](0), borrowerCaveat, lenderCaveat, loan); + SP.originate( + new AdditionalTransfer[](0), + fulfiller != borrower.addr ? borrowerCaveat : _emptyCaveat(), + fulfiller != lender.addr ? lenderCaveat : _emptyCaveat(), + loan + ); vm.stopPrank(); Vm.Log[] memory logs = vm.getRecordedLogs(); diff --git a/test/integration-testing/TestNewLoan.sol b/test/integration-testing/TestNewLoan.sol index 2cc643dc..7987e245 100644 --- a/test/integration-testing/TestNewLoan.sol +++ b/test/integration-testing/TestNewLoan.sol @@ -4,6 +4,7 @@ import "starport-test/StarportTest.sol"; import {StarportLib, Actions} from "starport-core/lib/StarportLib.sol"; import {BNPLHelper, IFlashLoanRecipient} from "starport-core/BNPLHelper.sol"; import {Originator} from "starport-core/originators/Originator.sol"; +import {SignatureCheckerLib} from "solady/src/utils/SignatureCheckerLib.sol"; contract FlashLoan { function flashLoan( @@ -51,10 +52,44 @@ contract FlashLoan { } } +contract ERC1271Proxy { + address public immutable owner; + + constructor(address owner_) { + owner = owner_; + } + + struct Action { + address to; + bytes data; + } + + function execute(bytes[] calldata encodedAction, bytes calldata signature) public payable { + if (msg.sender != owner) { + require( + SignatureCheckerLib.isValidSignatureNowCalldata(owner, keccak256(abi.encode(encodedAction)), signature), + "invalid signature" + ); + } + for (uint256 i = 0; i < encodedAction.length; i++) { + Action memory action = abi.decode(encodedAction[i], (Action)); + (bool success, bytes memory returnData) = action.to.call(action.data); + require(success, string(returnData)); + } + } + + function isValidSignature(bytes32 _messageHash, bytes calldata _signature) + public + view + returns (bytes4 magicValue) + { + require(SignatureCheckerLib.isValidSignatureNowCalldata(owner, _messageHash, _signature), "invalid signature"); + return this.isValidSignature.selector; + } +} + contract TestNewLoan is StarportTest { function testNewLoanERC721CollateralDefaultTerms2() public returns (Starport.Loan memory) { - Custodian custody = Custodian(SP.defaultCustodian()); - Starport.Terms memory terms = Starport.Terms({ status: address(status), settlement: address(settlement), @@ -398,4 +433,144 @@ contract TestNewLoan is StarportTest { recipient: address(0) }); } + + function testNewLoanAs1271ProxyAccountSender() public { + ERC1271Proxy proxy = new ERC1271Proxy(borrower.addr); + + uint256 initial721Balance = erc721s[0].balanceOf(borrower.addr); + assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); + uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); + + Starport.Loan memory originationDetails = _generateOriginationDetails( + _getERC721SpentItem(erc721s[0]), _getERC20SpentItem(erc20s[0], 100), lender.addr + ); + originationDetails.borrower = address(proxy); + _setApprovalsForSpentItems(originationDetails.issuer, originationDetails.debt); + + CaveatEnforcer.SignedCaveats memory borrowerCaveat = _generateSignedCaveatsBorrowerProxy( + originationDetails, address(proxy), borrower, address(borrowerEnforcer), bytes32(msg.sig), true + ); + CaveatEnforcer.SignedCaveats memory lenderCaveat = + _generateSignedCaveatLender(originationDetails, lender, bytes32(msg.sig), true); + + vm.prank(borrower.addr); + erc721s[0].approve(address(proxy), 1); + vm.prank(lender.addr); + erc20s[0].transfer(address(proxy), 1); + bytes[] memory actions = new bytes[](4); + actions[0] = abi.encode( + ERC1271Proxy.Action({ + to: address(erc721s[0]), + data: abi.encodeWithSelector(ERC721.transferFrom.selector, borrower.addr, address(proxy), 1) + }) + ); + actions[1] = abi.encode( + ERC1271Proxy.Action({ + to: address(erc721s[0]), + data: abi.encodeWithSelector(ERC721.approve.selector, address(SP), 1) + }) + ); + actions[2] = abi.encode( + ERC1271Proxy.Action({ + to: address(SP), + data: abi.encodeWithSelector( + Starport.originate.selector, + new AdditionalTransfer[](0), + _emptyCaveat(), + lenderCaveat, + originationDetails + ) + }) + ); + actions[3] = abi.encode( + ERC1271Proxy.Action({ + to: address(erc20s[0]), + data: abi.encodeWithSelector(ERC20.transfer.selector, borrower.addr, originationDetails.debt[0].amount) + }) + ); + + bytes32 hash = keccak256(abi.encode(actions)); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(borrower.key, hash); + vm.startPrank(fulfiller.addr); + proxy.execute(actions, abi.encodePacked(r, s, v)); + } + + function testNewLoanAs1271ProxyAccountThirdPartyFiller() public { + ERC1271Proxy proxy = new ERC1271Proxy(borrower.addr); + + Starport.Loan memory originationDetails = _generateOriginationDetails( + _getERC721SpentItem(erc721s[0]), _getERC20SpentItem(erc20s[0], 100), lender.addr + ); + originationDetails.borrower = address(proxy); + _setApprovalsForSpentItems(originationDetails.issuer, originationDetails.debt); + + CaveatEnforcer.SignedCaveats memory borrowerCaveat = _generateSignedCaveatsBorrowerProxy( + originationDetails, address(proxy), borrower, address(borrowerEnforcer), bytes32(msg.sig), true + ); + CaveatEnforcer.SignedCaveats memory lenderCaveat = + _generateSignedCaveatLender(originationDetails, lender, bytes32(msg.sig), true); + + vm.prank(borrower.addr); + erc721s[0].approve(address(proxy), 1); + vm.prank(lender.addr); + erc20s[0].transfer(address(proxy), 1); + bytes[] memory actions = new bytes[](2); + actions[0] = abi.encode( + ERC1271Proxy.Action({ + to: address(erc721s[0]), + data: abi.encodeWithSelector(ERC721.transferFrom.selector, borrower.addr, address(proxy), 1) + }) + ); + actions[1] = abi.encode( + ERC1271Proxy.Action({ + to: address(erc721s[0]), + data: abi.encodeWithSelector(ERC721.approve.selector, address(SP), 1) + }) + ); + + bytes32 hash = keccak256(abi.encode(actions)); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(borrower.key, hash); + vm.startPrank(fulfiller.addr); + proxy.execute(actions, abi.encodePacked(r, s, v)); + SP.originate(new AdditionalTransfer[](0), borrowerCaveat, lenderCaveat, originationDetails); + } + + function signCaveatForProxyAccount( + CaveatEnforcer.Caveat memory caveat, + bytes32 salt, + address account, + Account memory signer, + bool invalidate + ) public view returns (CaveatEnforcer.SignedCaveats memory signedCaveats) { + signedCaveats = CaveatEnforcer.SignedCaveats({ + signature: "", + singleUse: invalidate, + deadline: block.timestamp + 1 days, + salt: salt, + caveats: new CaveatEnforcer.Caveat[](1) + }); + + signedCaveats.caveats[0] = caveat; + bytes32 hash = SP.hashCaveatWithSaltAndNonce( + account, signedCaveats.singleUse, salt, signedCaveats.deadline, signedCaveats.caveats + ); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signer.key, hash); + signedCaveats.signature = abi.encodePacked(r, s, v); + } + + function _generateSignedCaveatsBorrowerProxy( + Starport.Loan memory loan, + address account, + Account memory signer, + address enforcer, + bytes32 salt, + bool invalidate + ) public view returns (CaveatEnforcer.SignedCaveats memory) { + LenderEnforcer.Details memory details = LenderEnforcer.Details({loan: loan}); + return signCaveatForProxyAccount( + CaveatEnforcer.Caveat({enforcer: enforcer, data: abi.encode(details)}), salt, account, signer, invalidate + ); + } }