Docs/add b20 spec - #1766
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
🟡 Heimdall Review Status
|
| | `TRANSFER_RECEIVER_POLICY` | `to` | `transfer`, `transferFrom`, and memo variants | | ||
| | `TRANSFER_EXECUTOR_POLICY` | `msg.sender` | `transferFrom` only when `msg.sender != from` | | ||
| | `MINT_RECEIVER_POLICY` | `to` | `mint` and `mintWithMemo` | | ||
| | `SEIZE_HOLDER_POLICY` | `from` | `seizeWithMemo`; the holder is seizable only when not authorized by this policy | |
There was a problem hiding this comment.
Can you double check the policies are all listed? Looks like we need to add SEIZE_RECEIVER_POLICY as well.
There was a problem hiding this comment.
Reviewed against base/base-std as the source of truth. Overall the generated reference is mostly faithful: precompile addresses, roles, variant bytes, decimals, PolicyType values, ~38 IB20 function pages, all IB20Asset pages, and every non-enum selector check out.
A few things worth fixing before merge (inline):
- Systematic selector/topic bug: enum params are hashed by type name instead of
uint8, so every enum-bearing selector/topic is wrong (createB20,getB20Address,B20Created,UnsupportedVersion,createPolicy/createPolicyWithAccounts/createCompositePolicy). Mechanical to regenerate, but breaks integrators. SEIZE_RECEIVER_POLICYmissing: the standard has 6 policy scopes; the docs show 5. This also makesseizeWithMemowrong (it gates the recipient).seizeWithMemodocuments a stale signature (returns (bool)) and stale policy semantics.- Smaller fixes:
finalizeUpdateAdminaccess-control,ChildPoliciesOutsideOfRangesignature,burnBlocked"no longer part of this interface" (still declared, just deprecated), and missing docs forMIN/MAX_COMPOSITE_CHILD_POLICIESand theUIMultiplierUpdatedevent.
There's no redirect for the exact old path /base-chain/specs/upgrades/beryl/b20, which will 404 post-merge.
|
|
||
| | Field | Value | | ||
| |---|---| | ||
| | Selector | `0xb263cb84` | |
There was a problem hiding this comment.
Selector is wrong: the enum type name was hashed instead of uint8. ABI selectors normalize enums to uint8, so this should be computed from createB20(uint8,bytes32,bytes,bytes[]) → 0x62975e6a (verified with cast), not createB20(B20Variant,...).
Also wrong: getB20Address (0x8c30260f), the B20Created topic0 (0xfd9bf2730513a1709722ff379a0844dfd8f997d600693c2bcc659e188bbdba0d), UnsupportedVersion (0xc0d8b4e0), and the IPolicyRegistry create* functions. The Canonical signature fields should also show uint8. These would break integrators computing calldata / log filters.
|
|
||
| | Field | Value | | ||
| |---|---| | ||
| | Selector | `0xe494a1f6` | |
There was a problem hiding this comment.
Same enum-selector bug as createB20: this is computed from createPolicy(address,PolicyType). The correct ABI signature is createPolicy(address,uint8) → 0xca5d55f6 (verified with cast). Related: createPolicyWithAccounts → 0xa2d3044f, createCompositePolicy → 0x6fdd1491. Please regenerate all enum-bearing selectors with uint8 normalization.
| ## Signature | ||
|
|
||
| ```solidity | ||
| function seizeWithMemo(address from, address to, uint256 amount, bytes32 memo) external returns (bool); |
There was a problem hiding this comment.
Two issues vs base-std IB20.sol:474:
- Return type: source is
... external;with no return value; thereturns (bool)here (and "always true on success" in the description) is incorrect.
function seizeWithMemo(address from, address to, uint256 amount, bytes32 memo) external;
- Policy semantics (stale): the page says
tois not policy-checked, but source now gates the recipient underSEIZE_RECEIVER_POLICYand revertsPolicyForbids(SEIZE_RECEIVER_POLICY, …)whentoisn't authorized (IB20.sol:460–467).
| | `TRANSFER_RECEIVER_POLICY` | `to` | `transfer`, `transferFrom`, and memo variants | | ||
| | `TRANSFER_EXECUTOR_POLICY` | `msg.sender` | `transferFrom` only when `msg.sender != from` | | ||
| | `MINT_RECEIVER_POLICY` | `to` | `mint` and `mintWithMemo` | | ||
| | `SEIZE_HOLDER_POLICY` | `from` | `seizeWithMemo`; the holder is seizable only when not authorized by this policy | |
There was a problem hiding this comment.
Missing the 6th policy scope. B20Constants.sol and IB20.sol:274 define SEIZE_RECEIVER_POLICY (gates the seize to recipient), so there are 6 scopes, not 5. Add a row here and update the bytes32[5] array below (~line 75) to include it — as written, the sample audit tool silently skips a real scope. The same omission appears in reference/constants-and-addresses.mdx, index.mdx, token-lifecycle.mdx, and implementation/policy-configuration-in-code.mdx, and there is no dedicated constant page for it.
|
|
||
| ## Access control | ||
|
|
||
| Callable by the policy admin for the target policy. |
There was a problem hiding this comment.
Access control is inverted. Per IPolicyRegistry.sol:161–168, finalizeUpdateAdmin must be called by the staged pending admin (reverts Unauthorized otherwise) — it "promotes the caller to active admin." The current policy admin cannot call it; that's the point of the two-step transfer.
| 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(2, 4)` when `childPolicyIds.length` is not in `[2, 4]`. |
There was a problem hiding this comment.
ChildPoliciesOutsideOfRange is parameterless in source (IPolicyRegistry.sol:62: error ChildPoliciesOutsideOfRange();) — writing it as (2, 4) misrepresents the signature. The bound is the named range [MIN_COMPOSITE_CHILD_POLICIES, MAX_COMPOSITE_CHILD_POLICIES]; the literal 2 is not defined in the interface. Same fix needed on updateComposite.
| | [`DEFAULT_ADMIN_ROLE`](/base-chain/specs/upgrades/beryl/b20/specification/reference/interfaces/IB20/DEFAULT_ADMIN_ROLE) | `0xa217fddf` | The default top-level admin role (`bytes32(0)`). Required to call `grantRole`, `revokeRole`, | | ||
| | [`MINT_ROLE`](/base-chain/specs/upgrades/beryl/b20/specification/reference/interfaces/IB20/MINT_ROLE) | `0xe9a9c850` | Required to call `mint` and `mintWithMemo`. | | ||
| | [`BURN_ROLE`](/base-chain/specs/upgrades/beryl/b20/specification/reference/interfaces/IB20/BURN_ROLE) | `0xb930908f` | Required to call `burn` and `burnWithMemo`. | | ||
| | [`BURN_BLOCKED_ROLE`](/base-chain/specs/upgrades/beryl/b20/specification/reference/interfaces/IB20/BURN_BLOCKED_ROLE) | `0x32ad9be8` | Required to call the deprecated `burnBlocked` (no longer part of this interface; retained for | |
There was a problem hiding this comment.
burnBlocked is still declared in IB20.sol:453 (marked DEPRECATED), so "no longer part of this interface" is inaccurate. Two related gaps: burnBlocked(address,uint256) has no reference page (the only non-constant IB20 function without one), and SEIZE_RECEIVER_POLICY() (IB20.sol:274) is missing from this function table.
| --- | ||
|
|
||
|
|
||
| ## Functions |
There was a problem hiding this comment.
Two functions declared in IPolicyRegistry.sol are missing from this index (and have no pages): MIN_COMPOSITE_CHILD_POLICIES() (:242) and MAX_COMPOSITE_CHILD_POLICIES() (:247). 16 functions in source, 14 documented — and these two are exactly what the composite-range prose references.
| | error | `LengthMismatch` | `0xab8b67c6` | `IB20Asset` | A batched function was called with parallel arrays of differing lengths. | | ||
| | event | `Memo` | `0x6989f5818dcfd11f8cd53b27c94cec33dae1589735f03e639cba54553a1825e8` | `IB20` | Emitted by `transferWithMemo`, `transferFromWithMemo`, `mintWithMemo`, and `burnWithMemo` | | ||
| | error | `MissingRequiredField` | `0x4a43ae87` | `IB20Factory` | A required string argument was the empty string. | | ||
| | event | `MultiplierUpdateCancelled` | `0xf8929975f3e67bbd1e5ec70d4cceaa7cce7ea0e811720f29c96cf1724de09397` | `IB20Asset` | A scheduled multiplier update was cancelled. Emitted by `cancelScheduledMultiplier`, | |
There was a problem hiding this comment.
UIMultiplierUpdated(uint256 oldMultiplier, uint256 newMultiplier, uint256 effectiveAtTimestamp) is missing from this index. It's declared in IScaledUIAmount.sol (inherited by IB20Asset) and is the primary event emitted on multiplier changes — MultiplierUpdateCancelled is listed, but the update event itself is not.
What changed? Why?