ETH Price: $3,069.21 (-7.87%)
Gas: 6.39 Gwei

Contract Diff Checker

Contract Name:
SykyGroups

Contract Source Code:

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

/// @author thirdweb

/**
 * @dev External interface of AccessControl declared to support ERC165 detection.
 */
interface IPermissions {
    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     *
     * _Available since v3.1._
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call, an admin role
     * bearer except when using {AccessControl-_setupRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     */
    function renounceRole(bytes32 role, address account) external;
}

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

/// @author thirdweb

import "./interface/IPermissions.sol";
import "../lib/TWStrings.sol";

/**
 *  @title   Permissions
 *  @dev     This contracts provides extending-contracts with role-based access control mechanisms
 */
contract Permissions is IPermissions {
    /// @dev Map from keccak256 hash of a role => a map from address => whether address has role.
    mapping(bytes32 => mapping(address => bool)) private _hasRole;

    /// @dev Map from keccak256 hash of a role to role admin. See {getRoleAdmin}.
    mapping(bytes32 => bytes32) private _getRoleAdmin;

    /// @dev Default admin role for all roles. Only accounts with this role can grant/revoke other roles.
    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /// @dev Modifier that checks if an account has the specified role; reverts otherwise.
    modifier onlyRole(bytes32 role) {
        _checkRole(role, msg.sender);
        _;
    }

    /**
     *  @notice         Checks whether an account has a particular role.
     *  @dev            Returns `true` if `account` has been granted `role`.
     *
     *  @param role     keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
     *  @param account  Address of the account for which the role is being checked.
     */
    function hasRole(bytes32 role, address account) public view override returns (bool) {
        return _hasRole[role][account];
    }

    /**
     *  @notice         Checks whether an account has a particular role;
     *                  role restrictions can be swtiched on and off.
     *
     *  @dev            Returns `true` if `account` has been granted `role`.
     *                  Role restrictions can be swtiched on and off:
     *                      - If address(0) has ROLE, then the ROLE restrictions
     *                        don't apply.
     *                      - If address(0) does not have ROLE, then the ROLE
     *                        restrictions will apply.
     *
     *  @param role     keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
     *  @param account  Address of the account for which the role is being checked.
     */
    function hasRoleWithSwitch(bytes32 role, address account) public view returns (bool) {
        if (!_hasRole[role][address(0)]) {
            return _hasRole[role][account];
        }

        return true;
    }

    /**
     *  @notice         Returns the admin role that controls the specified role.
     *  @dev            See {grantRole} and {revokeRole}.
     *                  To change a role's admin, use {_setRoleAdmin}.
     *
     *  @param role     keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
     */
    function getRoleAdmin(bytes32 role) external view override returns (bytes32) {
        return _getRoleAdmin[role];
    }

    /**
     *  @notice         Grants a role to an account, if not previously granted.
     *  @dev            Caller must have admin role for the `role`.
     *                  Emits {RoleGranted Event}.
     *
     *  @param role     keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
     *  @param account  Address of the account to which the role is being granted.
     */
    function grantRole(bytes32 role, address account) public virtual override {
        _checkRole(_getRoleAdmin[role], msg.sender);
        if (_hasRole[role][account]) {
            revert("Can only grant to non holders");
        }
        _setupRole(role, account);
    }

    /**
     *  @notice         Revokes role from an account.
     *  @dev            Caller must have admin role for the `role`.
     *                  Emits {RoleRevoked Event}.
     *
     *  @param role     keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
     *  @param account  Address of the account from which the role is being revoked.
     */
    function revokeRole(bytes32 role, address account) public virtual override {
        _checkRole(_getRoleAdmin[role], msg.sender);
        _revokeRole(role, account);
    }

    /**
     *  @notice         Revokes role from the account.
     *  @dev            Caller must have the `role`, with caller being the same as `account`.
     *                  Emits {RoleRevoked Event}.
     *
     *  @param role     keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
     *  @param account  Address of the account from which the role is being revoked.
     */
    function renounceRole(bytes32 role, address account) public virtual override {
        if (msg.sender != account) {
            revert("Can only renounce for self");
        }
        _revokeRole(role, account);
    }

    /// @dev Sets `adminRole` as `role`'s admin role.
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = _getRoleAdmin[role];
        _getRoleAdmin[role] = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /// @dev Sets up `role` for `account`
    function _setupRole(bytes32 role, address account) internal virtual {
        _hasRole[role][account] = true;
        emit RoleGranted(role, account, msg.sender);
    }

    /// @dev Revokes `role` from `account`
    function _revokeRole(bytes32 role, address account) internal virtual {
        _checkRole(role, account);
        delete _hasRole[role][account];
        emit RoleRevoked(role, account, msg.sender);
    }

    /// @dev Checks `role` for `account`. Reverts with a message including the required role.
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!_hasRole[role][account]) {
            revert(
                string(
                    abi.encodePacked(
                        "Permissions: account ",
                        TWStrings.toHexString(uint160(account), 20),
                        " is missing role ",
                        TWStrings.toHexString(uint256(role), 32)
                    )
                )
            );
        }
    }

    /// @dev Checks `role` for `account`. Reverts with a message including the required role.
    function _checkRoleWithSwitch(bytes32 role, address account) internal view virtual {
        if (!hasRoleWithSwitch(role, account)) {
            revert(
                string(
                    abi.encodePacked(
                        "Permissions: account ",
                        TWStrings.toHexString(uint160(account), 20),
                        " is missing role ",
                        TWStrings.toHexString(uint256(role), 32)
                    )
                )
            );
        }
    }
}

// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.0;

/// @author thirdweb

/**
 * @dev String operations.
 */
library TWStrings {
    bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        // Inspired by OraclizeAPI's implementation - MIT licence
        // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        if (value == 0) {
            return "0x00";
        }
        uint256 temp = value;
        uint256 length = 0;
        while (temp != 0) {
            length++;
            temp >>= 8;
        }
        return toHexString(value, length);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _HEX_SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @author Syky - Nathan Rempel

import "./interface/IGroupRegistry.sol";

import "@thirdweb-dev/contracts/extension/Permissions.sol";

contract GroupRegistry is IGroupRegistry, Permissions {
    /*//////////////////////////////////////////////////////////////
                            Constants
    //////////////////////////////////////////////////////////////*/

    bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");

    /*//////////////////////////////////////////////////////////////
                            Mappings
    //////////////////////////////////////////////////////////////*/

    mapping(uint256 => ItemSet) private _groups;
    mapping(uint256 => ItemSet) private _subgroups;

    /*//////////////////////////////////////////////////////////////
                            Constructor
    //////////////////////////////////////////////////////////////*/

    constructor(address defaultAdmin_) {
        _setupRole(DEFAULT_ADMIN_ROLE, defaultAdmin_);
        _setRoleAdmin(MANAGER_ROLE, DEFAULT_ADMIN_ROLE);
    }

    /*//////////////////////////////////////////////////////////////
                        Admin functions
    //////////////////////////////////////////////////////////////*/

    function assignMembers(
        uint256 _subgroupId,
        address[] calldata _members
    ) external onlyValidId(_subgroupId) onlyManager {
        ItemSet storage _itemSet = _subgroups[_subgroupId];
        uint256 memberLength = _members.length;
        uint256[] memory members = new uint256[](memberLength);
        for (uint256 i; i < memberLength; ) {
            members[i] = uint160(_members[i]);
            unchecked {
                ++i;
            }
        }
        _insertItems(_itemSet, members);
        emit MembersAssigned(_subgroupId, _members);
    }

    function removeMembers(
        uint256 _subgroupId,
        address[] calldata _members
    ) external onlyValidId(_subgroupId) onlyManager {
        ItemSet storage _itemSet = _subgroups[_subgroupId];
        uint256 memberLength = _members.length;
        uint256[] memory members = new uint256[](memberLength);
        for (uint256 i; i < memberLength; ) {
            members[i] = uint160(_members[i]);
            unchecked {
                ++i;
            }
        }
        _removeItems(_itemSet, members);
        emit MembersAssigned(_subgroupId, _members);
    }

    function assignSubgroups(
        uint256 _groupId,
        uint256[] calldata _subgroupIds
    ) external onlyValidId(_groupId) onlyManager {
        ItemSet storage _itemSet = _groups[_groupId];
        _insertItems(_itemSet, _subgroupIds);
        emit SubgroupsAssigned(_groupId, _subgroupIds);
    }

    function removeSubgroups(
        uint256 _groupId,
        uint256[] calldata _subgroupIds
    ) external onlyValidId(_groupId) onlyManager {
        ItemSet storage _itemSet = _groups[_groupId];
        _removeItems(_itemSet, _subgroupIds);
        emit SubgroupsRemoved(_groupId, _subgroupIds);
    }

    /*//////////////////////////////////////////////////////////////
                        Public getters
    //////////////////////////////////////////////////////////////*/

    function isGroupMember(
        uint256 _groupId,
        address _member
    ) external view returns (bool) {
        return _isGroupMember(_groupId, _member);
    }

    function isSubgroupMember(
        uint256 _subgroupId,
        address _member
    ) external view returns (bool) {
        return _isSubgroupMember(_subgroupId, _member);
    }

    function groupMembers(
        uint256 _groupId
    ) external view returns (GroupMembersQuery memory) {
        return
            GroupMembersQuery({groupId: _groupId, subgroups: _getGroupMembers(_groupId)});
    }

    function groupSubgroups(uint256 _groupId) external view returns (uint256[] memory) {
        return _getSubgroups(_groupId);
    }

    function subgroupMembers(
        uint256 _subgroupId
    ) external view returns (SubgroupMembersQuery memory) {
        return
            SubgroupMembersQuery({
                subgroupId: _subgroupId,
                members: _getSubgroupMembers(_subgroupId)
            });
    }

    /*//////////////////////////////////////////////////////////////
                            Modifiers
    //////////////////////////////////////////////////////////////*/

    /// @dev Modifier that checks if an account has admin or manager role; reverts otherwise.
    modifier onlyManager() {
        _checkManagerAdmin();
        _;
    }

    modifier onlyValidId(uint256 _id) {
        if (_id == 0) revert NonZeroIdRequired();
        _;
    }

    /*//////////////////////////////////////////////////////////////
                        Internal functions
    //////////////////////////////////////////////////////////////*/

    /// @dev Function that checks if an account has admin or manager role; reverts otherwise.
    function _checkManagerAdmin() internal view {
        if (
            !hasRole(DEFAULT_ADMIN_ROLE, msg.sender) && !hasRole(MANAGER_ROLE, msg.sender)
        ) {
            revert ManagerRoleRequired();
        }
    }

    function _insertItems(
        ItemSet storage _itemSet,
        uint256[] memory _items
    ) internal virtual {
        uint256 assignCount = _items.length;
        if (assignCount == 0) revert NonEmptyArrayRequired();

        uint256 currentIdx = _itemSet.index;
        for (uint256 i; i < assignCount; ) {
            //don't add zero group
            if (_items[i] == 0) continue;

            //don't add existing subgroup
            uint256 existingIdx = _itemSet.indexOf[_items[i]];
            if (_itemSet.items[existingIdx] == _items[i]) continue;

            //add subgroup to current index
            _itemSet.items[currentIdx] = _items[i];

            unchecked {
                //add the index reference, increment the current index
                _itemSet.indexOf[_items[i]] = currentIdx++;
                ++i;
            }
        }
        //revert if no changes to save gas
        if (_itemSet.index == currentIdx) revert AlreadyAssigned();

        //save the new index
        _itemSet.index = currentIdx;
    }

    function _removeItems(
        ItemSet storage _itemSet,
        uint256[] memory _items
    ) internal virtual {
        uint256 removeCount = _items.length;
        if (removeCount == 0) revert NonEmptyArrayRequired();

        bool unchanged = true;

        for (uint256 i; i < removeCount; ) {
            //don't add zero addresses
            if (_items[i] == 0) continue;

            //confirm the member exists
            uint256 existingIdx = _itemSet.indexOf[_items[i]];
            if (_itemSet.items[existingIdx] == _items[i]) {
                delete _itemSet.items[existingIdx];
                delete _itemSet.indexOf[_items[i]];
                unchanged = false;
            }

            unchecked {
                ++i;
            }
        }

        //revert if no changes to save gas
        if (unchanged) revert AlreadyRemoved();
    }

    function _selectItems(
        ItemSet storage _itemSet
    ) internal view returns (uint256[] memory) {
        uint256 itemLength = _itemSet.index;

        uint256[] memory allItems = new uint256[](itemLength);
        uint256 actualLength = 0;

        unchecked {
            for (uint256 i; i < itemLength; ) {
                allItems[i] = _itemSet.items[i];
                if (allItems[i] != 0) {
                    ++actualLength;
                }
                ++i;
            }
        }

        uint256[] memory actualItems = new uint256[](actualLength);

        uint256 idx;

        unchecked {
            for (uint256 i; i < itemLength; ) {
                if (allItems[i] != 0) {
                    actualItems[idx] = allItems[i];
                    ++idx;
                }
                ++i;
            }
        }

        return actualItems;
    }

    function _isGroupMember(
        uint256 _groupId,
        address _member
    ) internal view returns (bool) {
        uint256 subgroupCount = _groups[_groupId].index;
        for (uint256 i; i < subgroupCount; ) {
            uint256 subgroupId = _groups[_groupId].items[i];
            if (subgroupId == 0) continue;
            if (_isSubgroupMember(subgroupId, _member)) return true;
            unchecked {
                ++i;
            }
        }
        return false;
    }

    function _isSubgroupMember(
        uint256 _subgroupId,
        address _member
    ) internal view returns (bool) {
        uint256 memberIdx = _subgroups[_subgroupId].indexOf[uint160(_member)];
        return _subgroups[_subgroupId].items[memberIdx] == uint160(_member);
    }

    function _getSubgroups(uint256 _groupId) internal view returns (uint256[] memory) {
        ItemSet storage group = _groups[_groupId];
        return _selectItems(group);
    }

    function _getSubgroupMembers(
        uint256 _subgroupId
    ) internal view returns (address[] memory) {
        ItemSet storage subgroup = _subgroups[_subgroupId];
        uint256[] memory items = _selectItems(subgroup);
        uint256 itemsLength = items.length;

        address[] memory members = new address[](itemsLength);
        for (uint256 i; i < itemsLength; ) {
            members[i] = address(uint160(items[i]));
            unchecked {
                ++i;
            }
        }
        return members;
    }

    function _getGroupMembers(
        uint256 _groupId
    ) internal view returns (SubgroupMembersQuery[] memory) {
        uint256[] memory subgroupIds = _getSubgroups(_groupId);

        uint256 subgroupsLength = subgroupIds.length;
        SubgroupMembersQuery[] memory subgroups = new SubgroupMembersQuery[](
            subgroupsLength
        );

        for (uint256 i; i < subgroupsLength; ) {
            subgroups[i] = SubgroupMembersQuery({
                subgroupId: subgroupIds[i],
                members: _getSubgroupMembers(subgroupIds[i])
            });
            unchecked {
                ++i;
            }
        }

        return subgroups;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @author Syky - Nathan Rempel

interface IGroupRegistry {
    struct ItemSet {
        uint256 index;
        mapping(uint256 => uint256) items;
        mapping(uint256 => uint256) indexOf;
    }

    struct GroupMembersQuery {
        uint256 groupId;
        SubgroupMembersQuery[] subgroups;
    }

    struct SubgroupMembersQuery {
        uint256 subgroupId;
        address[] members;
    }

    function isGroupMember(
        uint256 _groupId,
        address _member
    ) external view returns (bool);

    function isSubgroupMember(
        uint256 _subgroupId,
        address _member
    ) external view returns (bool);

    function groupMembers(
        uint256 _groupId
    ) external view returns (GroupMembersQuery memory);

    function groupSubgroups(uint256 _groupId) external view returns (uint256[] memory);

    function subgroupMembers(
        uint256 _subgroupId
    ) external view returns (SubgroupMembersQuery memory);

    function assignMembers(uint256 _subgroupId, address[] calldata _members) external;

    function removeMembers(uint256 _subgroupId, address[] calldata _members) external;

    function assignSubgroups(uint256 _groupId, uint256[] calldata _subgroupIds) external;

    function removeSubgroups(uint256 _groupId, uint256[] calldata _subgroupIds) external;

    event MembersAssigned(uint256 indexed subgroupId, address[] members);
    event MembersRemoved(uint256 indexed subgroupId, address[] members);
    event SubgroupsAssigned(uint256 indexed groupId, uint256[] subgroupIds);
    event SubgroupsRemoved(uint256 indexed groupId, uint256[] subgroupIds);

    error ManagerRoleRequired();
    error NonZeroIdRequired();
    error NonEmptyArrayRequired();
    error AlreadyAssigned();
    error AlreadyRemoved();
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @author Syky - Nathan Rempel

/*
           @@@   @@@@@   @@@@@@@         @@@@    @@@@@@@      @@@@@     @@@@@@@        @@@@
         @@@@      @@@     @@@@@@        @@       @@@@@@       @@        @@@@@@        @@
         @@@@@      @@      @@@@@       @@        @@@@@       @            @@@@@      @@
         @@@@@@      @@      @@@@@     @@         @@@@@     @               @@@@     @@
          @@@@@@      @       @@@@@   @@          @@@@@    @                @@@@@    @
           @@@@@@@             @@@@@ @@           @@@@@  @@@                 @@@@@  @
             @@@@@@             @@@@@@            @@@@@@@@@@@                 @@@@@@
               @@@@@@           @@@@@             @@@@@  @@@@@                 @@@@@
                @@@@@@@         @@@@@             @@@@@   @@@@@                @@@@@
         @        @@@@@@        @@@@@             @@@@@    @@@@@               @@@@@
         @@        @@@@@        @@@@@             @@@@@     @@@@@              @@@@@
         @@@@      @@@@@        @@@@@             @@@@@@     @@@@@@           @@@@@@
         @@@@@@   @@@@         @@@@@@@           @@@@@@@     @@@@@@@          @@@@@@@
*/

import "../base/GroupRegistry.sol";

contract SykyGroups is GroupRegistry {
    /*//////////////////////////////////////////////////////////////
                            Version Info
    //////////////////////////////////////////////////////////////*/

    string public constant ENV = "GOERLI";
    string public constant VER = "1.0.0";

    /*//////////////////////////////////////////////////////////////
                            Constructor
    //////////////////////////////////////////////////////////////*/

    constructor(address defaultAdmin_) GroupRegistry(defaultAdmin_) {}
}

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

Context size (optional):