Access Control
Access Control is role-based authorization for Daml. It is the Daml analogue of OpenZeppelin's AccessControl and AccessControlDefaultAdminRules. An admin party (the DEFAULT_ADMIN_ROLE holder in Solidity) grants named roles to parties, and gated choices check that the caller holds the required role.
The package has no dependency on other OpenZeppelin packages.
Experimental. Version 0.x, unaudited, and subject to a redesign before
release. Source:
experiments/access/access-control-v1
in OpenZeppelin/canton-contracts.
This page documents the current experimental package. A replacement is in
review in PR #48
and will be merged soon. The new ScopedAuthorizationGrantV1 component
issues revocable, time-bounded grants. Before a protected choice runs, the
application checks the caller, the issuing authority, and the resource scope
of the grant. A treasury example in the PR builds role-based access control
on these grants. This page
will change when the new version is merged.
import OpenZeppelin.AccessControlV1The Daml Model
Roles are Text. Solidity uses bytes32 role IDs such as keccak256("MINTER_ROLE"). A Daml template field cannot be a type variable, so a generic role primitive stores role : Text. For compile-time checks, define your own role type and convert it with a function such as roleId : MyRole -> Text.
A grant is a contract. Daml-LF 2.1 has no contract keys, so there is no global role table. A RoleGrant is a contract that admin signs and that names one account. A gated choice fetches the grant that the caller presents and checks three things: admin issued it, it names the caller, and it carries the required role. A party cannot forge a grant, because only admin can sign one. A party that presents a grant for another account fails the check.
Templates
RoleGrant
A role that admin grants to account.
| Field | Type | Description |
|---|---|---|
admin | Party | The authority that issued the grant. |
account | Party | The party that holds the role. |
role | Text | The role ID, for example "MINTER_ROLE". |
Signatory admin, observer account.
RoleGrant_Renounce(controlleraccount, consuming, returns()): the holder gives up the role (therenounceRoleanalogue).
admin can also archive a grant directly, because it is the signatory.
RoleAdmin
The contract that issues and revokes grants. It has one field, admin : Party. Signatory admin. All choices are nonconsuming, so the contract stays active.
| Choice | Controller | Arguments | Result |
|---|---|---|---|
RoleAdmin_GrantRole | admin | account, role | ContractId RoleGrant |
RoleAdmin_RevokeRole | admin | grantCid | () |
RoleAdmin_GrantRoleAs | caller | caller, adminRole, adminGrantCid, account, role | ContractId RoleGrant |
RoleAdmin_RevokeRoleAs | caller | caller, adminRole, adminGrantCid, grantCid | () |
RoleAdmin_BeginDefaultAdminTransfer | admin | newAdmin, role, effectiveTime | ContractId DefaultAdminTransferOffer |
There are two ways to manage roles:
- Directly.
admingrants or revokes any role. - Through a delegate (the
getRoleAdminanalogue). Thecallerpresents its own grant foradminRole, then grants or revokesrole. The choice runs with the authority of theRoleAdmincontract, soadminstill signs the new grant. The delegate needsRoleAdmindisclosed to it, usually by an off-ledger service of the application.
The library has no role hierarchy. The caller supplies adminRole, and the
choice checks only that the caller holds a grant for it. A party that can see
RoleAdmin and holds any grant from the same admin can name that role as
adminRole, then grant or revoke any role. The application must check that
adminRole is the correct admin role for role before it accepts a delegated
grant or revoke, and it must bind grants to the resource that they protect.
DEFAULT_ADMIN_ROLE has no special handling. Restrictions on who can delegate the root role are application policy.
DefaultAdminTransferOffer
A pending, timelocked transfer of a role to newAdmin (the AccessControlDefaultAdminRules analogue). Use the DEFAULT_ADMIN_ROLE ID as role to hand over the default admin role.
| Field | Type | Description |
|---|---|---|
admin | Party | The current admin. |
newAdmin | Party | The party that receives the role. |
role | Text | The role to transfer. |
effectiveTime | Time | The earliest ledger time for acceptance. |
Signatory admin, observer newAdmin.
DefaultAdminTransferOffer_Accept(controllernewAdmin, returnsContractId RoleGrant): accept when ledger time is at or aftereffectiveTime. Creates aRoleGrantofrolefornewAdmin.DefaultAdminTransferOffer_Cancel(controlleradmin, returns()): cancel the transfer at any time before acceptance (thecancelDefaultAdminTransferanalogue).
Acceptance does not revoke the role from the current admin. To make the transfer exclusive, revoke the old grant after acceptance.
Helper Functions
requireRole : Party -> Text -> Party -> RoleGrant -> Update (): theonlyRolemodifier analogue. Call it at the start of a gated choice with the caller, the required role, the expected admin, and the fetched grant.hasRole : Party -> Text -> Party -> RoleGrant -> Bool: the same check as a pure predicate.requireTimelockElapsed : Time -> Update (): fails if ledger time is before the given time.DefaultAdminTransferOfferuses it, and you can use it in your own timelocks.
Gating a Choice
A privileged choice fetches the grant that the caller presents and checks it first. In this example, the enclosing template has an admin : Party field:
nonconsuming choice Mint : ContractId Token
with
caller : Party
grantCid : ContractId RoleGrant
amount : Int
controller caller
do
grant <- fetch grantCid
requireRole caller "MINTER_ROLE" admin grant
create Token with owner = caller; amountThe fetch succeeds because caller is an observer of its own grant.
Errors
| Message | Cause |
|---|---|
AccessControl: grant admin is not the expected authority | A different admin issued the presented grant, or the grant to revoke. |
AccessControl: grant does not name the caller (impersonation) | The grant names another party. |
AccessControl: grant does not carry the required role | The grant is for another role. |
AccessControl: default-admin handoff nominee is the current admin | newAdmin is the current admin. |
AccessControl: default-admin transfer timelock has not elapsed | Acceptance before effectiveTime. |