tempo_precompiles/tip20/
roles.rs

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    /// Initialize the UNGRANTABLE_ROLE to be self-administered
14    pub fn initialize_roles(&mut self) -> Result<()> {
15        self.set_role_admin_internal(UNGRANTABLE_ROLE, UNGRANTABLE_ROLE)
16    }
17
18    /// Grant the default admin role to an account
19    pub fn grant_default_admin(&mut self, admin: Address) -> Result<()> {
20        self.grant_role_internal(admin, DEFAULT_ADMIN_ROLE)
21    }
22
23    // Public functions that handle calldata and emit events
24    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    // Utility functions for checking roles without calldata
108    pub fn check_role(&self, account: Address, role: B256) -> Result<()> {
109        self.check_role_internal(account, role)
110    }
111
112    // Internal implementation functions
113    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    /// If sloads 0, will be equal to DEFAULT_ADMIN_ROLE
126    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            // Initialize and grant admin
160            token.initialize(
161                "name",
162                "symbol",
163                "currency",
164                Address::ZERO,
165                admin,
166                Address::ZERO,
167            )?;
168
169            // Test hasRole
170            let has_admin = token.has_role(IRolesAuth::hasRoleCall {
171                account: admin,
172                role: DEFAULT_ADMIN_ROLE,
173            })?;
174            assert!(has_admin);
175
176            // Grant custom role
177            token.grant_role(
178                admin,
179                IRolesAuth::grantRoleCall {
180                    role: custom_role,
181                    account: user,
182                },
183            )?;
184
185            // Check custom role
186            let has_custom = token.has_role(IRolesAuth::hasRoleCall {
187                account: user,
188                role: custom_role,
189            })?;
190            assert!(has_custom);
191
192            // Verify events were emitted
193            assert_eq!(token.emitted_events().len(), 1); // One grant event
194
195            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            // Initialize and grant admin
209            token.initialize(
210                "name",
211                "symbol",
212                "currency",
213                Address::ZERO,
214                admin,
215                Address::ZERO,
216            )?;
217
218            // Set custom admin for role
219            token.set_role_admin(
220                admin,
221                IRolesAuth::setRoleAdminCall {
222                    role: custom_role,
223                    adminRole: admin_role,
224                },
225            )?;
226
227            // Check role admin
228            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            // Renounce role
255            token.renounce_role(user, IRolesAuth::renounceRoleCall { role: custom_role })?;
256
257            // Check role is removed
258            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            // Try to grant role without permission
283            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}