1+ // SPDX-License-Identifier: MIT
2+ pragma solidity >= 0.8.30 ;
3+
4+
5+ /// @notice Interface for contracts that want to support safeTransfers.
6+ interface IERC721Receiver {
7+ function onERC721Received (address _operator , address _from , uint256 _tokenId , bytes calldata _data ) external returns (bytes4 );
8+ }
9+
10+
11+ /// @title ERC-721 token (zero-dependency implementation)
12+ /// @notice A complete, dependency-free ERC-721 implementation using the project's storage pattern.
13+ /// @dev This contract provides metadata, ownership, approvals, safe transfers (with local IERC721Receiver check),
14+ /// minting, burning, and helpers. It intentionally avoids external imports.
15+ contract ERC721EnumerableFacet {
16+
17+ // ERC-6093: Custom errors for ERC-721
18+ error ERC721InvalidOwner (address _owner );
19+ error ERC721NonexistentToken (uint256 _tokenId );
20+ error ERC721IncorrectOwner (address _sender , uint256 _tokenId , address _owner );
21+ error ERC721InvalidSender (address _sender );
22+ error ERC721InvalidReceiver (address _receiver );
23+ error ERC721InsufficientApproval (address _operator , uint256 _tokenId );
24+ error ERC721InvalidApprover (address _approver );
25+ error ERC721InvalidOperator (address _operator );
26+ error ERC721OutOfBoundsIndex (address _owner , uint256 _index );
27+
28+ event Transfer (address indexed _from , address indexed _to , uint256 indexed _tokenId );
29+ event Approval (address indexed _owner , address indexed _approved , uint256 indexed _tokenId );
30+ event ApprovalForAll (address indexed _owner , address indexed _operator , bool _approved );
31+
32+ // Struct storage position defined by keccak256 hash
33+ // of diamond storage identifier
34+ bytes32 constant STORAGE_POSITION = keccak256 ("compose.erc721.enumerable " );
35+
36+ // Storage defined using the ERC-8042 standard
37+ // @custom:storage-location erc8042:compose.erc721.enumerable
38+ struct ERC721EnumerableStorage {
39+ string name;
40+ string symbol;
41+ mapping (uint256 tokenId = > string tokenURI ) tokenURIOf;
42+
43+ mapping (uint256 tokenId = > address owner ) ownerOf;
44+ mapping (address owner = > uint256 [] ownedTokens ) ownedTokensOf;
45+ mapping (uint256 tokenId = > uint256 ownedTokensIndex ) ownedTokensIndexOf;
46+ uint256 [] allTokens;
47+ mapping (uint256 tokenId = > uint256 allTokensIndex ) allTokensIndexOf;
48+
49+ mapping (uint256 tokenId = > address approved ) approved;
50+ mapping (address owner = > mapping (address operator = > bool approved )) isApprovedForAll;
51+ }
52+
53+ function getStorage () internal pure returns (ERC721EnumerableStorage storage s ) {
54+ bytes32 position = STORAGE_POSITION;
55+ assembly {
56+ s.slot := position
57+ }
58+ }
59+
60+ function name () external view returns (string memory ) {
61+ return getStorage ().name;
62+ }
63+
64+ function symbol () external view returns (string memory ) {
65+ return getStorage ().symbol;
66+ }
67+
68+ function totalSupply () external view returns (uint256 ) {
69+ return getStorage ().allTokens.length ;
70+ }
71+
72+ function balanceOf (address _owner ) external view returns (uint256 ) {
73+ if (_owner == address (0 )) {
74+ revert ERC721InvalidOwner (_owner);
75+ }
76+ return getStorage ().ownedTokensOf[_owner].length ;
77+ }
78+
79+ function ownerOf (uint256 _tokenId ) public view returns (address ) {
80+ address owner = getStorage ().ownerOf[_tokenId];
81+ if (owner == address (0 )) {
82+ revert ERC721NonexistentToken (_tokenId);
83+ }
84+ return owner;
85+ }
86+
87+ // IERC721Enumerable
88+ function tokenOfOwnerByIndex (address _owner , uint256 _index ) external view returns (uint256 ) {
89+ ERC721EnumerableStorage storage s = getStorage ();
90+ if (_index >= s.ownedTokensOf[_owner].length ) {
91+ revert ERC721OutOfBoundsIndex (_owner, _index);
92+ }
93+ return s.ownedTokensOf[_owner][_index];
94+ }
95+
96+ function getApproved (uint256 _tokenId ) external view returns (address ) {
97+ address owner = getStorage ().ownerOf[_tokenId];
98+ if (owner == address (0 )) {
99+ revert ERC721NonexistentToken (_tokenId);
100+ }
101+ return getStorage ().approved[_tokenId];
102+ }
103+
104+ function isApprovedForAll (address _owner , address _operator ) external view returns (bool ) {
105+ return getStorage ().isApprovedForAll[_owner][_operator];
106+ }
107+
108+ function approve (address _approved , uint256 _tokenId ) external {
109+ ERC721EnumerableStorage storage s = getStorage ();
110+ address owner = s.ownerOf[_tokenId];
111+ if (owner == address (0 )) {
112+ revert ERC721NonexistentToken (_tokenId);
113+ }
114+ if (msg .sender != owner && ! s.isApprovedForAll[owner][msg .sender ]) {
115+ revert ERC721InvalidApprover (_approved);
116+ }
117+ s.approved[_tokenId] = _approved;
118+ emit Approval (owner, _approved, _tokenId);
119+ }
120+
121+ function setApprovalForAll (address _operator , bool _approved ) external {
122+ if (_operator == address (0 )) {
123+ revert ERC721InvalidOperator (_operator);
124+ }
125+ getStorage ().isApprovedForAll[msg .sender ][_operator] = _approved;
126+ emit ApprovalForAll (msg .sender , _operator, _approved);
127+ }
128+
129+ function internalTransferFrom (address _from , address _to , uint256 _tokenId ) internal {
130+ ERC721EnumerableStorage storage s = getStorage ();
131+ if (_to == address (0 )) {
132+ revert ERC721InvalidReceiver (address (0 ));
133+ }
134+ address owner = s.ownerOf[_tokenId];
135+ if (owner == address (0 )) {
136+ revert ERC721NonexistentToken (_tokenId);
137+ }
138+ if (owner != _from) {
139+ revert ERC721IncorrectOwner (_from, _tokenId, owner);
140+ }
141+ if (msg .sender != _from) {
142+ if (! s.isApprovedForAll[_from][msg .sender ] && msg .sender != s.approved[_tokenId]) {
143+ revert ERC721InsufficientApproval (msg .sender , _tokenId);
144+ }
145+ }
146+ delete s.approved[_tokenId];
147+ // removing token from _from's ownedTokens
148+ uint256 tokenIndex = s.ownedTokensIndexOf[_tokenId];
149+ uint256 lastTokenIndex = s.ownedTokensOf[_from].length - 1 ;
150+ if (tokenIndex != lastTokenIndex) {
151+ uint256 lastTokenId = s.ownedTokensOf[_from][lastTokenIndex];
152+ s.ownedTokensOf[_from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
153+ s.ownedTokensIndexOf[lastTokenId] = tokenIndex; // Update the moved token's index
154+ }
155+ s.ownedTokensOf[_from].pop ();
156+ // adding token to _to's ownedTokens
157+ s.ownedTokensIndexOf[_tokenId] = s.ownedTokensOf[_to].length ;
158+ s.ownedTokensOf[_to].push (_tokenId);
159+ s.ownerOf[_tokenId] = _to;
160+ emit Transfer (_from, _to, _tokenId);
161+ }
162+
163+ function transferFrom (address _from , address _to , uint256 _tokenId ) external {
164+ internalTransferFrom (_from, _to, _tokenId);
165+ }
166+
167+ function safeTransferFrom (address _from , address _to , uint256 _tokenId ) external {
168+ internalTransferFrom (_from, _to, _tokenId);
169+
170+ // If _to is a contract, check for IERC721Receiver implementation
171+ if (_to.code.length > 0 ) {
172+ try IERC721Receiver (_to).onERC721Received (msg .sender , _from, _tokenId, "" ) returns (bytes4 retval ) {
173+ if (retval != IERC721Receiver .onERC721Received.selector ) {
174+ revert ERC721InvalidReceiver (_to);
175+ }
176+ } catch (bytes memory reason ) {
177+ if (reason.length == 0 ) {
178+ // non-IERC721Receiver implementer
179+ revert ERC721InvalidReceiver (_to);
180+ } else {
181+ // Return the original revert reason
182+ // "memory-safe" means we used memory safely so Solidity does not disable optimizations
183+ assembly ("memory-safe" ) {
184+ revert (add (reason, 0x20 ), mload (reason))
185+ }
186+ }
187+ }
188+ }
189+ }
190+
191+ function safeTransferFrom (address _from , address _to , uint256 _tokenId , bytes calldata _data ) external {
192+ internalTransferFrom (_from, _to, _tokenId);
193+
194+ // If _to is a contract, check for IERC721Receiver implementation
195+ if (_to.code.length > 0 ) {
196+ try IERC721Receiver (_to).onERC721Received (msg .sender , _from, _tokenId, _data) returns (bytes4 retval ) {
197+ if (retval != IERC721Receiver .onERC721Received.selector ) {
198+ revert ERC721InvalidReceiver (_to);
199+ }
200+ } catch (bytes memory reason ) {
201+ if (reason.length == 0 ) {
202+ // non-IERC721Receiver implementer
203+ revert ERC721InvalidReceiver (_to);
204+ } else {
205+ // Return the original revert reason
206+ // "memory-safe" means we used memory safely so Solidity does not disable optimizations
207+ assembly ("memory-safe" ) {
208+ revert (add (reason, 0x20 ), mload (reason))
209+ }
210+ }
211+ }
212+ }
213+ }
214+
215+
216+ }
0 commit comments