1use alloy::primitives::{Address, B256};
10
11use crate::{
12 error::Result,
13 storage::Handler,
14 tip20::{IRolesAuth, RolesAuthError, RolesAuthEvent, TIP20Token},
15};
16
17pub const DEFAULT_ADMIN_ROLE: B256 = B256::ZERO;
19pub const UNGRANTABLE_ROLE: B256 = B256::new([0xff; 32]);
21
22impl TIP20Token {
23 pub fn initialize_roles(&mut self) -> Result<()> {
25 self.set_role_admin_internal(UNGRANTABLE_ROLE, UNGRANTABLE_ROLE)
26 }
27
28 pub fn grant_default_admin(&mut self, msg_sender: Address, admin: Address) -> Result<()> {
30 self.grant_role_internal(admin, DEFAULT_ADMIN_ROLE)?;
31
32 self.emit_event(RolesAuthEvent::RoleMembershipUpdated(
33 IRolesAuth::RoleMembershipUpdated {
34 role: DEFAULT_ADMIN_ROLE,
35 account: admin,
36 sender: msg_sender,
37 hasRole: true,
38 },
39 ))
40 }
41
42 pub fn has_role(&self, call: IRolesAuth::hasRoleCall) -> Result<bool> {
44 self.has_role_internal(call.account, call.role)
45 }
46
47 pub fn get_role_admin(&self, call: IRolesAuth::getRoleAdminCall) -> Result<B256> {
49 self.get_role_admin_internal(call.role)
50 }
51
52 pub fn grant_role(
57 &mut self,
58 msg_sender: Address,
59 call: IRolesAuth::grantRoleCall,
60 ) -> Result<()> {
61 let admin_role = self.get_role_admin_internal(call.role)?;
62 self.check_role_internal(msg_sender, admin_role)?;
63 self.grant_role_internal(call.account, call.role)?;
64
65 self.emit_event(RolesAuthEvent::RoleMembershipUpdated(
66 IRolesAuth::RoleMembershipUpdated {
67 role: call.role,
68 account: call.account,
69 sender: msg_sender,
70 hasRole: true,
71 },
72 ))
73 }
74
75 pub fn revoke_role(
80 &mut self,
81 msg_sender: Address,
82 call: IRolesAuth::revokeRoleCall,
83 ) -> Result<()> {
84 let admin_role = self.get_role_admin_internal(call.role)?;
85 self.check_role_internal(msg_sender, admin_role)?;
86 self.revoke_role_internal(call.account, call.role)?;
87
88 self.emit_event(RolesAuthEvent::RoleMembershipUpdated(
89 IRolesAuth::RoleMembershipUpdated {
90 role: call.role,
91 account: call.account,
92 sender: msg_sender,
93 hasRole: false,
94 },
95 ))
96 }
97
98 pub fn renounce_role(
103 &mut self,
104 msg_sender: Address,
105 call: IRolesAuth::renounceRoleCall,
106 ) -> Result<()> {
107 self.check_role_internal(msg_sender, call.role)?;
108 self.revoke_role_internal(msg_sender, call.role)?;
109
110 self.emit_event(RolesAuthEvent::RoleMembershipUpdated(
111 IRolesAuth::RoleMembershipUpdated {
112 role: call.role,
113 account: msg_sender,
114 sender: msg_sender,
115 hasRole: false,
116 },
117 ))
118 }
119
120 pub fn set_role_admin(
125 &mut self,
126 msg_sender: Address,
127 call: IRolesAuth::setRoleAdminCall,
128 ) -> Result<()> {
129 let current_admin_role = self.get_role_admin_internal(call.role)?;
130 self.check_role_internal(msg_sender, current_admin_role)?;
131
132 self.set_role_admin_internal(call.role, call.adminRole)?;
133
134 self.emit_event(RolesAuthEvent::RoleAdminUpdated(
135 IRolesAuth::RoleAdminUpdated {
136 role: call.role,
137 newAdminRole: call.adminRole,
138 sender: msg_sender,
139 },
140 ))
141 }
142
143 pub fn check_role(&self, account: Address, role: B256) -> Result<()> {
148 self.check_role_internal(account, role)
149 }
150
151 pub fn has_role_internal(&self, account: Address, role: B256) -> Result<bool> {
153 self.roles[account][role].read()
154 }
155
156 pub fn grant_role_internal(&mut self, account: Address, role: B256) -> Result<()> {
158 self.roles[account][role].write(true)
159 }
160
161 fn revoke_role_internal(&mut self, account: Address, role: B256) -> Result<()> {
162 self.roles[account][role].write(false)
163 }
164
165 fn get_role_admin_internal(&self, role: B256) -> Result<B256> {
167 self.role_admins[role].read()
168 }
169
170 fn set_role_admin_internal(&mut self, role: B256, admin_role: B256) -> Result<()> {
171 self.role_admins[role].write(admin_role)
172 }
173
174 fn check_role_internal(&self, account: Address, role: B256) -> Result<()> {
175 if !self.has_role_internal(account, role)? {
176 return Err(RolesAuthError::unauthorized().into());
177 }
178 Ok(())
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use alloy::primitives::keccak256;
185
186 use super::*;
187 use crate::{error::TempoPrecompileError, storage::StorageCtx, test_util::TIP20Setup};
188
189 #[test]
190 fn test_role_contract_grant_and_check() -> eyre::Result<()> {
191 let mut storage = crate::storage::hashmap::HashMapStorageProvider::new(1);
192 let admin = Address::random();
193 let user = Address::random();
194 let custom_role = keccak256(b"CUSTOM_ROLE");
195
196 StorageCtx::enter(&mut storage, || {
197 let mut token = TIP20Setup::create("Test", "TST", admin).apply()?;
198
199 let has_admin = token.has_role(IRolesAuth::hasRoleCall {
201 account: admin,
202 role: DEFAULT_ADMIN_ROLE,
203 })?;
204 assert!(has_admin);
205
206 token.grant_role(
208 admin,
209 IRolesAuth::grantRoleCall {
210 role: custom_role,
211 account: user,
212 },
213 )?;
214
215 let has_custom = token.has_role(IRolesAuth::hasRoleCall {
217 account: user,
218 role: custom_role,
219 })?;
220 assert!(has_custom);
221
222 token.assert_emitted_events(vec![
224 RolesAuthEvent::RoleMembershipUpdated(IRolesAuth::RoleMembershipUpdated {
226 role: DEFAULT_ADMIN_ROLE,
227 account: admin,
228 sender: admin,
229 hasRole: true,
230 }),
231 RolesAuthEvent::RoleMembershipUpdated(IRolesAuth::RoleMembershipUpdated {
233 role: custom_role,
234 account: user,
235 sender: admin,
236 hasRole: true,
237 }),
238 ]);
239
240 Ok(())
241 })
242 }
243
244 #[test]
245 fn test_role_admin_functions() -> eyre::Result<()> {
246 let mut storage = crate::storage::hashmap::HashMapStorageProvider::new(1);
247 let admin = Address::random();
248 let custom_role = keccak256(b"CUSTOM_ROLE");
249 let admin_role = keccak256(b"ADMIN_ROLE");
250
251 StorageCtx::enter(&mut storage, || {
252 let mut token = TIP20Setup::create("Test", "TST", admin).apply()?;
253
254 token.set_role_admin(
256 admin,
257 IRolesAuth::setRoleAdminCall {
258 role: custom_role,
259 adminRole: admin_role,
260 },
261 )?;
262
263 let retrieved_admin =
265 token.get_role_admin(IRolesAuth::getRoleAdminCall { role: custom_role })?;
266 assert_eq!(retrieved_admin, admin_role);
267
268 Ok(())
269 })
270 }
271
272 #[test]
273 fn test_renounce_role() -> eyre::Result<()> {
274 let mut storage = crate::storage::hashmap::HashMapStorageProvider::new(1);
275 let admin = Address::random();
276 let user = Address::random();
277 let custom_role = keccak256(b"CUSTOM_ROLE");
278
279 StorageCtx::enter(&mut storage, || {
280 let mut token = TIP20Setup::create("Test", "TST", admin).apply()?;
281 token.grant_role_internal(user, custom_role).unwrap();
282
283 token.renounce_role(user, IRolesAuth::renounceRoleCall { role: custom_role })?;
285
286 assert!(!token.has_role_internal(user, custom_role)?);
288
289 Ok(())
290 })
291 }
292
293 #[test]
294 fn test_unauthorized_access() -> eyre::Result<()> {
295 let mut storage = crate::storage::hashmap::HashMapStorageProvider::new(1);
296 let admin = Address::random();
297 let user = Address::random();
298 let other = Address::random();
299 let custom_role = keccak256(b"CUSTOM_ROLE");
300
301 StorageCtx::enter(&mut storage, || {
302 let mut token = TIP20Setup::create("Test", "TST", admin).apply()?;
303
304 let result = token.grant_role(
306 user,
307 IRolesAuth::grantRoleCall {
308 role: custom_role,
309 account: other,
310 },
311 );
312
313 assert!(matches!(
314 result,
315 Err(TempoPrecompileError::RolesAuthError(
316 RolesAuthError::Unauthorized(IRolesAuth::Unauthorized {})
317 ))
318 ));
319
320 Ok(())
321 })
322 }
323}