diff --git a/docs/base-chain/specs/reference/b20/constants-and-addresses.mdx b/docs/base-chain/specs/reference/b20/constants-and-addresses.mdx index 65c57caed..13eb9768a 100644 --- a/docs/base-chain/specs/reference/b20/constants-and-addresses.mdx +++ b/docs/base-chain/specs/reference/b20/constants-and-addresses.mdx @@ -45,7 +45,7 @@ These addresses are identical on every network where B20 is active. | Name | Value | |---|---| | `ALWAYS_ALLOW` | `0` | -| `ALWAYS_BLOCK` | `(uint64(uint8(IPolicyRegistry.PolicyType.ALLOWLIST)) << 56) \| 1` | +| `ALWAYS_BLOCK` | `(uint64(uint8(IPolicyRegistry.PolicyType.ALLOWLIST)) << 56) \| 1` | Custom policy IDs use this layout: @@ -53,6 +53,8 @@ Custom policy IDs use this layout: [8-bit PolicyType][56-bit counter] ``` +Simple and composite policies share the same global counter. The counter starts at `2` because `0` and `1` are reserved for `ALWAYS_ALLOW` and `ALWAYS_BLOCK`. + Policy type bytes: | PolicyType | Byte | @@ -62,6 +64,12 @@ Policy type bytes: | `UNION` | `0x02` | | `INTERSECT` | `0x03` | +`UNION` and `INTERSECT` are composite policy types. A `UNION` policy authorizes an account if any child policy authorizes it. An `INTERSECT` policy authorizes an account only if every child policy authorizes it. Each composite references two to four existing simple (`ALLOWLIST` or `BLOCKLIST`) policies. Composite policies cannot reference other composites. + + +A well-formed but never-created `INTERSECT` policy ID returns `true` for every account (vacuously authorized). Always call `policyExists(policyId)` before storing a composite policy ID; an invalid `INTERSECT` ID behaves like `ALWAYS_ALLOW`. + + ## Variant bytes | Variant | Byte | Address shape | diff --git a/docs/base-chain/specs/reference/b20/errors-and-events.mdx b/docs/base-chain/specs/reference/b20/errors-and-events.mdx index d4888150c..5a4f9c2f0 100644 --- a/docs/base-chain/specs/reference/b20/errors-and-events.mdx +++ b/docs/base-chain/specs/reference/b20/errors-and-events.mdx @@ -22,8 +22,8 @@ description: "Reverse lookup B20 errors and events by selector or topic." | error | `BatchSizeTooLarge` | `0x083e2f67` | `IPolicyRegistry` | A membership batch exceeded the registry limit. | | event | `BlocklistUpdated` | `0x2ff63c102b1b9fd7f5d39f83039c5d6aaf50a414a4f2def2704e41be2628f1e3` | `IPolicyRegistry` | One or more accounts had their BLOCKLIST membership set to `blocked` in a single batch. | | event | `BurnedBlocked` | `0x0b552e96653fd6842da37c477005d3b5c08a8c7d3631b1f43787b2dc9a1006a3` | `IB20` | Emitted by the deprecated `burnBlocked` in addition to `Transfer(from, address(0), amount)`. | -| error | `ChildPoliciesOutsideOfRange` | `0x697ec868` | `IPolicyRegistry` | A composite policy was created or updated with a child-policy count outside the | -| event | `CompositePolicyUpdated` | `0x4ff6adaab31b0df87aa7b8b7320c52b8b3b5eede3bf28a6baaaa8b8b7e1d6363` | `IPolicyRegistry` | A composite policy's child set was set or replaced in full with `childPolicyIds`. Emitted | +| error | `ChildPoliciesOutsideOfRange` | `0x697ec868` | `IPolicyRegistry` | A composite policy was created or updated with a child-policy count outside the allowed range of 2 to 4. | +| event | `CompositePolicyUpdated` | `0x4ff6adaab31b0df87aa7b8b7320c52b8b3b5eede3bf28a6baaaa8b8b7e1d6363` | `IPolicyRegistry` | A composite policy's child set was set or replaced in full with `childPolicyIds`. Emitted by `createCompositePolicy` and `updateComposite`. | | error | `ContractPaused` | `0xf9df5ac9` | `IB20` | The `PausableFeature` covering this operation is currently paused. | | event | `ContractURIUpdated` | `0xa5d4097edda6d87cb9329af83fb3712ef77eeb13738ffe43cc35a4ce305ad962` | `IB20` | Emitted by `updateContractURI`. Per ERC-7572, parameterless: integrators re-fetch `contractURI()`. | | error | `DelegateCallNotAllowed` | `0x0d89438e` | `IActivationRegistry` | The precompile was invoked via `DELEGATECALL` or `CALLCODE`. | @@ -38,7 +38,7 @@ description: "Reverse lookup B20 errors and events by selector or topic." | event | `FeatureActivated` | `0x8c7a0ecdbb8d96e867e43ec1aef80027976ee493c18bef399fa799ed19752451` | `IActivationRegistry` | Emitted when `feature` is activated. | | event | `FeatureDeactivated` | `0x15bf65a782c3258c63268ba9d7aed710cf9f9315d3687d9a4632ccdad7926c84` | `IActivationRegistry` | Emitted when `feature` is deactivated. | | error | `FeatureNotActivated` | `0xb9b2a425` | `IActivationRegistry` | Feature is not activated. | -| error | `IncompatiblePolicyType` | `0xf1011ef5` | `IPolicyRegistry` | The operation is incompatible with the policy's type. | +| error | `IncompatiblePolicyType` | `0xf1011ef5` | `IPolicyRegistry` | The operation is incompatible with the policy's type. `createPolicy` and `createPolicyWithAccounts` revert with this error when called with `UNION` or `INTERSECT`. | | error | `InitCallFailed` | `0x4eae0860` | `IB20Factory` | One of the `initCalls` reverted. The factory bubbles the underlying revert reason | | error | `InsufficientAllowance` | `0x192b9e4e` | `IB20` | `spender`'s allowance is less than `needed` for the requested `transferFrom`. | | error | `InsufficientBalance` | `0xdb42144d` | `IB20` | `sender`'s balance is less than `needed` for the requested transfer or burn. | @@ -46,7 +46,7 @@ description: "Reverse lookup B20 errors and events by selector or topic." | error | `InternalCallMalformed` | `0x4e2f143e` | `IB20Asset` | An inner call dispatched by `announce` was shorter than four bytes. | | error | `InvalidAmount` | `0x2c5211c6` | `IB20` | An amount argument was zero where a non-zero value is required. Not used for ERC-20 amount arguments. | | error | `InvalidApprover` | `0x8bc146c4` | `IB20` | The approval's `owner` address is invalid (typically `address(0)`). | -| error | `InvalidChildPolicy` | `0x46508ef6` | `IPolicyRegistry` | Composite policies are not simple policies. Child policies must be existing | +| error | `InvalidChildPolicy` | `0x46508ef6` | `IPolicyRegistry` | A child policy ID passed to `createCompositePolicy` or `updateComposite` is not a valid simple policy. Child policies must be existing `ALLOWLIST` or `BLOCKLIST` policies; composite policies and the built-in sentinels `ALWAYS_ALLOW` and `ALWAYS_BLOCK` are not valid children. | | error | `InvalidCurrency` | `0x997c1de8` | `IB20Factory` | The stablecoin `currency` was non-empty but contained a non-`A`-`Z` byte. | | error | `InvalidDecimals` | `0xca950391` | `IB20Factory` | The asset `decimals` was outside the allowed inclusive range | | error | `InvalidMetadataKey` | `0x86ea3abb` | `IB20Asset` | `updateExtraMetadata` was called with an empty `key`. | @@ -75,26 +75,4 @@ description: "Reverse lookup B20 errors and events by selector or topic." | event | `PolicyAdminStaged` | `0xdbf3b34a4c956c56ca05cd4b8f9293a4347ad61994445a4b89817d4a19561136` | `IPolicyRegistry` | A new admin was staged. `pendingAdmin == address(0)` clears a prior nomination. | | event | `PolicyAdminUpdated` | `0x98925cfb1bc09c5b43dd0dd56d3d95aa04fb3300927580cc588c3f5dd58c15e1` | `IPolicyRegistry` | The active admin changed. `newAdmin == address(0)` indicates renunciation; | | event | `PolicyCreated` | `0xdc870ce85be577234b8548f42b93f84bbff6d0c1f38ee725c809c49932a13885` | `IPolicyRegistry` | A new policy was created. | -| error | `PolicyForbids` | `0xa43fec12` | `IB20` | A policy slot denied the operation. | -| error | `PolicyNotFound` | `0xcccad523` | `IB20` | The provided policy ID does not exist in the policy registry. | -| error | `PolicyNotFound` | `0x720caa4f` | `IPolicyRegistry` | The referenced policy ID does not exist. | -| event | `PolicyUpdated` | `0x8b4790f7ff717fc8f60f07ae099e47ef318dc04b37ae98056b50a22b79056626` | `IB20` | Emitted by `updatePolicy` when a token's policy slot is changed. Initial slot assignment at | -| event | `RoleAdminChanged` | `0xbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff` | `IB20` | Emitted by `setRoleAdmin` when the admin role for `role` changes. | -| event | `RoleGranted` | `0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d` | `IB20` | Emitted when `account` is granted `role`. `sender` is the originating caller. | -| event | `RoleRevoked` | `0xf6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b` | `IB20` | Emitted when `role` is revoked from `account`. `sender` is the originating caller | -| error | `UIMultiplierUpdateExists` | `0x4481a68e` | `IB20Asset` | `updateUIMultiplier` was called while a live pending update already exists | -| event | `Seized` | `0xa9aec5d8b86e2fa2fd6ac3af62f2622e3dfdab1967d4cbbb56a5df7d74cb887c` | `IB20` | Emitted by `seizeWithMemo` in addition to `Transfer(from, to, amount)` (and the | -| error | `StaticCallNotAllowed` | `0xbeaba5b7` | `IActivationRegistry` | A state-mutating entry point was invoked from a `STATICCALL` frame. | -| error | `SupplyCapExceeded` | `0x4b344b11` | `IB20` | The mint would push `totalSupply` past the configured cap. | -| event | `SupplyCapUpdated` | `0x6d14f44808ce024f263432bc38d019a9951fbe674e9898b54844dbc8dc09c23a` | `IB20` | Emitted by `updateSupplyCap`. | -| event | `SymbolUpdated` | `0x64e8b5c6dcea43dd79766bb3b8af7c45968d12b68c960cf2da23856f34d598d4` | `IB20` | Emitted by `updateSymbol`. Carries the new symbol string. | -| error | `TokenAlreadyExists` | `0x15ef3a57` | `IB20Factory` | A token already exists at the deterministic address derived from | -| event | `Transfer` | `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` | `IB20` | ERC-20 transfer event. Emitted on every successful transfer (including memo'd variants), | -| error | `Unauthorized` | `0x8e4a23d6` | `IActivationRegistry` | Caller is not the activation admin. | -| error | `Unauthorized` | `0x82b42900` | `IB20` | Caller failed a positional authorization check that is not expressible as "missing role X". | -| error | `Unauthorized` | `0x82b42900` | `IPolicyRegistry` | Caller is not the admin required by the attempted operation. | -| event | `UIMultiplierUpdated` | `0x2205df4534432b2f60654a3fdb48737ffdaf3e9edb1a498bd985bc026b15b055` | `IB20Asset` | Emitted when the UI multiplier is updated. | -| event | `Unpaused` | `0xdc6281474ea3dea2a856e225c7ba3edf427de9164495817c4d49a595e683fed4` | `IB20` | Emitted by `unpause`. `features` is the argument to the call (not the resulting paused state). | -| error | `UnsupportedPolicyType` | `0xcdd98a4a` | `IB20` | `policyScope` is not a slot this token (or its variant) supports. | -| error | `UnsupportedVersion` | `0xc0d8b4e0` | `IB20Factory` | The leading `version` byte in `params` does not match any known encoding for the requested variant. | -| error | `ZeroAddress` | `0xd92e233d` | `IPolicyRegistry` | A required address argument was the zero address. | +| error | `PolicyForbids` | ` diff --git a/docs/base-chain/specs/reference/b20/index.mdx b/docs/base-chain/specs/reference/b20/index.mdx index a76152704..3f83cb0d5 100644 --- a/docs/base-chain/specs/reference/b20/index.mdx +++ b/docs/base-chain/specs/reference/b20/index.mdx @@ -53,10 +53,16 @@ State-changing PolicyRegistry calls are ActivationRegistry-gated. Read functions |---|---| | `BLOCKLIST` | Account is authorized unless listed. | | `ALLOWLIST` | Account is authorized only if listed. | -| `UNION` | Composite: account is authorized if any child simple policy authorizes it. | -| `INTERSECT` | Composite: account is authorized only if every child simple policy authorizes it. | +| `UNION` | Composite: account is authorized if any child simple policy authorizes it (OR). | +| `INTERSECT` | Composite: account is authorized only if every child simple policy authorizes it (AND). | -Composite policies reference existing simple `ALLOWLIST` or `BLOCKLIST` child policies. They cannot reference composites or built-ins as children. +Composite policies reference two to four existing simple `ALLOWLIST` or `BLOCKLIST` child policies. They cannot reference other composites or built-in sentinel policies as children. This constraint is enforced at write time, so `isAuthorized` on a composite never recurses beyond depth 1. + +Evaluation is live: each `isAuthorized` call reads the current membership of each evaluated child. `UNION` short-circuits on the first authorizing child; `INTERSECT` short-circuits on the first non-authorizing child. Child order affects gas cost but never the authorization result. + + +`isAuthorized` on an uncreated `UNION` ID returns `false` (deny-all). `isAuthorized` on an uncreated `INTERSECT` ID returns `true` (allow-all), which behaves identically to `ALWAYS_ALLOW`. Always call `policyExists(policyId)` before storing a policy ID in a token scope. + ### Policy IDs @@ -71,13 +77,13 @@ Counters `0` and `1` are reserved for built-ins: | Built-in | Value | Behavior | |---|---:|---| | `ALWAYS_ALLOW` | `0` | Authorizes every account. | -| `ALWAYS_BLOCK` | `(uint64(ALLOWLIST) << 56) \| 1` | Denies every account. | +| `ALWAYS_BLOCK` | `(uint64(ALLOWLIST) << 56) \| 1` | Denies every account. | -Custom policy creation starts at counter `2`. +Custom policy creation starts at counter `2`. Simple and composite policies share the same global counter; composite policies do not use a separate counter. ### Admin Model -Each policy has one admin. Admin transfer is two-step: `stageUpdateAdmin(policyId, newAdmin)` followed by `finalizeUpdateAdmin(policyId)` from the pending admin. `renounceAdmin(policyId)` permanently freezes membership or child-policy updates for that policy. +Each policy has one admin. Admin transfer is two-step: `stageUpdateAdmin(policyId, newAdmin)` followed by `finalizeUpdateAdmin(policyId)` from the pending admin. `renounceAdmin(policyId)` permanently freezes membership or child-policy updates for that policy. A renounced child policy remains effective in composites that reference it; its current authorization results continue to apply. ### Read Interface @@ -87,10 +93,29 @@ Each policy has one admin. Admin transfer is two-step: `stageUpdateAdmin(policyI | `policyExists(policyId)` | Returns whether a policy exists. | | `policyAdmin(policyId)` | Returns the current admin or zero. | | `pendingPolicyAdmin(policyId)` | Returns the staged admin or zero. | -| `compositePolicyChildIds(policyId)` | Returns child policy IDs for composite policies. | +| `compositePolicyChildIds(policyId)` | Returns child policy IDs for composite policies; empty for non-composites. | `isAuthorized` collapses uncreated IDs to empty-set semantics. Callers that write policy IDs into token scopes must validate `policyExists` unless writing a built-in. +### Composite Policy Interface + +```solidity +function createCompositePolicy(address admin, PolicyType policyType, uint64[] calldata childPolicyIds) + external + returns (uint64 newPolicyId); + +function updateComposite(uint64 policyId, uint64[] calldata childPolicyIds) external; + +function compositePolicyChildIds(uint64 policyId) external view returns (uint64[] memory); + +function MIN_COMPOSITE_CHILD_POLICIES() external view returns (uint256); +function MAX_COMPOSITE_CHILD_POLICIES() external view returns (uint256); +``` + +`createCompositePolicy` accepts `UNION` or `INTERSECT` as `policyType` and requires `childPolicyIds` to contain at least `MIN_COMPOSITE_CHILD_POLICIES` (`2`) and no more than `MAX_COMPOSITE_CHILD_POLICIES` (`4`) entries. `updateComposite` replaces the entire child set and enforces the same range. Neither function supports partial updates or an empty child set. + +`createPolicy` and `createPolicyWithAccounts` revert with `IncompatiblePolicyType` when called with `UNION` or `INTERSECT`. + ## Policy Integration B20 tokens store one `uint64 policyId` per supported policy scope. @@ -106,6 +131,8 @@ B20 tokens store one `uint64 policyId` per supported policy scope. All scopes default to `ALWAYS_ALLOW` at creation. `approve` and `permit` are not policy-gated. +A composite policy ID is passed to a B20 policy scope exactly like a simple policy ID. B20 stores policy scopes as an opaque `uint64` and calls `isAuthorized` generically; no B20 contract changes are required to use composite policies. + ## Mint `mint` and `mintWithMemo` are gated by `MINT_ROLE`, checked against `MINT_RECEIVER_POLICY`, and bounded by `supplyCap`. diff --git a/docs/base-chain/specs/reference/b20/interfaces/IB20.mdx b/docs/base-chain/specs/reference/b20/interfaces/IB20.mdx index 132933833..08d497006 100644 --- a/docs/base-chain/specs/reference/b20/interfaces/IB20.mdx +++ b/docs/base-chain/specs/reference/b20/interfaces/IB20.mdx @@ -76,40 +76,4 @@ description: "Generated B20 reference for IB20 functions, events, and errors." | `RoleRevoked` | `0xf6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b` | Emitted when `role` is revoked from `account`. `sender` is the originating caller | | `RoleAdminChanged` | `0xbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff` | Emitted by `setRoleAdmin` when the admin role for `role` changes. | | `LastAdminRenounced` | `0xe8d3a9872e7ca325571ff1e4c51ddd69090a0345240cc605ccde365ec867cc67` | Emitted by `renounceLastAdmin` in addition to the standard | -| `Paused` | `0x43e072977b8112813d7c2aa0b63d3c121c1fe3e714e6d2eacb5735fe4027e976` | Emitted by `pause`. `features` is the argument to the call (not the resulting paused state). | -| `Unpaused` | `0xdc6281474ea3dea2a856e225c7ba3edf427de9164495817c4d49a595e683fed4` | Emitted by `unpause`. `features` is the argument to the call (not the resulting paused state). | -| `PolicyUpdated` | `0x8b4790f7ff717fc8f60f07ae099e47ef318dc04b37ae98056b50a22b79056626` | Emitted by `updatePolicy` when a token's policy slot is changed. Initial slot assignment at | -| `SupplyCapUpdated` | `0x6d14f44808ce024f263432bc38d019a9951fbe674e9898b54844dbc8dc09c23a` | Emitted by `updateSupplyCap`. | -| `ContractURIUpdated` | `0xa5d4097edda6d87cb9329af83fb3712ef77eeb13738ffe43cc35a4ce305ad962` | Emitted by `updateContractURI`. Per ERC-7572, parameterless: integrators re-fetch `contractURI()`. | -| `NameUpdated` | `0x74321da206c1b9fa34367f7ece59ca49371dcd13820b9a5c3767ae1ecceed51a` | Emitted by `updateName`. Carries the new name string. | -| `SymbolUpdated` | `0x64e8b5c6dcea43dd79766bb3b8af7c45968d12b68c960cf2da23856f34d598d4` | Emitted by `updateSymbol`. Carries the new symbol string. | -| `EIP712DomainChanged` | `0x0a6387c9ea3628b88a633bb4f3b151770f70085117a15f9bf3787cda53f13d31` | ERC-5267 domain-change signal. Emitted exactly once per successful `updateName` call, | - -## Errors - -| Error | Selector | Summary | -|---|---|---| -| `NonPayable` | `0x6fb1b0e9` | ETH was attached to a call targeting a nonpayable token selector. | -| `AccessControlUnauthorizedAccount` | `0xe2517d3f` | `account` does not hold `neededRole`. | -| `Unauthorized` | `0x82b42900` | Caller failed a positional authorization check that is not expressible as "missing role X". | -| `ContractPaused` | `0xf9df5ac9` | The `PausableFeature` covering this operation is currently paused. | -| `InsufficientAllowance` | `0x192b9e4e` | `spender`'s allowance is less than `needed` for the requested `transferFrom`. | -| `InsufficientBalance` | `0xdb42144d` | `sender`'s balance is less than `needed` for the requested transfer or burn. | -| `InvalidSender` | `0x4c14f64c` | The transfer's source address is invalid (typically `address(0)`). | -| `InvalidReceiver` | `0x9cfea583` | The transfer's destination address is invalid (typically `address(0)`). | -| `InvalidApprover` | `0x8bc146c4` | The approval's `owner` address is invalid (typically `address(0)`). | -| `InvalidSpender` | `0x4e15efda` | The approval's `spender` address is invalid (typically `address(0)`). | -| `InvalidAmount` | `0x2c5211c6` | An amount argument was zero where a non-zero value is required. Not used for ERC-20 amount arguments. | -| `EmptyFeatureSet` | `0x4861ff45` | An empty array was passed to a function that requires at least one element. | -| `InvalidSupplyCap` | `0x0a3780ce` | The proposed supply cap is outside the permitted range: below the current | -| `SupplyCapExceeded` | `0x4b344b11` | The mint would push `totalSupply` past the configured cap. | -| `PolicyForbids` | `0xa43fec12` | A policy slot denied the operation. | -| `PolicyNotFound` | `0xcccad523` | The provided policy ID does not exist in the policy registry. | -| `UnsupportedPolicyType` | `0xcdd98a4a` | `policyScope` is not a slot this token (or its variant) supports. | -| `AccountNotSeizable` | `0x91dbbc8d` | `seizeWithMemo` was called against a `from` that is currently authorized under | -| `AccountNotBlocked` | `0x64a5cb46` | The deprecated `burnBlocked` was called against a `from` that is currently authorized under | -| `ExpiredSignature` | `0xbd2a913c` | An EIP-2612 `permit` was submitted with a `deadline` strictly less than `block.timestamp`. | -| `InvalidSigner` | `0x7ba5ffb5` | ECDSA recovery on an EIP-2612 `permit` returned `signer`, which does not match the claimed `owner`. | -| `LastAdminCannotRenounce` | `0x361513e7` | `renounceRole(DEFAULT_ADMIN_ROLE, ...)` was called by the sole remaining admin. | -| `NotSoleAdmin` | `0x2a98e73b` | `renounceLastAdmin()` was called when other accounts also hold `DEFAULT_ADMIN_ROLE`. | -| `AccessControlBadConfirmation` | `0x6697b232` | The `callerConfirmation` argument to `renounceRole` was not `msg.sender`. | +| `Paused` | `0x43e072977b8 diff --git a/docs/base-chain/specs/reference/b20/interfaces/IB20/policyId.mdx b/docs/base-chain/specs/reference/b20/interfaces/IB20/policyId.mdx index efeef8e08..cbc7aa15a 100644 --- a/docs/base-chain/specs/reference/b20/interfaces/IB20/policyId.mdx +++ b/docs/base-chain/specs/reference/b20/interfaces/IB20/policyId.mdx @@ -40,7 +40,7 @@ Read-only or ERC-20-standard access rules unless the NatSpec states otherwise. ## Policy interaction -Reads or writes a token policy-scope pointer into the PolicyRegistry. +Reads or writes a token policy-scope pointer into the PolicyRegistry. The returned `uint64` is an opaque policy ID that may refer to a simple policy (`ALLOWLIST` or `BLOCKLIST`) or, as of Cobalt, a composite policy (`UNION` or `INTERSECT`). B20 passes the ID to `isAuthorized` without inspecting its type. ## Example diff --git a/docs/base-chain/specs/reference/b20/interfaces/IB20/updatePolicy.mdx b/docs/base-chain/specs/reference/b20/interfaces/IB20/updatePolicy.mdx index 6d31d5103..8cc65b0d1 100644 --- a/docs/base-chain/specs/reference/b20/interfaces/IB20/updatePolicy.mdx +++ b/docs/base-chain/specs/reference/b20/interfaces/IB20/updatePolicy.mdx @@ -39,7 +39,11 @@ Updates the policy ID assigned to `policyScope`. Takes effect immediately for th ## Policy interaction -Reads or writes a token policy-scope pointer into the PolicyRegistry. +Reads or writes a token policy-scope pointer into the PolicyRegistry. The `newPolicyId` may reference a simple policy (`ALLOWLIST` or `BLOCKLIST`) or a composite policy (`UNION` or `INTERSECT`). B20 treats the value as an opaque `uint64` and delegates all authorization logic to the PolicyRegistry. + + +Before storing a composite policy ID, call `policyExists(policyId)` on the PolicyRegistry. An invalid `INTERSECT` ID with no children returns `true` for every account, behaving like `ALWAYS_ALLOW`. + ## Example diff --git a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry.mdx b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry.mdx index c8cddcc9b..54eb86e45 100644 --- a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry.mdx +++ b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry.mdx @@ -10,20 +10,20 @@ description: "Generated B20 reference for IPolicyRegistry functions, events, and |---|---|---| | [`createPolicy`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/createPolicy) | `0xca5d55f6` | Creates a new simple policy with no initial members. Permissionless. | | [`createPolicyWithAccounts`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/createPolicyWithAccounts) | `0xa2d3044f` | Creates a new simple policy seeded with `accounts` as initial members. Permissionless. | -| [`createCompositePolicy`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/createCompositePolicy) | `0x6fdd1491` | Creates a new composite policy that combines existing simple policies under a logic | +| [`createCompositePolicy`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/createCompositePolicy) | `0x6fdd1491` | Creates a new `UNION` or `INTERSECT` composite policy referencing two to four existing simple policies. | | [`stageUpdateAdmin`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/stageUpdateAdmin) | `0x1d7ae695` | Stages a proposed new admin for `policyId`. The active admin does not change | | [`finalizeUpdateAdmin`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/finalizeUpdateAdmin) | `0x33031a9c` | Completes a two-step admin transfer. Promotes the caller to active admin and clears the pending slot. | | [`renounceAdmin`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/renounceAdmin) | `0xefdb7fa3` | Permanently relinquishes administration of `policyId`. The member set is frozen | | [`updateAllowlist`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/updateAllowlist) | `0x3388fb5b` | Sets `accounts` membership in an ALLOWLIST policy to `allowed` in one batch. | | [`updateBlocklist`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/updateBlocklist) | `0x5c4e51b8` | Sets `accounts` membership in a BLOCKLIST policy to `blocked` in one batch. | -| [`updateComposite`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/updateComposite) | `0xbfe142c0` | Replaces a composite policy's child-policy set in full with `childPolicyIds`. | +| [`updateComposite`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/updateComposite) | `0xbfe142c0` | Replaces a composite policy's entire child set with two to four existing simple policies. | | [`isAuthorized`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/isAuthorized) | `0x55a1179e` | Returns whether `account` is authorized under `policyId`. Never reverts; unknown | -| [`MIN_COMPOSITE_CHILD_POLICIES`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/MIN_COMPOSITE_CHILD_POLICIES) | `0xb3ae29f7` | Minimum number of child policies a composite must reference, inclusive. Never reverts. | -| [`MAX_COMPOSITE_CHILD_POLICIES`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/MAX_COMPOSITE_CHILD_POLICIES) | `0x54309870` | Maximum number of child policies a composite may reference, inclusive. Never reverts. | +| [`MIN_COMPOSITE_CHILD_POLICIES`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/MIN_COMPOSITE_CHILD_POLICIES) | `0xb3ae29f7` | Minimum number of child policies a composite must reference, inclusive. Returns `2`. Never reverts. | +| [`MAX_COMPOSITE_CHILD_POLICIES`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/MAX_COMPOSITE_CHILD_POLICIES) | `0x54309870` | Maximum number of child policies a composite may reference, inclusive. Returns `4`. Never reverts. | | [`policyExists`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/policyExists) | `0x330f5637` | Returns whether `policyId` is a built-in sentinel or a previously-assigned custom ID. Never reverts. | | [`policyAdmin`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/policyAdmin) | `0x09dd0a47` | Returns the current admin of `policyId`, or `address(0)` for built-in sentinels, | | [`pendingPolicyAdmin`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/pendingPolicyAdmin) | `0x017548b7` | Returns the currently-staged pending admin for `policyId`, or `address(0)` when | -| [`compositePolicyChildIds`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/compositePolicyChildIds) | `0x7c40df74` | Returns the child-policy set of the composite `policyId`. | +| [`compositePolicyChildIds`](/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/compositePolicyChildIds) | `0x7c40df74` | Returns the child-policy set of the composite `policyId`. Returns an empty array for non-composites. | ## Events @@ -34,7 +34,7 @@ description: "Generated B20 reference for IPolicyRegistry functions, events, and | `PolicyAdminUpdated` | `0x98925cfb1bc09c5b43dd0dd56d3d95aa04fb3300927580cc588c3f5dd58c15e1` | The active admin changed. `newAdmin == address(0)` indicates renunciation; | | `AllowlistUpdated` | `0x18c46532f90187ba11e436e21da087b684801d7f0787f2043f26f079c91e9ef0` | One or more accounts had their ALLOWLIST membership set to `allowed` in a single batch. | | `BlocklistUpdated` | `0x2ff63c102b1b9fd7f5d39f83039c5d6aaf50a414a4f2def2704e41be2628f1e3` | One or more accounts had their BLOCKLIST membership set to `blocked` in a single batch. | -| `CompositePolicyUpdated` | `0x4ff6adaab31b0df87aa7b8b7320c52b8b3b5eede3bf28a6baaaa8b8b7e1d6363` | A composite policy's child set was set or replaced in full with `childPolicyIds`. Emitted | +| `CompositePolicyUpdated` | `0x4ff6adaab31b0df87aa7b8b7320c52b8b3b5eede3bf28a6baaaa8b8b7e1d6363` | A composite policy's child set was set or replaced in full with `childPolicyIds`. Emitted by both `createCompositePolicy` and `updateComposite`. | ## Errors @@ -43,9 +43,9 @@ description: "Generated B20 reference for IPolicyRegistry functions, events, and | `NonPayable` | `0x6fb1b0e9` | ETH was attached to a call targeting a nonpayable policy registry selector. | | `Unauthorized` | `0x82b42900` | Caller is not the admin required by the attempted operation. | | `PolicyNotFound` | `0x720caa4f` | The referenced policy ID does not exist. | -| `IncompatiblePolicyType` | `0xf1011ef5` | The operation is incompatible with the policy's type. | +| `IncompatiblePolicyType` | `0xf1011ef5` | The operation is incompatible with the policy's type. `createPolicy` and `createPolicyWithAccounts` revert with this error when called with `UNION` or `INTERSECT`. | | `ZeroAddress` | `0xd92e233d` | A required address argument was the zero address. | | `BatchSizeTooLarge` | `0x083e2f67` | A membership batch exceeded the registry limit. | | `NoPendingAdmin` | `0xb4539afa` | `finalizeUpdateAdmin` was called with no pending admin staged. | -| `ChildPoliciesOutsideOfRange` | `0x697ec868` | A composite policy was created or updated with a child-policy count outside the | -| `InvalidChildPolicy` | `0x46508ef6` | Composite policies are not simple policies. Child policies must be existing | +| `ChildPoliciesOutsideOfRange` | `0x697ec868` | A composite policy was created or updated with a child-policy count outside the `[2, 4]` range enforced by `MIN_COMPOSITE_CHILD_POLICIES` and `MAX_COMPOSITE_CHILD_POLICIES`. | +| `InvalidChildPolicy` | `0x46508ef6` | A child policy ID is not an existing simple policy. Composite policies and the built-in `ALWAYS_ALLOW` and `ALWAYS_BLOCK` sentinels are not valid children. | diff --git a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/MAX_COMPOSITE_CHILD_POLICIES.mdx b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/MAX_COMPOSITE_CHILD_POLICIES.mdx index 30284ab3e..704f7434a 100644 --- a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/MAX_COMPOSITE_CHILD_POLICIES.mdx +++ b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/MAX_COMPOSITE_CHILD_POLICIES.mdx @@ -18,19 +18,19 @@ function MAX_COMPOSITE_CHILD_POLICIES() external view returns (uint256); ## Description -Maximum number of child policies a composite may reference, inclusive. Never reverts. -Return: Maximum permitted child-policy count. +Returns the maximum number of child policies a composite policy may reference, inclusive. The current value is `4`. This bound limits worst-case `isAuthorized` gas and the authorization audit surface. Never reverts. ## Access control -Read-only or ERC-20-standard access rules unless the NatSpec states otherwise. +Read-only. No caller restrictions. ## Policy interaction -This is part of the singleton PolicyRegistry surface used by B20 policy scopes. +This is part of the singleton PolicyRegistry surface used by B20 policy scopes. Pass this value as the upper bound when validating the `childPolicyIds` array before calling `createCompositePolicy` or `updateComposite`. ## Example -```solidity -IPolicyRegistry(target).MAX_COMPOSITE_CHILD_POLICIES(); +```solidity Title Fetch composite child policy limits +uint256 max = IPolicyRegistry(target).MAX_COMPOSITE_CHILD_POLICIES(); // 4 +uint256 min = IPolicyRegistry(target).MIN_COMPOSITE_CHILD_POLICIES(); // 2 ``` diff --git a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/MIN_COMPOSITE_CHILD_POLICIES.mdx b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/MIN_COMPOSITE_CHILD_POLICIES.mdx index 32fe2f064..7a2375d13 100644 --- a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/MIN_COMPOSITE_CHILD_POLICIES.mdx +++ b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/MIN_COMPOSITE_CHILD_POLICIES.mdx @@ -18,19 +18,18 @@ function MIN_COMPOSITE_CHILD_POLICIES() external view returns (uint256); ## Description -Minimum number of child policies a composite must reference, inclusive. Never reverts. -Return: Minimum permitted child-policy count. +Returns the minimum number of child policies a composite policy must reference, inclusive. The current minimum is `2`. Never reverts. ## Access control -Read-only or ERC-20-standard access rules unless the NatSpec states otherwise. +Read-only. No authorization required. ## Policy interaction -This is part of the singleton PolicyRegistry surface used by B20 policy scopes. +This is part of the singleton PolicyRegistry surface used by B20 policy scopes. The returned value is enforced by both `createCompositePolicy` and `updateComposite` — passing fewer than `MIN_COMPOSITE_CHILD_POLICIES` children reverts with `ChildPoliciesOutsideOfRange`. ## Example -```solidity +```solidity Title Read minimum child count IPolicyRegistry(target).MIN_COMPOSITE_CHILD_POLICIES(); ``` diff --git a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/compositePolicyChildIds.mdx b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/compositePolicyChildIds.mdx index 33a83fc94..a4724979f 100644 --- a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/compositePolicyChildIds.mdx +++ b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/compositePolicyChildIds.mdx @@ -1,10 +1,8 @@ --- title: "IPolicyRegistry.compositePolicyChildIds" -description: "Generated B20 reference for compositePolicyChildIds(uint64)." +description: "Returns the child-policy set of a composite policy." --- - - ## Signature ```solidity @@ -18,26 +16,39 @@ function compositePolicyChildIds(uint64 policyId) external view returns (uint64[ ## Description -Returns the child-policy set of the composite `policyId`. -Dev: Child-policies are listed in the order they were last written. -Dev: Returns an empty array for simple policies, built-in sentinels, unknown IDs, and -malformed IDs. Never reverts. -Dev: An empty return unambiguously means "not a composite". -Dev: The registry preserves the caller's ordering verbatim and neither sorts nor -de-duplicates. -Param: policyId Policy to query. -Return: Child policy IDs, or an empty array. +Returns the child-policy IDs stored for `policyId` in the order they were last written. The registry preserves caller ordering verbatim and does not sort or deduplicate entries. + +Returns an empty array for simple policies, built-in sentinels (`ALWAYS_ALLOW`, `ALWAYS_BLOCK`), unknown IDs, and malformed IDs. Never reverts. An empty return unambiguously means "not a composite." + +## Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `policyId` | `uint64` | Policy to query. | + +## Returns + +| Type | Description | +| ---- | ----------- | +| `uint64[] memory` | Child policy IDs, or an empty array if `policyId` is not a composite. | + +## Behavior notes + +- Evaluation is live. Each `isAuthorized` call on a composite reads the child IDs returned by this function at call time — not a snapshot from creation or the last `updateComposite`. +- Child IDs are packed four per 256-bit slot. The two-to-four-child constraint means each composite occupies one element slot in the `children` mapping. +- Duplicate child IDs are preserved; the registry does not deduplicate them. ## Access control -Read-only or ERC-20-standard access rules unless the NatSpec states otherwise. +Read-only. No caller restrictions. ## Policy interaction -This is part of the singleton PolicyRegistry surface used by B20 policy scopes. +This is part of the singleton `PolicyRegistry` surface used by B20 policy scopes. ## Example -```solidity -IPolicyRegistry(target).compositePolicyChildIds(arg0); +```solidity Title Query composite children +uint64[] memory children = IPolicyRegistry(target).compositePolicyChildIds(policyId); +// Returns empty array if policyId is not a composite. ``` diff --git a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/createCompositePolicy.mdx b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/createCompositePolicy.mdx index 9a6ed58a9..9e1779e44 100644 --- a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/createCompositePolicy.mdx +++ b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/createCompositePolicy.mdx @@ -1,14 +1,14 @@ --- title: "IPolicyRegistry.createCompositePolicy" -description: "Generated B20 reference for createCompositePolicy(address,uint8,uint64[])." +description: "Creates a new composite policy that combines existing simple policies under a UNION (OR) or INTERSECT (AND) logic gate." --- - - ## Signature ```solidity -function createCompositePolicy(address admin, PolicyType policyType, uint64[] calldata childPolicyIds) external returns (uint64 newPolicyId); +function createCompositePolicy(address admin, PolicyType policyType, uint64[] calldata childPolicyIds) + external + returns (uint64 newPolicyId); ``` | Field | Value | @@ -18,33 +18,78 @@ function createCompositePolicy(address admin, PolicyType policyType, uint64[] ca ## Description -Creates a new composite policy that combines existing simple policies under a logic -gate. -Dev: Child policies must be simple policies (ALLOWLIST or BLOCKLIST), never another composite. -The child-policy set is capped at 4. -Dev: Reverts with `IncompatiblePolicyType` when `policyType` is not UNION or INTERSECT. -Dev: Reverts with `ZeroAddress` when `admin` is `address(0)`. -Dev: Reverts with `ChildPoliciesOutsideOfRange` when `childPolicyIds.length` is not in -`[MIN_COMPOSITE_CHILD_POLICIES, MAX_COMPOSITE_CHILD_POLICIES]`. -Dev: Reverts with `PolicyNotFound` when any child policy does not exist. -Dev: Reverts with `InvalidChildPolicy` when any child policy is not a simple policy or a built-in policy. -Dev: Panics with arithmetic overflow (Panic 0x11) when the policy counter has reached its maximum value. -Param: admin Initial admin authorized to update child policies and transfer or renounce -administration. -Param: policyType UNION or INTERSECT. -Param: childPolicyIds Existing simple policy IDs to combine. -Return: newPolicyId The newly assigned composite policy ID. +Creates a composite policy that evaluates authorization by combining the results of two to four existing simple policies. `PolicyType` ABI-encodes as `uint8`. + +- `UNION` — authorizes an account if **any** child policy authorizes it (OR). +- `INTERSECT` — authorizes an account only if **every** child policy authorizes it (AND). + +Authorization is live: each call reads the current membership of every evaluated child. `UNION` short-circuits on the first authorizing child; `INTERSECT` short-circuits on the first non-authorizing child. Because composite children are rejected at write time, authorization never recurses beyond depth 1. + +A composite policy ID can be stored in a B20 policy scope exactly like a simple policy ID. B20 calls `isAuthorized` generically against the opaque `uint64` and requires no code changes. + + +Always call `policyExists(policyId)` before storing a composite ID. A well-formed but never-created `INTERSECT` ID has no children and returns `true`, behaving identically to `ALWAYS_ALLOW`. + + +## Parameters + +| Parameter | Type | Description | +|---|---|---| +| `admin` | `address` | Initial admin authorized to update child policies and transfer or renounce administration. | +| `policyType` | `PolicyType` | Must be `UNION` (2) or `INTERSECT` (3). | +| `childPolicyIds` | `uint64[]` | Existing simple (`ALLOWLIST` or `BLOCKLIST`) policy IDs to combine. Must contain between `MIN_COMPOSITE_CHILD_POLICIES` (2) and `MAX_COMPOSITE_CHILD_POLICIES` (4) entries. | + +## Returns + +| Name | Type | Description | +|---|---|---| +| `newPolicyId` | `uint64` | Newly assigned composite policy ID. | + +## Revert conditions + +Reverts are checked in this order: + +| Order | Error | Condition | +|---|---|---| +| 1 | `ZeroAddress` | `admin` is `address(0)`. | +| 2 | `IncompatiblePolicyType` | `policyType` is not `UNION` or `INTERSECT`. | +| 3 | `ChildPoliciesOutsideOfRange` | `childPolicyIds.length` is outside `[2, 4]`. | +| 4 | `PolicyNotFound` | Any child ID does not exist. | +| 5 | `InvalidChildPolicy` | Any child is itself a composite or a built-in sentinel (`ALWAYS_ALLOW` / `ALWAYS_BLOCK`). | + +Panics with `Panic(0x11)` (arithmetic overflow) if the policy counter has reached its maximum value. + +## Events emitted + +Emitted in this order on success: + +1. `PolicyCreated(policyId, creator, policyType)` +2. `PolicyAdminUpdated(policyId, address(0), admin)` +3. `CompositePolicyUpdated(policyId, creator, childPolicyIds)` ## Access control Permissionless creation, but state-changing registry calls require the feature to be active. -## Policy interaction +## Gas -This is part of the singleton PolicyRegistry surface used by B20 policy scopes. +Each child policy evaluated by `isAuthorized` requires a membership storage read. The worst-case cost occurs when all four children are evaluated. Place the child most likely to short-circuit first to minimize gas. ## Example -```solidity -IPolicyRegistry(target).createCompositePolicy(arg0, arg1, arg2); +```solidity Title="Create a UNION composite policy" +IPolicyRegistry registry = IPolicyRegistry(target); + +uint64[] memory children = new uint64[](2); +children[0] = sharedAllowlistId; +children[1] = tokenSpecificAllowlistId; + +uint64 compositePolicyId = registry.createCompositePolicy( + admin, + IPolicyRegistry.PolicyType.UNION, + children +); + +// Verify the policy exists before storing the ID +require(registry.policyExists(compositePolicyId), "policy not found"); ``` diff --git a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/createPolicy.mdx b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/createPolicy.mdx index 6d6caf119..d9d46ba3e 100644 --- a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/createPolicy.mdx +++ b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/createPolicy.mdx @@ -19,11 +19,23 @@ function createPolicy(address admin, PolicyType policyType) external returns (ui ## Description Creates a new simple 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. -Param: admin Initial admin authorized to modify membership and transfer or renounce administration. -Param: policyType BLOCKLIST or ALLOWLIST. -Return: newPolicyId The newly assigned policy ID. + +Reverts with `ZeroAddress` when `admin` is `address(0)`. + +Reverts with `IncompatiblePolicyType` when `policyType` is `UNION` or `INTERSECT`. Use `createCompositePolicy` to create composite policies. + +## Parameters + +| Parameter | Type | Description | +|---|---|---| +| `admin` | `address` | Initial admin authorized to modify membership and transfer or renounce administration. | +| `policyType` | `PolicyType` | Must be `BLOCKLIST` or `ALLOWLIST`. | + +## Returns + +| Name | Type | Description | +|---|---|---| +| `newPolicyId` | `uint64` | The newly assigned policy ID. | ## Access control diff --git a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/createPolicyWithAccounts.mdx b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/createPolicyWithAccounts.mdx index c7111ae28..864dadb2c 100644 --- a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/createPolicyWithAccounts.mdx +++ b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/createPolicyWithAccounts.mdx @@ -19,14 +19,27 @@ function createPolicyWithAccounts(address admin, PolicyType policyType, address[ ## Description Creates a new simple 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 policyType. -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. -Param: admin Initial admin authorized to modify membership and transfer or renounce administration. -Param: policyType BLOCKLIST or ALLOWLIST. -Param: accounts Initial member set. -Return: newPolicyId The newly assigned policy ID. + +**Revert conditions:** + +- `ZeroAddress` — `admin` is `address(0)`. Takes precedence over `BatchSizeTooLarge`. +- `IncompatiblePolicyType` — `policyType` is `UNION` or `INTERSECT`. Use `createCompositePolicy` for composite policies. +- `BatchSizeTooLarge` — `accounts.length` exceeds the registry limit. +- `Panic(0x11)` — arithmetic overflow when the policy counter has reached its maximum value. + +## Parameters + +| Parameter | Type | Description | +|---|---|---| +| `admin` | `address` | Initial admin authorized to modify membership and transfer or renounce administration. | +| `policyType` | `PolicyType` | `BLOCKLIST` or `ALLOWLIST` only. `UNION` and `INTERSECT` are rejected. | +| `accounts` | `address[]` | Initial member set. | + +## Returns + +| Name | Type | Description | +|---|---|---| +| `newPolicyId` | `uint64` | The newly assigned policy ID. | ## Access control @@ -36,6 +49,10 @@ Permissionless creation, but state-changing registry calls require the feature t This is part of the singleton PolicyRegistry surface used by B20 policy scopes. + +Passing `policyType` byte `2` (`UNION`) or `3` (`INTERSECT`) now reverts with `IncompatiblePolicyType`. Use `createCompositePolicy` to create composite policies. + + ## Example ```solidity diff --git a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/isAuthorized.mdx b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/isAuthorized.mdx index 347aee02d..fd3af7e7b 100644 --- a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/isAuthorized.mdx +++ b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/isAuthorized.mdx @@ -3,8 +3,6 @@ title: "IPolicyRegistry.isAuthorized" description: "Generated B20 reference for isAuthorized(uint64,address)." --- - - ## Signature ```solidity @@ -18,21 +16,64 @@ function isAuthorized(uint64 policyId, address account) external view returns (b ## Description -Returns whether `account` is authorized under `policyId`. Never reverts; unknown -or malformed IDs collapse to empty-member-set semantics (ALLOWLIST -> false, -BLOCKLIST -> true). -Dev: Callers that store policy IDs MUST validate `policyExists(policyId)` at write time. -Param: policyId Policy to query. -Param: account Account to check. -Return: Whether `account` is authorized. +Returns whether `account` is authorized under `policyId`. Never reverts. + +The evaluation logic depends on the policy type: + +```text +isAuthorized(policyId, account): + if policy is ALLOWLIST: + return account is in the policy + + if policy is BLOCKLIST: + return account is not in the policy + + if policy is UNION: + for each child policy: + if isAuthorized(child, account): + return true + return false + + if policy is INTERSECT: + for each child policy: + if not isAuthorized(child, account): + return false + return true +``` + +For composite policies (`UNION` and `INTERSECT`), evaluation is live — each call reads the current membership of every evaluated child. Short-circuiting applies: `UNION` stops at the first authorizing child, and `INTERSECT` stops at the first non-authorizing child. Child order can therefore affect gas cost but never the authorization result. + +Unknown or never-created policy IDs collapse to empty-member-set semantics: + +- `ALLOWLIST` → `false` +- `BLOCKLIST` → `true` +- `UNION` with no children → `false` +- `INTERSECT` with no children → `true` + + +A never-created `INTERSECT` ID returns `true` for every account, behaving like `ALWAYS_ALLOW`. Callers that store policy IDs MUST call `policyExists(policyId)` before storing them. + + +## Parameters + +| Name | Type | Description | +|---|---|---| +| `policyId` | `uint64` | Policy to query. | +| `account` | `address` | Account to check. | + +## Returns + +| Type | Description | +|---|---| +| `bool` | `true` if `account` is authorized under `policyId`, `false` otherwise. | ## Access control -Read-only or ERC-20-standard access rules unless the NatSpec states otherwise. +Read-only. No caller restrictions. ## Policy interaction -This is part of the singleton PolicyRegistry surface used by B20 policy scopes. +This is part of the singleton PolicyRegistry surface used by B20 policy scopes. A composite policy ID is passed to a B20 policy slot exactly like a simple policy ID; B20 stores policy IDs as an opaque `uint64` and calls `isAuthorized` generically. ## Example diff --git a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/policyExists.mdx b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/policyExists.mdx index e7b698e50..4fafe0848 100644 --- a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/policyExists.mdx +++ b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/policyExists.mdx @@ -30,6 +30,10 @@ Read-only or ERC-20-standard access rules unless the NatSpec states otherwise. This is part of the singleton PolicyRegistry surface used by B20 policy scopes. + +Consumers that store a composite policy ID in a B20 policy slot MUST call `policyExists(policyId)` before storing it. A well-formed but never-created `INTERSECT` ID returns `true` for every account, behaving like `ALWAYS_ALLOW`. + + ## Example ```solidity diff --git a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/renounceAdmin.mdx b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/renounceAdmin.mdx index 673ea0dbb..3c11e6601 100644 --- a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/renounceAdmin.mdx +++ b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/renounceAdmin.mdx @@ -20,9 +20,17 @@ function renounceAdmin(uint64 policyId) external; Permanently relinquishes administration of `policyId`. The member set is frozen and the policy can never be re-administered; `isAuthorized` queries continue to work. -Dev: Reverts with `PolicyNotFound` when `policyId` does not exist. -Dev: Reverts with `Unauthorized` when the caller is not the current admin. -Param: policyId Policy whose administration is being renounced. + +If `policyId` is a child of one or more composite policies, renouncing freezes future +membership changes on that child but does not remove it from any composite. Each composite +continues to evaluate the child's current authorization results unchanged. + +## Revert conditions + +| Condition | Error | +|---|---| +| `policyId` does not exist | `PolicyNotFound` | +| Caller is not the current admin | `Unauthorized` | ## Access control diff --git a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/updateComposite.mdx b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/updateComposite.mdx index 42b10a73f..23d0f673f 100644 --- a/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/updateComposite.mdx +++ b/docs/base-chain/specs/reference/b20/interfaces/IPolicyRegistry/updateComposite.mdx @@ -18,27 +18,32 @@ function updateComposite(uint64 policyId, uint64[] calldata childPolicyIds) exte ## Description -Replaces a composite policy's child-policy set in full with `childPolicyIds`. -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 `ChildPoliciesOutsideOfRange` when `childPolicyIds.length` is not in -`[MIN_COMPOSITE_CHILD_POLICIES, MAX_COMPOSITE_CHILD_POLICIES]`; there is no clear-the-list -path (the composite child-policy range, not the 64-account batch limit). -Dev: Reverts with `PolicyNotFound` when any child policy does not exist. -Dev: Reverts with `InvalidChildPolicy` when any child policy is itself a composite -(not a simple policy). -Param: policyId Composite policy to update. -Param: childPolicyIds Complete new set of existing simple policy IDs. +Replaces a composite policy's entire child set with `childPolicyIds`. No partial update or clear-the-list operation is supported. + +The new child set must contain between `MIN_COMPOSITE_CHILD_POLICIES` (`2`) and `MAX_COMPOSITE_CHILD_POLICIES` (`4`) existing simple policies, subject to the same validation rules as `createCompositePolicy`. Authorization evaluates each child's current membership live on every call — the update takes effect immediately. + +## Revert conditions + +The canonical revert order is: + +1. `PolicyNotFound` — `policyId` does not exist. +2. `IncompatiblePolicyType` — `policyId` is a simple policy, not a `UNION` or `INTERSECT` composite. +3. `Unauthorized` — caller is not the current admin. A composite whose admin has been renounced (`address(0)`) can never be updated. +4. `ChildPoliciesOutsideOfRange` — `childPolicyIds.length` is outside `[2, 4]`. +5. `PolicyNotFound` — a child policy ID does not exist. +6. `InvalidChildPolicy` — a child policy is itself a composite or a built-in sentinel (`ALWAYS_ALLOW` or `ALWAYS_BLOCK`). + +## Events emitted + +Emits only `CompositePolicyUpdated(policyId, updater, childPolicyIds)`. The admin does not change, so no `PolicyAdminUpdated` event is emitted. ## Access control -Callable by the policy admin for the target policy. +Callable by the current policy admin for the target composite policy. ## Policy interaction -This is part of the singleton PolicyRegistry surface used by B20 policy scopes. +This is part of the singleton PolicyRegistry surface used by B20 policy scopes. A composite policy ID is passed to a B20 policy slot as an opaque `uint64`, identical to a simple policy ID — no B20 contract changes are required. ## Example