Skip to content

Instantly share code, notes, and snippets.

@jaredcohe
Created November 6, 2021 00:01
Show Gist options
  • Save jaredcohe/ffcf7028c88b63d8e79dcf1cec3cc33a to your computer and use it in GitHub Desktop.
Save jaredcohe/ffcf7028c88b63d8e79dcf1cec3cc33a to your computer and use it in GitHub Desktop.
// 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[] underlyingAssets;
uint[] amounts;
string details;
uint expiration; // unix
}
constructor(string memory name, string memory symbol) LexNFT(name, symbol) {
}
function makeDerivative(address address_, uint tokenId_, string memory tokenURI_, IERC20[] memory underlyingAssets_, string memory details_, uint[] memory amounts_, uint expiration_) external {
_mint(address_, tokenId_, tokenURI_);
Derivative memory _derivative = Derivative({
underlyingAssets: underlyingAssets_,
amounts: amounts_,
details: details_,
expiration: expiration_
});
derivatives[tokenId_] = _derivative;
uint countUnderlyingAssets = underlyingAssets_.length;
for (uint i=0; i<countUnderlyingAssets; i++) {
underlyingAssets_[i].transferFrom(msg.sender, address(this), amounts_[i]);
}
}
function withdraw(uint tokenId_) external {
_burn(tokenId_);
Derivative memory derivative = derivatives[tokenId_];
uint countUnderlyingAssets = derivative.underlyingAssets.length;
for (uint i=0; i<countUnderlyingAssets; i++) {
derivative.underlyingAssets[i].transfer(msg.sender, derivative.amounts[i]);
}
delete derivatives[tokenId_];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment