Transaction Hash:
Block:
17612006 at Jul-03-2023 07:37:47 AM +UTC
Transaction Fee:
0.0008682484631802 ETH
$1.84
Gas Used:
61,140 Gas / 14.20098893 Gwei
Emitted Events:
| 223 |
Elemental.ApprovalForAll( owner=[Sender] 0xe2473e383b7beae3ba49da4c6f8ccac7510ad879, operator=0x00000000...1F759a8A8, approved=True )
|
Execution Trace
Elemental.setApprovalForAll( operator=0x00000000000111AbE46ff893f3B2fdF1F759a8A8, approved=True )
-
OperatorFilterRegistry.isOperatorAllowed( registrant=0xB6a37b5d14D502c3Ab0Ae6f3a0E058BC9517786e, operator=0x00000000000111AbE46ff893f3B2fdF1F759a8A8 ) => ( True )
setApprovalForAll[Elemental (ln:250)]
setApprovalForAll[Elemental (ln:255)]
File 1 of 2: Elemental
File 2 of 2: OperatorFilterRegistry
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "solbase/src/tokens/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/token/common/ERC2981.sol";
import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
import "closedsea/OperatorFilterer.sol";
import "./MultisigOwnable.sol";
error NotAllowedByRegistry();
error RegistryNotSet();
error InvalidTokenId();
error BeanAddressNotSet();
error RedeemBeanNotOpen();
error InvalidRedeemer();
error NoMoreTokenIds();
interface IRegistry {
function isAllowedOperator(address operator) external view returns (bool);
}
contract Elemental is ERC2981, ERC721, MultisigOwnable, OperatorFilterer {
using Strings for uint256;
using BitMaps for BitMaps.BitMap;
event BeanRedeemed(
address indexed to,
uint256 indexed tokenId,
uint256 indexed beanId
);
bool public operatorFilteringEnabled = true;
bool public isRegistryActive = false;
address public registryAddress;
struct RedeemInfo {
bool redeemBeanOpen;
address beanAddress;
}
RedeemInfo public redeemInfo;
uint16 public immutable MAX_SUPPLY;
uint16 internal _numAvailableRemainingTokens;
// Data structure used for Fisher Yates shuffle
uint16[65536] internal _availableRemainingTokens;
constructor(
string memory _name,
string memory _symbol,
uint16 maxSupply_
) ERC721(_name, _symbol) {
MAX_SUPPLY = maxSupply_;
_numAvailableRemainingTokens = maxSupply_;
_registerForOperatorFiltering();
operatorFilteringEnabled = true;
}
// ---------------
// Name and symbol
// ---------------
function setNameAndSymbol(
string calldata _newName,
string calldata _newSymbol
) external onlyOwner {
name = _newName;
symbol = _newSymbol;
}
// ------------
// Redeem beans
// ------------
function redeemBeans(address to, uint256[] calldata beanIds)
public
returns (uint256[] memory)
{
RedeemInfo memory info = redeemInfo;
if (!info.redeemBeanOpen) {
revert RedeemBeanNotOpen();
}
if (msg.sender != info.beanAddress) {
revert InvalidRedeemer();
}
uint256 amount = beanIds.length;
uint256[] memory tokenIds = new uint256[](amount);
// Assume data has already been validated by the bean contract
for (uint256 i; i < amount; ) {
uint256 beanId = beanIds[i];
uint256 tokenId = _useRandomAvailableTokenId();
// Don't need safeMint, as the calling address has a MysteryBean in it already
_mint(to, tokenId);
emit BeanRedeemed(to, tokenId, beanId);
tokenIds[i] = tokenId;
unchecked {
++i;
}
}
return tokenIds;
}
// Generates a pseudorandom number between [0,MAX_SUPPLY) that has not yet been generated before, in O(1) time.
//
// Uses Durstenfeld's version of the Yates Shuffle https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
// with a twist to avoid having to manually spend gas to preset an array's values to be values 0...n.
// It does this by interpreting zero-values for an index X as meaning that index X itself is an available value
// that is returnable.
//
// How it works:
// - zero-initialize a mapping (_availableRemainingTokens) and track its length (_numAvailableRemainingTokens). functionally similar to an array with dynamic sizing
// - this mapping will track all remaining valid values that haven't been generated yet, through a combination of its indices and values
// - if _availableRemainingTokens[x] == 0, that means x has not been generated yet
// - if _availableRemainingTokens[x] != 0, that means _availableRemainingTokens[x] has not been generated yet
// - when prompted for a random number between [0,MAX_SUPPLY) that hasn't already been used:
// - generate a random index randIndex between [0,_numAvailableRemainingTokens)
// - examine the value at _availableRemainingTokens[randIndex]
// - if the value is zero, it means randIndex has not been used, so we can return randIndex
// - if the value is non-zero, it means the value has not been used, so we can return _availableRemainingTokens[randIndex]
// - update the _availableRemainingTokens mapping state
// - set _availableRemainingTokens[randIndex] to either the index or the value of the last entry in the mapping (depends on the last entry's state)
// - decrement _numAvailableRemainingTokens to mimic the shrinking of an array
function _useRandomAvailableTokenId() internal returns (uint256) {
uint256 numAvailableRemainingTokens = _numAvailableRemainingTokens;
if (numAvailableRemainingTokens == 0) {
revert NoMoreTokenIds();
}
uint256 randomNum = _getRandomNum(numAvailableRemainingTokens);
uint256 randomIndex = randomNum % numAvailableRemainingTokens;
uint256 valAtIndex = _availableRemainingTokens[randomIndex];
uint256 result;
if (valAtIndex == 0) {
// This means the index itself is still an available token
result = randomIndex;
} else {
// This means the index itself is not an available token, but the val at that index is.
result = valAtIndex;
}
uint256 lastIndex = numAvailableRemainingTokens - 1;
if (randomIndex != lastIndex) {
// Replace the value at randomIndex, now that it's been used.
// Replace it with the data from the last index in the array, since we are going to decrease the array size afterwards.
uint256 lastValInArray = _availableRemainingTokens[lastIndex];
if (lastValInArray == 0) {
// This means the index itself is still an available token
// Cast is safe as we know that lastIndex cannot > MAX_SUPPLY, which is a uint16
_availableRemainingTokens[randomIndex] = uint16(lastIndex);
} else {
// This means the index itself is not an available token, but the val at that index is.
// Cast is safe as we know that lastValInArray cannot > MAX_SUPPLY, which is a uint16
_availableRemainingTokens[randomIndex] = uint16(lastValInArray);
delete _availableRemainingTokens[lastIndex];
}
}
--_numAvailableRemainingTokens;
return result;
}
// On-chain randomness tradeoffs are acceptable here as it's only used for the Elemental's id number itself, not the resulting Elemental's metadata (which is determined by the source MysteryBean).
function _getRandomNum(uint256 numAvailableRemainingTokens)
internal
view
returns (uint256)
{
return
uint256(
keccak256(
abi.encode(
block.prevrandao,
blockhash(block.number - 1),
address(this),
numAvailableRemainingTokens
)
)
);
}
function setBeanAddress(address contractAddress) external onlyOwner {
redeemInfo = RedeemInfo(redeemInfo.redeemBeanOpen, contractAddress);
}
function setRedeemBeanState(bool _redeemBeanOpen) external onlyOwner {
address beanAddress = redeemInfo.beanAddress;
if (beanAddress == address(0)) {
revert BeanAddressNotSet();
}
redeemInfo = RedeemInfo(_redeemBeanOpen, beanAddress);
}
// ------------
// Total Supply
// ------------
function totalSupply() external view returns (uint256) {
unchecked {
// Does not need to account for burns as they aren't supported.
return MAX_SUPPLY - _numAvailableRemainingTokens;
}
}
// --------
// Metadata
// --------
function tokenURI(uint256 tokenId)
public
view
override
returns (string memory)
{
if (_ownerOf[tokenId] == address(0)) {
revert InvalidTokenId();
}
string memory baseURI = _getBaseURIForToken(tokenId);
return
bytes(baseURI).length > 0
? string(abi.encodePacked(baseURI, tokenId.toString()))
: "";
}
string private _baseTokenURI;
string private _baseTokenURIPermanent;
// Keys are Elemental token ids
BitMaps.BitMap private _isUriPermanentForToken;
function _getBaseURIForToken(uint256 tokenId)
private
view
returns (string memory)
{
return
_isUriPermanentForToken.get(tokenId)
? _baseTokenURIPermanent
: _baseTokenURI;
}
function setBaseURI(string calldata baseURI) external onlyOwner {
_baseTokenURI = baseURI;
}
function setBaseURIPermanent(string calldata baseURIPermanent)
external
onlyOwner
{
_baseTokenURIPermanent = baseURIPermanent;
}
function setIsUriPermanent(uint256[] calldata tokenIds) external onlyOwner {
for (uint256 i = 0; i < tokenIds.length; ) {
_isUriPermanentForToken.set(tokenIds[i]);
unchecked {
++i;
}
}
}
// --------
// EIP-2981
// --------
function setDefaultRoyalty(address receiver, uint96 feeNumerator)
external
onlyOwner
{
_setDefaultRoyalty(receiver, feeNumerator);
}
function setTokenRoyalty(
uint256 tokenId,
address receiver,
uint96 feeNumerator
) external onlyOwner {
_setTokenRoyalty(tokenId, receiver, feeNumerator);
}
// ---------------------------------------------------
// OperatorFilterer overrides (overrides, values etc.)
// ---------------------------------------------------
function setApprovalForAll(address operator, bool approved)
public
override
onlyAllowedOperatorApproval(operator)
{
super.setApprovalForAll(operator, approved);
}
function setOperatorFilteringEnabled(bool value) public onlyOwner {
operatorFilteringEnabled = value;
}
function _operatorFilteringEnabled() internal view override returns (bool) {
return operatorFilteringEnabled;
}
function approve(address operator, uint256 tokenId)
public
override
onlyAllowedOperatorApproval(operator)
{
super.approve(operator, tokenId);
}
// --------------
// Registry check
// --------------
// Solbase ERC721 calls transferFrom internally in its two safeTransferFrom functions, so we don't need to override those.
// Also, onlyAllowedOperator is from closedsea
function transferFrom(
address from,
address to,
uint256 id
) public override onlyAllowedOperator(from) {
if (!_isValidAgainstRegistry(msg.sender)) {
revert NotAllowedByRegistry();
}
super.transferFrom(from, to, id);
}
function _isValidAgainstRegistry(address operator)
internal
view
returns (bool)
{
if (isRegistryActive) {
IRegistry registry = IRegistry(registryAddress);
return registry.isAllowedOperator(operator);
}
return true;
}
function setIsRegistryActive(bool _isRegistryActive) external onlyOwner {
if (registryAddress == address(0)) revert RegistryNotSet();
isRegistryActive = _isRegistryActive;
}
function setRegistryAddress(address _registryAddress) external onlyOwner {
registryAddress = _registryAddress;
}
// -------
// EIP-165
// -------
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC2981)
returns (bool)
{
return
ERC721.supportsInterface(interfaceId) ||
ERC2981.supportsInterface(interfaceId);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Modern, minimalist, and gas-optimized ERC721 implementation.
/// @author SolDAO (https://github.com/Sol-DAO/solbase/blob/main/src/tokens/ERC721.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721 {
/// -----------------------------------------------------------------------
/// Events
/// -----------------------------------------------------------------------
event Transfer(address indexed from, address indexed to, uint256 indexed id);
event Approval(address indexed owner, address indexed spender, uint256 indexed id);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/// -----------------------------------------------------------------------
/// Custom Errors
/// -----------------------------------------------------------------------
error NotMinted();
error ZeroAddress();
error Unauthorized();
error WrongFrom();
error InvalidRecipient();
error UnsafeRecipient();
error AlreadyMinted();
/// -----------------------------------------------------------------------
/// Metadata Storage/Logic
/// -----------------------------------------------------------------------
string public name;
string public symbol;
function tokenURI(uint256 id) public view virtual returns (string memory);
/// -----------------------------------------------------------------------
/// ERC721 Balance/Owner Storage
/// -----------------------------------------------------------------------
mapping(uint256 => address) internal _ownerOf;
mapping(address => uint256) internal _balanceOf;
function ownerOf(uint256 id) public view virtual returns (address owner) {
if ((owner = _ownerOf[id]) == address(0)) revert NotMinted();
}
function balanceOf(address owner) public view virtual returns (uint256) {
if (owner == address(0)) revert ZeroAddress();
return _balanceOf[owner];
}
/// -----------------------------------------------------------------------
/// ERC721 Approval Storage
/// -----------------------------------------------------------------------
mapping(uint256 => address) public getApproved;
mapping(address => mapping(address => bool)) public isApprovedForAll;
/// -----------------------------------------------------------------------
/// Constructor
/// -----------------------------------------------------------------------
constructor(string memory _name, string memory _symbol) {
name = _name;
symbol = _symbol;
}
/// -----------------------------------------------------------------------
/// ERC721 Logic
/// -----------------------------------------------------------------------
function approve(address spender, uint256 id) public virtual {
address owner = _ownerOf[id];
if (msg.sender != owner && !isApprovedForAll[owner][msg.sender]) revert Unauthorized();
getApproved[id] = spender;
emit Approval(owner, spender, id);
}
function setApprovalForAll(address operator, bool approved) public virtual {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function transferFrom(address from, address to, uint256 id) public virtual {
if (from != _ownerOf[id]) revert WrongFrom();
if (to == address(0)) revert InvalidRecipient();
if (msg.sender != from && !isApprovedForAll[from][msg.sender] && msg.sender != getApproved[id])
revert Unauthorized();
// Underflow of the sender's balance is impossible because we check for
// ownership above and the recipient's balance can't realistically overflow.
unchecked {
_balanceOf[from]--;
_balanceOf[to]++;
}
_ownerOf[id] = to;
delete getApproved[id];
emit Transfer(from, to, id);
}
function safeTransferFrom(address from, address to, uint256 id) public virtual {
transferFrom(from, to, id);
if (to.code.length != 0) {
if (
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") !=
ERC721TokenReceiver.onERC721Received.selector
) revert UnsafeRecipient();
}
}
function safeTransferFrom(address from, address to, uint256 id, bytes calldata data) public virtual {
transferFrom(from, to, id);
if (to.code.length != 0) {
if (
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) !=
ERC721TokenReceiver.onERC721Received.selector
) revert UnsafeRecipient();
}
}
/// -----------------------------------------------------------------------
/// ERC165 Logic
/// -----------------------------------------------------------------------
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return
interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
}
/// -----------------------------------------------------------------------
/// Internal Mint/Burn Logic
/// -----------------------------------------------------------------------
function _mint(address to, uint256 id) internal virtual {
if (to == address(0)) revert InvalidRecipient();
if (_ownerOf[id] != address(0)) revert AlreadyMinted();
// Counter overflow is incredibly unrealistic.
unchecked {
_balanceOf[to]++;
}
_ownerOf[id] = to;
emit Transfer(address(0), to, id);
}
function _burn(uint256 id) internal virtual {
address owner = _ownerOf[id];
if (owner == address(0)) revert NotMinted();
// Ownership check above ensures no underflow.
unchecked {
_balanceOf[owner]--;
}
delete _ownerOf[id];
delete getApproved[id];
emit Transfer(owner, address(0), id);
}
/// -----------------------------------------------------------------------
/// Internal Safe Mint Logic
/// -----------------------------------------------------------------------
function _safeMint(address to, uint256 id) internal virtual {
_mint(to, id);
if (to.code.length != 0) {
if (
ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") !=
ERC721TokenReceiver.onERC721Received.selector
) revert UnsafeRecipient();
}
}
function _safeMint(address to, uint256 id, bytes memory data) internal virtual {
_mint(to, id);
if (to.code.length != 0) {
if (
ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) !=
ERC721TokenReceiver.onERC721Received.selector
) revert UnsafeRecipient();
}
}
}
/// @notice A generic interface for a contract which properly accepts ERC721 tokens.
/// @author SolDAO (https://github.com/Sol-DAO/solbase/blob/main/src/tokens/ERC721.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721TokenReceiver {
function onERC721Received(address, address, uint256, bytes calldata) external virtual returns (bytes4) {
return ERC721TokenReceiver.onERC721Received.selector;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
import "./math/Math.sol";
import "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toString(int256 value) internal pure returns (string memory) {
return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return keccak256(bytes(a)) == keccak256(bytes(b));
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/common/ERC2981.sol)
pragma solidity ^0.8.0;
import "../../interfaces/IERC2981.sol";
import "../../utils/introspection/ERC165.sol";
/**
* @dev Implementation of the NFT Royalty Standard, a standardized way to retrieve royalty payment information.
*
* Royalty information can be specified globally for all token ids via {_setDefaultRoyalty}, and/or individually for
* specific token ids via {_setTokenRoyalty}. The latter takes precedence over the first.
*
* Royalty is specified as a fraction of sale price. {_feeDenominator} is overridable but defaults to 10000, meaning the
* fee is specified in basis points by default.
*
* IMPORTANT: ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See
* https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments[Rationale] in the EIP. Marketplaces are expected to
* voluntarily pay royalties together with sales, but note that this standard is not yet widely supported.
*
* _Available since v4.5._
*/
abstract contract ERC2981 is IERC2981, ERC165 {
struct RoyaltyInfo {
address receiver;
uint96 royaltyFraction;
}
RoyaltyInfo private _defaultRoyaltyInfo;
mapping(uint256 => RoyaltyInfo) private _tokenRoyaltyInfo;
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {
return interfaceId == type(IERC2981).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @inheritdoc IERC2981
*/
function royaltyInfo(uint256 tokenId, uint256 salePrice) public view virtual override returns (address, uint256) {
RoyaltyInfo memory royalty = _tokenRoyaltyInfo[tokenId];
if (royalty.receiver == address(0)) {
royalty = _defaultRoyaltyInfo;
}
uint256 royaltyAmount = (salePrice * royalty.royaltyFraction) / _feeDenominator();
return (royalty.receiver, royaltyAmount);
}
/**
* @dev The denominator with which to interpret the fee set in {_setTokenRoyalty} and {_setDefaultRoyalty} as a
* fraction of the sale price. Defaults to 10000 so fees are expressed in basis points, but may be customized by an
* override.
*/
function _feeDenominator() internal pure virtual returns (uint96) {
return 10000;
}
/**
* @dev Sets the royalty information that all ids in this contract will default to.
*
* Requirements:
*
* - `receiver` cannot be the zero address.
* - `feeNumerator` cannot be greater than the fee denominator.
*/
function _setDefaultRoyalty(address receiver, uint96 feeNumerator) internal virtual {
require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice");
require(receiver != address(0), "ERC2981: invalid receiver");
_defaultRoyaltyInfo = RoyaltyInfo(receiver, feeNumerator);
}
/**
* @dev Removes default royalty information.
*/
function _deleteDefaultRoyalty() internal virtual {
delete _defaultRoyaltyInfo;
}
/**
* @dev Sets the royalty information for a specific token id, overriding the global default.
*
* Requirements:
*
* - `receiver` cannot be the zero address.
* - `feeNumerator` cannot be greater than the fee denominator.
*/
function _setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) internal virtual {
require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice");
require(receiver != address(0), "ERC2981: Invalid parameters");
_tokenRoyaltyInfo[tokenId] = RoyaltyInfo(receiver, feeNumerator);
}
/**
* @dev Resets royalty information for the token id back to the global default.
*/
function _resetTokenRoyalty(uint256 tokenId) internal virtual {
delete _tokenRoyaltyInfo[tokenId];
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/BitMaps.sol)
pragma solidity ^0.8.0;
/**
* @dev Library for managing uint256 to bool mapping in a compact and efficient way, providing the keys are sequential.
* Largely inspired by Uniswap's https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol[merkle-distributor].
*/
library BitMaps {
struct BitMap {
mapping(uint256 => uint256) _data;
}
/**
* @dev Returns whether the bit at `index` is set.
*/
function get(BitMap storage bitmap, uint256 index) internal view returns (bool) {
uint256 bucket = index >> 8;
uint256 mask = 1 << (index & 0xff);
return bitmap._data[bucket] & mask != 0;
}
/**
* @dev Sets the bit at `index` to the boolean `value`.
*/
function setTo(BitMap storage bitmap, uint256 index, bool value) internal {
if (value) {
set(bitmap, index);
} else {
unset(bitmap, index);
}
}
/**
* @dev Sets the bit at `index`.
*/
function set(BitMap storage bitmap, uint256 index) internal {
uint256 bucket = index >> 8;
uint256 mask = 1 << (index & 0xff);
bitmap._data[bucket] |= mask;
}
/**
* @dev Unsets the bit at `index`.
*/
function unset(BitMap storage bitmap, uint256 index) internal {
uint256 bucket = index >> 8;
uint256 mask = 1 << (index & 0xff);
bitmap._data[bucket] &= ~mask;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Optimized and flexible operator filterer to abide to OpenSea's
/// mandatory on-chain royalty enforcement in order for new collections to
/// receive royalties.
/// For more information, see:
/// See: https://github.com/ProjectOpenSea/operator-filter-registry
abstract contract OperatorFilterer {
/// @dev The default OpenSea operator blocklist subscription.
address internal constant _DEFAULT_SUBSCRIPTION = 0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6;
/// @dev The OpenSea operator filter registry.
address internal constant _OPERATOR_FILTER_REGISTRY = 0x000000000000AAeB6D7670E522A718067333cd4E;
/// @dev Registers the current contract to OpenSea's operator filter,
/// and subscribe to the default OpenSea operator blocklist.
/// Note: Will not revert nor update existing settings for repeated registration.
function _registerForOperatorFiltering() internal virtual {
_registerForOperatorFiltering(_DEFAULT_SUBSCRIPTION, true);
}
/// @dev Registers the current contract to OpenSea's operator filter.
/// Note: Will not revert nor update existing settings for repeated registration.
function _registerForOperatorFiltering(address subscriptionOrRegistrantToCopy, bool subscribe)
internal
virtual
{
/// @solidity memory-safe-assembly
assembly {
let functionSelector := 0x7d3e3dbe // `registerAndSubscribe(address,address)`.
// Clean the upper 96 bits of `subscriptionOrRegistrantToCopy` in case they are dirty.
subscriptionOrRegistrantToCopy := shr(96, shl(96, subscriptionOrRegistrantToCopy))
for {} iszero(subscribe) {} {
if iszero(subscriptionOrRegistrantToCopy) {
functionSelector := 0x4420e486 // `register(address)`.
break
}
functionSelector := 0xa0af2903 // `registerAndCopyEntries(address,address)`.
break
}
// Store the function selector.
mstore(0x00, shl(224, functionSelector))
// Store the `address(this)`.
mstore(0x04, address())
// Store the `subscriptionOrRegistrantToCopy`.
mstore(0x24, subscriptionOrRegistrantToCopy)
// Register into the registry.
if iszero(call(gas(), _OPERATOR_FILTER_REGISTRY, 0, 0x00, 0x44, 0x00, 0x04)) {
// If the function selector has not been overwritten,
// it is an out-of-gas error.
if eq(shr(224, mload(0x00)), functionSelector) {
// To prevent gas under-estimation.
revert(0, 0)
}
}
// Restore the part of the free memory pointer that was overwritten,
// which is guaranteed to be zero, because of Solidity's memory size limits.
mstore(0x24, 0)
}
}
/// @dev Modifier to guard a function and revert if the caller is a blocked operator.
modifier onlyAllowedOperator(address from) virtual {
if (from != msg.sender) {
if (!_isPriorityOperator(msg.sender)) {
if (_operatorFilteringEnabled()) _revertIfBlocked(msg.sender);
}
}
_;
}
/// @dev Modifier to guard a function from approving a blocked operator..
modifier onlyAllowedOperatorApproval(address operator) virtual {
if (!_isPriorityOperator(operator)) {
if (_operatorFilteringEnabled()) _revertIfBlocked(operator);
}
_;
}
/// @dev Helper function that reverts if the `operator` is blocked by the registry.
function _revertIfBlocked(address operator) private view {
/// @solidity memory-safe-assembly
assembly {
// Store the function selector of `isOperatorAllowed(address,address)`,
// shifted left by 6 bytes, which is enough for 8tb of memory.
// We waste 6-3 = 3 bytes to save on 6 runtime gas (PUSH1 0x224 SHL).
mstore(0x00, 0xc6171134001122334455)
// Store the `address(this)`.
mstore(0x1a, address())
// Store the `operator`.
mstore(0x3a, operator)
// `isOperatorAllowed` always returns true if it does not revert.
if iszero(staticcall(gas(), _OPERATOR_FILTER_REGISTRY, 0x16, 0x44, 0x00, 0x00)) {
// Bubble up the revert if the staticcall reverts.
returndatacopy(0x00, 0x00, returndatasize())
revert(0x00, returndatasize())
}
// We'll skip checking if `from` is inside the blacklist.
// Even though that can block transferring out of wrapper contracts,
// we don't want tokens to be stuck.
// Restore the part of the free memory pointer that was overwritten,
// which is guaranteed to be zero, if less than 8tb of memory is used.
mstore(0x3a, 0)
}
}
/// @dev For deriving contracts to override, so that operator filtering
/// can be turned on / off.
/// Returns true by default.
function _operatorFilteringEnabled() internal view virtual returns (bool) {
return true;
}
/// @dev For deriving contracts to override, so that preferred marketplaces can
/// skip operator filtering, helping users save gas.
/// Returns false for all inputs by default.
function _isPriorityOperator(address) internal view virtual returns (bool) {
return false;
}
}
// SPDX-License-Identifier: CC0-1.0
// Source: https://github.com/tubby-cats/dual-ownership-nft
pragma solidity ^0.8.4;
import '@openzeppelin/contracts/access/Ownable.sol';
abstract contract MultisigOwnable is Ownable {
address public realOwner;
constructor() {
realOwner = msg.sender;
}
modifier onlyRealOwner() {
require(
realOwner == msg.sender,
'MultisigOwnable: caller is not the real owner'
);
_;
}
function transferRealOwnership(address newRealOwner) public onlyRealOwner {
realOwner = newRealOwner;
}
function transferLowerOwnership(address newOwner) public onlyRealOwner {
transferOwnership(newOwner);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1, "Math: mulDiv overflow");
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (interfaces/IERC2981.sol)
pragma solidity ^0.8.0;
import "../utils/introspection/IERC165.sol";
/**
* @dev Interface for the NFT Royalty Standard.
*
* A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
* support for royalty payments across all NFT marketplaces and ecosystem participants.
*
* _Available since v4.5._
*/
interface IERC2981 is IERC165 {
/**
* @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
* exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
*/
function royaltyInfo(
uint256 tokenId,
uint256 salePrice
) external view returns (address receiver, uint256 royaltyAmount);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
File 2 of 2: OperatorFilterRegistry
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.0;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(bytes32 => uint256) _indexes;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._indexes[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slot
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
if (lastIndex != toDeleteIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastValue;
// Update the index for the moved value
set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slot
delete set._indexes[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._indexes[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {EnumerableSet} from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
interface IOperatorFilterRegistry {
function isOperatorAllowed(address registrant, address operator) external returns (bool);
function register(address registrant) external;
function registerAndSubscribe(address registrant, address subscription) external;
function registerAndCopyEntries(address registrant, address registrantToCopy) external;
function updateOperator(address registrant, address operator, bool filtered) external;
function updateOperators(address registrant, address[] calldata operators, bool filtered) external;
function updateCodeHash(address registrant, bytes32 codehash, bool filtered) external;
function updateCodeHashes(address registrant, bytes32[] calldata codeHashes, bool filtered) external;
function subscribe(address registrant, address registrantToSubscribe) external;
function unsubscribe(address registrant, bool copyExistingEntries) external;
function subscriptionOf(address addr) external returns (address registrant);
function subscribers(address registrant) external returns (address[] memory);
function subscriberAt(address registrant, uint256 index) external returns (address);
function copyEntriesOf(address registrant, address registrantToCopy) external;
function isOperatorFiltered(address registrant, address operator) external returns (bool);
function isCodeHashOfFiltered(address registrant, address operatorWithCode) external returns (bool);
function isCodeHashFiltered(address registrant, bytes32 codeHash) external returns (bool);
function filteredOperators(address addr) external returns (address[] memory);
function filteredCodeHashes(address addr) external returns (bytes32[] memory);
function filteredOperatorAt(address registrant, uint256 index) external returns (address);
function filteredCodeHashAt(address registrant, uint256 index) external returns (bytes32);
function isRegistered(address addr) external returns (bool);
function codeHashOf(address addr) external returns (bytes32);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {IOperatorFilterRegistry} from "./IOperatorFilterRegistry.sol";
import {Ownable} from "openzeppelin-contracts/access/Ownable.sol";
import {EnumerableSet} from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
import {OperatorFilterRegistryErrorsAndEvents} from "./OperatorFilterRegistryErrorsAndEvents.sol";
/**
* @title OperatorFilterRegistry
* @notice Borrows heavily from the QQL BlacklistOperatorFilter contract:
* https://github.com/qql-art/contracts/blob/main/contracts/BlacklistOperatorFilter.sol
* @notice This contracts allows tokens or token owners to register specific addresses or codeHashes that may be
* * restricted according to the isOperatorAllowed function.
*/
contract OperatorFilterRegistry is IOperatorFilterRegistry, OperatorFilterRegistryErrorsAndEvents {
using EnumerableSet for EnumerableSet.AddressSet;
using EnumerableSet for EnumerableSet.Bytes32Set;
/// @dev initialized accounts have a nonzero codehash (see https://eips.ethereum.org/EIPS/eip-1052)
/// Note that this will also be a smart contract's codehash when making calls from its constructor.
bytes32 constant EOA_CODEHASH = keccak256("");
mapping(address => EnumerableSet.AddressSet) private _filteredOperators;
mapping(address => EnumerableSet.Bytes32Set) private _filteredCodeHashes;
mapping(address => address) private _registrations;
mapping(address => EnumerableSet.AddressSet) private _subscribers;
/**
* @notice restricts method caller to the address or EIP-173 "owner()"
*/
modifier onlyAddressOrOwner(address addr) {
if (msg.sender != addr) {
try Ownable(addr).owner() returns (address owner) {
if (msg.sender != owner) {
revert OnlyAddressOrOwner();
}
} catch (bytes memory reason) {
if (reason.length == 0) {
revert NotOwnable();
} else {
/// @solidity memory-safe-assembly
assembly {
revert(add(32, reason), mload(reason))
}
}
}
}
_;
}
/**
* @notice Returns true if operator is not filtered for a given token, either by address or codeHash. Also returns
* true if supplied registrant address is not registered.
*/
function isOperatorAllowed(address registrant, address operator) external view returns (bool) {
address registration = _registrations[registrant];
if (registration != address(0)) {
EnumerableSet.AddressSet storage filteredOperatorsRef;
EnumerableSet.Bytes32Set storage filteredCodeHashesRef;
filteredOperatorsRef = _filteredOperators[registration];
filteredCodeHashesRef = _filteredCodeHashes[registration];
if (filteredOperatorsRef.contains(operator)) {
revert AddressFiltered(operator);
}
if (operator.code.length > 0) {
bytes32 codeHash = operator.codehash;
if (filteredCodeHashesRef.contains(codeHash)) {
revert CodeHashFiltered(operator, codeHash);
}
}
}
return true;
}
//////////////////
// AUTH METHODS //
//////////////////
/**
* @notice Registers an address with the registry. May be called by address itself or by EIP-173 owner.
*/
function register(address registrant) external onlyAddressOrOwner(registrant) {
if (_registrations[registrant] != address(0)) {
revert AlreadyRegistered();
}
_registrations[registrant] = registrant;
emit RegistrationUpdated(registrant, true);
}
/**
* @notice Unregisters an address with the registry and removes its subscription. May be called by address itself or by EIP-173 owner.
* Note that this does not remove any filtered addresses or codeHashes.
* Also note that any subscriptions to this registrant will still be active and follow the existing filtered addresses and codehashes.
*/
function unregister(address registrant) external onlyAddressOrOwner(registrant) {
address registration = _registrations[registrant];
if (registration == address(0)) {
revert NotRegistered(registrant);
}
if (registration != registrant) {
_subscribers[registration].remove(registrant);
emit SubscriptionUpdated(registrant, registration, false);
}
_registrations[registrant] = address(0);
emit RegistrationUpdated(registrant, false);
}
/**
* @notice Registers an address with the registry and "subscribes" to another address's filtered operators and codeHashes.
*/
function registerAndSubscribe(address registrant, address subscription) external onlyAddressOrOwner(registrant) {
address registration = _registrations[registrant];
if (registration != address(0)) {
revert AlreadyRegistered();
}
if (registrant == subscription) {
revert CannotSubscribeToSelf();
}
address subscriptionRegistration = _registrations[subscription];
if (subscriptionRegistration == address(0)) {
revert NotRegistered(subscription);
}
if (subscriptionRegistration != subscription) {
revert CannotSubscribeToRegistrantWithSubscription(subscription);
}
_registrations[registrant] = subscription;
_subscribers[subscription].add(registrant);
emit RegistrationUpdated(registrant, true);
emit SubscriptionUpdated(registrant, subscription, true);
}
/**
* @notice Registers an address with the registry and copies the filtered operators and codeHashes from another
* address without subscribing.
*/
function registerAndCopyEntries(address registrant, address registrantToCopy)
external
onlyAddressOrOwner(registrant)
{
if (registrantToCopy == registrant) {
revert CannotCopyFromSelf();
}
address registration = _registrations[registrant];
if (registration != address(0)) {
revert AlreadyRegistered();
}
address registrantRegistration = _registrations[registrantToCopy];
if (registrantRegistration == address(0)) {
revert NotRegistered(registrantToCopy);
}
_registrations[registrant] = registrant;
emit RegistrationUpdated(registrant, true);
_copyEntries(registrant, registrantToCopy);
}
/**
* @notice Update an operator address for a registered address - when filtered is true, the operator is filtered.
*/
function updateOperator(address registrant, address operator, bool filtered)
external
onlyAddressOrOwner(registrant)
{
address registration = _registrations[registrant];
if (registration == address(0)) {
revert NotRegistered(registrant);
}
if (registration != registrant) {
revert CannotUpdateWhileSubscribed(registration);
}
EnumerableSet.AddressSet storage filteredOperatorsRef = _filteredOperators[registrant];
if (!filtered) {
bool removed = filteredOperatorsRef.remove(operator);
if (!removed) {
revert AddressNotFiltered(operator);
}
} else {
bool added = filteredOperatorsRef.add(operator);
if (!added) {
revert AddressAlreadyFiltered(operator);
}
}
emit OperatorUpdated(registrant, operator, filtered);
}
/**
* @notice Update a codeHash for a registered address - when filtered is true, the codeHash is filtered.
*/
function updateCodeHash(address registrant, bytes32 codeHash, bool filtered)
external
onlyAddressOrOwner(registrant)
{
if (codeHash == EOA_CODEHASH) {
revert CannotFilterEOAs();
}
address registration = _registrations[registrant];
if (registration == address(0)) {
revert NotRegistered(registrant);
}
if (registration != registrant) {
revert CannotUpdateWhileSubscribed(registration);
}
EnumerableSet.Bytes32Set storage filteredCodeHashesRef = _filteredCodeHashes[registrant];
if (!filtered) {
bool removed = filteredCodeHashesRef.remove(codeHash);
if (!removed) {
revert CodeHashNotFiltered(codeHash);
}
} else {
bool added = filteredCodeHashesRef.add(codeHash);
if (!added) {
revert CodeHashAlreadyFiltered(codeHash);
}
}
emit CodeHashUpdated(registrant, codeHash, filtered);
}
/**
* @notice Update multiple operators for a registered address - when filtered is true, the operators will be filtered. Reverts on duplicates.
*/
function updateOperators(address registrant, address[] calldata operators, bool filtered)
external
onlyAddressOrOwner(registrant)
{
address registration = _registrations[registrant];
if (registration == address(0)) {
revert NotRegistered(registrant);
}
if (registration != registrant) {
revert CannotUpdateWhileSubscribed(registration);
}
EnumerableSet.AddressSet storage filteredOperatorsRef = _filteredOperators[registrant];
uint256 operatorsLength = operators.length;
unchecked {
if (!filtered) {
for (uint256 i = 0; i < operatorsLength; ++i) {
address operator = operators[i];
bool removed = filteredOperatorsRef.remove(operator);
if (!removed) {
revert AddressNotFiltered(operator);
}
}
} else {
for (uint256 i = 0; i < operatorsLength; ++i) {
address operator = operators[i];
bool added = filteredOperatorsRef.add(operator);
if (!added) {
revert AddressAlreadyFiltered(operator);
}
}
}
}
emit OperatorsUpdated(registrant, operators, filtered);
}
/**
* @notice Update multiple codeHashes for a registered address - when filtered is true, the codeHashes will be filtered. Reverts on duplicates.
*/
function updateCodeHashes(address registrant, bytes32[] calldata codeHashes, bool filtered)
external
onlyAddressOrOwner(registrant)
{
address registration = _registrations[registrant];
if (registration == address(0)) {
revert NotRegistered(registrant);
}
if (registration != registrant) {
revert CannotUpdateWhileSubscribed(registration);
}
EnumerableSet.Bytes32Set storage filteredCodeHashesRef = _filteredCodeHashes[registrant];
uint256 codeHashesLength = codeHashes.length;
unchecked {
if (!filtered) {
for (uint256 i = 0; i < codeHashesLength; ++i) {
bytes32 codeHash = codeHashes[i];
bool removed = filteredCodeHashesRef.remove(codeHash);
if (!removed) {
revert CodeHashNotFiltered(codeHash);
}
}
} else {
for (uint256 i = 0; i < codeHashesLength; ++i) {
bytes32 codeHash = codeHashes[i];
if (codeHash == EOA_CODEHASH) {
revert CannotFilterEOAs();
}
bool added = filteredCodeHashesRef.add(codeHash);
if (!added) {
revert CodeHashAlreadyFiltered(codeHash);
}
}
}
}
emit CodeHashesUpdated(registrant, codeHashes, filtered);
}
/**
* @notice Subscribe an address to another registrant's filtered operators and codeHashes. Will remove previous
* subscription if present.
* Note that accounts with subscriptions may go on to subscribe to other accounts - in this case,
* subscriptions will not be forwarded. Instead the former subscription's existing entries will still be
* used.
*/
function subscribe(address registrant, address newSubscription) external onlyAddressOrOwner(registrant) {
if (registrant == newSubscription) {
revert CannotSubscribeToSelf();
}
if (newSubscription == address(0)) {
revert CannotSubscribeToZeroAddress();
}
address registration = _registrations[registrant];
if (registration == address(0)) {
revert NotRegistered(registrant);
}
if (registration == newSubscription) {
revert AlreadySubscribed(newSubscription);
}
address newSubscriptionRegistration = _registrations[newSubscription];
if (newSubscriptionRegistration == address(0)) {
revert NotRegistered(newSubscription);
}
if (newSubscriptionRegistration != newSubscription) {
revert CannotSubscribeToRegistrantWithSubscription(newSubscription);
}
if (registration != registrant) {
_subscribers[registration].remove(registrant);
emit SubscriptionUpdated(registrant, registration, false);
}
_registrations[registrant] = newSubscription;
_subscribers[newSubscription].add(registrant);
emit SubscriptionUpdated(registrant, newSubscription, true);
}
/**
* @notice Unsubscribe an address from its current subscribed registrant, and optionally copy its filtered operators and codeHashes.
*/
function unsubscribe(address registrant, bool copyExistingEntries) external onlyAddressOrOwner(registrant) {
address registration = _registrations[registrant];
if (registration == address(0)) {
revert NotRegistered(registrant);
}
if (registration == registrant) {
revert NotSubscribed();
}
_subscribers[registration].remove(registrant);
_registrations[registrant] = registrant;
emit SubscriptionUpdated(registrant, registration, false);
if (copyExistingEntries) {
_copyEntries(registrant, registration);
}
}
/**
* @notice Copy filtered operators and codeHashes from a different registrantToCopy to addr.
*/
function copyEntriesOf(address registrant, address registrantToCopy) external onlyAddressOrOwner(registrant) {
if (registrant == registrantToCopy) {
revert CannotCopyFromSelf();
}
address registration = _registrations[registrant];
if (registration == address(0)) {
revert NotRegistered(registrant);
}
if (registration != registrant) {
revert CannotUpdateWhileSubscribed(registration);
}
address registrantRegistration = _registrations[registrantToCopy];
if (registrantRegistration == address(0)) {
revert NotRegistered(registrantToCopy);
}
_copyEntries(registrant, registrantToCopy);
}
/// @dev helper to copy entries from registrantToCopy to registrant and emit events
function _copyEntries(address registrant, address registrantToCopy) private {
EnumerableSet.AddressSet storage filteredOperatorsRef = _filteredOperators[registrantToCopy];
EnumerableSet.Bytes32Set storage filteredCodeHashesRef = _filteredCodeHashes[registrantToCopy];
uint256 filteredOperatorsLength = filteredOperatorsRef.length();
uint256 filteredCodeHashesLength = filteredCodeHashesRef.length();
unchecked {
for (uint256 i = 0; i < filteredOperatorsLength; ++i) {
address operator = filteredOperatorsRef.at(i);
bool added = _filteredOperators[registrant].add(operator);
if (added) {
emit OperatorUpdated(registrant, operator, true);
}
}
for (uint256 i = 0; i < filteredCodeHashesLength; ++i) {
bytes32 codehash = filteredCodeHashesRef.at(i);
bool added = _filteredCodeHashes[registrant].add(codehash);
if (added) {
emit CodeHashUpdated(registrant, codehash, true);
}
}
}
}
//////////////////
// VIEW METHODS //
//////////////////
/**
* @notice Get the subscription address of a given registrant, if any.
*/
function subscriptionOf(address registrant) external view returns (address subscription) {
subscription = _registrations[registrant];
if (subscription == address(0)) {
revert NotRegistered(registrant);
} else if (subscription == registrant) {
subscription = address(0);
}
}
/**
* @notice Get the set of addresses subscribed to a given registrant.
* Note that order is not guaranteed as updates are made.
*/
function subscribers(address registrant) external view returns (address[] memory) {
return _subscribers[registrant].values();
}
/**
* @notice Get the subscriber at a given index in the set of addresses subscribed to a given registrant.
* Note that order is not guaranteed as updates are made.
*/
function subscriberAt(address registrant, uint256 index) external view returns (address) {
return _subscribers[registrant].at(index);
}
/**
* @notice Returns true if operator is filtered by a given address or its subscription.
*/
function isOperatorFiltered(address registrant, address operator) external view returns (bool) {
address registration = _registrations[registrant];
if (registration != registrant) {
return _filteredOperators[registration].contains(operator);
}
return _filteredOperators[registrant].contains(operator);
}
/**
* @notice Returns true if a codeHash is filtered by a given address or its subscription.
*/
function isCodeHashFiltered(address registrant, bytes32 codeHash) external view returns (bool) {
address registration = _registrations[registrant];
if (registration != registrant) {
return _filteredCodeHashes[registration].contains(codeHash);
}
return _filteredCodeHashes[registrant].contains(codeHash);
}
/**
* @notice Returns true if the hash of an address's code is filtered by a given address or its subscription.
*/
function isCodeHashOfFiltered(address registrant, address operatorWithCode) external view returns (bool) {
bytes32 codeHash = operatorWithCode.codehash;
address registration = _registrations[registrant];
if (registration != registrant) {
return _filteredCodeHashes[registration].contains(codeHash);
}
return _filteredCodeHashes[registrant].contains(codeHash);
}
/**
* @notice Returns true if an address has registered
*/
function isRegistered(address registrant) external view returns (bool) {
return _registrations[registrant] != address(0);
}
/**
* @notice Returns a list of filtered operators for a given address or its subscription.
*/
function filteredOperators(address registrant) external view returns (address[] memory) {
address registration = _registrations[registrant];
if (registration != registrant) {
return _filteredOperators[registration].values();
}
return _filteredOperators[registrant].values();
}
/**
* @notice Returns the set of filtered codeHashes for a given address or its subscription.
* Note that order is not guaranteed as updates are made.
*/
function filteredCodeHashes(address registrant) external view returns (bytes32[] memory) {
address registration = _registrations[registrant];
if (registration != registrant) {
return _filteredCodeHashes[registration].values();
}
return _filteredCodeHashes[registrant].values();
}
/**
* @notice Returns the filtered operator at the given index of the set of filtered operators for a given address or
* its subscription.
* Note that order is not guaranteed as updates are made.
*/
function filteredOperatorAt(address registrant, uint256 index) external view returns (address) {
address registration = _registrations[registrant];
if (registration != registrant) {
return _filteredOperators[registration].at(index);
}
return _filteredOperators[registrant].at(index);
}
/**
* @notice Returns the filtered codeHash at the given index of the list of filtered codeHashes for a given address or
* its subscription.
* Note that order is not guaranteed as updates are made.
*/
function filteredCodeHashAt(address registrant, uint256 index) external view returns (bytes32) {
address registration = _registrations[registrant];
if (registration != registrant) {
return _filteredCodeHashes[registration].at(index);
}
return _filteredCodeHashes[registrant].at(index);
}
/// @dev Convenience method to compute the code hash of an arbitrary contract
function codeHashOf(address a) external view returns (bytes32) {
return a.codehash;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract OperatorFilterRegistryErrorsAndEvents {
error CannotFilterEOAs();
error AddressAlreadyFiltered(address operator);
error AddressNotFiltered(address operator);
error CodeHashAlreadyFiltered(bytes32 codeHash);
error CodeHashNotFiltered(bytes32 codeHash);
error OnlyAddressOrOwner();
error NotRegistered(address registrant);
error AlreadyRegistered();
error AlreadySubscribed(address subscription);
error NotSubscribed();
error CannotUpdateWhileSubscribed(address subscription);
error CannotSubscribeToSelf();
error CannotSubscribeToZeroAddress();
error NotOwnable();
error AddressFiltered(address filtered);
error CodeHashFiltered(address account, bytes32 codeHash);
error CannotSubscribeToRegistrantWithSubscription(address registrant);
error CannotCopyFromSelf();
event RegistrationUpdated(address indexed registrant, bool indexed registered);
event OperatorUpdated(address indexed registrant, address indexed operator, bool indexed filtered);
event OperatorsUpdated(address indexed registrant, address[] operators, bool indexed filtered);
event CodeHashUpdated(address indexed registrant, bytes32 indexed codeHash, bool indexed filtered);
event CodeHashesUpdated(address indexed registrant, bytes32[] codeHashes, bool indexed filtered);
event SubscriptionUpdated(address indexed registrant, address indexed subscription, bool indexed subscribed);
}