Add PolicyRegistry implementation#7
Closed
eric-ships wants to merge 16 commits into
Closed
Conversation
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.
…rename _loadCustom
PolicyNotSimple -> ConstituentIsCompound, _createSimple -> _createPolicy, _requireSimpleConstituent -> _requireConstituent. Updates interface NatSpec and comments throughout to use WHITELIST/BLACKLIST/COMPOUND directly.
…tdoc, simplify _checkRole
policyData() now returns the correct type for built-in IDs rather than the misleading WHITELIST placeholder.
Each compound constituent field now carries both the constituent policy ID (61 bits) and a single type bit (0=whitelist/built-in, 1=blacklist) in 62 bits total. _checkRole on a compound policy now reads only: 1. the compound slot itself 2. the relevant constituent's member set The constituent's own policy slot is no longer loaded on the hot path, matching the 'exactly 2 SLOADs per check' claim that the previous implementation overstated.
The compound-policy packing format reserves 61 bits per constituent ID (2.3e18 IDs), but the public API exposes uint64 IDs. _nextPolicyId now reverts with PolicyIdOverflow before issuing an ID that would silently truncate when stored in a compound slot. This enforces the packing invariant at the source: every ID returned from a create function is guaranteed safe to round-trip through encodeField / decodeField, so downstream code can rely on it without per-call defensive checks. Practically unreachable today (counter starts at 2 and only ever increments by 1), but the alternative is an unchecked truncation hazard hidden behind a comment.
Replaces single-step setPolicyAdmin (typo bricks a sanctions list) with the OZ Ownable2Step shape: - beginPolicyAdminTransfer(policyId, newAdmin) records a pending admin - acceptPolicyAdminTransfer(policyId) callable by pending only - cancelPolicyAdminTransfer(policyId) aborts the in-flight transfer - pendingPolicyAdmin(policyId) view inspects current pending Adds freezePolicy(policyId) as a one-way switch that locks the policy's membership AND admin permanently: - subsequent modifyPolicy* calls revert with PolicyFrozen - subsequent admin transfers revert with PolicyFrozen - isPolicyFrozen(policyId) view exposes the state Frozen flag lives in bit 168 of the policy slot (currently unused space above the 160-bit admin). Compound policies have no admin and cannot be frozen; calls revert with IncompatiblePolicyType. Why a freeze flag instead of letting admin be set to address(0): the existence sentinel _policyData[id] == 0 means 'never created'. A WHITELIST policy (type byte = 0) with admin = address(0) would pack to 0 exactly, colliding with the sentinel. The frozen bit avoids that collision and gives a cleaner one-way switch with a distinct event. Pending admin lives in its own mapping rather than the packed slot so the authorization hot path isn't affected; rotation is cold so the extra accept-time SLOAD is fine.
Correctness fixes on top of policy-registry impl
…ank lines, struct field tags
Collaborator
Author
|
Closing in favor of a new PR. The interface on main has been simplified significantly (no COMPOUND policies, spec naming, batch membership updates, ID 0 = always-allow). Reopening with a clean implementation that follows the current interface. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Implements
PolicyRegistry.sol(TIP-403 + TIP-1015 parity) with a fourth compound slot for redeem policy specification. Bit-packing logic is isolated inPolicySlot.sol.Interface changes
redeemerPolicyIdas a fourth slot onCompoundPolicyDatacreateCompoundPolicyandcompoundPolicyDatasignaturesisAuthorizedRedeemerqueryALWAYS_REJECTandALWAYS_ALLOWto thePolicyTypeenum for built-in IDsDesign decisions
Single-slot storage. Every policy fits in one
uint256. Low 8 bits are always the type tag; remaining bits depend on type:[type(8b) | admin(160b)]— 88 bits spare[type(8b) | sender(62b) | recipient(62b) | mintRecipient(62b) | redeemer(62b)]— exactly 256 bitsAny authorization check costs at most 2 SLOADs: one for the compound slot, one for the constituent's member set.
62-bit constituent IDs. Four 64-bit IDs plus an 8-bit type tag would be 264 bits. Trimming 2 bits per ID makes it fit exactly. The interface keeps
uint64throughout; truncation requires ~4.6e18 policies, which is not realistic.No recursion. Constituents are validated as non-COMPOUND at creation time. The evaluation graph is always one level deep — no cycle detection or depth cap needed.
Built-in IDs. IDs 0 (always-reject) and 1 (always-allow) are handled as constants with no storage access.
ALWAYS_REJECTandALWAYS_ALLOWare added to thePolicyTypeenum sopolicyData()returns a meaningful type for built-ins rather than a misleading placeholder.