1use alloy::primitives::{Address, B256};
2
3use crate::{
4 error::Result,
5 storage::Handler,
6 tip20::{IRolesAuth, RolesAuthError, RolesAuthEvent, TIP20Token},
7};
8
9pub const DEFAULT_ADMIN_ROLE: B256 = B256::ZERO;
10pub const UNGRANTABLE_ROLE: B256 = B256::new([0xff; 32]);
11
12impl TIP20Token {
13 pub fn initialize_roles(&mut self) -> Result<()> {
15 self.set_role_admin_internal(UNGRANTABLE_ROLE, UNGRANTABLE_ROLE)
16 }
17
18 pub fn grant_default_admin(&mut self, admin: Address) -> Result<()> {
20 self.grant_role_internal(admin, DEFAULT_ADMIN_ROLE)
21 }
22
23 pub fn has_role(&self, call: IRolesAuth::hasRoleCall) -> Result<bool> {
25 self.has_role_internal(call.account, call.role)
26 }
27
28 pub fn get_role_admin(&self, call: IRolesAuth::getRoleAdminCall) -> Result<B256> {
29 self.get_role_admin_internal(call.role)
30 }
31
32 pub fn grant_role(
33 &mut self,
34 msg_sender: Address,
35 call: IRolesAuth::grantRoleCall,
36 ) -> Result<()> {
37 let admin_role = self.get_role_admin_internal(call.role)?;
38 self.check_role_internal(msg_sender, admin_role)?;
39 self.grant_role_internal(call.account, call.role)?;
40
41 self.emit_event(RolesAuthEvent::RoleMembershipUpdated(
42 IRolesAuth::RoleMembershipUpdated {
43 role: call.role,
44 account: call.account,
45 sender: msg_sender,
46 hasRole: true,
47 },
48 ))
49 }
50
51 pub fn revoke_role(
52 &mut self,
53 msg_sender: Address,
54 call: IRolesAuth::revokeRoleCall,
55 ) -> Result<()> {
56 let admin_role = self.get_role_admin_internal(call.role)?;
57 self.check_role_internal(msg_sender, admin_role)?;
58 self.revoke_role_internal(call.account, call.role)?;
59
60 self.emit_event(RolesAuthEvent::RoleMembershipUpdated(
61 IRolesAuth::RoleMembershipUpdated {
62 role: call.role,
63 account: call.account,
64 sender: msg_sender,
65 hasRole: false,
66 },
67 ))
68 }
69
70 pub fn renounce_role(
71 &mut self,
72 msg_sender: Address,
73 call: IRolesAuth::renounceRoleCall,
74 ) -> Result<()> {
75 self.check_role_internal(msg_sender, call.role)?;
76 self.revoke_role_internal(msg_sender, call.role)?;
77
78 self.emit_event(RolesAuthEvent::RoleMembershipUpdated(
79 IRolesAuth::RoleMembershipUpdated {
80 role: call.role,
81 account: msg_sender,
82 sender: msg_sender,
83 hasRole: false,
84 },
85 ))
86 }
87
88 pub fn set_role_admin(
89 &mut self,
90 msg_sender: Address,
91 call: IRolesAuth::setRoleAdminCall,
92 ) -> Result<()> {
93 let current_admin_role = self.get_role_admin_internal(call.role)?;
94 self.check_role_internal(msg_sender, current_admin_role)?;
95
96 self.set_role_admin_internal(call.role, call.adminRole)?;
97
98 self.emit_event(RolesAuthEvent::RoleAdminUpdated(
99 IRolesAuth::RoleAdminUpdated {
100 role: call.role,
101 newAdminRole: call.adminRole,
102 sender: msg_sender,
103 },
104 ))
105 }
106
107 pub fn check_role(&self, account: Address, role: B256) -> Result<()> {
109 self.check_role_internal(account, role)
110 }
111
112 pub fn has_role_internal(&self, account: Address, role: B256) -> Result<bool> {
114 self.roles.at(account).at(role).read()
115 }
116
117 pub fn grant_role_internal(&mut self, account: Address, role: B256) -> Result<()> {
118 self.roles.at(account).at(role).write(true)
119 }
120
121 fn revoke_role_internal(&mut self, account: Address, role: B256) -> Result<()> {
122 self.roles.at(account).at(role).write(false)
123 }
124
125 fn get_role_admin_internal(&self, role: B256) -> Result<B256> {
127 self.role_admins.at(role).read()
128 }
129
130 fn set_role_admin_internal(&mut self, role: B256, admin_role: B256) -> Result<()> {
131 self.role_admins.at(role).write(admin_role)
132 }
133
134 fn check_role_internal(&self, account: Address, role: B256) -> Result<()> {
135 if !self.has_role_internal(account, role)? {
136 return Err(RolesAuthError::unauthorized().into());
137 }
138 Ok(())
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use alloy::primitives::keccak256;
145
146 use super::*;
147 use crate::{error::TempoPrecompileError, storage::StorageCtx, test_util::setup_storage};
148
149 #[test]
150 fn test_role_contract_grant_and_check() -> eyre::Result<()> {
151 let (mut storage, admin) = setup_storage();
152 let user = Address::random();
153 let custom_role = keccak256(b"CUSTOM_ROLE");
154 let token_id = 1;
155
156 StorageCtx::enter(&mut storage, || {
157 let mut token = TIP20Token::new(token_id);
158
159 token.initialize(
161 "name",
162 "symbol",
163 "currency",
164 Address::ZERO,
165 admin,
166 Address::ZERO,
167 )?;
168
169 let has_admin = token.has_role(IRolesAuth::hasRoleCall {
171 account: admin,
172 role: DEFAULT_ADMIN_ROLE,
173 })?;
174 assert!(has_admin);
175
176 token.grant_role(
178 admin,
179 IRolesAuth::grantRoleCall {
180 role: custom_role,
181 account: user,
182 },
183 )?;
184
185 let has_custom = token.has_role(IRolesAuth::hasRoleCall {
187 account: user,
188 role: custom_role,
189 })?;
190 assert!(has_custom);
191
192 assert_eq!(token.emitted_events().len(), 1); Ok(())
196 })
197 }
198
199 #[test]
200 fn test_role_admin_functions() -> eyre::Result<()> {
201 let (mut storage, admin) = setup_storage();
202 let custom_role = keccak256(b"CUSTOM_ROLE");
203 let admin_role = keccak256(b"ADMIN_ROLE");
204 let token_id = 1;
205
206 StorageCtx::enter(&mut storage, || {
207 let mut token = TIP20Token::new(token_id);
208 token.initialize(
210 "name",
211 "symbol",
212 "currency",
213 Address::ZERO,
214 admin,
215 Address::ZERO,
216 )?;
217
218 token.set_role_admin(
220 admin,
221 IRolesAuth::setRoleAdminCall {
222 role: custom_role,
223 adminRole: admin_role,
224 },
225 )?;
226
227 let retrieved_admin =
229 token.get_role_admin(IRolesAuth::getRoleAdminCall { role: custom_role })?;
230 assert_eq!(retrieved_admin, admin_role);
231
232 Ok(())
233 })
234 }
235
236 #[test]
237 fn test_renounce_role() -> eyre::Result<()> {
238 let (mut storage, user) = setup_storage();
239 let custom_role = keccak256(b"CUSTOM_ROLE");
240 let token_id = 1;
241
242 StorageCtx::enter(&mut storage, || {
243 let mut token = TIP20Token::new(token_id);
244 token.initialize(
245 "name",
246 "symbol",
247 "currency",
248 Address::ZERO,
249 Address::ZERO,
250 Address::ZERO,
251 )?;
252 token.grant_role_internal(user, custom_role).unwrap();
253
254 token.renounce_role(user, IRolesAuth::renounceRoleCall { role: custom_role })?;
256
257 assert!(!token.has_role_internal(user, custom_role)?);
259
260 Ok(())
261 })
262 }
263
264 #[test]
265 fn test_unauthorized_access() -> eyre::Result<()> {
266 let (mut storage, user) = setup_storage();
267 let other = Address::random();
268 let custom_role = keccak256(b"CUSTOM_ROLE");
269 let token_id = 1;
270
271 StorageCtx::enter(&mut storage, || {
272 let mut token = TIP20Token::new(token_id);
273 token.initialize(
274 "name",
275 "symbol",
276 "currency",
277 Address::ZERO,
278 Address::ZERO,
279 Address::ZERO,
280 )?;
281
282 let result = token.grant_role(
284 user,
285 IRolesAuth::grantRoleCall {
286 role: custom_role,
287 account: other,
288 },
289 );
290
291 assert!(matches!(
292 result,
293 Err(TempoPrecompileError::RolesAuthError(
294 RolesAuthError::Unauthorized(IRolesAuth::Unauthorized {})
295 ))
296 ));
297
298 Ok(())
299 })
300 }
301}