ETH Price: $2,072.99 (+4.10%)

Transaction Decoder

Block:
16922244 at Mar-28-2023 12:21:59 AM +UTC
Transaction Fee:
0.002240084753967412 ETH $4.64
Gas Used:
64,196 Gas / 34.894459997 Gwei

Emitted Events:

152 0xd57474e76c9ebecc01b65a1494f0a1211df7bcd8.0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31( 0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31, 0x00000000000000000000000082a36805f7439c6879b902d03e1a52ecdd4669d1, 0x0000000000000000000000001e0049783f008a0085193e00003d00cd54003c71, 0000000000000000000000000000000000000000000000000000000000000001 )

Account State Difference:

  Address   Before After State Difference Code
0x82a36805...cDD4669D1
1.132812047722339547 Eth
Nonce: 979
1.130571962968372135 Eth
Nonce: 980
0.002240084753967412
0xD57474E7...11df7bCd8
(Flashbots: Builder)
1.254421437521362524 Eth1.254428175366291944 Eth0.00000673784492942

Execution Trace

The Dev: DEV Token.a22cb465( )
  • 0x53a426781a70c4a84b5d64209920bc949a87b605.a22cb465( )
    • OperatorFilterRegistry.isOperatorAllowed( registrant=0xD57474E76C9EBecC01b65a1494F0a1211df7bCd8, operator=0x1E0049783F008A0085193E00003D00cd54003c71 ) => ( True )
      // 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);
      }