Created
November 5, 2021 17:09
-
-
Save z0r0z/a714b383f38cbb7b564c887a37d611ff to your computer and use it in GitHub Desktop.
NFTWrapper.sol
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: 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 { | |
mapping(uint => Derivative) derivatives; | |
struct Derivative { | |
IERC20 underlying; // TO DO BASKET | |
string details; | |
uint amount; | |
uint expiration; // unix | |
} | |
constructor(string memory name, string memory symbol) LexNFT(name, symbol) { | |
} | |
// TO DO: make underlying BASKET | |
function makeDerivative(address address_, uint256 tokenId_, string memory tokenURI_, IERC20 ierc20_, string memory details_, uint amount_, uint expiration_) external { | |
_mint(address_, tokenId_, tokenURI_); | |
Derivative memory _derivative = Derivative({ | |
underlying: ierc20_, | |
details: details_, | |
amount: amount_, | |
expiration: expiration_ | |
}); | |
derivatives[tokenId_] = _derivative; | |
ierc20_.transferFrom(msg.sender, address(this), amount_); | |
} | |
// TO DO: withdraw BASKET | |
function withdraw(uint tokenId_) external { | |
_burn(tokenId_); | |
Derivative memory derivative = derivatives[tokenId_]; | |
// fix me | |
transfer(msg.sender, derivative.amount); | |
delete derivatives[tokenId_]; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment