Transaction Hash:
Block:
22408635 at May-04-2025 06:31:47 AM +UTC
Transaction Fee:
0.000097193064507404 ETH
$0.35
Gas Used:
70,948 Gas / 1.369919723 Gwei
Emitted Events:
| 210 |
TransparentUpgradeableProxy.0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31( 0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31, 0x0000000000000000000000005b70facd084315d7466ab04ddf7d063f6569f71b, 0x0000000000000000000000001e0049783f008a0085193e00003d00cd54003c71, 0000000000000000000000000000000000000000000000000000000000000001 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x5B70fAcd...f6569f71b |
0.0050944 Eth
Nonce: 0
|
0.004997206935492596 Eth
Nonce: 1
| 0.000097193064507404 | ||
| 0x5CC5B05a...e50F90c38 | |||||
|
0x95222290...5CC4BAfe5
Miner
| (beaverbuild) | 17.399502747341709362 Eth | 17.399573695341709362 Eth | 0.000070948 |
Execution Trace
TransparentUpgradeableProxy.a22cb465( )
LandV3.setApprovalForAll( operator=0x1E0049783F008A0085193E00003D00cd54003c71, approved=True )
-
OperatorFilterRegistry.isOperatorAllowed( registrant=0x5CC5B05a8A13E3fBDB0BB9FcCd98D38e50F90c38, operator=0x1E0049783F008A0085193E00003D00cd54003c71 ) => ( True )
-
setApprovalForAll[LandV3 (ln:1543)]
setApprovalForAll[LandV3 (ln:1544)]
File 1 of 3: TransparentUpgradeableProxy
File 2 of 3: LandV3
File 3 of 3: OperatorFilterRegistry
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
/**
* @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
* instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
* be specified by overriding the virtual {_implementation} function.
*
* Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
* different contract through the {_delegate} function.
*
* The success and return data of the delegated call will be returned back to the caller of the proxy.
*/
abstract contract Proxy {
/**
* @dev Delegates the current call to `implementation`.
*
* This function does not return to its internall call site, it will return directly to the external caller.
*/
function _delegate(address implementation) internal {
// solhint-disable-next-line no-inline-assembly
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
/**
* @dev This is a virtual function that should be overriden so it returns the address to which the fallback function
* and {_fallback} should delegate.
*/
function _implementation() internal virtual view returns (address);
/**
* @dev Delegates the current call to the address returned by `_implementation()`.
*
* This function does not return to its internall call site, it will return directly to the external caller.
*/
function _fallback() internal {
_beforeFallback();
_delegate(_implementation());
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
* function in the contract matches the call data.
*/
fallback () payable external {
_fallback();
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
* is empty.
*/
receive () payable external {
_fallback();
}
/**
* @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
* call, or as part of the Solidity `fallback` or `receive` functions.
*
* If overriden should call `super._beforeFallback()`.
*/
function _beforeFallback() internal virtual {
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
import "./UpgradeableProxy.sol";
/**
* @dev This contract implements a proxy that is upgradeable by an admin.
*
* To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
* clashing], which can potentially be used in an attack, this contract uses the
* https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
* things that go hand in hand:
*
* 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
* that call matches one of the admin functions exposed by the proxy itself.
* 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
* implementation. If the admin tries to call a function on the implementation it will fail with an error that says
* "admin cannot fallback to proxy target".
*
* These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
* the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
* to sudden errors when trying to call a function from the proxy implementation.
*
* Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
* you should think of the `ProxyAdmin` instance as the real administrative inerface of your proxy.
*/
contract TransparentUpgradeableProxy is UpgradeableProxy {
/**
* @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
* optionally initialized with `_data` as explained in {UpgradeableProxy-constructor}.
*/
constructor(address initialLogic, address initialAdmin, bytes memory _data) payable UpgradeableProxy(initialLogic, _data) {
assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
_setAdmin(initialAdmin);
}
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 private constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
*/
modifier ifAdmin() {
if (msg.sender == _admin()) {
_;
} else {
_fallback();
}
}
/**
* @dev Returns the current admin.
*
* NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
*/
function admin() external ifAdmin returns (address) {
return _admin();
}
/**
* @dev Returns the current implementation.
*
* NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
*/
function implementation() external ifAdmin returns (address) {
return _implementation();
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {AdminChanged} event.
*
* NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.
*/
function changeAdmin(address newAdmin) external ifAdmin {
require(newAdmin != address(0), "TransparentUpgradeableProxy: new admin is the zero address");
emit AdminChanged(_admin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev Upgrade the implementation of the proxy.
*
* NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.
*/
function upgradeTo(address newImplementation) external ifAdmin {
_upgradeTo(newImplementation);
}
/**
* @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
* by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
* proxied contract.
*
* NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.
*/
function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
_upgradeTo(newImplementation);
// solhint-disable-next-line avoid-low-level-calls
(bool success,) = newImplementation.delegatecall(data);
require(success);
}
/**
* @dev Returns the current admin.
*/
function _admin() internal view returns (address adm) {
bytes32 slot = _ADMIN_SLOT;
// solhint-disable-next-line no-inline-assembly
assembly {
adm := sload(slot)
}
}
/**
* @dev Stores a new address in the EIP1967 admin slot.
*/
function _setAdmin(address newAdmin) private {
bytes32 slot = _ADMIN_SLOT;
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(slot, newAdmin)
}
}
/**
* @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.
*/
function _beforeFallback() internal override virtual {
require(msg.sender != _admin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
super._beforeFallback();
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
import "./Proxy.sol";
import "../utils/Address.sol";
/**
* @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
* implementation address that can be changed. This address is stored in storage in the location specified by
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
* implementation behind the proxy.
*
* Upgradeability is only provided internally through {_upgradeTo}. For an externally upgradeable proxy see
* {TransparentUpgradeableProxy}.
*/
contract UpgradeableProxy is Proxy {
/**
* @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
*
* If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
* function call, and allows initializating the storage of the proxy like a Solidity constructor.
*/
constructor(address _logic, bytes memory _data) payable {
assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
_setImplementation(_logic);
if(_data.length > 0) {
// solhint-disable-next-line avoid-low-level-calls
(bool success,) = _logic.delegatecall(_data);
require(success);
}
}
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 private constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev Returns the current implementation address.
*/
function _implementation() internal override view returns (address impl) {
bytes32 slot = _IMPLEMENTATION_SLOT;
// solhint-disable-next-line no-inline-assembly
assembly {
impl := sload(slot)
}
}
/**
* @dev Upgrades the proxy to a new implementation.
*
* Emits an {Upgraded} event.
*/
function _upgradeTo(address newImplementation) internal {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
}
/**
* @dev Stores a new address in the EIP1967 implementation slot.
*/
function _setImplementation(address newImplementation) private {
require(Address.isContract(newImplementation), "UpgradeableProxy: new implementation is not a contract");
bytes32 slot = _IMPLEMENTATION_SLOT;
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(slot, newImplementation)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
// solhint-disable-next-line no-inline-assembly
assembly { codehash := extcodehash(account) }
return (codehash != accountHash && codehash != 0x0);
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain`call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
return _functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
return _functionCallWithValue(target, data, value, errorMessage);
}
function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {
require(isContract(target), "Address: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.call{ value: weiValue }(data);
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
File 2 of 3: LandV3
pragma solidity 0.5.9;
contract AdminV2 {
address internal _admin;
event AdminChanged(address oldAdmin, address newAdmin);
/// @notice gives the current administrator of this contract.
/// @return the current administrator of this contract.
function getAdmin() external view returns (address) {
return _admin;
}
/// @notice change the administrator to be `newAdmin`.
/// @param newAdmin address of the new administrator.
function changeAdmin(address newAdmin) external {
address admin = _admin;
require(msg.sender == admin, "only admin can change admin");
require(newAdmin != admin, "it can be only changed to a new admin");
emit AdminChanged(admin, newAdmin);
_admin = newAdmin;
}
modifier onlyAdmin() {
require (msg.sender == _admin, "only admin allowed");
_;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.5.9;
import {AdminV2} from "./AdminV2.sol";
import {AddressUtils} from "../../contracts_common/Libraries/AddressUtils.sol";
/// @title MetaTransactionReceiverV2
/// @author The Sandbox
/// @notice Implements meta-transactions
/// @dev This contract permits to give an address the capacity to perform meta-transactions on behalf of any address
contract MetaTransactionReceiverV2 is AdminV2 {
using AddressUtils for address;
mapping(address => bool) internal _metaTransactionContracts;
event MetaTransactionProcessor(address indexed metaTransactionProcessor, bool enabled);
/// @notice Enable or disable the ability of `metaTransactionProcessor` to perform meta-tx (metaTransactionProcessor rights).
/// @param metaTransactionProcessor address that will be given/removed metaTransactionProcessor rights.
/// @param enabled set whether the metaTransactionProcessor is enabled or disabled.
function setMetaTransactionProcessor(address metaTransactionProcessor, bool enabled) public onlyAdmin {
require(
metaTransactionProcessor.isContract(),
"only contracts can be meta transaction processor"
);
_setMetaTransactionProcessor(metaTransactionProcessor, enabled);
}
/// @param metaTransactionProcessor address of the operator
/// @param enabled is it enabled
function _setMetaTransactionProcessor(address metaTransactionProcessor, bool enabled) internal {
_metaTransactionContracts[metaTransactionProcessor] = enabled;
emit MetaTransactionProcessor(metaTransactionProcessor, enabled);
}
/// @notice check whether address `who` is given meta-transaction execution rights.
/// @param who The address to query.
/// @return whether the address has meta-transaction execution rights.
function isMetaTransactionProcessor(address who) external view returns(bool) {
return _metaTransactionContracts[who];
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.5.9;
import {AdminV2} from "./AdminV2.sol";
/// @title SuperOperatorsV2
/// @author The Sandbox
/// @notice Implements a super operator role on the contract
/// @dev The contract inheriting SuperOperatorsV2 is able to use a super operator role
contract SuperOperatorsV2 is AdminV2 {
mapping(address => bool) internal _superOperators;
event SuperOperator(address indexed superOperator, bool enabled);
/// @notice Enable or disable the ability of `superOperator` to transfer tokens of all (superOperator rights).
/// @param superOperator address that will be given/removed superOperator right.
/// @param enabled set whether the superOperator is enabled or disabled.
function setSuperOperator(address superOperator, bool enabled) external onlyAdmin {
require(
superOperator != address(0),
"address 0 is not allowed as super operator"
);
require(
enabled != _superOperators[superOperator],
"the status should be different than the current one"
);
_superOperators[superOperator] = enabled;
emit SuperOperator(superOperator, enabled);
}
/// @notice check whether address `who` is given superOperator rights.
/// @param who The address to query.
/// @return whether the address has superOperator rights.
function isSuperOperator(address who) public view returns (bool) {
return _superOperators[who];
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.5.9;
/**
* @title ERC721 Non-Fungible Token Standard basic interface
* @dev see https://eips.ethereum.org/EIPS/eip-721
*/
interface ERC721Events {
event Transfer(
address indexed _from,
address indexed _to,
uint256 indexed _tokenId
);
event Approval(
address indexed _owner,
address indexed _approved,
uint256 indexed _tokenId
);
event ApprovalForAll(
address indexed _owner,
address indexed _operator,
bool _approved
);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.5.9;
/**
* @title ERC721MandatoryTokenReceiver
* @author The Sandbox
* @notice Interface for any contract that wants to support safeBatchTransfers
* from ERC721 asset contracts.
* @dev The ERC-165 identifier for this interface is 0x5e8bf644.
*/
interface ERC721MandatoryTokenReceiver {
/**
* @notice Whenever tokens are transferred to this contract via {IERC721-safeBatchTransferFrom}
* by `operator` from `from`, this function is called.
* @param operator sender
* @param from owner of the tokens
* @param ids token ids
* @param data extra data
* @return 0x4b808c46 if the transfer is a success
*/
function onERC721BatchReceived(
address operator,
address from,
uint256[] calldata ids,
bytes calldata data
) external returns (bytes4); // needs to return 0x4b808c46
/**
* @notice Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
* @param operator sender
* @param from owner of the token
* @param tokenId token id
* @param data extra data
* @return 0x150b7a02 if the transfer is a success
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4); // needs to return 0x150b7a02
}
// SPDX-License-Identifier: MIT
// solhint-disable-next-line compiler-fixed
pragma solidity 0.5.9;
/**
* @title ERC721TokenReceiver
* @author The Sandbox
* @notice Handle the receipt of an NFT
*/
interface ERC721TokenReceiver {
/**
* @notice Handle the receipt of an NFT
* @dev The ERC721 smart contract calls this function on the recipient
* after a `transfer`. This function MAY throw to revert and reject the
* transfer. Return of other than the magic value MUST result in the
* transaction being reverted.
* Note: the contract address is always the message sender.
* @param operator The address which called `safeTransferFrom` function
* @param from The address which previously owned the token
* @param tokenId The NFT identifier which is being transferred
* @param data Additional data with no specified format
* @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` unless throwing
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.5.9;
/**
* @title AddressUtils
* @author The Sandbox
* @notice Helper to manipulate addresses
*/
library AddressUtils {
/**
* @dev Cast the address to be payable
* @param _address target address
* @return a payable address
*/
function toPayable(address _address) internal pure returns (address payable) {
return address(uint160(_address));
}
/**
* @dev Check if the address is a contract
* @param addr target address
* @return is it a contract
*/
function isContract(address addr) internal view returns (bool) {
// for accounts without code, i.e. `keccak256('')`:
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
bytes32 codehash;
// solium-disable-next-line security/no-inline-assembly
assembly {
codehash := extcodehash(addr)
}
return (codehash != 0x0 && codehash != accountHash);
}
}
// SPDX-License-Identifier: MIT
/* solhint-disable func-order, code-complexity */
pragma solidity 0.5.9;
import {AddressUtils} from "../../contracts_common/Libraries/AddressUtils.sol";
import {ERC721TokenReceiver} from "../../contracts_common/Interfaces/ERC721TokenReceiver.sol";
import {ERC721Events} from "../../contracts_common/Interfaces/ERC721Events.sol";
import {SuperOperatorsV2} from "../../contracts_common/BaseWithStorage/SuperOperatorsV2.sol";
import {MetaTransactionReceiverV2} from "../../contracts_common/BaseWithStorage/MetaTransactionReceiverV2.sol";
import {ERC721MandatoryTokenReceiver} from "../../contracts_common/Interfaces/ERC721MandatoryTokenReceiver.sol";
/**
* @title ERC721BaseTokenV2
* @author The Sandbox
* @notice Basic functionalities of a NFT
* @dev ERC721 implementation that supports meta-transactions and super operators
*/
contract ERC721BaseTokenV2 is ERC721Events, SuperOperatorsV2, MetaTransactionReceiverV2 {
using AddressUtils for address;
bytes4 internal constant _ERC721_RECEIVED = 0x150b7a02;
bytes4 internal constant _ERC721_BATCH_RECEIVED = 0x4b808c46;
bytes4 internal constant ERC165ID = 0x01ffc9a7;
bytes4 internal constant ERC721_MANDATORY_RECEIVER = 0x5e8bf644;
/// @notice Number of NFT an address own
mapping (address => uint256) public _numNFTPerAddress;
/// @notice Token ids per address
mapping (uint256 => uint256) public _owners;
/// @notice Operators for each owner address for all tokens
mapping (address => mapping(address => bool)) public _operatorsForAll;
/// @notice Operator for each token id
mapping (uint256 => address) public _operators;
bool internal _initialized;
modifier initializer() {
require(!_initialized, "ERC721BaseToken: Contract already initialized");
_;
}
/**
* @notice Initializes the contract with the meta-transaction contract & admin
* @param metaTransactionContract Authorized contract for meta-transactions
* @param admin Admin of the contract
*/
function initialize (
address metaTransactionContract,
address admin
) public initializer {
_admin = admin;
_setMetaTransactionProcessor(metaTransactionContract, true);
_initialized = true;
emit AdminChanged(address(0), _admin);
}
/**
* @param from Sender address
* @param to Recipient address
* @param id Token id to transfer
*/
function _transferFrom(
address from,
address to,
uint256 id
) internal {
_numNFTPerAddress[from]--;
_numNFTPerAddress[to]++;
_owners[id] = uint256(to);
emit Transfer(from, to, id);
}
/**
* @notice Return the number of Land owned by an address
* @param owner The address to look for
* @return The number of Land token owned by the address
*/
function balanceOf(address owner) external view returns (uint256) {
require(owner != address(0), "owner is zero address");
return _numNFTPerAddress[owner];
}
/**
* @param id token id
* @return address of the owner
*/
function _ownerOf(uint256 id) internal view returns (address) {
return address(_owners[id]);
}
/**
* @param id Token id
* @return owner Address of the token's owner
* @return operatorEnabled Is he an operator
*/
function _ownerAndOperatorEnabledOf(uint256 id) internal view returns (address owner, bool operatorEnabled) {
uint256 data = _owners[id];
owner = address(data);
operatorEnabled = (data / 2**255) == 1;
}
/**
* @notice Return the owner of a Land
* @param id The id of the Land
* @return The address of the owner
*/
function ownerOf(uint256 id) external view returns (address owner) {
owner = _ownerOf(id);
require(owner != address(0), "token does not exist");
}
/**
* @param owner The address giving the approval
* @param operator The address receiving the approval
* @param id The id of the token
*/
function _approveFor(
address owner,
address operator,
uint256 id
) internal {
if (operator == address(0)) {
_owners[id] = uint256(owner); // no need to resset the operator, it will be overriden next time
} else {
_owners[id] = uint256(owner) + 2**255;
_operators[id] = operator;
}
emit Approval(owner, operator, id);
}
/**
* @notice Approve an operator to spend tokens on the sender behalf
* @param sender The address giving the approval
* @param operator The address receiving the approval
* @param id The id of the token
*/
function approveFor(
address sender,
address operator,
uint256 id
) public {
address owner = _ownerOf(id);
require(sender != address(0), "sender is zero address");
require(
msg.sender == sender ||
_metaTransactionContracts[msg.sender] ||
_operatorsForAll[sender][msg.sender] ||
_superOperators[msg.sender],
"not authorized to approve"
);
require(owner == sender, "owner != sender");
_approveFor(owner, operator, id);
}
/**
* @notice Approve an operator to spend tokens on the sender behalf
* @param operator The address receiving the approval
* @param id The id of the token
*/
function approve(address operator, uint256 id) public {
address owner = _ownerOf(id);
require(owner != address(0), "token does not exist");
require(
owner == msg.sender || _operatorsForAll[owner][msg.sender] || _superOperators[msg.sender],
"not authorized to approve"
);
_approveFor(owner, operator, id);
}
/**
* @notice Get the approved operator for a specific token
* @param id The id of the token
* @return The address of the operator
*/
function getApproved(uint256 id) external view returns (address) {
(address owner, bool operatorEnabled) = _ownerAndOperatorEnabledOf(id);
require(owner != address(0), "token does not exist");
if (operatorEnabled) {
return _operators[id];
} else {
return address(0);
}
}
/**
* @param from The sender of the token
* @param to The recipient of the token
* @param id The id of the token
* @return is it a meta-tx
*/
function _checkTransfer(
address from,
address to,
uint256 id
) internal view returns (bool isMetaTx) {
(address owner, bool operatorEnabled) = _ownerAndOperatorEnabledOf(id);
require(owner != address(0), "token does not exist");
require(owner == from, "not owner in _checkTransfer");
require(to != address(0), "can't send to zero address");
if (msg.sender != from) {
if(_metaTransactionContracts[msg.sender]) {
return true;
}
require(
_operatorsForAll[from][msg.sender] ||
(operatorEnabled && _operators[id] == msg.sender) ||
_superOperators[msg.sender],
"not approved to transfer"
);
}
}
/**
* @dev Checks if the target contract supports the given interface & doesn't exceed 10000 gas
* @param _contract The target contract
* @param interfaceId The interface id
* @return if the call is a success
*/
function _checkInterfaceWith10000Gas(address _contract, bytes4 interfaceId)
internal
view
returns (bool)
{
bool success;
bool result;
bytes memory call_data = abi.encodeWithSelector(ERC165ID, interfaceId);
// solium-disable-next-line security/no-inline-assembly
assembly {
let call_ptr := add(0x20, call_data)
let call_size := mload(call_data)
let output := mload(0x40) // Find empty storage location using "free memory pointer"
mstore(output, 0x0)
success := staticcall(10000, _contract, call_ptr, call_size, output, 0x20) // 32 bytes
result := mload(output)
}
// (10000 / 63) "not enough for supportsInterface(...)" // consume all gas, so caller can potentially know that there was not enough gas
assert(gasleft() > 158);
return success && result;
}
/**
* @notice Transfer a token between 2 addresses
* @param from The sender of the token
* @param to The recipient of the token
* @param id The id of the token
*/
function transferFrom(
address from,
address to,
uint256 id
) public {
bool metaTx = _checkTransfer(from, to, id);
_transferFrom(from, to, id);
if (to.isContract() && _checkInterfaceWith10000Gas(to, ERC721_MANDATORY_RECEIVER)) {
require(
_checkOnERC721Received(metaTx ? from : msg.sender, from, to, id, ""),
"erc721 transfer rejected by to"
);
}
}
/**
* @notice Transfer a token between 2 addresses letting the receiver knows of the transfer
* @param from The sender of the token
* @param to The recipient of the token
* @param id The id of the token
* @param data Additional data
*/
function safeTransferFrom(
address from,
address to,
uint256 id,
bytes memory data
) public {
bool metaTx = _checkTransfer(from, to, id);
_transferFrom(from, to, id);
if (to.isContract()) {
require(
_checkOnERC721Received(metaTx ? from : msg.sender, from, to, id, data),
"ERC721: transfer rejected by to"
);
}
}
/**
* @notice Transfer a token between 2 addresses letting the receiver knows of the transfer
* @param from The send of the token
* @param to The recipient of the token
* @param id The id of the token
*/
function safeTransferFrom(
address from,
address to,
uint256 id
) external {
safeTransferFrom(from, to, id, "");
}
/**
* @notice Transfer many tokens between 2 addresses
* @param from The sender of the token
* @param to The recipient of the token
* @param ids The ids of the tokens
* @param data additional data
*/
function batchTransferFrom(
address from,
address to,
uint256[] calldata ids,
bytes calldata data
) external {
_batchTransferFrom(from, to, ids, data, false);
}
/**
* @param from The sender of the token
* @param to The recipient of the token
* @param ids The ids of the tokens
* @param data additional data
* @param safe checks the target contract
*/
function _batchTransferFrom(
address from,
address to,
uint256[] memory ids,
bytes memory data,
bool safe
) internal {
bool metaTx = msg.sender != from && _metaTransactionContracts[msg.sender];
bool authorized =
msg.sender == from || metaTx || _operatorsForAll[from][msg.sender] || _superOperators[msg.sender];
require(from != address(0), "from is zero address");
require(to != address(0), "can't send to zero address");
uint256 numTokens = ids.length;
for (uint256 i = 0; i < numTokens; i++) {
uint256 id = ids[i];
(address owner, bool operatorEnabled) = _ownerAndOperatorEnabledOf(id);
require(owner == from, "not owner in batchTransferFrom");
require(authorized || (operatorEnabled && _operators[id] == msg.sender), "not authorized");
_owners[id] = uint256(to);
emit Transfer(from, to, id);
}
if (from != to) {
_numNFTPerAddress[from] -= numTokens;
_numNFTPerAddress[to] += numTokens;
}
if (to.isContract()) {
if (_checkInterfaceWith10000Gas(to, ERC721_MANDATORY_RECEIVER)) {
require(
_checkOnERC721BatchReceived(metaTx ? from : msg.sender, from, to, ids, data),
"erc721 batch transfer rejected by to"
);
} else if (safe) {
for (uint256 i = 0; i < numTokens; i++) {
require(
_checkOnERC721Received(metaTx ? from : msg.sender, from, to, ids[i], ""),
"erc721 transfer rejected by to"
);
}
}
}
}
/**
* @notice Transfer many tokens between 2 addresses ensuring the receiving contract has a receiver method
* @param from The sender of the token
* @param to The recipient of the token
* @param ids The ids of the tokens
* @param data additional data
*/
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
bytes calldata data
) external {
_batchTransferFrom(from, to, ids, data, true);
}
/**
* @notice Check if the contract supports an interface
* 0x01ffc9a7 is ERC-165
* 0x80ac58cd is ERC-721
* @param id The id of the interface
* @return True if the interface is supported
*/
function supportsInterface(bytes4 id) external pure returns (bool) {
return id == 0x01ffc9a7 || id == 0x80ac58cd;
}
/**
* @notice Set the approval for an operator to manage all the tokens of the sender
* @param sender The address giving the approval
* @param operator The address receiving the approval
* @param approved The determination of the approval
*/
function setApprovalForAllFor(
address sender,
address operator,
bool approved
) public {
require(sender != address(0), "Invalid sender address");
require(
msg.sender == sender || _metaTransactionContracts[msg.sender] || _superOperators[msg.sender],
"not authorized to approve for all"
);
_setApprovalForAll(sender, operator, approved);
}
/**
* @notice Set the approval for an operator to manage all the tokens of the sender
* @param operator The address receiving the approval
* @param approved The determination of the approval
*/
function setApprovalForAll(address operator, bool approved) public {
_setApprovalForAll(msg.sender, operator, approved);
}
/**
* @param sender Sender address
* @param operator The address receiving the approval
* @param approved The determination of the approval
*/
function _setApprovalForAll(
address sender,
address operator,
bool approved
) internal {
require(!_superOperators[operator], "super operator can't have their approvalForAll changed");
_operatorsForAll[sender][operator] = approved;
emit ApprovalForAll(sender, operator, approved);
}
/**
* @notice Check if the sender approved the operator
* @param owner The address of the owner
* @param operator The address of the operator
* @return The status of the approval
*/
function isApprovedForAll(address owner, address operator)
external
view
returns (bool)
{
return _operatorsForAll[owner][operator] || _superOperators[operator];
}
/**
* @param from sender address
* @param owner owner address of the token
* @param id token id to burn
*/
function _burn(
address from,
address owner,
uint256 id
) internal {
require(from == owner, "not owner");
_owners[id] = 2**160; // cannot mint it again
_numNFTPerAddress[from]--;
emit Transfer(from, address(0), id);
}
/// @notice Burns token `id`.
/// @param id token which will be burnt.
function burn(uint256 id) external {
_burn(msg.sender, _ownerOf(id), id);
}
/// @notice Burn token`id` from `from`.
/// @param from address whose token is to be burnt.
/// @param id token which will be burnt.
function burnFrom(address from, uint256 id) external {
require(from != address(0), "Invalid sender address");
(address owner, bool operatorEnabled) = _ownerAndOperatorEnabledOf(id);
require(
msg.sender == from ||
_metaTransactionContracts[msg.sender] ||
(operatorEnabled && _operators[id] == msg.sender) ||
_operatorsForAll[from][msg.sender] ||
_superOperators[msg.sender],
"not authorized to burn"
);
_burn(from, owner, id);
}
/**
* @param operator Sender of the tx
* @param from Owner of the token
* @param to Recipient
* @param tokenId Token id
* @param _data extra data
*/
function _checkOnERC721Received(
address operator,
address from,
address to,
uint256 tokenId,
bytes memory _data
) internal returns (bool) {
bytes4 retval = ERC721TokenReceiver(to).onERC721Received(operator, from, tokenId, _data);
return (retval == _ERC721_RECEIVED);
}
/**
* @dev Check if receiving contract accepts erc721 batch transfers.
* @param operator Sender of the tx
* @param from Owner of the token
* @param to Recipient
* @param ids Token ids
* @param _data extra data
* @return Whether the expected value of 0x4b808c46 is returned.
*/
function _checkOnERC721BatchReceived(
address operator,
address from,
address to,
uint256[] memory ids,
bytes memory _data
) internal returns (bool) {
bytes4 retval = ERC721MandatoryTokenReceiver(to).onERC721BatchReceived(operator, from, ids, _data);
return (retval == _ERC721_BATCH_RECEIVED);
}
// Empty storage space in contracts for future enhancements
// ref: https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/issues/13)
uint256[49] private __gap;
}
// SPDX-License-Identifier: MIT
/* solhint-disable func-order, code-complexity */
pragma solidity 0.5.9;
import {ERC721BaseTokenV2} from "./ERC721BaseTokenV2.sol";
/**
* @title LandBaseTokenV3
* @author The Sandbox
* @notice Implement LAND and quad functionalities on top of an ERC721 token
* @dev This contract implements a quad tree structure to handle groups of ERC721 tokens at once
*/
contract LandBaseTokenV3 is ERC721BaseTokenV2 {
// Our grid is 408 x 408 lands
uint256 internal constant GRID_SIZE = 408;
uint256 internal constant LAYER = 0xFF00000000000000000000000000000000000000000000000000000000000000;
uint256 internal constant LAYER_1x1 = 0x0000000000000000000000000000000000000000000000000000000000000000;
uint256 internal constant LAYER_3x3 = 0x0100000000000000000000000000000000000000000000000000000000000000;
uint256 internal constant LAYER_6x6 = 0x0200000000000000000000000000000000000000000000000000000000000000;
uint256 internal constant LAYER_12x12 = 0x0300000000000000000000000000000000000000000000000000000000000000;
uint256 internal constant LAYER_24x24 = 0x0400000000000000000000000000000000000000000000000000000000000000;
mapping(address => bool) internal _minters;
event Minter(address indexed superOperator, bool enabled);
struct Land {
uint256 x;
uint256 y;
uint256 size;
}
/**
* @notice Mint a new quad (aligned to a quad tree with size 1, 3, 6, 12 or 24 only)
* @param to The recipient of the new quad
* @param size The size of the new quad
* @param x The top left x coordinate of the new quad
* @param y The top left y coordinate of the new quad
* @param data extra data to pass to the transfer
*/
function mintQuad(
address to,
uint256 size,
uint256 x,
uint256 y,
bytes calldata data
) external {
require(to != address(0), "to is zero address");
require(size != 0, "size cannot be zero");
require(isMinter(msg.sender), "Only a minter can mint");
_isValidQuad(size, x, y);
(uint256 layer, , ) = _getQuadLayer(size);
uint256 quadId = _getQuadId(layer, x, y);
_checkOwner(size, x, y, 24);
for (uint256 i = 0; i < size * size; i++) {
uint256 _id = _idInPath(i, size, x, y);
require(_owners[_id] == 0, "Already minted");
emit Transfer(address(0), to, _id);
}
_owners[quadId] = uint256(to);
_numNFTPerAddress[to] += size * size;
_checkBatchReceiverAcceptQuad(msg.sender, address(0), to, size, x, y, data);
}
/**
* @notice Checks if a parent quad has child quads already minted.
* Then mints the rest child quads and transfers the parent quad.
* Should only be called by the tunnel.
* @param to The recipient of the new quad
* @param size The size of the new quad
* @param x The top left x coordinate of the new quad
* @param y The top left y coordinate of the new quad
* @param data extra data to pass to the transfer
*/
function mintAndTransferQuad(
address to,
uint256 size,
uint256 x,
uint256 y,
bytes calldata data
) external {
require(to != address(0), "to is zero address");
require(isMinter(msg.sender), "Only a minter can mint");
if (exists(size, x, y) == true) {
_transferQuad(msg.sender, to, size, x, y);
_numNFTPerAddress[msg.sender] -= size * size;
_numNFTPerAddress[to] += size * size;
_checkBatchReceiverAcceptQuad(msg.sender, msg.sender, to, size, x, y, data);
} else {
_mintAndTransferQuad(to, size, x, y, data);
}
}
/// @notice transfer one quad (aligned to a quad tree with size 3, 6, 12 or 24 only)
/// @param from current owner of the quad
/// @param to destination
/// @param size size of the quad
/// @param x The top left x coordinate of the quad
/// @param y The top left y coordinate of the quad
/// @param data additional data
function transferQuad(
address from,
address to,
uint256 size,
uint256 x,
uint256 y,
bytes calldata data
) external {
require(from != address(0), "from is zero address");
require(to != address(0), "can't send to zero address");
bool metaTx = msg.sender != from && _metaTransactionContracts[msg.sender];
if (msg.sender != from && !metaTx) {
require(
_operatorsForAll[from][msg.sender] || _superOperators[msg.sender],
"not authorized to transferQuad"
);
}
_transferQuad(from, to, size, x, y);
_numNFTPerAddress[from] -= size * size;
_numNFTPerAddress[to] += size * size;
_checkBatchReceiverAcceptQuad(metaTx ? from : msg.sender, from, to, size, x, y, data);
}
/// @notice transfer multiple quad (aligned to a quad tree with size 3, 6, 12 or 24 only)
/// @param from current owner of the quad
/// @param to destination
/// @param sizes list of sizes for each quad
/// @param xs list of top left x coordinates for each quad
/// @param ys list of top left y coordinates for each quad
/// @param data additional data
function batchTransferQuad(
address from,
address to,
uint256[] calldata sizes,
uint256[] calldata xs,
uint256[] calldata ys,
bytes calldata data
) external {
require(from != address(0), "from is zero address");
require(to != address(0), "can't send to zero address");
require(sizes.length == xs.length, "LandBaseTokenV3: sizes's and x's length are different");
require(xs.length == ys.length, "LandBaseTokenV3: x's and y's length are different");
bool metaTx = msg.sender != from && _metaTransactionContracts[msg.sender];
if (msg.sender != from && !metaTx) {
require(
_operatorsForAll[from][msg.sender] || _superOperators[msg.sender],
"not authorized to transferMultiQuads"
);
}
uint256 numTokensTransfered = 0;
for (uint256 i = 0; i < sizes.length; i++) {
uint256 size = sizes[i];
_transferQuad(from, to, size, xs[i], ys[i]);
numTokensTransfered += size * size;
}
_numNFTPerAddress[from] -= numTokensTransfered;
_numNFTPerAddress[to] += numTokensTransfered;
if (to.isContract() && _checkInterfaceWith10000Gas(to, ERC721_MANDATORY_RECEIVER)) {
uint256[] memory ids = new uint256[](numTokensTransfered);
uint256 counter = 0;
for (uint256 j = 0; j < sizes.length; j++) {
uint256 size = sizes[j];
for (uint256 i = 0; i < size * size; i++) {
ids[counter] = _idInPath(i, size, xs[j], ys[j]);
counter++;
}
}
require(
_checkOnERC721BatchReceived(metaTx ? from : msg.sender, from, to, ids, data),
"erc721 batch transfer rejected by to"
);
}
}
/// @notice Enable or disable the ability of `minter` to mint tokens
/// @param minter address that will be given/removed minter right.
/// @param enabled set whether the minter is enabled or disabled.
function setMinter(address minter, bool enabled) external onlyAdmin {
require(minter != address(0), "address 0 is not allowed as minter");
require(enabled != _minters[minter], "the status should be different than the current one");
_minters[minter] = enabled;
emit Minter(minter, enabled);
}
/// @notice total width of the map
/// @return width
function width() external pure returns (uint256) {
return GRID_SIZE;
}
/// @notice total height of the map
/// @return height
function height() external pure returns (uint256) {
return GRID_SIZE;
}
/// @notice x coordinate of Land token
/// @param id tokenId
/// @return the x coordinates
function getX(uint256 id) external pure returns (uint256) {
return _getX(id);
}
/// @notice y coordinate of Land token
/// @param id tokenId
/// @return the y coordinates
function getY(uint256 id) external pure returns (uint256) {
return _getY(id);
}
/// @notice check whether address `who` is given minter rights.
/// @param who The address to query.
/// @return whether the address has minter rights.
function isMinter(address who) public view returns (bool) {
return _minters[who];
}
/// @notice checks if Land has been minted or not
/// @param size size of the quad
/// @param x x coordinate of the quad
/// @param y y coordinate of the quad
/// @return bool for if Land has been minted or not
function exists(
uint256 size,
uint256 x,
uint256 y
) public view returns (bool) {
_isValidQuad(size, x, y);
return _ownerOfQuad(size, x, y) != address(0);
}
function _isValidQuad(uint256 size, uint256 x, uint256 y) internal pure {
require(size == 1 || size == 3 || size == 6 || size == 12 || size == 24, "Invalid size");
require(x % size == 0, "Invalid x coordinate");
require(y % size == 0, "Invalid y coordinate");
require(x <= GRID_SIZE - size, "x out of bounds");
require(y <= GRID_SIZE - size, "y out of bounds");
}
/**
* @dev checks if the child quads in the parent quad (size, x, y) are owned by msg.sender.
* It recursively checks child quad of every size(exculding Lands of 1x1 size) are minted or not.
* Quad which are minted are pushed into quadMinted to also check if every Land of size 1x1 in the parent quad is minted or not.
* While checking if the every child Quad and Land is minted it also checks and clear the owner for quads which are minted.
* Finally it checks if the new owner if is a contract can handle ERC721 tokens or not and transfers the parent quad to new owner.
* @param to The address to which the ownership of the quad will be transferred
* @param size The size of the quad being minted and transfered
* @param x The x-coordinate of the top-left corner of the quad being minted.
* @param y The y-coordinate of the top-left corner of the quad being minted.
* @param data extra data to pass to the transfer
*/
function _mintAndTransferQuad(
address to,
uint256 size,
uint256 x,
uint256 y,
bytes memory data
) internal {
(uint256 layer, , ) = _getQuadLayer(size);
uint256 quadId = _getQuadId(layer, x, y);
// Length of array is equal to number of 3x3 child quad a 24x24 quad can have. Would be used to push the minted Quads.
Land[] memory quadMinted = new Land[](64);
// index of last minted quad pushed on quadMinted Array
uint256 index;
uint256 numLandMinted;
// if size of the Quad in land struct to be transfered is greater than 3 we check recursivly if the child quads are minted or not.
if (size > 3) {
(index, numLandMinted) = _checkAndClearOwner(
Land({x: x, y: y, size: size}),
quadMinted,
numLandMinted,
index,
size / 2
);
}
// Lopping around the Quad in land struct to generate ids of 1x1 land token and checking if they are owned by msg.sender
{
for (uint256 i = 0; i < size * size; i++) {
uint256 _id = _idInPath(i, size, x, y);
// checking land with token id "_id" is in the quadMinted array.
bool isAlreadyMinted = _isQuadMinted(quadMinted, Land({x: _getX(_id), y: _getY(_id), size: 1}), index);
if (isAlreadyMinted) {
// if land is in the quadMinted array there just emitting transfer event.
emit Transfer(msg.sender, to, _id);
} else {
if (address(uint160(_owners[_id])) == msg.sender) {
if (_operators[_id] != address(0)) _operators[_id] = address(0);
numLandMinted += 1;
emit Transfer(msg.sender, to, _id);
} else {
// else is checked if owned by the msg.sender or not. If it is not owned by msg.sender it should not have an owner.
require(_owners[_id] == 0, "Already minted");
emit Transfer(address(0), to, _id);
}
}
}
}
// checking if the new owner "to" is a contract. If yes, checking if it could handle ERC721 tokens.
_checkBatchReceiverAcceptQuadAndClearOwner(quadMinted, index, numLandMinted, to, size, x, y, data);
_owners[quadId] = uint256(to);
_numNFTPerAddress[to] += size * size;
_numNFTPerAddress[msg.sender] -= numLandMinted;
}
/// @param operator sender of the tx
/// @param from owner of the token
/// @param to recipient
/// @param size The size of the new quad
/// @param x The top left x coordinate of the new quad
/// @param y The top left y coordinate of the new quad
/// @param data extra data
function _checkBatchReceiverAcceptQuad(
address operator,
address from,
address to,
uint256 size,
uint256 x,
uint256 y,
bytes memory data
) internal {
if (to.isContract() && _checkInterfaceWith10000Gas(to, ERC721_MANDATORY_RECEIVER)) {
uint256[] memory ids = new uint256[](size * size);
for (uint256 i = 0; i < size * size; i++) {
ids[i] = _idInPath(i, size, x, y);
}
require(_checkOnERC721BatchReceived(operator, from, to, ids, data), "erc721 batch transfer rejected by to");
}
}
/// @dev checks if the receiver of the quad(size, x, y) is a contact. If yes can it handle ERC721 tokens.
/// It also clears owner of 1x1 land's owned by msg.sender.
/// @param quadMinted array of lands
/// @param index array size
/// @param numLandMinted number of lands transferred
/// @param to recipient
/// @param size The size of the new quad
/// @param x The top left x coordinate of the new quad
/// @param y The top left y coordinate of the new quad
/// @param data extra data
function _checkBatchReceiverAcceptQuadAndClearOwner(
Land[] memory quadMinted,
uint256 index,
uint256 numLandMinted,
address to,
uint256 size,
uint256 x,
uint256 y,
bytes memory data
) internal {
// checks if to is a contract and supports ERC721_MANDATORY_RECEIVER interfaces.
// if it doesn't it just clears the owner of 1x1 lands in quad(size, x, y)
if (to.isContract() && _checkInterfaceWith10000Gas(to, ERC721_MANDATORY_RECEIVER)) {
// array to push minted 1x1 land
uint256[] memory idsToTransfer = new uint256[](numLandMinted);
// index of last land pushed in idsToTransfer array
uint256 transferIndex;
// array to push ids to be minted
uint256[] memory idsToMint = new uint256[]((size * size) - numLandMinted);
// index of last land pushed in idsToMint array
uint256 mintIndex;
// iterating over every 1x1 land in the quad to be pushed in the above arrays
for (uint256 i = 0; i < size * size; i++) {
uint256 id = _idInPath(i, size, x, y);
if (_isQuadMinted(quadMinted, Land({x: _getX(id), y: _getY(id), size: 1}), index)) {
// if land is in the quads already minted it just pushed in to the idsToTransfer array
idsToTransfer[transferIndex] = id;
transferIndex++;
} else if (address(uint160(_owners[id])) == msg.sender) {
_owners[id] = 0;
idsToTransfer[transferIndex] = id;
transferIndex++;
} else {
// else it is not owned by any one and and pushed in teh idsToMint array
idsToMint[mintIndex] = id;
mintIndex++;
}
}
// checking if "to" contact can handle ERC721 tokens
require(
_checkOnERC721BatchReceived(msg.sender, address(0), to, idsToMint, data),
"erc721 batch transfer rejected by to"
);
require(
_checkOnERC721BatchReceived(msg.sender, msg.sender, to, idsToTransfer, data),
"erc721 batch transfer rejected by to"
);
} else {
for (uint256 i = 0; i < size * size; i++) {
uint256 id = _idInPath(i, size, x, y);
if (address(uint160(_owners[id])) == msg.sender) _owners[id] = 0;
}
}
}
/// @param from current owner of the quad
/// @param to destination
/// @param size size of the quad
/// @param x The top left x coordinate of the quad
/// @param y The top left y coordinate of the quad
function _transferQuad(
address from,
address to,
uint256 size,
uint256 x,
uint256 y
) internal {
_isValidQuad(size, x, y);
if (size == 1) {
uint256 id1x1 = _getQuadId(LAYER_1x1, x, y);
address owner = _ownerOf(id1x1);
require(owner != address(0), "token does not exist");
require(owner == from, "not owner in _transferQuad");
_owners[id1x1] = uint256(to);
} else {
_regroupQuad(from, to, Land({x: x, y: y, size: size}), true, size / 2);
}
for (uint256 i = 0; i < size * size; i++) {
emit Transfer(from, to, _idInPath(i, size, x, y));
}
}
/// @dev checks if the quad is already minted compared to another quad size
/// @param size size of the quad
/// @param x The top left x coordinate of the quad
/// @param y The top left y coordinate of the quad
/// @param quadCompareSize size to compare with
function _checkOwner(
uint256 size,
uint256 x,
uint256 y,
uint256 quadCompareSize
) internal view {
(uint256 layer, , ) = _getQuadLayer(quadCompareSize);
if (size <= quadCompareSize) {
// when the size of the quad is smaller than the quadCompareSize(size to be compared with),
// then it is checked if the bigger quad which encapsulates the quad to be minted
// of with size equals the quadCompareSize has been minted or not
require(
_owners[
_getQuadId(layer, (x / quadCompareSize) * quadCompareSize, (y / quadCompareSize) * quadCompareSize)
] == 0,
"Already minted"
);
} else {
// when the size is bigger than the quadCompare size the owner of all the smaller quads with size
// quadCompare size in the quad to be minted are checked if they are minted or not
uint256 toX = x + size;
uint256 toY = y + size;
for (uint256 xi = x; xi < toX; xi += quadCompareSize) {
for (uint256 yi = y; yi < toY; yi += quadCompareSize) {
require(_owners[_getQuadId(layer, xi, yi)] == 0, "Already minted");
}
}
}
quadCompareSize = quadCompareSize / 2;
if (quadCompareSize >= 3) _checkOwner(size, x, y, quadCompareSize);
}
/// @dev checks the owner of land of token id 'id' to be 'from' and clears it
/// @param from owner of the token
/// @param tokenId token id
/// @return if the address is the owner of the token
function _checkAndClearLandOwner(address from, uint256 tokenId) internal returns (bool) {
uint256 currentOwner = _owners[tokenId];
if (currentOwner != 0) {
require(address(currentOwner) == from, "not owner");
_owners[tokenId] = 0;
return true;
}
return false;
}
/** @dev recursivly checks if the child quads are minted in land and push them to the quadMinted array.
* if a child quad is minted in land such quads child quads will be skipped such that there is no
* overlapping in quads which are minted. it clears the minted child quads owners.
* @param land the stuct which has the size x and y co-ordinate of Quad to be checked
* @param quadMinted array in which the minted child quad would be pushed
* @param numLandMinted number of lands transferred
* @param index index of last element of quadMinted array
* @param quadCompareSize the size of the child quads to be checked.
* @return the index of last quad pushed in quadMinted array and the total land already minted
* @return the number of lands minted
*/
function _checkAndClearOwner(
Land memory land,
Land[] memory quadMinted,
uint256 numLandMinted,
uint256 index,
uint256 quadCompareSize
) internal returns (uint256, uint256) {
(uint256 layer, , ) = _getQuadLayer(quadCompareSize);
uint256 toX = land.x + land.size;
uint256 toY = land.y + land.size;
//Lopping around the Quad in land struct to check if the child quad are minted or not
for (uint256 xi = land.x; xi < toX; xi += quadCompareSize) {
for (uint256 yi = land.y; yi < toY; yi += quadCompareSize) {
//checking if the child Quad is minted or not. i.e Checks if the quad is in the quadMinted array.
bool isQuadChecked = _isQuadMinted(quadMinted, Land({x: xi, y: yi, size: quadCompareSize}), index);
// if child quad is not already in the quadMinted array.
if (!isQuadChecked) {
uint256 id = _getQuadId(layer, xi, yi);
address owner = address(uint160(_owners[id]));
// owner of the child quad is checked to be owned by msg.sender else should not be owned by anyone.
if (owner == msg.sender) {
// if child quad is minted it would be pushed in quadMinted array.
quadMinted[index] = Land({x: xi, y: yi, size: quadCompareSize});
// index of quadMinted is increased
index++;
// total land minted is increase by the number if land of 1x1 in child quad
numLandMinted += quadCompareSize * quadCompareSize;
//owner is cleared
_owners[id] = 0;
} else {
require(owner == address(0), "Already minted");
}
}
}
}
// size of the child quad is set to be the next smaller child quad size (12 => 6 => 3)
quadCompareSize = quadCompareSize / 2;
// if child quad size is greater than 3 _checkAndClearOwner is checked for new child quads in the quad in land struct.
if (quadCompareSize >= 3)
(index, numLandMinted) = _checkAndClearOwner(land, quadMinted, numLandMinted, index, quadCompareSize);
return (index, numLandMinted);
}
/// @dev checks if the Land's child quads are owned by the from address and clears all the previous owners
/// if all the child quads are not owned by the "from" address then the owner of parent quad to the land
/// is checked if owned by the "from" address. If from is the owner then land owner is set to "to" address
/// @param from address of the previous owner
/// @param to address of the new owner
/// @param land the quad to be regrouped and transferred
/// @param set for setting the new owner
/// @param childQuadSize size of the child quad to be checked for owner in the regrouping
function _regroupQuad(
address from,
address to,
Land memory land,
bool set,
uint256 childQuadSize
) internal returns (bool) {
(uint256 layer, , uint256 childLayer) = _getQuadLayer(land.size);
uint256 quadId = _getQuadId(layer, land.x, land.y);
bool ownerOfAll = true;
{
// double for loop iterates and checks owner of all the smaller quads in land
for (uint256 xi = land.x; xi < land.x + land.size; xi += childQuadSize) {
for (uint256 yi = land.y; yi < land.y + land.size; yi += childQuadSize) {
uint256 ownerChild;
bool ownAllIndividual;
if (childQuadSize < 3) {
// case when the smaller quad is 1x1,
ownAllIndividual = _checkAndClearLandOwner(from, _getQuadId(LAYER_1x1, xi, yi)) && ownerOfAll;
} else {
// recursively calling the _regroupQuad function to check the owner of child quads.
ownAllIndividual = _regroupQuad(
from,
to,
Land({x: xi, y: yi, size: childQuadSize}),
false,
childQuadSize / 2
);
uint256 idChild = _getQuadId(childLayer, xi, yi);
ownerChild = _owners[idChild];
if (ownerChild != 0) {
if (!ownAllIndividual) {
// checking the owner of child quad
require(ownerChild == uint256(from), "not owner of child Quad");
}
// clearing owner of child quad
_owners[idChild] = 0;
}
}
// ownerOfAll should be true if "from" is owner of all the child quads iterated over
ownerOfAll = (ownAllIndividual || ownerChild != 0) && ownerOfAll;
}
}
}
// if set is true it check if the "from" is owner of all else checks for the owner of parent quad is
// owned by "from" and sets the owner for the id of land to "to" address.
if (set) {
if (!ownerOfAll) {
require(_ownerOfQuad(land.size, land.x, land.y) == from, "not owner of all sub quads nor parent quads");
}
_owners[quadId] = uint256(to);
return true;
}
return ownerOfAll;
}
/// @notice Goes through every token id of a quad id
/// @param i ith token of the quad
/// @param size size of the quad
/// @param x The top left x coordinate of the quad
/// @param y The top left y coordinate of the quad
/// @return the "ith" token id of the quad
function _idInPath(
uint256 i,
uint256 size,
uint256 x,
uint256 y
) internal pure returns (uint256) {
uint256 row = i / size;
if (row % 2 == 0) {
// allow ids to follow a path in a quad
return _getQuadId(LAYER_1x1, (x + (i % size)), (y + row));
} else {
return _getQuadId(LAYER_1x1, (x + size) - (1 + (i % size)), (y + row));
}
}
/// @param mintedLand array of lands
/// @param quad quad to check
/// @param index size of the array
/// @return is the quad minted
function _isQuadMinted(
Land[] memory mintedLand,
Land memory quad,
uint256 index
) internal pure returns (bool) {
for (uint256 i = 0; i < index; i++) {
Land memory land = mintedLand[i];
if (
land.size > quad.size &&
quad.x >= land.x &&
quad.x < land.x + land.size &&
quad.y >= land.y &&
quad.y < land.y + land.size
) {
return true;
}
}
return false;
}
/// @param id token id
/// @return the x coordinate
function _getX(uint256 id) internal pure returns (uint256) {
return (id & ~LAYER) % GRID_SIZE;
}
/// @param id token id
/// @return the y coordinate
function _getY(uint256 id) internal pure returns (uint256) {
return (id & ~LAYER) / GRID_SIZE;
}
/// @param size of the quad
/// @return layer the layer associated to that quad size
/// @return parentSize size of the parent quad
/// @return childLayer layer of the child quad size
function _getQuadLayer(uint256 size)
internal
pure
returns (
uint256 layer,
uint256 parentSize,
uint256 childLayer
)
{
if (size == 1) {
layer = LAYER_1x1;
parentSize = 3;
} else if (size == 3) {
layer = LAYER_3x3;
parentSize = 6;
} else if (size == 6) {
layer = LAYER_6x6;
parentSize = 12;
childLayer = LAYER_3x3;
} else if (size == 12) {
layer = LAYER_12x12;
parentSize = 24;
childLayer = LAYER_6x6;
} else if (size == 24) {
layer = LAYER_24x24;
childLayer = LAYER_12x12;
} else {
require(false, "Invalid size");
}
}
/// @param layer of the quad size
/// @param x coordinate of the quad
/// @param y coordinate of the quad
/// @return the quad id
function _getQuadId(
uint256 layer,
uint256 x,
uint256 y
) internal pure returns (uint256) {
return layer + x + y * GRID_SIZE;
}
/// @param size of the quad
/// @param x coordinate of the quad
/// @param y coordinate of the quad
/// @return address of the owner of the quad
function _ownerOfQuad(
uint256 size,
uint256 x,
uint256 y
) internal view returns (address) {
(uint256 layer, uint256 parentSize, ) = _getQuadLayer(size);
address owner = address(_owners[_getQuadId(layer, (x / size) * size, (y / size) * size)]);
if (owner != address(0)) {
return owner;
} else if (size < 24) {
return _ownerOfQuad(parentSize, x, y);
}
return address(0);
}
/// @param id quad id
/// @return size of the quad
/// @return x coordinate
/// @return y coordinate
function _getQuadById(uint256 id)
internal
pure
returns (
uint256 size,
uint256 x,
uint256 y
)
{
x = _getX(id);
y = _getY(id);
uint256 layer = id & LAYER;
if (layer == LAYER_1x1) {
size = 1;
} else if (layer == LAYER_3x3) {
size = 3;
} else if (layer == LAYER_6x6) {
size = 6;
} else if (layer == LAYER_12x12) {
size = 12;
} else if (layer == LAYER_24x24) {
size = 24;
} else {
require(false, "Invalid token id");
}
}
/// @param id quad id
/// @return address of the owner
function _ownerOf(uint256 id) internal view returns (address) {
require(id & LAYER == 0, "Invalid token id");
(uint256 size, uint256 x, uint256 y) = _getQuadById(id);
require(x % size == 0, "x coordinate: Invalid token id");
require(y % size == 0, "y coordinate: Invalid token id");
return _ownerOfQuad(size, x, y);
}
/// @param id token id
/// @return owner owner of the token
/// @return operatorEnabled is operator enabled
function _ownerAndOperatorEnabledOf(uint256 id) internal view returns (address owner, bool operatorEnabled) {
require(id & LAYER == 0, "Invalid token id");
uint256 x = _getX(id);
uint256 y = _getY(id);
uint256 owner1x1 = _owners[id];
if (owner1x1 != 0) {
owner = address(owner1x1);
operatorEnabled = (owner1x1 / 2**255) == 1;
} else {
owner = _ownerOfQuad(3, (x * 3) / 3, (y * 3) / 3);
operatorEnabled = false;
}
}
}
// SPDX-License-Identifier: MIT
/* solhint-disable no-empty-blocks */
pragma solidity 0.5.9;
import {LandBaseTokenV3} from "./Land/erc721/LandBaseTokenV3.sol";
import {OperatorFiltererUpgradeable, IOperatorFilterRegistry} from "./OperatorFilterer/contracts/upgradeable/OperatorFiltererUpgradeable.sol";
/**
* @title LandV3
* @author The Sandbox
* @notice LAND contract
* @dev LAND contract implements ERC721, quad and marketplace filtering functionalities
*/
contract LandV3 is LandBaseTokenV3, OperatorFiltererUpgradeable {
event OperatorRegistrySet(address indexed registry);
/**
* @notice Return the name of the token contract
* @return The name of the token contract
*/
function name() external pure returns (string memory) {
return "Sandbox's LANDs";
}
/**
* @notice Return the symbol of the token contract
* @return The symbol of the token contract
*/
function symbol() external pure returns (string memory) {
return "LAND";
}
// solium-disable-next-line security/no-assign-params
function uint2str(uint256 _i) internal pure returns (string memory) {
if (_i == 0) {
return "0";
}
uint256 j = _i;
uint256 len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint256 k = len - 1;
while (_i != 0) {
bstr[k--] = byte(uint8(48 + (_i % 10)));
_i /= 10;
}
return string(bstr);
}
/**
* @notice Return the URI of a specific token
* @param id The id of the token
* @return The URI of the token
*/
function tokenURI(uint256 id) public view returns (string memory) {
require(_ownerOf(id) != address(0), "LandV3: Id does not exist");
return string(abi.encodePacked("https://api.sandbox.game/lands/", uint2str(id), "/metadata.json"));
}
/**
* @notice Check if the contract supports an interface
* 0x01ffc9a7 is ERC-165
* 0x80ac58cd is ERC-721
* 0x5b5e139f is ERC-721 metadata
* @param id The id of the interface
* @return True if the interface is supported
*/
function supportsInterface(bytes4 id) external pure returns (bool) {
return id == 0x01ffc9a7 || id == 0x80ac58cd || id == 0x5b5e139f;
}
/// @notice This function is used to register Land contract on the Operator Filterer Registry of Opensea.can only be called by admin.
/// @dev used to register contract and subscribe to the subscriptionOrRegistrantToCopy's black list.
/// @param subscriptionOrRegistrantToCopy registration address of the list to subscribe.
/// @param subscribe bool to signify subscription "true"" or to copy the list "false".
function register(address subscriptionOrRegistrantToCopy, bool subscribe) external onlyAdmin {
require(subscriptionOrRegistrantToCopy != address(0), "LandV3: subscription can't be zero address");
_register(subscriptionOrRegistrantToCopy, subscribe);
}
/// @notice sets filter registry address deployed in test
/// @param registry the address of the registry
function setOperatorRegistry(address registry) external onlyAdmin {
operatorFilterRegistry = IOperatorFilterRegistry(registry);
emit OperatorRegistrySet(registry);
}
/**
* @notice Approve an operator to spend tokens on the sender behalf
* @param sender The address giving the approval
* @param operator The address receiving the approval
* @param id The id of the token
*/
function approveFor(
address sender,
address operator,
uint256 id
) public onlyAllowedOperatorApproval(operator) {
super.approveFor(sender, operator, id);
}
/**
* @notice Set the approval for an operator to manage all the tokens of the sender
* @param operator The address receiving the approval
* @param approved The determination of the approval
*/
function setApprovalForAll(address operator, bool approved) public onlyAllowedOperatorApproval(operator) {
super.setApprovalForAll(operator, approved);
}
/**
* @notice Set the approval for an operator to manage all the tokens of the sender
* @param sender The address giving the approval
* @param operator The address receiving the approval
* @param approved The determination of the approval
*/
function setApprovalForAllFor(
address sender,
address operator,
bool approved
) public onlyAllowedOperatorApproval(operator) {
super.setApprovalForAllFor(sender, operator, approved);
}
/**
* @notice Approve an operator to spend tokens on the sender behalf
* @param operator The address receiving the approval
* @param id The id of the token
*/
function approve(address operator, uint256 id) public onlyAllowedOperatorApproval(operator) {
super.approve(operator, id);
}
/**
* @notice Transfer a token between 2 addresses
* @param from The sender of the token
* @param to The recipient of the token
* @param id The id of the token
*/
function transferFrom(
address from,
address to,
uint256 id
) public onlyAllowedOperator(from) {
super.transferFrom(from, to, id);
}
/**
* @notice Transfer a token between 2 addresses letting the receiver knows of the transfer
* @param from The sender of the token
* @param to The recipient of the token
* @param id The id of the token
* @param data Additional data
*/
function safeTransferFrom(
address from,
address to,
uint256 id,
bytes memory data
) public onlyAllowedOperator(from) {
super.safeTransferFrom(from, to, id, data);
}
/**
* @notice Transfer a token between 2 addresses letting the receiver knows of the transfer
* @param from The send of the token
* @param to The recipient of the token
* @param id The id of the token
*/
function safeTransferFrom(
address from,
address to,
uint256 id
) external onlyAllowedOperator(from) {
super.safeTransferFrom(from, to, id, "");
}
}
//SPDX-License-Identifier: MIT
pragma solidity 0.5.9;
import {IOperatorFilterRegistry} from "../../interfaces/IOperatorFilterRegistry.sol";
import {AddressUtils} from "../../../contracts_common/Libraries/AddressUtils.sol";
/// @title OperatorFiltererUpgradeable
/// @author The Sandbox
/// @notice This contract would subscibe or copy or just to the subscription provided or just register to default subscription list
/// @dev This contract is the upgradeable version of the OpenSea implementation https://github.com/ProjectOpenSea/operator-filter-registry/blob/main/src/OperatorFilterer.sol and adapted to the 0.5.9 solidity version
contract OperatorFiltererUpgradeable {
using AddressUtils for address;
IOperatorFilterRegistry public operatorFilterRegistry;
event ContractRegistered(address indexed subscriptionOrRegistrant, bool subscribe);
/**
* @notice Register this contract into the registry
* @param subscriptionOrRegistrantToCopy address to subscribe or copy entries from
* @param subscribe should it subscribe
*/
function _register(address subscriptionOrRegistrantToCopy, bool subscribe) internal {
// If an inheriting token contract is deployed to a network without the registry deployed, the modifier
// will not revert, but the contract will need to be registered with the registry once it is deployed in
// order for the modifier to filter addresses.
if (address(operatorFilterRegistry).isContract()) {
if (!operatorFilterRegistry.isRegistered(address(this))) {
if (subscribe) {
operatorFilterRegistry.registerAndSubscribe(address(this), subscriptionOrRegistrantToCopy);
} else {
if (subscriptionOrRegistrantToCopy != address(0)) {
operatorFilterRegistry.registerAndCopyEntries(address(this), subscriptionOrRegistrantToCopy);
} else {
operatorFilterRegistry.register(address(this));
}
}
}
}
emit ContractRegistered(subscriptionOrRegistrantToCopy, subscribe);
}
modifier onlyAllowedOperator(address from) {
// Check registry code length to facilitate testing in environments without a deployed registry.
if (address(operatorFilterRegistry).isContract()) {
// Allow spending tokens from addresses with balance
// Note that this still allows listings and marketplaces with escrow to transfer tokens if transferred
// from an EOA.
if (from == msg.sender) {
_;
return;
}
if (!operatorFilterRegistry.isOperatorAllowed(address(this), msg.sender)) {
revert("Operator Not Allowed");
}
}
_;
}
modifier onlyAllowedOperatorApproval(address operator) {
// Check registry code length to facilitate testing in environments without a deployed registry.
if (address(operatorFilterRegistry).isContract()) {
if (!operatorFilterRegistry.isOperatorAllowed(address(this), operator)) {
revert("Operator Not Allowed");
}
}
_;
}
}
//SPDX-License-Identifier: MIT
pragma solidity 0.5.9;
/**
* @title IOperatorFilterRegistry
* @author OpenSea
* @notice Interface of the operator filter registry
* @dev This interface comes from OpenSea https://github.com/ProjectOpenSea/operator-filter-registry/blob/main/src/IOperatorFilterRegistry.sol and adapted to the 0.5.9 solidity version
*/
interface IOperatorFilterRegistry {
/**
* @notice Check if the operator is allowed for the given registrant
* @param registrant address of the registrant
* @param operator operator address to check
* @return is the operator allowed
*/
function isOperatorAllowed(address registrant, address operator) external view returns (bool);
/**
* @notice Register a new address
* @param registrant address to register
*/
function register(address registrant) external;
/**
* @notice Register a new address & subscribe to an address
* @param registrant address of the registrant
* @param subscription address where the registrant is subscribed to
*/
function registerAndSubscribe(address registrant, address subscription) external;
/**
* @notice Register and copy entries of another registrant
* @param registrant address of the registrant
* @param registrantToCopy address to copy from
*/
function registerAndCopyEntries(address registrant, address registrantToCopy) external;
/**
* @notice update the operator for a registrant
* @param registrant address of the registrant
* @param operator operator to be updated
* @param filtered is it filtered
*/
function updateOperator(
address registrant,
address operator,
bool filtered
) external;
/**
* @notice Update operators for a registrant
* @param registrant address of the registrant
* @param operators addresses of the operators
* @param filtered is it filtered
*/
function updateOperators(
address registrant,
address[] calldata operators,
bool filtered
) external;
/**
* @notice Update code hash
* @param registrant address of the registrant
* @param codehash code hash
* @param filtered is it filtered
*/
function updateCodeHash(
address registrant,
bytes32 codehash,
bool filtered
) external;
/**
* @notice Update code hashes
* @param registrant address of the registrant
* @param codeHashes code hashes
* @param filtered is it filtered
*/
function updateCodeHashes(
address registrant,
bytes32[] calldata codeHashes,
bool filtered
) external;
/**
* @notice Subscribe a registrant
* @param registrant address of the registrant
* @param registrantToSubscribe address to subscribe with
*/
function subscribe(address registrant, address registrantToSubscribe) external;
/**
* @notice Unsubscribe a registrant
* @param registrant address of the registrant
* @param copyExistingEntries copy existing entries
*/
function unsubscribe(address registrant, bool copyExistingEntries) external;
/**
* @notice Get the subscription of an address
* @param addr address to check
* @return the registrant address
*/
function subscriptionOf(address addr) external returns (address registrant);
/**
* @notice Get the subscribers of the registrant
* @param registrant address of the registrant
* @return the subscribers addresses
*/
function subscribers(address registrant) external returns (address[] memory);
/**
* @notice Get a specific subscriber
* @param registrant address of the registrant
* @param index index to check
* @return the ith subscriber of the registrant
*/
function subscriberAt(address registrant, uint256 index) external returns (address);
/**
* @notice Copy the entries of a registrant
* @param registrant address of the registrant
* @param registrantToCopy address to copy
*/
function copyEntriesOf(address registrant, address registrantToCopy) external;
/**
* @notice Is a registrant filtered
* @param registrant address of the registrant
* @param operator operator address to check
* @return is it filtered
*/
function isOperatorFiltered(address registrant, address operator) external returns (bool);
/**
* @notice Is the code hash of an operator filtered
* @param registrant address of the registrant
* @param operatorWithCode operator address to check
* @return is it filtered
*/
function isCodeHashOfFiltered(address registrant, address operatorWithCode) external returns (bool);
/**
* @notice Is the code hash filtered
* @param registrant address of the registrant
* @param codeHash code hash
* @return is it filtered
*/
function isCodeHashFiltered(address registrant, bytes32 codeHash) external returns (bool);
/**
* @notice Get the filtered operators
* @param addr address to check
* @return filtered operators
*/
function filteredOperators(address addr) external returns (address[] memory);
/**
* @notice Get the filtered code hashes
* @param addr address to check
* @return filtered code hashes
*/
function filteredCodeHashes(address addr) external returns (bytes32[] memory);
/**
* @notice Get a specific operator
* @param registrant address of the registrant
* @param index index to check
* @return address of the operator
*/
function filteredOperatorAt(address registrant, uint256 index) external returns (address);
/**
* @notice Get the ith filtered code hash
* @param registrant address of the registrant
* @param index index to check
* @return the code hash
*/
function filteredCodeHashAt(address registrant, uint256 index) external returns (bytes32);
/**
* @notice Is the address registered
* @param addr address to check
* @return is it registered
*/
function isRegistered(address addr) external returns (bool);
/**
* @notice Get the code hash for this address
* @param addr address to check
* @return the code hash
*/
function codeHashOf(address addr) external returns (bytes32);
}
File 3 of 3: 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);
}