//SPDX-License-Identifier: MIT pragma solidity >=0.8.23 <=0.8.28; import {ISemaphoreGroups} from "../interfaces/ISemaphoreGroups.sol"; import {InternalLeanIMT, LeanIMTData} from "@zk-kit/lean-imt.sol/InternalLeanIMT.sol"; /// @title Semaphore groups contract. /// @dev This contract allows you to create groups, add, remove and update members. /// You can use getters to obtain informations about groups (root, depth, number of leaves). abstract contract SemaphoreGroups is ISemaphoreGroups { using InternalLeanIMT for LeanIMTData; /// @dev Gets a group id and returns its tree data. /// The tree is an Incremental Merkle Tree /// which is called Lean Incremental Merkle Tree. mapping(uint256 => LeanIMTData) internal merkleTrees; /// @dev Gets a group id and returns its admin. /// The admin can be an Ethereum account or a smart contract. mapping(uint256 => address) internal admins; /// @dev Gets a group id and returns any pending admin. /// The pending admin can be an Ethereum account or a smart contract. mapping(uint256 => address) internal pendingAdmins; /// @dev Checks if the group admin is the transaction sender. /// @param groupId: Id of the group. modifier onlyGroupAdmin(uint256 groupId) { if (admins[groupId] != msg.sender) { revert Semaphore__CallerIsNotTheGroupAdmin(); } _; } /// @dev Checks if the group exists. /// @param groupId: Id of the group. modifier onlyExistingGroup(uint256 groupId) { if (admins[groupId] == address(0)) { revert Semaphore__GroupDoesNotExist(); } _; } /// @dev Creates a new group. Only the admin will be able to add or remove members. /// @param groupId: Id of the group. /// @param admin: Admin of the group. function _createGroup(uint256 groupId, address admin) internal virtual { admins[groupId] = admin; emit GroupCreated(groupId); emit GroupAdminUpdated(groupId, address(0), admin); } /// @dev Updates the group admin. In order for the new admin to actually be updated, /// they must explicitly accept by calling `_acceptGroupAdmin`. /// @param groupId: Id of the group. /// @param newAdmin: New admin of the group. function _updateGroupAdmin(uint256 groupId, address newAdmin) internal virtual onlyGroupAdmin(groupId) { pendingAdmins[groupId] = newAdmin; emit GroupAdminPending(groupId, msg.sender, newAdmin); } /// @dev Allows the new admin to accept to update the group admin with their address. /// @param groupId: Id of the group. function _acceptGroupAdmin(uint256 groupId) internal virtual { if (pendingAdmins[groupId] != msg.sender) { revert Semaphore__CallerIsNotThePendingGroupAdmin(); } address oldAdmin = admins[groupId]; admins[groupId] = msg.sender; delete pendingAdmins[groupId]; emit GroupAdminUpdated(groupId, oldAdmin, msg.sender); } /// @dev Adds an identity commitment to an existing group. /// @param groupId: Id of the group. /// @param identityCommitment: New identity commitment. /// @return merkleTreeRoot New root hash of the tree. function _addMember( uint256 groupId, uint256 identityCommitment ) internal virtual onlyGroupAdmin(groupId) returns (uint256 merkleTreeRoot) { uint256 index = getMerkleTreeSize(groupId); merkleTreeRoot = merkleTrees[groupId]._insert(identityCommitment); emit MemberAdded(groupId, index, identityCommitment, merkleTreeRoot); } /// @dev Adds new members to an existing group. /// @param groupId: Id of the group. /// @param identityCommitments: New identity commitments. /// @return merkleTreeRoot New root hash of the tree. function _addMembers( uint256 groupId, uint256[] calldata identityCommitments ) internal virtual onlyGroupAdmin(groupId) returns (uint256 merkleTreeRoot) { uint256 startIndex = getMerkleTreeSize(groupId); merkleTreeRoot = merkleTrees[groupId]._insertMany(identityCommitments); emit MembersAdded(groupId, startIndex, identityCommitments, merkleTreeRoot); } /// @dev Updates an identity commitment of an existing group. A proof of membership is /// needed to check if the node to be updated is part of the tree. /// @param groupId: Id of the group. /// @param oldIdentityCommitment: Existing identity commitment to be updated. /// @param newIdentityCommitment: New identity commitment. /// @param merkleProofSiblings: Array of the sibling nodes of the proof of membership. /// @return merkleTreeRoot New root hash of the tree. function _updateMember( uint256 groupId, uint256 oldIdentityCommitment, uint256 newIdentityCommitment, uint256[] calldata merkleProofSiblings ) internal virtual onlyGroupAdmin(groupId) returns (uint256 merkleTreeRoot) { uint256 index = merkleTrees[groupId]._indexOf(oldIdentityCommitment); merkleTreeRoot = merkleTrees[groupId]._update( oldIdentityCommitment, newIdentityCommitment, merkleProofSiblings ); emit MemberUpdated(groupId, index, oldIdentityCommitment, newIdentityCommitment, merkleTreeRoot); } /// @dev Removes an identity commitment from an existing group. A proof of membership is /// needed to check if the node to be deleted is part of the tree. /// @param groupId: Id of the group. /// @param identityCommitment: Existing identity commitment to be removed. /// @param merkleProofSiblings: Array of the sibling nodes of the proof of membership. /// @return merkleTreeRoot New root hash of the tree. function _removeMember( uint256 groupId, uint256 identityCommitment, uint256[] calldata merkleProofSiblings ) internal virtual onlyGroupAdmin(groupId) returns (uint256 merkleTreeRoot) { uint256 index = merkleTrees[groupId]._indexOf(identityCommitment); merkleTreeRoot = merkleTrees[groupId]._remove(identityCommitment, merkleProofSiblings); emit MemberRemoved(groupId, index, identityCommitment, merkleTreeRoot); } /// @dev See {ISemaphoreGroups-getGroupAdmin}. function getGroupAdmin(uint256 groupId) public view virtual override returns (address) { return admins[groupId]; } /// @dev See {ISemaphoreGroups-hasMember}. function hasMember(uint256 groupId, uint256 identityCommitment) public view virtual override returns (bool) { return merkleTrees[groupId]._has(identityCommitment); } /// @dev See {ISemaphoreGroups-indexOf}. function indexOf(uint256 groupId, uint256 identityCommitment) public view virtual override returns (uint256) { return merkleTrees[groupId]._indexOf(identityCommitment); } /// @dev See {ISemaphoreGroups-getMerkleTreeRoot}. function getMerkleTreeRoot(uint256 groupId) public view virtual override returns (uint256) { return merkleTrees[groupId]._root(); } /// @dev See {ISemaphoreGroups-getMerkleTreeDepth}. function getMerkleTreeDepth(uint256 groupId) public view virtual override returns (uint256) { return merkleTrees[groupId].depth; } /// @dev See {ISemaphoreGroups-getMerkleTreeSize}. function getMerkleTreeSize(uint256 groupId) public view virtual override returns (uint256) { return merkleTrees[groupId].size; } }