233 lines
8.1 KiB
Solidity
233 lines
8.1 KiB
Solidity
// SPDX-License-Identifier: CC0-1.0
|
|
pragma solidity ^0.8.16;
|
|
|
|
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
|
|
|
|
import {IERC721Errors} from "../interfaces/IERC721Errors.sol";
|
|
|
|
/// @title Reference Minimal ERC-721 Contract
|
|
contract ERC721 is IERC721, IERC721Errors {
|
|
|
|
/// @notice The total number of NFTs in circulation.
|
|
uint256 public totalSupply;
|
|
|
|
/// @notice Gets the approved address for an NFT.
|
|
/// @dev This implementation does not throw for zero-address queries.
|
|
mapping(uint256 => address) public getApproved;
|
|
|
|
/// @notice Gets the number of NFTs owned by an address.
|
|
mapping(address => uint256) internal _balanceOf;
|
|
|
|
/// @dev Tracks the assigned owner of an address.
|
|
mapping(uint256 => address) internal _ownerOf;
|
|
|
|
/// @dev Checks for an owner if an address is an authorized operator.
|
|
mapping(address => mapping(address => bool)) internal _operatorApprovals;
|
|
|
|
/// @dev EIP-165 identifiers for all supported interfaces.
|
|
bytes4 private constant _ERC165_INTERFACE_ID = 0x01ffc9a7;
|
|
bytes4 private constant _ERC721_INTERFACE_ID = 0x80ac58cd;
|
|
|
|
/// @notice Gets the assigned owner for token `id`.
|
|
/// @param id The id of the NFT being queried.
|
|
/// @return The address of the owner of the NFT of id `id`.
|
|
function ownerOf(uint256 id) external view virtual returns (address) {
|
|
return _ownerOf[id];
|
|
}
|
|
|
|
/// @notice Gets number of NFTs owned by address `owner`.
|
|
/// @param owner The address whose balance is being queried.
|
|
/// @return The number of NFTs owned by address `owner`.
|
|
function balanceOf(address owner) external view virtual returns (uint256) {
|
|
return _balanceOf[owner];
|
|
}
|
|
|
|
/// @notice Sets approved address of NFT of id `id` to address `approved`.
|
|
/// @param approved The new approved address for the NFT.
|
|
/// @param id The id of the NFT to approve.
|
|
function approve(address approved, uint256 id) external virtual {
|
|
address owner = _ownerOf[id];
|
|
|
|
if (msg.sender != owner && !_operatorApprovals[owner][msg.sender]) {
|
|
revert SenderUnauthorized();
|
|
}
|
|
|
|
getApproved[id] = approved;
|
|
emit Approval(owner, approved, id);
|
|
}
|
|
|
|
/// @notice Checks if `operator` is an authorized operator for `owner`.
|
|
/// @param owner The address of the owner.
|
|
/// @param operator The address of the owner's operator.
|
|
/// @return True if `operator` is approved operator of `owner`, else False.
|
|
function isApprovedForAll(address owner, address operator)
|
|
external
|
|
view
|
|
virtual returns (bool)
|
|
{
|
|
return _operatorApprovals[owner][operator];
|
|
}
|
|
|
|
/// @notice Sets the operator for `msg.sender` to `operator`.
|
|
/// @param operator The operator address that will manage the sender's NFTs.
|
|
/// @param approved Whether operator is allowed to operate sender's NFTs.
|
|
function setApprovalForAll(address operator, bool approved) external virtual {
|
|
_operatorApprovals[msg.sender][operator] = approved;
|
|
emit ApprovalForAll(msg.sender, operator, approved);
|
|
}
|
|
|
|
/// @notice Checks if interface of identifier `id` is supported.
|
|
/// @param id The ERC-165 interface identifier.
|
|
/// @return True if interface id `id` is supported, false otherwise.
|
|
function supportsInterface(bytes4 id) public pure virtual returns (bool) {
|
|
return
|
|
id == _ERC165_INTERFACE_ID ||
|
|
id == _ERC721_INTERFACE_ID;
|
|
}
|
|
|
|
/// @notice Transfers NFT of id `id` from address `from` to address `to`,
|
|
/// with safety checks ensuring `to` is capable of receiving the NFT.
|
|
/// @dev Safety checks are only performed if `to` is a smart contract.
|
|
/// @param from The existing owner address of the NFT to be transferred.
|
|
/// @param to The new owner address of the NFT being transferred.
|
|
/// @param id The id of the NFT being transferred.
|
|
/// @param data Additional transfer data to pass to the receiving contract.
|
|
function safeTransferFrom(
|
|
address from,
|
|
address to,
|
|
uint256 id,
|
|
bytes memory data
|
|
) public virtual {
|
|
transferFrom(from, to, id);
|
|
|
|
if (
|
|
to.code.length != 0 &&
|
|
IERC721Receiver(to).onERC721Received(msg.sender, from, id, data)
|
|
!=
|
|
IERC721Receiver.onERC721Received.selector
|
|
) {
|
|
revert SafeTransferUnsupported();
|
|
}
|
|
}
|
|
|
|
/// @notice Transfers NFT of id `id` from address `from` to address `to`,
|
|
/// with safety checks ensuring `to` is capable of receiving the NFT.
|
|
/// @dev Safety checks are only performed if `to` is a smart contract.
|
|
/// @param from The existing owner address of the NFT to be transferred.
|
|
/// @param to The new owner address of the NFT being transferred.
|
|
/// @param id The id of the NFT being transferred.
|
|
function safeTransferFrom(
|
|
address from,
|
|
address to,
|
|
uint256 id
|
|
) public virtual {
|
|
transferFrom(from, to, id);
|
|
|
|
if (
|
|
to.code.length != 0 &&
|
|
IERC721Receiver(to).onERC721Received(msg.sender, from, id, "")
|
|
!=
|
|
IERC721Receiver.onERC721Received.selector
|
|
) {
|
|
revert SafeTransferUnsupported();
|
|
}
|
|
}
|
|
|
|
/// @notice Transfers NFT of id `id` from address `from` to address `to`,
|
|
/// without performing any safety checks.
|
|
/// @dev Existence of an NFT is inferred by having a non-zero owner address.
|
|
/// Transfers clear owner approvals, but `Approval` events are omitted.
|
|
/// @param from The existing owner address of the NFT being transferred.
|
|
/// @param to The new owner address of the NFT being transferred.
|
|
/// @param id The id of the NFT being transferred.
|
|
function transferFrom(
|
|
address from,
|
|
address to,
|
|
uint256 id
|
|
) public virtual {
|
|
if (from != _ownerOf[id]) {
|
|
revert OwnerInvalid();
|
|
}
|
|
|
|
if (
|
|
msg.sender != from &&
|
|
msg.sender != getApproved[id] &&
|
|
!_operatorApprovals[from][msg.sender]
|
|
) {
|
|
revert SenderUnauthorized();
|
|
}
|
|
|
|
if (to == address(0)) {
|
|
revert ReceiverInvalid();
|
|
}
|
|
|
|
_beforeTokenTransfer(from, to, id);
|
|
|
|
delete getApproved[id];
|
|
|
|
unchecked {
|
|
_balanceOf[from]--;
|
|
_balanceOf[to]++;
|
|
}
|
|
|
|
_ownerOf[id] = to;
|
|
emit Transfer(from, to, id);
|
|
}
|
|
|
|
/// @dev Mints NFT of id `id` to address `to`. To save gas, it is assumed
|
|
/// that `maxSupply` < `type(uint256).max` (ex. for tabs, cap is very low).
|
|
/// @param to Address receiving the minted NFT.
|
|
/// @param id Identifier of the NFT being minted.
|
|
/// @return The id of the minted NFT.
|
|
function _mint(address to, uint256 id) internal virtual returns (uint256) {
|
|
if (to == address(0)) {
|
|
revert ReceiverInvalid();
|
|
}
|
|
if (_ownerOf[id] != address(0)) {
|
|
revert TokenAlreadyMinted();
|
|
}
|
|
|
|
_beforeTokenTransfer(address(0), to, id);
|
|
|
|
unchecked {
|
|
totalSupply++;
|
|
_balanceOf[to]++;
|
|
}
|
|
|
|
_ownerOf[id] = to;
|
|
emit Transfer(address(0), to, id);
|
|
return id;
|
|
}
|
|
|
|
/// @dev Burns NFT of id `id`, removing it from existence.
|
|
/// @param id Identifier of the NFT being burned
|
|
function _burn(uint256 id) internal virtual {
|
|
address owner = _ownerOf[id];
|
|
|
|
if (owner == address(0)) {
|
|
revert TokenNonExistent();
|
|
}
|
|
|
|
_beforeTokenTransfer(owner, address(0), id);
|
|
|
|
unchecked {
|
|
totalSupply--;
|
|
_balanceOf[owner]--;
|
|
}
|
|
|
|
delete _ownerOf[id];
|
|
emit Transfer(owner, address(0), id);
|
|
}
|
|
|
|
/// @notice Pre-transfer hook for embedding additional transfer behavior.
|
|
/// @param from The address of the existing owner of the NFT.
|
|
/// @param to The address of the new owner of the NFT.
|
|
/// @param id The id of the NFT being transferred.
|
|
function _beforeTokenTransfer(address from, address to, uint256 id)
|
|
internal
|
|
virtual
|
|
{}
|
|
|
|
}
|