@@ -3,7 +3,6 @@ pragma solidity 0.8.24;
33
44import {IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol " ;
55import {IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol " ;
6- import {IERC721Enumerable } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol " ;
76import {ContextUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol " ;
87import {IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol " ;
98import {ERC165Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol " ;
@@ -22,7 +21,7 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini
2221 * - No burn function (transfer to address(0) instead)
2322 * - Keeps only core transfer and ownership logic
2423 */
25- abstract contract ERC721EthscriptionsUpgradeable is Initializable , ContextUpgradeable , ERC165Upgradeable , IERC721 , IERC721Metadata , IERC721Enumerable , IERC721Errors {
24+ abstract contract ERC721EthscriptionsUpgradeable is Initializable , ContextUpgradeable , ERC165Upgradeable , IERC721 , IERC721Metadata , IERC721Errors {
2625 // Errors for enumerable functionality
2726 error ERC721OutOfBoundsIndex (address owner , uint256 index );
2827 error ERC721EnumerableForbiddenBatchMint ();
@@ -62,7 +61,7 @@ abstract contract ERC721EthscriptionsUpgradeable is Initializable, ContextUpgrad
6261 // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC721Enumerable")) - 1)) & ~bytes32(uint256(0xff))
6362 bytes32 private constant ERC721EnumerableStorageLocation = 0x645e039705490088daad89bae25049a34f4a9072d398537b1ab2425f24cbed00 ;
6463
65- function _getERC721EnumerableStorage () private pure returns (ERC721EnumerableStorage storage $) {
64+ function _getERC721EnumerableStorage () internal pure returns (ERC721EnumerableStorage storage $) {
6665 assembly {
6766 $.slot := ERC721EnumerableStorageLocation
6867 }
@@ -88,7 +87,6 @@ abstract contract ERC721EthscriptionsUpgradeable is Initializable, ContextUpgrad
8887 return
8988 interfaceId == type (IERC721 ).interfaceId ||
9089 interfaceId == type (IERC721Metadata ).interfaceId ||
91- interfaceId == type (IERC721Enumerable ).interfaceId ||
9290 super .supportsInterface (interfaceId);
9391 }
9492
@@ -132,13 +130,7 @@ abstract contract ERC721EthscriptionsUpgradeable is Initializable, ContextUpgrad
132130 */
133131 function tokenURI (uint256 tokenId ) public view virtual returns (string memory );
134132
135- /// @inheritdoc IERC721Enumerable
136- function totalSupply () public view virtual returns (uint256 ) {
137- ERC721EnumerableStorage storage $ = _getERC721EnumerableStorage ();
138- return $._allTokens.length ;
139- }
140-
141- /// @inheritdoc IERC721Enumerable
133+ /// @dev Return token owned by `owner` at `index`.
142134 function tokenOfOwnerByIndex (address owner , uint256 index ) public view virtual returns (uint256 ) {
143135 ERC721EnumerableStorage storage $ = _getERC721EnumerableStorage ();
144136 if (index >= balanceOf (owner)) {
@@ -147,15 +139,6 @@ abstract contract ERC721EthscriptionsUpgradeable is Initializable, ContextUpgrad
147139 return $._ownedTokens[owner][index];
148140 }
149141
150- /// @inheritdoc IERC721Enumerable
151- function tokenByIndex (uint256 index ) public view virtual returns (uint256 ) {
152- ERC721EnumerableStorage storage $ = _getERC721EnumerableStorage ();
153- if (index >= totalSupply ()) {
154- revert ERC721OutOfBoundsIndex (address (0 ), index);
155- }
156- return $._allTokens[index];
157- }
158-
159142 /**
160143 * @dev Approval functions removed - not needed for Ethscriptions.
161144 * These can be added back in child contracts if needed.
@@ -256,8 +239,8 @@ abstract contract ERC721EthscriptionsUpgradeable is Initializable, ContextUpgrad
256239 // This is a mint
257240 $._existsFlag[tokenId] = true ;
258241
259- // Add to all tokens enumeration
260- _addTokenToAllTokensEnumeration (tokenId);
242+ // Allow derived contracts to adjust enumeration state for newly minted token
243+ _afterTokenMint (tokenId);
261244
262245 // Add to owner enumeration (also increments balance)
263246 _addTokenToOwnerEnumeration (to, tokenId);
@@ -335,9 +318,10 @@ abstract contract ERC721EthscriptionsUpgradeable is Initializable, ContextUpgrad
335318 if (! exists && $._existsFlag[tokenId]) {
336319 address owner = $._owners[tokenId];
337320
321+ _beforeTokenRemoval (tokenId, owner);
322+
338323 // Remove from enumerations (balance is decremented inside _removeTokenFromOwnerEnumeration)
339324 _removeTokenFromOwnerEnumeration (owner, tokenId);
340- _removeTokenFromAllTokensEnumeration (tokenId);
341325
342326 // Clear owner storage for cleanliness
343327 delete $._owners[tokenId];
@@ -378,16 +362,6 @@ abstract contract ERC721EthscriptionsUpgradeable is Initializable, ContextUpgrad
378362 }
379363 }
380364
381- /**
382- * @dev Private function to add a token to this extension's token tracking data structures.
383- * @param tokenId uint256 ID of the token to be added to the tokens list
384- */
385- function _addTokenToAllTokensEnumeration (uint256 tokenId ) private {
386- ERC721EnumerableStorage storage $ = _getERC721EnumerableStorage ();
387- $._allTokensIndex[tokenId] = $._allTokens.length ;
388- $._allTokens.push (tokenId);
389- }
390-
391365 /**
392366 * @dev Private function to remove a token from this extension's ownership-tracking data structures.
393367 * Also decrements the owner's balance.
@@ -429,28 +403,12 @@ abstract contract ERC721EthscriptionsUpgradeable is Initializable, ContextUpgrad
429403 }
430404
431405 /**
432- * @dev Private function to remove a token from this extension's token tracking data structures.
433- * This has O(1) time complexity, but alters the order of the _allTokens array.
434- * @param tokenId uint256 ID of the token to be removed from the tokens list
406+ * @dev Hook for derived contracts to react to token minting.
435407 */
436- function _removeTokenFromAllTokensEnumeration (uint256 tokenId ) private {
437- ERC721EnumerableStorage storage $ = _getERC721EnumerableStorage ();
438- // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
439- // then delete the last slot (swap and pop).
440-
441- uint256 lastTokenIndex = $._allTokens.length - 1 ;
442- uint256 tokenIndex = $._allTokensIndex[tokenId];
443-
444- // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
445- // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
446- // an 'if' statement (like in _removeTokenFromOwnerEnumeration)
447- uint256 lastTokenId = $._allTokens[lastTokenIndex];
408+ function _afterTokenMint (uint256 ) internal virtual {}
448409
449- $._allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
450- $._allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
451-
452- // This also deletes the contents at the last position of the array
453- delete $._allTokensIndex[tokenId];
454- $._allTokens.pop ();
455- }
456- }
410+ /**
411+ * @dev Hook for derived contracts to react to token removal.
412+ */
413+ function _beforeTokenRemoval (uint256 , address ) internal virtual {}
414+ }
0 commit comments