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::role_membership_updated(
33 DEFAULT_ADMIN_ROLE,
34 admin,
35 msg_sender,
36 true,
37 ))
38 }
39
40 pub fn has_role(&self, call: IRolesAuth::hasRoleCall) -> Result<bool> {
42 self.has_role_internal(call.account, call.role)
43 }
44
45 pub fn get_role_admin(&self, call: IRolesAuth::getRoleAdminCall) -> Result<B256> {
47 self.get_role_admin_internal(call.role)
48 }
49
50 pub fn grant_role(
55 &mut self,
56 msg_sender: Address,
57 call: IRolesAuth::grantRoleCall,
58 ) -> Result<()> {
59 let admin_role = self.get_role_admin_internal(call.role)?;
60 self.check_role_internal(msg_sender, admin_role)?;
61 self.grant_role_internal(call.account, call.role)?;
62
63 self.emit_event(RolesAuthEvent::role_membership_updated(
64 call.role,
65 call.account,
66 msg_sender,
67 true,
68 ))
69 }
70
71 pub fn revoke_role(
76 &mut self,
77 msg_sender: Address,
78 call: IRolesAuth::revokeRoleCall,
79 ) -> Result<()> {
80 let admin_role = self.get_role_admin_internal(call.role)?;
81 self.check_role_internal(msg_sender, admin_role)?;
82 self.revoke_role_internal(call.account, call.role)?;
83
84 self.emit_event(RolesAuthEvent::role_membership_updated(
85 call.role,
86 call.account,
87 msg_sender,
88 false,
89 ))
90 }
91
92 pub fn renounce_role(
97 &mut self,
98 msg_sender: Address,
99 call: IRolesAuth::renounceRoleCall,
100 ) -> Result<()> {
101 self.check_role_internal(msg_sender, call.role)?;
102 self.revoke_role_internal(msg_sender, call.role)?;
103
104 self.emit_event(RolesAuthEvent::role_membership_updated(
105 call.role, msg_sender, msg_sender, false,
106 ))
107 }
108
109 pub fn set_role_admin(
114 &mut self,
115 msg_sender: Address,
116 call: IRolesAuth::setRoleAdminCall,
117 ) -> Result<()> {
118 let current_admin_role = self.get_role_admin_internal(call.role)?;
119 self.check_role_internal(msg_sender, current_admin_role)?;
120
121 self.set_role_admin_internal(call.role, call.adminRole)?;
122
123 self.emit_event(RolesAuthEvent::role_admin_updated(
124 call.role,
125 call.adminRole,
126 msg_sender,
127 ))
128 }
129
130 pub fn check_role(&self, account: Address, role: B256) -> Result<()> {
135 self.check_role_internal(account, role)
136 }
137
138 pub fn has_role_internal(&self, account: Address, role: B256) -> Result<bool> {
140 self.roles[account][role].read()
141 }
142
143 pub fn grant_role_internal(&mut self, account: Address, role: B256) -> Result<()> {
145 self.roles[account][role].write(true)
146 }
147
148 fn revoke_role_internal(&mut self, account: Address, role: B256) -> Result<()> {
149 self.roles[account][role].write(false)
150 }
151
152 fn get_role_admin_internal(&self, role: B256) -> Result<B256> {
154 self.role_admins[role].read()
155 }
156
157 fn set_role_admin_internal(&mut self, role: B256, admin_role: B256) -> Result<()> {
158 self.role_admins[role].write(admin_role)
159 }
160
161 fn check_role_internal(&self, account: Address, role: B256) -> Result<()> {
162 if !self.has_role_internal(account, role)? {
163 return Err(RolesAuthError::unauthorized().into());
164 }
165 Ok(())
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use alloy::primitives::keccak256;
172
173 use super::*;
174 use crate::{error::TempoPrecompileError, storage::StorageCtx, test_util::TIP20Setup};
175
176 #[test]
177 fn test_role_contract_grant_and_check() -> eyre::Result<()> {
178 let mut storage = crate::storage::hashmap::HashMapStorageProvider::new(1);
179 let admin = Address::random();
180 let user = Address::random();
181 let custom_role = keccak256(b"CUSTOM_ROLE");
182
183 StorageCtx::enter(&mut storage, || {
184 let mut token = TIP20Setup::create("Test", "TST", admin).apply()?;
185
186 let has_admin = token.has_role(IRolesAuth::hasRoleCall {
188 account: admin,
189 role: DEFAULT_ADMIN_ROLE,
190 })?;
191 assert!(has_admin);
192
193 token.grant_role(
195 admin,
196 IRolesAuth::grantRoleCall {
197 role: custom_role,
198 account: user,
199 },
200 )?;
201
202 let has_custom = token.has_role(IRolesAuth::hasRoleCall {
204 account: user,
205 role: custom_role,
206 })?;
207 assert!(has_custom);
208
209 token.assert_emitted_events(vec![
211 RolesAuthEvent::role_membership_updated(DEFAULT_ADMIN_ROLE, admin, admin, true),
213 RolesAuthEvent::role_membership_updated(custom_role, user, admin, true),
215 ]);
216
217 Ok(())
218 })
219 }
220
221 #[test]
222 fn test_role_admin_functions() -> eyre::Result<()> {
223 let mut storage = crate::storage::hashmap::HashMapStorageProvider::new(1);
224 let admin = Address::random();
225 let custom_role = keccak256(b"CUSTOM_ROLE");
226 let admin_role = keccak256(b"ADMIN_ROLE");
227
228 StorageCtx::enter(&mut storage, || {
229 let mut token = TIP20Setup::create("Test", "TST", admin).apply()?;
230
231 token.set_role_admin(
233 admin,
234 IRolesAuth::setRoleAdminCall {
235 role: custom_role,
236 adminRole: admin_role,
237 },
238 )?;
239
240 let retrieved_admin =
242 token.get_role_admin(IRolesAuth::getRoleAdminCall { role: custom_role })?;
243 assert_eq!(retrieved_admin, admin_role);
244
245 Ok(())
246 })
247 }
248
249 #[test]
250 fn test_renounce_role() -> eyre::Result<()> {
251 let mut storage = crate::storage::hashmap::HashMapStorageProvider::new(1);
252 let admin = Address::random();
253 let user = Address::random();
254 let custom_role = keccak256(b"CUSTOM_ROLE");
255
256 StorageCtx::enter(&mut storage, || {
257 let mut token = TIP20Setup::create("Test", "TST", admin).apply()?;
258 token.grant_role_internal(user, custom_role).unwrap();
259
260 token.renounce_role(user, IRolesAuth::renounceRoleCall { role: custom_role })?;
262
263 assert!(!token.has_role_internal(user, custom_role)?);
265
266 Ok(())
267 })
268 }
269
270 #[test]
271 fn test_unauthorized_access() -> eyre::Result<()> {
272 let mut storage = crate::storage::hashmap::HashMapStorageProvider::new(1);
273 let admin = Address::random();
274 let user = Address::random();
275 let other = Address::random();
276 let custom_role = keccak256(b"CUSTOM_ROLE");
277
278 StorageCtx::enter(&mut storage, || {
279 let mut token = TIP20Setup::create("Test", "TST", admin).apply()?;
280
281 let result = token.grant_role(
283 user,
284 IRolesAuth::grantRoleCall {
285 role: custom_role,
286 account: other,
287 },
288 );
289
290 assert!(matches!(
291 result,
292 Err(TempoPrecompileError::RolesAuthError(
293 RolesAuthError::Unauthorized(IRolesAuth::Unauthorized {})
294 ))
295 ));
296
297 Ok(())
298 })
299 }
300}