diff --git a/.gas-snapshot b/.gas-snapshot index e873aaac..2ed265b7 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,6 +1,8 @@ -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallBase() (gas: 1039520) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLender() (gas: 691168) -TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLiquidation() (gas: 770737) +DiffFuzzTestStarPortLib:testSpentToReceived((uint8,address,uint256,uint256)[]) (runs: 256, μ: 880466, ~: 883301) +DiffFuzzTestStarPortLib:testUnboundSpentToReceived((uint8,address,uint256,uint256)[]) (runs: 256, μ: 232905, ~: 237832) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallBase() (gas: 1039242) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLender() (gas: 691152) +TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLiquidation() (gas: 770721) TestCustodian:testCannotLazyMintTwice() (gas: 76597) TestCustodian:testCannotMintInvalidLoanInvalidCustodian() (gas: 66883) TestCustodian:testCannotMintInvalidLoanValidCustodian() (gas: 72422) @@ -9,9 +11,9 @@ 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:testGenerateOrderRepayERC1155AndERC20AndNative() (gas: 843710) +TestCustodian:testGenerateOrderRepayERC1155AndERC20AndNativeHandlerAuthorized() (gas: 785342) +TestCustodian:testGenerateOrderRepayERC1155WithRevert() (gas: 518146) TestCustodian:testGenerateOrderRepayInvalidHookAddress() (gas: 90249) TestCustodian:testGenerateOrderRepayInvalidHookReturnType() (gas: 84653) TestCustodian:testGenerateOrderRepayNotBorrower() (gas: 96449) @@ -39,44 +41,45 @@ 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) +TestLoanCombinations:testLoan20For721SimpleInterestDutchFixedRepay() (gas: 530961) +TestLoanCombinations:testLoan20for20SimpleInterestDutchFixedRepay() (gas: 499798) +TestLoanCombinations:testLoan721for20SimpleInterestDutchFixedRepay() (gas: 549718) +TestLoanCombinations:testLoanAstariaSettlementRepay() (gas: 539542) +TestLoanCombinations:testLoanSimpleInterestEnglishFixed() (gas: 544202) +TestLoanManager:testAdditionalTransfers() (gas: 293098) +TestLoanManager:testCannotIssueSameLoanTwice() (gas: 331317) 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:testCaveatEnforcerRevert() (gas: 116691) +TestLoanManager:testDefaultFeeRake() (gas: 350066) +TestLoanManager:testExoticDebtWithNoCaveatsNotAsBorrower() (gas: 342790) +TestLoanManager:testInitializedFlagSetProperly() (gas: 65252) +TestLoanManager:testInvalidAmountCollateral() (gas: 152889) +TestLoanManager:testInvalidAmountCollateral721() (gas: 152987) +TestLoanManager:testInvalidAmountDebt() (gas: 177200) +TestLoanManager:testInvalidItemType() (gas: 138937) +TestLoanManager:testInvalidTransferLengthCollateral() (gas: 161128) +TestLoanManager:testInvalidTransferLengthDebt() (gas: 165661) TestLoanManager:testIssued() (gas: 67055) TestLoanManager:testName() (gas: 7184) -TestLoanManager:testNonDefaultCustodianCustodyCallFails() (gas: 190240) -TestLoanManager:testNonDefaultCustodianCustodyCallSuccess() (gas: 258491) -TestLoanManager:testNonPayableFunctions() (gas: 173071) -TestLoanManager:testOverrideFeeRake() (gas: 343912) -TestLoanManager:testSupportsInterface() (gas: 9159) +TestLoanManager:testNonDefaultCustodianCustodyCallFails() (gas: 190232) +TestLoanManager:testNonDefaultCustodianCustodyCallSuccess() (gas: 258505) +TestLoanManager:testNonPayableFunctions() (gas: 173085) +TestLoanManager:testOverrideFeeRake() (gas: 343904) +TestLoanManager:testSupportsInterface() (gas: 9181) TestLoanManager:testSymbol() (gas: 7127) -TestLoanManager:testTokenNoCodeCollateral() (gas: 137717) -TestLoanManager:testTokenNoCodeDebt() (gas: 170775) +TestLoanManager:testTokenNoCodeCollateral() (gas: 137709) +TestLoanManager:testTokenNoCodeDebt() (gas: 170767) TestLoanManager:testTokenURI() (gas: 64914) -TestLoanManager:testTokenURIInvalidLoan() (gas: 13309) +TestLoanManager:testTokenURIInvalidLoan() (gas: 13244) TestLoanManager:testTransferFromFail() (gas: 80088) TestNewLoan:testBuyNowPayLater() (gas: 208) -TestNewLoan:testNewLoanERC721CollateralDefaultTerms2():((uint256,address,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256)[],(address,bytes,address,bytes,address,bytes))) (gas: 389501) -TestNewLoan:testNewLoanERC721CollateralLessDebtThanOffered():((uint256,address,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256)[],(address,bytes,address,bytes,address,bytes))) (gas: 2259) +TestNewLoan:testNewLoanERC721CollateralDefaultTerms2() (gas: 389485) +TestNewLoan:testNewLoanERC721CollateralLessDebtThanOffered() (gas: 2259) TestNewLoan:testNewLoanRefinanceNew() (gas: 207) 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) \ No newline at end of file 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/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; - } - } - } -}