diff --git a/.gas-snapshot b/.gas-snapshot index b2de333c04..8485e98a07 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -227,7 +227,7 @@ ERC4337FactoryTest:test__codesize() (gas: 13520) ERC4337Test:testCdFallback() (gas: 443989) ERC4337Test:testCdFallback2() (gas: 1140699) ERC4337Test:testDelegateExecute() (gas: 369570) -ERC4337Test:testDelegateExecute(uint256) (runs: 256, μ: 356646, ~: 344555) +ERC4337Test:testDelegateExecute(uint256) (runs: 256, μ: 352449, ~: 344555) ERC4337Test:testDelegateExecuteRevertsIfOwnerSlotValueChanged() (gas: 319282) ERC4337Test:testDepositFunctions() (gas: 502955) ERC4337Test:testDirectStorage() (gas: 70413) @@ -235,17 +235,17 @@ ERC4337Test:testDisableInitializerForImplementation() (gas: 1177324) ERC4337Test:testETHReceived() (gas: 16584) ERC4337Test:testExecute() (gas: 382786) ERC4337Test:testExecuteBatch() (gas: 692605) -ERC4337Test:testExecuteBatch(uint256) (runs: 256, μ: 510935, ~: 368132) +ERC4337Test:testExecuteBatch(uint256) (runs: 256, μ: 511473, ~: 368287) ERC4337Test:testInitializer() (gas: 285192) ERC4337Test:testIsValidSignature() (gas: 111663) ERC4337Test:testIsValidSignaturePersonalSign() (gas: 96270) ERC4337Test:testIsValidSignatureWrapped() (gas: 406706) ERC4337Test:testOnERC1155BatchReceived() (gas: 1393788) ERC4337Test:testOnERC1155Received() (gas: 1391111) -ERC4337Test:testOnERC721Received() (gas: 1311273) +ERC4337Test:testOnERC721Received() (gas: 1378408) ERC4337Test:testOwnerRecovery() (gas: 486105) ERC4337Test:testValidateUserOp() (gas: 491555) -ERC4337Test:test__codesize() (gas: 54502) +ERC4337Test:test__codesize() (gas: 54837) ERC4626Test:testDepositWithNoApprovalReverts() (gas: 16371) ERC4626Test:testDepositWithNotEnoughApprovalReverts() (gas: 89884) ERC4626Test:testDifferentialFullMulDiv(uint256,uint256,uint256) (runs: 256, μ: 3325, ~: 3185) @@ -266,21 +266,21 @@ ERC4626Test:testWithdrawWithNotEnoughUnderlyingAmountReverts() (gas: 144074) ERC4626Test:testWithdrawZero() (gas: 52807) ERC4626Test:test__codesize() (gas: 41067) ERC6551Test:testCdFallback() (gas: 894557) -ERC6551Test:testDeployERC6551(uint256) (runs: 256, μ: 171154, ~: 168739) +ERC6551Test:testDeployERC6551(uint256) (runs: 256, μ: 170994, ~: 168739) ERC6551Test:testDeployERC6551Proxy() (gas: 80751) ERC6551Test:testExecute() (gas: 507855) ERC6551Test:testExecuteBatch() (gas: 816977) -ERC6551Test:testExecuteBatch(uint256) (runs: 256, μ: 605059, ~: 483186) +ERC6551Test:testExecuteBatch(uint256) (runs: 256, μ: 589408, ~: 483186) ERC6551Test:testInitializeERC6551ProxyImplementation() (gas: 189801) ERC6551Test:testIsValidSignature() (gas: 187612) ERC6551Test:testOnERC1155BatchReceived() (gas: 1526542) ERC6551Test:testOnERC1155Received() (gas: 1523898) -ERC6551Test:testOnERC721Received() (gas: 1447973) +ERC6551Test:testOnERC721Received() (gas: 1515109) ERC6551Test:testOnERC721ReceivedCycles() (gas: 1711044) -ERC6551Test:testOnERC721ReceivedCyclesWithDifferentChainIds(uint256) (runs: 256, μ: 449351, ~: 454543) +ERC6551Test:testOnERC721ReceivedCyclesWithDifferentChainIds(uint256) (runs: 256, μ: 449262, ~: 455913) ERC6551Test:testSupportsInterface() (gas: 169387) ERC6551Test:testUpgrade() (gas: 1154845) -ERC6551Test:test__codesize() (gas: 47762) +ERC6551Test:test__codesize() (gas: 48097) ERC6909Test:testApprove() (gas: 36771) ERC6909Test:testApprove(address,uint256,uint256) (runs: 256, μ: 36480, ~: 37413) ERC6909Test:testBurn() (gas: 40676) @@ -317,55 +317,57 @@ ERC6909Test:testTransferInsufficientBalanceReverts(address,uint256,uint256,uint2 ERC6909Test:testTransferOverMaxUintReverts() (gas: 63438) ERC6909Test:testTransferOverMaxUintReverts(address,uint256,uint256,uint256) (runs: 256, μ: 63957, ~: 63967) ERC6909Test:test__codesize() (gas: 26802) -ERC721HooksTest:testERC721Hooks() (gas: 2877778) -ERC721HooksTest:test__codesize() (gas: 9707) -ERC721Test:testApprove(uint256) (runs: 256, μ: 108105, ~: 108158) -ERC721Test:testApproveAll(uint256) (runs: 256, μ: 47717, ~: 40312) -ERC721Test:testApproveBurn(uint256) (runs: 256, μ: 86748, ~: 86771) -ERC721Test:testApproveNonExistentReverts(uint256,address) (runs: 256, μ: 33665, ~: 33621) -ERC721Test:testApproveUnauthorizedReverts(uint256) (runs: 256, μ: 83247, ~: 82408) -ERC721Test:testAuthorizedEquivalence(address,bool,bool) (runs: 256, μ: 748, ~: 743) -ERC721Test:testAux(uint256) (runs: 256, μ: 191946, ~: 193057) -ERC721Test:testBurn(uint256) (runs: 256, μ: 83028, ~: 94016) +ERC721HooksTest:testERC721Hooks() (gas: 2948024) +ERC721HooksTest:test__codesize() (gas: 10061) +ERC721Test:testApprove(uint256) (runs: 256, μ: 108048, ~: 108093) +ERC721Test:testApproveAll(uint256) (runs: 256, μ: 47496, ~: 40243) +ERC721Test:testApproveBurn(uint256) (runs: 256, μ: 86755, ~: 86771) +ERC721Test:testApproveNonExistentReverts(uint256,address) (runs: 256, μ: 33661, ~: 33621) +ERC721Test:testApproveUnauthorizedReverts(uint256) (runs: 256, μ: 83350, ~: 82576) +ERC721Test:testAuthorizedEquivalence(address,bool,bool) (runs: 256, μ: 683, ~: 678) +ERC721Test:testAux(uint256) (runs: 256, μ: 192072, ~: 193158) +ERC721Test:testBurn(uint256) (runs: 256, μ: 82594, ~: 93970) ERC721Test:testBurnNonExistentReverts(uint256) (runs: 256, μ: 10761, ~: 10761) -ERC721Test:testCannotExceedMaxBalance() (gas: 149876) +ERC721Test:testCannotExceedMaxBalance() (gas: 149912) ERC721Test:testDoubleBurnReverts(uint256) (runs: 256, μ: 63486, ~: 63432) -ERC721Test:testDoubleMintReverts(uint256) (runs: 256, μ: 79139, ~: 79166) -ERC721Test:testEverything(uint256) (runs: 256, μ: 307291, ~: 302234) -ERC721Test:testExtraData(uint256) (runs: 256, μ: 99112, ~: 99158) -ERC721Test:testExtraData2(uint256,uint256) (runs: 256, μ: 54254, ~: 53903) -ERC721Test:testIsApprovedOrOwner(uint256) (runs: 256, μ: 135451, ~: 135446) -ERC721Test:testMint(uint256) (runs: 256, μ: 82887, ~: 82907) -ERC721Test:testMintToZeroReverts(uint256) (runs: 256, μ: 8686, ~: 8686) -ERC721Test:testOwnerOfNonExistent(uint256) (runs: 256, μ: 33384, ~: 33338) -ERC721Test:testSafeMintToEOA(uint256) (runs: 256, μ: 83482, ~: 83504) -ERC721Test:testSafeMintToERC721Recipient(uint256) (runs: 256, μ: 409451, ~: 410571) -ERC721Test:testSafeMintToERC721RecipientWithData(uint256,bytes) (runs: 256, μ: 470881, ~: 460078) -ERC721Test:testSafeMintToERC721RecipientWithWrongReturnData(uint256) (runs: 256, μ: 170008, ~: 170008) -ERC721Test:testSafeMintToERC721RecipientWithWrongReturnDataWithData(uint256,bytes) (runs: 256, μ: 171224, ~: 171171) +ERC721Test:testDoubleMintReverts(uint256) (runs: 256, μ: 81033, ~: 81065) +ERC721Test:testEverything(uint256) (runs: 256, μ: 310827, ~: 303159) +ERC721Test:testExtraData(uint256) (runs: 256, μ: 99225, ~: 99378) +ERC721Test:testExtraData2(uint256,uint256) (runs: 256, μ: 54384, ~: 54033) +ERC721Test:testIsApprovedOrOwner(uint256) (runs: 256, μ: 135467, ~: 135452) +ERC721Test:testMint(uint256) (runs: 256, μ: 82830, ~: 82851) +ERC721Test:testMintAndSetExtraData(uint256) (runs: 256, μ: 84492, ~: 84450) +ERC721Test:testMintAndSetExtraDataWithOverwrite(uint256,uint96) (runs: 256, μ: 83908, ~: 83818) +ERC721Test:testMintToZeroReverts(uint256) (runs: 256, μ: 10346, ~: 10346) +ERC721Test:testOwnerOfNonExistent(uint256) (runs: 256, μ: 33405, ~: 33360) +ERC721Test:testSafeMintToEOA(uint256) (runs: 256, μ: 83495, ~: 83531) +ERC721Test:testSafeMintToERC721Recipient(uint256) (runs: 256, μ: 409465, ~: 410593) +ERC721Test:testSafeMintToERC721RecipientWithData(uint256,bytes) (runs: 256, μ: 470885, ~: 460023) +ERC721Test:testSafeMintToERC721RecipientWithWrongReturnData(uint256) (runs: 256, μ: 170030, ~: 170030) +ERC721Test:testSafeMintToERC721RecipientWithWrongReturnDataWithData(uint256,bytes) (runs: 256, μ: 171246, ~: 171193) ERC721Test:testSafeMintToNonERC721RecipientReverts(uint256) (runs: 256, μ: 100470, ~: 100470) -ERC721Test:testSafeMintToNonERC721RecipientWithDataReverts(uint256,bytes) (runs: 256, μ: 101718, ~: 101665) -ERC721Test:testSafeMintToRevertingERC721RecipientReverts(uint256) (runs: 256, μ: 203127, ~: 203127) +ERC721Test:testSafeMintToNonERC721RecipientWithDataReverts(uint256,bytes) (runs: 256, μ: 101740, ~: 101687) +ERC721Test:testSafeMintToRevertingERC721RecipientReverts(uint256) (runs: 256, μ: 203149, ~: 203149) ERC721Test:testSafeMintToRevertingERC721RecipientWithDataReverts(uint256,bytes) (runs: 256, μ: 204364, ~: 204311) -ERC721Test:testSafeTransferFromToEOA(uint256) (runs: 256, μ: 121950, ~: 122066) -ERC721Test:testSafeTransferFromToERC721Recipient(uint256) (runs: 256, μ: 470952, ~: 472074) -ERC721Test:testSafeTransferFromToERC721RecipientWithData(uint256,bytes) (runs: 256, μ: 532330, ~: 521753) +ERC721Test:testSafeTransferFromToEOA(uint256) (runs: 256, μ: 121999, ~: 122108) +ERC721Test:testSafeTransferFromToERC721Recipient(uint256) (runs: 256, μ: 470972, ~: 472096) +ERC721Test:testSafeTransferFromToERC721RecipientWithData(uint256,bytes) (runs: 256, μ: 532406, ~: 521752) ERC721Test:testSafeTransferFromToERC721RecipientWithWrongReturnDataReverts(uint256) (runs: 256, μ: 200853, ~: 200900) -ERC721Test:testSafeTransferFromToERC721RecipientWithWrongReturnDataWithDataReverts(uint256,bytes) (runs: 256, μ: 202126, ~: 202120) -ERC721Test:testSafeTransferFromToNonERC721RecipientReverts(uint256) (runs: 256, μ: 131264, ~: 131198) -ERC721Test:testSafeTransferFromToNonERC721RecipientWithDataReverts(uint256,bytes) (runs: 256, μ: 132600, ~: 132599) -ERC721Test:testSafeTransferFromToRevertingERC721RecipientReverts(uint256) (runs: 256, μ: 233928, ~: 233985) -ERC721Test:testSafeTransferFromToRevertingERC721RecipientWithDataReverts(uint256,bytes) (runs: 256, μ: 235268, ~: 235260) -ERC721Test:testSafetyOfCustomStorage(uint256,uint256) (runs: 256, μ: 1063, ~: 713) +ERC721Test:testSafeTransferFromToERC721RecipientWithWrongReturnDataWithDataReverts(uint256,bytes) (runs: 256, μ: 202151, ~: 202142) +ERC721Test:testSafeTransferFromToNonERC721RecipientReverts(uint256) (runs: 256, μ: 131272, ~: 131220) +ERC721Test:testSafeTransferFromToNonERC721RecipientWithDataReverts(uint256,bytes) (runs: 256, μ: 132624, ~: 132621) +ERC721Test:testSafeTransferFromToRevertingERC721RecipientReverts(uint256) (runs: 256, μ: 233951, ~: 234003) +ERC721Test:testSafeTransferFromToRevertingERC721RecipientWithDataReverts(uint256,bytes) (runs: 256, μ: 235262, ~: 235260) +ERC721Test:testSafetyOfCustomStorage(uint256,uint256) (runs: 256, μ: 1085, ~: 735) ERC721Test:testTransferFrom() (gas: 85744) -ERC721Test:testTransferFrom(uint256) (runs: 256, μ: 113745, ~: 112511) -ERC721Test:testTransferFromApproveAll(uint256) (runs: 256, μ: 119344, ~: 119327) -ERC721Test:testTransferFromNotExistentReverts(address,address,uint256) (runs: 256, μ: 34017, ~: 33992) -ERC721Test:testTransferFromNotOwner(uint256) (runs: 256, μ: 84718, ~: 84691) -ERC721Test:testTransferFromSelf(uint256) (runs: 256, μ: 92791, ~: 92784) -ERC721Test:testTransferFromToZeroReverts(uint256) (runs: 256, μ: 79057, ~: 79023) -ERC721Test:testTransferFromWrongFromReverts(address,uint256) (runs: 256, μ: 80414, ~: 80414) -ERC721Test:test__codesize() (gas: 41923) +ERC721Test:testTransferFrom(uint256) (runs: 256, μ: 113989, ~: 112537) +ERC721Test:testTransferFromApproveAll(uint256) (runs: 256, μ: 119344, ~: 119308) +ERC721Test:testTransferFromNotExistentReverts(address,address,uint256) (runs: 256, μ: 34015, ~: 33992) +ERC721Test:testTransferFromNotOwner(uint256) (runs: 256, μ: 84740, ~: 84713) +ERC721Test:testTransferFromSelf(uint256) (runs: 256, μ: 92775, ~: 92764) +ERC721Test:testTransferFromToZeroReverts(uint256) (runs: 256, μ: 79052, ~: 79023) +ERC721Test:testTransferFromWrongFromReverts(address,uint256) (runs: 256, μ: 80417, ~: 80414) +ERC721Test:test__codesize() (gas: 43776) FixedPointMathLibTest:testAbs() (gas: 578) FixedPointMathLibTest:testAbs(int256) (runs: 256, μ: 516, ~: 485) FixedPointMathLibTest:testAbsEdgeCases() (gas: 410) diff --git a/src/tokens/ERC721.sol b/src/tokens/ERC721.sol index d21b424929..3832c608bd 100644 --- a/src/tokens/ERC721.sol +++ b/src/tokens/ERC721.sol @@ -490,6 +490,54 @@ abstract contract ERC721 { _afterTokenTransfer(address(0), to, id); } + /// @dev Mints token `id` to `to` and update the `extraData`. + /// + /// Requirements: + /// + /// - Token `id` must not exist. + /// - `to` cannot be the zero address. + /// + /// Emits a {Transfer} event. + function _mintAndSetExtraData(address to, uint256 id, uint96 value) internal virtual { + _beforeTokenTransfer(address(0), to, id); + /// @solidity memory-safe-assembly + assembly { + // Clear the upper 96 bits. + to := shr(96, shl(96, to)) + // Revert if `to` is the zero address. + if iszero(to) { + mstore(0x00, 0xea553b34) // `TransferToZeroAddress()`. + revert(0x1c, 0x04) + } + // Load the ownership data. + mstore(0x00, id) + mstore(0x1c, _ERC721_MASTER_SLOT_SEED) + let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20))) + let ownershipPacked := sload(ownershipSlot) + // Revert if the token already exists. + if shl(96, ownershipPacked) { + mstore(0x00, 0xc991cbb1) // `TokenAlreadyExists()`. + revert(0x1c, 0x04) + } + // Update with the owner and extra data. + sstore(ownershipSlot, or(shl(160, value), to)) + // Increment the balance of the owner. + { + mstore(0x00, to) + let balanceSlot := keccak256(0x0c, 0x1c) + let balanceSlotPacked := add(sload(balanceSlot), 1) + if iszero(and(balanceSlotPacked, _MAX_ACCOUNT_BALANCE)) { + mstore(0x00, 0x01336cea) // `AccountBalanceOverflow()`. + revert(0x1c, 0x04) + } + sstore(balanceSlot, balanceSlotPacked) + } + // Emit the {Transfer} event. + log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, 0, to, id) + } + _afterTokenTransfer(address(0), to, id); + } + /// @dev Equivalent to `_safeMint(to, id, "")`. function _safeMint(address to, uint256 id) internal virtual { _safeMint(to, id, ""); diff --git a/test/ERC721.t.sol b/test/ERC721.t.sol index 5591b2bd3c..053bf28a3c 100644 --- a/test/ERC721.t.sol +++ b/test/ERC721.t.sol @@ -335,6 +335,29 @@ contract ERC721Test is SoladyTest { assertEq(_ownerOf(id), owner); } + function testMintAndSetExtraData(uint256 id) public { + address owner = _randomNonZeroAddress(); + + _expectMintEvent(owner, id); + token.mintWithExtraData(owner, id, _extraData(id)); + + assertEq(token.balanceOf(owner), 1); + assertEq(_ownerOf(id), owner); + assertEq(token.getExtraData(id), _extraData(id)); + } + + function testMintAndSetExtraDataWithOverwrite(uint256 id, uint96 random) public { + address owner = _randomNonZeroAddress(); + + token.setExtraData(id, random); + assertEq(token.getExtraData(id), random); + + _expectMintEvent(owner, id); + token.mintWithExtraData(owner, id, _extraData(id)); + + assertEq(token.getExtraData(id), _extraData(id)); + } + function testBurn(uint256 id) public { address owner = _randomNonZeroAddress(); @@ -810,6 +833,9 @@ contract ERC721Test is SoladyTest { function testMintToZeroReverts(uint256 id) public { vm.expectRevert(ERC721.TransferToZeroAddress.selector); token.mint(address(0), id); + + vm.expectRevert(ERC721.TransferToZeroAddress.selector); + token.mintWithExtraData(address(0), id, _extraData(id)); } function testDoubleMintReverts(uint256 id) public { @@ -818,6 +844,9 @@ contract ERC721Test is SoladyTest { token.mint(to, id); vm.expectRevert(ERC721.TokenAlreadyExists.selector); token.mint(to, id); + + vm.expectRevert(ERC721.TokenAlreadyExists.selector); + token.mintWithExtraData(to, id, _extraData(id)); } function testBurnNonExistentReverts(uint256 id) public { diff --git a/test/utils/mocks/MockERC721.sol b/test/utils/mocks/MockERC721.sol index 81e8df1023..411730ad59 100644 --- a/test/utils/mocks/MockERC721.sol +++ b/test/utils/mocks/MockERC721.sol @@ -28,6 +28,10 @@ contract MockERC721 is ERC721 { _mint(_brutalized(to), id); } + function mintWithExtraData(address to, uint256 id, uint96 value) public virtual { + _mintAndSetExtraData(_brutalized(to), id, _brutalized(value)); + } + function burn(uint256 id) public virtual { _burn(msg.sender, id); } @@ -138,4 +142,11 @@ contract MockERC721 is ERC721 { result := or(a, shl(160, gas())) } } + + function _brutalized(uint96 value) internal view returns (uint96 result) { + /// @solidity memory-safe-assembly + assembly { + result := or(value, shl(160, gas())) + } + } }