// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; contract DBIS_SignerRegistry is AccessControl { bytes32 public constant SIGNER_ADMIN_ROLE = keccak256("SIGNER_ADMIN"); uint8 public constant CATEGORY_OPS = 0; uint8 public constant CATEGORY_COMPLIANCE = 1; uint8 public constant CATEGORY_CUSTODY = 2; uint8 public constant CATEGORY_RISK = 3; uint8 public constant CATEGORY_AUDITOR = 4; uint256 private constant NEVER_REVOKED = type(uint256).max; struct SignerInfo { uint8 category; uint256 effectiveFromBlock; uint256 revokedAtBlock; bool exists; } mapping(address => SignerInfo) private _signers; address[] private _signerList; uint8 public requiredSignatures = 3; uint256 public categoryMaskRequired = 1 << CATEGORY_COMPLIANCE; uint256 public categoryMaskAllowed = (1 << CATEGORY_OPS) | (1 << CATEGORY_COMPLIANCE) | (1 << CATEGORY_CUSTODY) | (1 << CATEGORY_RISK) | (1 << CATEGORY_AUDITOR); event SignerAdded(address indexed signer, uint8 category); event SignerRemoved(address indexed signer); event QuorumUpdated(uint8 requiredSigs, uint256 requiredMask, uint256 allowedMask); constructor(address admin) { _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(SIGNER_ADMIN_ROLE, admin); } function addSigner(address signer, uint8 category) external onlyRole(SIGNER_ADMIN_ROLE) { require(signer != address(0), "DBIS: zero signer"); require(!_signers[signer].exists, "DBIS: already signer"); require(category <= CATEGORY_AUDITOR, "DBIS: invalid category"); _signers[signer] = SignerInfo({ category: category, effectiveFromBlock: block.number, revokedAtBlock: NEVER_REVOKED, exists: true }); _signerList.push(signer); emit SignerAdded(signer, category); } function removeSigner(address signer) external onlyRole(SIGNER_ADMIN_ROLE) { require(_signers[signer].exists, "DBIS: not signer"); if (_signers[signer].revokedAtBlock == NEVER_REVOKED) { _signers[signer].revokedAtBlock = block.number; } _signers[signer].exists = false; for (uint256 i = 0; i < _signerList.length; i++) { if (_signerList[i] == signer) { _signerList[i] = _signerList[_signerList.length - 1]; _signerList.pop(); break; } } emit SignerRemoved(signer); } function revokeSignerAtBlock(address signer) external onlyRole(SIGNER_ADMIN_ROLE) { require(_signers[signer].exists, "DBIS: not signer"); _signers[signer].revokedAtBlock = block.number; } function setQuorum(uint8 requiredSigs, uint256 requiredMask, uint256 allowedMask) external onlyRole(SIGNER_ADMIN_ROLE) { requiredSignatures = requiredSigs; categoryMaskRequired = requiredMask; categoryMaskAllowed = allowedMask; emit QuorumUpdated(requiredSigs, requiredMask, allowedMask); } function isSigner(address signer) external view returns (bool) { return _signers[signer].exists && _signers[signer].revokedAtBlock == NEVER_REVOKED; } function isSignerActiveAtBlock(address signer, uint256 blockNum) external view returns (bool) { SignerInfo memory info = _signers[signer]; if (!info.exists) return false; if (blockNum < info.effectiveFromBlock) return false; if (info.revokedAtBlock != NEVER_REVOKED && blockNum >= info.revokedAtBlock) return false; return true; } function areSignersActiveAtBlock(address[] calldata signers, uint256 blockNum) external view returns (bool) { for (uint256 i = 0; i < signers.length; i++) { if (!this.isSignerActiveAtBlock(signers[i], blockNum)) return false; } return true; } function hasDuplicateSigners(address[] calldata signers) external pure returns (bool) { for (uint256 i = 0; i < signers.length; i++) { for (uint256 j = i + 1; j < signers.length; j++) { if (signers[i] == signers[j]) return true; } } return false; } function getSignerInfo(address signer) external view returns (uint8 category, uint256 effectiveFromBlock, uint256 revokedAtBlock) { SignerInfo memory info = _signers[signer]; require(info.exists, "DBIS: not signer"); return (info.category, info.effectiveFromBlock, info.revokedAtBlock); } function validateSigners(address[] calldata signers) external view returns (bool ok, string memory reason) { if (signers.length < requiredSignatures) return (false, "insufficient count"); uint256 categoryMask = 0; for (uint256 i = 0; i < signers.length; i++) { address s = signers[i]; (bool exists, uint8 cat, uint256 revoked) = _getSignerInfo(s); if (!exists) return (false, "not signer"); if (revoked != NEVER_REVOKED) return (false, "signer revoked"); if ((categoryMaskAllowed & (1 << cat)) == 0) return (false, "category not allowed"); if (_hasDuplicate(signers, i, s)) return (false, "duplicate signer"); categoryMask |= (1 << cat); } if ((categoryMask & categoryMaskRequired) != categoryMaskRequired) return (false, "required category missing"); return (true, ""); } function _getSignerInfo(address signer) private view returns (bool exists, uint8 category, uint256 revokedAtBlock) { SignerInfo memory info = _signers[signer]; return (info.exists, info.category, info.revokedAtBlock); } function _hasDuplicate(address[] calldata signers, uint256 currentIndex, address signer) private pure returns (bool) { for (uint256 j = currentIndex + 1; j < signers.length; j++) { if (signers[j] == signer) return true; } return false; } function getSignerCount() external view returns (uint256) { return _signerList.length; } uint256 public largeSwapAmountThreshold; uint8 public requiredSignaturesSmall = 2; uint8 public requiredSignaturesLarge = 3; function setSwapQuorum(uint256 largeThreshold, uint8 smallSigs, uint8 largeSigs) external onlyRole(SIGNER_ADMIN_ROLE) { largeSwapAmountThreshold = largeThreshold; requiredSignaturesSmall = smallSigs; requiredSignaturesLarge = largeSigs; } function validateSignersForSwap(address[] calldata signers, uint256 amountIn) external view returns (bool ok, string memory reason) { uint8 required = amountIn > largeSwapAmountThreshold ? requiredSignaturesLarge : requiredSignaturesSmall; if (signers.length < required) return (false, "insufficient count"); uint256 categoryMask = 0; for (uint256 i = 0; i < signers.length; i++) { address s = signers[i]; (bool exists, uint8 cat, uint256 revoked) = _getSignerInfo(s); if (!exists) return (false, "not signer"); if (revoked != NEVER_REVOKED) return (false, "signer revoked"); if ((categoryMaskAllowed & (1 << cat)) == 0) return (false, "category not allowed"); if (_hasDuplicate(signers, i, s)) return (false, "duplicate signer"); categoryMask |= (1 << cat); } if (amountIn > largeSwapAmountThreshold && (categoryMask & categoryMaskRequired) != categoryMaskRequired) { return (false, "required category missing"); } return (true, ""); } }