> ## Documentation Index
> Fetch the complete documentation index at: https://base-a060aa97-roethke-b20-upgrade-pages.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# PolicyRegistry: NOT / Invert Policies

> Denim reserves bit 63 of a policy ID as an invert flag, so isAuthorized returns the opposite of any policy's result without a second, mirrored policy.

## Abstract

[Denim](/upgrades/denim/overview) lets any policy ID reference the opposite (NOT) of its result at
query time. When bit 63 (`INVERTED_POLICY_BIT`) is set, `isAuthorized` resolves the base policy and
returns the negation of that policy's decision. The flag applies to every policy type: `ALLOWLIST`,
`BLOCKLIST`, `UNION`, and `INTERSECT`.

Members stay on the base policy and are shared, not copied, so an update to the base also updates
its inverse. Invert creates no new record and no new create path. The change is non-breaking:
existing IDs have bit 63 unset and behave exactly as before.

## Motivation

The Policy Registry increasingly serves as a shared registry of address lists that other policies
compose around. For example, an issuer may maintain one KYC list and need it to mean "only these
addresses" in one scope and "exclude these addresses" in another.

Before Denim, the opposite outcome required a second policy of the other type with a copy of the
same addresses. Every membership change had to land on both; if one update lagged, valid accounts
were rejected or invalid ones admitted. A composite that needed "NOT A" had to point at that mirror.

Encoding inversion in the policy reference makes one registry entry reusable for `A OR B`,
`A AND NOT B`, or `NOT A` without extra policies.

## What Changed

### Policy ID Layout

A policy ID is a `uint64`. Bits `0–55` hold a unique counter; bits `56–63` hold the `PolicyType`.
The four types in use (`0–3`) occupy bits `56–57`, leaving `58–63` unused. Denim reserves bit `63`
as the invert bit.

```text Policy ID Layout theme={null}
 63  62         56 55                             0
+---+-------------+-------------------------------+
| I | PolicyType  | unique counter                |
+---+-------------+-------------------------------+
```

### Interface Changes

```solidity IPolicyRegistry.sol theme={null}
// PolicyRegistryConstants
uint64 internal constant INVERTED_POLICY_BIT = uint64(1) << 63;

function invertedPolicyId(uint64 policyId) external view returns (uint64);
```

| Symbol | Selector | Status | Behavior |
| - | - | - | - |
| `invertedPolicyId(uint64)` | `0x6b468933` | New view | Toggles bit 63 (`policyId ^ INVERTED_POLICY_BIT`). Never reverts, reads no state, and is involutive. |
| `isAuthorized(uint64,address)` | Unchanged | Extended | An inverted ID resolves the base and returns the negated result. Fail-closed on an unknown or malformed base. |
| `policyExists(uint64)` | Unchanged | Extended | Strips to base: `policyExists(invertedPolicyId(id)) == policyExists(id)`. |
| `policyAdmin(uint64)` | Unchanged | Extended | Strips to base. |
| `pendingPolicyAdmin(uint64)` | Unchanged | Extended | Strips to base. |
| `compositePolicyChildIds(uint64)` | Unchanged | Extended | Strips the queried composite's own flag; returns child IDs verbatim, including any per-child invert bit. |
| `createCompositePolicy(address,uint8,uint64[])` | Unchanged | Extended | A child ID may carry the invert bit ("A AND NOT X"); validated against its base. |
| `updateComposite(uint64,uint64[])` | Unchanged | Extended | Same per-child invert handling. |

`invertedPolicyId` does not check existence. A missing or malformed base is denied later, at
`isAuthorized`.

### Behavioral Changes

#### Authorization

`isAuthorized` gains a leading invert branch. All non-inverted paths are unchanged.

```text Authorization Evaluation lines expandable wrap highlight={2-6} theme={null}
isAuthorized(policyId, account):
    if policyId has INVERTED_POLICY_BIT set:
        base = policyId without the bit
        if not policyExists(base):      # fail-closed guard
            return false
        return not isAuthorized(base, account)

    ... existing ALLOWLIST / BLOCKLIST / UNION / INTERSECT dispatch ...
```

Inverting a composite negates the composite's combined result. An inverted ID over a never-created
base returns `false`; it never becomes allow-everyone.

#### Getters Strip to Base

Read views clear bit 63 with a shared `_basePolicyId(id) = id & ~INVERTED_POLICY_BIT` helper and read
the base record. An inverted ID has no record of its own; it mirrors the base's existence, admin,
pending admin, and child set. A token can store an inverted ID and re-validate it exactly as it would
a plain one.

#### Composite Children

A composite child may carry the invert bit. The registry checks existence and type against the base:

| Child | Result |
| - | - |
| Inverted `ALLOWLIST` or `BLOCKLIST` | Accepted; stored with the invert bit kept |
| Inverted `UNION` or `INTERSECT` | Reverts `InvalidChildPolicy`, preserving the flat-tree invariant |
| Base does not exist | Reverts `PolicyNotFound`, which still takes precedence across the whole child set |
| Built-in sentinel (`ALWAYS_ALLOW_ID`, `ALWAYS_BLOCK_ID`), with or without the invert bit | Reverts `InvalidChildPolicy`, unchanged from Cobalt |

### State and Gas

Denim adds no storage slots. Invert is query-time only: storage keys, type decode, and existence
resolve against the stripped ID. An inverted query runs the fail-closed existence guard on the base,
then the existing dispatch, then one boolean flip in memory. Non-inverted queries are unchanged.

## Examples

Given a shared `ALLOWLIST` `sanctionedId` whose members are sanctioned addresses (authorized means
on the list), the inverse authorizes every account that is not on the list:

```solidity Standalone Invert theme={null}
uint64 notSanctioned = policyRegistry.invertedPolicyId(sanctionedId); // sanctionedId ^ (1 << 63)
```

Allow transfers only for accounts on `kycId` and not on `sanctionedId`, with an `INTERSECT`
composite and an inverted child:

```solidity Composite With an Inverted Child theme={null}
uint64[] memory children = new uint64[](2);
children[0] = kycId;
children[1] = policyRegistry.invertedPolicyId(sanctionedId); // not sanctioned
policyRegistry.createCompositePolicy(admin, IPolicyRegistry.PolicyType.INTERSECT, children);
```

Fail-closed: for any never-created base, `isAuthorized(base | INVERTED_POLICY_BIT, account)` returns
`false`.

Round-trip: `invertedPolicyId(invertedPolicyId(id)) == id`, and
`policyExists(invertedPolicyId(id)) == policyExists(id)`.

## Design Decisions and Alternatives Considered

Encoding NOT in bit 63 adds no storage and no create path, and any policy, simple or composite, can
be inverted on its own. The tradeoff: the flag occupies unused `PolicyType` bitspace, and every
getter must strip it through `_basePolicyId`.

### New `NOT` Policy Type

`createNot(admin, base)` would allocate a record pointing at a base, with the clearest explorer
legibility. Standalone NOT would cost \~3 `SLOAD`s vs 1 for a mirror blocklist, and "A AND NOT X"
\~6 vs 4. It also adds a create path and deepens hot-path recursion as a composite child.

### Per-Child Invert Bitmask on the Composite

A ≤4-bit mask packed into the children length word would flip individual children. It only works
inside a composite, so a simple policy could not be inverted without wrapping it in a two-child
composite. It could later compose on top of the invert bit.

## Migration

This change is non-breaking. All existing selectors, events, and errors are unchanged, and existing
composites are unaffected. To adopt:

1. Compute the inverse with `invertedPolicyId(policyId)`, or set bit 63 directly.
2. Bind it to a B20 scope with `updatePolicy`, or pass it as a composite child. B20 needs no change;
   it treats the ID as an opaque `uint64`.
3. Consumers that store policy IDs must still validate `policyExists(policyId)` at write time. This
   works for inverted IDs because existence resolves to the base.

For the current interface, see the
[IPolicyRegistry reference](/specifications/b20/reference/interfaces/i-policy-registry/index).
