Created
December 12, 2021 08:45
-
-
Save z0r0z/ef2ea71578b5ebb96c1bdf9c7b9a0f7a to your computer and use it in GitHub Desktop.
some bs for the intern
This file contains hidden or 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: GPL-3.0-or-later | |
| pragma solidity >=0.8.0; | |
| /// @notice Modern and gas efficient ERC-721 + ERC-20/EIP-2612-like implementation. | |
| contract NFTclone { | |
| /*/////////////////////////////////////////////////////////////// | |
| EVENTS | |
| //////////////////////////////////////////////////////////////*/ | |
| event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); | |
| event Approval(address indexed owner, address indexed spender, uint256 indexed tokenId); | |
| event ApprovalForAll(address indexed owner, address indexed operator, bool approved); | |
| /*/////////////////////////////////////////////////////////////// | |
| METADATA STORAGE | |
| //////////////////////////////////////////////////////////////*/ | |
| string public name; | |
| string public symbol; | |
| string public baseURI; | |
| address nftOwner; | |
| uint256 ethPrice; | |
| /*/////////////////////////////////////////////////////////////// | |
| ERC-721 STORAGE | |
| //////////////////////////////////////////////////////////////*/ | |
| uint256 public totalSupply; | |
| mapping(address => uint256) public balanceOf; | |
| mapping(uint256 => address) public ownerOf; | |
| mapping(uint256 => string) public tokenURI; | |
| mapping(uint256 => address) public getApproved; | |
| mapping(address => mapping(address => bool)) public isApprovedForAll; | |
| /*/////////////////////////////////////////////////////////////// | |
| EIP-2612-LIKE STORAGE | |
| //////////////////////////////////////////////////////////////*/ | |
| bytes32 public constant PERMIT_TYPEHASH = | |
| keccak256('Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)'); | |
| bytes32 public constant PERMIT_ALL_TYPEHASH = | |
| keccak256('Permit(address owner,address spender,uint256 nonce,uint256 deadline)'); | |
| uint256 internal INITIAL_CHAIN_ID; | |
| bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; | |
| mapping(uint256 => uint256) public nonces; | |
| mapping(address => uint256) public noncesForAll; | |
| /*/////////////////////////////////////////////////////////////// | |
| CONSTRUCTOR | |
| //////////////////////////////////////////////////////////////*/ | |
| constructor( | |
| string memory _name, | |
| string memory _symbol, | |
| string memory _baseURI, | |
| address _nftOwner, | |
| uint256 _ethPrice | |
| ) { | |
| require(INITIAL_CHAIN_ID == 0, 'INITIALIZED'); | |
| name = _name; | |
| symbol = _symbol; | |
| baseURI = _baseURI; | |
| nftOwner = _nftOwner; | |
| ethPrice = _ethPrice; | |
| INITIAL_CHAIN_ID = block.chainid; | |
| INITIAL_DOMAIN_SEPARATOR = _computeDomainSeparator(); | |
| } | |
| /*/////////////////////////////////////////////////////////////// | |
| ERC-721 LOGIC | |
| //////////////////////////////////////////////////////////////*/ | |
| function supportsInterface(bytes4 interfaceId) public pure virtual returns (bool supported) { | |
| supported = interfaceId == 0x80ac58cd || interfaceId == 0x5b5e139f || interfaceId == 0x01ffc9a7; | |
| } | |
| function approve(address spender, uint256 tokenId) public virtual { | |
| address owner = ownerOf[tokenId]; | |
| require(msg.sender == owner || isApprovedForAll[owner][msg.sender], 'NOT_APPROVED'); | |
| getApproved[tokenId] = spender; | |
| emit Approval(owner, spender, tokenId); | |
| } | |
| function setApprovalForAll(address operator, bool approved) public virtual { | |
| isApprovedForAll[msg.sender][operator] = approved; | |
| emit ApprovalForAll(msg.sender, operator, approved); | |
| } | |
| function transfer(address to, uint256 tokenId) public virtual { | |
| require(msg.sender == ownerOf[tokenId], 'NOT_OWNER'); | |
| // this is safe because ownership is checked | |
| // against decrement, and sum of all user | |
| // balances can't exceed 'type(uint256).max' | |
| unchecked { | |
| balanceOf[msg.sender]--; | |
| balanceOf[to]++; | |
| } | |
| delete getApproved[tokenId]; | |
| ownerOf[tokenId] = to; | |
| emit Transfer(msg.sender, to, tokenId); | |
| } | |
| function transferFrom( | |
| address, | |
| address to, | |
| uint256 tokenId | |
| ) public virtual { | |
| address owner = ownerOf[tokenId]; | |
| require( | |
| msg.sender == owner | |
| || msg.sender == getApproved[tokenId] | |
| || isApprovedForAll[owner][msg.sender], | |
| 'NOT_APPROVED' | |
| ); | |
| // this is safe because ownership is checked | |
| // against decrement, and sum of all user | |
| // balances can't exceed 'type(uint256).max' | |
| unchecked { | |
| balanceOf[owner]--; | |
| balanceOf[to]++; | |
| } | |
| delete getApproved[tokenId]; | |
| ownerOf[tokenId] = to; | |
| emit Transfer(owner, to, tokenId); | |
| } | |
| function safeTransferFrom( | |
| address, | |
| address to, | |
| uint256 tokenId | |
| ) public virtual { | |
| safeTransferFrom(address(0), to, tokenId, ''); | |
| } | |
| function safeTransferFrom( | |
| address, | |
| address to, | |
| uint256 tokenId, | |
| bytes memory data | |
| ) public virtual { | |
| transferFrom(address(0), to, tokenId); | |
| if (to.code.length > 0) { | |
| // selector = `onERC721Received(address,address,uint256,bytes)` | |
| (, bytes memory returned) = to.staticcall(abi.encodeWithSelector(0x150b7a02, | |
| msg.sender, address(0), tokenId, data)); | |
| bytes4 selector = abi.decode(returned, (bytes4)); | |
| require(selector == 0x150b7a02, 'NOT_ERC721_RECEIVER'); | |
| } | |
| } | |
| /*/////////////////////////////////////////////////////////////// | |
| EIP-2612-LIKE LOGIC | |
| //////////////////////////////////////////////////////////////*/ | |
| function _computeDomainSeparator() internal view virtual returns (bytes32 domainSeparator) { | |
| domainSeparator = keccak256( | |
| abi.encode( | |
| keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), | |
| keccak256(bytes(name)), | |
| keccak256(bytes('1')), | |
| block.chainid, | |
| address(this) | |
| ) | |
| ); | |
| } | |
| function DOMAIN_SEPARATOR() public view virtual returns (bytes32 domainSeparator) { | |
| domainSeparator = block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : _computeDomainSeparator(); | |
| } | |
| function permit( | |
| address spender, | |
| uint256 tokenId, | |
| uint256 deadline, | |
| uint8 v, | |
| bytes32 r, | |
| bytes32 s | |
| ) public virtual { | |
| require(block.timestamp <= deadline, 'PERMIT_DEADLINE_EXPIRED'); | |
| address owner = ownerOf[tokenId]; | |
| // this is reasonably safe from overflow because incrementing `nonces` beyond | |
| // 'type(uint256).max' is exceedingly unlikely compared to optimization benefits | |
| unchecked { | |
| bytes32 digest = keccak256( | |
| abi.encodePacked( | |
| '\x19\x01', | |
| DOMAIN_SEPARATOR(), | |
| keccak256(abi.encode(PERMIT_TYPEHASH, spender, tokenId, nonces[tokenId]++, deadline)) | |
| ) | |
| ); | |
| address recoveredAddress = ecrecover(digest, v, r, s); | |
| require(recoveredAddress != address(0), 'INVALID_PERMIT_SIGNATURE'); | |
| require(recoveredAddress == owner || isApprovedForAll[owner][recoveredAddress], 'INVALID_SIGNER'); | |
| } | |
| getApproved[tokenId] = spender; | |
| emit Approval(owner, spender, tokenId); | |
| } | |
| function permitAll( | |
| address owner, | |
| address operator, | |
| uint256 deadline, | |
| uint8 v, | |
| bytes32 r, | |
| bytes32 s | |
| ) public virtual { | |
| require(block.timestamp <= deadline, 'PERMIT_DEADLINE_EXPIRED'); | |
| // this is reasonably safe from overflow because incrementing `nonces` beyond | |
| // 'type(uint256).max' is exceedingly unlikely compared to optimization benefits | |
| unchecked { | |
| bytes32 digest = keccak256( | |
| abi.encodePacked( | |
| '\x19\x01', | |
| DOMAIN_SEPARATOR(), | |
| keccak256(abi.encode(PERMIT_ALL_TYPEHASH, owner, operator, noncesForAll[owner]++, deadline)) | |
| ) | |
| ); | |
| address recoveredAddress = ecrecover(digest, v, r, s); | |
| require( | |
| (recoveredAddress != address(0) && recoveredAddress == owner) || isApprovedForAll[owner][recoveredAddress], | |
| 'INVALID_PERMIT_SIGNATURE' | |
| ); | |
| } | |
| isApprovedForAll[owner][operator] = true; | |
| emit ApprovalForAll(owner, operator, true); | |
| } | |
| /*/////////////////////////////////////////////////////////////// | |
| MINT/BURN LOGIC | |
| //////////////////////////////////////////////////////////////*/ | |
| function mint(address to, uint256 tokenId) external payable { | |
| require(msg.value == ethPrice, 'NOT_PRICE'); | |
| _mint( | |
| to, | |
| tokenId, | |
| baseURI | |
| ); | |
| (bool success, ) = nftOwner.call{value: msg.value}(''); | |
| require(success, 'NOT_PAYABLE'); | |
| } | |
| function burn(uint256 tokenId) external { | |
| require(msg.sender == ownerOf[tokenId], 'NOT_OWNER'); | |
| _burn(tokenId); | |
| } | |
| function _mint( | |
| address to, | |
| uint256 tokenId, | |
| string memory tokenURI_ | |
| ) internal virtual { | |
| require(ownerOf[tokenId] == address(0), 'ALREADY_MINTED'); | |
| // this is reasonably safe from overflow because incrementing `totalSupply` beyond | |
| // 'type(uint256).max' is exceedingly unlikely compared to optimization benefits, | |
| // and because the sum of all user balances can't exceed 'type(uint256).max' | |
| unchecked { | |
| totalSupply++; | |
| balanceOf[to]++; | |
| } | |
| ownerOf[tokenId] = to; | |
| tokenURI[tokenId] = tokenURI_; | |
| emit Transfer(address(0), to, tokenId); | |
| } | |
| function _burn(uint256 tokenId) internal virtual { | |
| address owner = ownerOf[tokenId]; | |
| require(ownerOf[tokenId] != address(0), 'NOT_MINTED'); | |
| // this is safe because a user won't ever | |
| // have a balance larger than `totalSupply` | |
| unchecked { | |
| totalSupply--; | |
| balanceOf[owner]--; | |
| } | |
| delete ownerOf[tokenId]; | |
| delete tokenURI[tokenId]; | |
| emit Transfer(owner, address(0), tokenId); | |
| } | |
| } | |
| /// @notice Helper utility for NFT 'safe' transfers. | |
| abstract contract NFThelper { | |
| function onERC721Received( | |
| address, | |
| address, | |
| uint256, | |
| bytes calldata | |
| ) external pure returns (bytes4 sig) { | |
| sig = 0x150b7a02; // 'onERC721Received(address,address,uint256,bytes)' | |
| } | |
| function onERC1155Received( | |
| address, | |
| address, | |
| uint256, | |
| uint256, | |
| bytes calldata | |
| ) external pure returns (bytes4 sig) { | |
| sig = 0xf23a6e61; // 'onERC1155Received(address,address,uint256,uint256,bytes)' | |
| } | |
| function onERC1155BatchReceived( | |
| address, | |
| address, | |
| uint256[] calldata, | |
| uint256[] calldata, | |
| bytes calldata | |
| ) external pure returns (bytes4 sig) { | |
| sig = 0xbc197c81; // 'onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)' | |
| } | |
| } | |
| interface IERC721 { | |
| function name() external returns (string memory); | |
| function symbol() external returns (string memory); | |
| function tokenURI(uint256 tokenId) external view returns (string memory); | |
| function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; | |
| } | |
| /// @notice NFT cloner. | |
| contract NFTcloner { | |
| function cloneNFT( | |
| IERC721 NFT, | |
| uint256 tokenId, | |
| uint256 _ethPrice | |
| ) external returns (NFTclone clone) { | |
| clone = new NFTclone(NFT.name(), NFT.symbol(), NFT.tokenURI(tokenId), msg.sender, _ethPrice); | |
| NFT.safeTransferFrom(msg.sender, address(clone), tokenId, ''); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment