Invariants
Policy Registry
isAuthorizednever reverts for any combination ofpolicyIdandaccount.- A non-existent
BLOCKLISTpolicy authorizes everyone. A non-existentALLOWLISTpolicy denies everyone. - After
renounceAdmin(policyId), all membership-mutating calls on that policy revert permanently. ALWAYS_ALLOW(ID0) authorizes every account.ALWAYS_BLOCKdenies every account. Neither can be created, modified, or renounced.- Policy IDs are globally unique and monotonically increasing within each
PolicyTypeprefix.
Roles
- The last
DEFAULT_ADMIN_ROLEholder cannot be removed viarenounceRoleorrevokeRole— onlyrenounceLastAdmin(). - After
renounceLastAdmin(), no address can ever holdDEFAULT_ADMIN_ROLEagain. - Roles granted before admin renunciation continue to function.
- Custom roles have no built-in effect on any B20 operation.
Transfer Policies
approveis never policy-gated.TRANSFER_EXECUTOR_POLICYis checked only ontransferFrom, never ontransfer.MINT_RECEIVER_POLICYis always enforced, even during factoryinitCalls.- All three transfer-side scopes are bypassed during
initCalls. - Every scope defaults to
ALWAYS_ALLOWat token creation.
Supply
totalSupplycan never exceed the supply cap.- The supply cap can never be set below the current
totalSupply. - Burns reduce
totalSupplyand create headroom under the cap.
Pause
- Each
PausableFeatureis independent — pausing one does not affect the others. approveandpermitare never affected by any pause state.- Pause state is never bypassed during factory
initCalls.
Memos
- The
Memoevent is always emitted at exactlylogIndex + 1relative to its parentTransferevent. - Memo methods are functionally identical to their non-memo counterparts in all respects except event emission.
Permit
permitonly accepts ECDSA signatures. ERC-1271 contract signatures always fail.- Each successful
permitincrements the owner’s nonce by exactly 1. - Permits signed before
updateNamefail after the name change.
Variants
- Asset decimals are set at creation and immutable. Valid range is 6–18.
- Stablecoin decimals are always
6. OPERATOR_ROLEexists only on Asset tokens.- Announcement IDs are unique across a token’s lifetime.
- The currency code on a Stablecoin is immutable and contains only
A–Zcharacters. - Multiplier updates affect all holders simultaneously.
batchMintenforcesMINT_RECEIVER_POLICYfor each recipient individually.
Factory
- B20 addresses are deterministic: same inputs always produce the same address.
- The variant byte at address position 10 always matches the deployed variant.
- Each
(deployer, variant, salt)tuple produces exactly one address. initCallsexecute in array order. A revert in any initCall reverts the entire deployment.