From 8e5499db61fb1104513f2f5f5e576f82aa2d798b Mon Sep 17 00:00:00 2001 From: Vectorized Date: Fri, 5 Apr 2024 09:54:00 +0000 Subject: [PATCH] Optimize FindFirstSet routine --- .gas-snapshot | 118 ++++++++++++++++++++++---------------------- src/DN404.sol | 12 +++-- test/Mappings.t.sol | 12 +++-- 3 files changed, 75 insertions(+), 67 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index f61ce1c..cdab083 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -67,108 +67,108 @@ BenchTest:testMintPandora_13() (gas: 967960) BenchTest:testMintPandora_14() (gas: 1037484) BenchTest:testMintPandora_15() (gas: 1106967) BenchTest:testMintPandora_16() (gas: 1176473) -BenchTest:test__codesize() (gas: 26827) +BenchTest:test__codesize() (gas: 26848) DN404CustomUnitTest:testInitializeCorrectUnitSuccess() (gas: 129962) DN404CustomUnitTest:testInitializeWithUnitTooLargeReverts() (gas: 33824) DN404CustomUnitTest:testInitializeWithZeroUnitReverts() (gas: 13897) DN404CustomUnitTest:testMint() (gas: 162923) -DN404CustomUnitTest:testMintWithoutNFTs(uint256,uint256,uint256) (runs: 256, μ: 154657, ~: 163018) +DN404CustomUnitTest:testMintWithoutNFTs(uint256,uint256,uint256) (runs: 258, μ: 154944, ~: 163018) DN404CustomUnitTest:testNFTMint() (gas: 57526664) -DN404CustomUnitTest:testNFTMintAndBurn(uint256,uint256,uint256) (runs: 256, μ: 207418, ~: 161918) -DN404CustomUnitTest:testNFTMintViaTransfer(uint256,uint256,uint256) (runs: 256, μ: 217261, ~: 241493) -DN404CustomUnitTest:testTotalSupplyOverflowsTrick(uint256,uint256) (runs: 256, μ: 635, ~: 692) -DN404CustomUnitTest:testTotalSupplyOverflowsTrick(uint256,uint256,uint256) (runs: 256, μ: 780, ~: 746) -DN404CustomUnitTest:testUnitInvalidCheckTrick(uint256) (runs: 256, μ: 548, ~: 550) -DN404CustomUnitTest:test__codesize() (gas: 28180) +DN404CustomUnitTest:testNFTMintAndBurn(uint256,uint256,uint256) (runs: 258, μ: 206548, ~: 161918) +DN404CustomUnitTest:testNFTMintViaTransfer(uint256,uint256,uint256) (runs: 258, μ: 218693, ~: 241612) +DN404CustomUnitTest:testTotalSupplyOverflowsTrick(uint256,uint256) (runs: 258, μ: 635, ~: 692) +DN404CustomUnitTest:testTotalSupplyOverflowsTrick(uint256,uint256,uint256) (runs: 258, μ: 779, ~: 746) +DN404CustomUnitTest:testUnitInvalidCheckTrick(uint256) (runs: 258, μ: 548, ~: 550) +DN404CustomUnitTest:test__codesize() (gas: 28201) DN404MirrorTest:testBaseERC20() (gas: 114578) DN404MirrorTest:testFnSelectorNotRecognized() (gas: 7032) DN404MirrorTest:testLinkMirrorContract() (gas: 45864) DN404MirrorTest:testLogDirectTransfers() (gas: 378691) DN404MirrorTest:testLogTransfer() (gas: 120655) -DN404MirrorTest:testNameAndSymbol(string,string) (runs: 256, μ: 207746, ~: 208087) +DN404MirrorTest:testNameAndSymbol(string,string) (runs: 258, μ: 207574, ~: 208087) DN404MirrorTest:testNotLinked() (gas: 12767) DN404MirrorTest:testPullOwner() (gas: 112613) -DN404MirrorTest:testPullOwnerWithOwnable() (gas: 3244380) -DN404MirrorTest:testSafeTransferFrom(uint32) (runs: 256, μ: 465850, ~: 465839) +DN404MirrorTest:testPullOwnerWithOwnable() (gas: 3248596) +DN404MirrorTest:testSafeTransferFrom(uint32) (runs: 258, μ: 465848, ~: 465839) DN404MirrorTest:testSetAndGetApprovalForAll() (gas: 325298) DN404MirrorTest:testSetAndGetApproved() (gas: 322497) DN404MirrorTest:testSupportsInterface() (gas: 7544) -DN404MirrorTest:testTokenURI(string,uint256) (runs: 256, μ: 158053, ~: 135791) -DN404MirrorTest:testTransferFrom(uint32) (runs: 256, μ: 366878, ~: 366870) -DN404MirrorTest:testTransferFromMixed(uint256) (runs: 256, μ: 654691, ~: 601450) -DN404MirrorTest:test__codesize() (gas: 56875) +DN404MirrorTest:testTokenURI(string,uint256) (runs: 258, μ: 158056, ~: 135791) +DN404MirrorTest:testTransferFrom(uint32) (runs: 258, μ: 366878, ~: 366870) +DN404MirrorTest:testTransferFromMixed(uint256) (runs: 258, μ: 657212, ~: 616815) +DN404MirrorTest:test__codesize() (gas: 56917) DN404OnlyERC20Test:testApprove() (gas: 35803) -DN404OnlyERC20Test:testApprove(address,uint256) (runs: 256, μ: 30110, ~: 31354) +DN404OnlyERC20Test:testApprove(address,uint256) (runs: 258, μ: 30505, ~: 31354) DN404OnlyERC20Test:testBurn() (gas: 51486) -DN404OnlyERC20Test:testBurn(address,uint256,uint256) (runs: 256, μ: 52745, ~: 52687) -DN404OnlyERC20Test:testBurnInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 45816, ~: 45844) +DN404OnlyERC20Test:testBurn(address,uint256,uint256) (runs: 258, μ: 52484, ~: 52687) +DN404OnlyERC20Test:testBurnInsufficientBalanceReverts(address,uint256,uint256) (runs: 258, μ: 45646, ~: 45780) DN404OnlyERC20Test:testInfiniteApproveTransferFrom() (gas: 104368) -DN404OnlyERC20Test:testMaxSupplyTrick(uint256) (runs: 256, μ: 541, ~: 541) +DN404OnlyERC20Test:testMaxSupplyTrick(uint256) (runs: 258, μ: 541, ~: 541) DN404OnlyERC20Test:testMetadata() (gas: 10044) DN404OnlyERC20Test:testMint() (gas: 47556) DN404OnlyERC20Test:testMintOverMaxLimitReverts() (gas: 43535) -DN404OnlyERC20Test:testMintz(address,uint256) (runs: 256, μ: 47963, ~: 47978) +DN404OnlyERC20Test:testMintz(address,uint256) (runs: 258, μ: 47999, ~: 47978) DN404OnlyERC20Test:testTransfer() (gas: 77086) -DN404OnlyERC20Test:testTransfer(address,uint256) (runs: 256, μ: 77523, ~: 77543) +DN404OnlyERC20Test:testTransfer(address,uint256) (runs: 258, μ: 77584, ~: 77543) DN404OnlyERC20Test:testTransferFrom() (gas: 86826) -DN404OnlyERC20Test:testTransferFrom(address,address,address,uint256,uint256) (runs: 256, μ: 107687, ~: 109795) +DN404OnlyERC20Test:testTransferFrom(address,address,address,uint256,uint256) (runs: 258, μ: 108050, ~: 109795) DN404OnlyERC20Test:testTransferFromInsufficientAllowanceReverts() (gas: 70261) -DN404OnlyERC20Test:testTransferFromInsufficientAllowanceReverts(address,uint256,uint256) (runs: 256, μ: 70872, ~: 71371) +DN404OnlyERC20Test:testTransferFromInsufficientAllowanceReverts(address,uint256,uint256) (runs: 258, μ: 70540, ~: 71362) DN404OnlyERC20Test:testTransferFromInsufficientBalanceReverts() (gas: 76697) -DN404OnlyERC20Test:testTransferFromInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 77940, ~: 77827) +DN404OnlyERC20Test:testTransferFromInsufficientBalanceReverts(address,uint256,uint256) (runs: 258, μ: 77778, ~: 77818) DN404OnlyERC20Test:testTransferInsufficientBalanceReverts() (gas: 68196) -DN404OnlyERC20Test:testTransferInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 69324, ~: 69283) -DN404OnlyERC20Test:test__codesize() (gas: 30269) +DN404OnlyERC20Test:testTransferInsufficientBalanceReverts(address,uint256,uint256) (runs: 258, μ: 69152, ~: 69283) +DN404OnlyERC20Test:test__codesize() (gas: 30290) DN404Test:testBatchNFTLog() (gas: 291443) -DN404Test:testBurnOnTransfer(uint32,address) (runs: 256, μ: 269747, ~: 269767) +DN404Test:testBurnOnTransfer(uint32,address) (runs: 258, μ: 269767, ~: 269767) DN404Test:testFnSelectorNotRecognized() (gas: 6974) -DN404Test:testInitialize(uint32,address) (runs: 256, μ: 112848, ~: 116293) +DN404Test:testInitialize(uint32,address) (runs: 258, μ: 112476, ~: 116293) DN404Test:testMintAndBurn() (gas: 328527) DN404Test:testMintAndBurn2() (gas: 275124) DN404Test:testMintNext() (gas: 627811) -DN404Test:testMintNextMixed(uint256) (runs: 256, μ: 649840, ~: 563338) -DN404Test:testMintOnTransfer(uint32,address) (runs: 256, μ: 287397, ~: 287417) -DN404Test:testMixed(uint256) (runs: 256, μ: 839247, ~: 678570) -DN404Test:testNameAndSymbol(string,string) (runs: 256, μ: 207464, ~: 207805) +DN404Test:testMintNextMixed(uint256) (runs: 258, μ: 642243, ~: 558410) +DN404Test:testMintOnTransfer(uint32,address) (runs: 258, μ: 287407, ~: 287417) +DN404Test:testMixed(uint256) (runs: 258, μ: 812401, ~: 646502) +DN404Test:testNameAndSymbol(string,string) (runs: 258, μ: 207292, ~: 207805) DN404Test:testNumAliasesOverflowReverts() (gas: 57345) DN404Test:testOwnedIds() (gas: 350398) -DN404Test:testOwnedIds(uint256) (runs: 256, μ: 269018, ~: 279561) +DN404Test:testOwnedIds(uint256) (runs: 258, μ: 272722, ~: 282630) DN404Test:testPermit2() (gas: 437378) -DN404Test:testRegisterAndResolveAlias(address,address) (runs: 256, μ: 126495, ~: 126593) -DN404Test:testSetAndGetAux(address,uint88) (runs: 256, μ: 22003, ~: 22344) -DN404Test:testSetAndGetOperatorApprovals(address,address,bool) (runs: 256, μ: 129355, ~: 120416) +DN404Test:testRegisterAndResolveAlias(address,address) (runs: 258, μ: 126496, ~: 126593) +DN404Test:testSetAndGetAux(address,uint88) (runs: 258, μ: 22006, ~: 22344) +DN404Test:testSetAndGetOperatorApprovals(address,address,bool) (runs: 258, μ: 130828, ~: 140316) DN404Test:testSetAndGetSkipNFT() (gas: 719127) -DN404Test:testTokenURI(string,uint256) (runs: 256, μ: 157919, ~: 135657) +DN404Test:testTokenURI(string,uint256) (runs: 258, μ: 157922, ~: 135657) DN404Test:testTransfersAndBurns() (gas: 461532) -DN404Test:testWrapAround(uint32,uint256) (runs: 256, μ: 347850, ~: 343630) -DN404Test:test__codesize() (gas: 55865) -MappingsTest:testAddressPairMapSetAndGet(address[2],address[2],uint256,uint256) (runs: 256, μ: 45731, ~: 47053) -MappingsTest:testBitmapSetAndGet(uint256) (runs: 256, μ: 448253, ~: 406079) -MappingsTest:testBitmapSetAndGet(uint256,uint256,bool,bool) (runs: 256, μ: 25726, ~: 26337) -MappingsTest:testFindFirstUnset() (gas: 80100) -MappingsTest:testFindFirstUnset(uint256) (runs: 256, μ: 309525, ~: 222129) -MappingsTest:testRestrictNFTId(uint256) (runs: 256, μ: 340, ~: 340) -MappingsTest:testSetOwnerAliasAndOwnedIndex(uint256,uint32,uint32) (runs: 256, μ: 23247, ~: 23516) -MappingsTest:testStorageSlotsNoCollision(uint256,uint256,uint256,uint256) (runs: 256, μ: 26827, ~: 26688) -MappingsTest:testUint32MapSetAndGet(uint256) (runs: 256, μ: 1372635, ~: 1526111) -MappingsTest:testUint32MapSetAndGet(uint256,uint256,uint32,uint32) (runs: 256, μ: 42310, ~: 46244) -MappingsTest:testWrapNFTIdWithOverflowCheck(uint256,uint256,uint256) (runs: 256, μ: 823, ~: 852) -MappingsTest:test__codesize() (gas: 7973) -MintTests:test_WhenAmountIsGreaterThan_MAX_SUPPLYOrMintMakesNFTTotalSupplyExceed_MAX_SUPPLY(uint256) (runs: 256, μ: 64533, ~: 64706) -MintTests:test_WhenRecipientAddressHasSkipNFTEnabled(uint256) (runs: 256, μ: 85927, ~: 85920) -MintTests:test_WhenRecipientIsAddress0(uint256) (runs: 256, μ: 31105, ~: 31169) -MintTests:test_WhenRecipientsBalanceDifferenceIsNotUpTo1e18(uint256) (runs: 256, μ: 82833, ~: 82970) -MintTests:test_WhenRecipientsBalanceDifferenceIsUpTo1e18OrAbove(uint256) (runs: 256, μ: 89409, ~: 89492) -MintTests:test__codesize() (gas: 26518) +DN404Test:testWrapAround(uint32,uint256) (runs: 258, μ: 347412, ~: 343378) +DN404Test:test__codesize() (gas: 55886) +MappingsTest:testAddressPairMapSetAndGet(address[2],address[2],uint256,uint256) (runs: 258, μ: 45433, ~: 47053) +MappingsTest:testBitmapSetAndGet(uint256) (runs: 258, μ: 463246, ~: 422073) +MappingsTest:testBitmapSetAndGet(uint256,uint256,bool,bool) (runs: 258, μ: 25664, ~: 26361) +MappingsTest:testFindFirstUnset() (gas: 79815) +MappingsTest:testFindFirstUnset(uint256) (runs: 258, μ: 303430, ~: 221863) +MappingsTest:testRestrictNFTId(uint256) (runs: 258, μ: 340, ~: 340) +MappingsTest:testSetOwnerAliasAndOwnedIndex(uint256,uint32,uint32) (runs: 258, μ: 23331, ~: 23516) +MappingsTest:testStorageSlotsNoCollision(uint256,uint256,uint256,uint256) (runs: 258, μ: 26806, ~: 26675) +MappingsTest:testUint32MapSetAndGet(uint256) (runs: 258, μ: 1375364, ~: 1571823) +MappingsTest:testUint32MapSetAndGet(uint256,uint256,uint32,uint32) (runs: 258, μ: 42568, ~: 46244) +MappingsTest:testWrapNFTIdWithOverflowCheck(uint256,uint256,uint256) (runs: 258, μ: 827, ~: 852) +MappingsTest:test__codesize() (gas: 7995) +MintTests:test_WhenAmountIsGreaterThan_MAX_SUPPLYOrMintMakesNFTTotalSupplyExceed_MAX_SUPPLY(uint256) (runs: 258, μ: 64532, ~: 64713) +MintTests:test_WhenRecipientAddressHasSkipNFTEnabled(uint256) (runs: 258, μ: 85930, ~: 85920) +MintTests:test_WhenRecipientIsAddress0(uint256) (runs: 258, μ: 31104, ~: 31169) +MintTests:test_WhenRecipientsBalanceDifferenceIsNotUpTo1e18(uint256) (runs: 258, μ: 82890, ~: 82974) +MintTests:test_WhenRecipientsBalanceDifferenceIsUpTo1e18OrAbove(uint256) (runs: 258, μ: 89464, ~: 89492) +MintTests:test__codesize() (gas: 26539) NFTMintDN404Test:testAllowlistMint() (gas: 257449) NFTMintDN404Test:testMint() (gas: 231750) NFTMintDN404Test:testTotalSupplyReached() (gas: 629566900) -NFTMintDN404Test:test__codesize() (gas: 26501) +NFTMintDN404Test:test__codesize() (gas: 26522) SimpleDN404Test:testMint() (gas: 47571) SimpleDN404Test:testName() (gas: 9674) SimpleDN404Test:testSetBaseURI() (gas: 47011) SimpleDN404Test:testSymbol() (gas: 9672) SimpleDN404Test:testWithdraw() (gas: 18277) -SimpleDN404Test:test__codesize() (gas: 20220) +SimpleDN404Test:test__codesize() (gas: 20241) SoladyTest:test__codesize() (gas: 1102) TestPlus:test__codesize() (gas: 406) \ No newline at end of file diff --git a/src/DN404.sol b/src/DN404.sol index 7346d0b..a014f10 100644 --- a/src/DN404.sol +++ b/src/DN404.sol @@ -1280,11 +1280,15 @@ abstract contract DN404 { } if negBits { // Find-first-set routine. + // From: https://github.com/vectorized/solady/blob/main/src/utils/LibBit.sol let b := and(negBits, add(not(negBits), 1)) // Isolate the least significant bit. - let r := shl(7, lt(0xffffffffffffffffffffffffffffffff, b)) - r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, b)))) - r := or(r, shl(5, lt(0xffffffff, shr(r, b)))) - // For the remaining 32 bits, use a De Bruijn lookup. + // For the upper 3 bits of the result, use a De Bruijn-like lookup. + // Credit to adhusson: https://blog.adhusson.com/cheap-find-first-set-evm/ + // forgefmt: disable-next-item + let r := shl(5, shr(252, shl(shl(2, shr(250, mul(b, + 0x2aaaaaaaba69a69a6db6db6db2cb2cb2ce739ce73def7bdeffffffff))), + 0x1412563212c14164235266736f7425221143267a45243675267677))) + // For the lower 5 bits of the result, use a De Bruijn lookup. // forgefmt: disable-next-item r := or(r, byte(and(div(0xd76453e0, shr(r, b)), 0x1f), 0x001f0d1e100c1d070f090b19131c1706010e11080a1a141802121b1503160405)) diff --git a/test/Mappings.t.sol b/test/Mappings.t.sol index 68eb94a..9c76a35 100644 --- a/test/Mappings.t.sol +++ b/test/Mappings.t.sol @@ -152,11 +152,15 @@ contract MappingsTest is SoladyTest { } if negBits { // Find-first-set routine. + // From: https://github.com/vectorized/solady/blob/main/src/utils/LibBit.sol let b := and(negBits, add(not(negBits), 1)) // Isolate the least significant bit. - let r := shl(7, lt(0xffffffffffffffffffffffffffffffff, b)) - r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, b)))) - r := or(r, shl(5, lt(0xffffffff, shr(r, b)))) - // For the remaining 32 bits, use a De Bruijn lookup. + // For the upper 3 bits of the result, use a De Bruijn-like lookup. + // Credit to adhusson: https://blog.adhusson.com/cheap-find-first-set-evm/ + // forgefmt: disable-next-item + let r := shl(5, shr(252, shl(shl(2, shr(250, mul(b, + 0x2aaaaaaaba69a69a6db6db6db2cb2cb2ce739ce73def7bdeffffffff))), + 0x1412563212c14164235266736f7425221143267a45243675267677))) + // For the lower 5 bits of the result, use a De Bruijn lookup. // forgefmt: disable-next-item r := or(r, byte(and(div(0xd76453e0, shr(r, b)), 0x1f), 0x001f0d1e100c1d070f090b19131c1706010e11080a1a141802121b1503160405))