diff --git a/src/ERC20/ERC20/ERC20Facet.sol b/src/ERC20/ERC20/ERC20Facet.sol index 4b618ff5..c6fcdf99 100644 --- a/src/ERC20/ERC20/ERC20Facet.sol +++ b/src/ERC20/ERC20/ERC20Facet.sol @@ -4,6 +4,7 @@ pragma solidity >=0.8.30; contract ERC20Facet { + /// @notice Thrown when an account has insufficient balance for a transfer or burn. /// @param _sender Address attempting the transfer. /// @param _balance Current balance of the sender. @@ -32,7 +33,7 @@ contract ERC20Facet { /// @param _spender Invalid spender address. error ERC20InvalidSpender(address _spender); - + /// @notice Emitted when an approval is made for a spender by an owner. /// @param _owner The address granting the allowance. /// @param _spender The address receiving the allowance. @@ -45,6 +46,7 @@ contract ERC20Facet { /// @param _value Amount of tokens transferred. event Transfer(address indexed _from, address indexed _to, uint256 _value); + /// @dev Storage position determined by the keccak256 hash of the diamond storage identifier. bytes32 constant STORAGE_POSITION = keccak256("compose.erc20"); @@ -124,6 +126,7 @@ contract ERC20Facet { return getStorage().allowances[_owner][_spender]; } + /** * @notice Approves a spender to transfer up to a certain amount of tokens on behalf of the caller. * @dev Emits an {Approval} event. diff --git a/src/ERC721/ERC721/ERC721Facet.sol b/src/ERC721/ERC721/ERC721Facet.sol index db74f827..f2be040a 100644 --- a/src/ERC721/ERC721/ERC721Facet.sol +++ b/src/ERC721/ERC721/ERC721Facet.sol @@ -1,39 +1,60 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.30; - -/// @notice Interface for contracts that want to support safeTransfers. +/// @title ERC-721 Token Receiver Interface +/// @notice Interface for contracts that want to handle safe transfers of ERC-721 tokens. +/// @dev Contracts implementing this must return the selector to confirm token receipt. interface IERC721Receiver { + /// @notice Handles the receipt of an NFT. + /// @param _operator The address which called `safeTransferFrom`. + /// @param _from The previous owner of the token. + /// @param _tokenId The NFT identifier being transferred. + /// @param _data Additional data with no specified format. + /// @return The selector to confirm the token transfer. function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns (bytes4); } - -/// @title ERC-721 token (zero-dependency implementation) -/// @notice A complete, dependency-free ERC-721 implementation using the project's storage pattern. -/// @dev This contract provides metadata, ownership, approvals, safe transfers (with local IERC721Receiver check), -/// minting, burning, and helpers. It intentionally avoids external imports. +/// @title ERC-721 Token (Zero-Dependency Implementation) +/// @notice A complete, dependency-free ERC-721 implementation using the diamond storage pattern. +/// @dev This facet provides metadata, ownership, approvals, safe transfers, minting, burning, and helpers. contract ERC721Facet { - // ERC-6093: Custom errors for ERC-721 + /// @notice Error indicating the queried owner address is invalid (zero address). error ERC721InvalidOwner(address _owner); + + /// @notice Error indicating that the queried token does not exist. error ERC721NonexistentToken(uint256 _tokenId); + + /// @notice Error indicating the sender does not match the token owner. error ERC721IncorrectOwner(address _sender, uint256 _tokenId, address _owner); + + /// @notice Error indicating the sender address is invalid. error ERC721InvalidSender(address _sender); + + /// @notice Error indicating the receiver address is invalid. error ERC721InvalidReceiver(address _receiver); + + /// @notice Error indicating the operator lacks approval to transfer the given token. error ERC721InsufficientApproval(address _operator, uint256 _tokenId); + + /// @notice Error indicating the approver address is invalid. error ERC721InvalidApprover(address _approver); + + /// @notice Error indicating the operator address is invalid. error ERC721InvalidOperator(address _operator); + /// @notice Emitted when ownership of an NFT changes by any mechanism. event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); + + /// @notice Emitted when the approved address for an NFT is changed or reaffirmed. event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); + + /// @notice Emitted when an operator is enabled or disabled for an owner. event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); - // Struct storage position defined by keccak256 hash - // of diamond storage identifier bytes32 constant STORAGE_POSITION = keccak256("compose.erc721"); - // Storage defined using the ERC-8042 standard - // @custom:storage-location erc8042:compose.erc721 + /// @custom:storage-location erc8042:compose.erc721 struct ERC721Storage { string name; string symbol; @@ -44,6 +65,9 @@ contract ERC721Facet { mapping(address owner => mapping(address operator => bool approved)) isApprovedForAll; } + /// @notice Returns a pointer to the ERC-721 storage struct. + /// @dev Uses inline assembly to access the storage slot defined by STORAGE_POSITION. + /// @return s The ERC721Storage struct in storage. function getStorage() internal pure returns (ERC721Storage storage s) { bytes32 position = STORAGE_POSITION; assembly { @@ -51,14 +75,21 @@ contract ERC721Facet { } } + /// @notice Returns the token collection name. + /// @return The name of the token collection. function name() external view returns (string memory) { return getStorage().name; } + /// @notice Returns the token collection symbol. + /// @return The symbol of the token collection. function symbol() external view returns (string memory) { return getStorage().symbol; } + /// @notice Returns the number of tokens owned by a given address. + /// @param _owner The address to query the balance of. + /// @return The balance (number of tokens) owned by `_owner`. function balanceOf(address _owner) external view returns (uint256) { if (_owner == address(0)) { revert ERC721InvalidOwner(_owner); @@ -66,6 +97,9 @@ contract ERC721Facet { return getStorage().balanceOf[_owner]; } + /// @notice Returns the owner of a given token ID. + /// @param _tokenId The token ID to query. + /// @return The address of the token owner. function ownerOf(uint256 _tokenId) public view returns (address) { address owner = getStorage().ownerOf[_tokenId]; if (owner == address(0)) { @@ -74,6 +108,9 @@ contract ERC721Facet { return owner; } + /// @notice Returns the approved address for a given token ID. + /// @param _tokenId The token ID to query the approval of. + /// @return The approved address for the token. function getApproved(uint256 _tokenId) external view returns (address) { address owner = getStorage().ownerOf[_tokenId]; if (owner == address(0)) { @@ -82,10 +119,17 @@ contract ERC721Facet { return getStorage().approved[_tokenId]; } + /// @notice Returns true if an operator is approved to manage all of an owner's assets. + /// @param _owner The token owner. + /// @param _operator The operator address. + /// @return True if the operator is approved for all tokens of the owner. function isApprovedForAll(address _owner, address _operator) external view returns (bool) { return getStorage().isApprovedForAll[_owner][_operator]; } + /// @notice Approves another address to transfer the given token ID. + /// @param _approved The address to be approved. + /// @param _tokenId The token ID to approve. function approve(address _approved, uint256 _tokenId) external { ERC721Storage storage s = getStorage(); address owner = s.ownerOf[_tokenId]; @@ -99,6 +143,9 @@ contract ERC721Facet { emit Approval(owner, _approved, _tokenId); } + /// @notice Approves or revokes permission for an operator to manage all caller's assets. + /// @param _operator The operator address to set approval for. + /// @param _approved True to approve, false to revoke. function setApprovalForAll(address _operator, bool _approved) external { if (_operator == address(0)) { revert ERC721InvalidOperator(_operator); @@ -107,6 +154,10 @@ contract ERC721Facet { emit ApprovalForAll(msg.sender, _operator, _approved); } + /// @dev Internal function to transfer a token, checking for ownership and approval. + /// @param _from The current owner of the token. + /// @param _to The address to receive the token. + /// @param _tokenId The token ID to transfer. function internalTransferFrom(address _from, address _to, uint256 _tokenId) internal { ERC721Storage storage s = getStorage(); if (_to == address(0)) { @@ -133,14 +184,21 @@ contract ERC721Facet { emit Transfer(_from, _to, _tokenId); } + /// @notice Transfers a token from one address to another. + /// @param _from The current owner of the token. + /// @param _to The address to receive the token. + /// @param _tokenId The token ID to transfer. function transferFrom(address _from, address _to, uint256 _tokenId) external { internalTransferFrom(_from, _to, _tokenId); } + /// @notice Safely transfers a token, checking if the receiver can handle ERC-721 tokens. + /// @param _from The current owner of the token. + /// @param _to The address to receive the token. + /// @param _tokenId The token ID to transfer. function safeTransferFrom(address _from, address _to, uint256 _tokenId) external { internalTransferFrom(_from, _to, _tokenId); - // If _to is a contract, check for IERC721Receiver implementation if (_to.code.length > 0) { try IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, "") returns (bytes4 retval) { if (retval != IERC721Receiver.onERC721Received.selector) { @@ -148,11 +206,8 @@ contract ERC721Facet { } } catch (bytes memory reason) { if (reason.length == 0) { - // non-IERC721Receiver implementer revert ERC721InvalidReceiver(_to); } else { - // Return the revert reason - // "memory-safe" means we used memory safely so Solidity does not disable optimizations assembly ("memory-safe") { revert(add(reason, 0x20), mload(reason)) } @@ -161,9 +216,13 @@ contract ERC721Facet { } } + /// @notice Safely transfers a token with additional data. + /// @param _from The current owner of the token. + /// @param _to The address to receive the token. + /// @param _tokenId The token ID to transfer. + /// @param _data Additional data with no specified format. function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata _data) external { internalTransferFrom(_from, _to, _tokenId); - // If _to is a contract, check for IERC721Receiver implementation if (_to.code.length > 0) { try IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) returns (bytes4 retval) { if (retval != IERC721Receiver.onERC721Received.selector) { @@ -171,11 +230,8 @@ contract ERC721Facet { } } catch (bytes memory reason) { if (reason.length == 0) { - // non-IERC721Receiver implementer revert ERC721InvalidReceiver(_to); - } else { - // Return the revert reason - // "memory-safe" means we used memory safely so Solidity does not disable optimizations + } else { assembly ("memory-safe") { revert(add(reason, 0x20), mload(reason)) } @@ -183,4 +239,4 @@ contract ERC721Facet { } } } -} \ No newline at end of file +} diff --git a/src/ERC721/ERC721Enumerable/ERC721EnumerableFacet.sol b/src/ERC721/ERC721Enumerable/ERC721EnumerableFacet.sol index db1d4a3c..70e614d5 100644 --- a/src/ERC721/ERC721Enumerable/ERC721EnumerableFacet.sol +++ b/src/ERC721/ERC721Enumerable/ERC721EnumerableFacet.sol @@ -1,45 +1,61 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.30; - -/// @notice Interface for contracts that want to support safeTransfers. +/// @title ERC721 Receiver Interface +/// @notice Interface for contracts that want to support safe ERC721 token transfers. +/// @dev Implementers must return the function selector to confirm token receipt. interface IERC721Receiver { + /// @notice Handles the receipt of an NFT. + /// @param _operator The address which initiated the transfer. + /// @param _from The previous owner of the token. + /// @param _tokenId The NFT identifier being transferred. + /// @param _data Additional data with no specified format. + /// @return A bytes4 value indicating acceptance of the transfer. function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns (bytes4); } - -/// @title ERC-721 token (zero-dependency implementation) -/// @notice A complete, dependency-free ERC-721 implementation using the project's storage pattern. -/// @dev This contract provides metadata, ownership, approvals, safe transfers (with local IERC721Receiver check), -/// minting, burning, and helpers. It intentionally avoids external imports. +/// @title ERC-721 Enumerable Token (Zero-dependency Implementation) +/// @notice A complete, dependency-free ERC-721 implementation with enumeration support using a custom storage layout. +/// @dev Provides metadata, ownership, approvals, enumeration, safe transfers, minting, and burning features. contract ERC721EnumerableFacet { - // ERC-6093: Custom errors for ERC-721 + + /// @notice Thrown when querying or transferring from an invalid owner address. error ERC721InvalidOwner(address _owner); + /// @notice Thrown when operating on a non-existent token. error ERC721NonexistentToken(uint256 _tokenId); + /// @notice Thrown when the provided owner does not match the actual owner of the token. error ERC721IncorrectOwner(address _sender, uint256 _tokenId, address _owner); + /// @notice Thrown when the sender address is invalid. error ERC721InvalidSender(address _sender); + /// @notice Thrown when the receiver address is invalid. error ERC721InvalidReceiver(address _receiver); + /// @notice Thrown when the operator lacks sufficient approval for a transfer. error ERC721InsufficientApproval(address _operator, uint256 _tokenId); + /// @notice Thrown when an invalid approver is provided. error ERC721InvalidApprover(address _approver); + /// @notice Thrown when an invalid operator is provided. error ERC721InvalidOperator(address _operator); + /// @notice Thrown when an index is out of bounds during enumeration. error ERC721OutOfBoundsIndex(address _owner, uint256 _index); + + /// @notice Emitted when a token is transferred between addresses. event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); + /// @notice Emitted when a token is approved for transfer by another address. event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); + /// @notice Emitted when an operator is approved or revoked for all tokens of an owner. event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); - // Struct storage position defined by keccak256 hash - // of diamond storage identifier + bytes32 constant STORAGE_POSITION = keccak256("compose.erc721.enumerable"); - // Storage defined using the ERC-8042 standard - // @custom:storage-location erc8042:compose.erc721.enumerable + /// @custom:storage-location erc8042:compose.erc721.enumerable struct ERC721EnumerableStorage { string name; string symbol; mapping(uint256 tokenId => string tokenURI) tokenURIOf; - + mapping(uint256 tokenId => address owner) ownerOf; mapping(address owner => uint256[] ownedTokens) ownedTokensOf; mapping(uint256 tokenId => uint256 ownedTokensIndex) ownedTokensIndexOf; @@ -47,28 +63,41 @@ contract ERC721EnumerableFacet { mapping(uint256 tokenId => uint256 allTokensIndex) allTokensIndexOf; mapping(uint256 tokenId => address approved) approved; - mapping(address owner => mapping(address operator => bool approved)) isApprovedForAll; + mapping(address owner => mapping(address operator => bool approved)) isApprovedForAll; } + /// @notice Returns the storage struct used by this facet. + /// @return s The ERC721Enumerable storage struct. function getStorage() internal pure returns (ERC721EnumerableStorage storage s) { bytes32 position = STORAGE_POSITION; assembly { s.slot := position } } - + + + /// @notice Returns the name of the token collection. + /// @return The token collection name. function name() external view returns (string memory) { return getStorage().name; } + /// @notice Returns the symbol of the token collection. + /// @return The token symbol. function symbol() external view returns (string memory) { return getStorage().symbol; } + /// @notice Returns the total number of tokens in existence. + /// @return The total supply of tokens. function totalSupply() external view returns (uint256) { return getStorage().allTokens.length; - } + } + + /// @notice Returns the number of tokens owned by an address. + /// @param _owner The address to query. + /// @return The balance (number of tokens owned). function balanceOf(address _owner) external view returns (uint256) { if (_owner == address(0)) { revert ERC721InvalidOwner(_owner); @@ -76,15 +105,21 @@ contract ERC721EnumerableFacet { return getStorage().ownedTokensOf[_owner].length; } + /// @notice Returns the owner of a given token ID. + /// @param _tokenId The token ID to query. + /// @return The address of the token owner. function ownerOf(uint256 _tokenId) public view returns (address) { address owner = getStorage().ownerOf[_tokenId]; if (owner == address(0)) { revert ERC721NonexistentToken(_tokenId); - } + } return owner; } - // IERC721Enumerable + /// @notice Returns a token ID owned by a given address at a specific index. + /// @param _owner The address to query. + /// @param _index The index of the token. + /// @return The token ID owned by `_owner` at `_index`. function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256) { ERC721EnumerableStorage storage s = getStorage(); if (_index >= s.ownedTokensOf[_owner].length) { @@ -93,6 +128,10 @@ contract ERC721EnumerableFacet { return s.ownedTokensOf[_owner][_index]; } + + /// @notice Returns the approved address for a given token ID. + /// @param _tokenId The token ID to query. + /// @return The approved address for the token. function getApproved(uint256 _tokenId) external view returns (address) { address owner = getStorage().ownerOf[_tokenId]; if (owner == address(0)) { @@ -101,10 +140,17 @@ contract ERC721EnumerableFacet { return getStorage().approved[_tokenId]; } + /// @notice Returns whether an operator is approved for all tokens of an owner. + /// @param _owner The token owner. + /// @param _operator The operator address. + /// @return True if approved for all, false otherwise. function isApprovedForAll(address _owner, address _operator) external view returns (bool) { return getStorage().isApprovedForAll[_owner][_operator]; } + /// @notice Approves another address to transfer a specific token ID. + /// @param _approved The address being approved. + /// @param _tokenId The token ID to approve. function approve(address _approved, uint256 _tokenId) external { ERC721EnumerableStorage storage s = getStorage(); address owner = s.ownerOf[_tokenId]; @@ -113,11 +159,14 @@ contract ERC721EnumerableFacet { } if (msg.sender != owner && !s.isApprovedForAll[owner][msg.sender]) { revert ERC721InvalidApprover(_approved); - } + } s.approved[_tokenId] = _approved; - emit Approval(owner, _approved, _tokenId); + emit Approval(owner, _approved, _tokenId); } + /// @notice Approves or revokes an operator to manage all tokens of the caller. + /// @param _operator The operator address. + /// @param _approved True to approve, false to revoke. function setApprovalForAll(address _operator, bool _approved) external { if (_operator == address(0)) { revert ERC721InvalidOperator(_operator); @@ -126,9 +175,14 @@ contract ERC721EnumerableFacet { emit ApprovalForAll(msg.sender, _operator, _approved); } + + /// @notice Internal function to transfer ownership of a token ID. + /// @param _from The address sending the token. + /// @param _to The address receiving the token. + /// @param _tokenId The token ID being transferred. function internalTransferFrom(address _from, address _to, uint256 _tokenId) internal { ERC721EnumerableStorage storage s = getStorage(); - if (_to == address(0)) { + if (_to == address(0)) { revert ERC721InvalidReceiver(address(0)); } address owner = s.ownerOf[_tokenId]; @@ -137,80 +191,76 @@ contract ERC721EnumerableFacet { } if (owner != _from) { revert ERC721IncorrectOwner(_from, _tokenId, owner); - } + } if (msg.sender != _from) { - if(!s.isApprovedForAll[_from][msg.sender] && msg.sender != s.approved[_tokenId]) { + if (!s.isApprovedForAll[_from][msg.sender] && msg.sender != s.approved[_tokenId]) { revert ERC721InsufficientApproval(msg.sender, _tokenId); } } delete s.approved[_tokenId]; - // removing token from _from's ownedTokens + uint256 tokenIndex = s.ownedTokensIndexOf[_tokenId]; uint256 lastTokenIndex = s.ownedTokensOf[_from].length - 1; - if(tokenIndex != lastTokenIndex) { + if (tokenIndex != lastTokenIndex) { uint256 lastTokenId = s.ownedTokensOf[_from][lastTokenIndex]; - s.ownedTokensOf[_from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token - s.ownedTokensIndexOf[lastTokenId] = tokenIndex; // Update the moved token's index + s.ownedTokensOf[_from][tokenIndex] = lastTokenId; + s.ownedTokensIndexOf[lastTokenId] = tokenIndex; } - s.ownedTokensOf[_from].pop(); - // adding token to _to's ownedTokens + s.ownedTokensOf[_from].pop(); + s.ownedTokensIndexOf[_tokenId] = s.ownedTokensOf[_to].length; s.ownedTokensOf[_to].push(_tokenId); - s.ownerOf[_tokenId] = _to; + s.ownerOf[_tokenId] = _to; + emit Transfer(_from, _to, _tokenId); - } - + } + + /// @notice Transfers a token from one address to another. + /// @param _from The current owner of the token. + /// @param _to The recipient address. + /// @param _tokenId The token ID to transfer. function transferFrom(address _from, address _to, uint256 _tokenId) external { internalTransferFrom(_from, _to, _tokenId); } + /// @notice Safely transfers a token, checking for receiver contract compatibility. + /// @param _from The current owner of the token. + /// @param _to The recipient address. + /// @param _tokenId The token ID to transfer. function safeTransferFrom(address _from, address _to, uint256 _tokenId) external { internalTransferFrom(_from, _to, _tokenId); - - // If _to is a contract, check for IERC721Receiver implementation if (_to.code.length > 0) { try IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, "") returns (bytes4 retval) { if (retval != IERC721Receiver.onERC721Received.selector) { revert ERC721InvalidReceiver(_to); } } catch (bytes memory reason) { - if (reason.length == 0) { - // non-IERC721Receiver implementer - revert ERC721InvalidReceiver(_to); - } else { - // Return the original revert reason - // "memory-safe" means we used memory safely so Solidity does not disable optimizations - assembly ("memory-safe") { - revert(add(reason, 0x20), mload(reason)) - } + if (reason.length == 0) revert ERC721InvalidReceiver(_to); + assembly ("memory-safe") { + revert(add(reason, 0x20), mload(reason)) } } } } + /// @notice Safely transfers a token with additional data. + /// @param _from The current owner of the token. + /// @param _to The recipient address. + /// @param _tokenId The token ID to transfer. + /// @param _data Additional data to send to the receiver contract. function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata _data) external { internalTransferFrom(_from, _to, _tokenId); - - // If _to is a contract, check for IERC721Receiver implementation if (_to.code.length > 0) { try IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) returns (bytes4 retval) { if (retval != IERC721Receiver.onERC721Received.selector) { revert ERC721InvalidReceiver(_to); } } catch (bytes memory reason) { - if (reason.length == 0) { - // non-IERC721Receiver implementer - revert ERC721InvalidReceiver(_to); - } else { - // Return the original revert reason - // "memory-safe" means we used memory safely so Solidity does not disable optimizations - assembly ("memory-safe") { - revert(add(reason, 0x20), mload(reason)) - } + if (reason.length == 0) revert ERC721InvalidReceiver(_to); + assembly ("memory-safe") { + revert(add(reason, 0x20), mload(reason)) } } } } - - -} \ No newline at end of file +}