Last active
February 1, 2022 14:36
-
-
Save z0r0z/c0184b28c1ddb25eca31c6782f803b41 to your computer and use it in GitHub Desktop.
Based NFT implementation.
This file contains 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: AGPL-3.0-only | |
pragma solidity >=0.8.0; | |
/// @notice Based NFT implementation. | |
abstract contract NFT { | |
/*/////////////////////////////////////////////////////////////// | |
EVENTS | |
//////////////////////////////////////////////////////////////*/ | |
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); | |
event Approval(address indexed owner, address indexed spender, uint256 indexed tokenId); | |
/*/////////////////////////////////////////////////////////////// | |
METADATA STORAGE | |
//////////////////////////////////////////////////////////////*/ | |
string public name; | |
string public symbol; | |
string public baseURI; | |
/*/////////////////////////////////////////////////////////////// | |
ERC-721 STORAGE | |
//////////////////////////////////////////////////////////////*/ | |
uint256 public totalSupply; | |
mapping(address => uint256) public balanceOf; | |
mapping(uint256 => address) public ownerOf; | |
mapping(uint256 => address) public getApproved; | |
/*/////////////////////////////////////////////////////////////// | |
EIP-2612-LIKE STORAGE | |
//////////////////////////////////////////////////////////////*/ | |
bytes32 public constant PERMIT_TYPEHASH = | |
keccak256("Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)"); | |
uint256 internal immutable INITIAL_CHAIN_ID; | |
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; | |
mapping(uint256 => uint256) public nonces; | |
/*/////////////////////////////////////////////////////////////// | |
CONSTRUCTOR | |
//////////////////////////////////////////////////////////////*/ | |
constructor( | |
string memory _name, | |
string memory _symbol, | |
string memory _baseURI | |
) { | |
name = _name; | |
symbol = _symbol; | |
baseURI = _baseURI; | |
INITIAL_CHAIN_ID = block.chainid; | |
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); | |
} | |
/*/////////////////////////////////////////////////////////////// | |
ERC-20-LIKE LOGIC | |
//////////////////////////////////////////////////////////////*/ | |
function transfer(address to, uint256 tokenId) public virtual returns (bool success) { | |
require(msg.sender == ownerOf[tokenId], "NOT_OWNER"); | |
// Cannot overflow because because ownership is checked | |
// against decrement, and sum of all user | |
// balances can't exceed the max uint256 value. | |
unchecked { | |
balanceOf[msg.sender]--; | |
balanceOf[to]++; | |
} | |
delete getApproved[tokenId]; | |
ownerOf[tokenId] = to; | |
emit Transfer(msg.sender, to, tokenId); | |
success = true; | |
} | |
/*/////////////////////////////////////////////////////////////// | |
NFT LOGIC | |
//////////////////////////////////////////////////////////////*/ | |
function approve(address spender, uint256 tokenId) public virtual { | |
address owner = ownerOf[tokenId]; | |
require(msg.sender == owner, "NOT_OWNER"); | |
getApproved[tokenId] = spender; | |
emit Approval(owner, spender, tokenId); | |
} | |
function transferFrom( | |
address from, | |
address to, | |
uint256 tokenId | |
) public virtual { | |
require(from == ownerOf[tokenId], 'NOT_OWNER'); | |
require( | |
msg.sender == from | |
|| msg.sender == getApproved[tokenId], | |
'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[from]--; | |
balanceOf[to]++; | |
} | |
delete getApproved[tokenId]; | |
ownerOf[tokenId] = to; | |
emit Transfer(from, to, tokenId); | |
} | |
function tokenURI(uint256 tokenId) public view virtual returns (string memory) { | |
return string(abi.encodePacked(baseURI, tokenId)); | |
} | |
/*/////////////////////////////////////////////////////////////// | |
EIP-2612-LIKE LOGIC | |
//////////////////////////////////////////////////////////////*/ | |
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]; | |
// Unchecked because the only math done is incrementing | |
// the owner's nonce which cannot realistically overflow. | |
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, "INVALID_SIGNER"); | |
} | |
getApproved[tokenId] = spender; | |
emit Approval(owner, spender, tokenId); | |
} | |
function DOMAIN_SEPARATOR() public view virtual returns (bytes32 domainSeparator) { | |
domainSeparator = block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); | |
} | |
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) | |
) | |
); | |
} | |
/*/////////////////////////////////////////////////////////////// | |
MINT/BURN LOGIC | |
//////////////////////////////////////////////////////////////*/ | |
function _mint( | |
address to, | |
uint256 tokenId | |
) internal virtual { | |
require(ownerOf[tokenId] == address(0), "ALREADY_MINTED"); | |
// Cannot realistically overflow from incrementing total supply beyond | |
// the max uint256 value, and because the sum of all user balances | |
// can't exceed the max uint256 value. | |
unchecked { | |
totalSupply++; | |
balanceOf[to]++; | |
} | |
ownerOf[tokenId] = to; | |
emit Transfer(address(0), to, tokenId); | |
} | |
function _burn(uint256 tokenId) internal virtual { | |
address owner = ownerOf[tokenId]; | |
require(ownerOf[tokenId] != address(0), "NOT_MINTED"); | |
// Cannot underflow because a user's balance | |
// will never be larger than the total supply. | |
unchecked { | |
totalSupply--; | |
balanceOf[owner]--; | |
} | |
delete ownerOf[tokenId]; | |
emit Transfer(owner, address(0), tokenId); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment