Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ test/regression/ # interface renames/removals guard (B20Renames.t.sol,
test/lib/ # BaseTest.sol, B20Test.sol, mocks/ (reference behavior + storage layout)
script/smoke/ # Python 3.13 live-node smoketest (web3.py); see script/smoke/README.md
script/fork/ # node-based live-precompile runner (anvil + patched forge); see script/fork/README.md
docs/ # specs: docs/B20/README.md, docs/PolicyRegistry/, docs/ActivationRegistry/
docs/ # feature documentation index
docs/b20/ # B20 overview, architecture, concepts, guides, and reference
```

Deeper reading: `LIVE_PRECOMPILE_TESTING.md` (cross-validation architecture), `docs/B20/README.md` (B20 spec).
Deeper reading: `LIVE_PRECOMPILE_TESTING.md` (cross-validation architecture), `docs/b20/README.md` (B20 documentation).

## Code style

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ A collection of Solidity interfaces, libraries, and mock implementations for Bas
## Products

- [**ActivationRegistry**](src/interfaces/IActivationRegistry.sol) — Feature flags controlled by Base team to activate/deactivate features.
- [**PolicyRegistry**](docs/concepts/policies.md) — Membership sets controlled by custom admins, initially providing allow and block lists for B20 token operations.
- [**B20**](docs/overview.md) — Standard ERC-20 implementation with extensions for roles, policies, memos, pausing, ERC-2612 permits, and a variant system.
- [**PolicyRegistry**](docs/b20/concepts/policies.md) — Membership sets controlled by custom admins, initially providing allow and block lists for B20 token operations.
- [**B20**](docs/b20/overview.md) — Standard ERC-20 implementation with extensions for roles, policies, memos, pausing, ERC-2612 permits, and a variant system.
- [**BaseTime**](src/interfaces/IBaseTime.sol) — Read-only interface for the predeploy exposing the current block timestamp in milliseconds.

## Documentation

See [`docs/`](docs/README.md) for the full documentation map: overview, architecture, audience guides (integrator/indexer), concepts, and reference.
See [`docs/`](docs/README.md) for the feature documentation index. The [`B20 documentation`](docs/b20/README.md) covers overview, architecture, guides, concepts, and reference.

## Changelog

Expand Down
2 changes: 1 addition & 1 deletion changelog/03_Denim_B20_transfer_executor_enforcement.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Both gaps let a non-allowlisted holder move tokens by choosing a different entry

### Policy Registry and transfer-side scopes

The Policy Registry is a singleton precompile that B20 tokens call for pre-operation compliance checks on an address. A B20 token stores a `uint64` policy ID per scope and calls `isAuthorized(policyId, account)` before a gated operation. `isAuthorized` never reverts; a malformed or unknown ID returns `false` (deny). See [Policies](../docs/concepts/policies.md) for the full model.
The Policy Registry is a singleton precompile that B20 tokens call for pre-operation compliance checks on an address. A B20 token stores a `uint64` policy ID per scope and calls `isAuthorized(policyId, account)` before a gated operation. `isAuthorized` never reverts; a malformed or unknown ID returns `false` (deny). See [Policies](../docs/b20/concepts/policies.md) for the full model.

B20 has three transfer-side scopes, all checked inside the shared `_transfer` function that backs `transfer`, `transferFrom`, and their memo variants:

Expand Down
20 changes: 4 additions & 16 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
## Understanding B20
# Documentation

New to B20?
Documentation is organized by feature. Each feature has its own directory under `docs/`.

1. [B20 Overview](overview.md)
2. [Integration architecture](integration-architecture.md)
3. [How B20 Works](architecture.md)
- [B20](b20/README.md) — overview, architecture, concepts, guides, and reference for B20 tokens and their supporting precompiles.

Building something?

- [Seize a holder's B20 balance](guides/seizeing-assets.md)
- [Schedule a stock split](guides/scheduling-stock-splits.md)
- [Announce a corporate action](guides/announcing-corporate-actions.md)
- [Restrict who can initiate transfers](guides/restricting-transfer-initiators.md)

Looking for exact technical details?

- [Concepts](concepts/) — the mental model: assets, policies, roles, execution, versioning
- [Reference](reference/) — interfaces, events, errors, constants
Add future feature documentation alongside `b20/` (for example, `feature-2/` and `feature-3/`).
19 changes: 19 additions & 0 deletions docs/b20/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
## Understanding B20

New to B20?

1. [B20 Overview](overview.md)
2. [Integration architecture](integration-architecture.md)
3. [How B20 Works](architecture.md)

Building something?

- [Seize a holder's B20 balance](guides/seizeing-assets.md)
- [Schedule a stock split](guides/scheduling-stock-splits.md)
- [Announce a corporate action](guides/announcing-corporate-actions.md)
- [Restrict who can initiate transfers](guides/restricting-transfer-initiators.md)

Looking for exact technical details?

- [Concepts](concepts/) — the mental model: assets, policies, roles, execution, versioning
- [Reference](reference/) — interfaces, events, errors, constants
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ After creation, byte `[10]` is what selects the variant. How you recognize a liv

## 4. Asset

Asset is the general-purpose variant. That includes real-world assets (RWAs). It is not an RWA-only type. The type-specific surface is [`IB20Asset`](../../src/interfaces/IB20Asset.sol), which extends `IB20` at the same address.
Asset is the general-purpose variant. That includes real-world assets (RWAs). It is not an RWA-only type. The type-specific surface is [`IB20Asset`](../../../src/interfaces/IB20Asset.sol), which extends `IB20` at the same address.

Creation sets immutable `decimals` in `[6, 18]`. Values outside that range revert `InvalidDecimals`. Asset has no `currency()`.

Expand Down Expand Up @@ -85,7 +85,7 @@ sequenceDiagram
Factory-->>Issuer: token address
```

After return, address byte `[10]` is `0x01`. `decimals()` is `6`. `currency()` is `"USD"`. The type-specific surface is [`IB20Stablecoin`](../../src/interfaces/IB20Stablecoin.sol). Calling `announce` on that address does not run Asset logic.
After return, address byte `[10]` is `0x01`. `decimals()` is `6`. `currency()` is `"USD"`. The type-specific surface is [`IB20Stablecoin`](../../../src/interfaces/IB20Stablecoin.sol). Calling `announce` on that address does not run Asset logic.

### 6.2 Creating an Asset

Expand All @@ -107,7 +107,7 @@ sequenceDiagram
Factory-->>Issuer: token address
```

After return, address byte `[10]` is `0x00`. `decimals()` is `18`. [`IB20Asset`](../../src/interfaces/IB20Asset.sol) `announce` and `updateUIMultiplier` are live. There is no `currency()`.
After return, address byte `[10]` is `0x00`. `decimals()` is `18`. [`IB20Asset`](../../../src/interfaces/IB20Asset.sol) `announce` and `updateUIMultiplier` are live. There is no `currency()`.

### 6.3 A later call

Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ The Policy Registry holds the lists. A token stores a policy ID, not the members

The Activation Registry is a Base-operated switch. Issuers and apps read it. When a feature is inactive, writes that require it revert with `FeatureNotActivated`. Reads stay available. Turning a variant off stops new `createB20` calls for that variant. Tokens that already exist keep running.

Constants for these addresses live in [`StdPrecompiles`](../src/StdPrecompiles.sol).
Constants for these addresses live in [`StdPrecompiles`](../../src/StdPrecompiles.sol).

## 3. Identity and Code

Expand All @@ -49,7 +49,7 @@ The address is self-describing:
- Byte `[10]` is the variant. Asset is `0x00`. Stablecoin is `0x01`.
- The remaining bytes are derived from `keccak256(sender, salt)`.

Byte `[10]` is the type, and it does not change after `createB20` returns. An Asset address exposes [`IB20`](../src/interfaces/IB20.sol) and [`IB20Asset`](../src/interfaces/IB20Asset.sol). A Stablecoin address exposes `IB20` and [`IB20Stablecoin`](../src/interfaces/IB20Stablecoin.sol). A selector that belongs to the other variant does not run on that address.
Byte `[10]` is the type, and it does not change after `createB20` returns. An Asset address exposes [`IB20`](../../src/interfaces/IB20.sol) and [`IB20Asset`](../../src/interfaces/IB20Asset.sol). A Stablecoin address exposes `IB20` and [`IB20Stablecoin`](../../src/interfaces/IB20Stablecoin.sol). A selector that belongs to the other variant does not run on that address.

`isB20(address)` reports the `0xB2` prefix only. It can return true for an address the Factory has not created, and it never reverts. `isB20Initialized(address)` is the liveness check. It flips once, when the creating `createB20` returns, and it never reverts. During `initCalls` in that same call, it is still false.

Expand Down
4 changes: 2 additions & 2 deletions docs/overview.md → docs/b20/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Roles let an issuer assign each privileged operation to a specific account. An a

B20 implements this with [OpenZeppelin AccessControl](https://docs.openzeppelin.com/contracts/5.x/access-control) on the token. Roles are not a separate registry. One `DEFAULT_ADMIN_ROLE` holder grants and revokes the operating roles. A privileged call checks the role first, then the matching pause vector. Holder `transfer` skips the role check; it still hits the `TRANSFER` pause vector and policy.

The full role list and what each role gates is in [Roles](./concepts/roles.md). A role-gated call looks like this:
The full role list and what each role gates is in [Roles](./concepts/roles-and-pause.md). A role-gated call looks like this:

```mermaid
sequenceDiagram
Expand Down Expand Up @@ -197,4 +197,4 @@ If you are integrating B20:
For exact interfaces and protocol definitions:

→ [Reference](./reference/)
→ [Specifications](./specs/)
→ [Interfaces](./reference/interfaces.md)
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
# Constants

*Role identifiers, policy-type identifiers, precompile and predeploy addresses, and other fixed constants. See [`B20Constants`](../../src/lib/B20Constants.sol), [`StdPrecompiles`](../../src/StdPrecompiles.sol), and [`StdPredeploys`](../../src/StdPredeploys.sol).*
*Role identifiers, policy-type identifiers, precompile and predeploy addresses, and other fixed constants. See [`B20Constants`](../../../src/lib/B20Constants.sol), [`StdPrecompiles`](../../../src/StdPrecompiles.sol), and [`StdPredeploys`](../../../src/StdPredeploys.sol).*

## Precompile addresses

*Fixed addresses of Base's singleton precompiles. See [`StdPrecompiles`](../../src/StdPrecompiles.sol).*
*Fixed addresses of Base's singleton precompiles. See [`StdPrecompiles`](../../../src/StdPrecompiles.sol).*

| Name | Value | Purpose |
|---|---|---|
| `B20_FACTORY_ADDRESS` | `0xB20f000000000000000000000000000000000000` | Deploys and looks up B-20 tokens; every asset and stablecoin instance is created through the [`IB20Factory`](../../src/interfaces/IB20Factory.sol) at this address. |
| `B20_FACTORY_ADDRESS` | `0xB20f000000000000000000000000000000000000` | Deploys and looks up B-20 tokens; every asset and stablecoin instance is created through the [`IB20Factory`](../../../src/interfaces/IB20Factory.sol) at this address. |
| `POLICY_REGISTRY_ADDRESS` | `0x8453000000000000000000000000000000000002` | Stores allowlist/blocklist/composite policies and answers `isAuthorized` checks consulted by every policy scope (see [Policies](../concepts/policies.md)). |
| `ACTIVATION_REGISTRY_ADDRESS` | `0x8453000000000000000000000000000000000001` | Gates whether a B-20 variant or feature is live on a given chain; checked by the factory before it will create that variant. |

## Predeploy addresses

*Fixed addresses of Base's predeploys (EVM bytecode at `0x4200…` addresses). See [`StdPredeploys`](../../src/StdPredeploys.sol).*
*Fixed addresses of Base's predeploys (EVM bytecode at `0x4200…` addresses). See [`StdPredeploys`](../../../src/StdPredeploys.sol).*

| Name | Value | Purpose |
|---|---|---|
| `BASE_TIME_ADDRESS` | `0x4200000000000000000000000000000000000030` | Exposes the current block timestamp in milliseconds through [`IBaseTime`](../../src/interfaces/IBaseTime.sol) (`timestampMs`, `timestampMillisPart`); installed at Denim. |
| `BASE_TIME_ADDRESS` | `0x4200000000000000000000000000000000000030` | Exposes the current block timestamp in milliseconds through [`IBaseTime`](../../../src/interfaces/IBaseTime.sol) (`timestampMs`, `timestampMillisPart`); installed at Denim. |

## Roles

*Role identifiers checked via `hasRole`. See [`B20Constants`](../../src/lib/B20Constants.sol) and [`IB20`](../../src/interfaces/IB20.sol). Hex values are `keccak256` of the role name, verified with `cast keccak "<NAME>"` and cross-checked in `chisel`.*
*Role identifiers checked via `hasRole`. See [`B20Constants`](../../../src/lib/B20Constants.sol) and [`IB20`](../../../src/interfaces/IB20.sol). Hex values are `keccak256` of the role name, verified with `cast keccak "<NAME>"` and cross-checked in `chisel`.*

| Name | Value | Purpose |
|---|---|---|
Expand All @@ -38,7 +38,7 @@

## Policy types

*Policy scopes consulted by the PolicyRegistry. See [`B20Constants`](../../src/lib/B20Constants.sol) and [Policies](../concepts/policies.md). Hex values are `keccak256` of the policy name, verified with `cast keccak "<NAME>"` and cross-checked in `chisel`.*
*Policy scopes consulted by the PolicyRegistry. See [`B20Constants`](../../../src/lib/B20Constants.sol) and [Policies](../concepts/policies.md). Hex values are `keccak256` of the policy name, verified with `cast keccak "<NAME>"` and cross-checked in `chisel`.*

| Name | Value | Purpose |
|---|---|---|
Expand All @@ -51,7 +51,7 @@

## Feature and validation bounds

*Bitmasks and inclusive bounds used for pause features and B20Asset creation validation. See [`B20Constants`](../../src/lib/B20Constants.sol).*
*Bitmasks and inclusive bounds used for pause features and B20Asset creation validation. See [`B20Constants`](../../../src/lib/B20Constants.sol).*

| Name | Value | Purpose |
|---|---|---|
Expand All @@ -62,7 +62,7 @@

## Asset-variant precision constants

*Fixed-point constants used by the multiplier/rebasing surface. See [`IB20Asset`](../../src/interfaces/IB20Asset.sol).*
*Fixed-point constants used by the multiplier/rebasing surface. See [`IB20Asset`](../../../src/interfaces/IB20Asset.sol).*

| Name | Value | Purpose |
|---|---|---|
Expand Down
14 changes: 7 additions & 7 deletions docs/reference/errors.md → docs/b20/reference/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

*Exhaustive list of custom errors, selectors, and the conditions that trigger them. Selectors are the 4-byte `keccak256` hash of the error signature — computed with `cast sig "ErrorName(types...)"`. Enum parameters encode as their underlying `uint8`.*

*Note: several error names are reused across files with different parameters (or none), which changes the selector. `PolicyNotFound()` ([`IPolicyRegistry`](../../src/interfaces/IPolicyRegistry.sol)) and `PolicyNotFound(uint64)` ([`IB20`](../../src/interfaces/IB20.sol)) are unrelated errors with different selectors, as are `Unauthorized()` (`IB20` / `IPolicyRegistry`) and `Unauthorized(address)` ([`IActivationRegistry`](../../src/interfaces/IActivationRegistry.sol)). Conversely, `LengthMismatch(uint256,uint256)` shares one selector across [`IB20Asset`](../../src/interfaces/IB20Asset.sol) and [`B20FactoryLib`](../../src/lib/B20FactoryLib.sol) — they're independently declared but identical in signature.*
*Note: several error names are reused across files with different parameters (or none), which changes the selector. `PolicyNotFound()` ([`IPolicyRegistry`](../../../src/interfaces/IPolicyRegistry.sol)) and `PolicyNotFound(uint64)` ([`IB20`](../../../src/interfaces/IB20.sol)) are unrelated errors with different selectors, as are `Unauthorized()` (`IB20` / `IPolicyRegistry`) and `Unauthorized(address)` ([`IActivationRegistry`](../../../src/interfaces/IActivationRegistry.sol)). Conversely, `LengthMismatch(uint256,uint256)` shares one selector across [`IB20Asset`](../../../src/interfaces/IB20Asset.sol) and [`B20FactoryLib`](../../../src/lib/B20FactoryLib.sol) — they're independently declared but identical in signature.*

## [`IB20`](../../src/interfaces/IB20.sol)
## [`IB20`](../../../src/interfaces/IB20.sol)

| Error | Selector | Thrown when |
|---|---|---|
Expand Down Expand Up @@ -33,7 +33,7 @@
| `NotSoleAdmin()` | `0x2a98e73b` | `renounceLastAdmin()` was called when other accounts also hold `DEFAULT_ADMIN_ROLE`. |
| `AccessControlBadConfirmation()` | `0x6697b232` | The `callerConfirmation` argument to `renounceRole` was not `msg.sender`. |

## [`IB20Asset`](../../src/interfaces/IB20Asset.sol)
## [`IB20Asset`](../../../src/interfaces/IB20Asset.sol)

| Error | Selector | Thrown when |
|---|---|---|
Expand All @@ -50,7 +50,7 @@
| `InternalCallMalformed(bytes call)` | `0x4e2f143e` | An inner call dispatched by `announce` was shorter than four bytes. |
| `InternalCallFailed(bytes call)` | `0xb288a127` | An inner call dispatched by `announce` reverted with an ordinary revert (reason not bubbled). |

## [`IB20Factory`](../../src/interfaces/IB20Factory.sol)
## [`IB20Factory`](../../../src/interfaces/IB20Factory.sol)

| Error | Selector | Thrown when |
|---|---|---|
Expand All @@ -63,7 +63,7 @@
| `InvalidDecimals(uint8 decimals)` | `0xca950391` | The asset `decimals` was outside `[B20Constants.MIN_ASSET_DECIMALS, B20Constants.MAX_ASSET_DECIMALS]`. |
| `InitCallFailed(uint256 index)` | `0x4eae0860` | One of the `initCalls` reverted with no bubbled reason. |

## [`IPolicyRegistry`](../../src/interfaces/IPolicyRegistry.sol)
## [`IPolicyRegistry`](../../../src/interfaces/IPolicyRegistry.sol)

| Error | Selector | Thrown when |
|---|---|---|
Expand All @@ -77,7 +77,7 @@
| `ChildPoliciesOutsideOfRange()` | `0x697ec868` | A composite policy was created or updated with a child-policy count outside `[MIN_COMPOSITE_CHILD_POLICIES, MAX_COMPOSITE_CHILD_POLICIES]`. |
| `InvalidChildPolicy(uint64 childPolicyId)` | `0x46508ef6` | A child policy is not an existing simple (ALLOWLIST/BLOCKLIST) policy. |

## [`IActivationRegistry`](../../src/interfaces/IActivationRegistry.sol)
## [`IActivationRegistry`](../../../src/interfaces/IActivationRegistry.sol)

| Error | Selector | Thrown when |
|---|---|---|
Expand All @@ -87,7 +87,7 @@
| `DelegateCallNotAllowed()` | `0x0d89438e` | The precompile was invoked via `DELEGATECALL` or `CALLCODE`. |
| `StaticCallNotAllowed()` | `0xbeaba5b7` | A state-mutating entry point was invoked from a `STATICCALL` frame. |

## [`B20FactoryLib`](../../src/lib/B20FactoryLib.sol)
## [`B20FactoryLib`](../../../src/lib/B20FactoryLib.sol)

| Error | Selector | Thrown when |
|---|---|---|
Expand Down
Loading
Loading