Skip to main content

cougr_core/standards/
access_control.rs

1use soroban_sdk::{contracttype, Address, Env, Symbol};
2
3use super::error::StandardsError;
4
5const ROLE_MEMBER_PREFIX: &str = "std_role_m";
6const ROLE_ADMIN_PREFIX: &str = "std_role_a";
7
8pub const DEFAULT_ADMIN_ROLE_NAME: &str = "DEFAULT_ADMIN_ROLE";
9
10/// Role-based access control with per-role admin delegation.
11#[derive(Clone, Debug)]
12pub struct AccessControl {
13    id: Symbol,
14}
15
16#[contracttype]
17#[derive(Clone, Debug, Eq, PartialEq)]
18pub struct RoleGrantedEvent {
19    pub role: Symbol,
20    pub account: Address,
21    pub sender: Address,
22}
23
24#[contracttype]
25#[derive(Clone, Debug, Eq, PartialEq)]
26pub struct RoleRevokedEvent {
27    pub role: Symbol,
28    pub account: Address,
29    pub sender: Address,
30}
31
32#[contracttype]
33#[derive(Clone, Debug, Eq, PartialEq)]
34pub struct RoleAdminChangedEvent {
35    pub role: Symbol,
36    pub previous_admin_role: Symbol,
37    pub new_admin_role: Symbol,
38    pub sender: Address,
39}
40
41impl AccessControl {
42    pub fn new(id: Symbol) -> Self {
43        Self { id }
44    }
45
46    pub fn initialize(
47        &self,
48        env: &Env,
49        admin: &Address,
50    ) -> Result<RoleGrantedEvent, StandardsError> {
51        let default_admin_role = self.default_admin_role(env);
52        let admin_key = self.role_admin_key(env, &default_admin_role);
53        if env.storage().persistent().has(&admin_key) {
54            return Err(StandardsError::AlreadyInitialized);
55        }
56
57        env.storage()
58            .persistent()
59            .set(&admin_key, &default_admin_role);
60        self.grant_role_unchecked(env, &default_admin_role, admin);
61
62        Ok(RoleGrantedEvent {
63            role: default_admin_role,
64            account: admin.clone(),
65            sender: admin.clone(),
66        })
67    }
68
69    pub fn default_admin_role(&self, env: &Env) -> Symbol {
70        Symbol::new(env, DEFAULT_ADMIN_ROLE_NAME)
71    }
72
73    pub fn has_role(&self, env: &Env, role: &Symbol, account: &Address) -> bool {
74        env.storage()
75            .persistent()
76            .has(&self.role_member_key(env, role, account))
77    }
78
79    pub fn require_role(
80        &self,
81        env: &Env,
82        role: &Symbol,
83        account: &Address,
84    ) -> Result<(), StandardsError> {
85        if self.has_role(env, role, account) {
86            return Ok(());
87        }
88        Err(StandardsError::Unauthorized)
89    }
90
91    pub fn role_admin(&self, env: &Env, role: &Symbol) -> Symbol {
92        env.storage()
93            .persistent()
94            .get(&self.role_admin_key(env, role))
95            .unwrap_or_else(|| self.default_admin_role(env))
96    }
97
98    pub fn grant_role(
99        &self,
100        env: &Env,
101        caller: &Address,
102        role: &Symbol,
103        account: &Address,
104    ) -> Result<RoleGrantedEvent, StandardsError> {
105        let admin_role = self.role_admin(env, role);
106        self.require_role(env, &admin_role, caller)?;
107        if self.has_role(env, role, account) {
108            return Err(StandardsError::RoleAlreadyGranted);
109        }
110
111        self.grant_role_unchecked(env, role, account);
112        Ok(RoleGrantedEvent {
113            role: role.clone(),
114            account: account.clone(),
115            sender: caller.clone(),
116        })
117    }
118
119    pub fn revoke_role(
120        &self,
121        env: &Env,
122        caller: &Address,
123        role: &Symbol,
124        account: &Address,
125    ) -> Result<RoleRevokedEvent, StandardsError> {
126        let admin_role = self.role_admin(env, role);
127        self.require_role(env, &admin_role, caller)?;
128        if !self.has_role(env, role, account) {
129            return Err(StandardsError::RoleNotGranted);
130        }
131
132        env.storage()
133            .persistent()
134            .remove(&self.role_member_key(env, role, account));
135
136        Ok(RoleRevokedEvent {
137            role: role.clone(),
138            account: account.clone(),
139            sender: caller.clone(),
140        })
141    }
142
143    pub fn renounce_role(
144        &self,
145        env: &Env,
146        role: &Symbol,
147        caller: &Address,
148    ) -> Result<RoleRevokedEvent, StandardsError> {
149        if !self.has_role(env, role, caller) {
150            return Err(StandardsError::RoleNotGranted);
151        }
152
153        env.storage()
154            .persistent()
155            .remove(&self.role_member_key(env, role, caller));
156
157        Ok(RoleRevokedEvent {
158            role: role.clone(),
159            account: caller.clone(),
160            sender: caller.clone(),
161        })
162    }
163
164    pub fn set_role_admin(
165        &self,
166        env: &Env,
167        caller: &Address,
168        role: &Symbol,
169        new_admin_role: &Symbol,
170    ) -> Result<RoleAdminChangedEvent, StandardsError> {
171        let previous_admin_role = self.role_admin(env, role);
172        self.require_role(env, &previous_admin_role, caller)?;
173
174        env.storage()
175            .persistent()
176            .set(&self.role_admin_key(env, role), new_admin_role);
177
178        Ok(RoleAdminChangedEvent {
179            role: role.clone(),
180            previous_admin_role,
181            new_admin_role: new_admin_role.clone(),
182            sender: caller.clone(),
183        })
184    }
185
186    fn grant_role_unchecked(&self, env: &Env, role: &Symbol, account: &Address) {
187        env.storage()
188            .persistent()
189            .set(&self.role_member_key(env, role, account), &true);
190    }
191
192    fn role_member_key(
193        &self,
194        env: &Env,
195        role: &Symbol,
196        account: &Address,
197    ) -> (Symbol, Symbol, Symbol, Address) {
198        (
199            Symbol::new(env, ROLE_MEMBER_PREFIX),
200            self.id.clone(),
201            role.clone(),
202            account.clone(),
203        )
204    }
205
206    fn role_admin_key(&self, env: &Env, role: &Symbol) -> (Symbol, Symbol, Symbol) {
207        (
208            Symbol::new(env, ROLE_ADMIN_PREFIX),
209            self.id.clone(),
210            role.clone(),
211        )
212    }
213}