Skip to content

Add PolicyRegistry implementation#20

Open
eric-ships wants to merge 10 commits into
mainfrom
eric/policy-registry-impl
Open

Add PolicyRegistry implementation#20
eric-ships wants to merge 10 commits into
mainfrom
eric/policy-registry-impl

Conversation

@eric-ships
Copy link
Copy Markdown
Collaborator

Motivation

IPolicyRegistry was merged to main but had no implementation. This PR provides PolicyRegistry.sol, a reference implementation of the simplified v1 interface: ALLOWLIST/BLOCKLIST policies only, batch membership updates, the spec naming conventions (updateAllowlist, updateBlocklist, stageUpdateAdmin, finalizeUpdateAdmin, renounceAdmin), and the built-in sentinel IDs (0 = always-allow, type(uint64).max = always-reject).

What changed

  • src/impls/PolicyRegistry.sol: new file, full implementation of IPolicyRegistry

Storage design

Each policy is stored as a single packed uint256:

[255:168] unused
[167:8]   admin address (160 bits)
[7:0]     PolicyType discriminator (ALLOWLIST = 0, BLOCKLIST = 1)

Existence check: _policies[id] == 0 means never created. This works because createPolicy requires a non-zero admin, so any valid packed value is non-zero. Built-in IDs (0 and type(uint64).max) are handled as constants with no storage access.

A second mapping _members[policyId][account] stores set membership (true = on the list, regardless of type). A third mapping _pendingAdmins[policyId] holds the staged pending admin for in-flight two-step transfers.

What was tried / considered

Separate PolicySlot library (previous PR #7): The old implementation used a library to pack compound policy data into a single slot with four 62-bit constituent fields. That complexity is no longer needed since COMPOUND policies are out of scope for v1. Inlining the two encode/decode helpers directly into the contract is simpler and equally readable.

ID counter overflow guard: The counter _nextId increments with unchecked. It will never reach type(uint64).max in practice (that sentinel is reserved for always-reject), so no explicit overflow revert is added.

Implements IPolicyRegistry following the simplified interface on main:
ALLOWLIST/BLOCKLIST only (no COMPOUND), batch membership updates,
spec naming (updateAllowlist/Blocklist, stageUpdateAdmin,
finalizeUpdateAdmin, renounceAdmin), and built-in IDs 0 (always-allow)
and type(uint64).max (always-reject).

Storage layout: each policy is a single packed uint256 with the admin
address in bits [167:8] and the PolicyType discriminator in bits [7:0].
Existence is checked by _policies[id] == 0 (safe because createPolicy
requires a non-zero admin).
- ALWAYS_BLOCK_ID changes from type(uint64).max to 1
- Global counter (_nextCounter) starts at 2, typed uint56
- nextPolicyId(PolicyType) returns full top-byte-encoded ID
- Type is derived from ID top byte via _typeFromId; not stored in packed slot
- policyType() returns ALWAYS_ALLOW/ALWAYS_BLOCK for built-ins instead of reverting
- Remove AlwaysAllowPolicy/AlwaysRejectPolicy (not in interface)
- _requireCustom simplified: just checks CREATED_BIT, built-ins fall through to PolicyNotFound
@eric-ships eric-ships force-pushed the eric/policy-registry-impl branch from 9e51de3 to 6fd0607 Compare May 20, 2026 02:53
Replaces the skeleton MockPolicyRegistry with the full reference
implementation following the MockB20/MockActivationRegistry pattern:
written as Solidity-as-if-Rust, etched at the precompile address via
vm.etch. Removes src/impls/PolicyRegistry.sol.

Also fixes stale test stubs and BaseTest comment that still referenced
type(uint64).max as the always-reject sentinel (now built-in ID 1),
and rewrites nextPolicyId stubs to reflect the global counter design
(shared counter, both types advance together).
Fills in all 13 test stubs across createPolicy, createPolicyWithAccounts,
stageUpdateAdmin, finalizeUpdateAdmin, renounceAdmin, updateAllowlist,
updateBlocklist, isAuthorized, nextPolicyId, policyExists, policyType,
policyAdmin, and pendingPolicyAdmin.

Also fixes _nextCounter initialization: vm.etch does not run the
constructor so storage state variable initializers are not applied.
Counter starts at 0; the discriminator byte (0x02/0x03) ensures custom
IDs can never equal built-in IDs 0 or 1.
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