---
# System prepended metadata

title: Extending Switchboard Role Definitions

---

# Extending Switchboard Role Definitions

## Version History
- 2020-02-18: Changed RolesManager and RevocationRegistry be resolved by InterfaceResolver.
- 2020-02-25: Added [factory contract alternative design](#New-contract-per-role-via-a-factory-contract)

## Requirements
Roles/Role Definitions in Smart Contract Requirements: https://energyweb.atlassian.net/l/c/1gqaDEAH

## Work tracking

JIRA story: https://energyweb.atlassian.net/browse/MYEN-526

## Current Switchboard Role Definitions

Currently, role definitions in Switchboard are implemented using the [EnsPublicResolver](https://docs.ens.domains/contract-api-reference/publicresolver). A role definition is stored by calling the EIP634 `setText` to set the text for the “metadata” key to the info required to define a role. This text is currently a serialized role definition (i.e. `JSON.stringify(roleDefinitionData)`)

```mermaid
classDiagram

EnsRegistry --> EnsPublicResolver: resolves to
EnsPublicResolver --|> EnsResolver_EIP634 : inherits
EnsResolver_EIP634 : mapping~node,mapping{key,value}~ texts
EnsResolver_EIP634 : setText(bytes32 node, string key, string value)
```

## Proposed Design

This design is an extrapolation of Micha's proposal, [Managing smart-contract roles in Switchboard](https://hackmd.io/@Tq9npbj9Q52mgNSOPPB6_w/r15X5125P#Role-revocation), to provide details on how role definitions should be structured on-chain.

Key elements of Micha's proposal are:
1. A structured `issuers` definition for each role which is readable by other smart contracts
2. A `RolesManager` contract which can process role enrolment requests and answer whether or not a given address has a role

This design incorporates these elements and proposes that [a new EnsResolver be written](https://docs.ens.domains/contract-developer-guide/writing-a-resolver) that is tailored to represent a role definition.

Much of this design is directly inspired from the ENS resolver smart contracts available here: https://github.com/ensdomains/resolvers

### Smart Contract Class Diagram

*Most functions and properties and some resolvers are omitted due to HackMD diagram display size limitations.*

```mermaid
classDiagram

EnsRegistry --> EnsRoleDefinitionResolver: resolve to
EnsRoleDefinitionResolver --|> IssuersResolver : inherits
EnsRoleDefinitionResolver --|> InterfaceResolver : inherits
EnsRoleDefinitionResolver --|> VersionResolver : inherits
EnsRoleDefinitionResolver --|> xyzResolver : inherits
InterfaceResolver --> RolesManager: resolves to 
RolesManager: hasRole(address user, bytes32 role) timestamp~uint~
RolesManager: requestRole(string proof)
InterfaceResolver --> RevocationRegistry: resolves to 
RevocationRegistry : mapping~address,mapping{bytes32,bool}~ revocations
RevocationRegistry : addRevocation(bytes32 claimId, uint8 v, bytes32 r, bytes32 s)
RolesManager ..> IssuersResolver : depends on issuers() in order to process requestRole()
```

### EnsRoleDefinitionResolver

`EnsRoleDefinitionResolver` is comprised of a number of resolver profile implementations. Each resolver profile must confirm to [EIP137](https://eips.ethereum.org/EIPS/eip-137#resolver-specification) and so must implement `supportsInterface(bytes4 interfaceID) constant returns (bool)`, with the interfaceID corresponding to [EIP165](https://eips.ethereum.org/EIPS/eip-165). *Contrary* to the standard resolver interfaceIDs [which guarantee only the getters](https://docs.ens.domains/contract-api-reference/publicresolver#check-interface-support), it may be valuable for the interfaceIDs of the role definition resolvers to include setters.

All read functions are public. All mutations should only be allowed by the node owner and any addresses authorized by the owner.

#### EnsRoleDefinitionResolver functions

`EnsRoleDefinitionResolver` should have the [lop-level functions of the `PubicResolver`](https://github.com/ensdomains/resolvers/blob/master/contracts/PublicResolver.sol).
The `multicall` function is notable in that it could be useful in the case of wanting to retrieve the entire role definition (which would be helpful when caching the role definition).

#### Mandatory Resolvers

##### IssuersResolver
[Micha proposes](https://hackmd.io/@Tq9npbj9Q52mgNSOPPB6_w/r15X5125P#Resolver-for-Role-namespace) that an `issuers` struct be resolvable for given node:
```json=
struct issuers {
    address[] dids
    bytes32 role
}
```
- `issuers() : (adddress[dids], bytes32 role)`: returns the `issuers` struct in de-structured values as it is not possible for external functions to return a struct. This function allows other smart contracts to easily retrieve the issuer for a role (e.g. the `RoleRegistry` when processing `requestRole`).
- `setIssuers(address[] dids, bytes32 role)`

##### VersionResolver
Provides version control, potentially allowing an issued role claim to be associated with a particular version of the role definition
- `version() : string version`
- `setVersion(string version)`

##### InterfaceResolver
This is a resolver profile implementation provided by @ensdomains/resolver. It can be used to resolve both the `RolesManager` and the `RevocationRegistry` smart contracts (and any other smart contract).
- `interfaceImplementer(bytes4 interfaceID) : address`
- `setInterface(bytes4 interfaceID, address implementer)`

#### Optional resolvers

These resolvers are optional, as the data could simply be stored using the text resolver.

##### FieldsResolver
These are the fields that are filled during role enrolment. As these fields don't need to be read or manipulated on-chain, they can be set/retreived as an string (a serialization of all field data).
- `getFields() : string value`
- `setFields(string fields)`

##### RevocationContractAddressResolver
The address of the smart contract which can answer whether or not a claim corresponding to this role definition for a given user has been revoked.
- `getRevocationAddr() : address revocationAddr`
- `setRevocationAddr(address revocationAddr)`

##### RoleTypeResolver
Currently either "app" or "org".
- `getRoleType() : string roleType`
- `setVersion(string roleType)`

##### RoleNameResolver
The name of the role (e.g. "user", "admin", etc...)
- `getRoleName() : string roleName`
- `setRoleName(string roleName)`

#### User Flexibility and Checking Interface Support

It is required that a Switchboard user be able to provide their own role definition resolver implementation. In this event, the user should provide to Switchboard the address of a contract which implements `EIP165` `supportsInterface`. In this way, Switchboard can determine, in an automated manner, how to handle the provided contract. See https://docs.ens.domains/contract-api-reference/publicresolver#check-interface-support for details.

EnergyWeb should maintain a list of role definition resolver profiles, their associated interfaceIDs and perhaps their relationships to Switchboard functionality.

Of course, `EIP165` only verifies that the functions are present, not that the implementations are correct.

#### Single vs Multiple Instance

Only a single instance of `EnsRegistry` and `EnsRoleDefinitionResolver` would be required per chain. All role definitions could share the same resolver (as they do currently with the `EnsPublicResolver`). If multiple roles are sharing a resolver, each function in the resolver interface must include a `bytes32 node` parameter to identify the role node. 

However, it would be possible to have some role definitions rely on a shared instance of the `EnsRoleDefintionResolver` while others have their own instance.

Other role definitions may also rely on a different implementation. This would allow Switchboard users to potentially provide their own implementation without impacting other users.

The resolver used to represent a role defintion could easily be changed as the `EnsRegistry` provides a `setResolver` function to update the resolver address associated with a node.

```mermaid
graph LR

EnsRegistry-->|admin.org1|EnsRoleDefinitionResolver
EnsRegistry-->|user.org1|EnsRoleDefinitionResolver
EnsRegistry-->|user.org2|ImprovedEnsRoleDefinitionResolver
EnsRegistry-->|user.org3|CustomEnsRoleDefinitionResolver
```

##### EnsRoleDefinitionResolver Factory

If each role definition has an associated ens resolver contract, then a factory smart contract can be used to generate a new resolver contract for each role.

See: 

#### Upgradability/Migratability

As ENS consists of an ownership hierarchy it would be possible, in principle, for the owner of a higher node to update a role definition node. Given this, it would be possible, for example, for the owner of an organization to migrate the role definitions of all the roles under their organization to a new contract. 

### RolesManager and RevocationRegistry Smart Contract

These contracts correspond to the ["RolesManager" contract](https://hackmd.io/@Tq9npbj9Q52mgNSOPPB6_w/r15X5125P#Role-management-smart-contract) and the [revocation function](https://hackmd.io/@Tq9npbj9Q52mgNSOPPB6_w/r15X5125P#Role-revocation), respectively, that Micha describes in his proposal. Other methods could be used.

These contracts could either be deployed as singular instance per chain or multiples instances could be deployed (perhaps one per each role). If the contract is permissionless (as is the `RolesManager` Micha described), it is not essential to have an instance for each a role and a single instance could avoid the need to look up the address of the contract (or even register it in the first place).

As will the RoleResolver, it would be possible to have some role definitions rely on a shared instance of the `RolesManager` while others have their own instance. Other role definitions may also rely on a different `RolesManager` implementation.

```mermaid
graph LR

EnsResolver-->|admin.org1|RolesManager_Instance1
EnsResolver-->|user.org1|RolesManager_Instance1
EnsResolver-->|user.org2|RolesManager_Instance1
EnsResolver-->|user.org3|RolesManager_Instance2
EnsResolver-->|admin.org2|ImprovedRolesManager_Instance1
```

#### Interface Identification

It may be desirable for the RolesManager and RevocationRegistry to also leverage EIP165.

### Design Limitations
- Not backwards compatible. At a minium, clients which read/modify role definitions would need to be modified to retrieve the resolver address from ENS registry.
- It is not possible to dynamically add new resolver profiles to the `EnsRoleDefinitionResolver` once the resolver is deployed. This means that ease of migration to an improved resolver is important.
- If changing the `RolesManager` and `RevocationRegistry` to different version, all role definitions would need to be updated to point to the new version. (This )
- As this design involves a new ens resolver, standard tooling built for the PublicResolver (e.g. [Manager](https://medium.com/the-ethereum-name-service/new-text-records-now-available-for-ens-names-in-manager-a0ebb9cda73a)) may not work.

### Use by other EnergyWeb projects

#### Origin

[Origin requires](https://energyweb.atlassian.net/wiki/spaces/OR/pages/1682898968/On-chain+verifiable+claims+RBAC+system) a smart-contract representation of an organization, for which organization members with specific roles can execute transactions on behalf of the organization (i.e. "proxy functionality"). This organization smart-contract can use the `RolesManager.hasRole(address user, bytes32 role)` function to restrict the proxy functionality specific organization roles (corresponding specific ENS nodes).

The organization can follow an algorithm as follows to restrict functionality to specific roles:

1. When receiving a transaction request, look up the roles (stored as node hashes) which are allowed to execute the transaction
2. For each role:
    2.1. Resolve the `EnsRoleDefinitionResolver` from the `EnsRegistry` then resolve the `RolesManager` and `RevocationRegistry` (Optional, the `RolesManager` and `RevocationRegistry` could instead be known addresses)
    2.2. Ask the `RolesManager` if the transaction sender has the role and check with the `RevocationRegistry` whether or not it has been revoked 

## Alternative Designs Considered

### New contract per role via a factory contract

Other designs have suggested that each role definition be represented by a new smart contract with a factory contract used to create the roles:

- [Role Definition Factory Design](https://energyweb.atlassian.net/wiki/spaces/MYEN/pages/1549467649/Role+definition+factory+design)
- [Micha’s initial proposal on Role Based Access for EW-DID](https://hackmd.io/S2sE3jNWQv-PFAROGFZ16w?view)

The additional smart contract could leverage the `EnsRegistry` node ownership for authorisations, as the `EnsPublicResolver` does:
```
constructor(ENS _ens) public {
    ens = _ens;
}

function isAuthorised(bytes32 node) internal view returns(bool) {
    address owner = ens.owner(node);
    return owner == msg.sender || authorisations[node][owner][msg.sender];
}
```

#### Smart Contract Class Diagram

```mermaid
classDiagram

FactoryContract ..> EnsRoleDefinitionResolver_OneRoleOnly: create
EnsRegistry --> EnsRoleDefinitionResolver_OneRoleOnly: resolve to
```

#### Comparison with suggested design

##### Determining RoleDefinition implementation

[The Role Bases Access for EW-DID design](https://hackmd.io/S2sE3jNWQv-PFAROGFZ16w?view) states that

> In order to make sure that all the smart contracts referred to by ENS comply with the same interface and security requirements, we could create a factory contract who creates and references name space contracts

However, the using a shared EnsResolver also accomplishes the same thing as all role definitions using the same shared resolver obviously have the same functionality.

##### Needing to specify the node

If using a shared EnsResolver, each function call must specify the node upon which to operate (this is how the `EnsPublicResolver` works). When using a factory, the ENS node representing a role could be provided at the time of the construction, binding the role def contract to a particular node.

##### Transaction cost

Deploying an additional smart contract for each role is more expensive.

##### Upgradability

If the complexity of a factory is already introduced, it may make more sense to pursure [upgradability](#Upgradeable-EnsRoleDefinitionResolver).

### Use InterfaceResolver to reference another contract
Rather than deploying a new ens resolver, it may be possible to the `InterfaceResolver` of the existing `EnsPublicResolver` to reference the locations of a separate `RoleMetadata` smart contract (in addition to the `RolesManager` and `RevocationRegistry` smart contracts).

This `RoleMetadata` smart contract would contain values, such as the `issuers` struct, used to define the role definition.

#### Smart Contract Class Diagram

```mermaid
classDiagram

EnsRegistry --> EnsRoleDefinitionResolver: resolve to
EnsRoleDefinitionResolver --|> InterfaceResolver : inherits
EnsRoleDefinitionResolver --|> VersionResolver : inherits
EnsRoleDefinitionResolver --|> xyzResolver : inherits
InterfaceResolver --> RolesManager: resolves to 
RolesManager: hasRole(address user, bytes32 role) timestamp~uint~
RolesManager: requestRole(string proof)
InterfaceResolver --> RevocationRegistry: resolves to 
RevocationRegistry : mapping~address,mapping{bytes32,bool}~ revocations
RevocationRegistry : addRevocation(bytes32 claimId, uint8 v, bytes32 r, bytes32 s)
RolesManager ..> RoleMetadata : depends on issuers() in order to process requestRole()
InterfaceResolver --> RoleMetadata: resolves to 
RoleMetadata : issuers(address[] dids, bytes32 role)
RoleMetadata : getIssuers() (address[] dids, bytes32 role)
```

#### Comparison with suggested design

Cons:
- As this design adds an additional layer of indirection and adds an additional contract, it could be seen as being more complex.

Pros:
- The `RoleMetadata` contract can be updated separately from the other data.
    - In the suggested design, if needing to migrate to an `ImprovedRoleDefinition` in order to provide an additional field, data from all resolvers would need to be copied as well.
- The `EnsPublicResolver` (which is a standard, already deployed contract) can be used as is. This means that the "custom" functionality is separated from the standard functionality. This may be a good general design principle.


### Upgradeable EnsRoleDefinitionResolver

Having an upgradeable EnsRoleDefinitionResolver could be useful (for instance in the event that we want to change the datatypes which define a role definition and need to add a new resolver profile).

For example, OpenZeppelin has framework which uses proxy contract to allow a "logic contract" to be upgraded.
https://docs.openzeppelin.com/upgrades-plugins/1.x/

However, the proxy contract approach would require some limitations on the `EnsRoleDefinitionResolver`:
- Initializers must be used instead of constructors. Therefore, the existing `EnsPublicResolver` implementation could not be used as a base.
- `delegateCall` is not allowed in proxy-based upgradeable contracts because it could be used to [call `selfdestruct` in another contract](https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#potentially-unsafe-operations). However, I believe `multicall` function of `ENSPublicResolver` could still be used as it restricts `delegateCall` to functions of the contract itself:
```
 function multicall(bytes[] calldata data) external returns(bytes[] memory results) {
        results = new bytes[](data.length);
        for(uint i = 0; i < data.length; i++) {
            (bool success, bytes memory result) = address(this).delegatecall(data[i]);
            require(success);
            results[i] = result;
        }
        return results;
    }
```

### Resolve RolesManager and RevocationRegistry directly

It would be possible to resolve the RolesManager and RecovationRegistry functions directly.

#### Smart Contract Class Diagram

```mermaid
classDiagram

EnsRegistry --> EnsRoleDefinitionResolver: resolve to
EnsRoleDefinitionResolver --|> xyzResolver : inherits
EnsRoleDefinitionResolver --|> RolesManagerResolver : inherits
EnsRoleDefinitionResolver --|> RevocationRegistryResolver : inherits
RolesManagerResolver : hasRole(address user, bytes32 role) timestamp~uint~
RolesManagerResolver : requestRole(string proof)
RevocationRegistryResolver : mapping~address,mapping{bytes32,bool}~ revocations
RevocationRegistryResolver : addRevocation(bytes32 claimId, uint8 v, bytes32 r, bytes32 s)
```

#### Comparison with suggested design

Cons:
- There are consistency boundaries between the concepts "RoleDefinition", "RolesManager", "RevocationRegistry". For instance, typically, one would manipulate "RoleDefinition" then persist changes in a transaction that would not affect "RolesManager" or "RevocationRegistry"
- There cannot be a difference in cardinality between the role definitions and the roleManagers and revocationRegistry. For example, it would not be possible to have a many EnsRoleDefinitionResolver instances all using one RolesManagerResolver.
- Existing ENS resolvers profiles (at least the public ones) all have a simple get/set structure. This alternative would deviate from that standard.
- It would not be possible to upgrade either component independently

Pros:
- It is arguably simpler as all functionality would be in a single contract


### Store more/all data as text fields