Cleaned
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/ERC721.sol) | |
pragma solidity ^0.8.0; | |
import './IERC721.sol'; | |
import './IERC721Receiver.sol'; | |
import './extensions/IERC721Metadata.sol'; | |
import '../../utils/Address.sol'; | |
import '../../utils/Context.sol'; | |
import '../../utils/Strings.sol'; | |
import '../../utils/introspection/ERC165.sol'; | |
contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { | |
using Address for address; | |
using Strings for uint256; | |
// Token name | |
string private _name; | |
// Token symbol | |
string private _symbol; | |
// Mapping from token ID to owner address | |
mapping(uint256 => address) private _owners; | |
// Mapping owner address to token count | |
mapping(address => uint256) private _balances; | |
// Mapping from token ID to approved address | |
mapping(uint256 => address) private _tokenApprovals; | |
// Mapping from owner to operator approvals | |
mapping(address => mapping(address => bool)) private _operatorApprovals; | |
constructor(string memory name_, string memory symbol_) { | |
_name = name_; | |
_symbol = symbol_; | |
} | |
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { | |
return | |
interfaceId == type(IERC721).interfaceId || | |
interfaceId == type(IERC721Metadata).interfaceId || | |
super.supportsInterface(interfaceId); | |
} | |
function balanceOf(address owner) public view virtual override returns (uint256) { | |
require(owner != address(0), 'ERC721: address zero is not a valid owner'); | |
return _balances[owner]; | |
} | |
function ownerOf(uint256 tokenId) public view virtual override returns (address) { | |
address owner = _ownerOf(tokenId); | |
require(owner != address(0), 'ERC721: invalid token ID'); | |
return owner; | |
} | |
function name() public view virtual override returns (string memory) { | |
return _name; | |
} | |
function symbol() public view virtual override returns (string memory) { | |
return _symbol; | |
} | |
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { | |
_requireMinted(tokenId); | |
string memory baseURI = _baseURI(); | |
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ''; | |
} | |
function _baseURI() internal view virtual returns (string memory) { | |
return ''; | |
} | |
function approve(address to, uint256 tokenId) public virtual override { | |
address owner = ERC721.ownerOf(tokenId); | |
require(to != owner, 'ERC721: approval to current owner'); | |
require( | |
_msgSender() == owner || isApprovedForAll(owner, _msgSender()), | |
'ERC721: approve caller is not token owner or approved for all' | |
); | |
_approve(to, tokenId); | |
} | |
function getApproved(uint256 tokenId) public view virtual override returns (address) { | |
_requireMinted(tokenId); | |
return _tokenApprovals[tokenId]; | |
} | |
function setApprovalForAll(address operator, bool approved) public virtual override { | |
_setApprovalForAll(_msgSender(), operator, approved); | |
} | |
function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { | |
return _operatorApprovals[owner][operator]; | |
} | |
function transferFrom( | |
address from, | |
address to, | |
uint256 tokenId | |
) public virtual override { | |
//solhint-disable-next-line max-line-length | |
require(_isApprovedOrOwner(_msgSender(), tokenId), 'ERC721: caller is not token owner or approved'); | |
_transfer(from, to, tokenId); | |
} | |
function safeTransferFrom( | |
address from, | |
address to, | |
uint256 tokenId | |
) public virtual override { | |
safeTransferFrom(from, to, tokenId, ''); | |
} | |
function safeTransferFrom( | |
address from, | |
address to, | |
uint256 tokenId, | |
bytes memory data | |
) public virtual override { | |
require(_isApprovedOrOwner(_msgSender(), tokenId), 'ERC721: caller is not token owner or approved'); | |
_safeTransfer(from, to, tokenId, data); | |
} | |
function _safeTransfer( | |
address from, | |
address to, | |
uint256 tokenId, | |
bytes memory data | |
) internal virtual { | |
_transfer(from, to, tokenId); | |
require(_checkOnERC721Received(from, to, tokenId, data), 'ERC721: transfer to non ERC721Receiver implementer'); | |
} | |
function _ownerOf(uint256 tokenId) internal view virtual returns (address) { | |
return _owners[tokenId]; | |
} | |
function _exists(uint256 tokenId) internal view virtual returns (bool) { | |
return _ownerOf(tokenId) != address(0); | |
} | |
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { | |
address owner = ERC721.ownerOf(tokenId); | |
return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender); | |
} | |
function _safeMint(address to, uint256 tokenId) internal virtual { | |
_safeMint(to, tokenId, ''); | |
} | |
function _safeMint( | |
address to, | |
uint256 tokenId, | |
bytes memory data | |
) internal virtual { | |
_mint(to, tokenId); | |
require( | |
_checkOnERC721Received(address(0), to, tokenId, data), | |
'ERC721: transfer to non ERC721Receiver implementer' | |
); | |
} | |
function _mint(address to, uint256 tokenId) internal virtual { | |
require(to != address(0), 'ERC721: mint to the zero address'); | |
require(!_exists(tokenId), 'ERC721: token already minted'); | |
_beforeTokenTransfer(address(0), to, tokenId, 1); | |
// Check that tokenId was not minted by `_beforeTokenTransfer` hook | |
require(!_exists(tokenId), 'ERC721: token already minted'); | |
unchecked { | |
// Will not overflow unless all 2**256 token ids are minted to the same owner. | |
// Given that tokens are minted one by one, it is impossible in practice that | |
// this ever happens. Might change if we allow batch minting. | |
// The ERC fails to describe this case. | |
_balances[to] += 1; | |
} | |
_owners[tokenId] = to; | |
emit Transfer(address(0), to, tokenId); | |
_afterTokenTransfer(address(0), to, tokenId, 1); | |
} | |
function _burn(uint256 tokenId) internal virtual { | |
address owner = ERC721.ownerOf(tokenId); | |
_beforeTokenTransfer(owner, address(0), tokenId, 1); | |
// Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook | |
owner = ERC721.ownerOf(tokenId); | |
// Clear approvals | |
delete _tokenApprovals[tokenId]; | |
unchecked { | |
// Cannot overflow, as that would require more tokens to be burned/transferred | |
// out than the owner initially received through minting and transferring in. | |
_balances[owner] -= 1; | |
} | |
delete _owners[tokenId]; | |
emit Transfer(owner, address(0), tokenId); | |
_afterTokenTransfer(owner, address(0), tokenId, 1); | |
} | |
function _transfer( | |
address from, | |
address to, | |
uint256 tokenId | |
) internal virtual { | |
require(ERC721.ownerOf(tokenId) == from, 'ERC721: transfer from incorrect owner'); | |
require(to != address(0), 'ERC721: transfer to the zero address'); | |
_beforeTokenTransfer(from, to, tokenId, 1); | |
// Check that tokenId was not transferred by `_beforeTokenTransfer` hook | |
require(ERC721.ownerOf(tokenId) == from, 'ERC721: transfer from incorrect owner'); | |
// Clear approvals from the previous owner | |
delete _tokenApprovals[tokenId]; | |
unchecked { | |
// `_balances[from]` cannot overflow for the same reason as described in `_burn`: | |
// `from`'s balance is the number of token held, which is at least one before the current | |
// transfer. | |
// `_balances[to]` could overflow in the conditions described in `_mint`. That would require | |
// all 2**256 token ids to be minted, which in practice is impossible. | |
_balances[from] -= 1; | |
_balances[to] += 1; | |
} | |
_owners[tokenId] = to; | |
emit Transfer(from, to, tokenId); | |
_afterTokenTransfer(from, to, tokenId, 1); | |
} | |
/** | |
* @dev Approve `to` to operate on `tokenId` | |
* | |
* Emits an {Approval} event. | |
*/ | |
function _approve(address to, uint256 tokenId) internal virtual { | |
_tokenApprovals[tokenId] = to; | |
emit Approval(ERC721.ownerOf(tokenId), to, tokenId); | |
} | |
function _setApprovalForAll( | |
address owner, | |
address operator, | |
bool approved | |
) internal virtual { | |
require(owner != operator, 'ERC721: approve to caller'); | |
_operatorApprovals[owner][operator] = approved; | |
emit ApprovalForAll(owner, operator, approved); | |
} | |
/** | |
* @dev Reverts if the `tokenId` has not been minted yet. | |
*/ | |
function _requireMinted(uint256 tokenId) internal view virtual { | |
require(_exists(tokenId), 'ERC721: invalid token ID'); | |
} | |
/** | |
* @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address. | |
* The call is not executed if the target address is not a contract. | |
* | |
* @param from address representing the previous owner of the given token ID | |
* @param to target address that will receive the tokens | |
* @param tokenId uint256 ID of the token to be transferred | |
* @param data bytes optional data to send along with the call | |
* @return bool whether the call correctly returned the expected magic value | |
*/ | |
function _checkOnERC721Received( | |
address from, | |
address to, | |
uint256 tokenId, | |
bytes memory data | |
) private returns (bool) { | |
if (to.isContract()) { | |
try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) { | |
return retval == IERC721Receiver.onERC721Received.selector; | |
} catch (bytes memory reason) { | |
if (reason.length == 0) { | |
revert('ERC721: transfer to non ERC721Receiver implementer'); | |
} else { | |
/// @solidity memory-safe-assembly | |
assembly { | |
revert(add(32, reason), mload(reason)) | |
} | |
} | |
} | |
} else { | |
return true; | |
} | |
} | |
/** | |
* @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is | |
* used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1. | |
* | |
* Calling conditions: | |
* | |
* - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`. | |
* - When `from` is zero, the tokens will be minted for `to`. | |
* - When `to` is zero, ``from``'s tokens will be burned. | |
* - `from` and `to` are never both zero. | |
* - `batchSize` is non-zero. | |
* | |
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. | |
*/ | |
function _beforeTokenTransfer( | |
address from, | |
address to, | |
uint256, /* firstTokenId */ | |
uint256 batchSize | |
) internal virtual { | |
if (batchSize > 1) { | |
if (from != address(0)) { | |
_balances[from] -= batchSize; | |
} | |
if (to != address(0)) { | |
_balances[to] += batchSize; | |
} | |
} | |
} | |
/** | |
* @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is | |
* used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1. | |
* | |
* Calling conditions: | |
* | |
* - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`. | |
* - When `from` is zero, the tokens were minted for `to`. | |
* - When `to` is zero, ``from``'s tokens were burned. | |
* - `from` and `to` are never both zero. | |
* - `batchSize` is non-zero. | |
* | |
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. | |
*/ | |
function _afterTokenTransfer( | |
address from, | |
address to, | |
uint256 firstTokenId, | |
uint256 batchSize | |
) internal virtual {} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment