Skip to main content
These invariants and conformance test cases are the normative behavioral guarantees of the B20 precompiles. They complement the B20 specification and the errors & events index.

Invariants

Policy Registry

  1. isAuthorized never reverts for any combination of policyId and account.
  2. A non-existent BLOCKLIST policy authorizes everyone. A non-existent ALLOWLIST policy denies everyone.
  3. After renounceAdmin(policyId), all membership-mutating calls on that policy revert permanently.
  4. ALWAYS_ALLOW (ID 0) authorizes every account. ALWAYS_BLOCK denies every account. Neither can be created, modified, or renounced.
  5. Policy IDs are globally unique and monotonically increasing within each PolicyType prefix.

Roles

  1. The last DEFAULT_ADMIN_ROLE holder cannot be removed via renounceRole or revokeRole — only renounceLastAdmin().
  2. After renounceLastAdmin(), no address can ever hold DEFAULT_ADMIN_ROLE again.
  3. Roles granted before admin renunciation continue to function.
  4. Custom roles have no built-in effect on any B20 operation.

Transfer Policies

  1. approve is never policy-gated.
  2. TRANSFER_EXECUTOR_POLICY is checked only on transferFrom, never on transfer.
  3. MINT_RECEIVER_POLICY is always enforced, even during factory initCalls.
  4. All three transfer-side scopes are bypassed during initCalls.
  5. Every scope defaults to ALWAYS_ALLOW at token creation.

Supply

  1. totalSupply can never exceed the supply cap.
  2. The supply cap can never be set below the current totalSupply.
  3. Burns reduce totalSupply and create headroom under the cap.

Pause

  1. Each PausableFeature is independent — pausing one does not affect the others.
  2. approve and permit are never affected by any pause state.
  3. Pause state is never bypassed during factory initCalls.

Memos

  1. The Memo event is always emitted at exactly logIndex + 1 relative to its parent Transfer event.
  2. Memo methods are functionally identical to their non-memo counterparts in all respects except event emission.

Permit

  1. permit only accepts ECDSA signatures. ERC-1271 contract signatures always fail.
  2. Each successful permit increments the owner’s nonce by exactly 1.
  3. Permits signed before updateName fail after the name change.

Variants

  1. Asset decimals are set at creation and immutable. Valid range is 6–18.
  2. Stablecoin decimals are always 6.
  3. OPERATOR_ROLE exists only on Asset tokens.
  4. Announcement IDs are unique across a token’s lifetime.
  5. The currency code on a Stablecoin is immutable and contains only AZ characters.
  6. Multiplier updates affect all holders simultaneously.
  7. batchMint enforces MINT_RECEIVER_POLICY for each recipient individually.

Factory

  1. B20 addresses are deterministic: same inputs always produce the same address.
  2. The variant byte at address position 10 always matches the deployed variant.
  3. Each (deployer, variant, salt) tuple produces exactly one address.
  4. initCalls execute in array order. A revert in any initCall reverts the entire deployment.

Test cases

Policy Registry

Roles

Transfer Policies

Mint and Supply Cap

Burn and Seize

Pause

Memos

Permit

Factory

Variants