-
Notifications
You must be signed in to change notification settings - Fork 12
Allow list with cohort and auth admin roles #341
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,149 +2,119 @@ | |
|
|
||
| pragma solidity ^0.8.0; | ||
|
|
||
| import "../lib/LookupKey.sol"; | ||
| import "@openzeppelin-upgradeable/contracts/access/AccessControlUpgradeable.sol"; | ||
| import "./GlobalAllowList.sol"; | ||
| import "./Coordinator.sol"; | ||
| import {UpfrontSubscriptionWithEncryptorsCap} from "./subscription/Subscription.sol"; | ||
|
|
||
| /** | ||
| * @title ManagedAllowList | ||
| * @notice Manages a list of addresses that are authorized to decrypt ciphertexts, with additional management features. | ||
| * @notice Manages a list of addresses that are authorized to encrypt plaintexts, with additional management features. | ||
|
Muhammad-Altabba marked this conversation as resolved.
|
||
| * This contract extends the GlobalAllowList contract and introduces additional management features. | ||
| * It maintains a reference to a Subscription contract, which is used to manage the authorization caps for different addresses and rituals. | ||
| * The Subscription contract is used to enforce limits on the number of authorization actions that can be performed, and these limits can be set and updated through the ManagedAllowList contract. | ||
| */ | ||
| contract ManagedAllowList is GlobalAllowList { | ||
| mapping(bytes32 => uint256) internal allowance; | ||
| contract ManagedAllowList is GlobalAllowList, AccessControlUpgradeable { | ||
| mapping(address authAdmin => mapping(bytes32 lookupKey => bool)) public authAdmins; | ||
|
|
||
| /** | ||
| * @notice The Subscription contract used to manage authorization caps | ||
| */ | ||
| // TODO: Should it be updatable? | ||
| UpfrontSubscriptionWithEncryptorsCap public immutable subscription; | ||
| bytes32 public constant COHORT_ADMIN_BASE = keccak256("COHORT_ADMIN"); | ||
| bytes32 public constant AUTH_ADMIN_BASE = keccak256("AUTH_ADMIN"); | ||
|
|
||
| /** | ||
| * @notice Emitted when an administrator cap is set | ||
| * @param ritualId The ID of the ritual | ||
| * @param _address The address of the administrator | ||
| * @param cap The cap value | ||
| * @notice Sets the coordinator contract | ||
| * @dev The coordinator contract cannot be a zero address and must have a valid number of rituals | ||
| * @param _coordinator The address of the coordinator contract | ||
| */ | ||
| event AdministratorCapSet(uint32 indexed ritualId, address indexed _address, uint256 cap); | ||
| constructor(Coordinator _coordinator) GlobalAllowList(_coordinator) {} | ||
|
|
||
| /** | ||
| * @notice Sets the coordinator and subscription contracts | ||
| * @dev The coordinator and subscription contracts cannot be zero addresses | ||
| * @param _coordinator The address of the coordinator contract | ||
| * @param _subscription The address of the subscription contract | ||
| * Prepares role ID for the specific ritual | ||
| * @param ritualId The ID of the ritual | ||
| * @param role Role ID | ||
| */ | ||
| constructor( | ||
| Coordinator _coordinator, | ||
| UpfrontSubscriptionWithEncryptorsCap _subscription // TODO replace with IFeeModel subscription | ||
| ) GlobalAllowList(_coordinator) { | ||
| require(address(_subscription) != address(0), "Subscription cannot be the zero address"); | ||
| subscription = _subscription; | ||
| function ritualRole(uint32 ritualId, bytes32 role) public pure returns (bytes32) { | ||
| return keccak256(abi.encodePacked(ritualId, role)); | ||
|
cygnusv marked this conversation as resolved.
|
||
| } | ||
|
|
||
| /** | ||
| * @notice Checks if the sender is the authority of the ritual | ||
| * Prepares cohort admin role ID for the specific ritual | ||
| * @param ritualId The ID of the ritual | ||
| */ | ||
| modifier onlyCohortAuthority(uint32 ritualId) { | ||
| require( | ||
| coordinator.getAuthority(ritualId) == msg.sender, | ||
| "Only cohort authority is permitted" | ||
| ); | ||
| _; | ||
| function cohortAdminRole(uint32 ritualId) public pure returns (bytes32) { | ||
| return ritualRole(ritualId, COHORT_ADMIN_BASE); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Checks if the sender has allowance to set authorizations | ||
| * @dev This function overrides the canSetAuthorizations modifier in the GlobalAllowList contract | ||
| * Prepares auth admin role ID for the specific ritual | ||
| * @param ritualId The ID of the ritual | ||
| */ | ||
| modifier canSetAuthorizations(uint32 ritualId) override { | ||
| require(getAllowance(ritualId, msg.sender) > 0, "Only administrator is permitted"); | ||
| _; | ||
| function authAdminRole(uint32 ritualId) public pure returns (bytes32) { | ||
| return ritualRole(ritualId, AUTH_ADMIN_BASE); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Returns the allowance of an administrator for a ritual | ||
| * Acquire cohort admin role | ||
| * @param ritualId The ID of the ritual | ||
| * @param admin The address of the administrator | ||
| * @return The allowance of the administrator | ||
| */ | ||
| function getAllowance(uint32 ritualId, address admin) public view returns (uint256) { | ||
| return allowance[LookupKey.lookupKey(ritualId, admin)]; | ||
| function initializeCohortAdminRole(uint32 ritualId) public { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When it is expected this function to be called? The way I see it, we could do it in the coordinator when the ritual is initiated. This shouldn't matter in the case of ritual failing, but if ritual succeeds, then this is already done from day 1.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd avoid any changes in Coordinator to call it |
||
| bytes32 cohortAdminRole = cohortAdminRole(ritualId); | ||
| address authority = coordinator.getAuthority(ritualId); | ||
| require(authority != address(0), "Ritual is not initiated"); | ||
| if (hasRole(cohortAdminRole, authority)) { | ||
| return; | ||
| } | ||
| _setRoleAdmin(authAdminRole(ritualId), cohortAdminRole); | ||
| _grantRole(cohortAdminRole, authority); | ||
| } | ||
|
|
||
| /** | ||
| * @dev This function is called before the setAuthorizations function | ||
| * Grants Auth Admin role. Can be called only by Cohort Admin or Authority of the ritual | ||
| * @dev Automatically grants Cohort Admin role to the authority if was not granted before | ||
| * @param ritualId The ID of the ritual | ||
| * @param addresses The addresses to be authorized | ||
| * @param value The authorization status | ||
| * @param account Address for Auth Admin role | ||
| */ | ||
| function _beforeSetAuthorization( | ||
| uint32 ritualId, | ||
| address[] calldata addresses, | ||
| // TODO: Currently unused, remove? | ||
| bool value | ||
| ) internal override { | ||
| super._beforeSetAuthorization(ritualId, addresses, value); | ||
| for (uint256 i = 0; i < addresses.length; i++) { | ||
| require( | ||
| authActions[ritualId] < | ||
| subscription.authorizationActionsCap(ritualId, addresses[i]), | ||
| "Authorization cap exceeded" | ||
| ); | ||
| } | ||
| function grantAuthAdminRole(uint32 ritualId, address account) external { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need some docstrings here saying that this can only be called by the cohort admin
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ |
||
| initializeCohortAdminRole(ritualId); | ||
| grantRole(authAdminRole(ritualId), account); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Sets the administrator caps for a ritual | ||
| * @dev Only active rituals can set administrator caps | ||
| * @notice Checks if the sender is an Authorization Admin of the ritual | ||
| * @param ritualId The ID of the ritual | ||
| * @param addresses The addresses of the administrators | ||
| * @param value The cap value | ||
| */ | ||
| function setAdministratorCaps( | ||
| uint32 ritualId, | ||
| address[] calldata addresses, | ||
| uint256 value | ||
| ) internal { | ||
| require( | ||
| coordinator.isRitualActive(ritualId), | ||
| "Only active rituals can set administrator caps" | ||
| ); | ||
| for (uint256 i = 0; i < addresses.length; i++) { | ||
| allowance[LookupKey.lookupKey(ritualId, addresses[i])] = value; | ||
| emit AdministratorCapSet(ritualId, addresses[i], value); | ||
| } | ||
| authActions[ritualId] += addresses.length; | ||
| modifier canSetAuthorizations(uint32 ritualId) virtual override { | ||
| require(hasRole(authAdminRole(ritualId), msg.sender), "Only auth admin is permitted"); | ||
| _; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Adds administrators for a ritual | ||
| * @notice Authorizes a list of addresses for a ritual | ||
| * @param ritualId The ID of the ritual | ||
| * @param addresses The addresses of the administrators | ||
| * @param cap The cap value | ||
| * @param addresses The addresses to be authorized | ||
| */ | ||
| function addAdministrators( | ||
| function authorize( | ||
| uint32 ritualId, | ||
| address[] calldata addresses, | ||
| uint256 cap | ||
| ) external onlyCohortAuthority(ritualId) { | ||
| setAdministratorCaps(ritualId, addresses, cap); | ||
| address[] calldata addresses | ||
| ) external override canSetAuthorizations(ritualId) { | ||
| setAuthorizations(ritualId, addresses, true); | ||
| for (uint256 i = 0; i < addresses.length; i++) { | ||
| bytes32 lookupKey = LookupKey.lookupKey(ritualId, addresses[i]); | ||
| authAdmins[msg.sender][lookupKey] = true; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @notice Removes administrators for a ritual | ||
| * @notice Deauthorizes a list of addresses for a ritual | ||
| * @param ritualId The ID of the ritual | ||
| * @param addresses The addresses of the administrators | ||
| * @param addresses The addresses to be deauthorized | ||
| */ | ||
| function removeAdministrators( | ||
| function deauthorize( | ||
| uint32 ritualId, | ||
| address[] calldata addresses | ||
| ) external onlyCohortAuthority(ritualId) { | ||
| setAdministratorCaps(ritualId, addresses, 0); | ||
| ) external override canSetAuthorizations(ritualId) { | ||
| for (uint256 i = 0; i < addresses.length; i++) { | ||
| bytes32 lookupKey = LookupKey.lookupKey(ritualId, addresses[i]); | ||
| require( | ||
| authAdmins[msg.sender][lookupKey], | ||
| "Encryptor has not been previously authorized by the sender" | ||
| ); | ||
| } | ||
| setAuthorizations(ritualId, addresses, false); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation mentions 'decrypt plaintexts' which is inconsistent - plaintexts are unencrypted data. This should likely be 'decrypt ciphertexts' or 'perform decryption operations'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vzotova
Was this meant to be?: