Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {ERC721} from "solady/tokens/ERC721.sol";
import {IShadowCallbackReceiver} from "../../src/interfaces/IShadowCallbackReceiver.sol";
contract MockNFT is ERC721 {
mapping(uint256 => bool) public locked;
uint256 private iterator;
function name() public pure override returns (string memory) {
return "Mock NFT";
}
function symbol() public pure override returns (string memory) {
return "MOCK";
}
function tokenURI(uint256) public pure override returns (string memory) {
return "";
}
function mint(address to, uint256 tokenId) public returns (uint256) {
_mint(to, tokenId);
return tokenId;
}
function setLocked(uint256 tokenId, bool isLocked) public {
locked[tokenId] = isLocked;
}
function readWithCallback(uint256[] calldata tokenIds, uint32[] calldata, uint128)
external
payable
returns (bytes32)
{
// Simple mock implementation - just return a deterministic guid
bytes32 guid = keccak256(abi.encodePacked(block.timestamp, tokenIds, ++iterator));
// Execute callback immediately for testing purposes
IShadowCallbackReceiver(msg.sender).executeCallback(guid);
return guid;
}
function tokensOfOwnerIn(address owner, uint256 start, uint256 stop) external view returns (uint256[] memory) {
uint256 numberOfTokens = balanceOf(owner);
uint256 tokenIdIdx = 0;
uint256[] memory tokenIds = new uint256[](numberOfTokens);
for (uint256 i = start; i <= stop; i++) {
if (_ownerOf(i) == owner) {
tokenIds[tokenIdIdx] = i;
unchecked {
tokenIdIdx++;
}
if (tokenIdIdx == numberOfTokens) {
return tokenIds;
}
}
}
// in case we did not find all the tokens within this start/stop, we need to resize the array
uint256[] memory result = new uint256[](tokenIdIdx);
for (uint256 i = 0; i < tokenIdIdx; i++) {
result[i] = tokenIds[i];
}
return result;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple ERC721 implementation with storage hitchhiking.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC721.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC721/ERC721.sol)
///
/// @dev Note:
/// - The ERC721 standard allows for self-approvals.
/// For performance, this implementation WILL NOT revert for such actions.
/// Please add any checks with overrides if desired.
/// - For performance, methods are made payable where permitted by the ERC721 standard.
/// - The `safeTransfer` functions use the identity precompile (0x4)
/// to copy memory internally.
///
/// If you are overriding:
/// - NEVER violate the ERC721 invariant:
/// the balance of an owner MUST always be equal to their number of ownership slots.
/// The transfer functions do not have an underflow guard for user token balances.
/// - Make sure all variables written to storage are properly cleaned
/// (e.g. the bool value for `isApprovedForAll` MUST be either 1 or 0 under the hood).
/// - Check that the overridden function is actually used in the function you want to
/// change the behavior of. Much of the code has been manually inlined for performance.
abstract contract ERC721 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev An account can hold up to 4294967295 tokens.
uint256 internal constant _MAX_ACCOUNT_BALANCE = 0xffffffff;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Only the token owner or an approved account can manage the token.
error NotOwnerNorApproved();
/// @dev The token does not exist.
error TokenDoesNotExist();
/// @dev The token already exists.
error TokenAlreadyExists();
/// @dev Cannot query the balance for the zero address.
error BalanceQueryForZeroAddress();
/// @dev Cannot mint or transfer to the zero address.
error TransferToZeroAddress();
/// @dev The token must be owned by `from`.
error TransferFromIncorrectOwner();
/// @dev The recipient's balance has overflowed.
error AccountBalanceOverflow();
/// @dev Cannot safely transfer to a contract that does not implement
/// the ERC721Receiver interface.
error TransferToNonERC721ReceiverImplementer();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Emitted when token `id` is transferred from `from` to `to`.
event Transfer(address indexed from, address indexed to, uint256 indexed id);
/// @dev Emitted when `owner` enables `account` to manage the `id` token.
event Approval(address indexed owner, address indexed account, uint256 indexed id);
/// @dev Emitted when `owner` enables or disables `operator` to manage all of their tokens.
event ApprovalForAll(address indexed owner, address indexed operator, bool isApproved);
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
/// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
uint256 private constant _APPROVAL_EVENT_SIGNATURE =
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
/// @dev `keccak256(bytes("ApprovalForAll(address,address,bool)"))`.
uint256 private constant _APPROVAL_FOR_ALL_EVENT_SIGNATURE =
0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ownership data slot of `id` is given by:
/// ```
/// mstore(0x00, id)
/// mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
/// let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
/// ```
/// Bits Layout:
/// - [0..159] `addr`
/// - [160..255] `extraData`
///
/// The approved address slot is given by: `add(1, ownershipSlot)`.
///
/// See: https://notes.ethereum.org/%40vbuterin/verkle_tree_eip
///
/// The balance slot of `owner` is given by:
/// ```
/// mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
/// mstore(0x00, owner)
/// let balanceSlot := keccak256(0x0c, 0x1c)
/// ```
/// Bits Layout:
/// - [0..31] `balance`
/// - [32..255] `aux`
///
/// The `operator` approval slot of `owner` is given by:
/// ```
/// mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, operator))
/// mstore(0x00, owner)
/// let operatorApprovalSlot := keccak256(0x0c, 0x30)
/// ```
uint256 private constant _ERC721_MASTER_SLOT_SEED = 0x7d8825530a5a2e7a << 192;
/// @dev Pre-shifted and pre-masked constant.
uint256 private constant _ERC721_MASTER_SLOT_SEED_MASKED = 0x0a5a2e7a00000000;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC721 METADATA */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the token collection name.
function name() public view virtual returns (string memory);
/// @dev Returns the token collection symbol.
function symbol() public view virtual returns (string memory);
/// @dev Returns the Uniform Resource Identifier (URI) for token `id`.
function tokenURI(uint256 id) public view virtual returns (string memory);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC721 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the owner of token `id`.
///
/// Requirements:
/// - Token `id` must exist.
function ownerOf(uint256 id) public view virtual returns (address result) {
result = _ownerOf(id);
/// @solidity memory-safe-assembly
assembly {
if iszero(result) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Returns the number of tokens owned by `owner`.
///
/// Requirements:
/// - `owner` must not be the zero address.
function balanceOf(address owner) public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
// Revert if the `owner` is the zero address.
if iszero(owner) {
mstore(0x00, 0x8f4eb604) // `BalanceQueryForZeroAddress()`.
revert(0x1c, 0x04)
}
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
mstore(0x00, owner)
result := and(sload(keccak256(0x0c, 0x1c)), _MAX_ACCOUNT_BALANCE)
}
}
/// @dev Returns the account approved to manage token `id`.
///
/// Requirements:
/// - Token `id` must exist.
function getApproved(uint256 id) public view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
if iszero(shl(96, sload(ownershipSlot))) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
result := sload(add(1, ownershipSlot))
}
}
/// @dev Sets `account` as the approved account to manage token `id`.
///
/// Requirements:
/// - Token `id` must exist.
/// - The caller must be the owner of the token,
/// or an approved operator for the token owner.
///
/// Emits an {Approval} event.
function approve(address account, uint256 id) public payable virtual {
_approve(msg.sender, account, id);
}
/// @dev Returns whether `operator` is approved to manage the tokens of `owner`.
function isApprovedForAll(address owner, address operator)
public
view
virtual
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
mstore(0x1c, operator)
mstore(0x08, _ERC721_MASTER_SLOT_SEED_MASKED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x30))
}
}
/// @dev Sets whether `operator` is approved to manage the tokens of the caller.
///
/// Emits an {ApprovalForAll} event.
function setApprovalForAll(address operator, bool isApproved) public virtual {
/// @solidity memory-safe-assembly
assembly {
// Convert to 0 or 1.
isApproved := iszero(iszero(isApproved))
// Update the `isApproved` for (`msg.sender`, `operator`).
mstore(0x1c, operator)
mstore(0x08, _ERC721_MASTER_SLOT_SEED_MASKED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x30), isApproved)
// Emit the {ApprovalForAll} event.
mstore(0x00, isApproved)
// forgefmt: disable-next-item
log3(0x00, 0x20, _APPROVAL_FOR_ALL_EVENT_SIGNATURE, caller(), shr(96, shl(96, operator)))
}
}
/// @dev Transfers token `id` from `from` to `to`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - `from` must be the owner of the token.
/// - `to` cannot be the zero address.
/// - The caller must be the owner of the token, or be approved to manage the token.
///
/// Emits a {Transfer} event.
function transferFrom(address from, address to, uint256 id) public payable virtual {
_beforeTokenTransfer(from, to, id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
let bitmaskAddress := shr(96, not(0))
from := and(bitmaskAddress, from)
to := and(bitmaskAddress, to)
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, caller()))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked := sload(ownershipSlot)
let owner := and(bitmaskAddress, ownershipPacked)
// Revert if the token does not exist, or if `from` is not the owner.
if iszero(mul(owner, eq(owner, from))) {
// `TokenDoesNotExist()`, `TransferFromIncorrectOwner()`.
mstore(shl(2, iszero(owner)), 0xceea21b6a1148100)
revert(0x1c, 0x04)
}
// Load, check, and update the token approval.
{
mstore(0x00, from)
let approvedAddress := sload(add(1, ownershipSlot))
// Revert if the caller is not the owner, nor approved.
if iszero(or(eq(caller(), from), eq(caller(), approvedAddress))) {
if iszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
revert(0x1c, 0x04)
}
}
// Delete the approved address if any.
if approvedAddress { sstore(add(1, ownershipSlot), 0) }
}
// Update with the new owner.
sstore(ownershipSlot, xor(ownershipPacked, xor(from, to)))
// Decrement the balance of `from`.
{
let fromBalanceSlot := keccak256(0x0c, 0x1c)
sstore(fromBalanceSlot, sub(sload(fromBalanceSlot), 1))
}
// Increment the balance of `to`.
{
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x1c)
let p := sload(toBalanceSlot) // `toBalanceSlotPacked`.
// Revert if `to` is the zero address, or if the account balance is maxed.
if iszero(mul(to, xor(and(p, _MAX_ACCOUNT_BALANCE), _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(toBalanceSlot, add(1, p))
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, id)
}
_afterTokenTransfer(from, to, id);
}
/// @dev Equivalent to `safeTransferFrom(from, to, id, "")`.
function safeTransferFrom(address from, address to, uint256 id) public payable virtual {
transferFrom(from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, "");
}
/// @dev Transfers token `id` from `from` to `to`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - `from` must be the owner of the token.
/// - `to` cannot be the zero address.
/// - The caller must be the owner of the token, or be approved to manage the token.
/// - If `to` refers to a smart contract, it must implement
/// {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
///
/// Emits a {Transfer} event.
function safeTransferFrom(address from, address to, uint256 id, bytes calldata data)
public
payable
virtual
{
transferFrom(from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
}
/// @dev Returns true if this contract implements the interface defined by `interfaceId`.
/// See: https://eips.ethereum.org/EIPS/eip-165
/// This function call must use less than 30000 gas.
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
let s := shr(224, interfaceId)
// ERC165: 0x01ffc9a7, ERC721: 0x80ac58cd, ERC721Metadata: 0x5b5e139f.
result := or(or(eq(s, 0x01ffc9a7), eq(s, 0x80ac58cd)), eq(s, 0x5b5e139f))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL QUERY FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns if token `id` exists.
function _exists(uint256 id) internal view virtual returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result := iszero(iszero(shl(96, sload(add(id, add(id, keccak256(0x00, 0x20)))))))
}
}
/// @dev Returns the owner of token `id`.
/// Returns the zero address instead of reverting if the token does not exist.
function _ownerOf(uint256 id) internal view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result := shr(96, shl(96, sload(add(id, add(id, keccak256(0x00, 0x20))))))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL DATA HITCHHIKING FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// For performance, no events are emitted for the hitchhiking setters.
// Please emit your own events if required.
/// @dev Returns the auxiliary data for `owner`.
/// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data.
/// Auxiliary data can be set for any address, even if it does not have any tokens.
function _getAux(address owner) internal view virtual returns (uint224 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
mstore(0x00, owner)
result := shr(32, sload(keccak256(0x0c, 0x1c)))
}
}
/// @dev Set the auxiliary data for `owner` to `value`.
/// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data.
/// Auxiliary data can be set for any address, even if it does not have any tokens.
function _setAux(address owner, uint224 value) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
mstore(0x00, owner)
let balanceSlot := keccak256(0x0c, 0x1c)
let packed := sload(balanceSlot)
sstore(balanceSlot, xor(packed, shl(32, xor(value, shr(32, packed)))))
}
}
/// @dev Returns the extra data for token `id`.
/// Minting, transferring, burning a token will not change the extra data.
/// The extra data can be set on a non-existent token.
function _getExtraData(uint256 id) internal view virtual returns (uint96 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result := shr(160, sload(add(id, add(id, keccak256(0x00, 0x20)))))
}
}
/// @dev Sets the extra data for token `id` to `value`.
/// Minting, transferring, burning a token will not change the extra data.
/// The extra data can be set on a non-existent token.
function _setExtraData(uint256 id, uint96 value) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let packed := sload(ownershipSlot)
sstore(ownershipSlot, xor(packed, shl(160, xor(value, shr(160, packed)))))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL MINT FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Mints token `id` to `to`.
///
/// Requirements:
///
/// - Token `id` must not exist.
/// - `to` cannot be the zero address.
///
/// Emits a {Transfer} event.
function _mint(address to, uint256 id) internal virtual {
_beforeTokenTransfer(address(0), to, id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
to := shr(96, shl(96, to))
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked := sload(ownershipSlot)
// Revert if the token already exists.
if shl(96, ownershipPacked) {
mstore(0x00, 0xc991cbb1) // `TokenAlreadyExists()`.
revert(0x1c, 0x04)
}
// Update with the owner.
sstore(ownershipSlot, or(ownershipPacked, to))
// Increment the balance of the owner.
{
mstore(0x00, to)
let balanceSlot := keccak256(0x0c, 0x1c)
let p := sload(balanceSlot) // `balanceSlotPacked`.
// Revert if `to` is the zero address, or if the account balance is maxed.
if iszero(mul(to, xor(and(p, _MAX_ACCOUNT_BALANCE), _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(balanceSlot, add(1, p))
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, 0, to, id)
}
_afterTokenTransfer(address(0), to, id);
}
/// @dev Mints token `id` to `to`, and updates the extra data for token `id` to `value`.
/// Does NOT check if token `id` already exists (assumes `id` is auto-incrementing).
///
/// Requirements:
///
/// - `to` cannot be the zero address.
///
/// Emits a {Transfer} event.
function _mintAndSetExtraDataUnchecked(address to, uint256 id, uint96 value) internal virtual {
_beforeTokenTransfer(address(0), to, id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
to := shr(96, shl(96, to))
// Update with the owner and extra data.
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
sstore(add(id, add(id, keccak256(0x00, 0x20))), or(shl(160, value), to))
// Increment the balance of the owner.
{
mstore(0x00, to)
let balanceSlot := keccak256(0x0c, 0x1c)
let p := sload(balanceSlot) // `balanceSlotPacked`.
// Revert if `to` is the zero address, or if the account balance is maxed.
if iszero(mul(to, xor(and(p, _MAX_ACCOUNT_BALANCE), _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(balanceSlot, add(1, p))
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, 0, to, id)
}
_afterTokenTransfer(address(0), to, id);
}
/// @dev Equivalent to `_safeMint(to, id, "")`.
function _safeMint(address to, uint256 id) internal virtual {
_safeMint(to, id, "");
}
/// @dev Mints token `id` to `to`.
///
/// Requirements:
///
/// - Token `id` must not exist.
/// - `to` cannot be the zero address.
/// - If `to` refers to a smart contract, it must implement
/// {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
///
/// Emits a {Transfer} event.
function _safeMint(address to, uint256 id, bytes memory data) internal virtual {
_mint(to, id);
if (_hasCode(to)) _checkOnERC721Received(address(0), to, id, data);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL BURN FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to `_burn(address(0), id)`.
function _burn(uint256 id) internal virtual {
_burn(address(0), id);
}
/// @dev Destroys token `id`, using `by`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - If `by` is not the zero address,
/// it must be the owner of the token, or be approved to manage the token.
///
/// Emits a {Transfer} event.
function _burn(address by, uint256 id) internal virtual {
address owner = ownerOf(id);
_beforeTokenTransfer(owner, address(0), id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
by := shr(96, shl(96, by))
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked := sload(ownershipSlot)
// Reload the owner in case it is changed in `_beforeTokenTransfer`.
owner := shr(96, shl(96, ownershipPacked))
// Revert if the token does not exist.
if iszero(owner) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
// Load and check the token approval.
{
mstore(0x00, owner)
let approvedAddress := sload(add(1, ownershipSlot))
// If `by` is not the zero address, do the authorization check.
// Revert if the `by` is not the owner, nor approved.
if iszero(or(iszero(by), or(eq(by, owner), eq(by, approvedAddress)))) {
if iszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
revert(0x1c, 0x04)
}
}
// Delete the approved address if any.
if approvedAddress { sstore(add(1, ownershipSlot), 0) }
}
// Clear the owner.
sstore(ownershipSlot, xor(ownershipPacked, owner))
// Decrement the balance of `owner`.
{
let balanceSlot := keccak256(0x0c, 0x1c)
sstore(balanceSlot, sub(sload(balanceSlot), 1))
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, owner, 0, id)
}
_afterTokenTransfer(owner, address(0), id);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL APPROVAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns whether `account` is the owner of token `id`, or is approved to manage it.
///
/// Requirements:
/// - Token `id` must exist.
function _isApprovedOrOwner(address account, uint256 id)
internal
view
virtual
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
result := 1
// Clear the upper 96 bits.
account := shr(96, shl(96, account))
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, account))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let owner := shr(96, shl(96, sload(ownershipSlot)))
// Revert if the token does not exist.
if iszero(owner) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
// Check if `account` is the `owner`.
if iszero(eq(account, owner)) {
mstore(0x00, owner)
// Check if `account` is approved to manage the token.
if iszero(sload(keccak256(0x0c, 0x30))) {
result := eq(account, sload(add(1, ownershipSlot)))
}
}
}
}
/// @dev Returns the account approved to manage token `id`.
/// Returns the zero address instead of reverting if the token does not exist.
function _getApproved(uint256 id) internal view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result := sload(add(1, add(id, add(id, keccak256(0x00, 0x20)))))
}
}
/// @dev Equivalent to `_approve(address(0), account, id)`.
function _approve(address account, uint256 id) internal virtual {
_approve(address(0), account, id);
}
/// @dev Sets `account` as the approved account to manage token `id`, using `by`.
///
/// Requirements:
/// - Token `id` must exist.
/// - If `by` is not the zero address, `by` must be the owner
/// or an approved operator for the token owner.
///
/// Emits a {Approval} event.
function _approve(address by, address account, uint256 id) internal virtual {
assembly {
// Clear the upper 96 bits.
let bitmaskAddress := shr(96, not(0))
account := and(bitmaskAddress, account)
by := and(bitmaskAddress, by)
// Load the owner of the token.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let owner := and(bitmaskAddress, sload(ownershipSlot))
// Revert if the token does not exist.
if iszero(owner) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
// If `by` is not the zero address, do the authorization check.
// Revert if `by` is not the owner, nor approved.
if iszero(or(iszero(by), eq(by, owner))) {
mstore(0x00, owner)
if iszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
revert(0x1c, 0x04)
}
}
// Sets `account` as the approved account to manage `id`.
sstore(add(1, ownershipSlot), account)
// Emit the {Approval} event.
log4(codesize(), 0x00, _APPROVAL_EVENT_SIGNATURE, owner, account, id)
}
}
/// @dev Approve or remove the `operator` as an operator for `by`,
/// without authorization checks.
///
/// Emits an {ApprovalForAll} event.
function _setApprovalForAll(address by, address operator, bool isApproved) internal virtual {
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
by := shr(96, shl(96, by))
operator := shr(96, shl(96, operator))
// Convert to 0 or 1.
isApproved := iszero(iszero(isApproved))
// Update the `isApproved` for (`by`, `operator`).
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, operator))
mstore(0x00, by)
sstore(keccak256(0x0c, 0x30), isApproved)
// Emit the {ApprovalForAll} event.
mstore(0x00, isApproved)
log3(0x00, 0x20, _APPROVAL_FOR_ALL_EVENT_SIGNATURE, by, operator)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL TRANSFER FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to `_transfer(address(0), from, to, id)`.
function _transfer(address from, address to, uint256 id) internal virtual {
_transfer(address(0), from, to, id);
}
/// @dev Transfers token `id` from `from` to `to`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - `from` must be the owner of the token.
/// - `to` cannot be the zero address.
/// - If `by` is not the zero address,
/// it must be the owner of the token, or be approved to manage the token.
///
/// Emits a {Transfer} event.
function _transfer(address by, address from, address to, uint256 id) internal virtual {
_beforeTokenTransfer(from, to, id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
let bitmaskAddress := shr(96, not(0))
from := and(bitmaskAddress, from)
to := and(bitmaskAddress, to)
by := and(bitmaskAddress, by)
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked := sload(ownershipSlot)
let owner := and(bitmaskAddress, ownershipPacked)
// Revert if the token does not exist, or if `from` is not the owner.
if iszero(mul(owner, eq(owner, from))) {
// `TokenDoesNotExist()`, `TransferFromIncorrectOwner()`.
mstore(shl(2, iszero(owner)), 0xceea21b6a1148100)
revert(0x1c, 0x04)
}
// Load, check, and update the token approval.
{
mstore(0x00, from)
let approvedAddress := sload(add(1, ownershipSlot))
// If `by` is not the zero address, do the authorization check.
// Revert if the `by` is not the owner, nor approved.
if iszero(or(iszero(by), or(eq(by, from), eq(by, approvedAddress)))) {
if iszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
revert(0x1c, 0x04)
}
}
// Delete the approved address if any.
if approvedAddress { sstore(add(1, ownershipSlot), 0) }
}
// Update with the new owner.
sstore(ownershipSlot, xor(ownershipPacked, xor(from, to)))
// Decrement the balance of `from`.
{
let fromBalanceSlot := keccak256(0x0c, 0x1c)
sstore(fromBalanceSlot, sub(sload(fromBalanceSlot), 1))
}
// Increment the balance of `to`.
{
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x1c)
let p := sload(toBalanceSlot) // `toBalanceSlotPacked`.
// Revert if `to` is the zero address, or if the account balance is maxed.
if iszero(mul(to, xor(and(p, _MAX_ACCOUNT_BALANCE), _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(toBalanceSlot, add(1, p))
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, id)
}
_afterTokenTransfer(from, to, id);
}
/// @dev Equivalent to `_safeTransfer(from, to, id, "")`.
function _safeTransfer(address from, address to, uint256 id) internal virtual {
_safeTransfer(from, to, id, "");
}
/// @dev Transfers token `id` from `from` to `to`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - `from` must be the owner of the token.
/// - `to` cannot be the zero address.
/// - The caller must be the owner of the token, or be approved to manage the token.
/// - If `to` refers to a smart contract, it must implement
/// {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
///
/// Emits a {Transfer} event.
function _safeTransfer(address from, address to, uint256 id, bytes memory data)
internal
virtual
{
_transfer(address(0), from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
}
/// @dev Equivalent to `_safeTransfer(by, from, to, id, "")`.
function _safeTransfer(address by, address from, address to, uint256 id) internal virtual {
_safeTransfer(by, from, to, id, "");
}
/// @dev Transfers token `id` from `from` to `to`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - `from` must be the owner of the token.
/// - `to` cannot be the zero address.
/// - If `by` is not the zero address,
/// it must be the owner of the token, or be approved to manage the token.
/// - If `to` refers to a smart contract, it must implement
/// {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
///
/// Emits a {Transfer} event.
function _safeTransfer(address by, address from, address to, uint256 id, bytes memory data)
internal
virtual
{
_transfer(by, from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HOOKS FOR OVERRIDING */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Hook that is called before any token transfers, including minting and burning.
function _beforeTokenTransfer(address from, address to, uint256 id) internal virtual {}
/// @dev Hook that is called after any token transfers, including minting and burning.
function _afterTokenTransfer(address from, address to, uint256 id) internal virtual {}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns if `a` has bytecode of non-zero length.
function _hasCode(address a) private view returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := extcodesize(a) // Can handle dirty upper bits.
}
}
/// @dev Perform a call to invoke {IERC721Receiver-onERC721Received} on `to`.
/// Reverts if the target does not support the function correctly.
function _checkOnERC721Received(address from, address to, uint256 id, bytes memory data)
private
{
/// @solidity memory-safe-assembly
assembly {
// Prepare the calldata.
let m := mload(0x40)
let onERC721ReceivedSelector := 0x150b7a02
mstore(m, onERC721ReceivedSelector)
mstore(add(m, 0x20), caller()) // The `operator`, which is always `msg.sender`.
mstore(add(m, 0x40), shr(96, shl(96, from)))
mstore(add(m, 0x60), id)
mstore(add(m, 0x80), 0x80)
let n := mload(data)
mstore(add(m, 0xa0), n)
if n { pop(staticcall(gas(), 4, add(data, 0x20), n, add(m, 0xc0), n)) }
// Revert if the call reverts.
if iszero(call(gas(), to, 0, add(m, 0x1c), add(n, 0xa4), m, 0x20)) {
if returndatasize() {
// Bubble up the revert if the call reverts.
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
}
// Load the returndata and compare it.
if iszero(eq(mload(m), shl(224, onERC721ReceivedSelector))) {
mstore(0x00, 0xd1a57ed6) // `TransferToNonERC721ReceiverImplementer()`.
revert(0x1c, 0x04)
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
interface IShadowCallbackReceiver {
function executeCallback(bytes32 guid) external;
}