From 739c10cbfc9d703a6bd91993593bcfeb1f561853 Mon Sep 17 00:00:00 2001 From: GregTheDev Date: Wed, 25 Oct 2023 11:24:18 -0500 Subject: [PATCH 1/2] differential fuzzing of lib --- src/lib/RefStarPortLib.sol | 64 +++++++ src/lib/StarPortLib.sol | 16 +- .../differential-fuzzing/TestStarPortLib.sol | 140 ++++++++++++++++ .../differential-fuzzing/TestUtils.sol | 157 ------------------ 4 files changed, 206 insertions(+), 171 deletions(-) create mode 100644 src/lib/RefStarPortLib.sol create mode 100644 test/fuzz-testing/differential-fuzzing/TestStarPortLib.sol delete mode 100644 test/fuzz-testing/differential-fuzzing/TestUtils.sol 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..a7c6adf9 --- /dev/null +++ b/test/fuzz-testing/differential-fuzzing/TestStarPortLib.sol @@ -0,0 +1,140 @@ +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; - } - } - } -} From 76777aa7dd84e3e21d49507802df1b35c2418f39 Mon Sep 17 00:00:00 2001 From: 0xgregthedev <0xgregthedev@users.noreply.github.com> Date: Wed, 25 Oct 2023 16:32:04 +0000 Subject: [PATCH 2/2] format & snapshot --- .gas-snapshot | 44 ++++++++++--------- .../differential-fuzzing/TestStarPortLib.sol | 1 - 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index edbc069a..2d068fc4 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,10 +1,12 @@ -EnforcerTest:testCollateralEnforcer() (gas: 954073) -EnforcerTest:testFailCollateralEnforcerDifferentCollateral() (gas: 834092) -EnforcerTest:testFailRateEnforcerMaxCarryRate() (gas: 789228) -EnforcerTest:testFailRateEnforcerMaxRate() (gas: 789200) -EnforcerTest:testFailRateEnforcerMaxRateAndMaxCarryRate() (gas: 789065) -EnforcerTest:testRateEnforcerBasic() (gas: 911246) -EnforcerTest:testTermEnforcerBasic() (gas: 983574) +DiffFuzzTestStarPortLib:testSpentToReceived((uint8,address,uint256,uint256)[]) (runs: 256, μ: 880511, ~: 882230) +DiffFuzzTestStarPortLib:testUnboundSpentToReceived((uint8,address,uint256,uint256)[]) (runs: 256, μ: 232905, ~: 237832) +EnforcerTest:testCollateralEnforcer() (gas: 954065) +EnforcerTest:testFailCollateralEnforcerDifferentCollateral() (gas: 834084) +EnforcerTest:testFailRateEnforcerMaxCarryRate() (gas: 789220) +EnforcerTest:testFailRateEnforcerMaxRate() (gas: 789192) +EnforcerTest:testFailRateEnforcerMaxRateAndMaxCarryRate() (gas: 789057) +EnforcerTest:testRateEnforcerBasic() (gas: 911238) +EnforcerTest:testTermEnforcerBasic() (gas: 983566) TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallBase() (gas: 1218188) TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLender() (gas: 858353) TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLiquidation() (gas: 894602) @@ -56,22 +58,22 @@ TestLoanCombinations:testLoanSimpleInterestEnglishFixed() (gas: 701482) TestLoanManager:testCannotIssueSameLoanTwice() (gas: 1529536) TestLoanManager:testCannotSettleInvalidLoan() (gas: 72844) TestLoanManager:testCannotSettleUnlessValidCustodian() (gas: 68990) -TestLoanManager:testCaveatEnforcerInvalidOrigination() (gas: 1797025) +TestLoanManager:testCaveatEnforcerInvalidOrigination() (gas: 1797017) TestLoanManager:testDefaultFeeRake() (gas: 403187) TestLoanManager:testExoticDebtWithNoCaveatsAsBorrower() (gas: 1587178) -TestLoanManager:testExoticDebtWithNoCaveatsNotAsBorrower() (gas: 1677088) -TestLoanManager:testGenerateOrder() (gas: 1495062) +TestLoanManager:testExoticDebtWithNoCaveatsNotAsBorrower() (gas: 1677080) +TestLoanManager:testGenerateOrder() (gas: 1495054) TestLoanManager:testGenerateOrderInvalidAction() (gas: 1354698) TestLoanManager:testGenerateOrderNotSeaport() (gas: 13023) TestLoanManager:testInitializedFlagSetProperly() (gas: 65194) TestLoanManager:testInvalidDebtLength() (gas: 39181) -TestLoanManager:testInvalidDebtType() (gas: 1364311) +TestLoanManager:testInvalidDebtType() (gas: 1364303) TestLoanManager:testInvalidMaximumSpentEmpty() (gas: 50192) TestLoanManager:testIssued() (gas: 67041) TestLoanManager:testName() (gas: 7209) TestLoanManager:testNativeDebtWithNoCaveatsAsBorrower() (gas: 1500521) -TestLoanManager:testNativeDebtWithNoCaveatsNotAsBorrower() (gas: 1541426) -TestLoanManager:testNativeDebtWithNoCaveatsNotAsBorrowerFeesOn() (gas: 1604818) +TestLoanManager:testNativeDebtWithNoCaveatsNotAsBorrower() (gas: 1541418) +TestLoanManager:testNativeDebtWithNoCaveatsNotAsBorrowerFeesOn() (gas: 1604810) TestLoanManager:testNonDefaultCustodianCustodyCallFails() (gas: 23953) TestLoanManager:testNonDefaultCustodianCustodyCallSuccess() (gas: 25327) TestLoanManager:testNonPayableFunctions() (gas: 109562) @@ -92,13 +94,13 @@ TestLoanManager:testSymbol() (gas: 7238) TestLoanManager:testTokenURI() (gas: 64967) TestLoanManager:testTokenURIInvalidLoan() (gas: 13290) TestLoanManager:testTransferFromFailFromSeaport() (gas: 82187) -TestNewLoan:testBuyNowPayLater() (gas: 1143307) -TestNewLoan:testNewLoanERC721CollateralDefaultTerms2():((uint256,address,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256)[],(address,bytes,address,bytes,address,bytes))) (gas: 982374) -TestNewLoan:testNewLoanERC721CollateralLessDebtThanOffered():((uint256,address,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256)[],(address,bytes,address,bytes,address,bytes))) (gas: 982708) +TestNewLoan:testBuyNowPayLater() (gas: 1143299) +TestNewLoan:testNewLoanERC721CollateralDefaultTerms2() (gas: 982366) +TestNewLoan:testNewLoanERC721CollateralLessDebtThanOffered() (gas: 982700) TestNewLoan:testNewLoanRefinanceNew() (gas: 702267) -TestNewLoan:testSettleLoan() (gas: 1223453) +TestNewLoan:testSettleLoan() (gas: 1223445) +TestRefStarPortLib:testSpentToReceived() (gas: 13315) +TestRefStarPortLib:testValidateSalt(address,bytes32) (runs: 256, μ: 33865, ~: 33865) TestRepayLoan:testRepayLoan() (gas: 683942) -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/test/fuzz-testing/differential-fuzzing/TestStarPortLib.sol b/test/fuzz-testing/differential-fuzzing/TestStarPortLib.sol index a7c6adf9..d4835cfb 100644 --- a/test/fuzz-testing/differential-fuzzing/TestStarPortLib.sol +++ b/test/fuzz-testing/differential-fuzzing/TestStarPortLib.sol @@ -52,7 +52,6 @@ contract DiffFuzzTestStarPortLib is Test, Bound, DeepEq { assertTrue(success, "expected success"); } } - } abstract contract BaseTestStarPortLib is Test {