// SPDX-License-Identifier: GPL-3.0-only // Modified from OpenZeppelin's v4.9.0 contracts pragma solidity ^0.8.26; import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; import "lens-modules/contracts/core/interfaces/ITokenURIProvider.sol"; import "lens-modules/contracts/core/interfaces/IERC4906Events.sol"; import {Errors} from "lens-modules/contracts/core/types/Errors.sol"; import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; abstract contract LensERC721 is IERC721Metadata, ERC165 { using AddressUpgradeable for address; event Lens_ERC721_TokenURIProviderSet(address indexed tokenURIProvider); event Lens_ERC721_Initialized(string name, string symbol); struct ERC721Storage { // Token name string name; // Token symbol string symbol; // Token URI provider ITokenURIProvider tokenURIProvider; // Mapping from token ID to owner address mapping(uint256 => address) owners; // Mapping owner address to token count mapping(address => uint256) balances; // Mapping from token ID to approved address mapping(uint256 => address) tokenApprovals; // Mapping from owner to operator approvals mapping(address => mapping(address => bool)) operatorApprovals; } /// @custom:keccak lens.storage.ERC721 bytes32 constant STORAGE__ERC721 = 0x9773440c5f3d31ef6a1be068fec8ef97f4aa1ba801bb599e82d4fd5382955955; function $erc721Storage() internal pure returns (ERC721Storage storage _storage) { assembly { _storage.slot := STORAGE__ERC721 } } function _initialize(string memory nftName, string memory nftSymbol, ITokenURIProvider tokenURIProvider) internal { $erc721Storage().name = nftName; $erc721Storage().symbol = nftSymbol; $erc721Storage().tokenURIProvider = tokenURIProvider; emit Lens_ERC721_Initialized(nftName, nftSymbol); emit Lens_ERC721_TokenURIProviderSet(address(tokenURIProvider)); } /** * @dev See {IERC721-balanceOf}. */ function balanceOf(address owner) public view virtual override returns (uint256) { require(owner != address(0), Errors.InvalidParameter()); return $erc721Storage().balances[owner]; } /** * @dev See {IERC721-ownerOf}. */ function ownerOf(uint256 tokenId) public view virtual override returns (address) { address owner = _ownerOf(tokenId); require(owner != address(0), Errors.DoesNotExist()); return owner; } /** * @dev See {IERC721Metadata-name}. */ function name() public view virtual override returns (string memory) { return $erc721Storage().name; } /** * @dev See {IERC721Metadata-symbol}. */ function symbol() public view virtual override returns (string memory) { return $erc721Storage().symbol; } /** * @dev See {IERC721Metadata-tokenURI}. */ function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { _requireMinted(tokenId); return $erc721Storage().tokenURIProvider.tokenURI(tokenId); } function setTokenURIProvider(ITokenURIProvider tokenURIProvider) external virtual { _beforeTokenURIProviderSet(tokenURIProvider); // solc-ignore-next-line unreachable $erc721Storage().tokenURIProvider = tokenURIProvider; emit IERC4906Events.BatchMetadataUpdate(0, type(uint256).max); emit Lens_ERC721_TokenURIProviderSet(address(tokenURIProvider)); } /** * @dev See {IERC721-approve}. */ function approve(address to, uint256 tokenId) public virtual override { address owner = LensERC721.ownerOf(tokenId); require(to != owner, Errors.InvalidParameter()); require(msg.sender == owner || isApprovedForAll(owner, msg.sender), Errors.InvalidMsgSender()); _approve(to, tokenId); } /** * @dev See {IERC721-getApproved}. */ function getApproved(uint256 tokenId) public view virtual override returns (address) { _requireMinted(tokenId); return $erc721Storage().tokenApprovals[tokenId]; } /** * @dev See {IERC721-setApprovalForAll}. */ function setApprovalForAll(address operator, bool approved) public virtual override { _setApprovalForAll(msg.sender, operator, approved); } /** * @dev See {IERC721-isApprovedForAll}. */ function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { return $erc721Storage().operatorApprovals[owner][operator]; } /** * @dev See {IERC721-transferFrom}. */ function transferFrom(address from, address to, uint256 tokenId) public virtual override { //solhint-disable-next-line max-line-length require(_isApprovedOrOwner(msg.sender, tokenId), Errors.InvalidMsgSender()); _transfer(from, to, tokenId); } /** * @dev See {IERC721-safeTransferFrom}. */ function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override { safeTransferFrom(from, to, tokenId, ""); } /** * @dev See {IERC721-safeTransferFrom}. */ function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override { require(_isApprovedOrOwner(msg.sender, tokenId), Errors.InvalidMsgSender()); _safeTransfer(from, to, tokenId, data); } /** * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients * are aware of the ERC721 protocol to prevent tokens from being forever locked. * * `data` is additional data, it has no specified format and it is sent in call to `to`. * * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. * implement alternative mechanisms to perform token transfer, such as signature-based. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual { _transfer(from, to, tokenId); require(_checkOnERC721Received(from, to, tokenId, data), Errors.UnexpectedContractImpl()); } /** * @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 $erc721Storage().owners[tokenId]; } /** * @dev Returns whether `tokenId` exists. * * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. * * Tokens start existing when they are minted (`_mint`), * and stop existing when they are burned (`_burn`). */ function _exists(uint256 tokenId) internal view virtual returns (bool) { return _ownerOf(tokenId) != address(0); } /** * @dev Returns whether `spender` is allowed to manage `tokenId`. * * Requirements: * * - `tokenId` must exist. */ function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { address owner = LensERC721.ownerOf(tokenId); return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender); } /** * @dev Safely mints `tokenId` and transfers it to `to`. * * Requirements: * * - `tokenId` must not exist. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function _safeMint(address to, uint256 tokenId) internal virtual { _safeMint(to, tokenId, ""); } /** * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. */ function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual { _mint(to, tokenId); require(_checkOnERC721Received(address(0), to, tokenId, data), Errors.UnexpectedContractImpl()); } /** * @dev Mints `tokenId` and transfers it to `to`. * * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible * * Requirements: * * - `tokenId` must not exist. * - `to` cannot be the zero address. * * Emits a {Transfer} event. */ function _mint(address to, uint256 tokenId) internal virtual { require(to != address(0), Errors.InvalidParameter()); require(!_exists(tokenId), Errors.AlreadyExists()); _beforeTokenTransfer(address(0), to, tokenId); // Check that tokenId was not minted by `_beforeTokenTransfer` hook require(!_exists(tokenId), Errors.AlreadyExists()); 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. $erc721Storage().balances[to] += 1; } $erc721Storage().owners[tokenId] = to; emit Transfer(address(0), to, tokenId); _afterTokenTransfer(address(0), to, tokenId); } /** * @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: * * - `tokenId` must exist. * * Emits a {Transfer} event. */ function _burn(uint256 tokenId) internal virtual { address owner = LensERC721.ownerOf(tokenId); _beforeTokenTransfer(owner, address(0), tokenId); // Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook owner = LensERC721.ownerOf(tokenId); // Clear approvals delete $erc721Storage().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. $erc721Storage().balances[owner] -= 1; } delete $erc721Storage().owners[tokenId]; emit Transfer(owner, address(0), tokenId); _afterTokenTransfer(owner, address(0), tokenId); } /** * @dev Transfers `tokenId` from `from` to `to`. * As opposed to {transferFrom}, this imposes no restrictions on msg.sender. * * Requirements: * * - `to` cannot be the zero address. * - `tokenId` token must be owned by `from`. * * Emits a {Transfer} event. */ function _transfer(address from, address to, uint256 tokenId) internal virtual { require(LensERC721.ownerOf(tokenId) == from, Errors.InvalidParameter()); require(to != address(0), Errors.InvalidParameter()); _beforeTokenTransfer(from, to, tokenId); // Check that tokenId was not transferred by `_beforeTokenTransfer` hook require(LensERC721.ownerOf(tokenId) == from, Errors.InvalidParameter()); // Clear approvals from the previous owner delete $erc721Storage().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. $erc721Storage().balances[from] -= 1; $erc721Storage().balances[to] += 1; } $erc721Storage().owners[tokenId] = to; emit Transfer(from, to, tokenId); _afterTokenTransfer(from, to, tokenId); } /** * @dev Approve `to` to operate on `tokenId` * * Emits an {Approval} event. */ function _approve(address to, uint256 tokenId) internal virtual { $erc721Storage().tokenApprovals[tokenId] = to; emit Approval(LensERC721.ownerOf(tokenId), to, tokenId); } /** * @dev Approve `operator` to operate on all of `owner` tokens * * Emits an {ApprovalForAll} event. */ function _setApprovalForAll(address owner, address operator, bool approved) internal virtual { require(owner != operator, Errors.InvalidParameter()); $erc721Storage().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), Errors.DoesNotExist()); } /** * @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 IERC721ReceiverUpgradeable(to).onERC721Received(msg.sender, from, tokenId, data) returns (bytes4 retval) { return retval == IERC721ReceiverUpgradeable.onERC721Received.selector; } catch (bytes memory reason) { if (reason.length == 0) { revert Errors.UnexpectedContractImpl(); } else { /// @solidity memory-safe-assembly assembly { revert(add(32, reason), mload(reason)) } } } } else { return true; } } function _beforeTokenURIProviderSet(ITokenURIProvider tokenURIProvider) internal virtual {} /** * @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. * * 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 {} /** * @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. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _afterTokenTransfer(address from, address to, uint256 tokenId) internal virtual {} /** * @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override. * * WARNING: Anyone calling this MUST ensure that the balances remain consistent with the ownership. The invariant * being that for any address `a` the value returned by `balanceOf(a)` must be equal to the number of tokens such * that `ownerOf(tokenId)` is `a`. */ // solhint-disable-next-line func-name-mixedcase function __unsafe_increaseBalance(address account, uint256 amount) internal { $erc721Storage().balances[account] += amount; } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { return interfaceId == type(IERC721).interfaceId || interfaceId == type(IERC721Metadata).interfaceId || super.supportsInterface(interfaceId); } }