Skip to content

Latest commit

 

History

History
1641 lines (888 loc) · 66 KB

Roles.md

File metadata and controls

1641 lines (888 loc) · 66 KB

Module 0x1::Roles

This module defines role-based access control for the Diem framework.

Roles are associated with accounts and govern what operations are permitted by those accounts. A role is typically asserted on function entry using a statement like Self::assert_diem_root(account). This module provides multiple assertion functions like this one, as well as the functions to setup roles.

For a conceptual discussion of roles, see the DIP-2 document.

Resource RoleId

The roleId contains the role id for the account. This is only moved to an account as a top-level resource, and is otherwise immovable.

struct RoleId has key
Fields
role_id: u64

Constants

The signer didn't have the required Diem Root role

const EDIEM_ROOT: u64 = 1;

The signer didn't have the required Treasury & Compliance role

const ETREASURY_COMPLIANCE: u64 = 2;

const CHILD_VASP_ROLE_ID: u64 = 6;

const DESIGNATED_DEALER_ROLE_ID: u64 = 2;

const DIEM_ROOT_ROLE_ID: u64 = 0;

The signer didn't have the required Child VASP role

const ECHILD_VASP: u64 = 9;

The signer didn't have the required Designated Dealer role

const EDESIGNATED_DEALER: u64 = 6;

The signer didn't have the required Parent VASP role

const EPARENT_VASP: u64 = 3;

The signer didn't have the required ParentVASP or ChildVASP role

The signer didn't have the required Parent VASP or Designated Dealer role

A RoleId resource was in an unexpected state

const EROLE_ID: u64 = 0;

The signer didn't have the required Validator role

const EVALIDATOR: u64 = 7;

The signer didn't have the required Validator Operator role

const EVALIDATOR_OPERATOR: u64 = 8;

const PARENT_VASP_ROLE_ID: u64 = 5;

const VALIDATOR_ROLE_ID: u64 = 3;

Function grant_diem_root_role

Publishes diem root role. Granted only in genesis.

public(friend) fun grant_diem_root_role(dr_account: &signer)
Implementation
public(friend) fun grant_diem_root_role(
    dr_account: &signer,
) {
    DiemTimestamp::assert_genesis();
    // Checks actual Diem root because Diem root role is not set
    // until next line of code.
    CoreAddresses::assert_diem_root(dr_account);
    // Grant the role to the diem root account
    grant_role(dr_account, DIEM_ROOT_ROLE_ID);
}
Specification
include DiemTimestamp::AbortsIfNotGenesis;
include CoreAddresses::AbortsIfNotDiemRoot{account: dr_account};
include GrantRole{addr: Signer::address_of(dr_account), role_id: DIEM_ROOT_ROLE_ID};

Function grant_treasury_compliance_role

Publishes treasury compliance role. Granted only in genesis.

public(friend) fun grant_treasury_compliance_role(treasury_compliance_account: &signer, dr_account: &signer)
Implementation
public(friend) fun grant_treasury_compliance_role(
    treasury_compliance_account: &signer,
    dr_account: &signer,
) acquires RoleId {
    DiemTimestamp::assert_genesis();
    CoreAddresses::assert_treasury_compliance(treasury_compliance_account);
    assert_diem_root(dr_account);
    // Grant the TC role to the treasury_compliance_account
    grant_role(treasury_compliance_account, TREASURY_COMPLIANCE_ROLE_ID);
}
Specification
include DiemTimestamp::AbortsIfNotGenesis;
include CoreAddresses::AbortsIfNotTreasuryCompliance{account: treasury_compliance_account};
include AbortsIfNotDiemRoot{account: dr_account};
include GrantRole{addr: Signer::address_of(treasury_compliance_account), role_id: TREASURY_COMPLIANCE_ROLE_ID};

Function new_designated_dealer_role

Publishes a DesignatedDealer RoleId under new_account. The creating_account must be treasury compliance.

public(friend) fun new_designated_dealer_role(creating_account: &signer, new_account: &signer)
Implementation
public(friend) fun new_designated_dealer_role(
    creating_account: &signer,
    new_account: &signer,
) acquires RoleId {
    assert_treasury_compliance(creating_account);
    grant_role(new_account, DESIGNATED_DEALER_ROLE_ID);
}
Specification
include AbortsIfNotTreasuryCompliance{account: creating_account};
include GrantRole{addr: Signer::address_of(new_account), role_id: DESIGNATED_DEALER_ROLE_ID};

Function new_validator_role

Publish a Validator RoleId under new_account. The creating_account must be diem root.

public(friend) fun new_validator_role(creating_account: &signer, new_account: &signer)
Implementation
public(friend) fun new_validator_role(
    creating_account: &signer,
    new_account: &signer
) acquires RoleId {
    assert_diem_root(creating_account);
    grant_role(new_account, VALIDATOR_ROLE_ID);
}
Specification
include AbortsIfNotDiemRoot{account: creating_account};
include GrantRole{addr: Signer::address_of(new_account), role_id: VALIDATOR_ROLE_ID};

Function new_validator_operator_role

Publish a ValidatorOperator RoleId under new_account. The creating_account must be DiemRoot

public(friend) fun new_validator_operator_role(creating_account: &signer, new_account: &signer)
Implementation
public(friend) fun new_validator_operator_role(
    creating_account: &signer,
    new_account: &signer,
) acquires RoleId {
    assert_diem_root(creating_account);
    grant_role(new_account, VALIDATOR_OPERATOR_ROLE_ID);
}
Specification
include AbortsIfNotDiemRoot{account: creating_account};
include GrantRole{addr: Signer::address_of(new_account), role_id: VALIDATOR_OPERATOR_ROLE_ID};

Function new_parent_vasp_role

Publish a ParentVASP RoleId under new_account. The creating_account must be TreasuryCompliance

public(friend) fun new_parent_vasp_role(creating_account: &signer, new_account: &signer)
Implementation
public(friend) fun new_parent_vasp_role(
    creating_account: &signer,
    new_account: &signer,
) acquires RoleId {
    assert_treasury_compliance(creating_account);
    grant_role(new_account, PARENT_VASP_ROLE_ID);
}
Specification
include AbortsIfNotTreasuryCompliance{account: creating_account};
include GrantRole{addr: Signer::address_of(new_account), role_id: PARENT_VASP_ROLE_ID};

Function new_child_vasp_role

Publish a ChildVASP RoleId under new_account. The creating_account must be a ParentVASP

public(friend) fun new_child_vasp_role(creating_account: &signer, new_account: &signer)
Implementation
public(friend) fun new_child_vasp_role(
    creating_account: &signer,
    new_account: &signer,
) acquires RoleId {
    assert_parent_vasp_role(creating_account);
    grant_role(new_account, CHILD_VASP_ROLE_ID);
}
Specification
include AbortsIfNotParentVasp{account: creating_account};
include GrantRole{addr: Signer::address_of(new_account), role_id: CHILD_VASP_ROLE_ID};

Function grant_role

Helper function to grant a role.

fun grant_role(account: &signer, role_id: u64)
Implementation
fun grant_role(account: &signer, role_id: u64) {
    assert(!exists<RoleId>(Signer::address_of(account)), Errors::already_published(EROLE_ID));
    move_to(account, RoleId { role_id });
}
Specification
pragma opaque;
include GrantRole{addr: Signer::address_of(account)};
let addr = Signer::spec_address_of(account);
requires role_id == DIEM_ROOT_ROLE_ID ==> addr == @DiemRoot;
requires role_id == TREASURY_COMPLIANCE_ROLE_ID ==> addr == @TreasuryCompliance;

schema GrantRole {
    addr: address;
    role_id: num;
    aborts_if exists<RoleId>(addr) with Errors::ALREADY_PUBLISHED;
    ensures exists<RoleId>(addr);
    ensures global<RoleId>(addr).role_id == role_id;
    modifies global<RoleId>(addr);
}

Function has_role

fun has_role(account: &signer, role_id: u64): bool
Implementation
fun has_role(account: &signer, role_id: u64): bool acquires RoleId {
   let addr = Signer::address_of(account);
   exists<RoleId>(addr)
       && borrow_global<RoleId>(addr).role_id == role_id
}

Function has_diem_root_role

public fun has_diem_root_role(account: &signer): bool
Implementation
public fun has_diem_root_role(account: &signer): bool acquires RoleId {
    has_role(account, DIEM_ROOT_ROLE_ID)
}

Function has_treasury_compliance_role

public fun has_treasury_compliance_role(account: &signer): bool
Implementation
public fun has_treasury_compliance_role(account: &signer): bool acquires RoleId {
    has_role(account, TREASURY_COMPLIANCE_ROLE_ID)
}

Function has_designated_dealer_role

public fun has_designated_dealer_role(account: &signer): bool
Implementation
public fun has_designated_dealer_role(account: &signer): bool acquires RoleId {
    has_role(account, DESIGNATED_DEALER_ROLE_ID)
}

Function has_validator_role

public fun has_validator_role(account: &signer): bool
Implementation
public fun has_validator_role(account: &signer): bool acquires RoleId {
    has_role(account, VALIDATOR_ROLE_ID)
}

Function has_validator_operator_role

public fun has_validator_operator_role(account: &signer): bool
Implementation
public fun has_validator_operator_role(account: &signer): bool acquires RoleId {
    has_role(account, VALIDATOR_OPERATOR_ROLE_ID)
}

Function has_parent_VASP_role

public fun has_parent_VASP_role(account: &signer): bool
Implementation
public fun has_parent_VASP_role(account: &signer): bool acquires RoleId {
    has_role(account, PARENT_VASP_ROLE_ID)
}

Function has_child_VASP_role

public fun has_child_VASP_role(account: &signer): bool
Implementation
public fun has_child_VASP_role(account: &signer): bool acquires RoleId {
    has_role(account, CHILD_VASP_ROLE_ID)
}

Function get_role_id

public fun get_role_id(a: address): u64
Implementation
public fun get_role_id(a: address): u64 acquires RoleId {
    assert(exists<RoleId>(a), Errors::not_published(EROLE_ID));
    borrow_global<RoleId>(a).role_id
}

Function can_hold_balance

Return true if addr is allowed to receive and send Diem<T> for any T

public fun can_hold_balance(account: &signer): bool
Implementation
public fun can_hold_balance(account: &signer): bool acquires RoleId {
    // VASP accounts and designated_dealers can hold balances.
    // Administrative accounts (`Validator`, `ValidatorOperator`, `TreasuryCompliance`, and
    // `DiemRoot`) cannot.
    has_parent_VASP_role(account) ||
    has_child_VASP_role(account) ||
    has_designated_dealer_role(account)
}

Function assert_diem_root

Assert that the account is diem root.

public fun assert_diem_root(account: &signer)
Implementation
public fun assert_diem_root(account: &signer) acquires RoleId {
    CoreAddresses::assert_diem_root(account);
    let addr = Signer::address_of(account);
    assert(exists<RoleId>(addr), Errors::not_published(EROLE_ID));
    assert(borrow_global<RoleId>(addr).role_id == DIEM_ROOT_ROLE_ID, Errors::requires_role(EDIEM_ROOT));
}
Specification

Function assert_treasury_compliance

Assert that the account is treasury compliance.

public fun assert_treasury_compliance(account: &signer)
Implementation
public fun assert_treasury_compliance(account: &signer) acquires RoleId {
    CoreAddresses::assert_treasury_compliance(account);
    let addr = Signer::address_of(account);
    assert(exists<RoleId>(addr), Errors::not_published(EROLE_ID));
    assert(
        borrow_global<RoleId>(addr).role_id == TREASURY_COMPLIANCE_ROLE_ID,
        Errors::requires_role(ETREASURY_COMPLIANCE)
    )
}
Specification
pragma opaque;
include AbortsIfNotTreasuryCompliance;

Function assert_parent_vasp_role

Assert that the account has the parent vasp role.

public fun assert_parent_vasp_role(account: &signer)
Implementation
public fun assert_parent_vasp_role(account: &signer) acquires RoleId {
    let addr = Signer::address_of(account);
    assert(exists<RoleId>(addr), Errors::not_published(EROLE_ID));
    assert(
        borrow_global<RoleId>(addr).role_id == PARENT_VASP_ROLE_ID,
        Errors::requires_role(EPARENT_VASP)
    )
}
Specification
pragma opaque;
include AbortsIfNotParentVasp;

Function assert_child_vasp_role

Assert that the account has the child vasp role.

public fun assert_child_vasp_role(account: &signer)
Implementation
public fun assert_child_vasp_role(account: &signer) acquires RoleId {
    let addr = Signer::address_of(account);
    assert(exists<RoleId>(addr), Errors::not_published(EROLE_ID));
    assert(
        borrow_global<RoleId>(addr).role_id == CHILD_VASP_ROLE_ID,
        Errors::requires_role(ECHILD_VASP)
    )
}
Specification
pragma opaque;
include AbortsIfNotChildVasp{account: Signer::address_of(account)};

Function assert_designated_dealer

Assert that the account has the designated dealer role.

public fun assert_designated_dealer(account: &signer)
Implementation
public fun assert_designated_dealer(account: &signer) acquires RoleId {
    let addr = Signer::address_of(account);
    assert(exists<RoleId>(addr), Errors::not_published(EROLE_ID));
    assert(
        borrow_global<RoleId>(addr).role_id == DESIGNATED_DEALER_ROLE_ID,
        Errors::requires_role(EDESIGNATED_DEALER)
    )
}
Specification
pragma opaque;
include AbortsIfNotDesignatedDealer;

Function assert_validator

Assert that the account has the validator role.

public fun assert_validator(validator_account: &signer)
Implementation
public fun assert_validator(validator_account: &signer) acquires RoleId {
    let validator_addr = Signer::address_of(validator_account);
    assert(exists<RoleId>(validator_addr), Errors::not_published(EROLE_ID));
    assert(
        borrow_global<RoleId>(validator_addr).role_id == VALIDATOR_ROLE_ID,
        Errors::requires_role(EVALIDATOR)
    )
}
Specification
pragma opaque;
include AbortsIfNotValidator{account: validator_account};

Function assert_validator_operator

Assert that the account has the validator operator role.

public fun assert_validator_operator(validator_operator_account: &signer)
Implementation
public fun assert_validator_operator(validator_operator_account: &signer) acquires RoleId {
    let validator_operator_addr = Signer::address_of(validator_operator_account);
    assert(exists<RoleId>(validator_operator_addr), Errors::not_published(EROLE_ID));
    assert(
        borrow_global<RoleId>(validator_operator_addr).role_id == VALIDATOR_OPERATOR_ROLE_ID,
        Errors::requires_role(EVALIDATOR_OPERATOR)
    )
}
Specification
pragma opaque;
include AbortsIfNotValidatorOperator{account: validator_operator_account};

Function assert_parent_vasp_or_designated_dealer

Assert that the account has either the parent vasp or designated dealer role.

public fun assert_parent_vasp_or_designated_dealer(account: &signer)
Implementation
public fun assert_parent_vasp_or_designated_dealer(account: &signer) acquires RoleId {
    let addr = Signer::address_of(account);
    assert(exists<RoleId>(addr), Errors::not_published(EROLE_ID));
    let role_id = borrow_global<RoleId>(addr).role_id;
    assert(
        role_id == PARENT_VASP_ROLE_ID || role_id == DESIGNATED_DEALER_ROLE_ID,
        Errors::requires_role(EPARENT_VASP_OR_DESIGNATED_DEALER)
    );
}
Specification

Function assert_parent_vasp_or_child_vasp

public fun assert_parent_vasp_or_child_vasp(account: &signer)
Implementation
public fun assert_parent_vasp_or_child_vasp(account: &signer) acquires RoleId {
    let addr = Signer::address_of(account);
    assert(exists<RoleId>(addr), Errors::not_published(EROLE_ID));
    let role_id = borrow_global<RoleId>(addr).role_id;
    assert(
        role_id == PARENT_VASP_ROLE_ID || role_id == CHILD_VASP_ROLE_ID,
        Errors::requires_role(EPARENT_VASP_OR_CHILD_VASP)
    );
}
Specification
pragma opaque;
include AbortsIfNotParentVaspOrChildVasp;

Module Specification

Persistence of Roles

Once an account at an address is granted a role it will remain an account role for all time.

invariant update
    forall addr: address where old(exists<RoleId>(addr)):
        exists<RoleId>(addr) && old(global<RoleId>(addr).role_id) == global<RoleId>(addr).role_id;

Access Control

In this section, the conditions from the requirements for access control are systematically applied to the functions in this module. While some of those conditions have already been included in individual function specifications, listing them here again gives additional assurance that that all requirements are covered.

The DiemRoot role is only granted in genesis [A1]. A new RoleId with DIEM_ROOT_ROLE_ID is only published through grant_diem_root_role which aborts if it is not invoked in genesis.

apply ThisRoleIsNotNewlyPublished{this: DIEM_ROOT_ROLE_ID} to * except grant_diem_root_role, grant_role;
apply DiemTimestamp::AbortsIfNotGenesis to grant_diem_root_role;

TreasuryCompliance role is only granted in genesis [A2]. A new RoleId with TREASURY_COMPLIANCE_ROLE_ID is only published through grant_treasury_compliance_role which aborts if it is not invoked in genesis.

apply ThisRoleIsNotNewlyPublished{this: TREASURY_COMPLIANCE_ROLE_ID} to *
    except grant_treasury_compliance_role, grant_role;
apply DiemTimestamp::AbortsIfNotGenesis to grant_treasury_compliance_role;

Validator roles are only granted by DiemRoot [A3]. A new RoleId with VALIDATOR_ROLE_ID is only published through new_validator_role which aborts if creating_account does not have the DiemRoot role.

apply ThisRoleIsNotNewlyPublished{this: VALIDATOR_ROLE_ID} to * except new_validator_role, grant_role;
apply AbortsIfNotDiemRoot{account: creating_account} to new_validator_role;

ValidatorOperator roles are only granted by DiemRoot [A4]. A new RoleId with VALIDATOR_OPERATOR_ROLE_ID is only published through new_validator_operator_role which aborts if creating_account does not have the DiemRoot role.

apply ThisRoleIsNotNewlyPublished{this: VALIDATOR_OPERATOR_ROLE_ID} to *
    except new_validator_operator_role, grant_role;
apply AbortsIfNotDiemRoot{account: creating_account} to new_validator_operator_role;

DesignatedDealer roles are only granted by TreasuryCompliance [A5]. A new RoleId with DESIGNATED_DEALER_ROLE_ID() is only published through new_designated_dealer_role which aborts if creating_account does not have the TreasuryCompliance role.

apply ThisRoleIsNotNewlyPublished{this: DESIGNATED_DEALER_ROLE_ID} to *
    except new_designated_dealer_role, grant_role;
apply AbortsIfNotTreasuryCompliance{account: creating_account} to new_designated_dealer_role;

ParentVASP roles are only granted by TreasuryCompliance [A6]. A new RoleId with PARENT_VASP_ROLE_ID() is only published through new_parent_vasp_role which aborts if creating_account does not have the TreasuryCompliance role.

apply ThisRoleIsNotNewlyPublished{this: PARENT_VASP_ROLE_ID} to * except new_parent_vasp_role, grant_role;
apply AbortsIfNotTreasuryCompliance{account: creating_account} to new_parent_vasp_role;

ChildVASP roles are only granted by ParentVASP [A7]. A new RoleId with CHILD_VASP_ROLE_ID is only published through new_child_vasp_role which aborts if creating_account does not have the ParentVASP role.

apply ThisRoleIsNotNewlyPublished{this: CHILD_VASP_ROLE_ID} to * except new_child_vasp_role, grant_role;
apply AbortsIfNotParentVasp{account: creating_account} to new_child_vasp_role;

The DiemRoot role is globally unique [B1], and is published at DIEM_ROOT_ADDRESS [C1]. In other words, a RoleId with DIEM_ROOT_ROLE_ID uniquely exists at DIEM_ROOT_ADDRESS.

invariant forall addr: address where spec_has_diem_root_role_addr(addr): addr == @DiemRoot;
invariant [suspendable]
    DiemTimestamp::is_operating() ==> spec_has_diem_root_role_addr(@DiemRoot);

The TreasuryCompliance role is globally unique [B2], and is published at TREASURY_COMPLIANCE_ADDRESS [C2]. In other words, a RoleId with TREASURY_COMPLIANCE_ROLE_ID uniquely exists at TREASURY_COMPLIANCE_ADDRESS.

invariant forall addr: address where spec_has_treasury_compliance_role_addr(addr):
  addr == @TreasuryCompliance;
invariant [suspendable]
    DiemTimestamp::is_operating() ==> spec_has_treasury_compliance_role_addr(@TreasuryCompliance);

DiemRoot cannot have balances [D1].

invariant forall addr: address
    where spec_has_diem_root_role_addr(addr): !spec_can_hold_balance_addr(addr);

TreasuryCompliance cannot have balances [D2].

invariant forall addr: address
    where spec_has_treasury_compliance_role_addr(addr): !spec_can_hold_balance_addr(addr);

Validator cannot have balances [D3].

invariant forall addr: address
    where spec_has_validator_role_addr(addr): !spec_can_hold_balance_addr(addr);

ValidatorOperator cannot have balances [D4].

invariant forall addr: address
    where spec_has_validator_operator_role_addr(addr): !spec_can_hold_balance_addr(addr);

DesignatedDealer have balances [D5].

invariant forall addr: address
    where spec_has_designated_dealer_role_addr(addr): spec_can_hold_balance_addr(addr);

ParentVASP have balances [D6].

invariant forall addr: address
    where spec_has_parent_VASP_role_addr(addr): spec_can_hold_balance_addr(addr);

ChildVASP have balances [D7].

invariant forall addr: address
    where spec_has_child_VASP_role_addr(addr): spec_can_hold_balance_addr(addr);

Helper Functions and Schemas

schema ThisRoleIsNotNewlyPublished {
    this: u64;
    ensures forall addr: address where exists<RoleId>(addr) && global<RoleId>(addr).role_id == this:
        old(exists<RoleId>(addr)) && old(global<RoleId>(addr).role_id) == this;
}

schema AbortsIfNotDiemRoot {
    account: signer;
    include CoreAddresses::AbortsIfNotDiemRoot;
    let addr = Signer::spec_address_of(account);
    aborts_if !exists<RoleId>(addr) with Errors::NOT_PUBLISHED;
    aborts_if global<RoleId>(addr).role_id != DIEM_ROOT_ROLE_ID with Errors::REQUIRES_ROLE;
}

schema AbortsIfNotTreasuryCompliance {
    account: signer;
    include CoreAddresses::AbortsIfNotTreasuryCompliance;
    let addr = Signer::spec_address_of(account);
    aborts_if !exists<RoleId>(addr) with Errors::NOT_PUBLISHED;
    aborts_if global<RoleId>(addr).role_id != TREASURY_COMPLIANCE_ROLE_ID with Errors::REQUIRES_ROLE;
}

schema AbortsIfNotParentVasp {
    account: signer;
    let addr = Signer::spec_address_of(account);
    aborts_if !exists<RoleId>(addr) with Errors::NOT_PUBLISHED;
    aborts_if global<RoleId>(addr).role_id != PARENT_VASP_ROLE_ID with Errors::REQUIRES_ROLE;
}

schema AbortsIfNotChildVasp {
    account: address;
    aborts_if !exists<RoleId>(account) with Errors::NOT_PUBLISHED;
    aborts_if global<RoleId>(account).role_id != CHILD_VASP_ROLE_ID with Errors::REQUIRES_ROLE;
}

schema AbortsIfNotDesignatedDealer {
    account: signer;
    let addr = Signer::spec_address_of(account);
    aborts_if !exists<RoleId>(addr) with Errors::NOT_PUBLISHED;
    aborts_if global<RoleId>(addr).role_id != DESIGNATED_DEALER_ROLE_ID with Errors::REQUIRES_ROLE;
}

schema AbortsIfNotParentVaspOrDesignatedDealer {
    account: signer;
    let addr = Signer::spec_address_of(account);
    aborts_if !exists<RoleId>(addr) with Errors::NOT_PUBLISHED;
    let role_id = global<RoleId>(addr).role_id;
    aborts_if role_id != PARENT_VASP_ROLE_ID && role_id != DESIGNATED_DEALER_ROLE_ID
        with Errors::REQUIRES_ROLE;
}

schema AbortsIfNotParentVaspOrChildVasp {
    account: signer;
    let addr = Signer::spec_address_of(account);
    aborts_if !exists<RoleId>(addr) with Errors::NOT_PUBLISHED;
    let role_id = global<RoleId>(addr).role_id;
    aborts_if role_id != PARENT_VASP_ROLE_ID && role_id != CHILD_VASP_ROLE_ID
        with Errors::REQUIRES_ROLE;
}

schema AbortsIfNotValidator {
    account: signer;
    let addr = Signer::spec_address_of(account);
    aborts_if !exists<RoleId>(addr) with Errors::NOT_PUBLISHED;
    aborts_if global<RoleId>(addr).role_id != VALIDATOR_ROLE_ID with Errors::REQUIRES_ROLE;
}

schema AbortsIfNotValidatorOperator {
    account: signer;
    let addr = Signer::spec_address_of(account);
    aborts_if !exists<RoleId>(addr) with Errors::NOT_PUBLISHED;
    aborts_if global<RoleId>(addr).role_id != VALIDATOR_OPERATOR_ROLE_ID
        with Errors::REQUIRES_ROLE;
}