Skip to content

Instantly share code, notes, and snippets.

@z0r0z
Created December 12, 2021 08:45
Show Gist options
  • Select an option

  • Save z0r0z/ef2ea71578b5ebb96c1bdf9c7b9a0f7a to your computer and use it in GitHub Desktop.

Select an option

Save z0r0z/ef2ea71578b5ebb96c1bdf9c7b9a0f7a to your computer and use it in GitHub Desktop.
some bs for the intern
// 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