Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/ERC20/ERC20/ERC20Facet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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");

Expand Down Expand Up @@ -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.
Expand Down
100 changes: 78 additions & 22 deletions src/ERC721/ERC721/ERC721Facet.sol
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -44,28 +65,41 @@ 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 {
s.slot := position
}
}

/// @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);
}
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)) {
Expand All @@ -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)) {
Expand All @@ -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];
Expand All @@ -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);
Expand All @@ -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)) {
Expand All @@ -133,26 +184,30 @@ 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) {
revert ERC721InvalidReceiver(_to);
}
} 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))
}
Expand All @@ -161,26 +216,27 @@ 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) {
revert ERC721InvalidReceiver(_to);
}
} 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))
}
}
}
}
}
}
}
Loading
Loading