Our synthetic token authorizes a primary minter and a set of extra minters. The primary identity also appears in that extra set. Clearing the primary field leaves the old identity authorized unless the transition removes its duplicate grant too.

We build this as an independent authorization fixture using symbolic identities and no token addresses. The design is a custom extension inspired by the general role problem. It does not claim that the base CW20 specification supplies this exact multi-minter policy or describes an issue in a live contract.

Enumerate the predicates that grant permission

The authorization rule in the fixture is a logical OR. An identity may mint if it equals the primary or appears in the extra set. Revocation has to account for every branch that can still evaluate to true for the identity being removed.

The intended policy is explicit. Rotating or clearing the primary removes the previous primary from the extra set as well. Unrelated extra minters keep their existing grants. Clearing the primary therefore does not disable all minting because separately authorized identities remain permitted.

function changePrimary(state, caller, replacement) {
  if (caller !== state.admin) throw new Error("administrator");
  const previous = state.primary;
  state.primary = replacement;
  if (previous !== null) state.extras.delete(previous);
}

That distinction prevents a misleading interface claim. A button labeled disable all minting requires a different state transition that closes every grant path. A button labeled remove primary can implement the narrower policy in this example but must not imply that all other minters have lost authority.

Test behavior after the configuration changes

A configuration query returning no primary proves only that one field changed. The important test attempts a mint as the old primary and expects authorization failure. We also attempt a mint as an unrelated extra minter and expect success.

A rotation test grants a new primary and repeats the old identity rejection. This catches a cleanup implementation that handles clearing but forgets transfer. A later explicit grant to the removed identity is a separate authorized operation and should be distinguished from stale permission that survived accidentally.

We test the action after revocation because a configuration view can display the intended primary field while a second authorization branch still accepts the old identity and the action test establishes whether that identity can create supply after the transition even when the storage query appears to confirm that the requested configuration change has completed.

In a CosmWasm implementation the stored configuration and any cw-storage-plus map entries form the complete authorization state. A correct write to one location does not erase another location. The transition should update both under the contract's declared authority rules.

Preserve unrelated permissions deliberately

Removing every extra minter when rotating the primary is a different policy. It may revoke legitimate delegated issuers and cause another operational failure. The regression should assert the intended preservation behavior instead of declaring any broadly restrictive result secure.

The fixture snapshots the current primary before changing it and removes that previous identity from the additional set. It does not remove the replacement unless another policy says the replacement should have only one representation. Authorization can remain logically correct even when configuration has redundant entries but redundancy makes future revocation harder to reason about.

Previous primary revoked; unrelated grant retained; replacement authorized
10 assertions passed

Our tests cover clearing and rotation alongside an unauthorized administrator attempt. They verify that the old identity fails to mint while an unrelated authorized identity still succeeds. The model checks permissions without changing supply so it isolates the role transition from token arithmetic.

Restore the contract boundaries in Rust tests

A Rust unit test can instantiate a synthetic contract state and send the same sequence of execute messages through the actual entry point. That exercises address validation and storage access alongside the permission predicate. A chain integration test then verifies the deployed artifact and message encoding.

The JavaScript example models none of those runtime boundaries. It also does not assess governance security or key custody. An administrator allowed to grant roles can intentionally restore an identity's authority and that power belongs in the system's documented trust model.

A generated sequence of role additions and rotations can exercise more configurations than a single removal case. After each primary transition attempt minting as the previous identity and every unrelated delegate so the assertions defend revocation and permission preservation together.

Sources and examples