From 89bcf50f40eac269ebbb4368bee2baf590a239ac Mon Sep 17 00:00:00 2001 From: Andrei Kozlov Date: Tue, 12 Jul 2022 09:13:58 +0200 Subject: [PATCH] NFTOwnable contract --- src/contracts/access-control/NFTOwnable.sol | 100 ++++++++++++++ src/contracts/oz-common/IERC165.sol | 27 ++++ src/contracts/oz-common/IERC721.sol | 143 ++++++++++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 src/contracts/access-control/NFTOwnable.sol create mode 100644 src/contracts/oz-common/IERC165.sol create mode 100644 src/contracts/oz-common/IERC721.sol diff --git a/src/contracts/access-control/NFTOwnable.sol b/src/contracts/access-control/NFTOwnable.sol new file mode 100644 index 0000000..f2a4d49 --- /dev/null +++ b/src/contracts/access-control/NFTOwnable.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +// Inspired by OpenZeppelin (access/Ownable.sol) +// From commit https://github.com/OpenZeppelin/openzeppelin-contracts/commit/8b778fa20d6d76340c5fac1ed66c80273f05b95a + +pragma solidity ^0.8.0; + +import {Context} from '../oz-common/Context.sol'; +import {IERC721} from '../oz-common/IERC721.sol'; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner of some NFT) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _nftContract; + uint256 private _nftToOwn; + + event OwnershipTransferred( + address indexed oldNftContract, + address indexed newNftContract, + uint256 oldNftToOwn, + uint256 newNFTToOwn + ); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor(address nftContract, uint256 nftToOwn) { + _transferOwnership(nftContract, nftToOwn); + _checkOwner(); + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + _checkOwner(); + _; + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return IERC721(_nftContract).ownerOf(_nftToOwn); + } + + /** + * @dev Throws if the sender is not the owner. + */ + function _checkOwner() internal view virtual { + require(owner() == _msgSender(), 'NFTOwnable: caller is not the owner'); + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _transferOwnership(address(0), 0); + } + + /** + * @dev Transfers ownership of the contract to a new NFT (`newNFT`) located at a new NFT contract (newNftContract). + * Can only be called by the current owner. + */ + function transferOwnership(address newNftContract, uint256 newNFT) public virtual onlyOwner { + require(newNftContract != address(0), 'NFTOwnable: new owner NFT contract is the zero address'); + require( + IERC721(_nftContract).ownerOf(_nftToOwn) != address(0), + 'NFTOwnable: new NFT to own owner is the zero address' + ); + _transferOwnership(newNftContract, newNFT); + } + + /** + * @dev Transfers ownership of the contract to a new nft (`newNFTToOwn`) located at a new NFT contract (newNftContract). + * Internal function without access restriction. + */ + function _transferOwnership(address newNftContract, uint256 newNFTToOwn) internal virtual { + address oldNftContract = _nftContract; + uint256 oldNftToOwn = _nftToOwn; + + _nftContract = newNftContract; + _nftToOwn = newNFTToOwn; + + emit OwnershipTransferred(oldNftContract, newNftContract, oldNftToOwn, newNFTToOwn); + } +} diff --git a/src/contracts/oz-common/IERC165.sol b/src/contracts/oz-common/IERC165.sol new file mode 100644 index 0000000..e433352 --- /dev/null +++ b/src/contracts/oz-common/IERC165.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) + +pragma solidity ^0.8.0; + +// Latest commit https://github.com/OpenZeppelin/openzeppelin-contracts/commit/6bd6b76d1156e20e45d1016f355d154141c7e5b9 + +/** + * @dev Interface of the ERC165 standard, as defined in the + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others ({ERC165Checker}). + * + * For an implementation, see {ERC165}. + */ +interface IERC165 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} diff --git a/src/contracts/oz-common/IERC721.sol b/src/contracts/oz-common/IERC721.sol new file mode 100644 index 0000000..5c2d963 --- /dev/null +++ b/src/contracts/oz-common/IERC721.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/IERC721.sol) +// Latest commit https://github.com/OpenZeppelin/openzeppelin-contracts/commit/3dac7bbed7b4c0dbf504180c33e8ed8e350b93eb +pragma solidity ^0.8.0; + +import './IERC165.sol'; + +/** + * @dev Required interface of an ERC721 compliant contract. + */ +interface IERC721 is IERC165 { + /** + * @dev Emitted when `tokenId` token is transferred from `from` to `to`. + */ + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. + */ + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + */ + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /** + * @dev Returns the number of tokens in ``owner``'s account. + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @dev Returns the owner of the `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function ownerOf(uint256 tokenId) external view returns (address owner); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external; + + /** + * @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. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Transfers `tokenId` token from `from` to `to`. + * + * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Gives permission to `to` to transfer `tokenId` token to another account. + * The approval is cleared when the token is transferred. + * + * Only a single account can be approved at a time, so approving the zero address clears previous approvals. + * + * Requirements: + * + * - The caller must own the token or be an approved operator. + * - `tokenId` must exist. + * + * Emits an {Approval} event. + */ + function approve(address to, uint256 tokenId) external; + + /** + * @dev Approve or remove `operator` as an operator for the caller. + * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. + * + * Requirements: + * + * - The `operator` cannot be the caller. + * + * Emits an {ApprovalForAll} event. + */ + function setApprovalForAll(address operator, bool _approved) external; + + /** + * @dev Returns the account approved for `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function getApproved(uint256 tokenId) external view returns (address operator); + + /** + * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. + * + * See {setApprovalForAll} + */ + function isApprovedForAll(address owner, address operator) external view returns (bool); +}