Skip to content

Commit

Permalink
✨ Add _mintAndSetExtraDataUnchecked in ERC721 (#800)
Browse files Browse the repository at this point in the history
Co-authored-by: Vectorized <[email protected]>
  • Loading branch information
atarpara and Vectorized committed Jan 22, 2024
1 parent 8326baa commit 57fef53
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 75 deletions.
136 changes: 69 additions & 67 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -227,25 +227,25 @@ 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, μ: 354926, ~: 344555)
ERC4337Test:testDelegateExecuteRevertsIfOwnerSlotValueChanged() (gas: 319282)
ERC4337Test:testDepositFunctions() (gas: 502955)
ERC4337Test:testDirectStorage() (gas: 70413)
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, μ: 509100, ~: 368285)
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: 1371823)
ERC4337Test:testOwnerRecovery() (gas: 486105)
ERC4337Test:testValidateUserOp() (gas: 491555)
ERC4337Test:test__codesize() (gas: 54502)
ERC4337Test:test__codesize() (gas: 54804)
ERC4626Test:testDepositWithNoApprovalReverts() (gas: 16371)
ERC4626Test:testDepositWithNotEnoughApprovalReverts() (gas: 89884)
ERC4626Test:testDifferentialFullMulDiv(uint256,uint256,uint256) (runs: 256, μ: 3325, ~: 3185)
Expand All @@ -265,22 +265,22 @@ ERC4626Test:testWithdrawWithNoUnderlyingAmountReverts() (gas: 13102)
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:testCdFallback() (gas: 894585)
ERC6551Test:testDeployERC6551(uint256) (runs: 256, μ: 170952, ~: 168747)
ERC6551Test:testDeployERC6551Proxy() (gas: 80751)
ERC6551Test:testExecute() (gas: 507855)
ERC6551Test:testExecuteBatch() (gas: 816977)
ERC6551Test:testExecuteBatch(uint256) (runs: 256, μ: 605059, ~: 483186)
ERC6551Test:testInitializeERC6551ProxyImplementation() (gas: 189801)
ERC6551Test:testIsValidSignature() (gas: 187612)
ERC6551Test:testOnERC1155BatchReceived() (gas: 1526542)
ERC6551Test:testOnERC1155Received() (gas: 1523898)
ERC6551Test:testOnERC721Received() (gas: 1447973)
ERC6551Test:testOnERC721ReceivedCycles() (gas: 1711044)
ERC6551Test:testOnERC721ReceivedCyclesWithDifferentChainIds(uint256) (runs: 256, μ: 449351, ~: 454543)
ERC6551Test:testSupportsInterface() (gas: 169387)
ERC6551Test:testUpgrade() (gas: 1154845)
ERC6551Test:test__codesize() (gas: 47762)
ERC6551Test:testExecute() (gas: 507949)
ERC6551Test:testExecuteBatch() (gas: 817049)
ERC6551Test:testExecuteBatch(uint256) (runs: 256, μ: 630096, ~: 483223)
ERC6551Test:testInitializeERC6551ProxyImplementation() (gas: 189807)
ERC6551Test:testIsValidSignature() (gas: 187662)
ERC6551Test:testOnERC1155BatchReceived() (gas: 1526548)
ERC6551Test:testOnERC1155Received() (gas: 1523904)
ERC6551Test:testOnERC721Received() (gas: 1508551)
ERC6551Test:testOnERC721ReceivedCycles() (gas: 1714607)
ERC6551Test:testOnERC721ReceivedCyclesWithDifferentChainIds(uint256) (runs: 256, μ: 448530, ~: 454141)
ERC6551Test:testSupportsInterface() (gas: 169393)
ERC6551Test:testUpgrade() (gas: 1154917)
ERC6551Test:test__codesize() (gas: 48064)
ERC6909Test:testApprove() (gas: 36771)
ERC6909Test:testApprove(address,uint256,uint256) (runs: 256, μ: 36480, ~: 37413)
ERC6909Test:testBurn() (gas: 40676)
Expand Down Expand Up @@ -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)
ERC721HooksTest:testERC721Hooks() (gas: 2944418)
ERC721HooksTest:test__codesize() (gas: 10028)
ERC721Test:testApprove(uint256) (runs: 256, μ: 108111, ~: 108127)
ERC721Test:testApproveAll(uint256) (runs: 256, μ: 48141, ~: 40334)
ERC721Test:testApproveBurn(uint256) (runs: 256, μ: 86790, ~: 86847)
ERC721Test:testApproveNonExistentReverts(uint256,address) (runs: 256, μ: 33641, ~: 33571)
ERC721Test:testApproveUnauthorizedReverts(uint256) (runs: 256, μ: 83247, ~: 82344)
ERC721Test:testAuthorizedEquivalence(address,bool,bool) (runs: 256, μ: 748, ~: 743)
ERC721Test:testAux(uint256) (runs: 256, μ: 191946, ~: 193057)
ERC721Test:testBurn(uint256) (runs: 256, μ: 83028, ~: 94016)
ERC721Test:testBurnNonExistentReverts(uint256) (runs: 256, μ: 10761, ~: 10761)
ERC721Test:testCannotExceedMaxBalance() (gas: 149876)
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:testSafeMintToNonERC721RecipientReverts(uint256) (runs: 256, μ: 100470, ~: 100470)
ERC721Test:testSafeMintToNonERC721RecipientWithDataReverts(uint256,bytes) (runs: 256, μ: 101718, ~: 101665)
ERC721Test:testSafeMintToRevertingERC721RecipientReverts(uint256) (runs: 256, μ: 203127, ~: 203127)
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: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:testAux(uint256) (runs: 256, μ: 191837, ~: 193040)
ERC721Test:testBurn(uint256) (runs: 256, μ: 83028, ~: 94027)
ERC721Test:testBurnNonExistentReverts(uint256) (runs: 256, μ: 10783, ~: 10783)
ERC721Test:testCannotExceedMaxBalance() (gas: 169072)
ERC721Test:testDoubleBurnReverts(uint256) (runs: 256, μ: 63578, ~: 63525)
ERC721Test:testDoubleMintReverts(uint256) (runs: 256, μ: 79139, ~: 79170)
ERC721Test:testEverything(uint256) (runs: 256, μ: 312948, ~: 302540)
ERC721Test:testExtraData(uint256) (runs: 256, μ: 99094, ~: 99171)
ERC721Test:testExtraData2(uint256,uint256) (runs: 256, μ: 54285, ~: 53934)
ERC721Test:testIsApprovedOrOwner(uint256) (runs: 256, μ: 135164, ~: 135195)
ERC721Test:testMint(uint256) (runs: 256, μ: 82814, ~: 82740)
ERC721Test:testMintAndSetExtraDataUnchecked(uint256) (runs: 256, μ: 84349, ~: 84378)
ERC721Test:testMintAndSetExtraDataUncheckedWithOverwrite(uint256,uint96) (runs: 256, μ: 83606, ~: 83515)
ERC721Test:testMintToZeroReverts(uint256) (runs: 256, μ: 39209, ~: 39209)
ERC721Test:testOwnerOfNonExistent(uint256) (runs: 256, μ: 33392, ~: 33338)
ERC721Test:testSafeMintToEOA(uint256) (runs: 256, μ: 83347, ~: 83395)
ERC721Test:testSafeMintToERC721Recipient(uint256) (runs: 256, μ: 409311, ~: 410424)
ERC721Test:testSafeMintToERC721RecipientWithData(uint256,bytes) (runs: 256, μ: 470744, ~: 460012)
ERC721Test:testSafeMintToERC721RecipientWithWrongReturnData(uint256) (runs: 256, μ: 169926, ~: 169926)
ERC721Test:testSafeMintToERC721RecipientWithWrongReturnDataWithData(uint256,bytes) (runs: 256, μ: 171141, ~: 171088)
ERC721Test:testSafeMintToNonERC721RecipientReverts(uint256) (runs: 256, μ: 100388, ~: 100388)
ERC721Test:testSafeMintToNonERC721RecipientWithDataReverts(uint256,bytes) (runs: 256, μ: 101635, ~: 101582)
ERC721Test:testSafeMintToRevertingERC721RecipientReverts(uint256) (runs: 256, μ: 203045, ~: 203045)
ERC721Test:testSafeMintToRevertingERC721RecipientWithDataReverts(uint256,bytes) (runs: 256, μ: 204281, ~: 204228)
ERC721Test:testSafeTransferFromToEOA(uint256) (runs: 256, μ: 121882, ~: 122007)
ERC721Test:testSafeTransferFromToERC721Recipient(uint256) (runs: 256, μ: 470906, ~: 472038)
ERC721Test:testSafeTransferFromToERC721RecipientWithData(uint256,bytes) (runs: 256, μ: 532326, ~: 521624)
ERC721Test:testSafeTransferFromToERC721RecipientWithWrongReturnDataReverts(uint256) (runs: 256, μ: 200882, ~: 200936)
ERC721Test:testSafeTransferFromToERC721RecipientWithWrongReturnDataWithDataReverts(uint256,bytes) (runs: 256, μ: 202118, ~: 202133)
ERC721Test:testSafeTransferFromToNonERC721RecipientReverts(uint256) (runs: 256, μ: 131284, ~: 131226)
ERC721Test:testSafeTransferFromToNonERC721RecipientWithDataReverts(uint256,bytes) (runs: 256, μ: 132590, ~: 132603)
ERC721Test:testSafeTransferFromToRevertingERC721RecipientReverts(uint256) (runs: 256, μ: 233957, ~: 234005)
ERC721Test:testSafeTransferFromToRevertingERC721RecipientWithDataReverts(uint256,bytes) (runs: 256, μ: 235263, ~: 235274)
ERC721Test:testSafetyOfCustomStorage(uint256,uint256) (runs: 256, μ: 1063, ~: 713)
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() (gas: 85772)
ERC721Test:testTransferFrom(uint256) (runs: 256, μ: 113857, ~: 112464)
ERC721Test:testTransferFromApproveAll(uint256) (runs: 256, μ: 119317, ~: 119293)
ERC721Test:testTransferFromNotExistentReverts(address,address,uint256) (runs: 256, μ: 34043, ~: 34014)
ERC721Test:testTransferFromNotOwner(uint256) (runs: 256, μ: 84742, ~: 84719)
ERC721Test:testTransferFromSelf(uint256) (runs: 256, μ: 92723, ~: 92713)
ERC721Test:testTransferFromToZeroReverts(uint256) (runs: 256, μ: 79072, ~: 79051)
ERC721Test:testTransferFromWrongFromReverts(address,uint256) (runs: 256, μ: 80432, ~: 80418)
ERC721Test:test__codesize() (gas: 43715)
FixedPointMathLibTest:testAbs() (gas: 578)
FixedPointMathLibTest:testAbs(int256) (runs: 256, μ: 516, ~: 485)
FixedPointMathLibTest:testAbsEdgeCases() (gas: 410)
Expand Down Expand Up @@ -916,7 +918,7 @@ OwnableTest:test__codesize() (gas: 12253)
ReceiverTest:testETHReceived() (gas: 9621)
ReceiverTest:testOnERC1155BatchReceived() (gas: 48975)
ReceiverTest:testOnERC1155Received() (gas: 46717)
ReceiverTest:testOnERC721Received() (gas: 64108)
ReceiverTest:testOnERC721Received() (gas: 64136)
ReceiverTest:test__codesize() (gas: 3310)
RedBlackTreeLibTest:testRedBlackTreeBenchUint160() (gas: 3433600)
RedBlackTreeLibTest:testRedBlackTreeBenchUint256() (gas: 5847065)
Expand Down
51 changes: 43 additions & 8 deletions src/tokens/ERC721.sol
Original file line number Diff line number Diff line change
Expand Up @@ -456,11 +456,6 @@ abstract contract ERC721 {
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)
Expand All @@ -478,9 +473,49 @@ abstract contract ERC721 {
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)
// Revert if `to` is the zero address, or if the account balance overflows.
if iszero(mul(to, and(balanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
mstore(0x00, 0xea553b3401336cea)
revert(xor(0x1c, shl(2, iszero(to))), 0x04)
}
sstore(balanceSlot, balanceSlotPacked)
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, 0, to, id)
}
_afterTokenTransfer(address(0), to, id);
}

/// @dev Mints token `id` to `to` and update the `extraData`,
/// but without checking if the token `id` already exists,
/// as `id` will usually be from an auto-incrementing counter.
///
/// Requirements:
///
/// - `to` cannot be the zero address.
///
/// Emits a {Transfer} event.
function _mintAndSetExtraDataUnchecked(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))
// Update with the owner and extra data.
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
sstore(add(id, add(id, keccak256(0x00, 0x20))), 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)
// Revert if `to` is the zero address, or if the account balance overflows.
if iszero(mul(to, and(balanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
mstore(0x00, 0xea553b3401336cea)
revert(xor(0x1c, shl(2, iszero(to))), 0x04)
}
sstore(balanceSlot, balanceSlotPacked)
}
Expand Down
29 changes: 29 additions & 0 deletions test/ERC721.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,9 @@ contract ERC721Test is SoladyTest {
vm.expectRevert(ERC721.AccountBalanceOverflow.selector);
token.mint(owner0, 1);

vm.expectRevert(ERC721.AccountBalanceOverflow.selector);
token.mintWithExtraDataUnchecked(owner0, 1, _extraData(1));

token.uncheckedBurn(0);
assertEq(token.balanceOf(owner0), 0xfffffffe);

Expand All @@ -335,6 +338,29 @@ contract ERC721Test is SoladyTest {
assertEq(_ownerOf(id), owner);
}

function testMintAndSetExtraDataUnchecked(uint256 id) public {
address owner = _randomNonZeroAddress();

_expectMintEvent(owner, id);
token.mintWithExtraDataUnchecked(owner, id, _extraData(id));

assertEq(token.balanceOf(owner), 1);
assertEq(_ownerOf(id), owner);
assertEq(token.getExtraData(id), _extraData(id));
}

function testMintAndSetExtraDataUncheckedWithOverwrite(uint256 id, uint96 random) public {
address owner = _randomNonZeroAddress();

token.setExtraData(id, random);
assertEq(token.getExtraData(id), random);

_expectMintEvent(owner, id);
token.mintWithExtraDataUnchecked(owner, id, _extraData(id));

assertEq(token.getExtraData(id), _extraData(id));
}

function testBurn(uint256 id) public {
address owner = _randomNonZeroAddress();

Expand Down Expand Up @@ -810,6 +836,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.mintWithExtraDataUnchecked(address(0), id, _extraData(id));
}

function testDoubleMintReverts(uint256 id) public {
Expand Down
11 changes: 11 additions & 0 deletions test/utils/mocks/MockERC721.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ contract MockERC721 is ERC721 {
_mint(_brutalized(to), id);
}

function mintWithExtraDataUnchecked(address to, uint256 id, uint96 value) public virtual {
_mintAndSetExtraDataUnchecked(_brutalized(to), id, _brutalized(value));
}

function burn(uint256 id) public virtual {
_burn(msg.sender, id);
}
Expand Down Expand Up @@ -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(96, gas()))
}
}
}

0 comments on commit 57fef53

Please sign in to comment.