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}