Skip to content
Draft
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
100 changes: 94 additions & 6 deletions src/interfaces/IPolicyRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@ interface IPolicyRegistry {
TYPES
//////////////////////////////////////////////////////////////*/

/// @notice Policy type discriminator.
/// @notice Policy type discriminator. Discriminants are append-only.
///
/// @param BLOCKLIST Authorized unless in the policy's set.
/// @param ALLOWLIST Authorized only if in the policy's set.
/// @dev Two kinds of policy:
/// - Membership (BLOCKLIST, ALLOWLIST): decide from an address set.
/// - Composite (UNION, INTERSECT): decide by combining child membership policies.
///
/// @param BLOCKLIST Membership. Account is authorized unless it is in the set.
/// @param ALLOWLIST Membership. Account is authorized only if it is in the set.
/// @param UNION Composite (OR). Account is authorized if any child policy authorizes it.
/// @param INTERSECT Composite (AND). Account is authorized only if every child policy authorizes it.
enum PolicyType {
BLOCKLIST,
ALLOWLIST
ALLOWLIST,
UNION,
INTERSECT
}

/*//////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -48,6 +56,20 @@ interface IPolicyRegistry {
/// @notice `finalizeUpdateAdmin` was called with no pending admin staged.
error NoPendingAdmin();

/// @notice A composite policy was created or updated with an empty child set.
error EmptyChildSet();

/// @notice A composite policy was created or updated with fewer than the minimum number of
/// children. A composite must reference at least two membership policies.
/// @param provided Number of child policy IDs supplied.
/// @param minimum Minimum required (2).
error TooFewChildren(uint256 provided, uint256 minimum);

/// @notice A composite child is not a membership policy. Children must be existing ALLOWLIST or
/// BLOCKLIST policies; a composite may not reference another composite.
/// @param childPolicyId The offending child policy ID.
error InvalidChildPolicy(uint64 childPolicyId);

/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
Expand All @@ -68,13 +90,25 @@ interface IPolicyRegistry {
/// @notice One or more accounts had their BLOCKLIST membership set to `blocked` in a single batch.
event BlocklistUpdated(uint64 indexed policyId, address indexed updater, bool blocked, address[] accounts);

/// @notice A composite policy (UNION or INTERSECT) was created over `childPolicyIds`.
event CompositePolicyCreated(
uint64 indexed policyId, address indexed creator, PolicyType policyType, uint64[] childPolicyIds
);

/// @notice A composite policy's child set was replaced in full with `childPolicyIds`. The event
/// carries the complete post-update set, so an indexer reconstructs current state from a
/// single log with no delta folding.
event CompositeChildrenUpdated(uint64 indexed policyId, address indexed updater, uint64[] childPolicyIds);

/*//////////////////////////////////////////////////////////////
POLICY CREATION
//////////////////////////////////////////////////////////////*/

/// @notice Creates a new policy with no initial members. Permissionless.
/// @notice Creates a new membership policy with no initial members. Permissionless.
///
/// @dev Reverts with `ZeroAddress` when `admin` is `address(0)`.
/// @dev Reverts with `IncompatiblePolicyType` when `policyType` is a composite gate (UNION or
/// INTERSECT); use `createCompositePolicy` for those.
/// @dev Panics with arithmetic overflow (Panic 0x11) when the policy counter has reached its maximum value.
///
/// @param admin Initial admin authorized to modify membership and transfer or renounce administration.
Expand All @@ -83,9 +117,11 @@ interface IPolicyRegistry {
/// @return newPolicyId The newly assigned policy ID.
function createPolicy(address admin, PolicyType policyType) external returns (uint64 newPolicyId);

/// @notice Creates a new policy seeded with `accounts` as initial members. Permissionless.
/// @notice Creates a new membership policy seeded with `accounts` as initial members. Permissionless.
///
/// @dev Reverts with `ZeroAddress` when `admin` is `address(0)`. Takes precedence over `BatchSizeTooLarge`.
/// @dev Reverts with `IncompatiblePolicyType` when `policyType` is a composite gate (UNION or
/// INTERSECT); use `createCompositePolicy` for those. Takes precedence over `BatchSizeTooLarge`.
/// @dev Reverts with `BatchSizeTooLarge` when `accounts.length` exceeds the registry limit.
/// @dev Panics with arithmetic overflow (Panic 0x11) when the policy counter has reached its maximum value.
///
Expand All @@ -98,6 +134,32 @@ interface IPolicyRegistry {
external
returns (uint64 newPolicyId);

/// @notice Creates a new composite policy that combines existing membership policies under a logic
/// gate. Permissionless. A UNION authorizes an account if any child authorizes it; an
/// INTERSECT authorizes only if every child does. `isAuthorized` folds the children and
/// never reverts.
///
/// @dev Children must be membership policies (ALLOWLIST or BLOCKLIST), never another composite.
/// Because children reference smaller, already-assigned IDs, the reference graph is a DAG and
/// cannot cycle. The child set is capped at the composite child limit.
/// @dev Reverts with `IncompatiblePolicyType` when `gate` is not UNION or INTERSECT.
/// @dev Reverts with `ZeroAddress` when `admin` is `address(0)`.
/// @dev Reverts with `EmptyChildSet` when `childPolicyIds` is empty.
/// @dev Reverts with `TooFewChildren` when `childPolicyIds.length == 1`.
/// @dev Reverts with `BatchSizeTooLarge` when `childPolicyIds.length` exceeds the composite child limit.
/// @dev Reverts with `PolicyNotFound` when any child does not exist.
/// @dev Reverts with `InvalidChildPolicy` when any child is itself a composite (not a membership policy).
/// @dev Panics with arithmetic overflow (Panic 0x11) when the policy counter has reached its maximum value.
///
/// @param admin Initial admin authorized to update children and transfer or renounce administration.
/// @param gate UNION or INTERSECT.
/// @param childPolicyIds Existing membership policy IDs to combine.
///
/// @return newPolicyId The newly assigned composite policy ID.
function createCompositePolicy(address admin, PolicyType policyType, uint64[] calldata childPolicyIds)
external
returns (uint64 newPolicyId);

/*//////////////////////////////////////////////////////////////
POLICY ADMINISTRATION
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -154,6 +216,32 @@ interface IPolicyRegistry {
/// @param accounts Accounts to update.
function updateBlocklist(uint64 policyId, bool blocked, address[] calldata accounts) external;

/// @notice Replaces a composite policy's child set in full with `childPolicyIds`. The caller sends
/// the complete desired child list, and it is re-validated as at creation. The gate (UNION
/// or INTERSECT) is fixed in the policy ID and cannot change; a different gate requires a
/// new composite.
///
/// @dev Overwrite semantics are last-write-wins. The function carries no expected-version guard, so
/// concurrent full-set submissions by shared admins overwrite one another with no conflict
/// signal; serialize edits through the governing multisig or approval flow, and read the
/// current children before editing.
/// @dev Guard order: `PolicyNotFound` -> `IncompatiblePolicyType` -> `Unauthorized` ->
/// `EmptyChildSet` / `TooFewChildren` / `BatchSizeTooLarge` ->
/// per-child `PolicyNotFound` / `InvalidChildPolicy`.
/// @dev Reverts with `PolicyNotFound` when `policyId` does not exist.
/// @dev Reverts with `IncompatiblePolicyType` when `policyId` is not a composite (UNION or INTERSECT).
/// @dev Reverts with `Unauthorized` when the caller is not the current admin. A renounced composite
/// (admin `address(0)`) can never be updated.
/// @dev Reverts with `EmptyChildSet` when `childPolicyIds` is empty; there is no clear-the-list path.
/// @dev Reverts with `TooFewChildren` when `childPolicyIds.length == 1`.
/// @dev Reverts with `BatchSizeTooLarge` when `childPolicyIds.length` exceeds the composite child limit.
/// @dev Reverts with `PolicyNotFound` when any child does not exist.
/// @dev Reverts with `InvalidChildPolicy` when any child is itself a composite (not a membership policy).
///
/// @param policyId Composite policy to update.
/// @param childPolicyIds Complete new child set of existing membership policy IDs.
function updateCompositeChildren(uint64 policyId, uint64[] calldata childPolicyIds) external;

/*//////////////////////////////////////////////////////////////
AUTHORIZATION QUERIES
//////////////////////////////////////////////////////////////*/
Expand Down
Loading