Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions contracts/contracts/coordination/GlobalAllowList.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import "./Coordinator.sol";

/**
* @title GlobalAllowList
* @notice Manages a global allow list of addresses that are authorized to decrypt ciphertexts.
* @notice Manages a global allow list of addresses that are authorized to decrypt plaintexts.

Copilot AI Sep 22, 2025

Copy link

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'.

Suggested change
* @notice Manages a global allow list of addresses that are authorized to decrypt plaintexts.
* @notice Manages a global allow list of addresses that are authorized to decrypt ciphertexts.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown

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?:

Suggested change
* @notice Manages a global allow list of addresses that are authorized to decrypt plaintexts.
* @notice Manages a global allow list of addresses that are authorized to encrypt plaintexts.

*/
contract GlobalAllowList is IEncryptionAuthorizer, Initializable {
using MessageHashUtils for bytes32;
Expand Down Expand Up @@ -129,7 +129,7 @@ contract GlobalAllowList is IEncryptionAuthorizer, Initializable {
function authorize(
uint32 ritualId,
address[] calldata addresses
) external canSetAuthorizations(ritualId) {
) external virtual canSetAuthorizations(ritualId) {
setAuthorizations(ritualId, addresses, true);
}

Expand All @@ -141,7 +141,7 @@ contract GlobalAllowList is IEncryptionAuthorizer, Initializable {
function deauthorize(
uint32 ritualId,
address[] calldata addresses
) external canSetAuthorizations(ritualId) {
) external virtual canSetAuthorizations(ritualId) {
setAuthorizations(ritualId, addresses, false);
}

Expand Down
156 changes: 63 additions & 93 deletions contracts/contracts/coordination/ManagedAllowList.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment thread
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));
Comment thread
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 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd avoid any changes in Coordinator to call it
This function included in grantAuthAdminRole function, so it will be called anyway during first granting

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 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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);
}
}
Loading
Loading