diff --git a/.gas-snapshot b/.gas-snapshot index 72d1c96..b0d045d 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -67,7 +67,7 @@ BenchTest:testMintDN420_13() (gas: 111640) BenchTest:testMintDN420_14() (gas: 113114) BenchTest:testMintDN420_15() (gas: 114496) BenchTest:testMintDN420_16() (gas: 115948) -BenchTest:test__codesize() (gas: 28709) +BenchTest:test__codesize() (gas: 28697) DN404CustomUnitTest:testInitializeCorrectUnitSuccess() (gas: 129992) DN404CustomUnitTest:testInitializeWithUnitTooLargeReverts() (gas: 33824) DN404CustomUnitTest:testInitializeWithZeroUnitReverts() (gas: 13897) @@ -145,38 +145,38 @@ DN404Test:test__codesize() (gas: 57359) DN420OnlyERC20Test:testApprove() (gas: 35770) DN420OnlyERC20Test:testApprove(address,uint256) (runs: 258, μ: 30086, ~: 31321) DN420OnlyERC20Test:testBurn() (gas: 50438) -DN420OnlyERC20Test:testBurn(address,uint256,uint256) (runs: 258, μ: 51572, ~: 51639) -DN420OnlyERC20Test:testBurnInsufficientBalanceReverts(address,uint256,uint256) (runs: 258, μ: 44649, ~: 44709) +DN420OnlyERC20Test:testBurn(address,uint256,uint256) (runs: 258, μ: 51558, ~: 51639) +DN420OnlyERC20Test:testBurnInsufficientBalanceReverts(address,uint256,uint256) (runs: 258, μ: 44641, ~: 44709) DN420OnlyERC20Test:testInfiniteApproveTransferFrom() (gas: 102630) DN420OnlyERC20Test:testMaxSupplyTrick(uint256) (runs: 258, μ: 541, ~: 541) DN420OnlyERC20Test:testMetadata() (gas: 10112) DN420OnlyERC20Test:testMint() (gas: 45958) DN420OnlyERC20Test:testMintOverMaxLimitReverts() (gas: 41962) -DN420OnlyERC20Test:testMintz(address,uint256) (runs: 258, μ: 46269, ~: 46380) +DN420OnlyERC20Test:testMintz(address,uint256) (runs: 258, μ: 46257, ~: 46380) DN420OnlyERC20Test:testTransfer() (gas: 75434) -DN420OnlyERC20Test:testTransfer(address,uint256) (runs: 258, μ: 75786, ~: 75891) +DN420OnlyERC20Test:testTransfer(address,uint256) (runs: 258, μ: 75765, ~: 75891) DN420OnlyERC20Test:testTransferFrom() (gas: 85088) -DN420OnlyERC20Test:testTransferFrom(address,address,address,uint256,uint256) (runs: 258, μ: 105808, ~: 108058) +DN420OnlyERC20Test:testTransferFrom(address,address,address,uint256,uint256) (runs: 258, μ: 105791, ~: 108058) DN420OnlyERC20Test:testTransferFromInsufficientAllowanceReverts() (gas: 68658) -DN420OnlyERC20Test:testTransferFromInsufficientAllowanceReverts(address,uint256,uint256) (runs: 258, μ: 69198, ~: 69823) +DN420OnlyERC20Test:testTransferFromInsufficientAllowanceReverts(address,uint256,uint256) (runs: 258, μ: 69188, ~: 69763) DN420OnlyERC20Test:testTransferFromInsufficientBalanceReverts() (gas: 75577) -DN420OnlyERC20Test:testTransferFromInsufficientBalanceReverts(address,uint256,uint256) (runs: 258, μ: 76651, ~: 76698) +DN420OnlyERC20Test:testTransferFromInsufficientBalanceReverts(address,uint256,uint256) (runs: 258, μ: 76642, ~: 76698) DN420OnlyERC20Test:testTransferInsufficientBalanceReverts() (gas: 67140) -DN420OnlyERC20Test:testTransferInsufficientBalanceReverts(address,uint256,uint256) (runs: 258, μ: 68269, ~: 68227) -DN420OnlyERC20Test:test__codesize() (gas: 26873) -DN420Test:testERC1155Methods(uint256) (runs: 258, μ: 4344623, ~: 4588648) -DN420Test:testERC1155MethodsSelfTransfers(uint256) (runs: 258, μ: 2500189, ~: 2683341) -DN420Test:testFindOwnedIds() (gas: 2647894) -DN420Test:testMintNext() (gas: 2124439) -DN420Test:testMintToNonERC155RecipientReverts(uint256) (runs: 258, μ: 935274, ~: 800745) -DN420Test:testMintToRevertingERC155RecipientReverts(uint256) (runs: 258, μ: 1421495, ~: 981677) -DN420Test:testMintToZeroReverts(uint256) (runs: 258, μ: 746923, ~: 664301) -DN420Test:testMixed(uint256) (runs: 258, μ: 10345519, ~: 6390023) -DN420Test:testSafeBatchTransferFromToERC1155Recipient(uint256) (runs: 258, μ: 2203130, ~: 2180647) -DN420Test:testSafeTransferFromToERC1155Recipient(uint256) (runs: 258, μ: 1770268, ~: 1474343) -DN420Test:testTransferFromToERC1155Recipient(uint256) (runs: 258, μ: 2879243, ~: 2866733) -DN420Test:testTransferMixedReverts(uint256) (runs: 258, μ: 3666699, ~: 2722022) -DN420Test:test__codesize() (gas: 66283) +DN420OnlyERC20Test:testTransferInsufficientBalanceReverts(address,uint256,uint256) (runs: 258, μ: 68178, ~: 68227) +DN420OnlyERC20Test:test__codesize() (gas: 26870) +DN420Test:testERC1155Methods(uint256) (runs: 258, μ: 4329154, ~: 4577238) +DN420Test:testERC1155MethodsSelfTransfers(uint256) (runs: 258, μ: 2462392, ~: 2068392) +DN420Test:testFindOwnedIds() (gas: 2646614) +DN420Test:testMintNext() (gas: 2123677) +DN420Test:testMintToNonERC155RecipientReverts(uint256) (runs: 258, μ: 904192, ~: 796788) +DN420Test:testMintToRevertingERC155RecipientReverts(uint256) (runs: 258, μ: 1514526, ~: 981456) +DN420Test:testMintToZeroReverts(uint256) (runs: 258, μ: 739930, ~: 664014) +DN420Test:testMixed(uint256) (runs: 258, μ: 10214241, ~: 6379210) +DN420Test:testSafeBatchTransferFromToERC1155Recipient(uint256) (runs: 258, μ: 2221345, ~: 2179908) +DN420Test:testSafeTransferFromToERC1155Recipient(uint256) (runs: 258, μ: 1720072, ~: 1473626) +DN420Test:testTransferFromToERC1155Recipient(uint256) (runs: 258, μ: 2879746, ~: 2866163) +DN420Test:testTransferMixedReverts(uint256) (runs: 258, μ: 4148311, ~: 3905610) +DN420Test:test__codesize() (gas: 67049) MappingsTest:testAddressPairMapSetAndGet(address[2],address[2],uint256,uint256) (runs: 258, μ: 45763, ~: 47075) MappingsTest:testBitmapSetAndGet(uint256) (runs: 258, μ: 453208, ~: 413769) MappingsTest:testBitmapSetAndGet(uint256,uint256,bool,bool) (runs: 258, μ: 25592, ~: 26270) diff --git a/src/DN420.sol b/src/DN420.sol index 742f69c..c473ae8 100644 --- a/src/DN420.sol +++ b/src/DN420.sol @@ -761,19 +761,17 @@ abstract contract DN420 { if (!isApprovedForAll(from, by)) revert NotOwnerNorApproved(); } - { - Bitmap storage fromOwned = $.owned[from]; - if (!_owns(fromOwned, id)) revert TransferFromIncorrectOwner(); - _set(fromOwned, id, false); - _set($.owned[to], id, true); - } + Bitmap storage fromOwned = $.owned[from]; + if (!_owns(fromOwned, id)) revert TransferFromIncorrectOwner(); + _set(fromOwned, id, false); + _set($.owned[to], id, true); uint256 unit = _unit(); AddressData storage fromAddressData = $.addressData[from]; AddressData storage toAddressData = $.addressData[to]; /// @solidity memory-safe-assembly assembly { - let diff := or(shl(160, unit), shl(128, 1)) + let diff := shl(128, or(shl(32, unit), 1)) sstore(fromAddressData.slot, sub(sload(fromAddressData.slot), diff)) let toPacked := sload(toAddressData.slot) let toCheckpoint := and(0xffffffff, shr(96, toPacked)) @@ -826,35 +824,33 @@ abstract contract DN420 { } uint256 amount; + uint256 upTo; + AddressData storage fromAddressData = $.addressData[from]; + AddressData storage toAddressData = $.addressData[to]; unchecked { - uint256 upTo; - AddressData storage fromAddressData = $.addressData[from]; - AddressData storage toAddressData = $.addressData[to]; - { - uint256 n = ids.length; - amount = n * _unit(); - Bitmap storage fromOwned = $.owned[from]; - Bitmap storage toOwned = $.owned[to]; - while (n != 0) { - uint256 id = _get(ids, --n); - if (!_owns(fromOwned, id)) revert TransferFromIncorrectOwner(); - _set(fromOwned, id, false); - _set(toOwned, id, true); - _afterNFTTransfer(from, to, id); - upTo = _max(upTo, id); - } - } - /// @solidity memory-safe-assembly - assembly { - let diff := or(shl(160, amount), shl(128, mload(ids))) - sstore(fromAddressData.slot, sub(sload(fromAddressData.slot), diff)) - let toPacked := sload(toAddressData.slot) - let toCheckpoint := and(0xffffffff, shr(96, toPacked)) - // forgefmt: disable-next-item - sstore(toAddressData.slot, add(diff, - xor(toPacked, shl(96, mul(gt(upTo, toCheckpoint), xor(upTo, toCheckpoint)))))) + uint256 n = ids.length; + amount = n * _unit(); + Bitmap storage fromOwned = $.owned[from]; + Bitmap storage toOwned = $.owned[to]; + while (n != 0) { + uint256 id = _get(ids, --n); + if (!_owns(fromOwned, id)) revert TransferFromIncorrectOwner(); + _set(fromOwned, id, false); + _set(toOwned, id, true); + _afterNFTTransfer(from, to, id); + upTo = _max(upTo, id); } } + /// @solidity memory-safe-assembly + assembly { + let diff := shl(128, or(shl(32, amount), mload(ids))) + sstore(fromAddressData.slot, sub(sload(fromAddressData.slot), diff)) + let toPacked := sload(toAddressData.slot) + let toCheckpoint := and(0xffffffff, shr(96, toPacked)) + // forgefmt: disable-next-item + sstore(toAddressData.slot, add(diff, + xor(toPacked, shl(96, mul(gt(upTo, toCheckpoint), xor(upTo, toCheckpoint)))))) + } _batchTransferEmit(from, to, ids); /// @solidity memory-safe-assembly assembly { diff --git a/test/DN420.t.sol b/test/DN420.t.sol index 78390e4..8c885ed 100644 --- a/test/DN420.t.sol +++ b/test/DN420.t.sol @@ -219,9 +219,14 @@ contract DN420Test is SoladyTest { } struct _TestTemps { + address from; + uint88 aliceAux; + uint88 fromAux; uint256[] aliceIds; uint256[] bobIds; uint256[] ids; + uint256[] oriIds; + uint256 balanceBefore; uint256 id; address[] owners; uint256[] expectedBalances; @@ -899,21 +904,19 @@ contract DN420Test is SoladyTest { function testTransferMixedReverts(uint256) public { dn.initializeDN420(0, address(this)); - address from = _randomNonZeroAddress(); - uint256 amount = _bound(_random(), 1, 10) * _WAD; - dn.mint(from, amount, ""); - - uint88 fromAux = uint88(_random()); - dn.setAux(from, fromAux); - _testTransferMixedReverts(from); - _testTransferMixedReverts2(from); - _testTransferMixedReverts3(from); - assertEq(dn.getAux(from), fromAux); + _TestTemps memory t; + t.from = _randomNonZeroAddress(); + dn.mint(t.from, _bound(_random(), 1, 10) * _WAD, ""); + + dn.setAux(t.from, t.fromAux = uint88(_random())); + _testTransferMixedReverts(t); + _testTransferMixedReverts2(t); + _testTransferMixedReverts3(t); + assertEq(dn.getAux(t.from), t.fromAux); } - function _testTransferMixedReverts(address from) internal { - uint256 amount = dn.balanceOf(from); - + function _testTransferMixedReverts(_TestTemps memory t) internal { + uint256 amount = dn.balanceOf(t.from); if (_random() % 4 == 0) { vm.expectRevert(DN420.TransferToZeroAddress.selector); dn.mint(address(0), _random(), ""); @@ -921,90 +924,90 @@ contract DN420Test is SoladyTest { dn.mintNext(address(0), _random(), ""); vm.expectRevert(DN420.TransferToZeroAddress.selector); dn.transfer(address(0), amount); - vm.prank(from); + vm.prank(t.from); dn.approve(address(this), type(uint256).max); vm.expectRevert(DN420.TransferToZeroAddress.selector); - dn.transferFrom(from, address(0), amount); + dn.transferFrom(t.from, address(0), amount); address to = _randomNonZeroAddress(); - dn.transferFrom(from, to, amount); + dn.transferFrom(t.from, to, amount); vm.prank(to); - dn.transfer(from, amount); + dn.transfer(t.from, amount); } if (_random() % 4 == 0) { address by; - if (_random() % 8 > 0) by = _random() % 2 == 0 ? from : _randomNonZeroAddress(); + if (_random() % 8 > 0) by = _random() % 2 == 0 ? t.from : _randomNonZeroAddress(); address to = _randomNonZeroAddress(); - if (by == address(0) || by == from) { - _safeTransferFromNFT(by, from, to, 1); + if (by == address(0) || by == t.from) { + _safeTransferFromNFT(by, t.from, to, 1); } else if (_random() % 2 == 0) { - vm.prank(from); + vm.prank(t.from); dn.setApprovalForAll(by, true); - _safeTransferFromNFT(by, from, to, 1); - vm.prank(from); + _safeTransferFromNFT(by, t.from, to, 1); + vm.prank(t.from); dn.setApprovalForAll(by, false); } else { vm.expectRevert(DN420.NotOwnerNorApproved.selector); - _safeTransferFromNFT(by, from, to, 1); + _safeTransferFromNFT(by, t.from, to, 1); } if (dn.owns(to, 1)) { vm.prank(to); - _safeTransferFromNFT(to, to, from, 1); + _safeTransferFromNFT(to, to, t.from, 1); } } } - function _testTransferMixedReverts2(address from) internal { + function _testTransferMixedReverts2(_TestTemps memory t) internal { if (_random() % 4 == 0) { uint256 id = _bound(_random(), 0, 256); - while (dn.owns(from, id)) id = _bound(_random(), 0, 256); + while (dn.owns(t.from, id)) id = _bound(_random(), 0, 256); vm.expectRevert(DN420.TransferFromIncorrectOwner.selector); - _safeTransferFromNFT(from, from, _randomNonZeroAddress(), id); + _safeTransferFromNFT(t.from, t.from, _randomNonZeroAddress(), id); } while (_random() % 4 == 0) { address to = _randomNonZeroAddress(); - while (from == to) to = _randomNonZeroAddress(); - uint256[] memory ids = dn.findOwnedIds(from); - if (ids.length < 2) break; - ids[0] = ids[_bound(_random(), 1, ids.length - 1)]; + while (t.from == to) to = _randomNonZeroAddress(); + t.ids = dn.findOwnedIds(t.from); + if (t.ids.length < 2) break; + t.ids[0] = t.ids[_bound(_random(), 1, t.ids.length - 1)]; vm.expectRevert(DN420.TransferFromIncorrectOwner.selector); - dn.safeBatchTransferFromNFTs(from, from, to, ids); + dn.safeBatchTransferFromNFTs(t.from, t.from, to, t.ids); } } - function _testTransferMixedReverts3(address from) internal { + function _testTransferMixedReverts3(_TestTemps memory t) internal { if (_random() % 2 == 0) { - address to = _random() % 2 == 0 ? from : _randomNonZeroAddress(); - uint256[] memory ids = dn.findOwnedIds(from); - ids[0] = _bound(_random(), 0, 256); - while (dn.owns(from, ids[0])) ids[0] = _bound(_random(), 0, 256); + address to = _random() % 2 == 0 ? t.from : _randomNonZeroAddress(); + t.ids = dn.findOwnedIds(t.from); + t.ids[0] = _bound(_random(), 0, 256); + while (dn.owns(t.from, t.ids[0])) t.ids[0] = _bound(_random(), 0, 256); vm.expectRevert(DN420.TransferFromIncorrectOwner.selector); - dn.safeBatchTransferFromNFTs(from, from, to, ids); - dn.safeBatchTransferFromNFTs(from, from, to, dn.findOwnedIds(from)); - dn.safeBatchTransferFromNFTs(to, to, from, dn.findOwnedIds(to)); + dn.safeBatchTransferFromNFTs(t.from, t.from, to, t.ids); + dn.safeBatchTransferFromNFTs(t.from, t.from, to, dn.findOwnedIds(t.from)); + dn.safeBatchTransferFromNFTs(to, to, t.from, dn.findOwnedIds(to)); if (_random() % 2 == 0) { - ids = dn.findOwnedIds(from); - LibSort.sort(ids); - uint256 oriLength = ids.length; - ids = LibSort.union(ids, _randomSampleWithoutReplacements(ids)); - if (ids.length > oriLength) { - if (from != to) { + t.ids = dn.findOwnedIds(t.from); + LibSort.sort(t.ids); + t.oriIds = t.ids; + t.ids = LibSort.union(t.ids, _randomSampleWithoutReplacements(t.ids)); + if (t.ids.length > t.oriIds.length) { + if (t.from != to) { vm.expectRevert(DN420.TransferFromIncorrectOwner.selector); - dn.safeBatchTransferFromNFTs(from, from, to, ids); + dn.safeBatchTransferFromNFTs(t.from, t.from, to, t.ids); } else if (_random() % 2 == 0) { - uint256 balanceBefore = dn.balanceOf(from); - dn.safeBatchTransferFromNFTs(from, from, to, ids); - assertEq(dn.balanceOf(from), balanceBefore); + t.balanceBefore = dn.balanceOf(t.from); + dn.safeBatchTransferFromNFTs(t.from, t.from, to, t.ids); + assertEq(dn.balanceOf(t.from), t.balanceBefore); } else { - uint256[] memory idsBefore = dn.findOwnedIds(from); - dn.safeBatchTransferFromNFTs(from, from, to, ids); - assertEq(dn.findOwnedIds(from), idsBefore); + t.oriIds = dn.findOwnedIds(t.from); + dn.safeBatchTransferFromNFTs(t.from, t.from, to, t.ids); + assertEq(dn.findOwnedIds(t.from), t.oriIds); } } else { - dn.safeBatchTransferFromNFTs(from, from, to, ids); - dn.safeBatchTransferFromNFTs(to, to, from, ids); + dn.safeBatchTransferFromNFTs(t.from, t.from, to, t.ids); + dn.safeBatchTransferFromNFTs(to, to, t.from, t.ids); } } }