Skip to content

Instantly share code, notes, and snippets.

@z0r0z
Created November 5, 2021 16:12
Show Gist options
  • Save z0r0z/b82027f530ab52ee0cd932a1ac70864a to your computer and use it in GitHub Desktop.
Save z0r0z/b82027f530ab52ee0cd932a1ac70864a to your computer and use it in GitHub Desktop.
derivatives yay
// 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 LexNFT {
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);
string public name;
string public symbol;
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;
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 immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(uint256 => uint256) public nonces;
mapping(address => uint256) public noncesForAll;
constructor(
string memory _name,
string memory _symbol
) {
name = _name;
symbol = _symbol;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = _calculateDomainSeparator();
}
function _calculateDomainSeparator() internal view 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 returns (bytes32 domainSeparator) {
domainSeparator = block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : _calculateDomainSeparator();
}
function supportsInterface(bytes4 interfaceId) public pure 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,uint,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");
}
}
function permit(
address spender,
uint256 tokenId,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "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(deadline >= block.timestamp, "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);
}
function _mint(address to, uint256 tokenId, string memory _tokenURI) internal {
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 {
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);
}
}
interface IERC20 {
function transfer(address to, uint256 amount) external;
function transferFrom(address from, address to, uint256 amount) external;
}
contract NFTWrapper is LexNFT {
IERC20 public underlying;
constructor(IERC20 underlying_) {
underlying = underlying_;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment