Skip to content

Add PolicyRegistry implementation#7

Draft
eric-ships wants to merge 11 commits into
masterfrom
impl/policy-registry
Draft

Add PolicyRegistry implementation#7
eric-ships wants to merge 11 commits into
masterfrom
impl/policy-registry

Conversation

@eric-ships
Copy link
Copy Markdown
Collaborator

@eric-ships eric-ships commented May 16, 2026

Motivation

This adds an implementation of PolicyRegistry.sol and the small interface update
needed to support it, covering policy creation, administration, and authorization for
the WHITELIST, BLACKLIST, and COMPOUND types (TIP-403 and TIP-1015 parity, plus a
fourth compound slot for redeem policy specification).

What changed

  • IPolicyRegistry: added redeemerPolicyId as a fourth slot on CompoundPolicyData,
    updated createCompoundPolicy and compoundPolicyData signatures to match, added
    the isAuthorizedRedeemer query, and updated CompoundPolicyCreated event.
  • PolicyRegistry.sol: implementation covering policy creation, administration,
    and authorization queries for WHITELIST, BLACKLIST, and COMPOUND policy types.

What was tried / considered

Single-slot storage layout. The central design constraint was: a compound policy
lookup should cost at most 2 SLOADs, regardless of which role is being checked. To hit
that, all four constituent IDs and the type discriminator need to fit in one slot.

The naive layout would use two separate mappings (one for simple policies, one for
compound) and require reading the type first then branching to the right mapping, which
is either 1 or 2 SLOADs depending on whether you get lucky. Instead, both policy types
are packed into a single mapping(uint64 => uint256), with the low 8 bits always
holding the type tag and the remaining bits varying by type:

  • WHITELIST/BLACKLIST: [type(8b) | admin(160b)], fits with 88 bits to spare
  • COMPOUND: [type(8b) | sender(62b) | recipient(62b) | mintRecipient(62b) | redeemer(62b)],
    exactly 256 bits

Using 62-bit rather than 64-bit child policy IDs is what makes compound fit. Four 64-bit
IDs plus the 8-bit type tag would be 264 bits (8 over budget), so 2 bits are trimmed
from each ID. The interface still uses uint64 throughout; the truncation only happens
in packed storage, and 2^62 policies is not a realistic ceiling.

Unified policy ID namespace. All policy IDs, whether simple or constituent slots
inside a compound policy, are drawn from the same counter. A 62-bit ID supports the
same range regardless of where it appears.

No recursion cap needed. Compound constituents are validated at creation time to be
non-COMPOUND, so the evaluation graph is at most one level deep by construction. No
cycle detection or depth counter is needed at query time.

isAuthorized semantics unchanged. The existing isAuthorized(policyId, user)
remains isAuthorizedSender && isAuthorizedRecipient. Redeem checks use the new
isAuthorizedRedeemer explicitly; there is no combined "all roles" helper.

Implements IPolicyRegistry with a single-slot storage layout optimized
for lookup efficiency. Every policy lookup (simple or compound) resolves
in at most 2 SLOADs.

Also adds a fourth slot to CompoundPolicyData for redeem policy
specification, with matching updates to the interface signatures and
a new isAuthorizedRedeemer query.
@eric-ships eric-ships marked this pull request as draft May 16, 2026 02:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant