Skip to content

Commit 60468b0

Browse files
committed
feat(RAM): add emergency role revocation
Add IEmergencyRoleControl interface and emergencyRevokeRole to RecurringAgreementManager. Allows PAUSE_ROLE holders to surgically disable a specific actor (operator, collector, data service) without halting the entire contract. Governor role is excluded to prevent a pause guardian from locking out governance. Tests in following commit (depend on refactored test infrastructure).
1 parent 07e00fa commit 60468b0

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
3+
pragma solidity ^0.7.6 || ^0.8.0;
4+
5+
/**
6+
* @title IEmergencyRoleControl
7+
* @author Edge & Node
8+
* @notice Interface for emergency role revocation by pause-role holders.
9+
* @dev Provides a surgical alternative to pausing: disable a specific actor
10+
* (operator, collector, data service) without halting the entire contract.
11+
* Only the governor (role admin) can re-grant revoked roles.
12+
*/
13+
interface IEmergencyRoleControl {
14+
/**
15+
* @notice Emergency role revocation by pause-role holder
16+
* @dev Allows pause-role holders to revoke any non-governor role as a fast-response
17+
* emergency measure. Governor role is excluded to prevent a pause guardian from
18+
* locking out governance.
19+
* @param role The role to revoke
20+
* @param account The account to revoke the role from
21+
*/
22+
function emergencyRevokeRole(bytes32 role, address account) external;
23+
}

packages/issuance/contracts/agreement/RecurringAgreementManager.sol

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { IPaymentsEscrow } from "@graphprotocol/interfaces/contracts/horizon/IPa
1818
import { IRecurringCollector } from "@graphprotocol/interfaces/contracts/horizon/IRecurringCollector.sol";
1919
import { IDataServiceAgreements } from "@graphprotocol/interfaces/contracts/data-service/IDataServiceAgreements.sol";
2020
import { IProviderEligibility } from "@graphprotocol/interfaces/contracts/issuance/eligibility/IProviderEligibility.sol";
21+
import { IEmergencyRoleControl } from "@graphprotocol/interfaces/contracts/issuance/common/IEmergencyRoleControl.sol";
2122

2223
import { EnumerableSetUtil } from "../common/EnumerableSetUtil.sol";
2324
import { BaseUpgradeable } from "../common/BaseUpgradeable.sol";
@@ -60,7 +61,8 @@ contract RecurringAgreementManager is
6061
IRecurringEscrowManagement,
6162
IProviderEligibilityManagement,
6263
IRecurringAgreements,
63-
IProviderEligibility
64+
IProviderEligibility,
65+
IEmergencyRoleControl
6466
{
6567
using EnumerableSet for EnumerableSet.Bytes32Set;
6668
using EnumerableSet for EnumerableSet.AddressSet;
@@ -73,6 +75,9 @@ contract RecurringAgreementManager is
7375
/// @notice Thrown when the issuance allocator does not support IIssuanceAllocationDistribution
7476
error InvalidIssuanceAllocator(address allocator);
7577

78+
/// @notice Thrown when attempting to emergency-revoke the governor role
79+
error CannotRevokeGovernorRole();
80+
7681
using EnumerableSetUtil for EnumerableSet.Bytes32Set;
7782

7883
// -- Role Constants --
@@ -201,6 +206,7 @@ contract RecurringAgreementManager is
201206
interfaceId == type(IProviderEligibilityManagement).interfaceId ||
202207
interfaceId == type(IRecurringAgreements).interfaceId ||
203208
interfaceId == type(IProviderEligibility).interfaceId ||
209+
interfaceId == type(IEmergencyRoleControl).interfaceId ||
204210
super.supportsInterface(interfaceId);
205211
}
206212

@@ -234,6 +240,15 @@ contract RecurringAgreementManager is
234240
$.issuanceAllocator = IIssuanceAllocationDistribution(newIssuanceAllocator);
235241
}
236242

243+
// -- IEmergencyRoleControl --
244+
245+
/// @inheritdoc IEmergencyRoleControl
246+
/// @dev Governor role is excluded to prevent a pause guardian from locking out governance.
247+
function emergencyRevokeRole(bytes32 role, address account) external override onlyRole(PAUSE_ROLE) {
248+
require(role != GOVERNOR_ROLE, CannotRevokeGovernorRole());
249+
_revokeRole(role, account);
250+
}
251+
237252
// -- IAgreementOwner --
238253

239254
/// @inheritdoc IAgreementOwner

0 commit comments

Comments
 (0)