|
@@ -1,5 +1,5 @@
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
-// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/ERC721.sol)
|
|
|
+// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/ERC721.sol)
|
|
|
|
|
|
pragma solidity ^0.8.0;
|
|
|
|
|
@@ -68,7 +68,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
|
|
|
* @dev See {IERC721-ownerOf}.
|
|
|
*/
|
|
|
function ownerOf(uint256 tokenId) public view virtual override returns (address) {
|
|
|
- address owner = _owners[tokenId];
|
|
|
+ address owner = _ownerOf(tokenId);
|
|
|
require(owner != address(0), "ERC721: invalid token ID");
|
|
|
return owner;
|
|
|
}
|
|
@@ -115,7 +115,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
|
|
|
|
|
|
require(
|
|
|
_msgSender() == owner || isApprovedForAll(owner, _msgSender()),
|
|
|
- "ERC721: approve caller is not token owner nor approved for all"
|
|
|
+ "ERC721: approve caller is not token owner or approved for all"
|
|
|
);
|
|
|
|
|
|
_approve(to, tokenId);
|
|
@@ -153,7 +153,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
|
|
|
uint256 tokenId
|
|
|
) public virtual override {
|
|
|
//solhint-disable-next-line max-line-length
|
|
|
- require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved");
|
|
|
+ require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
|
|
|
|
|
|
_transfer(from, to, tokenId);
|
|
|
}
|
|
@@ -178,7 +178,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
|
|
|
uint256 tokenId,
|
|
|
bytes memory data
|
|
|
) public virtual override {
|
|
|
- require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved");
|
|
|
+ require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
|
|
|
_safeTransfer(from, to, tokenId, data);
|
|
|
}
|
|
|
|
|
@@ -210,6 +210,13 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
|
|
|
require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
|
|
|
+ */
|
|
|
+ function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
|
|
|
+ return _owners[tokenId];
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* @dev Returns whether `tokenId` exists.
|
|
|
*
|
|
@@ -219,7 +226,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
|
|
|
* and stop existing when they are burned (`_burn`).
|
|
|
*/
|
|
|
function _exists(uint256 tokenId) internal view virtual returns (bool) {
|
|
|
- return _owners[tokenId] != address(0);
|
|
|
+ return _ownerOf(tokenId) != address(0);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -280,19 +287,30 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
|
|
|
require(to != address(0), "ERC721: mint to the zero address");
|
|
|
require(!_exists(tokenId), "ERC721: token already minted");
|
|
|
|
|
|
- _beforeTokenTransfer(address(0), to, tokenId);
|
|
|
+ _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;
|
|
|
+ }
|
|
|
|
|
|
- _balances[to] += 1;
|
|
|
_owners[tokenId] = to;
|
|
|
|
|
|
emit Transfer(address(0), to, tokenId);
|
|
|
|
|
|
- _afterTokenTransfer(address(0), to, tokenId);
|
|
|
+ _afterTokenTransfer(address(0), to, tokenId, 1);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @dev Destroys `tokenId`.
|
|
|
* The approval is cleared when the token is burned.
|
|
|
+ * This is an internal function that does not check if the sender is authorized to operate on the token.
|
|
|
*
|
|
|
* Requirements:
|
|
|
*
|
|
@@ -303,17 +321,24 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
|
|
|
function _burn(uint256 tokenId) internal virtual {
|
|
|
address owner = ERC721.ownerOf(tokenId);
|
|
|
|
|
|
- _beforeTokenTransfer(owner, address(0), tokenId);
|
|
|
+ _beforeTokenTransfer(owner, address(0), tokenId, 1);
|
|
|
+
|
|
|
+ // Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
|
|
|
+ owner = ERC721.ownerOf(tokenId);
|
|
|
|
|
|
// Clear approvals
|
|
|
- _approve(address(0), tokenId);
|
|
|
+ delete _tokenApprovals[tokenId];
|
|
|
|
|
|
- _balances[owner] -= 1;
|
|
|
+ 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);
|
|
|
+ _afterTokenTransfer(owner, address(0), tokenId, 1);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -335,18 +360,28 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
|
|
|
require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
|
|
|
require(to != address(0), "ERC721: transfer to the zero address");
|
|
|
|
|
|
- _beforeTokenTransfer(from, to, tokenId);
|
|
|
+ _beforeTokenTransfer(from, to, tokenId, 1);
|
|
|
|
|
|
- // Clear approvals from the previous owner
|
|
|
- _approve(address(0), tokenId);
|
|
|
+ // Check that tokenId was not transferred by `_beforeTokenTransfer` hook
|
|
|
+ require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
|
|
|
|
|
|
- _balances[from] -= 1;
|
|
|
- _balances[to] += 1;
|
|
|
+ // 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);
|
|
|
+ _afterTokenTransfer(from, to, tokenId, 1);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -416,39 +451,53 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @dev Hook that is called before any token transfer. This includes minting
|
|
|
- * and burning.
|
|
|
+ * @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 `tokenId` will be
|
|
|
- * transferred to `to`.
|
|
|
- * - When `from` is zero, `tokenId` will be minted for `to`.
|
|
|
- * - When `to` is zero, ``from``'s `tokenId` will be burned.
|
|
|
+ * - 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 tokenId
|
|
|
- ) internal virtual {}
|
|
|
+ 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 transfer of tokens. This includes
|
|
|
- * minting and burning.
|
|
|
+ * @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.
|
|
|
+ * - 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 tokenId
|
|
|
+ uint256 firstTokenId,
|
|
|
+ uint256 batchSize
|
|
|
) internal virtual {}
|
|
|
}
|