From 956d5b5201ce054536197030a0dac3de7aed6d35 Mon Sep 17 00:00:00 2001 From: Andrew Redden Date: Sun, 5 Nov 2023 07:51:46 -0400 Subject: [PATCH] initial fuzz originate --- foundry.toml | 1 + test/StarportTest.sol | 2 +- test/fuzz-testing/TestFuzzOrigination.sol | 130 ++++++++++++++++++ .../differential-fuzzing/TestStarPortLib.sol | 6 +- test/utils/Bound.sol | 94 ++++++++++--- 5 files changed, 215 insertions(+), 18 deletions(-) create mode 100644 test/fuzz-testing/TestFuzzOrigination.sol diff --git a/foundry.toml b/foundry.toml index 49315808..eb9efe73 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,5 +3,6 @@ src = "src" out = "out" libs = ["lib"] +ignored_error_codes = [2018, 9302, 5574] #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/test/StarportTest.sol b/test/StarportTest.sol index aae824be..1476ed7b 100644 --- a/test/StarportTest.sol +++ b/test/StarportTest.sol @@ -275,7 +275,7 @@ contract StarportTest is BaseOrderTest { uint256 i = 0; for (; i < items.length;) { if (items[i].itemType == ItemType.ERC20) { - ERC20(items[i].token).approve(address(SP), items[i].amount); + ERC20(items[i].token).approve(address(SP), type(uint256).max); } else if (items[i].itemType == ItemType.ERC721) { ERC721(items[i].token).setApprovalForAll(address(SP), true); } else if (items[i].itemType == ItemType.ERC1155) { diff --git a/test/fuzz-testing/TestFuzzOrigination.sol b/test/fuzz-testing/TestFuzzOrigination.sol new file mode 100644 index 00000000..e84e54eb --- /dev/null +++ b/test/fuzz-testing/TestFuzzOrigination.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT +import "starport-test/StarportTest.sol"; +import "starport-test/utils/Bound.sol"; + +contract TestFuzzOrigination is StarportTest, Bound { + function setUp() public override { + super.setUp(); + + vm.warp(100_000); + } + + function _boundTokenByItemType(ItemType itemType) internal view override returns (address token) { + if (itemType == ItemType.ERC20) { + token = address(erc20s[0]); + } else if (itemType == ItemType.ERC721) { + token = address(erc721s[0]); + } else if (itemType == ItemType.ERC1155) { + token = address(erc1155s[0]); + } else { + revert("invalid itemType"); + } + } + + function _issueAndApproveTarget(SpentItem[] memory what, address who, address target) internal { + vm.startPrank(who); + for (uint256 i = 0; i < what.length; i++) { + if (what[i].itemType == ItemType.ERC20) { + vm.assume(TestERC20(what[i].token).totalSupply() + what[i].amount < type(uint256).max); + TestERC20(what[i].token).mint(who, what[i].amount); + TestERC20(what[i].token).approve(address(target), type(uint256).max); + } else if (what[i].itemType == ItemType.ERC721) { + TestERC721(what[i].token).mint(who, what[i].identifier); + TestERC721(what[i].token).approve(address(target), what[i].identifier); + } else if (what[i].itemType == ItemType.ERC1155) { + TestERC1155(what[i].token).mint(who, what[i].identifier, what[i].amount); + TestERC1155(what[i].token).setApprovalForAll(address(target), true); + } + } + vm.stopPrank(); + } + + function boundPricingData() internal view returns (bytes memory pricingData) { + BasePricing.Details memory details = BasePricing.Details({ + rate: _boundMin(0, (uint256(1e16) * 150) / (365 * 1 days)), + carryRate: _boundMin(0, uint256((1e16 * 100))) + }); + pricingData = abi.encode(details); + } + + function boundStatusData() internal view returns (bytes memory statusData) { + FixedTermStatus.Details memory details = FixedTermStatus.Details({loanDuration: _boundMin(1 hours, 1095 days)}); + statusData = abi.encode(details); + } + + function boundSettlementData() internal view returns (bytes memory settlementData) { + DutchAuctionSettlement.Details memory details = DutchAuctionSettlement.Details({ + startingPrice: _boundMin(1000 ether, 501 ether), + endingPrice: _boundMin(500 ether, 1 ether), + window: _boundMin(1 days, 100 days) + }); + settlementData = abi.encode(details); + } + + function boundFuzzLenderTerms() internal view returns (Starport.Terms memory terms) { + terms.status = address(status); + terms.settlement = address(settlement); + terms.pricing = address(pricing); + terms.pricingData = boundPricingData(); + terms.statusData = boundStatusData(); + terms.settlementData = boundSettlementData(); + } + + struct FuzzLoan { + address custodian; //where the collateral is being held + address issuer; //the capital issuer/lender + address fulfiller; + Fuzz.SpentItem[] collateral; //array of collateral + } + + function boundFuzzLoan(FuzzLoan memory params) internal returns (Starport.Loan memory loan) { + uint256 length = _boundMin(1, 4); + loan.terms = boundFuzzLenderTerms(); + uint256 i = 0; + if (length > params.collateral.length) { + length = params.collateral.length; + } + SpentItem[] memory ret = new SpentItem[](length); + + console.log(params.collateral.length); + for (; i < length; i++) { + ret[i] = _boundSpentItem(params.collateral[i]); + } + loan.collateral = ret; + SpentItem[] memory debt = new SpentItem[](1); + debt[0] = SpentItem({ + itemType: ItemType.ERC20, + identifier: 0, + amount: _boundMin(1, type(uint64).max), + token: address(erc20s[0]) + }); + loan.debt = debt; + loan.borrower = borrower.addr; + loan.custodian = SP.defaultCustodian(); + loan.issuer = lender.addr; + return loan; + } + + function willArithmeticOverflow(Starport.Loan memory loan) internal view { + FixedTermStatus.Details memory statusDetails = abi.decode(loan.terms.statusData, (FixedTermStatus.Details)); + BasePricing.Details memory pricingDetails = abi.decode(loan.terms.pricingData, (BasePricing.Details)); + try BasePricing(loan.terms.pricing).getPaymentConsideration(loan) returns ( + SpentItem[] memory repayConsideration, SpentItem[] memory carryConsideration + ) {} catch { + revert("arithmetic overflow"); + } + } + + function testFuzzNewOrigination(FuzzLoan memory params) public { + vm.assume(params.collateral.length > 1); + Starport.Loan memory loan = boundFuzzLoan(params); + willArithmeticOverflow(loan); + _issueAndApproveTarget(loan.collateral, loan.borrower, address(SP)); + _issueAndApproveTarget(loan.debt, loan.issuer, address(SP)); + + bytes32 borrowerSalt = _boundMinBytes32(0, type(uint256).max); + bytes32 lenderSalt = _boundMinBytes32(0, type(uint256).max); + address fulfiller = _toAddress(_boundMin(_toUint(params.fulfiller), 100)); + newLoan(loan, borrowerSalt, lenderSalt, fulfiller); + } +} diff --git a/test/fuzz-testing/differential-fuzzing/TestStarPortLib.sol b/test/fuzz-testing/differential-fuzzing/TestStarPortLib.sol index 0d4bd326..c97bc273 100644 --- a/test/fuzz-testing/differential-fuzzing/TestStarPortLib.sol +++ b/test/fuzz-testing/differential-fuzzing/TestStarPortLib.sol @@ -27,7 +27,11 @@ contract DiffFuzzTestStarportLib is Test, Bound, DeepEq { refContract = new RefStarportLibImpl(); } - function testSpentToReceived(Fuzz.SpentItem[] memory unbSpentItems) public view { + function _boundTokenByItemType(ItemType itemType) internal view override returns (address token) { + token = _toAddress(_boundMin(100, 1000)); + } + + function testSpentToReceived(Fuzz.SpentItem[] memory unbSpentItems) public { SpentItem[] memory spentItems = _boundSpentItems(unbSpentItems); ReceivedItem[] memory consideration0 = testContract.toReceivedItems(spentItems, address(1)); diff --git a/test/utils/Bound.sol b/test/utils/Bound.sol index ce3c888b..7b380766 100644 --- a/test/utils/Bound.sol +++ b/test/utils/Bound.sol @@ -1,47 +1,109 @@ -pragma solidity ^0.8.17; +pragma solidity =0.8.17; -import {ItemType, SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import { + ItemType, + SpentItem, + ReceivedItem, + OfferItem, + ConsiderationItem +} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {Cast} from "starport-test/utils/Cast.sol"; import "starport-test/utils/FuzzStructs.sol" as Fuzz; import "forge-std/Test.sol"; +import "forge-std/Vm.sol"; abstract contract Bound is StdUtils { + VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); + using Cast for *; - function _boundItemType(uint8 itemType) internal pure returns (ItemType) { - return _bound(itemType, uint8(ItemType.NATIVE), uint8(ItemType.ERC1155_WITH_CRITERIA)).toItemType(); + mapping(uint256 => bool) public used; + + function _boundItemType(uint8 itemType) internal view returns (ItemType) { + return _bound(itemType, uint8(ItemType.ERC20), uint8(ItemType.ERC1155)).toItemType(); } - function _boundSpentItem(Fuzz.SpentItem memory input) internal pure returns (SpentItem memory ret) { - ret = SpentItem({ - itemType: _boundItemType(input.itemType), - token: input.token, - identifier: input.identifier, - amount: input.amount - }); + function _boundTokenByItemType(ItemType itemType) internal view virtual returns (address); + + function _boundSpentItem(Fuzz.SpentItem memory input) internal returns (SpentItem memory ret) { + ItemType itemType = _boundItemType(input.itemType); + address token = _boundTokenByItemType(itemType); + if (itemType == ItemType.ERC721) { + input.identifier = _boundMin(4, type(uint256).max); + vm.assume(!used[input.identifier]); + input.amount = 1; + used[input.identifier] = true; + } else if (itemType == ItemType.ERC20) { + input.identifier = 0; + input.amount = _boundMin(1, type(uint32).max); + } else if (itemType == ItemType.ERC1155) { + input.amount = _boundMin(1, type(uint64).max); + } + + ret = SpentItem({itemType: itemType, token: token, identifier: input.identifier, amount: input.amount}); } - function _boundSpentItems(Fuzz.SpentItem[] memory input) internal pure returns (SpentItem[] memory ret) { + function _boundSpentItems(Fuzz.SpentItem[] memory input) internal returns (SpentItem[] memory ret) { + vm.assume(input.length <= 4); ret = new SpentItem[](input.length); for (uint256 i = 0; i < input.length; i++) { ret[i] = _boundSpentItem(input[i]); } } - function _boundReceivedItem(Fuzz.ReceivedItem memory input) internal pure returns (ReceivedItem memory ret) { + function _boundReceivedItem(Fuzz.ReceivedItem memory input) internal view returns (ReceivedItem memory ret) { + ItemType itemType = _boundItemType(input.itemType); + address token = _boundTokenByItemType(itemType); + // if (itemType == uint8(ItemType.ERC721)) { + // assert(!used[input.identifier]); + // used[itemType] = input.identifier; + // } ret = ReceivedItem({ - itemType: _boundItemType(input.itemType), - token: input.token, + itemType: itemType, + token: token, identifier: input.identifier, amount: input.amount, recipient: input.recipient }); } - function _boundReceivedItems(Fuzz.ReceivedItem[] memory input) internal pure returns (ReceivedItem[] memory ret) { + function _boundReceivedItems(Fuzz.ReceivedItem[] memory input) internal view returns (ReceivedItem[] memory ret) { + vm.assume(input.length <= 4); ret = new ReceivedItem[](input.length); for (uint256 i = 0; i < input.length; i++) { ret[i] = _boundReceivedItem(input[i]); } } + + function _boundMin(uint256 value, uint256 min) internal view returns (uint256) { + return _bound(value, min, type(uint256).max); + } + + function _boundMinBytes32(uint256 value, uint256 min) internal view returns (bytes32) { + return bytes32(_bound(value, min, type(uint256).max)); + } + + function _boundMax(uint256 value, uint256 max) internal view returns (uint256) { + return _bound(value, 0, max); + } + + function _boundNonZero(uint256 value) internal view returns (uint256) { + return _boundMin(value, 1); + } + + function _toUint(address value) internal view returns (uint256 output) { + assembly { + output := value + } + } + + function _toAddress(uint256 value) internal view returns (address output) { + assembly { + output := value + } + } + + function _boundNonZero(address value) internal view returns (address) { + return _toAddress(_boundMin(_toUint(value), 1)); + } }