For the complete documentation index, see llms.txt. This page is also available as Markdown.

12. Remove a compliance rule

Removing a compliance rule unbinds a module from the asset's compliance. From that moment the module is no longer consulted, and the limit it imposed no longer applies. The Compliance Owner does this when the terms of the asset change, when a rule is replaced by another, or when a module misbehaves. A forced removal exists for a module that refuses to be unbound.

At a glance

Triggered by

Compliance Owner

Actors

Compliance Owner, Issuer, Investor, Platform

Contracts

ModularCompliance, the compliance module

Preconditions

The module is bound to the compliance (see 11); the caller holds OWNER in the asset's domain (see 25)

Result

The module is no longer bound, its settings for this compliance are discarded, and it is no longer called on any movement

Actors and roles

Actor
Role in this flow
What they hold

Compliance Owner

Unbinds the module.

OWNER on the ModularCompliance

Issuer

Decides that the rule no longer applies. Usually the same party as the Compliance Owner.

The terms of the asset

Investor

Is affected: movements the rule limited become possible. Does not transact.

A position

Platform

Runs the reads and submits with the Compliance Owner's key.

The Compliance Owner's signing key, or a key authorized for it

Sequence

Step by step

Step 1. Check what the removal changes

Who: Platform, on behalf of the Compliance Owner

Call: Off-chain, using read calls

Before the call: The Issuer has decided to remove the rule.

What happens: The Platform reads ModularCompliance.isModuleBound(module), which must be true, and ModularCompliance.getModulesByType(type) for each type, to see what remains bound. Removing the last RULE module leaves movements limited by eligibility alone. Removing the last SPENDER module makes every spender acceptable. If the rule is being replaced, the Platform prepares the binding of the new module so that both can go in one transaction.

What changes: Nothing on-chain.

Step 2. Remove the module

Who: Compliance Owner (OWNER)

Call: ModularCompliance.removeModule(module)

Before the call: Step 1 is done.

What the contract checks:

  1. The caller holds OWNER in the asset's domain. Otherwise the call reverts with AccessManagedUnauthorized.

  2. The module is not the zero address. Otherwise ZeroAddress.

  3. The module is bound. Otherwise ModuleNotBound.

  4. The module accepts to unbind. If its unbindCompliance reverts, the whole removal reverts and the module stays bound.

What changes: The compliance removes the module from its set and from the list of every type. The module discards its binding and its settings for this compliance. The compliance emits ModuleRemoved(module) and the module emits ComplianceUnbound(compliance). From the next movement, the module is not called.

Step 3. Force the removal

Only when step 2 reverts because of the module.

Who: Compliance Owner (OWNER)

Call: ModularCompliance.forceRemoveModule(module)

Before the call: removeModule reverted with an error raised by the module, or the module reverts on every call.

What the contract checks:

  1. The caller holds OWNER. Otherwise AccessManagedUnauthorized.

  2. The module is not zero and is bound. Otherwise ZeroAddress or ModuleNotBound.

What changes: The compliance removes the module from its set and from the list of every type, without calling the module. It emits ModuleForceRemoved(module) and no ModuleRemoved. The module still records a binding to this compliance on its side, so the same module address cannot be bound to this compliance again. A new deployment of the module can.

State after the flow

  • ModularCompliance.isModuleBound(module) is false and ModularCompliance.getModules() no longer includes the module.

  • ModularCompliance.canTransfer(from, to, amount) no longer reflects the rule.

  • After a normal removal, the module holds no settings for this compliance.

  • No balance and no position has changed. The compliance ledger is kept by the compliance itself and is unaffected.

Failure modes

Symptom
Cause
Fix

Reverts with AccessManagedUnauthorized

The caller does not hold OWNER in the asset's domain (check 1)

Grant the role: 25

Reverts with ModuleNotBound

The module is not in the set (step 2, check 3; step 3, check 2)

Read isModuleBound first. Nothing to remove

removeModule reverts with an error from the module

The module's unbindCompliance reverts (step 2, check 4)

forceRemoveModule

removeModule runs out of gas

The transaction did not carry enough gas for the call into the module

Send with more gas. If the module consumes all of it, forceRemoveModule

Binding the same module again reverts with ComplianceAlreadyBound

The module was force-removed and still holds its binding

Bind a new deployment of the module

A module bound again has lost its settings

Settings belong to a binding and are discarded when it ends

Configure the module again. Use addAndSetModule

After removing a spender module, any approved spender can transfer

It was the last SPENDER module

Intended. Bind the replacement in the same transaction if a policy is still required

Common misunderstandings

Wrong: "Removing a rule and adding it back is a way to reset or refresh it."

Right: A removal discards the module's settings for this compliance. A module bound again starts empty: a cap is unset, a list has no entry. To make the compliance re-read the types of an upgraded module, use resyncModuleTypes. To change settings, use callModuleFunction.

Wrong: "A faulty module can block the asset."

Right: A module that reverts when told of a movement does block movements while it is bound. It cannot stay bound against the Compliance Owner's will: forceRemoveModule removes it without calling it.

Wrong: "Removing a rule recalculates positions."

Right: Positions are kept by the compliance, not by modules. Removing a module changes which questions are asked before a movement. It does not touch the ledger.

Wrong: "Force removal is the same as removal, only faster."

Right: A forced removal leaves the module's own record of the binding in place. The same module address can never be bound to this compliance again. It is a remedy for a module that misbehaves, not the normal path.

Integration notes

  • When replacing a rule, remove the old module and bind the new one in the same transaction, so that there is no interval without the rule.

  • Index ModuleRemoved and ModuleForceRemoved separately. A forced removal signals a module that could not be unbound normally.

  • After a forced removal, record that the module address is unusable for this compliance.

  • Read ModularCompliance.getModulesByType(type) after the removal to confirm which rules, spender policies and trackers remain.

  • Movements authorized on satellites before the removal keep the amount range they were issued with. The removal applies to validations issued afterward.

Last updated