casper_contract_sdk/contrib/
access_control.rs

1#[allow(unused_imports)]
2use crate as casper_contract_sdk; // Workaround for absolute crate path in derive CasperABI macro
3
4use casper_contract_macros::casper;
5
6use crate::{
7    casper::{self, Entity},
8    collections::{sorted_vector::SortedVector, Map},
9};
10
11/// A role is a unique identifier for a specific permission or set of permissions.
12///
13/// You can use `blake2b256` macro to generate a unique identifier for a role at compile time.
14pub type Role = [u8; 32];
15
16/// A role is a unique identifier for a specific permission or set of permissions.
17const ROLES_PREFIX: &str = "roles";
18
19/// The state of the access control contract, which contains a mapping of entities to their roles.
20#[casper(path = "crate")]
21pub struct AccessControlState {
22    roles: Map<Entity, SortedVector<Role>>,
23}
24
25impl AccessControlState {
26    /// Creates a new instance of `AccessControlState`.
27    pub fn new() -> Self {
28        Self {
29            roles: Map::new(ROLES_PREFIX),
30        }
31    }
32}
33
34impl Default for AccessControlState {
35    fn default() -> Self {
36        Self::new()
37    }
38}
39
40/// Represents the possible errors that can occur during access control operations.
41#[casper(path = "crate")]
42#[derive(PartialEq, Eq, Copy, Clone, Debug)]
43pub enum AccessControlError {
44    /// The caller is not authorized to perform the action.
45    NotAuthorized,
46}
47
48/// The AccessControl trait provides a simple role-based access control mechanism.
49/// It allows for multiple roles to be assigned to an account, and provides functions to check,
50/// grant, and revoke roles.
51/// It also provides functions to check if the caller has a specific role or any of a set of roles.
52///
53/// The roles are stored in a `Map` where the key is the account address and the value is a
54/// `SortedVector` of roles.
55///
56/// None of these methods are turned into smart contract entry points, so they are not exposed
57/// accidentally.
58///
59/// The `AccessControl` trait is designed to be used with the `casper` macro, which generates
60/// the necessary boilerplate code for the contract.
61#[casper(path = "crate", export = true)]
62pub trait AccessControl {
63    /// The state of the contract, which contains the roles.
64    #[casper(private)]
65    fn state(&self) -> &AccessControlState;
66    /// The mutable state of the contract, which allows modifying the roles.
67    #[casper(private)]
68    fn state_mut(&mut self) -> &mut AccessControlState;
69
70    /// Checks if the given account has the specified role.
71    #[casper(private)]
72    fn has_role(&self, entity: Entity, role: Role) -> bool {
73        match self.state().roles.get(&entity) {
74            Some(roles) => roles.contains(&role),
75            None => false,
76        }
77    }
78
79    #[casper(private)]
80    fn has_any_role(&self, entity: Entity, roles: &[Role]) -> bool {
81        match self.state().roles.get(&entity) {
82            Some(roles_vec) => roles_vec.iter().any(|r| roles.contains(&r)),
83            None => false,
84        }
85    }
86
87    /// Grants a role to an account. If the account already has the role, it does nothing.
88    #[casper(private)]
89    fn grant_role(&mut self, entity: Entity, role: Role) {
90        match self.state_mut().roles.get(&entity) {
91            Some(mut roles) => {
92                if roles.contains(&role) {
93                    return;
94                }
95                roles.push(role);
96            }
97            None => {
98                let mut roles = SortedVector::new(format!(
99                    "{ROLES_PREFIX}-{:02x}{}",
100                    entity.tag(),
101                    base16::encode_lower(&entity.address())
102                ));
103                roles.push(role);
104                self.state_mut().roles.insert(&entity, &roles);
105            }
106        }
107    }
108
109    /// Revokes a role from an account. If the account does not have the role, it does nothing.
110    #[casper(private)]
111    fn revoke_role(&mut self, entity: Entity, role: Role) {
112        if let Some(mut roles) = self.state_mut().roles.get(&entity) {
113            roles.retain(|r| r != &role);
114        }
115    }
116
117    /// Checks if the caller has the specified role and reverts if not.
118    #[casper(private)]
119    fn require_role(&self, role: Role) -> Result<(), AccessControlError> {
120        let caller = casper::get_caller();
121        if !self.has_role(caller, role) {
122            // Caller does not have specified role.
123            return Err(AccessControlError::NotAuthorized);
124        }
125        Ok(())
126    }
127
128    /// Checks if the caller has any of the specified roles and reverts if not.
129    #[casper(private)]
130    fn require_any_role(&self, roles: &[Role]) -> Result<(), AccessControlError> {
131        let caller = casper::get_caller();
132        if !self.has_any_role(caller, roles) {
133            // Caller does not have any of the specified roles.
134            return Err(AccessControlError::NotAuthorized);
135        }
136        Ok(())
137    }
138}