1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
use super::{
    errors::Error,
    events::{RoleAdminChanged, RoleGranted, RoleRevoked}
};
use odra::{
    contract_env,
    types::{event::OdraEvent, Address},
    Mapping
};

pub type Role = [u8; 32];

pub const DEFAULT_ADMIN_ROLE: Role = [0u8; 32];

/// This contract module enables the implementation of role-based access control mechanisms for children
/// modules.
///
/// Roles are identified by their 32-bytes identifier, which should be unique and exposed in the external API.
///
/// Roles can be used to represent a set of permissions, and the hasRole function is used to restrict
/// access to a function call.
///
/// Roles can be granted and revoked dynamically using the [`grant_role()`](AccessControl::grant_role) and
/// [`revoke_role()`](AccessControl::revoke_role) functions,
/// respectively. Each role has an associated admin role, and only accounts that have the role's admin role
/// can call grant_role and revoke_role.
///
/// By default, the admin role for all roles is [`DEFAULT_ADMIN_ROLE`], which means that only accounts with
/// this role can grant or revoke other roles.
///
/// More complex role relationships can be established using the [set_admin_role()](AccessControl::set_admin_role) function.
#[odra::module(events = [RoleAdminChanged, RoleGranted, RoleRevoked])]
pub struct AccessControl {
    roles: Mapping<Role, Mapping<Address, bool>>,
    role_admin: Mapping<Role, Role>
}

#[odra::module]
impl AccessControl {
    /// Returns true if account has been granted `role`.
    pub fn has_role(&self, role: &Role, address: &Address) -> bool {
        self.roles.get_instance(role).get_or_default(address)
    }

    /// Returns the admin role that controls `role`.
    ///
    /// The admin role may be changed using [set_admin_role()](AccessControl::set_admin_role).
    pub fn get_role_admin(&self, role: &Role) -> Role {
        let admin_role = self.role_admin.get(role);
        if let Some(admin) = admin_role {
            admin
        } else {
            DEFAULT_ADMIN_ROLE
        }
    }

    /// Grants `role` to `address`.
    ///
    /// If the role has been already granted - nothing happens,
    /// otherwise [`RoleGranted`] event is emitted.
    ///
    /// The caller must have `role`'s admin role.
    pub fn grant_role(&mut self, role: &Role, address: &Address) {
        self.check_role(&self.get_role_admin(role), &contract_env::caller());
        self.unchecked_grant_role(role, address);
    }

    /// Grants `role` to `address`.
    ///
    /// If the role has been already revoked - nothing happens,
    /// otherwise [`RoleRevoked`] event is emitted.
    ///
    /// The caller must have `role`'s admin role.
    pub fn revoke_role(&mut self, role: &Role, address: &Address) {
        self.check_role(&self.get_role_admin(role), &contract_env::caller());
        self.unchecked_revoke_role(role, address);
    }

    /// The function is used to remove a role from the account that initiated the call.
    ///
    /// One common way of managing roles is by using [`grant_role()`](Self::grant_role())
    /// and [`revoke_role()`](Self::revoke_role()).
    /// The purpose of revokeRole is to provide a mechanism for revoking privileges from an account
    /// in case it gets compromised.
    ///
    /// If the account had previously been granted the role, the function will trigger a `RoleRevoked` event.
    ///
    /// Note that only `address` is authorized to call this function.
    pub fn renounce_role(&mut self, role: &Role, address: &Address) {
        if address != &contract_env::caller() {
            contract_env::revert(Error::RoleRenounceForAnotherAddress);
        }
        self.unchecked_revoke_role(role, address);
    }
}

impl AccessControl {
    /// Ensures `address` has `role`. If not, reverts with [Error::MissingRole].
    pub fn check_role(&self, role: &Role, address: &Address) {
        if !self.has_role(role, address) {
            contract_env::revert(Error::MissingRole);
        }
    }

    /// Sets `admin_role` as `role`'s admin role.
    ///
    /// Emits a `RoleAdminChanged` event.
    pub fn set_admin_role(&mut self, role: &Role, admin_role: &Role) {
        let previous_admin_role = self.get_role_admin(role);
        self.role_admin.set(role, *admin_role);
        RoleAdminChanged {
            role: *role,
            previous_admin_role,
            new_admin_role: *admin_role
        }
        .emit();
    }

    /// Grants `role` to `address`.
    ///
    /// Internal function without access restriction.
    /// This function should be used to setup the initial access control.
    ///
    /// May emit a `RoleGranted` event.
    pub fn unchecked_grant_role(&mut self, role: &Role, address: &Address) {
        if !self.has_role(role, address) {
            self.roles.get_instance(role).set(address, true);
            RoleGranted {
                role: *role,
                address: *address,
                sender: contract_env::caller()
            }
            .emit();
        }
    }

    /// Revokes `role` from `address`.
    ///
    /// Internal function without access restriction.
    /// This function should be used to setup the initial access control.
    ///
    /// May emit a `RoleRevoked` event.
    pub fn unchecked_revoke_role(&mut self, role: &Role, address: &Address) {
        if self.has_role(role, address) {
            self.roles.get_instance(role).set(address, false);
            RoleRevoked {
                role: *role,
                address: *address,
                sender: contract_env::caller()
            }
            .emit();
        }
    }
}