Pausable
Pausable is an emergency stop for Daml. It is the Daml analogue of OpenZeppelin's Pausable. Your template holds a paused flag and implements the Pausable interface. Gated choices call whenNotPaused this and refuse to run while the flag is set.
Pre-release and unaudited. The packages have no release yet. Source:
packages/security/api-pausable-v1
and
packages/security/pausable-v1
in OpenZeppelin/canton-contracts.
Packages
| Package | Module | Contents |
|---|---|---|
openzeppelin-api-pausable-v1 | OpenZeppelin.Api.PausableV1 | The Pausable interface and PausableView. Frozen. |
openzeppelin-pausable-v1 | OpenZeppelin.PausableV1 | The guards and the failure statuses. Depends on the API package. |
The interface is in its own package because Daml cannot upgrade an interface. A fix to a guard is a new version of openzeppelin-pausable-v1, and the interface package does not change.
import OpenZeppelin.Api.PausableV1 (Pausable, PausableView (..))
import qualified OpenZeppelin.PausableV1 as PausableThe Flag Is on Your Template
Solidity stores _paused in the contract that it protects. Pausable does the same in Daml: the flag is a field of your template. A guard reads the flag from the contract that the choice runs on, so the caller cannot substitute a different switch.
The package has no template of its own. The interface defines no choices, so a party that holds only a ContractId Pausable cannot change the flag. Your own choices change it.
Usage
1. Add the flag and the interface instance.
template Vault
with
admin : Party
owner : Party
balance : Decimal
paused : Bool
where
signatory admin, owner
interface instance Pausable for Vault where
view = PausableView with paused2. Guard the choices that a pause must stop. Call whenNotPaused this before the choice changes state:
choice Vault_Withdraw : ContractId Vault
with amount : Decimal
controller owner
do
Pausable.whenNotPaused this
create this with balance = balance - amount3. Write the pause and unpause choices. The controller and the body of these choices define who can pause. Each choice guards, then creates the next contract with paused changed:
choice Vault_Pause : ContractId Vault
controller admin
do
Pausable.whenNotPaused this
create this with paused = True
choice Vault_Unpause : ContractId Vault
controller admin
do
Pausable.whenPaused this
create this with paused = FalseMake these choices consuming, or call archive self, so that one contract stays active. Any authority model fits: a single party, a role grant, an M-of-N approval, or a timelock. For a role, take the caller and its credential as choice arguments and check them before the flip.
Functions
All functions take any template that implements Pausable.
whenNotPaused : HasToInterface t Pausable => t -> Update (): fails witheEnforcedPauseif the contract is paused. Use it in gated choices and in the pause choice.whenPaused : HasToInterface t Pausable => t -> Update (): fails witheExpectedPauseif the contract is not paused. Use it in the unpause choice and in choices that run only during an incident, such as an emergency drain.isPaused : HasToInterface t Pausable => t -> Bool: returns the flag, for a choice that branches instead of failing.
Failure Statuses
Each guard fails with a FailureStatus. A Ledger API or JSON Ledger API client receives a DAML_FAILURE error with the errorId below. Match on the errorId, not on the message.
| Status | errorId | Message |
|---|---|---|
eEnforcedPause | openzeppelin.com/pausable-enforced-pause | Pausable: the contract is paused |
eExpectedPause | openzeppelin.com/pausable-expected-pause | Pausable: the contract is not paused |
In Daml Script, compare the whole status:
Left (FailureStatusError status) <-
trySubmit owner do exerciseCmd vault Vault_Withdraw with amount = 1.0
status === Pausable.eEnforcedPauseReading the Flag Off-Ledger
A wallet, a registry endpoint, or an auditor reads PausableView through the interface, without knowing the template. In Daml Script:
Some v <- queryInterfaceContractId reader (toInterfaceContractId @Pausable cid)
v.paused === TrueAdding Pausable to an Existing Template
A template with active contracts adds Pausable in its next Smart Contract Upgrade (SCU) version. SCU accepts a new field only as an Optional at the end of the record, so the flag is paused : Optional Bool, and the view reads None as unpaused:
import DA.Optional (fromOptional)
interface instance Pausable for UpgradedVault where
view = PausableView with paused = fromOptional False pausedThe pause and unpause choices store Some True and Some False. By default, damlc rejects a new interface instance on an existing template with template-has-new-interface-instance. Review the old-version behavior below, then build with -Wno-template-has-new-interface-instance.
The choices of the old version have no guard, so a submission that selects the old version skips the pause. After a flip stores Some True or Some False, the old version cannot read the contract, and such a submission fails with an upgrade error. If clients of the old version must resume after an unpause, store None on unpause. Unvet the old version when all clients use the new one.
The examples/pausable/ directory has runnable projects for a vault, a registry, and this retrofit.
Security Considerations
- Per-contract switch. Each contract has its own flag. To pause several templates together, give each a flag, or route every protected operation through one contract that holds the flag.
- One guard per choice. A choice is gated only if it calls the guard. Call
whenNotPausedbefore the state change in every choice that a pause must stop. The guard checks only the flag, so keep each choice's controller as its access control. - Signatories bypass the flip choice. The template's
Archivehas no guard. Its signatories can archive a paused contract and create it again with any flag value, without your pause authority checks. Make every signatory part of your pause authority, or trust it with the flag. - Pass
this. Do not guard with aPausablefetched from a contract ID that the caller supplies. The caller can present an unpaused contract, and the gated choice runs. - Origination control. A pause stops new exercises of gated choices. It does not affect committed transactions.
- CIP-0112 fields.
PausableViewcarries onlypaused. A registry that serves CIP-0112reasonanduntilkeeps them as its own template fields; seeexamples/pausable/registry.
Upgrades
- A fix to
openzeppelin-pausable-v1reaches your contracts in your next SCU version, built against the new DAR. A submission that selects your old version still runs the old guard, so unvet it. EacherrorIdstays the same across versions. - Your template stays upgradeable, because the interface instance is on your template.
- To adopt a future
openzeppelin-api-pausable-v2, add a second interface instance under SCU and keep both, or create a new template version outside SCU and migrate the contracts offline. The migration must copy the flag.
Build and Consume
From the root of canton-contracts:
DAML_PACKAGE=packages/security/api-pausable-v1 dpm build
DAML_PACKAGE=packages/security/pausable-v1 dpm builddata-dependencies:
- ../canton-contracts/packages/security/api-pausable-v1/.daml/dist/openzeppelin-api-pausable-v1-0.1.0.dar
- ../canton-contracts/packages/security/pausable-v1/.daml/dist/openzeppelin-pausable-v1-0.1.0.darRelated
- Access Control or Ownable to control who can pause.