Skip to content

Commit 49089fa

Browse files
fix: correct B20 native token standard docs against base-std (#1629)
Verified docs/base-chain/specs/upgrades/beryl/b20.mdx against the base/base-std interfaces (origin/main) and the Rust precompile implementation, and corrected several inaccuracies: - createB20 signature arg order is (variant, salt, params, initCalls) - Supply cap sentinel/max is type(uint128).max, not type(uint256).max (and updateSupplyCap also reverts above that bound) - PolicyRegistry admin fns are stageUpdateAdmin / finalizeUpdateAdmin / renounceAdmin (not transfer/accept/renouncePolicyAdmin) - createPolicy arg order is (admin, policyType) - Membership setters are updateBlocklist / updateAllowlist (bool-based, type-specific), not addToPolicy / removeFromPolicy - Clarify OPERATOR_ROLE only gates the multiplier and announcements; batch mint uses MINT_ROLE and extra metadata uses METADATA_ROLE - Clarify initCalls bootstrap bypass covers role gates and transfer-side policy gates; note createB20 activation gating - Minor: TRANSFER_EXECUTOR_POLICY only checked when msg.sender != from; add language to the address-derivation code block Generated with Claude Code Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2c0f278 commit 49089fa

1 file changed

Lines changed: 21 additions & 16 deletions

File tree

  • docs/base-chain/specs/upgrades/beryl

docs/base-chain/specs/upgrades/beryl/b20.mdx

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ B20 role-based access control extends OpenZeppelin `AccessControl` with a fixed
3434
| `UNPAUSE_ROLE` | `unpause` |
3535
| `METADATA_ROLE` | `updateName`, `updateSymbol`, `updateContractURI` |
3636

37-
User-defined roles are supported via `setRoleAdmin` and `grantRole`. They carry no built-in effect - B20 only enforces gates against the seven roles above.
37+
User-defined roles are supported via `setRoleAdmin` and `grantRole`. They carry no built-in effect - B20 only enforces gates against the seven base-surface roles above. The Asset variant adds an eighth role, `OPERATOR_ROLE` (see [Variants](#variants)).
3838

3939
### Admin Renunciation
4040

@@ -82,17 +82,20 @@ Consumers that write a policy ID (e.g. `updatePolicy`) MUST validate `policyExis
8282

8383
### Admin Model
8484

85-
Each policy has one admin. Admin transfers are two-step: the current admin calls `transferPolicyAdmin(policyId, newAdmin)`, then the pending admin calls `acceptPolicyAdmin(policyId)`. `renouncePolicyAdmin(policyId)` permanently freezes the policy - membership can never be changed again.
85+
Each policy has one admin. Admin transfers are two-step: the current admin calls `stageUpdateAdmin(policyId, newAdmin)`, then the pending admin calls `finalizeUpdateAdmin(policyId)`. `renounceAdmin(policyId)` permanently freezes the policy - membership can never be changed again.
8686

8787
### Creating and Managing Policies
8888

8989
```solidity
90-
// Create a policy
91-
uint64 policyId = policyRegistry.createPolicy(PolicyType.BLOCKLIST, adminAddress);
92-
93-
// Update membership (batched)
94-
policyRegistry.addToPolicy(policyId, accounts);
95-
policyRegistry.removeFromPolicy(policyId, accounts);
90+
// Create a policy (admin first, then type)
91+
uint64 policyId = policyRegistry.createPolicy(adminAddress, PolicyType.BLOCKLIST);
92+
// Or seed the initial member set in one call:
93+
// uint64 policyId = policyRegistry.createPolicyWithAccounts(adminAddress, PolicyType.BLOCKLIST, accounts);
94+
95+
// Update membership (batched). The setter is type-specific; the bool sets membership state.
96+
policyRegistry.updateBlocklist(policyId, true, accounts); // block these accounts
97+
policyRegistry.updateBlocklist(policyId, false, accounts); // unblock these accounts
98+
// For ALLOWLIST policies: policyRegistry.updateAllowlist(policyId, allowed, accounts)
9699
```
97100

98101
### Read Interface
@@ -112,7 +115,7 @@ B20 declares a fixed set of policy scopes. Each scope stores a `uint64` policy I
112115
|-------|-------|
113116
| `TRANSFER_SENDER_POLICY` | The `from` of `transfer` / `transferFrom` |
114117
| `TRANSFER_RECEIVER_POLICY` | The `to` of `transfer` / `transferFrom` |
115-
| `TRANSFER_EXECUTOR_POLICY` | The `msg.sender` of `transferFrom` (not checked on `transfer`) |
118+
| `TRANSFER_EXECUTOR_POLICY` | The `msg.sender` of `transferFrom`, when distinct from `from` (not checked on `transfer`) |
116119
| `MINT_RECEIVER_POLICY` | The `to` of `mint` |
117120

118121
`approve` is not policy-gated - only actual balance movement via `transfer` / `transferFrom` is checked.
@@ -136,7 +139,7 @@ Two burn paths exist:
136139

137140
## Supply Cap
138141

139-
The supply cap is optional. The sentinel `type(uint256).max` indicates no cap (the default at creation). `updateSupplyCap(newCap)` is admin-gated and emits `SupplyCapUpdated`. Lowering below the current `totalSupply` reverts with `InvalidSupplyCap`.
142+
The supply cap is optional. The sentinel `type(uint128).max` indicates no cap (the default at creation); it is also the maximum permitted cap, so `totalSupply` can never exceed it. `updateSupplyCap(newCap)` is admin-gated and emits `SupplyCapUpdated`. It reverts with `InvalidSupplyCap` if `newCap` is below the current `totalSupply` or above `type(uint128).max`.
140143

141144
## Memos
142145

@@ -165,28 +168,30 @@ B20 implements ERC-2612 (signed approvals) using an EIP-712 domain shaped as `(n
165168

166169
## Factory
167170

168-
All B20 tokens are created through the singleton `IB20Factory` precompile via `createB20(variant, params, initCalls, salt)`.
171+
All B20 tokens are created through the singleton `IB20Factory` precompile via `createB20(variant, salt, params, initCalls)`.
169172

170173
| Parameter | Description |
171174
|-----------|-------------|
172175
| `variant` | `ASSET` or `STABLECOIN` |
173-
| `params` | ABI-encoded, variant-specific creation struct (versioned by leading byte) |
174-
| `initCalls` | Optional array of ABI-encoded calls dispatched post-creation with admin privilege bypass |
175176
| `salt` | Caller-chosen entropy for address derivation |
177+
| `params` | ABI-encoded, variant-specific creation struct (versioned by leading byte) |
178+
| `initCalls` | Optional array of ABI-encoded calls dispatched post-creation; factory-originated calls bypass role gates and transfer-side policy gates during this window |
179+
180+
`createB20` reverts with `IActivationRegistry.FeatureNotActivated` if the requested variant's feature is not yet activated on the chain.
176181

177182
### Address Derivation
178183

179184
B20 addresses are deterministic and encode the variant directly:
180185

181-
```
186+
```text
182187
[10-byte B20 prefix][1-byte variant][9-byte keccak256(deployer, salt)]
183188
```
184189

185190
The variant is recoverable from the address alone without an RPC call. Helper functions `getB20Address(variant, deployer, salt)`, `isB20(addr)`, and `isB20Initialized(addr)` are available on the factory.
186191

187192
### initCalls Semantics
188193

189-
`initCalls` are dispatched after token creation with admin privilege bypass, allowing configuration (e.g. setting policies, granting roles) in the same transaction as deployment. The bypass is partial:
194+
`initCalls` are dispatched after token creation. During this bootstrap window, factory-originated calls bypass the token's role gates and its transfer-side policy gates (`TRANSFER_SENDER_POLICY`, `TRANSFER_RECEIVER_POLICY`, `TRANSFER_EXECUTOR_POLICY`), allowing admin-gated configuration (e.g. setting policies, granting roles) and bootstrap transfers in the same transaction as deployment. The bypass is deliberately not total:
190195

191196
- `MINT_RECEIVER_POLICY` is always enforced even during `initCalls`.
192197
- Pause state is never bypassed.
@@ -198,7 +203,7 @@ The variant is recoverable from the address alone without an RPC call. Helper fu
198203

199204
The general-purpose variant for assets of all kinds. Decimals are configurable between 6 and 18 at deployment time and are immutable after creation.
200205

201-
In addition to the base B20 surface, Asset tokens add an `OPERATOR_ROLE` that gates the following capabilities:
206+
In addition to the base B20 surface, Asset tokens add several capabilities. A new `OPERATOR_ROLE` gates the multiplier and announcements; batch mint and extra metadata reuse the existing `MINT_ROLE` and `METADATA_ROLE` respectively.
202207

203208
#### Multiplier
204209

0 commit comments

Comments
 (0)