ETH Price: $2,373.14 (-2.63%)

Contract Diff Checker

Contract Name:
DefinitelyMemberships

Contract Source Code:

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern, minimalist, and gas efficient ERC-721 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 indexed id);

    event Approval(address indexed owner, address indexed spender, uint256 indexed id);

    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /*//////////////////////////////////////////////////////////////
                         METADATA STORAGE/LOGIC
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    function tokenURI(uint256 id) public view virtual returns (string memory);

    /*//////////////////////////////////////////////////////////////
                      ERC721 BALANCE/OWNER STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(uint256 => address) internal _ownerOf;

    mapping(address => uint256) internal _balanceOf;

    function ownerOf(uint256 id) public view virtual returns (address owner) {
        require((owner = _ownerOf[id]) != address(0), "NOT_MINTED");
    }

    function balanceOf(address owner) public view virtual returns (uint256) {
        require(owner != address(0), "ZERO_ADDRESS");

        return _balanceOf[owner];
    }

    /*//////////////////////////////////////////////////////////////
                         ERC721 APPROVAL STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(uint256 => address) public getApproved;

    mapping(address => mapping(address => bool)) public isApprovedForAll;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(string memory _name, string memory _symbol) {
        name = _name;
        symbol = _symbol;
    }

    /*//////////////////////////////////////////////////////////////
                              ERC721 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 id) public virtual {
        address owner = _ownerOf[id];

        require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED");

        getApproved[id] = spender;

        emit Approval(owner, spender, id);
    }

    function setApprovalForAll(address operator, bool approved) public virtual {
        isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function transferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual {
        require(from == _ownerOf[id], "WRONG_FROM");

        require(to != address(0), "INVALID_RECIPIENT");

        require(
            msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id],
            "NOT_AUTHORIZED"
        );

        // Underflow of the sender's balance is impossible because we check for
        // ownership above and the recipient's balance can't realistically overflow.
        unchecked {
            _balanceOf[from]--;

            _balanceOf[to]++;
        }

        _ownerOf[id] = to;

        delete getApproved[id];

        emit Transfer(from, to, id);
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        bytes calldata data
    ) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    /*//////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
            interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 id) internal virtual {
        require(to != address(0), "INVALID_RECIPIENT");

        require(_ownerOf[id] == address(0), "ALREADY_MINTED");

        // Counter overflow is incredibly unrealistic.
        unchecked {
            _balanceOf[to]++;
        }

        _ownerOf[id] = to;

        emit Transfer(address(0), to, id);
    }

    function _burn(uint256 id) internal virtual {
        address owner = _ownerOf[id];

        require(owner != address(0), "NOT_MINTED");

        // Ownership check above ensures no underflow.
        unchecked {
            _balanceOf[owner]--;
        }

        delete _ownerOf[id];

        delete getApproved[id];

        emit Transfer(owner, address(0), id);
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL SAFE MINT LOGIC
    //////////////////////////////////////////////////////////////*/

    function _safeMint(address to, uint256 id) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _safeMint(
        address to,
        uint256 id,
        bytes memory data
    ) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }
}

/// @notice A generic interface for a contract which properly accepts ERC721 tokens.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721TokenReceiver {
    function onERC721Received(
        address,
        address,
        uint256,
        bytes calldata
    ) external virtual returns (bytes4) {
        return ERC721TokenReceiver.onERC721Received.selector;
    }
}

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.17;

/**
                                                      ...:--==***#@%%-
                                             ..:  -*@@@@@@@@@@@@@#*:  
                               -:::-=+*#%@@@@@@*-=@@@@@@@@@@@#+=:     
           .::---:.         +#@@@@@@@@@@@@@%*+-. -@@@@@@+..           
    .-+*%@@@@@@@@@@@#-     -@@@@@@@@@@%#*=:.    :@@@@@@@#%@@@@@%:     
 =#@@@@@@@@@@@@@@@@@@@%.   %@@@@@@-..           *@@@@@@@@@@@@%*.      
-@@@@@@@@@#*+=--=#@@@@@%  +@@@@@@%*#%@@@%*=-.. .@@@@@@@%%*+=:         
 :*@@@@@@*       .@@@@@@.*@@@@@@@@@@@@*+-      =%@@@@%                
  =@@@@@@.       *@@@@@%:@@@@@@*==-:.          =@@@@@:                
 .@@@@@@=      =@@@@@@%.*@@@@@=   ..::--=+*=+*+=@@@@=                 
 #@@@@@*    .+@@@@@@@* .+@@@@@#%%@@@@@@@@#+:.  =#@@=                  
 @@@@@%   :*@@@@@@@*:  .#@@@@@@@@@@@@@%#:       ---                   
:@@@@%. -%@@@@@@@+.     +@@@@@%#*+=:.                                 
+@@@%=*@@@@@@@*:        =*:                                           
:*#+%@@@@%*=.                                                         
 :+##*=:.

*/

import {ERC721} from "solmate/tokens/ERC721.sol";
import {Auth} from "./lib/Auth.sol";
import {IDefinitelyMemberships} from "./interfaces/IDefinitelyMemberships.sol";
import {IDefinitelyMetadata} from "./interfaces/IDefinitelyMetadata.sol";

/**
 * @title
 * Definitely Memberships
 *
 * @author
 * DEF DAO
 *
 * @notice
 * A membership token for DEF DAO in the form of a ERC721 NFT.
 *
 * Features:
 *   - Non-transferrable tokens. This can be bypassed by a membership transfer contract to
 *     allow for social recovery if necessary.
 *   - Approved contracts for issuing memberships. This allows different issuing
 *     mechanisms e.g. invites, props, funding etc. at the same time.
 *   - Approved contracts for revoking memberships.
 *   - Approved contracts for transferring memberships.
 *   - A separate upgradable metadata contract.
 *   - Per token metadata overriding.
 *
 */
contract DefinitelyMemberships is IDefinitelyMemberships, ERC721, Auth {
    /* ------------------------------------------------------------------------
       S T O R A G E
    ------------------------------------------------------------------------ */

    /* ERC-721 ------------------------------------------------------------- */

    /// @dev Tracks ERC-721 token ids
    uint256 private _nextMembershipId = 1;

    /* ISSUING MEMBERSHIPS ------------------------------------------------- */

    /// @dev Contracts that are allowed to issue memberships
    mapping(address => bool) private _allowedMembershipIssuingContracts;

    /// @dev Maps a token id to the block number it was issued at for "insight" score
    mapping(uint256 => uint256) private _memberSinceBlock;

    /* REVOKING MEMBERSHIPS ------------------------------------------------ */

    /// @dev Contracts that are allowed to revoke memberships
    mapping(address => bool) private _allowedMembershipRevokingContracts;

    /// @dev Prevents an address from becoming an owner of this token
    mapping(address => bool) private _denyList;

    /* TRANSFERRING MEMBERSHIPS -------------------------------------------- */

    /// @dev Contracts that are allowed to transfer memberships between accounts
    mapping(address => bool) private _allowedMembershipTransferContracts;

    bool public globalTransferLocked;

    /* METADATA ------------------------------------------------------------ */

    /// @dev A fallback metadata address for all tokens that don't specify an override
    address private _defaultMetadata;

    /// @dev Allows a specific token ID to use it's own metadata address
    mapping(uint256 => address) private _tokenMetadataOverrideAddress;

    /* ------------------------------------------------------------------------
       E V E N T S
    ------------------------------------------------------------------------ */

    /* INIT ---------------------------------------------------------------- */

    event DefinitelyShipping();

    /* ISSUING MEMBERSHIPS ------------------------------------------------- */

    event MembershipIssuingContractAdded(address indexed contractAddress);
    event MembershipIssuingContractRemoved(address indexed contractAddress);
    event MembershipIssued(uint256 indexed id, address indexed newOwner);

    /* REVOKING MEMBERSHIPS ------------------------------------------------ */

    event MembershipRevokingContractAdded(address indexed contractAddress);
    event MembershipRevokingContractRemoved(address indexed contractAddress);
    event MembershipRevoked(uint256 indexed id, address indexed prevOwner);
    event AddedToDenyList(address indexed account);
    event RemovedFromDenyList(address indexed account);

    /* TRANSFERRING MEMBERSHIPS -------------------------------------------- */

    event MembershipTransferContractAdded(address indexed contractAddress);
    event MembershipTransferContractRemoved(address indexed contractAddress);
    event TransferLockSet(bool indexed isTransferLocked);

    /* METADATA ------------------------------------------------------------ */

    event DefaultMetadataUpdated(address indexed metadata);
    event MetadataOverridden(uint256 indexed id, address indexed metadata);
    event MetadataResetToDefault(uint256 indexed id);

    /* ------------------------------------------------------------------------
       E R R O R S
    ------------------------------------------------------------------------ */

    error NotAuthorizedToIssueMembership();
    error NotAuthorizedToRevokeMembership();
    error NotAuthorizedToTransferMembership();

    error NotDefMember();
    error AlreadyDefMember();
    error NotOwnerOfToken();
    error OnDenyList();

    error CannotTransferToZeroAddress();

    /* ------------------------------------------------------------------------
       M O D I F I E R S
    ------------------------------------------------------------------------ */

    /// @dev Reverts if not a member
    modifier onlyDefMember() {
        if (_balanceOf[msg.sender] < 1) revert NotDefMember();
        _;
    }

    /// @dev Reverts if `account` is already a member
    modifier whenNotDefMember(address account) {
        if (_balanceOf[account] > 0) revert AlreadyDefMember();
        _;
    }

    /// @dev Reverts if `account` is on the deny list
    modifier whenNotOnDenyList(address account) {
        if (_denyList[account]) revert OnDenyList();
        _;
    }

    /// @dev Reverts if not an allowed minting contract
    modifier onlyMembershipIssuingContract() {
        if (!_allowedMembershipIssuingContracts[msg.sender]) {
            revert NotAuthorizedToIssueMembership();
        }
        _;
    }

    /// @dev Reverts if not the allowed membership revoking
    modifier onlyMembershipRevokingContract() {
        if (!_allowedMembershipRevokingContracts[msg.sender]) {
            revert NotAuthorizedToRevokeMembership();
        }
        _;
    }

    /// @dev Reverts if not the allowed membership transfer contract
    modifier onlyMembershipTransferContract() {
        if (!_allowedMembershipTransferContracts[msg.sender]) {
            revert NotAuthorizedToTransferMembership();
        }
        _;
    }

    /* ------------------------------------------------------------------------
       I N I T
    ------------------------------------------------------------------------ */

    /**
     * @param owner_ Contract owner address
     */
    constructor(address owner_) ERC721("DEF", "Definitely Memberships") Auth(owner_) {
        emit DefinitelyShipping();

        globalTransferLocked = true;
        emit TransferLockSet(true);
    }

    /* ------------------------------------------------------------------------
       I S S U I N G   M E M B E R S H I P S
    ------------------------------------------------------------------------ */

    /**
     * @notice
     * Allows another contract to issue a membership token to someone
     *
     * @dev
     * Reverts if:
     *   - the caller is not an approved issuing contract
     *   - `to` is already a member
     *   - `to` is on the deny list
     *
     * @param to Address to issue a membership NFT to
     */
    function issueMembership(address to)
        external
        override
        onlyMembershipIssuingContract
        whenNotDefMember(to)
        whenNotOnDenyList(to)
    {
        _mint(to, _nextMembershipId);
        _memberSinceBlock[_nextMembershipId] = block.number;
        emit MembershipIssued(_nextMembershipId, to);
        unchecked {
            ++_nextMembershipId;
        }
    }

    /* ------------------------------------------------------------------------
       R E V O K I N G   M E M B E R S H I P S
    ------------------------------------------------------------------------ */

    /**
     * @notice
     * Revokes a membership by burning a token
     *
     * @dev
     * This allows some level of governance for when a membership should be revoked.
     * Optionally adds the address to the deny list so they cannot be issued a new
     * membership in the future.
     *
     * Reverts if:
     *   - the caller is not an approved revoking contract
     *
     * @param id The token id of the membership to revoke
     * @param addToDenyList Whether to add the current owner to the deny list
     */
    function revokeMembership(uint256 id, bool addToDenyList)
        external
        onlyMembershipRevokingContract
    {
        address prevOwner = _ownerOf[id];
        if (addToDenyList) _setDenyListStatus(prevOwner, true);
        _burn(id);
        emit MembershipRevoked(id, prevOwner);
    }

    /**
     * @notice
     * Adds an address to the deny list
     *
     * @dev
     * This allows some level of governance for when an address should be added
     * to the deny list.
     *
     * Reverts if:
     *   - the caller is not an approved revoking contract
     *
     * @param account The account to add to the deny list
     */
    function addAddressToDenyList(address account) public onlyMembershipRevokingContract {
        _setDenyListStatus(account, true);
    }

    /**
     * @notice
     * Removes an address from the deny list
     *
     * @dev
     * This allows some level of governance for when an address should be removed
     * from the deny list.
     *
     * Reverts if:
     *   - the caller is not an approved revoking contract
     *
     * @param account The account to remove from the deny list
     */
    function removeAddressFromDenyList(address account) external onlyMembershipRevokingContract {
        _setDenyListStatus(account, false);
    }

    /**
     * @dev
     * Internal function to manage deny list status and emit relevant events
     *
     * @param account The account to set the deny list status for
     * @param isDenied Whether the account should be on the deny list or not
     */
    function _setDenyListStatus(address account, bool isDenied) internal {
        _denyList[account] = isDenied;

        if (isDenied) emit AddedToDenyList(account);
        if (!isDenied) emit RemovedFromDenyList(account);
    }

    /* ------------------------------------------------------------------------
       T R A N S F E R R I N G   M E M B E R S H I P S
    ------------------------------------------------------------------------ */

    /**
     * @notice
     * Allows an account to bypass the transfer lock mechanic and transfer a membership
     *
     * @dev
     * This allows for some level of governance around when to actually allow a
     * membership transfer to happen.
     *
     * Reverts if:
     *   - the caller is not an approved transfer contract
     *
     * @param id The token id of the membership being transferred
     * @param to The new owner of the membership token
     */
    function transferMembership(uint256 id, address to) external onlyMembershipTransferContract {
        if (to == address(0)) revert CannotTransferToZeroAddress();
        address from = _ownerOf[id];

        unchecked {
            _balanceOf[from]--;
            _balanceOf[to]++;
        }

        _ownerOf[id] = to;
        delete getApproved[id];
        emit Transfer(from, to, id);
    }

    /* ------------------------------------------------------------------------
       T R A N S F E R   L O C K
    ------------------------------------------------------------------------ */

    /**
     * @notice
     * Overridden `transferFrom` to prevent membership transfer if transfers are locked
     *
     * @dev
     * If a transfer is required while `globalTransferLocked` is true, use an approved
     * membership transfer contract instead
     */
    function transferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual override {
        if (globalTransferLocked) revert NotAuthorizedToTransferMembership();
        super.transferFrom(from, to, id);
    }

    /* ------------------------------------------------------------------------
       M E T A D A T A
    ------------------------------------------------------------------------ */

    /**
     * @notice
     * Allows a token holder to set a new metadata address for tokenURI customization
     *
     * @param id The token to override metadata for
     * @param metadata The new metadata contract address for this token
     */
    function overrideMetadataForToken(uint256 id, address metadata) external {
        if (_ownerOf[id] != msg.sender) revert NotOwnerOfToken();
        _tokenMetadataOverrideAddress[id] = address(metadata);
        emit MetadataOverridden(id, metadata);
    }

    /**
     * @notice
     * Allows a token holder to use the default metadata address for their token
     *
     * @param id The token that should use the default metadata contract
     */
    function resetMetadataForToken(uint256 id) external {
        delete _tokenMetadataOverrideAddress[id];
        emit MetadataResetToDefault(id);
    }

    /* ------------------------------------------------------------------------
       E R C - 7 2 1
    ------------------------------------------------------------------------ */

    /**
     * @notice
     * Burn your membership token
     *
     * @param id The token you want to burn
     */
    function burn(uint256 id) external {
        if (_ownerOf[id] != msg.sender) revert NotOwnerOfToken();
        _burn(id);
    }

    /**
     * @notice
     * ERC-721 tokenURI returns the metadata for a given token
     *
     * @dev
     * Returns the metadata override if present, or fall back to the default metadata address
     *
     * @param id The token id to get metadata for
     */
    function tokenURI(uint256 id) public view virtual override returns (string memory) {
        address metadataOverride = _tokenMetadataOverrideAddress[id];
        return
            IDefinitelyMetadata(
                metadataOverride != address(0) ? metadataOverride : _defaultMetadata
            ).tokenURI(id);
    }

    /* ------------------------------------------------------------------------
       A D M I N
    ------------------------------------------------------------------------ */

    /**
     * @notice
     * Adds a new membership issuing contract
     *
     * @dev
     * The new contract will be able to mint membership tokens to people who aren't already
     * members, and who aren't on the deny list. There are no other restrictions so the issuing
     * contract must implement additional checks if necessary
     *
     * @param addr A membership issuing contract address
     */
    function addMembershipIssuingContract(address addr) external onlyOwnerOrAdmin {
        _allowedMembershipIssuingContracts[addr] = true;
        emit MembershipIssuingContractAdded(addr);
    }

    /**
     * @notice
     * Removes an existing membership issuing contract
     *
     * @dev
     * This will prevent the contract from calling `issueMembership`
     *
     * @param addr A membership issuing contract address
     */
    function removeMembershipIssuingContract(address addr) external onlyOwnerOrAdmin {
        _allowedMembershipIssuingContracts[addr] = false;
        emit MembershipIssuingContractRemoved(addr);
    }

    /**
     * @notice
     * Adds a new membership revoking contract
     *
     * @dev
     * The new contract will be able to burn tokens effectively revoking membership
     *
     * @param addr A membership revoking contract address
     */
    function addMembershipRevokingContract(address addr) external onlyOwnerOrAdmin {
        _allowedMembershipRevokingContracts[addr] = true;
        emit MembershipRevokingContractAdded(addr);
    }

    /**
     * @notice
     * Removes an existing membership revoking contract
     *
     * @dev
     * This will prevent the contract from calling `revokeMembership`
     *
     * @param addr A membership revoking contract address
     */
    function removeMembershipRevokingContract(address addr) external onlyOwnerOrAdmin {
        _allowedMembershipRevokingContracts[addr] = false;
        emit MembershipRevokingContractRemoved(addr);
    }

    /**
     * @notice
     * Adds a new membership transfer contract
     *
     * @dev
     * The new contract will be able to bypass the transfer lock mechanic and transfer tokens
     *
     * @param addr A membership transfer contract address
     */
    function addMembershipTransferContract(address addr) external onlyOwnerOrAdmin {
        _allowedMembershipTransferContracts[addr] = true;
        emit MembershipTransferContractAdded(addr);
    }

    /**
     * @notice
     * Removes an existing membership transfer contract
     *
     * @dev
     * This will prevent the contract from calling `transferMembership`
     *
     * @param addr A membership transfer contract address
     */
    function removeMembershipTransferContract(address addr) external onlyOwnerOrAdmin {
        _allowedMembershipTransferContracts[addr] = false;
        emit MembershipTransferContractRemoved(addr);
    }

    /**
     * @notice
     * Updates the fallback metadata used for all tokens that haven't set an override
     *
     * @param addr A metadata contract address
     */
    function setDefaultMetadata(address addr) external onlyOwnerOrAdmin {
        _defaultMetadata = address(addr);
        emit DefaultMetadataUpdated(addr);
    }

    /**
     * @notice
     * Updates the global transfer lock flag to lock/unlock standard ERC721 transfers
     *
     * @param locked If global direct ERC721 transfers should be prevented
     */
    function setGlobalTransferLock(bool locked) external onlyOwnerOrAdmin {
        globalTransferLocked = locked;
        emit TransferLockSet(locked);
    }

    /* ------------------------------------------------------------------------
       P U B L I C   G E T T E R S
    ------------------------------------------------------------------------ */

    /**
     * @notice Checks if an account is part of DEF with a simple balance check
     */
    function isDefMember(address account) external view returns (bool) {
        return _balanceOf[account] > 0;
    }

    /**
     * @notice
     * Checks if an account is on the DEF deny list
     *
     * @dev
     * If the account is on the deny list then they will not be allowed to become a member
     * until they are removed from the deny list by a revoking contract.
     */
    function isOnDenyList(address account) external view returns (bool) {
        return _denyList[account];
    }

    /**
     * @notice Returns the block number for when the token was issued
     */
    function memberSinceBlock(uint256 id) external view returns (uint256) {
        return _memberSinceBlock[id];
    }

    /**
     * @notice Gets the metadata address for a given token
     */
    function metadataAddressForToken(uint256 id) external view returns (address) {
        address metadataOverride = _tokenMetadataOverrideAddress[id];
        return metadataOverride != address(0) ? metadataOverride : _defaultMetadata;
    }

    /**
     * @notice Gets the fallback metadata contract address
     */
    function defaultMetadataAddress() external view returns (address) {
        return _defaultMetadata;
    }

    /**
     * @notice Checks if an address is allowed to issue memberships
     */
    function allowedMembershipIssuingContract(address addr) external view returns (bool) {
        return _allowedMembershipIssuingContracts[addr];
    }

    /**
     * @notice Checks if an address is allowed to revoke memberships
     */
    function allowedMembershipRevokingContract(address addr) external view returns (bool) {
        return _allowedMembershipRevokingContracts[addr];
    }

    /**
     * @notice Checks if an address is allowed to transfer memberships
     */
    function allowedMembershipTransferContract(address addr) external view returns (bool) {
        return _allowedMembershipTransferContracts[addr];
    }
}

// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.17;

interface IDefinitelyMemberships {
    function issueMembership(address to) external;

    function revokeMembership(uint256 id, bool addToDenyList) external;

    function addAddressToDenyList(address account) external;

    function removeAddressFromDenyList(address account) external;

    function transferMembership(uint256 id, address to) external;

    function overrideMetadataForToken(uint256 id, address metadata) external;

    function resetMetadataForToken(uint256 id) external;

    function isDefMember(address account) external view returns (bool);

    function isOnDenyList(address account) external view returns (bool);

    function memberSinceBlock(uint256 id) external view returns (uint256);

    function defaultMetadataAddress() external view returns (address);

    function metadataAddressForToken(uint256 id) external view returns (address);

    function allowedMembershipIssuingContract(address addr) external view returns (bool);

    function allowedMembershipRevokingContract(address addr) external view returns (bool);

    function allowedMembershipTransferContract(address addr) external view returns (bool);
}

// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.17;

interface IDefinitelyMetadata {
    function tokenURI(uint256 id) external view returns (string memory);
}

// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.17;

/**
 * @author DEF DAO
 * @title  Simple owner and admin authentication
 * @notice Allows the management of a contract by using simple ownership and admin modifiers.
 */
abstract contract Auth {
    /* ------------------------------------------------------------------------
       S T O R A G E
    ------------------------------------------------------------------------ */

    /// @notice Current owner of the contract
    address public owner;

    /// @notice Current admins of the contract
    mapping(address => bool) public admins;

    /* ------------------------------------------------------------------------
       E V E N T S
    ------------------------------------------------------------------------ */

    /**
     * @notice When the contract owner is updated
     * @param user The account that updated the new owner
     * @param newOwner The new owner of the contract
     */
    event OwnerUpdated(address indexed user, address indexed newOwner);

    /**
     * @notice When an admin is added to the contract
     * @param user The account that added the new admin
     * @param newAdmin The admin that was added
     */
    event AdminAdded(address indexed user, address indexed newAdmin);

    /**
     * @notice When an admin is removed from the contract
     * @param user The account that removed an admin
     * @param prevAdmin The admin that got removed
     */
    event AdminRemoved(address indexed user, address indexed prevAdmin);

    /* ------------------------------------------------------------------------
       M O D I F I E R S
    ------------------------------------------------------------------------ */

    /**
     * @dev Only the owner can call
     */
    modifier onlyOwner() {
        require(msg.sender == owner, "UNAUTHORIZED");
        _;
    }

    /**
     * @dev Only an admin can call
     */
    modifier onlyAdmin() {
        require(admins[msg.sender], "UNAUTHORIZED");
        _;
    }

    /**
     * @dev Only the owner or an admin can call
     */
    modifier onlyOwnerOrAdmin() {
        require((msg.sender == owner || admins[msg.sender]), "UNAUTHORIZED");
        _;
    }

    /* ------------------------------------------------------------------------
       I N I T
    ------------------------------------------------------------------------ */

    /**
     * @dev Sets the initial owner and a first admin upon creation.
     * @param owner_ The initial owner of the contract
     */
    constructor(address owner_) {
        owner = owner_;
        emit OwnerUpdated(address(0), owner_);
    }

    /* ------------------------------------------------------------------------
       A D M I N
    ------------------------------------------------------------------------ */

    /**
     * @notice Transfers ownership of the contract to `newOwner`
     * @dev Can only be called by the current owner or an admin
     * @param newOwner The new owner of the contract
     */
    function setOwner(address newOwner) public virtual onlyOwnerOrAdmin {
        owner = newOwner;
        emit OwnerUpdated(msg.sender, newOwner);
    }

    /**
     * @notice Adds `newAdmin` as an admin of the contract
     * @dev Can only be called by the current owner or an admin
     * @param newAdmin A new admin of the contract
     */
    function addAdmin(address newAdmin) public virtual onlyOwnerOrAdmin {
        admins[newAdmin] = true;
        emit AdminAdded(msg.sender, newAdmin);
    }

    /**
     * @notice Removes `prevAdmin` as an admin of the contract
     * @dev Can only be called by the current owner or an admin
     * @param prevAdmin The admin to remove
     */
    function removeAdmin(address prevAdmin) public virtual onlyOwnerOrAdmin {
        admins[prevAdmin] = false;
        emit AdminRemoved(msg.sender, prevAdmin);
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):