/// @title ERC721
/// @notice SPDX-License-Identifier: MIT
/// @author asnared <https://github.com/abigger87>
/// @author kadenzipfel <https://github.com/kadenzipfel>
/// @notice Modern and heavily gas golfed ERC-721 implementation
/// @notice Adapted from Solmate https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol
// Imports
#include "../utils/CommonErrors.huff"
#include "../auth/NonPayable.huff"
#include "../data-structures/Hashmap.huff"
// Interface
#define function name() nonpayable returns (string)
#define function symbol() nonpayable returns (string)
#define function tokenURI(uint256) nonpayable returns (string)
#define function mint(address, uint256) payable returns ()
#define function burn(uint256) nonpayable returns ()
#define function transfer(address,uint256) nonpayable returns ()
#define function transferFrom(address,address,uint256) nonpayable returns ()
#define function safeTransferFrom(address,address,uint256) nonpayable returns ()
#define function safeTransferFrom(address,address,uint256,bytes) nonpayable returns ()
#define function approve(address,uint256) nonpayable returns ()
#define function setApprovalForAll(address,bool) nonpayable returns ()
#define function getApproved(uint256) view returns (address)
#define function isApprovedForAll(address,address) view returns (bool)
#define function ownerOf(uint256) view returns (address)
#define function balanceOf(address) view returns (uint256)
#define function supportsInterface(bytes4) view returns (bool)
// Events
#define event Transfer(address,address,uint256)
#define event Approval(address,address,uint256)
#define event ApprovalForAll(address,address,bool)
// Storage Slots
#define constant OWNER_LOCATION = FREE_STORAGE_POINTER()
#define constant BALANCE_LOCATION = FREE_STORAGE_POINTER()
#define constant SINGLE_APPROVAL_LOCATION = FREE_STORAGE_POINTER()
// Metadata
// META_NAME = "Token"
#define constant META_NAME = 0x546f6b656e000000000000000000000000000000000000000000000000000000
#define constant META_NAME_LENGTH = 0x05
// META_SYMBOL = "TKN"
#define constant META_SYMBOL = 0x544B4E0000000000000000000000000000000000000000000000000000000000
#define constant META_SYMBOL_LENGTH = 0x03
/// >>>>>>>>>>>>>>>>>>>>> VIEW FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< ///
/// @notice Name
/// @notice Returns the token name string
#define macro NAME() = takes (0) returns (0) {
NON_PAYABLE() // []
0x20 0x00 mstore // []
[META_NAME_LENGTH] 0x20 mstore // []
[META_NAME] 0x40 mstore // []
0x60 0x00 return // []
}
/// @notice Symbol
/// @notice Returns the symbol of the token
#define macro SYMBOL() = takes (0) returns (0) {
NON_PAYABLE() // []
0x20 0x00 mstore // []
[META_SYMBOL_LENGTH] 0x20 mstore // []
[META_SYMBOL] 0x40 mstore // []
0x60 0x00 return // []
}
/// @notice Balance Of
/// @notice Returns the balance of the given address
#define macro BALANCE_OF() = takes (0) returns (0) {
NON_PAYABLE() // []
0x04 calldataload // [account]
[BALANCE_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance]
0x00 mstore // []
0x20 0x00 return // []
}
/// @notice Owner Of
/// @notice Returns the owner of the given token id
#define macro OWNER_OF() = takes (0) returns (0) {
0x04 calldataload // [tokenId]
[OWNER_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [owner]
0x00 mstore // []
0x20 0x00 return // []
}
/// @notice Is Approved For All
/// @notice Returns whether the given operator is approved for all tokens of the given owner
#define macro IS_APPROVED_FOR_ALL() = takes (0) returns (0) {
0x24 calldataload // [to]
0x04 calldataload // [from, to]
LOAD_ELEMENT_FROM_KEYS(0x00) // [value]
0x00 mstore // []
0x20 0x00 return // []
}
/// @notice Get Approved
/// @notice Returns the approved address for the given token id
#define macro GET_APPROVED() = takes (0) returns (0) {
0x04 calldataload // [tokenId]
[SINGLE_APPROVAL_LOCATION] // [approval_slot, tokenId]
LOAD_ELEMENT_FROM_KEYS(0x00) // [spender]
0x00 mstore // []
0x20 0x00 return // []
}
/// @notice Token URI
#define macro TOKEN_URI() = takes (0) returns (0) {
0x20 0x00 mstore
0x00 0x20 mstore
0x40 0x00 return
}
/// @notice Checks if the given interface is supported
#define macro SUPPORTS_INTERFACE() = takes (0) returns (0) {
// grab interfaceId
0x04 calldataload // [interfaceId]
0xe0 shr // [right_aligned_interfaceId]
// Check if erc165 interfaceId
dup1 // [interfaceId, interfaceId]
0x01ffc9a7 eq // [is_erc165, interfaceId]
is_interface jumpi
// Check if erc721 interfaceId
dup1 // [interfaceId, interfaceId]
0x80ac58cd eq // [is_erc721, interfaceId]
is_interface jumpi
// Check if erc721Metadata interfaceId
0x5b5e139f eq // [is_erc721Metadata]
is_interface jumpi
// Return false (0x00)
0x00 mstore // []
0x20 0x00 return // []
// Return true (0x01)
is_interface:
pop // []
0x01 0x00 mstore // []
0x20 0x00 return // []
}
/// >>>>>>>>>>>>>>>>>>>>> INTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< ///
/// @notice Mint
/// @notice Mints a new token
/// @dev The Mint function is payable
#define macro _MINT() = takes (2) returns (0) {
// Input stack: // [to, tokenId]
// Output stack: // []
// Check that the recipient is valid
dup1 iszero invalid_recipient jumpi // [to, tokenId]
// Create the minting params
0x00 dup3 // [tokenId, from (0x00), to, tokenId]
// Check token ownership
[OWNER_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [owner, from (0x00), to, tokenId]
iszero iszero unauthorized jumpi
// Give tokens to the recipient.
TRANSFER_GIVE_TO() // [from (0x00), to, tokenId]
// Emit the transfer event.
__EVENT_HASH(Transfer) // [sig, from (0x00), to, tokenId]
0x00 0x00 log4 // []
// Continue Executing
cont jump
invalid_recipient:
INVALID_RECIPIENT(0x00)
unauthorized:
ALREADY_MINTED(0x00)
cont:
}
/// @notice Burn
/// @notice Burns the token with the given id
#define macro _BURN() = takes (1) returns (0) {
// Input stack: // [tokenId]
NON_PAYABLE() // [tokenId]
dup1 // [tokenId, tokenId]
[OWNER_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [owner, tokenId]
// Check that the recipient is valid
dup1 iszero // [owner == 0, owner, tokenId]
not_minted jumpi // [owner, tokenId]
// Create the burning params
0x00 swap1 // [owner, to (0x00), tokenId]
// Reduce the balance of owner by 1
0x01 dup2 // [owner, 1, owner, to, tokenId]
[BALANCE_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance, 1, owner, to, tokenId]
sub dup2 // [owner, balance-1, owner, to, tokenId]
[BALANCE_LOCATION]
STORE_ELEMENT_FROM_KEYS(0x00) // [owner, to, tokenId]
// Set the owner of the token to 0x00
0x00 dup4 [OWNER_LOCATION] // [slot, owner, 0x00, owner, to, tokenId]
STORE_ELEMENT_FROM_KEYS(0x00) // [owner, to, tokenId]
// Set the approval of the token to 0x00 for the owner
0x00 dup4 [SINGLE_APPROVAL_LOCATION] // [slot, owner, 0x00, owner, to, tokenId]
STORE_ELEMENT_FROM_KEYS(0x00) // [owner, to, tokenId]
// Emit the transfer event.
__EVENT_HASH(Transfer) // [sig, owner, to (0x00), tokenId]
0x00 0x00 // [0, 0, sig, owner, to (0x00), tokenId]
log4 // []
// Continue Executing
cont jump
not_minted:
NOT_MINTED(0x00)
cont:
}
/// >>>>>>>>>>>>>>>>>>>>> EXTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< ///
/// @notice Approve
/// @notice Approves a spender for a specific token
#define macro APPROVE() = takes (0) returns (0) {
// Load the token owner
0x24 calldataload dup1 // [tokenId, tokenId]
[OWNER_LOCATION]
LOAD_ELEMENT_FROM_KEYS(0x00) // [owner, tokenId]
dup1 caller eq // [is_sender_owner, owner, tokenId]
// Check if approved for all
caller dup3 // [owner, msg.sender, is_sender_owner, owner, tokenId]
LOAD_ELEMENT_FROM_KEYS(0x00) // [is_approved_for_all, is_sender_owner, owner, tokenId]]
or cont jumpi // [owner, tokenId]
not_authorized jump
cont:
// Store approval
0x04 calldataload dup1 dup4 // [tokenId, spender, spender, owner, tokenId]
[SINGLE_APPROVAL_LOCATION]
STORE_ELEMENT_FROM_KEYS(0x00) // [spender, owner, tokenId]
swap1 // [owner, spender, tokenId]
// Emit the approval event
__EVENT_HASH(Approval) // [sig, owner, spender, tokenId]
0x00 0x00 log4 // []
stop
not_authorized:
UNAUTHORIZED(0x00)
}
/// @notice Set Approval For All
/// @notice Sets an operator as approved for all tokens of the caller
#define macro SET_APPROVAL_FOR_ALL() = takes (0) returns (0) {
// Store the operator as approved for all
0x24 calldataload // [approved]
0x04 calldataload // [operator, approved]
caller // [msg.sender, operator, approved]
STORE_ELEMENT_FROM_KEYS(0x00) // []
// Emit the ApprovalForAll event
0x24 calldataload // [approved]
0x04 calldataload // [operator, approved]
caller // [msg.sender, operator, approved]
__EVENT_HASH(ApprovalForAll) // [sig, owner, operator]
0x00 0x00 // [0, 32, sig, owner, operator]
log4 // []
// Stop execution
stop
}
/// @notice Transfer From
/// @notice Transfers a token from one address to another
#define macro TRANSFER_FROM() = takes (0) returns (0) {
// Setup the stack for the transfer function.
0x44 calldataload // [tokenId]
0x24 calldataload // [to, tokenId]
0x04 calldataload // [from, to, tokenId]
// Accounting Logic
TRANSFER_TAKE_FROM() // [from, to, tokenId]
TRANSFER_GIVE_TO() // [from, to, tokenId]
// Emit the transfer event
__EVENT_HASH(Transfer) // [sig,from, to, tokenId]
0x20 0x00 log4 // []
// Stop execution
stop
}
/// @notice Safe Transfer From
#define macro SAFE_TRANSFER_FROM() = takes (0) returns (0) {
// Setup the stack for the transfer function.
0x44 calldataload // [tokenId]
0x24 calldataload // [to, tokenId]
0x04 calldataload // [from, to, tokenId]
TRANSFER_TAKE_FROM() // [from, to, tokenId]
TRANSFER_GIVE_TO() // [from, to, tokenId]
// Emit the transfer event
__EVENT_HASH(Transfer) // [sig, from, to, tokenId]
0x00 0x00 log4 // []
// Make sure we can transfer to the recipient
0x24 calldataload // [to]
dup1 extcodesize // [to.code.length, to]
iszero safe jumpi // [to]
// onERC721Received Selector
0x150b7a02 dup1 // [onERC721Received, onERC721Received, to]
0xE0 shl // [onERC721Received_shifted, onERC721Received, to]
// Store the left-shifted selector for call
0x20 mstore // [onERC721Received, to]
// Store the msg.sender as the first arg
caller 0x24 mstore // [onERC721Received, to]
// Store from as the second arg
0x04 calldataload // [from, onERC721Received, to]
0x44 mstore // [onERC721Received, to]
// Id is the third arg
0x44 calldataload // [tokenId, onERC721Received, to]
0x64 mstore // [onERC721Received, to]
// Blank bytes array as 4th arg (no data)
0x80 0x84 mstore
0x00 0xA4 mstore
// Call address(to).onERC721Received(msg.sender, from, tokenId, "")
0x20 // [retSize, onERC721Received, to]
0x00 // [retOffset, retSize, onERC721Received, to]
0xA4 // [argSize, retOffset, retSize, onERC721Received, to]
dup3 // [argOffset, argSize, retOffset, retSize, onERC721Received, to]
dup3 // [value, argOffset, argSize, retOffset, retSize, onERC721Received, to]
dup7 // [to, value, argOffset, argSize, retOffset, retSize, onERC721Received, to]
gas // [gas, to, value, argOffset, argSize, retOffset, retSize, onERC721Received, to]
call // [success, onERC721Received, to]
// Revert if call isn't successful
cont jumpi // [onERC721Received, to]
0x00 dup1 revert
cont:
// Compare the return data to the onERC721Received selector
0x00 mload 0xE0 shr // [response, onERC721Received, to]
eq safe jumpi // [to]
// Revert if the return data is not accepted
UNSAFE_RECIPIENT(0x00)
// Stop execution if safe
safe:
stop
}
#define macro SAFE_TRANSFER_FROM_WITH_DATA() = takes (0) returns (0) {
// Setup the stack for the transfer function.
0x44 calldataload // [tokenId]
0x24 calldataload // [to, tokenId]
0x04 calldataload // [from, to, tokenId]
TRANSFER_TAKE_FROM() // [from, to, tokenId]
TRANSFER_GIVE_TO() // [from, to, tokenId]
// Emit the transfer event.
__EVENT_HASH(Transfer) // [sig, from, to, tokenId]
0x00 0x00 log4 // []
// Make sure we can transfer to the recipient
0x24 calldataload // [to]
dup1 extcodesize // [to.code.length, to]
iszero safe jumpi // [to]
// onERC721Received Selector
0x150b7a02 dup1 // [onERC721Received, onERC721Received, to]
0xE0 shl // [onERC721Received_shifted, onERC721Received, to]
// Store the left-shifted selector for call
0x20 mstore // [onERC721Received, to]
// Store the msg.sender as the first arg
caller 0x24 mstore // [onERC721Received, to]
// Store from as the second arg
0x04 calldataload // [from, onERC721Received, to]
0x44 mstore // [onERC721Received, to]
// Id is the third arg
0x44 calldataload // [tokenId, onERC721Received, to]
0x64 mstore // [onERC721Received, to]
0x84 calldataload // [len(data), onERC721Received, to]
0x05 shl // [len(data) * 0x20, onERC721Received, to]
0x40 add // [len(data) * 0x20 + 0x40, onERC721Received, to]
dup1 // [len(data) * 0x20 + 0x40, len(data) * 0x20 + 0x40, onERC721received, to]
0x64 // [0x64, len(data) * 0x20 + 0x40, len(data) * 0x20 + 0x40, onERC721received, to]
0x84 // [0x20, 0x64, len(data) * 0x20 + 0x40, len(data) * 0x20 + 0x40, onERC721received, to]
calldatacopy // [len(bytes), onERC721received, to]
// Call address(to).onERC721Received(msg.sender, from, tokenId, bytes)
0x20 // [retSize, len(bytes), onERC721Received, to]
0x00 // [retOffset, retSize, len(bytes), onERC721Received, to]
swap1 swap2 // [len(bytes), retOffset, retSize, onERC721Received, to]
0x64 add // [argSize, retOffset, retSize, onERC721Received, to]
dup3 // [argOffset, argSize, retOffset, retSize, len(bytes), onERC721Received, to]
dup3 // [value, argOffset, argSize, retOffset, retSize, len(bytes), onERC721Received, to]
dup7 // [to, value, argOffset, argSize, retOffset, retSize, len(bytes), onERC721Received, to]
gas // [gas, to, value, argOffset, argSize, retOffset, retSize, len(bytes), onERC721Received, to]
call // [success, len(bytes), onERC721Received, to]
// Revert if call isn't successful
cont jumpi // [len(bytes), onERC721Received, to]
0x00 dup1 revert
cont:
// Compare the return data to the onERC721Received selector
0x00 mload 0xE0 shr // [response, onERC721Received, to]
eq safe jumpi // [to]
// Revert if the return data is not accepted
UNSAFE_RECIPIENT(0x00)
// Stop execution if safe
safe:
stop
}
/// >>>>>>>>>>>>>>>>>>>>> INTERNAL HELPERS <<<<<<<<<<<<<<<<<<<<<< ///
/// @notice Internal Macro to update Transfer from accounting
#define macro TRANSFER_TAKE_FROM() = takes (3) returns (3) {
// Input stack: [from, to, tokenId]
// If from !== ownerOf[tokenId] revert with "WRONG_FROM"
dup1 dup4 // [tokenId, from, from, to, tokenId]
[OWNER_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [owner, from, from, to, tokenId]
eq cont jumpi // [from, to, tokenId]
WRONG_FROM(0x00)
cont:
// If to === address(0) revert with "INVALID_RECIPIENT"
dup2 iszero iszero continue jumpi // [from, to, tokenId]
INVALID_RECIPIENT(0x00)
continue:
// Check if msg.sender == from
dup1 caller eq // [msg.sender == from, from, to, tokenId]
is_authorized jumpi // [from, to, tokenId]
// Check if approved for all
caller dup2 // [from, msg.sender, from, to, tokenId]
LOAD_ELEMENT_FROM_KEYS(0x00) // [is_approved_for_all, from, to, tokenId]
is_authorized jumpi // [from, to, tokenId]
// Check if approved for tokenId
dup3 // [tokenId, from, to, tokenId]
[SINGLE_APPROVAL_LOCATION] // [SINGLE_APPROVAL_LOCATION, tokenId, from, to, tokenId]
LOAD_ELEMENT_FROM_KEYS(0x00) // [address_approved_for_tokenId, from, to, tokenId]
caller eq is_authorized jumpi // [from, to, tokenId]
// If msg.sender != from && !isApprovedForAll[from][msg.sender] && msg.sender != getApproved[id],
UNAUTHORIZED(0x00)
is_authorized:
// Update balance of from
0x01 dup2 // [from, 1, from, to, tokenId]
[BALANCE_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance, 1, from, to, tokenId]
sub dup2 // [from, balance-1, from, to, tokenId]
[BALANCE_LOCATION]
STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId]
}
/// @notice Internal Macro to update Transfer to accounting
#define macro TRANSFER_GIVE_TO() = takes (3) returns (3) {
// retrieve balance
// input stack: // [from, to, tokenId]
dup2 // [to, from, to, tokenId]
[BALANCE_LOCATION] // [balance_slot, to, from, to, tokenId]
LOAD_ELEMENT_FROM_KEYS(0x00) // [balance, from, to, tokenId]
0x01 add // [balance+1, from, to, tokenId]
// update balance
dup3 // [to, balance+1, from, to, tokenId]
[BALANCE_LOCATION] // [balance_slot, to, balance+1, from, to, tokenId]
STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId]
// update ownerOf
dup2 dup4 // [tokenId, to, from, to, tokenId]
[OWNER_LOCATION] // [owner_slot, tokenId, to, from, to, tokenId]
STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId]
// update approval
0x00 dup4 // [tokenId, address(0), from, to, tokenId]
[SINGLE_APPROVAL_LOCATION] // [approval_slot, tokenId, address(0), from, to, tokenId]
STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId]
}